mirror of
https://gitee.com/xiaonuobase/snowy.git
synced 2026-10-06 06:52:58 +08:00
【更新】修改两个首页及使用过程中一些常见的细节改进
This commit is contained in:
@@ -61,5 +61,9 @@ export default {
|
||||
// 获取基础工具数据
|
||||
indexToolDataCount(data) {
|
||||
return request('toolDataCount', data, 'get')
|
||||
},
|
||||
// 获取系统运行信息
|
||||
indexSysInfo(data) {
|
||||
return request('sysInfo', data, 'get')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :loading="miniMessageLoading">
|
||||
<template #extra><a @click="leaveFor('/usercenter')">更多</a></template>
|
||||
<a-card :title="title" :bordered="false" :loading="miniMessageLoading" class="mini-message-card">
|
||||
<template #extra>
|
||||
<a-button type="link" size="small" @click="leaveFor('/usercenter')">更多</a-button>
|
||||
</template>
|
||||
<div class="index-message-list">
|
||||
<a-list :data-source="messageList" size="small" :loading="miniMessageLoading">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item-meta :description="item.createTime">
|
||||
<a-list-item class="message-item">
|
||||
<a-list-item-meta>
|
||||
<template #title>
|
||||
<a @click="messageDetail(item)">{{ item.subject }}</a>
|
||||
<div class="message-header">
|
||||
<a class="message-subject" @click="messageDetail(item)">{{ item.subject }}</a>
|
||||
<span class="message-time">{{ item.createTime }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
@@ -17,13 +22,13 @@
|
||||
<xn-form-container title="详情" :width="700" :visible="visible" :destroy-on-close="true" @close="onClose">
|
||||
<a-form ref="formRef" :model="formData" layout="vertical">
|
||||
<a-form-item label="主题:" name="subject">
|
||||
<span>{{ formData.subject }}</span>
|
||||
<span class="detail-text">{{ formData.subject }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="发送时间:" name="createTime">
|
||||
<span>{{ formData.createTime }}</span>
|
||||
<span class="detail-text">{{ formData.createTime }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="内容:" name="content">
|
||||
<span>{{ formData.content }}</span>
|
||||
<span class="detail-text">{{ formData.content }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="查收情况:" name="receiveInfoList">
|
||||
<s-table
|
||||
@@ -37,8 +42,9 @@
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'read'">
|
||||
<span v-if="record.read" class="xn-color-d9d9d9">已读</span>
|
||||
<span v-else class="xn-color-ff4d4f">未读</span>
|
||||
<a-tag :color="record.read ? 'default' : 'error'">
|
||||
{{ record.read ? '已读' : '未读' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</s-table>
|
||||
@@ -50,7 +56,7 @@
|
||||
|
||||
<script setup name="miniMessage">
|
||||
import indexApi from '@/api/sys/indexApi'
|
||||
import { onMounted } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import router from '@/router'
|
||||
const miniMessageLoading = ref(false)
|
||||
const messageList = ref([])
|
||||
@@ -98,8 +104,8 @@
|
||||
// 以下部分是抽屉的
|
||||
const visible = ref(false)
|
||||
const formRef = ref()
|
||||
const receiveInfoList = ref([])
|
||||
const formData = ref({})
|
||||
const receiveInfoList = ref([])
|
||||
const tableRef = ref()
|
||||
const columns = [
|
||||
{
|
||||
@@ -107,23 +113,74 @@
|
||||
dataIndex: 'receiveUserName'
|
||||
},
|
||||
{
|
||||
title: '是否已读',
|
||||
title: '是否查收',
|
||||
dataIndex: 'read'
|
||||
}
|
||||
]
|
||||
// 关闭抽屉
|
||||
const onClose = () => {
|
||||
visible.value = false
|
||||
formData.value = {}
|
||||
receiveInfoList.value = []
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.index-message-list {
|
||||
overflow: auto;
|
||||
}
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.index-message-list {
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: 0 5px;
|
||||
/* 自定义滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #e8e8e8;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
.message-item {
|
||||
padding: 12px 0 !important;
|
||||
border-bottom: 1px dashed #f0f0f0 !important;
|
||||
&:last-child {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
}
|
||||
.message-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.message-subject {
|
||||
font-size: 14px;
|
||||
color: #262626;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
margin-right: 12px;
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
:deep(.snowy-theme-dark) & {
|
||||
.message-subject {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
.message-item {
|
||||
border-bottom-color: #303030 !important;
|
||||
}
|
||||
.index-message-list {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
.message-time {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.detail-text {
|
||||
color: #595959;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,44 +1,43 @@
|
||||
<template>
|
||||
<a-card :bordered="false" :title="title" :loading="apiLoading">
|
||||
<template #extra><a @click="leaveFor('/biz/notice')">更多</a></template>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
size="small"
|
||||
:pagination="false"
|
||||
:showHeader="false"
|
||||
:scroll="{ y: 260 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'title'">
|
||||
<a-popover>
|
||||
<template #content>
|
||||
<a-flex>
|
||||
<img v-if="record.image" :src="record.image" style="width: 100px; height: 70px" />
|
||||
<div style="padding-left: 10px; width: 300px; max-height: 100px; text-overflow: ellipsis">
|
||||
{{ record.digest }}
|
||||
<div style="float: right; padding-right: 10px">
|
||||
<a-button type="primary" size="small" @click="detailRef.onOpen(record.id)">详情</a-button>
|
||||
<a-card :bordered="false" :title="title" :loading="apiLoading" class="notice-card">
|
||||
<template #extra>
|
||||
<a-button type="link" size="small" @click="leaveFor('/biz/notice')">更多</a-button>
|
||||
</template>
|
||||
<div class="notice-list">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
size="small"
|
||||
:pagination="false"
|
||||
:showHeader="false"
|
||||
class="notice-table"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'title'">
|
||||
<a-popover>
|
||||
<template #content>
|
||||
<div class="popover-content">
|
||||
<img v-if="record.image" :src="record.image" class="notice-img" />
|
||||
<div class="notice-detail">
|
||||
<p class="notice-digest">{{ record.digest }}</p>
|
||||
<div class="notice-footer">
|
||||
<a-button type="primary" size="small" @click="detailRef.onOpen(record.id)">详情</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-flex>
|
||||
</template>
|
||||
<div v-if="record.type === 'NOTICE'">
|
||||
<a-badge color="green" />
|
||||
{{ record.title }}
|
||||
</div>
|
||||
<div v-if="record.type === 'ANNOUNCEMENT'">
|
||||
<a-badge color="blue" />
|
||||
{{ record.title }}
|
||||
</div>
|
||||
<div v-if="record.type === 'WARNING'">
|
||||
<a-badge color="orange" />
|
||||
{{ record.title }}
|
||||
</div>
|
||||
</a-popover>
|
||||
</template>
|
||||
<div class="notice-title-wrapper">
|
||||
<a-badge :color="getBadgeColor(record.type)" />
|
||||
<span class="notice-title">{{ record.title }}</span>
|
||||
</div>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'createTime'">
|
||||
<span class="notice-time">{{ $TOOL.parseTime(record.createTime, '{m}-{d}') }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-table>
|
||||
</div>
|
||||
<detail ref="detailRef" />
|
||||
</a-card>
|
||||
</template>
|
||||
@@ -47,6 +46,8 @@
|
||||
import bizIndexApi from '@/api/biz/bizIndexApi'
|
||||
import router from '@/router'
|
||||
import Detail from './detail.vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const detailRef = ref()
|
||||
const columns = [
|
||||
{
|
||||
@@ -57,12 +58,14 @@
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'createTime',
|
||||
align: 'right'
|
||||
align: 'right',
|
||||
width: 80
|
||||
}
|
||||
]
|
||||
const title = ref('通知公告')
|
||||
const dataSource = ref([])
|
||||
const apiLoading = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
apiLoading.value = true
|
||||
bizIndexApi
|
||||
@@ -75,14 +78,93 @@
|
||||
apiLoading.value = false
|
||||
})
|
||||
})
|
||||
|
||||
const leaveFor = (url = '/') => {
|
||||
router.replace({
|
||||
path: url
|
||||
})
|
||||
}
|
||||
|
||||
const getBadgeColor = (type) => {
|
||||
const colors = {
|
||||
NOTICE: 'green',
|
||||
ANNOUNCEMENT: 'blue',
|
||||
WARNING: 'orange'
|
||||
}
|
||||
return colors[type] || 'blue'
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.notice-list {
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: 0 5px;
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #e8e8e8;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
.notice-table {
|
||||
:deep(.ant-table-cell) {
|
||||
padding: 10px 8px !important;
|
||||
}
|
||||
}
|
||||
.notice-title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.notice-title {
|
||||
margin-left: 8px;
|
||||
color: #262626;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 200px;
|
||||
}
|
||||
:deep(.snowy-theme-dark) & {
|
||||
.notice-title {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
.notice-digest {
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
}
|
||||
.notice-time {
|
||||
color: #8c8c8c;
|
||||
font-size: 12px;
|
||||
}
|
||||
.popover-content {
|
||||
display: flex;
|
||||
width: 350px;
|
||||
}
|
||||
.notice-img {
|
||||
width: 100px;
|
||||
height: 70px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.notice-detail {
|
||||
flex: 1;
|
||||
padding-left: 12px;
|
||||
}
|
||||
.notice-digest {
|
||||
font-size: 13px;
|
||||
color: #595959;
|
||||
margin-bottom: 8px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.notice-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" class="mt-2" :loading="apiLoading">
|
||||
<a-calendar v-model:value="calendarValue" :fullscreen="false" @select="onPanelSelect" />
|
||||
<a-card :bordered="false">
|
||||
<a-timeline>
|
||||
<a-timeline-item :key="schedule.id" v-for="schedule in scheduleList">
|
||||
{{ schedule.scheduleTime }} {{ schedule.scheduleContent }}
|
||||
<a class="xn-fdr"><delete-outlined @click="deleteSchedules(schedule)" /></a>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
<div class="add-schedule" @click="addSchedule"><plus-circle-two-tone /> 新增日程</div>
|
||||
</a-card>
|
||||
<xn-form-container title="增加日程" :width="700" :visible="visible" :destroy-on-close="true" @close="onClose">
|
||||
<a-card :title="title" :bordered="false" class="schedule-card" :loading="apiLoading">
|
||||
<div class="card-body-inner">
|
||||
<div class="calendar-wrapper">
|
||||
<a-calendar v-model:value="calendarValue" :fullscreen="false" @select="onPanelSelect" />
|
||||
</div>
|
||||
<div class="schedule-list">
|
||||
<a-timeline v-if="scheduleList.length > 0">
|
||||
<a-timeline-item :key="schedule.id" v-for="schedule in scheduleList" color="blue">
|
||||
<div class="schedule-item">
|
||||
<span class="schedule-time">{{ schedule.scheduleTime }}</span>
|
||||
<span class="schedule-content">{{ schedule.scheduleContent }}</span>
|
||||
<a-button type="link" danger size="small" class="delete-btn" @click="deleteSchedules(schedule)">
|
||||
<template #icon><delete-outlined /></template>
|
||||
</a-button>
|
||||
</div>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
<a-empty v-else :image="SimpleImage" description="暂无日程" />
|
||||
</div>
|
||||
<div class="add-schedule-btn" @click="addSchedule"><plus-circle-outlined /> <span>新增日程</span></div>
|
||||
</div>
|
||||
|
||||
<xn-form-container title="新增日程" :width="400" :visible="visible" :destroy-on-close="true" @close="onClose">
|
||||
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
|
||||
<a-form-item label="时间:" name="scheduleTime">
|
||||
<a-time-picker v-model:value="scheduleTime" value-format="HH:mm:ss" />
|
||||
<a-form-item label="时间" name="scheduleTime">
|
||||
<a-time-picker v-model:value="scheduleTime" value-format="HH:mm:ss" style="width: 100%" />
|
||||
</a-form-item>
|
||||
<a-form-item label="日程描述:" name="scheduleContent">
|
||||
<a-form-item label="日程描述" name="scheduleContent">
|
||||
<a-textarea
|
||||
v-model:value="formData.scheduleContent"
|
||||
placeholder="请输入日程描述"
|
||||
@@ -25,7 +36,7 @@
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button class="xn-mr8" @click="onClose">关闭</a-button>
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button type="primary" :loading="submitLoading" @click="onSubmit">保存</a-button>
|
||||
</template>
|
||||
</xn-form-container>
|
||||
@@ -36,16 +47,23 @@
|
||||
import dayjs from 'dayjs'
|
||||
import weekday from 'dayjs/plugin/weekday'
|
||||
import localeData from 'dayjs/plugin/localeData'
|
||||
import { Empty } from 'ant-design-vue'
|
||||
dayjs.extend(weekday)
|
||||
dayjs.extend(localeData)
|
||||
|
||||
const SimpleImage = Empty.PRESENTED_IMAGE_SIMPLE
|
||||
|
||||
import { required } from '@/utils/formRules'
|
||||
import { onMounted } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import indexApi from '@/api/sys/indexApi'
|
||||
import { PlusCircleOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
const title = ref('我的日程')
|
||||
const scheduleList = ref([])
|
||||
const calendarValue = ref(dayjs())
|
||||
const apiLoading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
// 进来后执行查询
|
||||
seleScheduleList()
|
||||
@@ -92,31 +110,32 @@
|
||||
const scheduleTime = ref('09:00:00')
|
||||
const formRef = ref()
|
||||
const formData = ref({
|
||||
scheduleTime: scheduleTime.value
|
||||
scheduleTime: scheduleTime.value,
|
||||
scheduleContent: ''
|
||||
})
|
||||
const formRules = {
|
||||
scheduleTime: [required('请选择时间')],
|
||||
scheduleContent: [required('请输入日程备注')]
|
||||
}
|
||||
const submitLoading = ref(false)
|
||||
// 关闭抽屉
|
||||
|
||||
const onClose = () => {
|
||||
visible.value = false
|
||||
scheduleTime.value = '09:00:00'
|
||||
formData.value.scheduleContent = ''
|
||||
}
|
||||
// 保存
|
||||
|
||||
const onSubmit = () => {
|
||||
formRef.value.validate().then(() => {
|
||||
formData.value.scheduleTime = scheduleTime.value
|
||||
formData.value.scheduleDate = calendarValue.value.format('YYYY-MM-DD')
|
||||
submitLoading.value = true
|
||||
// 调用接口
|
||||
const params = {
|
||||
scheduleDate: calendarValue.value.format('YYYY-MM-DD'),
|
||||
scheduleTime: scheduleTime.value,
|
||||
scheduleContent: formData.value.scheduleContent
|
||||
}
|
||||
indexApi
|
||||
.indexScheduleAdd(formData.value)
|
||||
.indexScheduleAdd(params)
|
||||
.then(() => {
|
||||
seleScheduleList()
|
||||
onClose()
|
||||
seleScheduleList()
|
||||
})
|
||||
.finally(() => {
|
||||
submitLoading.value = false
|
||||
@@ -125,15 +144,95 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.add-schedule {
|
||||
cursor: pointer;
|
||||
/*margin-top: -10px;*/
|
||||
}
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0 !important;
|
||||
height: calc(100% - 57px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
:deep(.ant-timeline-item-content) {
|
||||
min-height: 10px !important;
|
||||
.schedule-card {
|
||||
height: 100%;
|
||||
}
|
||||
.card-body-inner {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.calendar-wrapper {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
:deep(.ant-picker-calendar-header) {
|
||||
padding: 8px 0 !important;
|
||||
}
|
||||
}
|
||||
.schedule-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #e8e8e8;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
.schedule-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding-right: 30px;
|
||||
&:hover {
|
||||
.delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.schedule-time {
|
||||
font-weight: 600;
|
||||
color: #1890ff;
|
||||
margin-right: 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.schedule-content {
|
||||
color: #595959;
|
||||
font-size: 13px;
|
||||
}
|
||||
:deep(.snowy-theme-dark) & {
|
||||
.calendar-wrapper {
|
||||
border-bottom-color: #303030;
|
||||
}
|
||||
.schedule-list {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #333;
|
||||
}
|
||||
}
|
||||
.schedule-content {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
}
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.add-schedule-btn {
|
||||
margin-top: 8px;
|
||||
padding: 8px;
|
||||
border: 1px dashed #d9d9d9;
|
||||
text-align: center;
|
||||
color: #8c8c8c;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
border-color: #1890ff;
|
||||
background: #f0f7ff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :loading="apiLoading">
|
||||
<a-card :title="title" :bordered="false" :loading="apiLoading" class="shortcut-card">
|
||||
<div class="card-div">
|
||||
<a-row :gutter="10">
|
||||
<a-row v-if="shortcutList && shortcutList.length > 0" :gutter="16">
|
||||
<a-col :span="6" :key="shortcut.id" v-for="shortcut in shortcutList" :xs="12" :sm="8" :md="6" :lg="8" :xl="6">
|
||||
<shortcutCard
|
||||
class="mb-3"
|
||||
:icon="shortcut.icon ? shortcut.icon : 'menu-outlined'"
|
||||
:label="shortcut.title"
|
||||
@click="leaveFor(shortcut.path)"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div v-else class="empty-state">
|
||||
<appstore-outlined class="empty-icon" />
|
||||
<p class="empty-text">暂无快捷方式</p>
|
||||
<p class="empty-hint">前往个人中心配置常用功能</p>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup name="shortcut">
|
||||
import { AppstoreOutlined } from '@ant-design/icons-vue'
|
||||
import router from '@/router'
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import ShortcutCard from '@/components/ShortcutCard/index.vue'
|
||||
import { onMounted } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
const shortcutList = ref([])
|
||||
const title = ref('快捷方式')
|
||||
const apiLoading = ref(false)
|
||||
@@ -47,15 +54,42 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ant-list-item {
|
||||
padding: 8px 0px !important;
|
||||
}
|
||||
.card-div {
|
||||
overflow: scroll;
|
||||
max-height: 260px;
|
||||
}
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.card-div {
|
||||
overflow-y: auto;
|
||||
height: 240px;
|
||||
padding: 5px;
|
||||
scrollbar-width: none;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.empty-state {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.empty-icon {
|
||||
font-size: 36px;
|
||||
color: var(--primary-color);
|
||||
opacity: 0.25;
|
||||
}
|
||||
.empty-text {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: var(--text-color-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
.empty-hint {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-color-secondary);
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
text-align: center;
|
||||
height: 180px;
|
||||
line-height: 150px;
|
||||
background: #1890ff;
|
||||
background: var(--primary-1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.ant-carousel :deep(.slick-arrow.custom-slick-arrow) {
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :loading="apiLoading">
|
||||
<a-row>
|
||||
<a-card :title="title" :bordered="false" :loading="apiLoading" class="biz-data-card">
|
||||
<a-row :gutter="16">
|
||||
<a-col :xs="12" :sm="12" :md="12" :lg="6" :xl="6">
|
||||
<a-statistic :value="dataSource.userCount">
|
||||
<template #title>
|
||||
<user-outlined style="color: #1890ff" />
|
||||
用户数量
|
||||
</template>
|
||||
</a-statistic>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon stat-icon-blue">
|
||||
<user-outlined />
|
||||
</div>
|
||||
<a-statistic :value="dataSource.userCount" title="用户数量" />
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :xs="12" :sm="12" :md="12" :lg="6" :xl="6">
|
||||
<a-statistic :value="dataSource.orgCount">
|
||||
<template #title>
|
||||
<cluster-outlined style="color: rgba(229, 159, 18, 0.35)" />
|
||||
组织数量
|
||||
</template>
|
||||
</a-statistic>
|
||||
<a-col :xs="12" :sm="12" :md="12" :lg="12" :xl="6">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon stat-icon-orange">
|
||||
<cluster-outlined />
|
||||
</div>
|
||||
<a-statistic :value="dataSource.orgCount" title="组织数量" />
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :xs="12" :sm="12" :md="12" :lg="6" :xl="6">
|
||||
<a-statistic :value="dataSource.positionCount">
|
||||
<template #title>
|
||||
<apartment-outlined style="color: rgba(245, 6, 6, 0.2)" />
|
||||
职位数量
|
||||
</template>
|
||||
</a-statistic>
|
||||
<a-col :xs="12" :sm="12" :md="12" :lg="12" :xl="6">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon stat-icon-red">
|
||||
<apartment-outlined />
|
||||
</div>
|
||||
<a-statistic :value="dataSource.positionCount" title="职位数量" />
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :xs="12" :sm="12" :md="12" :lg="6" :xl="6">
|
||||
<a-statistic :value="dataSource.roleCount">
|
||||
<template #title>
|
||||
<deployment-unit-outlined style="color: #09c755" />
|
||||
角色数量
|
||||
</template>
|
||||
</a-statistic>
|
||||
<a-col :xs="12" :sm="12" :md="12" :lg="12" :xl="6">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon stat-icon-green">
|
||||
<deployment-unit-outlined />
|
||||
</div>
|
||||
<a-statistic :value="dataSource.roleCount" title="角色数量" />
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
@@ -40,6 +40,8 @@
|
||||
<script setup name="sysBizDataCard">
|
||||
import indexApi from '@/api/sys/indexApi'
|
||||
import { UserOutlined, ClusterOutlined, ApartmentOutlined, DeploymentUnitOutlined } from '@ant-design/icons-vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const title = ref('业务数据')
|
||||
const apiLoading = ref(false)
|
||||
const dataSource = ref({
|
||||
@@ -62,8 +64,50 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
background: var(--background-color-light);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.stat-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
.stat-icon-blue {
|
||||
background-color: var(--blue-1);
|
||||
color: var(--blue-6);
|
||||
}
|
||||
.stat-icon-orange {
|
||||
background-color: var(--orange-1);
|
||||
color: var(--orange-6);
|
||||
}
|
||||
.stat-icon-red {
|
||||
background-color: var(--red-1);
|
||||
color: var(--red-5);
|
||||
}
|
||||
.stat-icon-green {
|
||||
background-color: var(--green-1);
|
||||
color: var(--green-6);
|
||||
}
|
||||
:deep(.ant-statistic-title) {
|
||||
font-size: 14px;
|
||||
color: var(--text-color-secondary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
:deep(.ant-statistic-content-value) {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: var(--text-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<a-card title="系统信息" :bordered="false" class="sys-info-card">
|
||||
<a-descriptions :column="1" size="small">
|
||||
<a-descriptions-item label="Java版本">
|
||||
<a-tag color="blue">{{ sysInfo.javaVersion || '-' }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="操作系统">
|
||||
<span class="info-text">{{ sysInfo.osName || '-' }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="服务器IP">
|
||||
<span class="info-text">{{ sysInfo.serverIp || '-' }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="运行时长">
|
||||
<a-tag color="green">{{ sysInfo.runTime || '-' }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="最后登录">
|
||||
<span class="info-text">{{ userInfo.lastLoginTime || '首次登录' }}</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup name="sysInfoCard">
|
||||
import indexApi from '@/api/sys/indexApi'
|
||||
import tool from '@/utils/tool'
|
||||
|
||||
const userInfo = tool.data.get('USER_INFO') || {}
|
||||
const sysInfo = ref({})
|
||||
|
||||
onMounted(() => {
|
||||
indexApi
|
||||
.indexSysInfo()
|
||||
.then((data) => {
|
||||
sysInfo.value = data
|
||||
})
|
||||
.catch(() => {})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.sys-info-card {
|
||||
height: 100%;
|
||||
}
|
||||
.info-text {
|
||||
color: var(--text-color);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
@@ -1,63 +1,22 @@
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :loading="apiLoading">
|
||||
<a-row>
|
||||
<!-- 因租户内定时任务需解决,暂先注释 -->
|
||||
<!--
|
||||
<a-col :span="4">
|
||||
<a-statistic :value="dataSource.jobCount">
|
||||
<template #title>
|
||||
<field-time-outlined style="color: #ec2c09" />
|
||||
任务调度
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-col>
|
||||
-->
|
||||
<a-col :xs="12" :sm="12" :md="12" :lg="4" :xl="4">
|
||||
<a-statistic :value="dataSource.sysDictCount">
|
||||
<template #title>
|
||||
<read-outlined style="color: #4b4b4b" />
|
||||
系统字典
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-col>
|
||||
<a-col :xs="12" :sm="12" :md="12" :lg="4" :xl="4">
|
||||
<a-statistic :value="dataSource.bizDictCount">
|
||||
<template #title>
|
||||
<read-outlined style="color: #353779" />
|
||||
业务字典
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-col>
|
||||
<a-col :xs="12" :sm="12" :md="12" :lg="4" :xl="4">
|
||||
<a-statistic :value="dataSource.backUserSessionCount">
|
||||
<template #title>
|
||||
<usergroup-delete-outlined style="color: #3ceecd" />
|
||||
后台在线用户
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-col>
|
||||
<a-col :xs="12" :sm="12" :md="12" :lg="4" :xl="4">
|
||||
<a-statistic :value="dataSource.clientUserSessionCount">
|
||||
<template #title>
|
||||
<UserSwitchOutlined style="color: rgba(229, 159, 18, 0.35)" />
|
||||
前台在线用户
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-col>
|
||||
<a-col :xs="12" :sm="12" :md="12" :lg="4" :xl="4">
|
||||
<a-statistic :value="dataSource.thirdUserCount">
|
||||
<template #title>
|
||||
<team-outlined style="color: #1890ff" />
|
||||
三方用户
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-card :title="title" :bordered="false" :loading="apiLoading" class="op-data-card">
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item" v-for="item in statList" :key="item.key">
|
||||
<div class="stat-icon-wrapper" :class="item.colorClass">
|
||||
<component :is="item.icon" />
|
||||
</div>
|
||||
<div class="stat-label">{{ item.label }}</div>
|
||||
<div class="stat-value">{{ dataSource[item.key] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup name="sysBizDataCard">
|
||||
<script setup name="sysOpDataCard">
|
||||
import indexApi from '@/api/sys/indexApi'
|
||||
import { ReadOutlined, UsergroupDeleteOutlined, UserSwitchOutlined, TeamOutlined } from '@ant-design/icons-vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const title = ref('运维一览')
|
||||
const apiLoading = ref(false)
|
||||
const dataSource = ref({
|
||||
@@ -67,6 +26,15 @@
|
||||
clientUserSessionCount: 0,
|
||||
thirdUserCount: 0
|
||||
})
|
||||
|
||||
const statList = [
|
||||
{ label: '系统字典', key: 'sysDictCount', icon: ReadOutlined, colorClass: 'stat-icon-blue' },
|
||||
{ label: '业务字典', key: 'bizDictCount', icon: ReadOutlined, colorClass: 'stat-icon-purple' },
|
||||
{ label: '后台在线', key: 'backUserSessionCount', icon: UsergroupDeleteOutlined, colorClass: 'stat-icon-green' },
|
||||
{ label: '前台在线', key: 'clientUserSessionCount', icon: UserSwitchOutlined, colorClass: 'stat-icon-orange' },
|
||||
{ label: '三方用户', key: 'thirdUserCount', icon: TeamOutlined, colorClass: 'stat-icon-pink' }
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
apiLoading.value = true
|
||||
indexApi
|
||||
@@ -81,8 +49,63 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 8px 4px;
|
||||
border-radius: 8px;
|
||||
background: var(--background-color-light);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.stat-icon-wrapper {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.stat-icon-blue {
|
||||
background-color: var(--blue-1);
|
||||
color: var(--blue-6);
|
||||
}
|
||||
.stat-icon-purple {
|
||||
background-color: var(--purple-1);
|
||||
color: var(--purple-6);
|
||||
}
|
||||
.stat-icon-green {
|
||||
background-color: var(--green-1);
|
||||
color: var(--green-6);
|
||||
}
|
||||
.stat-icon-orange {
|
||||
background-color: var(--orange-1);
|
||||
color: var(--orange-6);
|
||||
}
|
||||
.stat-icon-pink {
|
||||
background-color: var(--pink-1);
|
||||
color: var(--pink-6);
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
color: var(--text-color-secondary);
|
||||
margin-bottom: 2px;
|
||||
text-align: center;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--text-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :loading="apiLoading">
|
||||
<template #extra v-if="displayMore()"><a @click="leaveFor('/dev/oplog')">更多</a></template>
|
||||
<a-card :title="title" :bordered="false" :loading="apiLoading" class="op-log-card">
|
||||
<template #extra v-if="displayMore()">
|
||||
<a-button type="link" size="small" @click="leaveFor('/dev/oplog')">更多</a-button>
|
||||
</template>
|
||||
<div class="timeline-div">
|
||||
<a-timeline>
|
||||
<a-timeline-item :key="opLog.id" v-for="opLog in opLogList" :color="getTimelineColor(opLog.exeStatus)"
|
||||
>{{ opLog.opTime }} {{ opLog.name }}
|
||||
<a-timeline-item :key="opLog.id" v-for="opLog in opLogList" :color="getTimelineColor(opLog.exeStatus)">
|
||||
<div class="log-item">
|
||||
<div class="log-header">
|
||||
<span class="log-name">{{ opLog.name }}</span>
|
||||
<span class="log-time">{{ opLog.opTime }}</span>
|
||||
</div>
|
||||
<a-tag :color="opLog.exeStatus === 'SUCCESS' ? 'success' : 'error'" size="small">
|
||||
{{ opLog.exeStatus === 'SUCCESS' ? '成功' : '失败' }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
</div>
|
||||
@@ -14,7 +24,7 @@
|
||||
<script setup name="indexOpLog">
|
||||
import router from '@/router'
|
||||
import indexApi from '@/api/sys/indexApi'
|
||||
import { onMounted } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import tool from '@/utils/tool'
|
||||
const userInfo = tool.data.get('USER_INFO')
|
||||
const opLogList = ref([])
|
||||
@@ -46,22 +56,45 @@
|
||||
path: url
|
||||
})
|
||||
}
|
||||
// 获取颜色
|
||||
const getTimelineColor = (value) => {
|
||||
if (value === 'SUCCESS') {
|
||||
return 'blue'
|
||||
} else {
|
||||
return 'red'
|
||||
}
|
||||
return value === 'SUCCESS' ? 'green' : 'red'
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.ant-timeline-item {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 10px !important;
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.timeline-div {
|
||||
height: 300px;
|
||||
overflow: auto;
|
||||
height: 330px;
|
||||
overflow-y: auto;
|
||||
padding: 10px 5px;
|
||||
/* 自定义滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color-split);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
.log-item {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.log-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.log-name {
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.log-time {
|
||||
font-size: 12px;
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
:deep(.ant-timeline-item-content) {
|
||||
margin-left: 25px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,37 +1,13 @@
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :loading="apiLoading">
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-statistic :value="dataSource.fileCount">
|
||||
<template #title>
|
||||
<FileFilled style="color: #1890ff" />
|
||||
文件
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-statistic :value="dataSource.smsCount">
|
||||
<template #title>
|
||||
<MessageFilled style="color: #07913e" />
|
||||
短信
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-statistic :value="dataSource.emailCount">
|
||||
<template #title>
|
||||
<MailFilled style="color: #fada00" />
|
||||
邮件
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-statistic :value="dataSource.messageCount">
|
||||
<template #title>
|
||||
<NotificationFilled style="color: #fada00" />
|
||||
站内信
|
||||
</template>
|
||||
</a-statistic>
|
||||
<a-card :title="title" :bordered="false" :loading="apiLoading" class="tool-data-card">
|
||||
<a-row :gutter="10">
|
||||
<a-col :span="12" v-for="item in statList" :key="item.key">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon" :class="item.colorClass">
|
||||
<component :is="item.icon" />
|
||||
</div>
|
||||
<a-statistic :value="dataSource[item.key]" :title="item.label" />
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
@@ -39,6 +15,9 @@
|
||||
|
||||
<script setup name="sysToolDataCard">
|
||||
import indexApi from '@/api/sys/indexApi'
|
||||
import { FileFilled, MessageFilled, MailFilled, NotificationFilled } from '@ant-design/icons-vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const title = ref('基础工具')
|
||||
const apiLoading = ref(false)
|
||||
const dataSource = ref({
|
||||
@@ -47,6 +26,14 @@
|
||||
emailCount: 0,
|
||||
messageCount: 0
|
||||
})
|
||||
|
||||
const statList = [
|
||||
{ label: '文件', key: 'fileCount', icon: FileFilled, colorClass: 'stat-icon-blue' },
|
||||
{ label: '短信', key: 'smsCount', icon: MessageFilled, colorClass: 'stat-icon-green' },
|
||||
{ label: '邮件', key: 'emailCount', icon: MailFilled, colorClass: 'stat-icon-orange' },
|
||||
{ label: '站内信', key: 'messageCount', icon: NotificationFilled, colorClass: 'stat-icon-red' }
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
apiLoading.value = true
|
||||
indexApi
|
||||
@@ -61,8 +48,52 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background: var(--background-color-light);
|
||||
transition: all 0.3s;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.stat-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.stat-icon-blue {
|
||||
background-color: var(--blue-1);
|
||||
color: var(--blue-6);
|
||||
}
|
||||
.stat-icon-green {
|
||||
background-color: var(--green-1);
|
||||
color: var(--green-6);
|
||||
}
|
||||
.stat-icon-orange {
|
||||
background-color: var(--orange-1);
|
||||
color: var(--orange-6);
|
||||
}
|
||||
.stat-icon-red {
|
||||
background-color: var(--red-1);
|
||||
color: var(--red-5);
|
||||
}
|
||||
:deep(.ant-statistic-title) {
|
||||
font-size: 13px;
|
||||
color: var(--text-color-secondary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
:deep(.ant-statistic-content-value) {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--text-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-card :bordered="false" class="user-info-card">
|
||||
<div class="xn-card-line">
|
||||
<div class="xn-flex">
|
||||
<a-avatar class="xn-wh60" :src="userInfo.avatar" :size="{ xs: 80, sm: 80, md: 80, lg: 80, xl: 80 }" />
|
||||
<div class="snowy-index-card-left-one-username">
|
||||
<span>{{ userInfo.name }}</span>
|
||||
<span>{{ userInfo.orgName }} | {{ userInfo.positionName }}</span>
|
||||
<a-avatar class="user-avatar" :src="userInfo.avatar" />
|
||||
<div class="user-details">
|
||||
<div class="welcome-text">{{ welcomeMessage }},{{ userInfo.name }}</div>
|
||||
<div class="user-org">{{ userInfo.orgName }} | {{ userInfo.positionName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="snowy-index-userinfo-time">
|
||||
{{ currentTime }}
|
||||
</span>
|
||||
<div class="time-box">
|
||||
<span class="time-text">{{ currentTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
@@ -22,14 +22,24 @@
|
||||
dayjs.extend(weekday)
|
||||
dayjs.extend(localeData)
|
||||
|
||||
import { onBeforeUnmount } from 'vue'
|
||||
import { ref, onBeforeUnmount, computed } from 'vue'
|
||||
import tool from '@/utils/tool'
|
||||
const userInfo = tool.data.get('USER_INFO')
|
||||
|
||||
const currentTime = ref(dayjs().format('YYYY年MM月DD日 HH时mm分ss秒'))
|
||||
const currentTime = ref(dayjs().format('YYYY年MM月DD日 HH:mm:ss'))
|
||||
|
||||
const welcomeMessage = computed(() => {
|
||||
const hour = dayjs().hour()
|
||||
if (hour < 9) return '早上好'
|
||||
if (hour < 12) return '上午好'
|
||||
if (hour < 14) return '中午好'
|
||||
if (hour < 18) return '下午好'
|
||||
return '晚上好'
|
||||
})
|
||||
|
||||
// 运行定时器,一秒获取一次
|
||||
const interval = window.setInterval(() => {
|
||||
currentTime.value = dayjs().format('YYYY年MM月DD日 HH时mm分ss秒')
|
||||
currentTime.value = dayjs().format('YYYY年MM月DD日 HH:mm:ss')
|
||||
}, 1000)
|
||||
// 这个界面不在我们视线中的时候,关闭定时器
|
||||
onBeforeUnmount(() => {
|
||||
@@ -37,39 +47,57 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
.user-info-card {
|
||||
background: var(--component-background);
|
||||
}
|
||||
.xn-card-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.xn-wh60 {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
.user-avatar {
|
||||
width: 60px !important;
|
||||
height: 60px !important;
|
||||
line-height: 60px !important;
|
||||
border: 2px solid var(--component-background);
|
||||
box-shadow: 0 2px 8px var(--shadow-color);
|
||||
:deep(img) {
|
||||
width: 60px !important;
|
||||
height: 60px !important;
|
||||
}
|
||||
}
|
||||
.snowy-index-card-left-one-username {
|
||||
margin-left: 8px;
|
||||
.user-details {
|
||||
margin-left: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.snowy-index-card-left-one-username > span:nth-child(1) {
|
||||
.welcome-text {
|
||||
font-weight: 600;
|
||||
margin: 2px;
|
||||
font-size: 18px;
|
||||
color: var(--text-color);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.snowy-index-card-left-one-username > span:nth-child(2) {
|
||||
color: #6d737b;
|
||||
margin: 2px;
|
||||
.user-org {
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
.snowy-index-userinfo-time {
|
||||
margin: 2px;
|
||||
.time-box {
|
||||
text-align: right;
|
||||
}
|
||||
.time-text {
|
||||
font-size: 16px;
|
||||
color: var(--primary-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
@media (max-width: 992px) {
|
||||
.snowy-index-userinfo-time {
|
||||
.time-box {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.xn-flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
xField: 'date',
|
||||
yField: valueKey,
|
||||
seriesField: seriesKey,
|
||||
color: ['#1677FF', 'rgb(188, 189, 190)'],
|
||||
color: ['#1677FF', '#bcbdbe'],
|
||||
appendPadding: [0, 8, 0, 0]
|
||||
})
|
||||
line.render()
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :loading="apiLoading">
|
||||
<template #extra v-if="displayMore()"><a @click="leaveFor('/dev/vislog')">更多</a></template>
|
||||
<a-card :title="title" :bordered="false" :loading="apiLoading" class="vis-log-card">
|
||||
<template #extra v-if="displayMore()">
|
||||
<a-button type="link" size="small" @click="leaveFor('/dev/vislog')">更多</a-button>
|
||||
</template>
|
||||
<div class="timeline-div">
|
||||
<a-timeline>
|
||||
<a-timeline-item :key="visLog.id" v-for="visLog in visLogList" :color="getTimelineColor(visLog.category)">
|
||||
{{ $TOOL.parseTime(visLog.opTime, '{y}-{m}-{d} {h}:{i}:{s}') }} {{ visLog.name }}
|
||||
<p class="timeline-item-p">{{ visLog.opIp }} {{ visLog.opAddress }}</p>
|
||||
<div class="log-item">
|
||||
<div class="log-header">
|
||||
<span class="log-name">{{ visLog.name }}</span>
|
||||
<span class="log-time">{{ $TOOL.parseTime(visLog.opTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</div>
|
||||
<p class="log-address">{{ visLog.opIp }} {{ visLog.opAddress }}</p>
|
||||
</div>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
</div>
|
||||
@@ -15,7 +22,7 @@
|
||||
<script setup name="indexVisLog">
|
||||
import router from '@/router'
|
||||
import indexApi from '@/api/sys/indexApi'
|
||||
import { onMounted } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import tool from '@/utils/tool'
|
||||
const userInfo = tool.data.get('USER_INFO')
|
||||
const visLogList = ref([])
|
||||
@@ -48,27 +55,51 @@
|
||||
path: url
|
||||
})
|
||||
}
|
||||
// 获取颜色
|
||||
const getTimelineColor = (value) => {
|
||||
if (value === 'LOGIN') {
|
||||
return 'blue'
|
||||
}
|
||||
if (value === 'LOGOUT') {
|
||||
return 'gray'
|
||||
}
|
||||
if (value === 'LOGOUT') return 'gray'
|
||||
return 'blue'
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.ant-timeline-item {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
.timeline-item-p {
|
||||
margin-bottom: 0;
|
||||
color: rgb(188, 189, 190);
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.timeline-div {
|
||||
height: 300px;
|
||||
overflow: auto;
|
||||
height: 330px;
|
||||
overflow-y: auto;
|
||||
padding: 10px 5px;
|
||||
/* 自定义滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color-split);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
.log-item {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.log-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.log-name {
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.log-time {
|
||||
font-size: 12px;
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
.log-address {
|
||||
margin-bottom: 0;
|
||||
font-size: 12px;
|
||||
color: var(--disabled-color);
|
||||
}
|
||||
:deep(.ant-timeline-item-content) {
|
||||
margin-left: 25px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<a-tag :color="randomColor()" class="container-tag">
|
||||
<component :is="props.icon" class="container-tag-icon" />
|
||||
</a-tag>
|
||||
<span class="container-span">{{ props.label }}</span>
|
||||
<div class="shortcut-item">
|
||||
<div class="icon-wrapper" :style="{ backgroundColor: getBgColor() }">
|
||||
<component :is="props.icon" :style="{ color: getColor() }" class="shortcut-icon" />
|
||||
</div>
|
||||
<span class="shortcut-label">{{ props.label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -25,60 +25,61 @@
|
||||
required: false
|
||||
}
|
||||
})
|
||||
// 颜色列表
|
||||
const colorList = ['#7265E6', '#FFBF00', '#00A2AE', '#F56A00', '#1677FF', '#606D80']
|
||||
// 获取随机颜色
|
||||
const randomColor = () => {
|
||||
if (props.color) {
|
||||
return props.color
|
||||
}
|
||||
return colorList[randomNum(0, colorList.length - 1)]
|
||||
}
|
||||
// 获取minNum到maxNum内的随机数
|
||||
const randomNum = (minNum, maxNum) => {
|
||||
switch (arguments.length) {
|
||||
case 1:
|
||||
return parseInt(Math.random() * minNum + 1, 10)
|
||||
case 2:
|
||||
return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
const colorList = [
|
||||
{ color: 'var(--blue-6)', bg: 'var(--blue-1)' },
|
||||
{ color: 'var(--green-6)', bg: 'var(--green-1)' },
|
||||
{ color: 'var(--orange-6)', bg: 'var(--orange-1)' },
|
||||
{ color: 'var(--red-5)', bg: 'var(--red-1)' },
|
||||
{ color: 'var(--purple-6)', bg: 'var(--purple-1)' },
|
||||
{ color: 'var(--cyan-6)', bg: 'var(--cyan-1)' }
|
||||
]
|
||||
|
||||
let colorIndex = Math.floor(Math.random() * colorList.length)
|
||||
|
||||
const getColor = () => props.color || colorList[colorIndex].color
|
||||
const getBgColor = () => colorList[colorIndex].bg
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
height: 60px;
|
||||
/*border:1px solid var(--border-color-split);*/
|
||||
border-radius: 5px;
|
||||
<style scoped lang="less">
|
||||
.shortcut-item {
|
||||
height: 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
/*实现渐变(时间变化效果)*/
|
||||
-webkit-transition: all 0.5s;
|
||||
-moz-transition: all 0.5s;
|
||||
-ms-transition: all 0.5s;
|
||||
-o-transition: all 0.5s;
|
||||
transition: all 0.5s;
|
||||
transition: all 0.25s ease;
|
||||
border-radius: 2px;
|
||||
background: var(--component-background);
|
||||
border: 1px solid var(--border-color-split);
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px var(--shadow-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.container:hover {
|
||||
background: var(--border-color-split);
|
||||
}
|
||||
.container-tag {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
.icon-wrapper {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
justify-content: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.container-tag-icon {
|
||||
font-size: 25px;
|
||||
.shortcut-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
.container-span {
|
||||
max-width: 60%;
|
||||
.shortcut-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
max-width: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<div class="resizable-panel" :style="wrapperStyle" ref="rootRef">
|
||||
<div
|
||||
class="panel-left"
|
||||
:class="{ 'panel-left-animating': animating }"
|
||||
:style="leftPanelStyle"
|
||||
v-if="!shouldHideLeft"
|
||||
>
|
||||
@@ -75,6 +76,7 @@
|
||||
const leftSize = ref(props.initialSize)
|
||||
const isResizing = ref(false)
|
||||
const collapsed = ref(false)
|
||||
const animating = ref(false)
|
||||
const rootRef = ref(null)
|
||||
let activeContainer = null
|
||||
|
||||
@@ -100,8 +102,10 @@
|
||||
|
||||
const toggleCollapse = () => {
|
||||
if (!props.collapsible) return
|
||||
animating.value = true
|
||||
collapsed.value = !collapsed.value
|
||||
emit('collapse', collapsed.value)
|
||||
setTimeout(() => { animating.value = false }, 300)
|
||||
}
|
||||
|
||||
const onResizerMouseDown = (e) => {
|
||||
@@ -185,6 +189,9 @@
|
||||
.panel-left {
|
||||
overflow: hidden;
|
||||
background: var(--snowy-background-color);
|
||||
}
|
||||
|
||||
.panel-left-animating {
|
||||
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
height 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
min-width 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
|
||||
@@ -89,7 +89,6 @@
|
||||
import { BellOutlined } from '@ant-design/icons-vue'
|
||||
import router from '@/router'
|
||||
import { onMounted } from 'vue'
|
||||
import sysConfig from '@/config'
|
||||
import { convertUrl } from '@/utils/apiAdaptive'
|
||||
import tool from '@/utils/tool'
|
||||
|
||||
@@ -105,8 +104,9 @@
|
||||
// 创建ws连接
|
||||
const createWebSocketConnect = () => {
|
||||
if ('WebSocket' in window) {
|
||||
let url = sysConfig.API_URL + convertUrl('/dev/message/ws') + '?token=' + tool.data.get('TOKEN')
|
||||
url = url.replace('https://', 'wss://').replace('http://', 'ws://')
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://'
|
||||
let url =
|
||||
protocol + window.location.host + '/api' + convertUrl('/dev/message/ws') + '?token=' + tool.data.get('TOKEN')
|
||||
const socket = new WebSocket(url)
|
||||
// 监听打开事件
|
||||
socket.onopen = () => {
|
||||
|
||||
@@ -59,7 +59,7 @@ const clientOpenRouter = ['/front/client/login', '/front/client/findPwd', '/fron
|
||||
*/
|
||||
export const validateClientAccess = (path) => {
|
||||
// 如果不是C端路由,直接返回true
|
||||
if (!path.includes('/front/client/')) {
|
||||
if (!path.startsWith('/front/client')) {
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
|
||||
@@ -110,33 +110,33 @@ service.interceptors.response.use(
|
||||
} else {
|
||||
// 统一成功提示(支持 skipSuccessMessage 跳过)
|
||||
if (!response.config.skipSuccessMessage) {
|
||||
const functionName = response.config.url.split('/').pop()
|
||||
const apiNameArray = [
|
||||
'add',
|
||||
'edit',
|
||||
'delete',
|
||||
'update',
|
||||
'grant',
|
||||
'reset',
|
||||
'stop',
|
||||
'pass',
|
||||
'disable',
|
||||
'enable',
|
||||
'revoke',
|
||||
'suspend',
|
||||
'active',
|
||||
'turn',
|
||||
'adjust',
|
||||
'reject',
|
||||
'saveDraft',
|
||||
'setDefault'
|
||||
]
|
||||
apiNameArray.forEach((apiName) => {
|
||||
// 上面去掉接口路径后,方法内包含内置的进行统一提示成功
|
||||
if (functionName.includes(apiName)) {
|
||||
message.success(data.msg)
|
||||
}
|
||||
})
|
||||
const functionName = response.config.url.split('/').pop()
|
||||
const apiNameArray = [
|
||||
'add',
|
||||
'edit',
|
||||
'delete',
|
||||
'update',
|
||||
'grant',
|
||||
'reset',
|
||||
'stop',
|
||||
'pass',
|
||||
'disable',
|
||||
'enable',
|
||||
'revoke',
|
||||
'suspend',
|
||||
'active',
|
||||
'turn',
|
||||
'adjust',
|
||||
'reject',
|
||||
'saveDraft',
|
||||
'setDefault'
|
||||
]
|
||||
apiNameArray.forEach((apiName) => {
|
||||
// 上面去掉接口路径后,方法内包含内置的进行统一提示成功
|
||||
if (functionName.includes(apiName)) {
|
||||
message.success(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return Promise.resolve(data.data)
|
||||
@@ -156,7 +156,6 @@ service.interceptors.response.use(
|
||||
|
||||
// 适配器, 用于适配不同的请求方式
|
||||
export const baseRequest = (url, value = {}, method = 'post', options = {}) => {
|
||||
url = sysConfig.API_URL + convertUrl(url)
|
||||
if (method === 'post') {
|
||||
return service.post(url, value, options)
|
||||
} else if (method === 'get') {
|
||||
|
||||
@@ -1,338 +0,0 @@
|
||||
/**
|
||||
* Copyright [2022] [https://www.xiaonuo.vip]
|
||||
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
|
||||
* 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
* 2.请不要删除和修改Snowy源码头部的版权声明。
|
||||
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
|
||||
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
|
||||
* 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
|
||||
* 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
|
||||
*/
|
||||
/* !
|
||||
* template.js v0.7.1 (https://github.com/yanhaijing/template.js)
|
||||
* API https://github.com/yanhaijing/template.js/blob/master/doc/api.md
|
||||
* Copyright 2015 yanhaijing. All Rights Reserved
|
||||
* Licensed under MIT (https://github.com/yanhaijing/template.js/blob/master/MIT-LICENSE.txt)
|
||||
*/
|
||||
/* eslint-disable */
|
||||
;(function (root, factory) {
|
||||
var template = factory(root)
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD
|
||||
define('template', function () {
|
||||
return template
|
||||
})
|
||||
} else if (typeof exports === 'object') {
|
||||
// Node.js
|
||||
module.exports = template
|
||||
} else {
|
||||
// Browser globals
|
||||
var _template = root.template
|
||||
|
||||
template.noConflict = function () {
|
||||
if (root.template === template) {
|
||||
root.template = _template
|
||||
}
|
||||
|
||||
return template
|
||||
}
|
||||
root.template = template
|
||||
}
|
||||
})(this, function (root) {
|
||||
'use strict'
|
||||
var o = {
|
||||
sTag: '<%', //开始标签
|
||||
eTag: '%>', //结束标签
|
||||
compress: false, //是否压缩html
|
||||
escape: true, //默认输出是否进行HTML转义
|
||||
error: function (e) {} //错误回调
|
||||
}
|
||||
var functionMap = {} //内部函数对象
|
||||
//修饰器前缀
|
||||
var modifierMap = {
|
||||
'': function (param) {
|
||||
return nothing(param)
|
||||
},
|
||||
h: function (param) {
|
||||
return encodeHTML(param)
|
||||
},
|
||||
u: function (param) {
|
||||
return encodeURI(param)
|
||||
}
|
||||
}
|
||||
|
||||
var toString = {}.toString
|
||||
var slice = [].slice
|
||||
function type(x) {
|
||||
if (x === null) {
|
||||
return 'null'
|
||||
}
|
||||
|
||||
var t = typeof x
|
||||
|
||||
if (t !== 'object') {
|
||||
return t
|
||||
}
|
||||
|
||||
var c = toString.call(x).slice(8, -1).toLowerCase()
|
||||
if (c !== 'object') {
|
||||
return c
|
||||
}
|
||||
|
||||
if (x.constructor == Object) {
|
||||
return c
|
||||
}
|
||||
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
function isObject(obj) {
|
||||
return type(obj) === 'object'
|
||||
}
|
||||
function isFunction(fn) {
|
||||
return type(fn) === 'function'
|
||||
}
|
||||
function isString(str) {
|
||||
return type(str) === 'string'
|
||||
}
|
||||
function extend() {
|
||||
var target = arguments[0] || {}
|
||||
var arrs = slice.call(arguments, 1)
|
||||
var len = arrs.length
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
var arr = arrs[i]
|
||||
for (var name in arr) {
|
||||
target[name] = arr[name]
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
function clone() {
|
||||
var args = slice.call(arguments)
|
||||
return extend.apply(null, [{}].concat(args))
|
||||
}
|
||||
function nothing(param) {
|
||||
return param
|
||||
}
|
||||
function encodeHTML(source) {
|
||||
return String(source)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/\\/g, '\')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
function compress(html) {
|
||||
return html.replace(/\s+/g, ' ').replace(/<!--[\w\W]*?-->/g, '')
|
||||
}
|
||||
function consoleAdapter(cmd, msg) {
|
||||
typeof console !== 'undefined' && console[cmd] && console[cmd](msg)
|
||||
}
|
||||
function handelError(e) {
|
||||
var message = 'template.js error\n\n'
|
||||
|
||||
for (var key in e) {
|
||||
message += '<' + key + '>\n' + e[key] + '\n\n'
|
||||
}
|
||||
message += '<message>\n' + e.message + '\n\n'
|
||||
consoleAdapter('error', message)
|
||||
|
||||
o.error(e)
|
||||
function error() {
|
||||
return 'template.js error'
|
||||
}
|
||||
error.toString = function () {
|
||||
return '__code__ = "template.js error"'
|
||||
}
|
||||
return error
|
||||
}
|
||||
function parse(tpl, opt) {
|
||||
var code = ''
|
||||
var sTag = opt.sTag
|
||||
var eTag = opt.eTag
|
||||
var escape = opt.escape
|
||||
function parsehtml(line) {
|
||||
// 单双引号转义,换行符替换为空格
|
||||
line = line.replace(/('|")/g, '\\$1')
|
||||
var lineList = line.split('\n')
|
||||
var code = ''
|
||||
for (var i = 0; i < lineList.length; i++) {
|
||||
code += ';__code__ += ("' + lineList[i] + (i === lineList.length - 1 ? '")\n' : '\\n")\n')
|
||||
}
|
||||
return code
|
||||
}
|
||||
function parsejs(line) {
|
||||
//var reg = /^(:?)(.*?)=(.*)$/;
|
||||
var reg = /^(?:=|(:.*?)=)(.*)$/
|
||||
var html
|
||||
var arr
|
||||
var modifier
|
||||
|
||||
// = := :*=
|
||||
// :h=123 [':h=123', 'h', '123']
|
||||
if ((arr = reg.exec(line))) {
|
||||
html = arr[2] // 输出
|
||||
if (Boolean(arr[1])) {
|
||||
// :开头
|
||||
modifier = arr[1].slice(1)
|
||||
} else {
|
||||
// = 开头
|
||||
modifier = escape ? 'h' : ''
|
||||
}
|
||||
|
||||
return (
|
||||
';__code__ += __modifierMap__["' +
|
||||
modifier +
|
||||
'"](typeof (' +
|
||||
html +
|
||||
') !== "undefined" ? (' +
|
||||
html +
|
||||
') : "")\n'
|
||||
)
|
||||
}
|
||||
|
||||
//原生js
|
||||
return ';' + line + '\n'
|
||||
}
|
||||
|
||||
var tokens = tpl.split(sTag)
|
||||
|
||||
for (var i = 0, len = tokens.length; i < len; i++) {
|
||||
var token = tokens[i].split(eTag)
|
||||
|
||||
if (token.length === 1) {
|
||||
code += parsehtml(token[0])
|
||||
} else {
|
||||
code += parsejs(token[0], true)
|
||||
if (token[1]) {
|
||||
code += parsehtml(token[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
function compiler(tpl, opt) {
|
||||
var mainCode = parse(tpl, opt)
|
||||
|
||||
var headerCode =
|
||||
'\n' +
|
||||
' var html = (function (__data__, __modifierMap__) {\n' +
|
||||
' var __str__ = "", __code__ = "";\n' +
|
||||
' for(var key in __data__) {\n' +
|
||||
' __str__+=("var " + key + "=__data__[\'" + key + "\'];");\n' +
|
||||
' }\n' +
|
||||
' eval(__str__);\n\n'
|
||||
|
||||
var footerCode =
|
||||
'\n' + ' ;return __code__;\n' + ' }(__data__, __modifierMap__));\n' + ' return html;\n'
|
||||
|
||||
var code = headerCode + mainCode + footerCode
|
||||
code = code.replace(/[\r]/g, ' ') // ie 7 8 会报错,不知道为什么
|
||||
try {
|
||||
var Render = new Function('__data__', '__modifierMap__', code)
|
||||
Render.toString = function () {
|
||||
return mainCode
|
||||
}
|
||||
return Render
|
||||
} catch (e) {
|
||||
e.temp = 'function anonymous(__data__, __modifierMap__) {' + code + '}'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
function compile(tpl, opt) {
|
||||
opt = clone(o, opt)
|
||||
|
||||
try {
|
||||
var Render = compiler(tpl, opt)
|
||||
} catch (e) {
|
||||
e.name = 'CompileError'
|
||||
e.tpl = tpl
|
||||
e.render = e.temp
|
||||
delete e.temp
|
||||
return handelError(e)
|
||||
}
|
||||
|
||||
function render(data) {
|
||||
data = clone(functionMap, data)
|
||||
try {
|
||||
var html = Render(data, modifierMap)
|
||||
html = opt.compress ? compress(html) : html
|
||||
return html
|
||||
} catch (e) {
|
||||
e.name = 'RenderError'
|
||||
e.tpl = tpl
|
||||
e.render = Render.toString()
|
||||
return handelError(e)()
|
||||
}
|
||||
}
|
||||
|
||||
render.toString = function () {
|
||||
return Render.toString()
|
||||
}
|
||||
return render
|
||||
}
|
||||
function template(tpl, data) {
|
||||
if (typeof tpl !== 'string') {
|
||||
return ''
|
||||
}
|
||||
|
||||
var fn = compile(tpl)
|
||||
if (!isObject(data)) {
|
||||
return fn
|
||||
}
|
||||
|
||||
return fn(data)
|
||||
}
|
||||
|
||||
template.config = function (option) {
|
||||
if (isObject(option)) {
|
||||
o = extend(o, option)
|
||||
}
|
||||
return clone(o)
|
||||
}
|
||||
|
||||
template.registerFunction = function (name, fn) {
|
||||
if (!isString(name)) {
|
||||
return clone(functionMap)
|
||||
}
|
||||
if (!isFunction(fn)) {
|
||||
return functionMap[name]
|
||||
}
|
||||
|
||||
return (functionMap[name] = fn)
|
||||
}
|
||||
template.unregisterFunction = function (name) {
|
||||
if (!isString(name)) {
|
||||
return false
|
||||
}
|
||||
delete functionMap[name]
|
||||
return true
|
||||
}
|
||||
|
||||
template.registerModifier = function (name, fn) {
|
||||
if (!isString(name)) {
|
||||
return clone(modifierMap)
|
||||
}
|
||||
if (!isFunction(fn)) {
|
||||
return modifierMap[name]
|
||||
}
|
||||
|
||||
return (modifierMap[name] = fn)
|
||||
}
|
||||
template.unregisterModifier = function (name) {
|
||||
if (!isString(name)) {
|
||||
return false
|
||||
}
|
||||
delete modifierMap[name]
|
||||
return true
|
||||
}
|
||||
|
||||
template.__encodeHTML = encodeHTML
|
||||
template.__compress = compress
|
||||
template.__handelError = handelError
|
||||
template.__compile = compile
|
||||
template.version = '0.7.1'
|
||||
return template
|
||||
})
|
||||
@@ -9,7 +9,6 @@
|
||||
* 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
|
||||
*/
|
||||
import { generate } from '@ant-design/colors'
|
||||
import tool from '../utils/tool'
|
||||
import config from '../config'
|
||||
import { themeEnum } from '@/layout/enum/themeEnum'
|
||||
|
||||
@@ -55,27 +54,4 @@ const loadLocalTheme = (localSetting) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地保存的配置
|
||||
* @param loadTheme {boolean} 是否加载配置中的主题
|
||||
* @returns {Object}
|
||||
*/
|
||||
const getLocalSetting = (loadTheme) => {
|
||||
let localSetting = {}
|
||||
try {
|
||||
const theme = tool.data.get('SNOWY_THEME')
|
||||
const themeColor = tool.data.get('SNOWY_THEME_COLOR')
|
||||
localSetting = {
|
||||
theme,
|
||||
themeColor
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
if (loadTheme) {
|
||||
loadLocalTheme(localSetting)
|
||||
}
|
||||
return localSetting
|
||||
}
|
||||
|
||||
export { loadLocalTheme, getLocalSetting, changeColor }
|
||||
export { loadLocalTheme, changeColor }
|
||||
|
||||
@@ -1,44 +1,42 @@
|
||||
<template>
|
||||
<a-row :gutter="10">
|
||||
<!-- 轮播图(小屏情况显示) -->
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="0" :xl="0">
|
||||
<biz-slideshow-card :config="slideshowConfig" />
|
||||
</a-col>
|
||||
<!-- 左侧 -->
|
||||
<a-col :xs="24" :sm="24" :md="16" :lg="16" :xl="16">
|
||||
<!-- 人员信息 -->
|
||||
<sys-user-info-card class="mb-2" />
|
||||
<a-row :gutter="10" class="mt-2">
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<!-- 快捷方式 -->
|
||||
<biz-shortcut-card />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="10" class="mt-2">
|
||||
<a-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<!-- 通知公告 -->
|
||||
<biz-notice-card />
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<!-- 站内信息 -->
|
||||
<biz-mini-message-card />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<!-- 右侧 -->
|
||||
<a-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
|
||||
<a-row :gutter="10">
|
||||
<a-col :xs="0" :sm="0" :md="0" :lg="24" :xl="24">
|
||||
<!-- 轮播图 -->
|
||||
<biz-slideshow-card :config="slideshowConfig" />
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<!-- 我的日程 -->
|
||||
<biz-schedule-card />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="index-container">
|
||||
<a-row :gutter="10">
|
||||
<!-- 轮播图(小屏情况显示) -->
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="0" :xl="0">
|
||||
<biz-slideshow-card :config="slideshowConfig" class="mb-2" />
|
||||
</a-col>
|
||||
<!-- 左侧 -->
|
||||
<a-col :xs="24" :sm="24" :md="16" :lg="16" :xl="16">
|
||||
<!-- 人员信息 -->
|
||||
<sys-user-info-card class="mb-2" />
|
||||
<a-row :gutter="10" class="mt-2">
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<!-- 快捷方式 -->
|
||||
<biz-shortcut-card />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="10" class="mt-2">
|
||||
<a-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<!-- 通知公告 -->
|
||||
<biz-notice-card />
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<!-- 站内信息 -->
|
||||
<biz-mini-message-card />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<!-- 右侧 -->
|
||||
<a-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8" class="right-col">
|
||||
<!-- 轮播图(大屏显示) -->
|
||||
<div class="slideshow-lg">
|
||||
<biz-slideshow-card :config="slideshowConfig" class="mb-2" />
|
||||
</div>
|
||||
<!-- 我的日程 -->
|
||||
<biz-schedule-card class="schedule-fill" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="bizIndex">
|
||||
@@ -49,3 +47,24 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.index-container {
|
||||
:deep(.ant-card) {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
}
|
||||
.right-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.slideshow-lg {
|
||||
display: none;
|
||||
@media (min-width: 992px) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.schedule-fill {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<template>
|
||||
<a-tabs v-model:activeKey="activeKey" tab-position="left">
|
||||
<a-tab-pane key="xiaonuoSms" tab="小诺短信">
|
||||
<xiaonuo-sms-form />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="aliyunSms" tab="阿里短信">
|
||||
<aliyun-sms-form />
|
||||
</a-tab-pane>
|
||||
@@ -13,8 +10,7 @@
|
||||
</template>
|
||||
|
||||
<script setup name="smsConfig">
|
||||
import XiaonuoSmsForm from './xiaonuoSmsForm.vue'
|
||||
import AliyunSmsForm from './aliyunSmsForm.vue'
|
||||
import TencentSmsForm from './tencentSmsForm.vue'
|
||||
const activeKey = ref('xiaonuoSms')
|
||||
const activeKey = ref('aliyunSms')
|
||||
</script>
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
<template>
|
||||
<a-spin :spinning="loadSpinning">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
:label-col="{ ...layout.labelCol, offset: 0 }"
|
||||
:wrapper-col="{ ...layout.wrapperCol, offset: 0 }"
|
||||
>
|
||||
<a-row :gutter="8">
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<a-form-item name="SNOWY_SMS_XIAONUO_ACCESS_KEY_ID">
|
||||
<template #label>
|
||||
<a-tooltip>
|
||||
<template #title> 通过官网申请短信或联系站长! </template>
|
||||
<question-circle-outlined />
|
||||
</a-tooltip>
|
||||
小诺短信账号:
|
||||
</template>
|
||||
<a-input v-model:value="formData.SNOWY_SMS_XIAONUO_ACCESS_KEY_ID" placeholder="请输入小诺短信账号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<a-form-item label="小诺短信秘钥:" name="SNOWY_SMS_XIAONUO_ACCESS_KEY_SECRET">
|
||||
<a-input v-model:value="formData.SNOWY_SMS_XIAONUO_ACCESS_KEY_SECRET" placeholder="请输入小诺短信秘钥" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<a-form-item label="发送短信URL:" name="SNOWY_SMS_XIAONUO_REQUEST_URL">
|
||||
<a-input v-model:value="formData.SNOWY_SMS_XIAONUO_REQUEST_URL" placeholder="请输入发送短信URL" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<a-form-item name="SNOWY_SMS_XIAONUO_DEFAULT_SIGN_NAME">
|
||||
<template #label>
|
||||
<a-tooltip>
|
||||
<template #title> 注:若账号跟密钥已绑定签名,则此处配置签名后无效! </template>
|
||||
<question-circle-outlined />
|
||||
</a-tooltip>
|
||||
短信签名:
|
||||
</template>
|
||||
<a-input v-model:value="formData.SNOWY_SMS_XIAONUO_DEFAULT_SIGN_NAME" placeholder="请输入短信签名" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" :loading="submitLoading" @click="onSubmit()">保存</a-button>
|
||||
<a-button @click="() => formRef.resetFields()">重置</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup name="xiaonuoSmsForm">
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { message } from 'ant-design-vue'
|
||||
import configApi from '@/api/dev/configApi'
|
||||
|
||||
const formRef = ref()
|
||||
const formData = ref({})
|
||||
const submitLoading = ref(false)
|
||||
const loadSpinning = ref(true)
|
||||
|
||||
// 查询此界面的配置项,并转为表单
|
||||
const param = {
|
||||
category: 'SMS_XIAONUO'
|
||||
}
|
||||
configApi.configList(param).then((data) => {
|
||||
loadSpinning.value = false
|
||||
if (data) {
|
||||
data.forEach((item) => {
|
||||
formData.value[item.configKey] = item.configValue
|
||||
})
|
||||
} else {
|
||||
message.warning('表单项不存在,请初始化数据库')
|
||||
}
|
||||
})
|
||||
|
||||
// 默认要校验的
|
||||
const formRules = {
|
||||
SNOWY_SMS_XIAONUO_ACCESS_KEY_ID: [required('请输入小诺短信账号')],
|
||||
SNOWY_SMS_XIAONUO_ACCESS_KEY_SECRET: [required('请输入小诺短信秘钥')],
|
||||
SNOWY_SMS_XIAONUO_REQUEST_URL: [required('请输入发送短信URL')],
|
||||
SNOWY_SMS_XIAONUO_DEFAULT_SIGN_NAME: [required('请输入短信签名')]
|
||||
}
|
||||
// 验证并提交数据
|
||||
const onSubmit = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
submitLoading.value = true
|
||||
let submitParam = cloneDeep(formData.value)
|
||||
const param = Object.entries(submitParam).map((item) => {
|
||||
return {
|
||||
configKey: item[0],
|
||||
configValue: item[1]
|
||||
}
|
||||
})
|
||||
configApi
|
||||
.configEditForm(param)
|
||||
.then(() => {})
|
||||
.finally(() => {
|
||||
submitLoading.value = false
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
const layout = {
|
||||
labelCol: {
|
||||
span: 24
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 12
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -14,7 +14,6 @@
|
||||
<a-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<a-form-item label="系统LOGO:" name="SNOWY_SYS_LOGO">
|
||||
<a-upload
|
||||
v-model:file-list="formData.SNOWY_SYS_LOGO"
|
||||
class="avatar-uploader"
|
||||
list-type="picture-card"
|
||||
:show-upload-list="false"
|
||||
@@ -200,10 +199,9 @@
|
||||
loadSpinning.value = false
|
||||
if (data) {
|
||||
data.forEach((item) => {
|
||||
// 如果是系统的logo,它这个组件要一个数组,我将其单独处理
|
||||
// 如果是系统的logo,直接存base64字符串
|
||||
if (item.configKey === 'SNOWY_SYS_LOGO') {
|
||||
formData.value[item.configKey] = [item.configValue]
|
||||
// 让其回显
|
||||
formData.value[item.configKey] = item.configValue
|
||||
imageUrl.value = item.configValue
|
||||
} else if (item.configKey === 'SNOWY_SYS_DEFAULT_WORKBENCH_DATA') {
|
||||
try {
|
||||
@@ -236,11 +234,10 @@
|
||||
}
|
||||
}
|
||||
const customRequest = (data) => {
|
||||
formData.value.SNOWY_SYS_LOGO = ref([])
|
||||
getBase64(data.file)
|
||||
.then((res) => {
|
||||
imageUrl.value = res
|
||||
formData.value.SNOWY_SYS_LOGO.push(res)
|
||||
formData.value.SNOWY_SYS_LOGO = res
|
||||
})
|
||||
.catch((err) => {})
|
||||
}
|
||||
@@ -260,7 +257,6 @@
|
||||
|
||||
// 默认要校验的
|
||||
const formRules = {
|
||||
SNOWY_SYS_LOGO: [required('请上传系统LOGO')],
|
||||
SNOWY_SYS_NAME: [required('请输入系统名称')],
|
||||
SNOWY_SYS_VERSION: [required('请输入系统版本')],
|
||||
SNOWY_SYS_COPYRIGHT: [required('请输入版权信息')],
|
||||
@@ -293,7 +289,6 @@
|
||||
shortcut: menuTreeSelectRef.value.getSelectData()
|
||||
}
|
||||
submitParam.SNOWY_SYS_DEFAULT_WORKBENCH_DATA = JSON.stringify(shortcut)
|
||||
submitParam.SNOWY_SYS_LOGO = submitParam.SNOWY_SYS_LOGO[0]
|
||||
const param = Object.entries(submitParam).map((item) => {
|
||||
return {
|
||||
configKey: item[0],
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
@close="onClose"
|
||||
>
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="XiaonuoSmsSend" tab="小诺短信">
|
||||
<xiaouo-sms-send ref="xiaonuoSmsSendRef" @loadingStart="loadingStart" @loadingEnd="loadingEnd" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="AliyunSmsSend" tab="阿里云短信">
|
||||
<aliyun-sms-send ref="aliyunSmsSendRef" @loadingStart="loadingStart" @loadingEnd="loadingEnd" />
|
||||
</a-tab-pane>
|
||||
@@ -26,17 +23,15 @@
|
||||
</template>
|
||||
|
||||
<script setup name="smsForm">
|
||||
import XiaouoSmsSend from './send/xiaonuoSmsSend.vue'
|
||||
import AliyunSmsSend from './send/aliyunSmsSend.vue'
|
||||
import TencentSmsSend from './send/tencentSmsSend.vue'
|
||||
|
||||
const xiaonuoSmsSendRef = ref()
|
||||
const aliyunSmsSendRef = ref()
|
||||
const tencentSmsSendRef = ref()
|
||||
|
||||
// 默认是关闭状态
|
||||
const visible = ref(false)
|
||||
const activeKey = ref('XiaonuoSmsSend')
|
||||
const activeKey = ref('AliyunSmsSend')
|
||||
const sendLoading = ref(false)
|
||||
// 打开抽屉
|
||||
const onOpen = () => {
|
||||
@@ -53,8 +48,6 @@
|
||||
aliyunSmsSendRef.value.send()
|
||||
} else if (tabActiveKey === 'TencentSmsSend') {
|
||||
tencentSmsSendRef.value.send()
|
||||
} else if (tabActiveKey === 'XiaonuoSmsSend') {
|
||||
xiaonuoSmsSendRef.value.send()
|
||||
}
|
||||
}
|
||||
// 请求loading开始
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
<template>
|
||||
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
|
||||
<a-form-item label="手机号:" name="phoneNumbers">
|
||||
<a-textarea
|
||||
v-model:value="formData.phoneNumbers"
|
||||
placeholder="请输入手机号,多个逗号拼接"
|
||||
:auto-size="{ minRows: 2, maxRows: 5 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="短信内容:" name="message">
|
||||
<a-textarea
|
||||
v-model:value="formData.message"
|
||||
placeholder="请输入短信内容"
|
||||
:auto-size="{ minRows: 3, maxRows: 5 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="短信签名:" name="signName">
|
||||
<a-input v-model:value="formData.signName" placeholder="请输入审核通过的短信签名" allow-clear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup name="XiaonuoSmsSend">
|
||||
import { message } from 'ant-design-vue'
|
||||
import { required } from '@/utils/formRules'
|
||||
import smsApi from '@/api/dev/smsApi'
|
||||
// 定义emit事件
|
||||
const emit = defineEmits({ loadingStart: null, loadingEnd: null })
|
||||
const formRef = ref()
|
||||
// 表单数据,也就是默认给一些数据
|
||||
const formData = ref({})
|
||||
|
||||
// 默认要校验的
|
||||
const formRules = {
|
||||
phoneNumbers: [required('请输入手机号,多个逗号拼接')]
|
||||
}
|
||||
// 发送短信
|
||||
const send = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
emit('loadingStart')
|
||||
smsApi
|
||||
.smsSendXiaonuo(formData.value)
|
||||
.then(() => {
|
||||
message.success('发送成功')
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
emit('loadingEnd')
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
// 调用这个函数将子组件的一些数据和方法暴露出去
|
||||
defineExpose({
|
||||
send
|
||||
})
|
||||
</script>
|
||||
@@ -1,17 +1,18 @@
|
||||
<template>
|
||||
<a-row :gutter="10">
|
||||
<!-- 轮播图(小屏情况显示) -->
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="0" :xl="0">
|
||||
<!-- 轮播图 -->
|
||||
<biz-slideshow-card :config="slideshowConfig" />
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="16" :xl="16">
|
||||
<div class="index-container">
|
||||
<a-row :gutter="10">
|
||||
<!-- 轮播图(小屏情况显示) -->
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="0" :xl="0">
|
||||
<!-- 轮播图 -->
|
||||
<biz-slideshow-card :config="slideshowConfig" class="mb-2" />
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="16" :xl="16">
|
||||
<!-- 人员信息 -->
|
||||
<sys-user-info-card class="mb-2" />
|
||||
<!-- 业务数据 -->
|
||||
<sys-biz-data-card class="mb-2" />
|
||||
<!-- 运维一览 -->
|
||||
<sys-op-data-card class="mb-2" />
|
||||
<!-- 周访问量 -->
|
||||
<sys-vis-chart-data-card class="mb-2" />
|
||||
<a-row :gutter="10">
|
||||
<!-- 访问记录 -->
|
||||
<a-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
@@ -27,19 +28,24 @@
|
||||
<a-row :gutter="10">
|
||||
<a-col :xs="0" :sm="0" :md="0" :lg="24" :xl="24">
|
||||
<!-- 轮播图 -->
|
||||
<biz-slideshow-card :config="slideshowConfig" />
|
||||
<biz-slideshow-card :config="slideshowConfig" class="mb-2" />
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<!-- 基础工具 -->
|
||||
<sys-tool-data-card class="mb-2" />
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<!-- 周访问量 -->
|
||||
<sys-vis-chart-data-card />
|
||||
<!-- 运维一览 -->
|
||||
<sys-op-data-card class="mb-2" />
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<!-- 系统信息 -->
|
||||
<sys-info-card class="mb-2" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="indexHome">
|
||||
@@ -50,3 +56,11 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.index-container {
|
||||
:deep(.ant-card) {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -125,12 +125,7 @@ export default defineConfig(({ command, mode }) => {
|
||||
preprocessorOptions: {
|
||||
less: {
|
||||
javascriptEnabled: true,
|
||||
plugins: [
|
||||
new Less2CssVariablePlugin({
|
||||
// TODO:有必要用的情况下,是否需要传入 variables,可能会造成重复引用
|
||||
variables: { ...v3Token }
|
||||
})
|
||||
],
|
||||
plugins: [new Less2CssVariablePlugin()],
|
||||
modifyVars: v3Token
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,4 +37,12 @@ public interface DevApi {
|
||||
* @date 2024/7/18 17:35
|
||||
**/
|
||||
JSONObject getToolDataCount();
|
||||
|
||||
/**
|
||||
* 获取系统运行信息(Java版本、OS、运行时长、服务器IP)
|
||||
*
|
||||
* @author yubaoshan
|
||||
* @date 2026/4/21
|
||||
**/
|
||||
JSONObject getSysRunInfo();
|
||||
}
|
||||
|
||||
-14
@@ -62,18 +62,4 @@ public interface DevSmsApi {
|
||||
* @date 2022/2/24 13:42
|
||||
**/
|
||||
void sendSmsTencent(String phoneNumbers, String signName, String templateCode, String templateParam);
|
||||
|
||||
/* =========小诺短信========= */
|
||||
|
||||
/**
|
||||
* 发送短信
|
||||
*
|
||||
* @param phoneNumbers 手机号码,支持对多个手机号码发送短信,手机号码之间以半角逗号(,)分隔。
|
||||
* 上限为1000个手机号码。批量调用相对于单条调用及时性稍有延迟。
|
||||
* @param signName 短信服务控制台配置且审核通过的短信签名
|
||||
* @param message 短信内容,发送时编写好的整条短信内容,不带签名【】
|
||||
* @author yubaoshan
|
||||
* @date 2024/5/20 12:00
|
||||
**/
|
||||
void sendSmsXiaonuo(String phoneNumbers, String signName, String message);
|
||||
}
|
||||
|
||||
+1
-4
@@ -27,10 +27,7 @@ public enum AuthSmsEngineTypeEnum {
|
||||
ALIYUN("ALIYUN"),
|
||||
|
||||
/** 腾讯云 */
|
||||
TENCENT("TENCENT"),
|
||||
|
||||
/** 小诺短信 */
|
||||
XIAONUO("XIAONUO");
|
||||
TENCENT("TENCENT");
|
||||
|
||||
private final String value;
|
||||
|
||||
|
||||
+17
@@ -12,7 +12,10 @@
|
||||
*/
|
||||
package vip.xiaonuo.dev.modular.dev;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -26,6 +29,9 @@ import vip.xiaonuo.dev.modular.job.service.DevJobService;
|
||||
import vip.xiaonuo.dev.modular.message.service.DevMessageService;
|
||||
import vip.xiaonuo.dev.modular.sms.service.DevSmsService;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.RuntimeMXBean;
|
||||
|
||||
/**
|
||||
* 开发工具模块综合API接口实现类
|
||||
*
|
||||
@@ -78,4 +84,15 @@ public class DevApiProvider implements DevApi {
|
||||
json.set("messageCount", devMessageService.count());
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getSysRunInfo() {
|
||||
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
|
||||
JSONObject json = new JSONObject();
|
||||
json.set("javaVersion", SystemUtil.get("java.version", false));
|
||||
json.set("osName", SystemUtil.getOsInfo().getName());
|
||||
json.set("serverIp", NetUtil.getLocalhostStr());
|
||||
json.set("runTime", DateUtil.formatBetween(DateUtil.date(runtimeMXBean.getStartTime()), DateUtil.date()));
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -53,7 +53,7 @@ public class DevLogApiProvider implements DevLogApi {
|
||||
@Override
|
||||
public List<JSONObject> currentUserVisLogList() {
|
||||
return devLogService.page(CommonPageRequest.defaultPage(), new LambdaQueryWrapper<DevLog>()
|
||||
.select(DevLog::getName, DevLog::getOpUser, DevLog::getOpTime, DevLog::getOpAddress, DevLog::getOpIp)
|
||||
.select(DevLog::getId, DevLog::getName, DevLog::getOpUser, DevLog::getOpTime, DevLog::getOpAddress, DevLog::getOpIp, DevLog::getCategory)
|
||||
.eq(DevLog::getOpUser, StpLoginUserUtil.getLoginUser().getName())
|
||||
.in(DevLog::getCategory, DevLogCategoryEnum.LOGIN.getValue(), DevLogCategoryEnum.LOGOUT.getValue())
|
||||
.orderByDesc(DevLog::getCreateTime))
|
||||
@@ -63,7 +63,7 @@ public class DevLogApiProvider implements DevLogApi {
|
||||
@Override
|
||||
public List<JSONObject> currentUserOpLogList() {
|
||||
return devLogService.page(CommonPageRequest.defaultPage(), new LambdaQueryWrapper<DevLog>()
|
||||
.select(DevLog::getName, DevLog::getOpUser, DevLog::getOpTime, DevLog::getOpAddress, DevLog::getOpIp)
|
||||
.select(DevLog::getId, DevLog::getName, DevLog::getOpUser, DevLog::getOpTime, DevLog::getOpAddress, DevLog::getOpIp, DevLog::getExeStatus)
|
||||
.eq(DevLog::getOpUser, StpLoginUserUtil.getLoginUser().getName())
|
||||
.in(DevLog::getCategory, DevLogCategoryEnum.OPERATE.getValue(), DevLogCategoryEnum.EXCEPTION.getValue())
|
||||
.orderByDesc(DevLog::getCreateTime))
|
||||
|
||||
-14
@@ -89,20 +89,6 @@ public class DevSmsController {
|
||||
return CommonResult.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信——小诺短信
|
||||
*
|
||||
* @author xuyuxiang
|
||||
* @date 2022/4/24 20:47
|
||||
*/
|
||||
@Operation(summary = "发送小诺短信")
|
||||
@CommonLog("发送小诺短信")
|
||||
@PostMapping("/dev/sms/sendXiaonuo")
|
||||
public CommonResult<String> sendXiaonuo(@RequestBody @Valid DevSmsSendXiaonuoParam devSmsSendXiaonuoParam) {
|
||||
devSmsService.sendXiaonuo(devSmsSendXiaonuoParam);
|
||||
return CommonResult.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取短信分页
|
||||
*
|
||||
|
||||
+1
-4
@@ -27,10 +27,7 @@ public enum DevSmsEngineTypeEnum {
|
||||
ALIYUN("ALIYUN"),
|
||||
|
||||
/** 腾讯云 */
|
||||
TENCENT("TENCENT"),
|
||||
|
||||
/** 小诺短信 */
|
||||
XIAONUO("XIAONUO");
|
||||
TENCENT("TENCENT");
|
||||
|
||||
private final String value;
|
||||
|
||||
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright [2022] [https://www.xiaonuo.vip]
|
||||
*
|
||||
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
|
||||
*
|
||||
* 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
* 2.请不要删除和修改Snowy源码头部的版权声明。
|
||||
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
|
||||
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
|
||||
* 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
|
||||
* 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
|
||||
*/
|
||||
package vip.xiaonuo.dev.modular.sms.param;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 短信发送——小诺参数
|
||||
*
|
||||
* @author xuyuxiang
|
||||
* @date 2022/7/31 15:21
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class DevSmsSendXiaonuoParam {
|
||||
|
||||
/** 手机号 */
|
||||
@Schema(description = "手机号,多个逗号拼接", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "phoneNumbers不能为空")
|
||||
private String phoneNumbers;
|
||||
|
||||
/** 模板编码 */
|
||||
@Schema(description = "模板编码")
|
||||
private String templateCode;
|
||||
|
||||
/** 发送参数 */
|
||||
@Schema(description = "发送参数")
|
||||
private String templateParam;
|
||||
|
||||
/** 短信内容 */
|
||||
@Schema(description = "短信内容")
|
||||
private String message;
|
||||
|
||||
/** 短信签名 */
|
||||
@Schema(description = "短信签名")
|
||||
private String signName;
|
||||
|
||||
|
||||
|
||||
}
|
||||
-10
@@ -20,7 +20,6 @@ import vip.xiaonuo.dev.api.DevSmsApi;
|
||||
import vip.xiaonuo.dev.modular.sms.param.DevSmsSendAliyunParam;
|
||||
import vip.xiaonuo.dev.modular.sms.param.DevSmsSendDynamicParam;
|
||||
import vip.xiaonuo.dev.modular.sms.param.DevSmsSendTencentParam;
|
||||
import vip.xiaonuo.dev.modular.sms.param.DevSmsSendXiaonuoParam;
|
||||
import vip.xiaonuo.dev.modular.sms.service.DevSmsService;
|
||||
|
||||
/**
|
||||
@@ -63,13 +62,4 @@ public class DevSmsApiProvider implements DevSmsApi {
|
||||
devSmsSendTencentParam.setTemplateParam(templateParam);
|
||||
devSmsService.sendTencent(devSmsSendTencentParam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendSmsXiaonuo(String phoneNumbers, String signName, String message) {
|
||||
DevSmsSendXiaonuoParam devSmsSendXiaonuoParam = new DevSmsSendXiaonuoParam();
|
||||
devSmsSendXiaonuoParam.setPhoneNumbers(phoneNumbers);
|
||||
devSmsSendXiaonuoParam.setSignName(signName);
|
||||
devSmsSendXiaonuoParam.setMessage(message);
|
||||
devSmsService.sendXiaonuo(devSmsSendXiaonuoParam);
|
||||
}
|
||||
}
|
||||
|
||||
-8
@@ -64,14 +64,6 @@ public interface DevSmsService extends IService<DevSms> {
|
||||
**/
|
||||
void sendTencent(DevSmsSendTencentParam devSmsSendTencentParam);
|
||||
|
||||
/**
|
||||
* 发送短信——小诺短信
|
||||
*
|
||||
* @author xuyuxiang
|
||||
* @date 2024/1/25 14:08
|
||||
**/
|
||||
void sendXiaonuo(DevSmsSendXiaonuoParam devSmsSendXiaonuoParam);
|
||||
|
||||
/**
|
||||
* 获取短信分页
|
||||
*
|
||||
|
||||
+1
-31
@@ -14,7 +14,6 @@ package vip.xiaonuo.dev.modular.sms.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollStreamUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.PhoneUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -37,9 +36,7 @@ import vip.xiaonuo.dev.modular.sms.param.*;
|
||||
import vip.xiaonuo.dev.modular.sms.service.DevSmsService;
|
||||
import vip.xiaonuo.dev.modular.sms.util.DevSmsAliyunUtil;
|
||||
import vip.xiaonuo.dev.modular.sms.util.DevSmsTencentUtil;
|
||||
import vip.xiaonuo.dev.modular.sms.util.DevSmsXiaonuoUtil;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -60,13 +57,7 @@ public class DevSmsServiceImpl extends ServiceImpl<DevSmsMapper, DevSms> impleme
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void sendDynamic(String engine, String phoneNumbers, String templateCodeOrId, JSONObject templateParam) {
|
||||
if(engine.equals(DevSmsEngineTypeEnum.XIAONUO.getValue())) {
|
||||
DevSmsSendXiaonuoParam devSmsSendXiaonuoParam = new DevSmsSendXiaonuoParam();
|
||||
devSmsSendXiaonuoParam.setPhoneNumbers(phoneNumbers);
|
||||
devSmsSendXiaonuoParam.setTemplateCode(templateCodeOrId);
|
||||
devSmsSendXiaonuoParam.setTemplateParam(JSONUtil.toJsonStr(templateParam));
|
||||
this.sendXiaonuo(devSmsSendXiaonuoParam);
|
||||
} else if (engine.equals(DevSmsEngineTypeEnum.ALIYUN.getValue())) {
|
||||
if (engine.equals(DevSmsEngineTypeEnum.ALIYUN.getValue())) {
|
||||
DevSmsSendAliyunParam devSmsSendAliyunParam = new DevSmsSendAliyunParam();
|
||||
devSmsSendAliyunParam.setPhoneNumbers(phoneNumbers);
|
||||
devSmsSendAliyunParam.setTemplateCode(templateCodeOrId);
|
||||
@@ -125,27 +116,6 @@ public class DevSmsServiceImpl extends ServiceImpl<DevSmsMapper, DevSms> impleme
|
||||
this.save(devSms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendXiaonuo(DevSmsSendXiaonuoParam devSmsSendXiaonuoParam) {
|
||||
validPhone(devSmsSendXiaonuoParam.getPhoneNumbers());
|
||||
String receiptInfo;
|
||||
if(ObjectUtil.isEmpty(devSmsSendXiaonuoParam.getTemplateCode())) {
|
||||
receiptInfo = DevSmsXiaonuoUtil.sendSms(devSmsSendXiaonuoParam.getPhoneNumbers(), devSmsSendXiaonuoParam.getSignName(),
|
||||
devSmsSendXiaonuoParam.getMessage());
|
||||
} else {
|
||||
LinkedHashMap<String, String> paramMap = new LinkedHashMap<>();
|
||||
JSONUtil.parseObj(devSmsSendXiaonuoParam.getTemplateParam()).forEach((k, v) -> paramMap.put(k, Convert.toStr(v)));
|
||||
receiptInfo = DevSmsXiaonuoUtil.sendSms(devSmsSendXiaonuoParam.getPhoneNumbers(), devSmsSendXiaonuoParam.getSignName(),
|
||||
devSmsSendXiaonuoParam.getTemplateCode(), paramMap);
|
||||
}
|
||||
DevSms devSms = new DevSms();
|
||||
BeanUtil.copyProperties(devSmsSendXiaonuoParam, devSms);
|
||||
devSms.setSignName(ObjectUtil.isNotEmpty(devSms.getSignName())?devSms.getSignName():DevSmsXiaonuoUtil.getDefaultSignName());
|
||||
devSms.setEngine(DevSmsEngineTypeEnum.XIAONUO.getValue());
|
||||
devSms.setReceiptInfo(receiptInfo);
|
||||
this.save(devSms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DevSms> page(DevSmsPageParam devSmsPageParam) {
|
||||
QueryWrapper<DevSms> queryWrapper = new QueryWrapper<DevSms>().checkSqlInjection();
|
||||
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright [2022] [https://www.xiaonuo.vip]
|
||||
*
|
||||
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
|
||||
*
|
||||
* 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
* 2.请不要删除和修改Snowy源码头部的版权声明。
|
||||
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
|
||||
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
|
||||
* 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
|
||||
* 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
|
||||
*/
|
||||
package vip.xiaonuo.dev.modular.sms.util;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.dao.SmsDaoDefaultImpl;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||
import org.dromara.sms4j.dingzhong.config.DingZhongConfig;
|
||||
import org.dromara.sms4j.javase.config.SEInitializer;
|
||||
import org.dromara.sms4j.provider.config.SmsConfig;
|
||||
import vip.xiaonuo.common.exception.CommonException;
|
||||
import vip.xiaonuo.dev.api.DevConfigApi;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* 小诺短信工具类
|
||||
*
|
||||
* @author xuyuxiang
|
||||
* @date 2022/1/2 17:05
|
||||
*/
|
||||
@Slf4j
|
||||
public class DevSmsXiaonuoUtil {
|
||||
|
||||
private static SmsBlend smsBlend;
|
||||
|
||||
private static final String SNOWY_SMS_XIAONUO_ACCESS_KEY_ID_KEY = "SNOWY_SMS_XIAONUO_ACCESS_KEY_ID";
|
||||
private static final String SNOWY_SMS_XIAONUO_ACCESS_KEY_SECRET_KEY = "SNOWY_SMS_XIAONUO_ACCESS_KEY_SECRET";
|
||||
private static final String SNOWY_SMS_XIAONUO_REQUEST_URL_KEY = "SNOWY_SMS_XIAONUO_REQUEST_URL";
|
||||
private static final String SNOWY_SMS_XIAONUO_DEFAULT_SIGN_NAME_KEY = "SNOWY_SMS_XIAONUO_DEFAULT_SIGN_NAME";
|
||||
|
||||
/**
|
||||
* 初始化操作的客户端
|
||||
*
|
||||
* @author xuyuxiang
|
||||
* @date 2022/1/5 23:24
|
||||
*/
|
||||
private static void initClient(String signName) {
|
||||
|
||||
DevConfigApi devConfigApi = SpringUtil.getBean(DevConfigApi.class);
|
||||
|
||||
/* accessKeyId */
|
||||
String accessKeyId = devConfigApi.getValueByKey(SNOWY_SMS_XIAONUO_ACCESS_KEY_ID_KEY);
|
||||
|
||||
if(ObjectUtil.isEmpty(accessKeyId)) {
|
||||
throw new CommonException("小诺短信操作客户端未正确配置:accessKeyId为空");
|
||||
}
|
||||
|
||||
/* accessKeySecret */
|
||||
String accessKeySecret = devConfigApi.getValueByKey(SNOWY_SMS_XIAONUO_ACCESS_KEY_SECRET_KEY);
|
||||
|
||||
if(ObjectUtil.isEmpty(accessKeySecret)) {
|
||||
throw new CommonException("小诺短信操作客户端未正确配置:accessKeySecret为空");
|
||||
}
|
||||
|
||||
/* requestUrl */
|
||||
String requestUrl = devConfigApi.getValueByKey(SNOWY_SMS_XIAONUO_REQUEST_URL_KEY);
|
||||
|
||||
if(ObjectUtil.isEmpty(requestUrl)) {
|
||||
throw new CommonException("小诺短信操作客户端未正确配置:requestUrl为空");
|
||||
}
|
||||
|
||||
DingZhongConfig dingZhongConfig = new DingZhongConfig();
|
||||
dingZhongConfig.setConfigId(accessKeyId);
|
||||
dingZhongConfig.setAccessKeyId(accessKeyId);
|
||||
dingZhongConfig.setAccessKeySecret(accessKeySecret);
|
||||
dingZhongConfig.setSignature(signName);
|
||||
dingZhongConfig.setRequestUrl(requestUrl);
|
||||
SEInitializer.initializer().registerSmsDao(SmsDaoDefaultImpl.getInstance()).fromConfig(new SmsConfig(), CollectionUtil.newArrayList(dingZhongConfig));
|
||||
smsBlend = SmsFactory.getSmsBlend(dingZhongConfig.getConfigId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送纯文字短信
|
||||
*
|
||||
* @param phoneNumbers 手机号码,支持对多个手机号码发送短信,手机号码之间以半角逗号(,)分隔。
|
||||
* @param signName 短信签名,为空则使用默认签名
|
||||
* @param message 短信内容,自定义文字
|
||||
* @return 发送的结果信息
|
||||
* @author xuyuxiang
|
||||
* @date 2022/2/24 13:42
|
||||
**/
|
||||
public static String sendSms(String phoneNumbers, String signName, String message) {
|
||||
try {
|
||||
if(ObjectUtil.isEmpty(signName)) {
|
||||
signName = getDefaultSignName();
|
||||
}
|
||||
// 初始化客户端
|
||||
initClient(signName);
|
||||
// 发送短信
|
||||
SmsResponse smsResponse = smsBlend.massTexting(StrUtil.split(phoneNumbers, StrUtil.COMMA), message);
|
||||
if(smsResponse.isSuccess()) {
|
||||
return JSONUtil.toJsonStr(smsResponse.getData());
|
||||
} else {
|
||||
String data = Convert.toStr(smsResponse.getData());
|
||||
if(JSONUtil.isTypeJSON(data)) {
|
||||
JSONObject responseData = JSONUtil.parseObj(smsResponse.getData());
|
||||
throw new CommonException(responseData.getStr("resInfo"));
|
||||
} else {
|
||||
throw new CommonException(data);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new CommonException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送模板短信
|
||||
*
|
||||
* @param phoneNumbers 手机号码,支持对多个手机号码发送短信,手机号码之间以半角逗号(,)分隔。
|
||||
* @param signName 短信签名,为空则使用默认签名
|
||||
* @param templateId 模板id
|
||||
* @param paramMap 短信参数
|
||||
* @return 发送的结果信息
|
||||
* @author xuyuxiang
|
||||
* @date 2022/2/24 13:42
|
||||
**/
|
||||
public static String sendSms(String phoneNumbers, String signName, String templateId, LinkedHashMap<String, String> paramMap) {
|
||||
try {
|
||||
if(ObjectUtil.isEmpty(signName)) {
|
||||
signName = getDefaultSignName();
|
||||
}
|
||||
// 初始化客户端
|
||||
initClient(signName);
|
||||
// 发送短信
|
||||
SmsResponse smsResponse = smsBlend.massTexting(StrUtil.split(phoneNumbers, StrUtil.COMMA), templateId, paramMap);
|
||||
if(smsResponse.isSuccess()) {
|
||||
return JSONUtil.toJsonStr(smsResponse.getData());
|
||||
} else {
|
||||
JSONObject responseData = JSONUtil.parseObj(smsResponse.getData());
|
||||
throw new CommonException(responseData.getStr("resInfo"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new CommonException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认签名
|
||||
*
|
||||
* @author xuyuxiang
|
||||
* @date 2024/1/26 16:40
|
||||
**/
|
||||
public static String getDefaultSignName() {
|
||||
// 签名为空,则获取默认签名
|
||||
DevConfigApi devConfigApi = SpringUtil.getBean(DevConfigApi.class);
|
||||
String signName = devConfigApi.getValueByKey(SNOWY_SMS_XIAONUO_DEFAULT_SIGN_NAME_KEY);
|
||||
if(ObjectUtil.isEmpty(signName)) {
|
||||
throw new CommonException("小诺短信操作客户端未正确配置:signName为空");
|
||||
}
|
||||
return signName;
|
||||
}
|
||||
}
|
||||
+12
@@ -183,4 +183,16 @@ public class SysIndexController {
|
||||
public CommonResult<SysToolDataCountResult> getToolDataCount() {
|
||||
return CommonResult.data(sysIndexService.getToolDataCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统运行信息
|
||||
*
|
||||
* @author yubaoshan
|
||||
* @date 2026/4/21
|
||||
*/
|
||||
@Operation(summary = "获取系统运行信息")
|
||||
@GetMapping("/sys/index/sysInfo")
|
||||
public CommonResult<SysIndexSysInfoResult> getSysInfo() {
|
||||
return CommonResult.data(sysIndexService.getSysInfo());
|
||||
}
|
||||
}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package vip.xiaonuo.sys.modular.index.result;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 系统运行信息
|
||||
*
|
||||
* @author yubaoshan
|
||||
* @date 2026/4/21
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class SysIndexSysInfoResult {
|
||||
|
||||
/** Java版本 */
|
||||
private String javaVersion;
|
||||
|
||||
/** 操作系统 */
|
||||
private String osName;
|
||||
|
||||
/** 服务器IP */
|
||||
private String serverIp;
|
||||
|
||||
/** 运行时长 */
|
||||
private String runTime;
|
||||
}
|
||||
+8
@@ -112,4 +112,12 @@ public interface SysIndexService {
|
||||
* @date 2024/7/18 17:35
|
||||
*/
|
||||
SysToolDataCountResult getToolDataCount();
|
||||
|
||||
/**
|
||||
* 获取系统运行信息
|
||||
*
|
||||
* @author yubaoshan
|
||||
* @date 2026/4/21
|
||||
*/
|
||||
SysIndexSysInfoResult getSysInfo();
|
||||
}
|
||||
|
||||
+5
@@ -161,4 +161,9 @@ public class SysIndexServiceImpl implements SysIndexService {
|
||||
public SysToolDataCountResult getToolDataCount() {
|
||||
return BeanUtil.toBean(devApi.getToolDataCount(), SysToolDataCountResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysIndexSysInfoResult getSysInfo() {
|
||||
return BeanUtil.toBean(devApi.getSysRunInfo(), SysIndexSysInfoResult.class);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-4
@@ -27,10 +27,7 @@ public enum SysSmsEngineTypeEnum {
|
||||
ALIYUN("ALIYUN"),
|
||||
|
||||
/** 腾讯云 */
|
||||
TENCENT("TENCENT"),
|
||||
|
||||
/** 小诺短信 */
|
||||
XIAONUO("XIAONUO");
|
||||
TENCENT("TENCENT");
|
||||
|
||||
private final String value;
|
||||
|
||||
|
||||
@@ -238,17 +238,14 @@ INSERT INTO `DEV_CONFIG` VALUES ('1554740179362967590', 'SNOWY_SMS_TENCENT_DEFAU
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1554740179362967591', 'SNOWY_SMS_ALIYUN_ACCESS_KEY_ID', '阿里云短信AccessKeyId', 'SMS_ALIYUN', '阿里云短信AccessKeyId', 46, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1554740179362967592', 'SNOWY_SMS_ALIYUN_ACCESS_KEY_SECRET', '阿里云短信AccessKeySecret', 'SMS_ALIYUN', '阿里云短信AccessKeySecret', 47, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1554740179362967594', 'SNOWY_SMS_ALIYUN_DEFAULT_SIGN_NAME', '阿里云短信默认签名', 'SMS_ALIYUN', '阿里云短信默认签名', 49, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1554740179362967595', 'SNOWY_SMS_XIAONUO_ACCESS_KEY_ID', '小诺短信账号Id', 'SMS_XIAONUO', '小诺短信账号Id', 50, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1554740179362967596', 'SNOWY_SMS_XIAONUO_ACCESS_KEY_SECRET', '小诺短信账号Secret', 'SMS_XIAONUO', '小诺短信账号', 51, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1554740179362967597', 'SNOWY_SMS_XIAONUO_REQUEST_URL', '小诺短信服务URL', 'SMS_XIAONUO', '小诺短信服务URL', 52, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1554740179362967598', 'SNOWY_SMS_XIAONUO_DEFAULT_SIGN_NAME', '小诺短信签名', 'SMS_XIAONUO', '小诺短信签名', 53, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1554740179362968000', 'SNOWY_EMAIL_LOCAL_SMTP_HOST', 'smtp.qq.com', 'EMAIL_LOCAL', '本地邮件SMTP服务器域名', 80, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1554740179362968001', 'SNOWY_EMAIL_LOCAL_SMTP_PORT', '465', 'EMAIL_LOCAL', '本地邮件SMTP服务端口', 81, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1554740179362968002', 'SNOWY_EMAIL_LOCAL_AUTH', 'true', 'EMAIL_LOCAL', '本地邮件是否需要用户名密码验证', 82, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1554740179362968003', 'SNOWY_EMAIL_LOCAL_SSL_ENABLE', 'true', 'EMAIL_LOCAL', '本地邮件是否使用SSL安全连接', 83, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1554740179362968004', 'SNOWY_EMAIL_LOCAL_STARTTLS_ENABLE', 'true', 'EMAIL_LOCAL', '本地邮件是否使用STARTTLS安全连接', 84, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1908870094824755202', 'SNOWY_SYS_DEFAULT_PUSH_ENGINE', 'DINGTALK', 'SYS_BASE', '默认消息推送引擎', 85, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1908870094824755203', 'SNOWY_SYS_DEFAULT_SMS_ENGINE', 'XIAONUO', 'SYS_BASE', '默认短信发送引擎', 86, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1908870094824755203', 'SNOWY_SYS_DEFAULT_SMS_ENGINE', 'ALIYUN', 'SYS_BASE', '默认短信发送引擎', 86, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1908870094824755204', 'SNOWY_SYS_DEFAULT_EMAIL_ENGINE', 'LOCAL', 'SYS_BASE', '默认邮件发送引擎', 87, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1908870094824755205', 'SNOWY_SYS_DEFAULT_CAPTCHA_OPEN_FLAG_FOR_C', 'true', 'SYS_BASE', 'C端全局验证码开关', 88, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `DEV_CONFIG` VALUES ('1908870094824755206', 'SNOWY_SYS_DEFAULT_CAPTCHA_EXPIRED_DURATION_FOR_B', '5', 'SYS_BASE', 'B端验证码失效时间', 89, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
|
||||
|
||||
Reference in New Issue
Block a user