mirror of
https://gitee.com/xiaonuobase/snowy.git
synced 2026-03-22 10:47:16 +08:00
【升级】v3.2版本升级
This commit is contained in:
@@ -53,6 +53,8 @@ gitee下载地址:[https://gitee.com/xiaonuobase/snowy](https://gitee.com/xiao
|
|||||||
|
|
||||||
github下载地址(镜像):[https://github.com/xiaonuobase/Snowy](https://github.com/xiaonuobase/Snowy)
|
github下载地址(镜像):[https://github.com/xiaonuobase/Snowy](https://github.com/xiaonuobase/Snowy)
|
||||||
|
|
||||||
|
gitcode下载地址:[https://gitcode.com/xiaonuobase/Snowy](https://gitcode.com/xiaonuobase/Snowy)
|
||||||
|
|
||||||
演示地址:[https://snowy.xiaonuo.vip](https://snowy.xiaonuo.vip)
|
演示地址:[https://snowy.xiaonuo.vip](https://snowy.xiaonuo.vip)
|
||||||
|
|
||||||
文档地址:[https://xiaonuo.vip/doc](https://xiaonuo.vip/doc)
|
文档地址:[https://xiaonuo.vip/doc](https://xiaonuo.vip/doc)
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
# 本地环境
|
|
||||||
NODE_ENV = development
|
|
||||||
|
|
||||||
# 标题
|
|
||||||
VITE_TITLE = Snowy
|
|
||||||
|
|
||||||
# 接口地址
|
# 接口地址
|
||||||
VITE_API_BASEURL = http://127.0.0.1:82
|
VITE_API_BASEURL = http://127.0.0.1:82
|
||||||
|
|
||||||
@@ -12,3 +6,6 @@ VITE_PORT = 81
|
|||||||
|
|
||||||
# 开启设置抽屉
|
# 开启设置抽屉
|
||||||
VITE_SET_DRAWER = true
|
VITE_SET_DRAWER = true
|
||||||
|
|
||||||
|
# 本地环境
|
||||||
|
NODE_ENV = development
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
# 生产环境
|
|
||||||
NODE_ENV = production
|
|
||||||
|
|
||||||
# 标题
|
|
||||||
VITE_TITLE = Snowy
|
|
||||||
|
|
||||||
# 接口地址
|
# 接口地址
|
||||||
VITE_API_BASEURL = http://127.0.0.1:82
|
VITE_API_BASEURL = http://127.0.0.1:82
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
<title>Snowy</title>
|
<title>Snowy</title>
|
||||||
<style>
|
<style>
|
||||||
.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1677FF;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}
|
.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1677FF;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}
|
||||||
.app-loading {position: absolute;top:0px;left:0px;right:0px;bottom:0px;display: flex;justify-content: center;align-items: center;flex-direction: column;background: #fff;}
|
.app-loading {position: absolute;top:0;left:0;right:0;bottom:0;display: flex;justify-content: center;align-items: center;flex-direction: column;background: #fff;}
|
||||||
.app-loading__logo {margin-bottom: 30px;}
|
.app-loading-logo {margin-bottom: 30px;}
|
||||||
.app-loading__logo img {width: 90px;vertical-align: bottom;}
|
.app-loading-logo img {width: 90px;vertical-align: bottom;}
|
||||||
.app-loading__title {font-size: 24px;color: #333;margin-top: 30px;}
|
.app-loading-title {font-size: 24px;color: #333;margin-top: 30px;}
|
||||||
.app-main { height: 100% }
|
.app-main { height: 100% }
|
||||||
@keyframes loader {
|
@keyframes loader {
|
||||||
0% {
|
0% {
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong>We're sorry but Snowy2.0 doesn't work properly without JavaScript
|
<strong>We're sorry but Snowy doesn't work properly without JavaScript
|
||||||
enabled. Please enable it to continue.</strong>
|
enabled. Please enable it to continue.</strong>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="app" class="app-main"></div>
|
<div id="app" class="app-main"></div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card title="站内信" :bordered="false">
|
<a-card :title="title" :bordered="false" :loading="miniMessageLoading">
|
||||||
<template #extra><a @click="leaveFor('/usercenter')">更多</a></template>
|
<template #extra><a @click="leaveFor('/usercenter')">更多</a></template>
|
||||||
<div class="index-message-list">
|
<div class="index-message-list">
|
||||||
<a-list :data-source="messageList" size="small" :loading="miniMessageLoading">
|
<a-list :data-source="messageList" size="small" :loading="miniMessageLoading">
|
||||||
@@ -54,9 +54,7 @@
|
|||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
const miniMessageLoading = ref(false)
|
const miniMessageLoading = ref(false)
|
||||||
const messageList = ref([])
|
const messageList = ref([])
|
||||||
const miniMessageBodyStyle = ref({
|
const title = ref('站内信')
|
||||||
'padding-top': '10px'
|
|
||||||
})
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 进来后执行查询
|
// 进来后执行查询
|
||||||
getMessageList()
|
getMessageList()
|
||||||
@@ -69,6 +67,7 @@
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
messageList.value = data
|
messageList.value = data
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
miniMessageLoading.value = false
|
miniMessageLoading.value = false
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<xn-form-container
|
<xn-form-container title="详情" :width="1000" v-model:open="open" :destroy-on-close="true" @close="onClose">
|
||||||
title="详情"
|
|
||||||
:width="1000"
|
|
||||||
v-model:open="open"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
@close="onClose"
|
|
||||||
>
|
|
||||||
<a-descriptions bordered>
|
<a-descriptions bordered>
|
||||||
<a-descriptions-item label="标题">{{formData.title}}</a-descriptions-item>
|
<a-descriptions-item label="标题">{{formData.title}}</a-descriptions-item>
|
||||||
<a-descriptions-item label="类型">
|
<a-descriptions-item label="类型">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card :bordered="false" :title="title">
|
<a-card :bordered="false" :title="title" :loading="apiLoading">
|
||||||
<template #extra><a @click="leaveFor('/biz/notice')">更多</a></template>
|
<template #extra><a @click="leaveFor('/biz/notice')">更多</a></template>
|
||||||
<a-table
|
<a-table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
@@ -63,9 +63,19 @@
|
|||||||
]
|
]
|
||||||
const title = ref('通知公告')
|
const title = ref('通知公告')
|
||||||
const dataSource = ref([])
|
const dataSource = ref([])
|
||||||
bizIndexApi.bizIndexNoticeList().then((data) => {
|
const apiLoading = ref(false)
|
||||||
|
onMounted(() => {
|
||||||
|
apiLoading.value = true
|
||||||
|
bizIndexApi
|
||||||
|
.bizIndexNoticeList()
|
||||||
|
.then((data) => {
|
||||||
dataSource.value = data
|
dataSource.value = data
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
apiLoading.value = false
|
||||||
|
})
|
||||||
|
})
|
||||||
const leaveFor = (url = '/') => {
|
const leaveFor = (url = '/') => {
|
||||||
router.replace({
|
router.replace({
|
||||||
path: url
|
path: url
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card :title="title" :bordered="false" class="mt-2">
|
<a-card :title="title" :bordered="false" class="mt-2" :loading="apiLoading">
|
||||||
<a-calendar v-model:value="calendarValue" :fullscreen="false" @select="onPanelSelect" />
|
<a-calendar v-model:value="calendarValue" :fullscreen="false" @select="onPanelSelect" />
|
||||||
<a-card :bordered="false">
|
<a-card :bordered="false">
|
||||||
<a-timeline>
|
<a-timeline>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
const title = ref('我的日程')
|
const title = ref('我的日程')
|
||||||
const scheduleList = ref([])
|
const scheduleList = ref([])
|
||||||
const calendarValue = ref(dayjs())
|
const calendarValue = ref(dayjs())
|
||||||
|
const apiLoading = ref(false)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 进来后执行查询
|
// 进来后执行查询
|
||||||
seleScheduleList()
|
seleScheduleList()
|
||||||
@@ -55,9 +55,16 @@
|
|||||||
const param = {
|
const param = {
|
||||||
scheduleDate: calendarValue.value.format('YYYY-MM-DD')
|
scheduleDate: calendarValue.value.format('YYYY-MM-DD')
|
||||||
}
|
}
|
||||||
indexApi.indexScheduleList(param).then((data) => {
|
apiLoading.value = true
|
||||||
|
indexApi
|
||||||
|
.indexScheduleList(param)
|
||||||
|
.then((data) => {
|
||||||
scheduleList.value = data
|
scheduleList.value = data
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
apiLoading.value = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击某一天
|
// 点击某一天
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card :title="title" :bordered="false">
|
<a-card :title="title" :bordered="false" :loading="apiLoading">
|
||||||
<div class="card-div">
|
<div class="card-div">
|
||||||
<a-row :gutter="10">
|
<a-row :gutter="10">
|
||||||
<a-col :span="6" :key="shortcut.id" v-for="shortcut in shortcutList" :xs="12" :sm="8" :md="6" :lg="8" :xl="6">
|
<a-col :span="6" :key="shortcut.id" v-for="shortcut in shortcutList" :xs="12" :sm="8" :md="6" :lg="8" :xl="6">
|
||||||
@@ -21,16 +21,24 @@
|
|||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
const shortcutList = ref([])
|
const shortcutList = ref([])
|
||||||
const title = ref('快捷方式')
|
const title = ref('快捷方式')
|
||||||
|
const apiLoading = ref(false)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 进来后执行查询
|
// 进来后执行查询
|
||||||
getUserLoginWorkbench()
|
getUserLoginWorkbench()
|
||||||
})
|
})
|
||||||
const getUserLoginWorkbench = () => {
|
const getUserLoginWorkbench = () => {
|
||||||
userCenterApi.userLoginWorkbench().then((data) => {
|
apiLoading.value = true
|
||||||
|
userCenterApi
|
||||||
|
.userLoginWorkbench()
|
||||||
|
.then((data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
shortcutList.value = JSON.parse(data).shortcut
|
shortcutList.value = JSON.parse(data).shortcut
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
apiLoading.value = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
const leaveFor = (url = '/') => {
|
const leaveFor = (url = '/') => {
|
||||||
router.replace({
|
router.replace({
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<template #nextArrow>
|
<template #nextArrow>
|
||||||
<div class="custom-slick-arrow" style="right: 10px"><RightOutlined /></div>
|
<div class="custom-slick-arrow" style="right: 10px"><RightOutlined /></div>
|
||||||
</template>
|
</template>
|
||||||
|
<div v-if="!isEmpty(slideshowList)">
|
||||||
<img
|
<img
|
||||||
v-for="item in slideshowList"
|
v-for="item in slideshowList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -16,12 +17,15 @@
|
|||||||
class="carousel-images"
|
class="carousel-images"
|
||||||
@click="leaveForOpen(item.pathDetails)"
|
@click="leaveForOpen(item.pathDetails)"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
||||||
</a-carousel>
|
</a-carousel>
|
||||||
</a-card>
|
</a-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name="carousel">
|
<script setup name="carousel">
|
||||||
import bizIndexApi from '@/api/biz/bizIndexApi'
|
import bizIndexApi from '@/api/biz/bizIndexApi'
|
||||||
|
import { Empty } from 'ant-design-vue'
|
||||||
import { isEmpty, cloneDeep } from 'lodash-es'
|
import { isEmpty, cloneDeep } from 'lodash-es'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
const slideshowList = ref([])
|
const slideshowList = ref([])
|
||||||
@@ -40,9 +44,12 @@
|
|||||||
// 这是字典内维护的该位置
|
// 这是字典内维护的该位置
|
||||||
place: props.config.options.place ? props.config.options.place : 'BACK_SYS_INDEX'
|
place: props.config.options.place ? props.config.options.place : 'BACK_SYS_INDEX'
|
||||||
}
|
}
|
||||||
bizIndexApi.bizIndexSlideshowList(param).then((data) => {
|
bizIndexApi
|
||||||
|
.bizIndexSlideshowList(param)
|
||||||
|
.then((data) => {
|
||||||
slideshowList.value = data
|
slideshowList.value = data
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
})
|
})
|
||||||
// URL跟路由的跳转
|
// URL跟路由的跳转
|
||||||
const leaveForOpen = (value) => {
|
const leaveForOpen = (value) => {
|
||||||
@@ -81,7 +88,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
height: 180px;
|
height: 180px;
|
||||||
line-height: 150px;
|
line-height: 150px;
|
||||||
background: #364d79;
|
background: #1890ff;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.ant-carousel :deep(.slick-arrow.custom-slick-arrow) {
|
.ant-carousel :deep(.slick-arrow.custom-slick-arrow) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card :title="title" :bordered="false">
|
<a-card :title="title" :bordered="false" :loading="apiLoading">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="6">
|
<a-col :span="6">
|
||||||
<a-statistic :value="dataSource.userCount">
|
<a-statistic :value="dataSource.userCount">
|
||||||
@@ -40,6 +40,7 @@
|
|||||||
<script setup name="sysBizDataCard">
|
<script setup name="sysBizDataCard">
|
||||||
import indexApi from '@/api/sys/indexApi'
|
import indexApi from '@/api/sys/indexApi'
|
||||||
const title = ref('业务数据')
|
const title = ref('业务数据')
|
||||||
|
const apiLoading = ref(false)
|
||||||
const dataSource = ref({
|
const dataSource = ref({
|
||||||
userCount: 0,
|
userCount: 0,
|
||||||
roleCount: 0,
|
roleCount: 0,
|
||||||
@@ -47,9 +48,16 @@
|
|||||||
positionCount: 0
|
positionCount: 0
|
||||||
})
|
})
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
indexApi.indexBizDataCount().then((data) => {
|
apiLoading.value = true
|
||||||
|
indexApi
|
||||||
|
.indexBizDataCount()
|
||||||
|
.then((data) => {
|
||||||
dataSource.value = data
|
dataSource.value = data
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
apiLoading.value = false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card :title="title" :bordered="false">
|
<a-card :title="title" :bordered="false" :loading="apiLoading">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="4">
|
<a-col :span="4">
|
||||||
<a-statistic :value="dataSource.jobCount">
|
<a-statistic :value="dataSource.jobCount">
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
<script setup name="sysBizDataCard">
|
<script setup name="sysBizDataCard">
|
||||||
import indexApi from '@/api/sys/indexApi'
|
import indexApi from '@/api/sys/indexApi'
|
||||||
const title = ref('运维一览')
|
const title = ref('运维一览')
|
||||||
|
const apiLoading = ref(false)
|
||||||
const dataSource = ref({
|
const dataSource = ref({
|
||||||
jobCount: 0,
|
jobCount: 0,
|
||||||
sysDictCount: 0,
|
sysDictCount: 0,
|
||||||
@@ -65,9 +66,16 @@
|
|||||||
thirdUserCount: 0
|
thirdUserCount: 0
|
||||||
})
|
})
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
indexApi.indexOpDataCount().then((data) => {
|
apiLoading.value = true
|
||||||
|
indexApi
|
||||||
|
.indexOpDataCount()
|
||||||
|
.then((data) => {
|
||||||
dataSource.value = data
|
dataSource.value = data
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
apiLoading.value = false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card title="操作记录" :bordered="false">
|
<a-card :title="title" :bordered="false" :loading="apiLoading">
|
||||||
<template #extra v-if="displayMore()"><a @click="leaveFor('/dev/oplog')">更多</a></template>
|
<template #extra v-if="displayMore()"><a @click="leaveFor('/dev/oplog')">更多</a></template>
|
||||||
<div class="timeline-div">
|
<div class="timeline-div">
|
||||||
<a-timeline>
|
<a-timeline>
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
import tool from '@/utils/tool'
|
import tool from '@/utils/tool'
|
||||||
const userInfo = tool.data.get('USER_INFO')
|
const userInfo = tool.data.get('USER_INFO')
|
||||||
const opLogList = ref([])
|
const opLogList = ref([])
|
||||||
|
const title = ref('操作记录')
|
||||||
|
const apiLoading = ref(false)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 进来后执行查询
|
// 进来后执行查询
|
||||||
getOpLogList()
|
getOpLogList()
|
||||||
@@ -28,9 +30,16 @@
|
|||||||
return userInfo.roleCodeList && userInfo.roleCodeList.toString().indexOf('superAdmin') !== -1
|
return userInfo.roleCodeList && userInfo.roleCodeList.toString().indexOf('superAdmin') !== -1
|
||||||
}
|
}
|
||||||
const getOpLogList = () => {
|
const getOpLogList = () => {
|
||||||
indexApi.indexOpLogList().then((data) => {
|
apiLoading.value = true
|
||||||
|
indexApi
|
||||||
|
.indexOpLogList()
|
||||||
|
.then((data) => {
|
||||||
opLogList.value = data
|
opLogList.value = data
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
apiLoading.value = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
const leaveFor = (url = '/') => {
|
const leaveFor = (url = '/') => {
|
||||||
router.replace({
|
router.replace({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card :title="title" :bordered="false">
|
<a-card :title="title" :bordered="false" :loading="apiLoading">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-statistic :value="dataSource.fileCount">
|
<a-statistic :value="dataSource.fileCount">
|
||||||
@@ -40,6 +40,7 @@
|
|||||||
<script setup name="sysToolDataCard">
|
<script setup name="sysToolDataCard">
|
||||||
import indexApi from '@/api/sys/indexApi'
|
import indexApi from '@/api/sys/indexApi'
|
||||||
const title = ref('基础工具')
|
const title = ref('基础工具')
|
||||||
|
const apiLoading = ref(false)
|
||||||
const dataSource = ref({
|
const dataSource = ref({
|
||||||
fileCount: 0,
|
fileCount: 0,
|
||||||
smsCount: 0,
|
smsCount: 0,
|
||||||
@@ -47,9 +48,16 @@
|
|||||||
messageCount: 0
|
messageCount: 0
|
||||||
})
|
})
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
indexApi.indexToolDataCount().then((data) => {
|
apiLoading.value = true
|
||||||
|
indexApi
|
||||||
|
.indexToolDataCount()
|
||||||
|
.then((data) => {
|
||||||
dataSource.value = data
|
dataSource.value = data
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
apiLoading.value = false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card :bordered="false" title="周访问量">
|
<a-card :bordered="false" :title="title">
|
||||||
<div id="visLogChartLine" class="xn-ht200"></div>
|
<div id="visLogChartLine" class="xn-ht200"></div>
|
||||||
</a-card>
|
</a-card>
|
||||||
</template>
|
</template>
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
const seriesKey = 'series'
|
const seriesKey = 'series'
|
||||||
const valueKey = 'value'
|
const valueKey = 'value'
|
||||||
|
const title = ref('周访问量')
|
||||||
const processData = (data, yFields, meta) => {
|
const processData = (data, yFields, meta) => {
|
||||||
const result = []
|
const result = []
|
||||||
data.forEach((d) => {
|
data.forEach((d) => {
|
||||||
@@ -34,7 +35,9 @@
|
|||||||
alias: '登出'
|
alias: '登出'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logApi.logVisLineChartData().then((data) => {
|
logApi
|
||||||
|
.logVisLineChartData()
|
||||||
|
.then((data) => {
|
||||||
const line = new Line('visLogChartLine', {
|
const line = new Line('visLogChartLine', {
|
||||||
data: processData(data, ['loginCount', 'logoutCount'], lineMeta),
|
data: processData(data, ['loginCount', 'logoutCount'], lineMeta),
|
||||||
padding: 'auto',
|
padding: 'auto',
|
||||||
@@ -46,6 +49,7 @@
|
|||||||
})
|
})
|
||||||
line.render()
|
line.render()
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card title="访问记录" :bordered="false">
|
<a-card :title="title" :bordered="false" :loading="apiLoading">
|
||||||
<template #extra v-if="displayMore()"><a @click="leaveFor('/dev/vislog')">更多</a></template>
|
<template #extra v-if="displayMore()"><a @click="leaveFor('/dev/vislog')">更多</a></template>
|
||||||
<div class="timeline-div">
|
<div class="timeline-div">
|
||||||
<a-timeline>
|
<a-timeline>
|
||||||
@@ -19,6 +19,8 @@
|
|||||||
import tool from '@/utils/tool'
|
import tool from '@/utils/tool'
|
||||||
const userInfo = tool.data.get('USER_INFO')
|
const userInfo = tool.data.get('USER_INFO')
|
||||||
const visLogList = ref([])
|
const visLogList = ref([])
|
||||||
|
const title = ref('访问记录')
|
||||||
|
const apiLoading = ref(false)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 进来后执行查询
|
// 进来后执行查询
|
||||||
getVisLogList()
|
getVisLogList()
|
||||||
@@ -29,9 +31,16 @@
|
|||||||
}
|
}
|
||||||
// 查询数据
|
// 查询数据
|
||||||
const getVisLogList = () => {
|
const getVisLogList = () => {
|
||||||
indexApi.indexVisLogList().then((data) => {
|
apiLoading.value = true
|
||||||
|
indexApi
|
||||||
|
.indexVisLogList()
|
||||||
|
.then((data) => {
|
||||||
visLogList.value = data
|
visLogList.value = data
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
apiLoading.value = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
// 跳转
|
// 跳转
|
||||||
const leaveFor = (url = '/') => {
|
const leaveFor = (url = '/') => {
|
||||||
@@ -55,7 +64,7 @@
|
|||||||
padding-bottom: 10px !important;
|
padding-bottom: 10px !important;
|
||||||
}
|
}
|
||||||
.timeline-item-p {
|
.timeline-item-p {
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0;
|
||||||
color: rgb(188, 189, 190);
|
color: rgb(188, 189, 190);
|
||||||
}
|
}
|
||||||
.timeline-div {
|
.timeline-div {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 统计列数据 -->
|
<!-- 统计列数据 -->
|
||||||
<a-alert showIcon class="mb-4" v-if="props.alert">
|
<a-alert showIcon class="s-table-alert mb-4" v-if="props.alert">
|
||||||
<template #message>
|
<template #message>
|
||||||
<div>
|
<div>
|
||||||
<span className="mr-3">
|
<span className="mr-3">
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
@change="loadData"
|
@change="loadData"
|
||||||
@expand="
|
@expand="
|
||||||
(expanded, record) => {
|
(expanded, record) => {
|
||||||
emit('expand', expanded, record)
|
emit('onExpand', expanded, record)
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
:rowClassName="
|
:rowClassName="
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
import { get } from 'lodash-es'
|
import { get } from 'lodash-es'
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const emit = defineEmits(['expand'])
|
const emit = defineEmits(['onExpand'])
|
||||||
const renderSlots = Object.keys(slots)
|
const renderSlots = Object.keys(slots)
|
||||||
|
|
||||||
const props = defineProps(
|
const props = defineProps(
|
||||||
@@ -367,7 +367,8 @@
|
|||||||
// 用请求数据请求该列表的返回数据
|
// 用请求数据请求该列表的返回数据
|
||||||
const result = props.data(parameter)
|
const result = props.data(parameter)
|
||||||
if ((typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') {
|
if ((typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') {
|
||||||
result.then((r) => {
|
result
|
||||||
|
.then((r) => {
|
||||||
if (r == null) {
|
if (r == null) {
|
||||||
data.localLoading = false
|
data.localLoading = false
|
||||||
return
|
return
|
||||||
@@ -588,4 +589,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.s-table-alert {
|
||||||
|
background-color: var(--primary-1) !important;
|
||||||
|
border-color: var(--primary-color) !important;
|
||||||
|
:deep(.ant-alert-icon),
|
||||||
|
a {
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -15,6 +15,10 @@
|
|||||||
const emit = defineEmits({ batchCallBack: null })
|
const emit = defineEmits({ batchCallBack: null })
|
||||||
const buttonLoading = ref(false)
|
const buttonLoading = ref(false)
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
idKey: {
|
||||||
|
type: String,
|
||||||
|
default: () => 'id'
|
||||||
|
},
|
||||||
buttonName: {
|
buttonName: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => '批量操作'
|
default: () => '批量操作'
|
||||||
@@ -62,7 +66,7 @@
|
|||||||
const deleteBatch = () => {
|
const deleteBatch = () => {
|
||||||
const params = props.selectedRowKeys.map((m) => {
|
const params = props.selectedRowKeys.map((m) => {
|
||||||
return {
|
return {
|
||||||
id: m
|
[props.idKey]: m
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 发起方法调用,谁的谁来实现
|
// 发起方法调用,谁的谁来实现
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div style="position: relative">
|
||||||
<a-space class="go-back-button">
|
<a-space class="go-back-button">
|
||||||
<a-button :href="props.src" size="small" target="_blank">
|
<a-button :href="props.src" size="small" target="_blank">
|
||||||
<template #icon><download-outlined /></template>
|
<template #icon><download-outlined /></template>
|
||||||
@@ -45,6 +46,7 @@
|
|||||||
<a-result v-else status="warning" title="不支持预览的文件类型" />
|
<a-result v-else status="warning" title="不支持预览的文件类型" />
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -122,7 +124,7 @@
|
|||||||
.go-back-button {
|
.go-back-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
float: right;
|
float: right;
|
||||||
right: 10px;
|
right: 0;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-modal v-if="isModal" :open="visible" @cancel="cancel" v-bind="$attrs">
|
<a-modal
|
||||||
|
v-if="isModal"
|
||||||
|
:open="visible"
|
||||||
|
@cancel="cancel"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:footer="slotKeys.includes('footer') ? undefined : null"
|
||||||
|
>
|
||||||
<template v-for="slotKey in slotKeys" #[slotKey]>
|
<template v-for="slotKey in slotKeys" #[slotKey]>
|
||||||
<slot :name="slotKey" />
|
<slot :name="slotKey" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -40,6 +40,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
.hljs-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
/** 滚动条 */
|
/** 滚动条 */
|
||||||
:deep(.hljs, .hljs-container) {
|
:deep(.hljs, .hljs-container) {
|
||||||
max-height: 300px !important;
|
max-height: 300px !important;
|
||||||
@@ -78,8 +81,8 @@
|
|||||||
/** 复制样式 */
|
/** 复制样式 */
|
||||||
.hljs-copy {
|
.hljs-copy {
|
||||||
float: right;
|
float: right;
|
||||||
top: 10px;
|
top: 6px;
|
||||||
right: 10px;
|
right: 6px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
:showSearch="props.showSearch"
|
:showSearch="props.showSearch"
|
||||||
:filterOption="!props.showSearch"
|
:filterOption="!props.showSearch"
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
@search="handleSearch"
|
@onSearch="handleSearch"
|
||||||
@popupScroll="handlePopupScroll"
|
@popupScroll="handlePopupScroll"
|
||||||
/>
|
/>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
const initParams = ref({})
|
const initParams = ref({})
|
||||||
const options = ref([])
|
const options = ref([])
|
||||||
const spinning = ref(false)
|
const spinning = ref(false)
|
||||||
const emit = defineEmits({ change: null, 'update:value': null, search:null })
|
const emit = defineEmits({ change: null, 'update:value': null, search: null })
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -110,9 +110,9 @@
|
|||||||
// change
|
// change
|
||||||
const handleChange = (value, array) => {
|
const handleChange = (value, array) => {
|
||||||
modelValue.value = value
|
modelValue.value = value
|
||||||
if (value == null && props.showSearch){
|
if (value == null && props.showSearch) {
|
||||||
//被清空,重置查询条件
|
//被清空,重置查询条件
|
||||||
initParams.value[props.searchKeyName] = value;
|
initParams.value[props.searchKeyName] = value
|
||||||
}
|
}
|
||||||
// 更新数据
|
// 更新数据
|
||||||
emit('update:value', value)
|
emit('update:value', value)
|
||||||
@@ -121,10 +121,10 @@
|
|||||||
}
|
}
|
||||||
// search
|
// search
|
||||||
const handleSearch = (searchValue) => {
|
const handleSearch = (searchValue) => {
|
||||||
let _params = {current: 1};
|
let _params = { current: 1 }
|
||||||
if (props.searchKeyName && props.searchKeyName !== ''){
|
if (props.searchKeyName && props.searchKeyName !== '') {
|
||||||
_params[props.searchKeyName] = searchValue;
|
_params[props.searchKeyName] = searchValue
|
||||||
onPage({ ...initParams.value, ..._params});
|
onPage({ ...initParams.value, ..._params })
|
||||||
}
|
}
|
||||||
// 触发search事件
|
// 触发search事件
|
||||||
emit('search', searchValue)
|
emit('search', searchValue)
|
||||||
|
|||||||
@@ -1,503 +0,0 @@
|
|||||||
@import './index.less';
|
|
||||||
|
|
||||||
body {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&.colorWeak {
|
|
||||||
filter: invert(80%);
|
|
||||||
}
|
|
||||||
&.userLayout {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout.ant-layout {
|
|
||||||
height: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
|
|
||||||
&.mobile,
|
|
||||||
&.tablet {
|
|
||||||
.ant-layout-content {
|
|
||||||
.content {
|
|
||||||
margin: 24px 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.topmenu {
|
|
||||||
/* 必须为 topmenu 才能启用流式布局 */
|
|
||||||
&.content-width-Fluid {
|
|
||||||
.header-index-wide {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mobile {
|
|
||||||
.sidemenu {
|
|
||||||
.ant-header-fixedHeader {
|
|
||||||
&.ant-header-side-opened,
|
|
||||||
&.ant-header-side-closed {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ant-layout-has-sider {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger {
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 55px;
|
|
||||||
padding: 0 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: color 0.3s;
|
|
||||||
&:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.025);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.topmenu {
|
|
||||||
.ant-header-fixedHeader {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 9;
|
|
||||||
width: 100%;
|
|
||||||
transition: width 0.2s;
|
|
||||||
|
|
||||||
&.ant-header-side-opened {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ant-header-side-closed {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* 必须为 topmenu 才能启用流式布局 */
|
|
||||||
&.content-width-Fluid {
|
|
||||||
.header-index-wide {
|
|
||||||
max-width: unset;
|
|
||||||
.header-index-left {
|
|
||||||
flex: 1 1 1000px;
|
|
||||||
.logo{
|
|
||||||
margin-left: 25px;
|
|
||||||
}
|
|
||||||
.ant-menu.ant-menu-horizontal{
|
|
||||||
max-width: calc(100vw - 190px - 238px - 25px);
|
|
||||||
flex: 1 1 calc(100vw - 190px - 238px - 25px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.header-index-right{
|
|
||||||
margin-right:25px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header-index-wide {
|
|
||||||
max-width: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidemenu {
|
|
||||||
.ant-header-fixedHeader {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 9;
|
|
||||||
width: 100%;
|
|
||||||
transition: width 0.2s;
|
|
||||||
|
|
||||||
&.ant-header-side-opened {
|
|
||||||
width: calc(100% - 230px);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ant-header-side-closed {
|
|
||||||
width: calc(100% - 80px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
height: 55px;
|
|
||||||
// padding: 0 12px 0 0;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header,
|
|
||||||
.top-nav-header-index {
|
|
||||||
.user-wrapper {
|
|
||||||
float: right;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.action {
|
|
||||||
line-height: 55px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0 12px;
|
|
||||||
display: inline-block;
|
|
||||||
transition: all 0.3s;
|
|
||||||
height: 100%;
|
|
||||||
color: rgba(0, 0, 0, 0.65);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.025);
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
margin: 15px 8px 15px 0;
|
|
||||||
color: #1890ff;
|
|
||||||
background: hsla(0, 0%, 100%, 0.85);
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.dark {
|
|
||||||
.user-wrapper {
|
|
||||||
.action {
|
|
||||||
color: rgba(255, 255, 255, 0.85);
|
|
||||||
a {
|
|
||||||
color: rgba(255, 255, 255, 0.85);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mobile,
|
|
||||||
&.tablet {
|
|
||||||
.top-nav-header-index {
|
|
||||||
.header-index-wide {
|
|
||||||
.header-index-left {
|
|
||||||
.trigger {
|
|
||||||
color: rgba(255, 255, 255, 0.85);
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo.top-nav-header {
|
|
||||||
flex: 0 0 56px;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 58px;
|
|
||||||
h1 {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.light {
|
|
||||||
.header-index-wide {
|
|
||||||
.header-index-left {
|
|
||||||
.trigger {
|
|
||||||
color: rgba(0, 0, 0, 0.65);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.tablet {
|
|
||||||
// overflow: hidden; text-overflow:ellipsis; white-space: nowrap;
|
|
||||||
.top-nav-header-index {
|
|
||||||
.header-index-wide {
|
|
||||||
.header-index-left {
|
|
||||||
.logo > a {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ant-menu.ant-menu-horizontal {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-nav-header-index {
|
|
||||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
|
||||||
position: relative;
|
|
||||||
transition: background 0.3s, width 0.2s;
|
|
||||||
|
|
||||||
.header-index-wide {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: auto;
|
|
||||||
padding-left: 0;
|
|
||||||
display: flex;
|
|
||||||
height: 55px;
|
|
||||||
|
|
||||||
.ant-menu.ant-menu-horizontal {
|
|
||||||
max-width: 835px;
|
|
||||||
flex: 0 1 835px;
|
|
||||||
border: none;
|
|
||||||
height: 55px;
|
|
||||||
line-height: 55px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-index-left {
|
|
||||||
flex: 0 1 1000px;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.logo.top-nav-header {
|
|
||||||
flex: 0 0 165px;
|
|
||||||
width: 165px;
|
|
||||||
height: 55px;
|
|
||||||
position: relative;
|
|
||||||
line-height: 55px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
img,
|
|
||||||
svg {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: #fff;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 0 0 0 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-index-right {
|
|
||||||
flex: 0 0 238px;
|
|
||||||
align-self: flex-end;
|
|
||||||
height: 55px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.content-box {
|
|
||||||
float: right;
|
|
||||||
.action {
|
|
||||||
max-width: 140px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow:ellipsis;
|
|
||||||
white-space:nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.light {
|
|
||||||
background-color: #fff;
|
|
||||||
|
|
||||||
.header-index-wide {
|
|
||||||
.header-index-left {
|
|
||||||
.logo {
|
|
||||||
h1 {
|
|
||||||
color: #002140;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 内容区
|
|
||||||
.layout-content {
|
|
||||||
margin: 24px 24px 0px;
|
|
||||||
//height: 100%;
|
|
||||||
//height: 64px;
|
|
||||||
padding: 0 12px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// footer
|
|
||||||
.ant-layout-footer {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.topmenu {
|
|
||||||
.page-header-index-wide {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// drawer-sider 自定义
|
|
||||||
.ant-drawer.drawer-sider {
|
|
||||||
.sider {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.dark {
|
|
||||||
.ant-drawer-content {
|
|
||||||
background-color: rgb(0, 21, 41);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.light {
|
|
||||||
box-shadow: none;
|
|
||||||
.ant-drawer-content {
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-drawer-body {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 菜单样式
|
|
||||||
.sider {
|
|
||||||
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
|
|
||||||
position: relative;
|
|
||||||
z-index: @ant-global-sider-zindex;
|
|
||||||
min-height: 100vh;
|
|
||||||
|
|
||||||
.ant-layout-sider-children {
|
|
||||||
overflow-y: hidden;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ant-fixed-sidemenu {
|
|
||||||
position: fixed;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
// logo区域样式
|
|
||||||
.logo {
|
|
||||||
position: relative;
|
|
||||||
height: 55px;
|
|
||||||
padding-left: 24px;
|
|
||||||
overflow: hidden;
|
|
||||||
line-height: 55px;
|
|
||||||
background: #002140;
|
|
||||||
transition: all .3s;
|
|
||||||
|
|
||||||
img,
|
|
||||||
svg,
|
|
||||||
h1 {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
img,
|
|
||||||
svg {
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 20px;
|
|
||||||
margin: 0 0 0 12px;
|
|
||||||
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
|
||||||
font-weight: 600;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.light {
|
|
||||||
background-color: #fff;
|
|
||||||
box-shadow: 2px 0px 8px 0px rgba(29, 35, 41, 0.05);
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 1px 1px 0px 0px #e8e8e8;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-menu-light {
|
|
||||||
border-right-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 外置的样式控制
|
|
||||||
.user-dropdown-menu {
|
|
||||||
span {
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.user-dropdown-menu-wrapper.ant-dropdown-menu {
|
|
||||||
padding: 4px 0;
|
|
||||||
|
|
||||||
.ant-dropdown-menu-item {
|
|
||||||
width: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-dropdown-menu-item > .anticon:first-child,
|
|
||||||
.ant-dropdown-menu-item > a > .anticon:first-child,
|
|
||||||
.ant-dropdown-menu-submenu-title > .anticon:first-child .ant-dropdown-menu-submenu-title > a > .anticon:first-child {
|
|
||||||
min-width: 12px;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 数据列表 样式
|
|
||||||
.table-alert {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-page-search-wrapper {
|
|
||||||
.ant-form-inline {
|
|
||||||
.ant-form-item {
|
|
||||||
display: flex;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
margin-right: 0;
|
|
||||||
|
|
||||||
.ant-form-item-control-wrapper {
|
|
||||||
flex: 1 1;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .ant-form-item-label {
|
|
||||||
line-height: 32px;
|
|
||||||
padding-right: 8px;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
.ant-form-item-control {
|
|
||||||
height: 32px;
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-page-search-submitButtons {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
.table-operator {
|
|
||||||
margin-bottom: 18px;
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
@import "ant-design-vue/lib/style/index";
|
|
||||||
|
|
||||||
// The prefix to use on all css classes from ant-pro.
|
|
||||||
@ant-pro-prefix : ant-pro;
|
|
||||||
@ant-global-sider-zindex : 106;
|
|
||||||
@ant-global-header-zindex : 105;
|
|
||||||
@@ -18,6 +18,9 @@ const DEFAULT_CONFIG = {
|
|||||||
// 请求超时
|
// 请求超时
|
||||||
TIMEOUT: 60000,
|
TIMEOUT: 60000,
|
||||||
|
|
||||||
|
// 版本更新时间 默认10s
|
||||||
|
UPDATE_VERSION_TIME: 10000,
|
||||||
|
|
||||||
// TokenName // Authorization
|
// TokenName // Authorization
|
||||||
TOKEN_NAME: 'token',
|
TOKEN_NAME: 'token',
|
||||||
|
|
||||||
|
|||||||
@@ -77,14 +77,18 @@
|
|||||||
import { layoutEnum } from '@/layout/enum/layoutEnum'
|
import { layoutEnum } from '@/layout/enum/layoutEnum'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import tool from '@/utils/tool'
|
import tool from '@/utils/tool'
|
||||||
import { message } from 'ant-design-vue'
|
import { notification, Button } from 'ant-design-vue'
|
||||||
import ClassicalMenu from '@/layout/menu/classicalMenu.vue'
|
import ClassicalMenu from '@/layout/menu/classicalMenu.vue'
|
||||||
import DoubleRowMenu from '@/layout/menu/doubleRowMenu.vue'
|
import DoubleRowMenu from '@/layout/menu/doubleRowMenu.vue'
|
||||||
import TopMenu from '@/layout/menu/topMenu.vue'
|
import TopMenu from '@/layout/menu/topMenu.vue'
|
||||||
import { NextLoading } from '@/utils/loading'
|
import { NextLoading } from '@/utils/loading'
|
||||||
import { useMenuStore } from '@/store/menu'
|
import { useMenuStore } from '@/store/menu'
|
||||||
import { userStore } from '@/store/user'
|
import { userStore } from '@/store/user'
|
||||||
|
import { getLocalHash, checkHash } from '@/utils/version'
|
||||||
|
import sysConfig from '@/config/index'
|
||||||
|
import dictApi from '@/api/dev/dictApi'
|
||||||
|
|
||||||
|
let timer = null
|
||||||
const store = globalStore()
|
const store = globalStore()
|
||||||
const kStore = keepAliveStore()
|
const kStore = keepAliveStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -246,15 +250,71 @@
|
|||||||
switchoverTopHeaderThemeColor()
|
switchoverTopHeaderThemeColor()
|
||||||
settingTopHeaderThemeOrColor(theme.value, layout.value)
|
settingTopHeaderThemeOrColor(theme.value, layout.value)
|
||||||
settingFixedWidth()
|
settingFixedWidth()
|
||||||
|
updateVersion()
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
getNav(menu.value)
|
getNav()
|
||||||
// 刷新登录信息,不影响其他
|
// 刷新登录信息,不影响其他
|
||||||
userStore().refreshUserLoginUserInfo()
|
userStore().refreshUserLoginUserInfo()
|
||||||
|
// 刷新菜单信息,不影响其他
|
||||||
|
useMenuStore().refreshApiMenu()
|
||||||
|
// 刷新字典信息,不影响其他
|
||||||
|
dictApi.dictTree().then((data) => {
|
||||||
|
// 设置字典到store中
|
||||||
|
tool.data.set('DICT_TYPE_TREE_DATA', data)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearUpdateVersion()
|
||||||
|
window.removeEventListener('resize', onLayoutResize)
|
||||||
|
window.removeEventListener('resize', getNav)
|
||||||
|
})
|
||||||
|
// 新版检测
|
||||||
|
const updateVersion = () => {
|
||||||
|
timer = setInterval(async () => {
|
||||||
|
// 本地
|
||||||
|
let localVersion = getLocalHash()
|
||||||
|
// 线上
|
||||||
|
let onlineVersion = await checkHash()
|
||||||
|
// 如果不一样,提示更新
|
||||||
|
if (localVersion !== onlineVersion) {
|
||||||
|
if (document.querySelector('.notification-update-version')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const key = `open${Date.now()}`
|
||||||
|
notification.open({
|
||||||
|
type: 'info',
|
||||||
|
message: '发现新版本',
|
||||||
|
description: '检测到新版本,请刷新后使用',
|
||||||
|
duration: 0,
|
||||||
|
class: 'notification-update-version',
|
||||||
|
btn: () =>
|
||||||
|
h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
type: 'primary',
|
||||||
|
size: 'small',
|
||||||
|
onClick: () => {
|
||||||
|
notification.close(key)
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ default: () => '立即更新' }
|
||||||
|
),
|
||||||
|
key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, sysConfig.UPDATE_VERSION_TIME)
|
||||||
|
}
|
||||||
|
// 销毁定时器
|
||||||
|
const clearUpdateVersion = () => {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer)
|
||||||
|
timer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
// 动态获取横向导航栏隐藏数量
|
// 动态获取横向导航栏隐藏数量
|
||||||
const getNav = (items) => {
|
const getNav = () => {
|
||||||
const item = menu.value
|
|
||||||
// 判断一下是不是顶部导航栏的模式
|
// 判断一下是不是顶部导航栏的模式
|
||||||
if (layout.value !== 'top') return
|
if (layout.value !== 'top') return
|
||||||
const menuNavList = menu.value
|
const menuNavList = menu.value
|
||||||
@@ -337,7 +397,7 @@
|
|||||||
switchoverTopHeaderThemeColor()
|
switchoverTopHeaderThemeColor()
|
||||||
// top下的顶栏
|
// top下的顶栏
|
||||||
settingTopHeaderThemeOrColor(theme.value, newValue)
|
settingTopHeaderThemeOrColor(theme.value, newValue)
|
||||||
getNav(menu.value)
|
getNav()
|
||||||
settingFixedWidth()
|
settingFixedWidth()
|
||||||
let element = document.querySelector('#xn-line-nav')
|
let element = document.querySelector('#xn-line-nav')
|
||||||
if (element) {
|
if (element) {
|
||||||
@@ -572,7 +632,7 @@
|
|||||||
message.warning('该模块下无任何菜单')
|
message.warning('该模块下无任何菜单')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getNav(menu.value)
|
getNav()
|
||||||
}
|
}
|
||||||
// 通过标签切换应用
|
// 通过标签切换应用
|
||||||
const tagSwitchModule = (id) => {
|
const tagSwitchModule = (id) => {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
* 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
|
* 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
|
||||||
*/
|
*/
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import { notification } from 'ant-design-vue'
|
|
||||||
import NProgress from 'nprogress'
|
import NProgress from 'nprogress'
|
||||||
import 'nprogress/nprogress.css'
|
import 'nprogress/nprogress.css'
|
||||||
import systemRouter from './systemRouter'
|
import systemRouter from './systemRouter'
|
||||||
@@ -128,7 +127,7 @@ router.afterEach((to, from) => {
|
|||||||
router.onError((error) => {
|
router.onError((error) => {
|
||||||
NProgress.done()
|
NProgress.done()
|
||||||
window.nextLoading && NextLoading.done()
|
window.nextLoading && NextLoading.done()
|
||||||
notification.error({
|
console.error({
|
||||||
message: '路由错误',
|
message: '路由错误',
|
||||||
description: error.message
|
description: error.message
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ import userCenterApi from '@/api/sys/userCenterApi'
|
|||||||
import whiteList from '@/router/whiteList'
|
import whiteList from '@/router/whiteList'
|
||||||
import routesData from '@/router/systemRouter'
|
import routesData from '@/router/systemRouter'
|
||||||
|
|
||||||
const modules = import.meta.glob('/src/views/**/**.vue')
|
// findPwd和login路由组件已静态加载,此处不在进行异步加载
|
||||||
|
const modules = import.meta.glob([
|
||||||
|
'/src/views/**/**.vue',
|
||||||
|
'!/src/views/auth/findPwd/**.vue',
|
||||||
|
'!/src/views/auth/login/**.vue'
|
||||||
|
])
|
||||||
export const useMenuStore = defineStore('menuStore', () => {
|
export const useMenuStore = defineStore('menuStore', () => {
|
||||||
const menuData = ref([])
|
const menuData = ref([])
|
||||||
const refreshFlag = ref(false)
|
const refreshFlag = ref(false)
|
||||||
@@ -119,24 +124,33 @@ export const useMenuStore = defineStore('menuStore', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 获取用户菜单
|
// 获取用户菜单(通过API重新初始化菜单,用于界面实时响应)
|
||||||
const fetchMenu = async () => {
|
const fetchMenu = async () => {
|
||||||
const menu = await userCenterApi.userLoginMenu()
|
const menu = await userCenterApi.userLoginMenu()
|
||||||
tool.data.set('MENU', menu)
|
tool.data.set('MENU', menu)
|
||||||
refreshMenu()
|
refreshMenu()
|
||||||
}
|
}
|
||||||
// 刷新菜单
|
// 刷新菜单(非API刷新,用于路由守卫内使用)
|
||||||
const refreshMenu = () => {
|
const refreshMenu = () => {
|
||||||
loadMenu()
|
loadMenu()
|
||||||
removeFromRouter()
|
removeFromRouter()
|
||||||
addToRouter()
|
addToRouter()
|
||||||
changeRefreshFlag(true)
|
changeRefreshFlag(true)
|
||||||
}
|
}
|
||||||
|
// 通过API刷新菜单(仅在layout的onMounted内使用,浏览器刷新只刷新一次)
|
||||||
|
const refreshApiMenu = () => {
|
||||||
|
userCenterApi.userLoginMenu().then((data) => {
|
||||||
|
tool.data.set('MENU', data)
|
||||||
|
nextTick(() => {
|
||||||
|
refreshMenu()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
// 将菜单添加到路由
|
// 将菜单添加到路由
|
||||||
const addToRouter = () => {
|
const addToRouter = () => {
|
||||||
menuData.value.forEach((item) => {
|
menuData.value.forEach((item) => {
|
||||||
router.addRoute('layout', item)
|
router.addRoute('layout', item)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return { menuData, loadMenu, addToRouter, refreshMenu, changeRefreshFlag, refreshFlag, fetchMenu }
|
return { menuData, loadMenu, addToRouter, refreshMenu, changeRefreshFlag, refreshFlag, fetchMenu, refreshApiMenu }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -367,6 +367,7 @@ body,
|
|||||||
.selector-table,
|
.selector-table,
|
||||||
.card-div,
|
.card-div,
|
||||||
.ant-table-body,
|
.ant-table-body,
|
||||||
|
.dict-tree-div,
|
||||||
|
|
||||||
.admin-ui-main{
|
.admin-ui-main{
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const required = (message, trigger = ['blur', 'change']) => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 常用正则规则大全:https://any86.github.io/any-rule/
|
// 常用正则规则大全:https://any86.github.io/any-rule/
|
||||||
|
// 表单上面使用参照菜单管理的 title 字段,例如:-> title: [required('请输入菜单名称'), rules.horizontalChart]
|
||||||
export const rules = {
|
export const rules = {
|
||||||
phone: {
|
phone: {
|
||||||
pattern: /^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/,
|
pattern: /^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/,
|
||||||
@@ -47,5 +47,17 @@ export const rules = {
|
|||||||
pattern: /(?:^[1-9]([0-9]+)?(?:\.[0-9]{1,2})?$)|(?:^(?:0)$)|(?:^[0-9]\.[0-9](?:[0-9])?$)/,
|
pattern: /(?:^[1-9]([0-9]+)?(?:\.[0-9]{1,2})?$)|(?:^(?:0)$)|(?:^[0-9]\.[0-9](?:[0-9])?$)/,
|
||||||
message: '只支持正数金额',
|
message: '只支持正数金额',
|
||||||
trigger: 'blur'
|
trigger: 'blur'
|
||||||
|
},
|
||||||
|
horizontalChart: {
|
||||||
|
pattern: /^[^-]*$/,
|
||||||
|
message: '不可包含横杠 “-”'
|
||||||
|
},
|
||||||
|
initialNotBackslashChart: {
|
||||||
|
pattern: /^(?!\/)[\s\S]*$/,
|
||||||
|
message: '首字母不可出现反斜杠 “/”'
|
||||||
|
},
|
||||||
|
initialYesBackslashChart: {
|
||||||
|
pattern: /^\/[^/].*$/,
|
||||||
|
message: '首字母必须是反斜杠 “/”'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { nextTick } from 'vue'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 页面全局 Loading
|
* 页面全局 Loading
|
||||||
* @method start 创建 loading
|
* @method start 创建 loading
|
||||||
@@ -10,19 +8,18 @@ export const NextLoading = {
|
|||||||
start: () => {
|
start: () => {
|
||||||
const el = document.querySelector('.admin-ui')
|
const el = document.querySelector('.admin-ui')
|
||||||
if (el) return
|
if (el) return
|
||||||
const bodys = document.body
|
const body = document.body
|
||||||
const div = document.createElement('div')
|
const div = document.createElement('div')
|
||||||
div.setAttribute('class', 'admin-ui')
|
div.setAttribute('class', 'admin-ui')
|
||||||
const htmls = `
|
div.innerHTML = `
|
||||||
<div class="app-loading">
|
<div class="app-loading">
|
||||||
<div class="app-loading__logo">
|
<div class="app-loading-logo">
|
||||||
<img src="/img/logo.png"/>
|
<img src="/img/logo.png"/>
|
||||||
</div>
|
</div>
|
||||||
<div><span class="dot dot-spin"><i></i><i></i><i></i><i></i></span></div>
|
<div><span class="dot dot-spin"><i></i><i></i><i></i><i></i></span></div>
|
||||||
<div class="app-loading__title">Snowy</div>
|
<div class="app-loading-title">Snowy</div>
|
||||||
</div>`
|
</div>`
|
||||||
div.innerHTML = htmls
|
body.insertBefore(div, body.childNodes[0])
|
||||||
bodys.insertBefore(div, bodys.childNodes[0])
|
|
||||||
window.nextLoading = true
|
window.nextLoading = true
|
||||||
},
|
},
|
||||||
// 移除 loading
|
// 移除 loading
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
// 统一的请求发送
|
// 统一的请求发送
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import qs from 'qs'
|
import qs from 'qs'
|
||||||
import { Modal, message, notification } from 'ant-design-vue'
|
import { Modal, message } from 'ant-design-vue'
|
||||||
import sysConfig from '@/config/index'
|
import sysConfig from '@/config/index'
|
||||||
import tool from '@/utils/tool'
|
import tool from '@/utils/tool'
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ service.interceptors.response.use(
|
|||||||
if (error) {
|
if (error) {
|
||||||
const status = 503
|
const status = 503
|
||||||
const description = errorCodeMap[status]
|
const description = errorCodeMap[status]
|
||||||
notification.error({
|
console.error({
|
||||||
message: '请求错误',
|
message: '请求错误',
|
||||||
description
|
description
|
||||||
})
|
})
|
||||||
|
|||||||
14
snowy-admin-web/src/utils/treeHandler.js
Normal file
14
snowy-admin-web/src/utils/treeHandler.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// 递归选中或取消子节点(a-tree专属方法)
|
||||||
|
export const checkOrUnCheckChildren = (checked, node, checkedKeys) => {
|
||||||
|
if (node.children) {
|
||||||
|
node.children.forEach((item) => {
|
||||||
|
if (checked) {
|
||||||
|
checkedKeys.checked.push(item.id)
|
||||||
|
} else {
|
||||||
|
checkedKeys.checked = checkedKeys.checked.filter((k) => k !== item.id)
|
||||||
|
}
|
||||||
|
checkOrUnCheckChildren(checked, item, checkedKeys)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return checkedKeys.checked
|
||||||
|
}
|
||||||
35
snowy-admin-web/src/utils/version.js
Normal file
35
snowy-admin-web/src/utils/version.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// 获取app.js 的哈希值
|
||||||
|
const getAppHash = (scripts) => {
|
||||||
|
let localVersion = ''
|
||||||
|
for (let i = 0; i < scripts.length; i++) {
|
||||||
|
let src = scripts[i].getAttribute('src')
|
||||||
|
if (src && src.indexOf('main.') !== -1) {
|
||||||
|
// 返回时间戳
|
||||||
|
localVersion = src.split('t=')[1] || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return localVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取本地的app.js版本号
|
||||||
|
export const getLocalHash = () => {
|
||||||
|
return getAppHash(document.getElementsByTagName('script'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取线上的app.js版本号
|
||||||
|
export const checkHash = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 加上时间戳,防止缓存
|
||||||
|
fetch('/?t=' + Date.now())
|
||||||
|
.then(async (res) => {
|
||||||
|
let html = await res.text() //转成字符串判断
|
||||||
|
let doc = new DOMParser().parseFromString(html, 'text/html')
|
||||||
|
let newVersion = getAppHash(doc.getElementsByTagName('script'))
|
||||||
|
resolve(newVersion)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('获取版本号失败', err)
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -139,8 +139,7 @@
|
|||||||
}
|
}
|
||||||
.login-form {
|
.login-form {
|
||||||
width: 450px;
|
width: 450px;
|
||||||
position: absolute;
|
margin-top: 110px;
|
||||||
top: 21.8%;
|
|
||||||
}
|
}
|
||||||
.login-header {
|
.login-header {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@@ -175,7 +174,7 @@
|
|||||||
.logo_background {
|
.logo_background {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 56px;
|
top: 50px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
padding-left: 56px;
|
padding-left: 56px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -240,11 +239,8 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
.login_background_front {
|
.logo_background {
|
||||||
display: none;
|
padding-left: 40px;
|
||||||
}
|
|
||||||
.logo_background{
|
|
||||||
padding-left:40px;
|
|
||||||
}
|
}
|
||||||
.login-form {
|
.login-form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -23,50 +23,32 @@
|
|||||||
background-image: url(/img/login_background.png);
|
background-image: url(/img/login_background.png);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.login_background_front {
|
|
||||||
width: 450px;
|
|
||||||
height: 450px;
|
|
||||||
margin-left: 100px;
|
|
||||||
margin-top: 15%;
|
|
||||||
overflow: hidden;
|
|
||||||
/*position: relative;*/
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
background-image: url(/img/login_background_front.png);
|
|
||||||
animation-name: myfirst;
|
|
||||||
animation-duration: 5s;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-delay: 1s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
animation-direction: alternate;
|
|
||||||
animation-play-state: running;
|
|
||||||
}
|
|
||||||
@keyframes myfirst {
|
@keyframes myfirst {
|
||||||
0% {
|
0% {
|
||||||
left: 0px;
|
left: 0;
|
||||||
top: 0px;
|
top: 0;
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
left: 50px;
|
left: 50px;
|
||||||
top: 0px;
|
top: 0;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
left: 0px;
|
left: 0;
|
||||||
top: 0px;
|
top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@-webkit-keyframes myfirst /* Safari and Chrome */ {
|
@-webkit-keyframes myfirst {
|
||||||
0% {
|
0% {
|
||||||
left: 0px;
|
left: 0;
|
||||||
top: 0px;
|
top: 0;
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
left: 50px;
|
left: 50px;
|
||||||
top: 0px;
|
top: 0;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
left: 0px;
|
left: 0;
|
||||||
top: 0px;
|
top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.login_adv__title h2 {
|
.login_adv__title h2 {
|
||||||
@@ -107,8 +89,7 @@
|
|||||||
}
|
}
|
||||||
.login-form {
|
.login-form {
|
||||||
width: 450px;
|
width: 450px;
|
||||||
position: absolute;
|
margin-top: 110px;
|
||||||
top:21.8%
|
|
||||||
}
|
}
|
||||||
.login-header {
|
.login-header {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@@ -126,7 +107,7 @@
|
|||||||
.logo_background{
|
.logo_background{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 56px;
|
top: 50px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
padding-left: 56px;
|
padding-left: 56px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -180,9 +161,6 @@
|
|||||||
left:0;
|
left:0;
|
||||||
right:0;
|
right:0;
|
||||||
}
|
}
|
||||||
.login_background_front {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.login-form {
|
.login-form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px 40px;
|
padding: 20px 40px;
|
||||||
|
|||||||
@@ -170,7 +170,9 @@
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
let formData = ref(configData.SYS_BASE_CONFIG)
|
let formData = ref(configData.SYS_BASE_CONFIG)
|
||||||
configApi.configSysBaseList().then((data) => {
|
configApi
|
||||||
|
.configSysBaseList()
|
||||||
|
.then((data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
formData.value[item.configKey] = item.configValue
|
formData.value[item.configKey] = item.configValue
|
||||||
@@ -181,6 +183,7 @@
|
|||||||
refreshSwitch()
|
refreshSwitch()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
@@ -238,7 +241,7 @@
|
|||||||
// 获取token
|
// 获取token
|
||||||
try {
|
try {
|
||||||
const loginToken = await loginApi.login(loginData)
|
const loginToken = await loginApi.login(loginData)
|
||||||
const loginAfter = afterLogin(loginToken)
|
await afterLogin(loginToken)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
if (captchaOpen.value === 'true') {
|
if (captchaOpen.value === 'true') {
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<xn-form-container
|
<xn-form-container title="详情" :width="1000" v-model:open="open" :destroy-on-close="true" @close="onClose">
|
||||||
title="详情"
|
|
||||||
:width="1000"
|
|
||||||
v-model:open="open"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
@close="onClose"
|
|
||||||
>
|
|
||||||
<a-descriptions bordered>
|
<a-descriptions bordered>
|
||||||
<a-descriptions-item label="标题">{{formData.title}}</a-descriptions-item>
|
<a-descriptions-item label="标题">{{formData.title}}</a-descriptions-item>
|
||||||
<a-descriptions-item label="类型">
|
<a-descriptions-item label="类型">
|
||||||
|
|||||||
@@ -118,17 +118,18 @@
|
|||||||
</a-row>
|
</a-row>
|
||||||
|
|
||||||
<a-form-item label="任职信息" name="positionJson">
|
<a-form-item label="任职信息" name="positionJson">
|
||||||
<a-button type="primary" class="childAddButton" @click="addDomains()">
|
|
||||||
<PlusOutlined />
|
|
||||||
增加任职
|
|
||||||
</a-button>
|
|
||||||
<a-row :gutter="10" class="form-row">
|
<a-row :gutter="10" class="form-row">
|
||||||
<a-col :span="7" class="form-row-con"> 机构 </a-col>
|
<a-col :span="7" class="form-row-con"> 机构 </a-col>
|
||||||
<a-col :span="7" class="form-row-con"> 职位 </a-col>
|
<a-col :span="7" class="form-row-con"> 职位 </a-col>
|
||||||
<a-col :span="7" class="form-row-con"> 主管 </a-col>
|
<a-col :span="7" class="form-row-con"> 主管 </a-col>
|
||||||
<a-col :span="3" class="form-row-con"> 操作 </a-col>
|
<a-col :span="3" class="form-row-con">
|
||||||
|
<a-button type="primary" @click="addDomains()" size="small">
|
||||||
|
<PlusOutlined />
|
||||||
|
增加
|
||||||
|
</a-button>
|
||||||
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<div v-for="(positionInfo, index) in formData.positionJson" class="form-div">
|
<div :key="positionInfo" v-for="(positionInfo, index) in formData.positionJson">
|
||||||
<a-row :gutter="10">
|
<a-row :gutter="10">
|
||||||
<a-col :span="7">
|
<a-col :span="7">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
@@ -545,24 +546,15 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped type="less">
|
<style scoped lang="less">
|
||||||
.childAddButton {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.form-row {
|
.form-row {
|
||||||
background-color: var(--item-hover-bg);
|
background-color: var(--item-hover-bg);
|
||||||
margin-left: 0px !important;
|
margin-left: 0 !important;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
.form-row-con {
|
.form-row-con {
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
}
|
}
|
||||||
.dashedButton {
|
|
||||||
margin-top: 10px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.form-div {
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,184 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a-row :gutter="10">
|
|
||||||
<a-col :xs="24" :sm="24" :md="24" :lg="5" :xl="5">
|
|
||||||
<a-tree
|
|
||||||
v-if="treeData.length > 0"
|
|
||||||
v-model:expandedKeys="defaultExpandedKeys"
|
|
||||||
:tree-data="treeData"
|
|
||||||
:field-names="treeFieldNames"
|
|
||||||
@select="treeSelect"
|
|
||||||
>
|
|
||||||
</a-tree>
|
|
||||||
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
|
||||||
</a-col>
|
|
||||||
<a-col :xs="24" :sm="24" :md="24" :lg="19" :xl="19">
|
|
||||||
<a-form ref="searchFormRef" name="advanced_search" class="ant-advanced-search-form" :model="searchFormState">
|
|
||||||
<a-row :gutter="24">
|
|
||||||
<a-col :span="8">
|
|
||||||
<a-form-item name="searchKey" label="字典名称">
|
|
||||||
<a-input v-model:value="searchFormState.searchKey" placeholder="请输入字典名称" />
|
|
||||||
</a-form-item>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="8">
|
|
||||||
<a-button type="primary" @click="tableRef.refresh(true)">
|
|
||||||
<template #icon><SearchOutlined /></template>
|
|
||||||
查询
|
|
||||||
</a-button>
|
|
||||||
<a-button class="snowy-button-left" @click="reset">
|
|
||||||
<template #icon><redo-outlined /></template>
|
|
||||||
重置
|
|
||||||
</a-button>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-form>
|
|
||||||
<a-divider class="m-3 mx-0" />
|
|
||||||
<s-table
|
|
||||||
ref="tableRef"
|
|
||||||
:columns="columns"
|
|
||||||
:data="loadData"
|
|
||||||
:expand-row-by-click="true"
|
|
||||||
bordered
|
|
||||||
:tool-config="toolConfig"
|
|
||||||
:row-key="(record) => record.id"
|
|
||||||
>
|
|
||||||
<template #operator class="table-operator">
|
|
||||||
<a-button type="primary" @click="formRef.onOpen(undefined, 'FRM', searchFormState.parentId)">
|
|
||||||
<template #icon><plus-outlined /></template>
|
|
||||||
新增
|
|
||||||
</a-button>
|
|
||||||
</template>
|
|
||||||
<template #bodyCell="{ column, record }">
|
|
||||||
<template v-if="column.dataIndex === 'level'">
|
|
||||||
<a-tag color="blue" v-if="record.level">{{ record.level }}</a-tag>
|
|
||||||
<a-tag color="green" v-else>子级</a-tag>
|
|
||||||
</template>
|
|
||||||
<template v-if="column.dataIndex === 'action'">
|
|
||||||
<a @click="formRef.onOpen(record, 'FRM')">编辑</a>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</s-table>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
<Form ref="formRef" @successful="formSuccessful()" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { Empty } from 'ant-design-vue'
|
|
||||||
import dictApi from '@/api/dev/dictApi'
|
|
||||||
import Form from './form.vue'
|
|
||||||
import tool from '@/utils/tool'
|
|
||||||
const searchFormState = ref({})
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: '字典名称',
|
|
||||||
dataIndex: 'dictLabel',
|
|
||||||
width: 350
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '字典值',
|
|
||||||
dataIndex: 'dictValue',
|
|
||||||
width: 350
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '排序',
|
|
||||||
dataIndex: 'sortCode'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
dataIndex: 'action',
|
|
||||||
align: 'center',
|
|
||||||
width: '150px'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
// 定义tableDOM
|
|
||||||
const tableRef = ref(null)
|
|
||||||
const formRef = ref()
|
|
||||||
const searchFormRef = ref()
|
|
||||||
// 默认展开的节点
|
|
||||||
const defaultExpandedKeys = ref([])
|
|
||||||
const treeData = ref([])
|
|
||||||
// 替换treeNode 中 title,key,children
|
|
||||||
const treeFieldNames = { children: 'children', title: 'dictLabel', key: 'id' }
|
|
||||||
const toolConfig = { refresh: true, height: true, columnSetting: true, striped: false }
|
|
||||||
|
|
||||||
// 表格查询 返回 Promise 对象
|
|
||||||
const loadData = (parameter) => {
|
|
||||||
loadTreeData()
|
|
||||||
parameter.category = 'FRM'
|
|
||||||
return dictApi.dictPage(Object.assign(parameter, searchFormState.value)).then((data) => {
|
|
||||||
if (data.records) {
|
|
||||||
if (searchFormState.value.parentId) {
|
|
||||||
let dataArray = []
|
|
||||||
data.records.forEach((item) => {
|
|
||||||
const obj = data.records.find((f) => f.id === item.parentId)
|
|
||||||
if (!obj) {
|
|
||||||
dataArray.push(item)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (dataArray.length === 1) {
|
|
||||||
data.records.forEach((item) => {
|
|
||||||
if (item.id === dataArray[0].id) {
|
|
||||||
item.level = '上级'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
dataArray = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 重置
|
|
||||||
const reset = () => {
|
|
||||||
searchFormRef.value.resetFields()
|
|
||||||
tableRef.value.refresh(true)
|
|
||||||
}
|
|
||||||
// 加载左侧的树
|
|
||||||
const loadTreeData = () => {
|
|
||||||
const param = {
|
|
||||||
category: 'FRM'
|
|
||||||
}
|
|
||||||
dictApi.dictTree(param).then((res) => {
|
|
||||||
if (res) {
|
|
||||||
treeData.value = res
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 点击树查询
|
|
||||||
const treeSelect = (selectedKeys) => {
|
|
||||||
if (selectedKeys && selectedKeys.length > 0) {
|
|
||||||
searchFormState.value.parentId = selectedKeys.toString()
|
|
||||||
if (!columns.find((f) => f.title === '层级')) {
|
|
||||||
columns.splice(2, 0, {
|
|
||||||
title: '层级',
|
|
||||||
dataIndex: 'level',
|
|
||||||
width: 100
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
delete searchFormState.value.parentId
|
|
||||||
columns.splice(2, 1)
|
|
||||||
}
|
|
||||||
tableRef.value.refresh(true)
|
|
||||||
}
|
|
||||||
// 表单界面回调
|
|
||||||
const formSuccessful = () => {
|
|
||||||
tableRef.value.refresh()
|
|
||||||
refreshStoreDict()
|
|
||||||
}
|
|
||||||
// 刷新store中的字典
|
|
||||||
const refreshStoreDict = () => {
|
|
||||||
dictApi.dictTree().then((res) => {
|
|
||||||
tool.data.set('DICT_TYPE_TREE_DATA', res)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.ant-form-item {
|
|
||||||
margin-bottom: 0 !important;
|
|
||||||
}
|
|
||||||
.snowy-button-left {
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-row :gutter="10">
|
<a-row :gutter="10">
|
||||||
<a-col :xs="24" :sm="24" :md="24" :lg="5" :xl="5">
|
<a-col :xs="24" :sm="24" :md="24" :lg="5" :xl="5">
|
||||||
|
<div class="dict-tree-div">
|
||||||
<a-tree
|
<a-tree
|
||||||
v-if="treeData.length > 0"
|
v-if="treeData.length > 0"
|
||||||
v-model:expandedKeys="defaultExpandedKeys"
|
v-model:expandedKeys="defaultExpandedKeys"
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
>
|
>
|
||||||
</a-tree>
|
</a-tree>
|
||||||
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
<a-empty v-else :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
||||||
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :md="24" :lg="19" :xl="19">
|
<a-col :xs="24" :sm="24" :md="24" :lg="19" :xl="19">
|
||||||
<a-form ref="searchFormRef" name="advanced_search" class="ant-advanced-search-form mb-3" :model="searchFormState">
|
<a-form ref="searchFormRef" name="advanced_search" class="ant-advanced-search-form mb-3" :model="searchFormState">
|
||||||
@@ -42,7 +44,7 @@
|
|||||||
:row-key="(record) => record.id"
|
:row-key="(record) => record.id"
|
||||||
>
|
>
|
||||||
<template #operator class="table-operator">
|
<template #operator class="table-operator">
|
||||||
<a-button type="primary" @click="formRef.onOpen(undefined, 'BIZ', searchFormState.parentId)">
|
<a-button type="primary" @click="formRef.onOpen(undefined, categoryType, searchFormState.parentId)">
|
||||||
<template #icon><plus-outlined /></template>
|
<template #icon><plus-outlined /></template>
|
||||||
新增
|
新增
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -53,7 +55,7 @@
|
|||||||
<a-tag color="green" v-else>子级</a-tag>
|
<a-tag color="green" v-else>子级</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.dataIndex === 'action'">
|
<template v-if="column.dataIndex === 'action'">
|
||||||
<a @click="formRef.onOpen(record, 'BIZ')">编辑</a>
|
<a @click="formRef.onOpen(record, categoryType)">编辑</a>
|
||||||
<a-divider type="vertical" />
|
<a-divider type="vertical" />
|
||||||
<a-popconfirm title="删除此字典与下级字典吗?" @confirm="remove(record)">
|
<a-popconfirm title="删除此字典与下级字典吗?" @confirm="remove(record)">
|
||||||
<a-button type="link" danger size="small">删除</a-button>
|
<a-button type="link" danger size="small">删除</a-button>
|
||||||
@@ -66,11 +68,17 @@
|
|||||||
<Form ref="formRef" @successful="formSuccessful()" />
|
<Form ref="formRef" @successful="formSuccessful()" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup name="dictCategoryIndex">
|
||||||
import { Empty } from 'ant-design-vue'
|
import { Empty } from 'ant-design-vue'
|
||||||
import dictApi from '@/api/dev/dictApi'
|
import dictApi from '@/api/dev/dictApi'
|
||||||
import Form from './form.vue'
|
import Form from './form.vue'
|
||||||
import tool from '@/utils/tool'
|
import tool from '@/utils/tool'
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'FRM'
|
||||||
|
}
|
||||||
|
})
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '字典名称',
|
title: '字典名称',
|
||||||
@@ -93,6 +101,9 @@
|
|||||||
width: '150px'
|
width: '150px'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
const categoryType = computed(() => {
|
||||||
|
return props.type
|
||||||
|
})
|
||||||
// 定义tableDOM
|
// 定义tableDOM
|
||||||
const tableRef = ref(null)
|
const tableRef = ref(null)
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
@@ -108,7 +119,7 @@
|
|||||||
// 表格查询 返回 Promise 对象
|
// 表格查询 返回 Promise 对象
|
||||||
const loadData = (parameter) => {
|
const loadData = (parameter) => {
|
||||||
loadTreeData()
|
loadTreeData()
|
||||||
parameter.category = 'BIZ'
|
parameter.category = categoryType.value
|
||||||
return dictApi.dictPage(Object.assign(parameter, searchFormState.value)).then((data) => {
|
return dictApi.dictPage(Object.assign(parameter, searchFormState.value)).then((data) => {
|
||||||
if (data.records) {
|
if (data.records) {
|
||||||
if (searchFormState.value.parentId) {
|
if (searchFormState.value.parentId) {
|
||||||
@@ -140,7 +151,7 @@
|
|||||||
// 加载左侧的树
|
// 加载左侧的树
|
||||||
const loadTreeData = () => {
|
const loadTreeData = () => {
|
||||||
const param = {
|
const param = {
|
||||||
category: 'BIZ'
|
category: categoryType.value
|
||||||
}
|
}
|
||||||
dictApi.dictTree(param).then((res) => {
|
dictApi.dictTree(param).then((res) => {
|
||||||
if (res) {
|
if (res) {
|
||||||
@@ -172,8 +183,12 @@
|
|||||||
id: record.id
|
id: record.id
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
dictApi.dictDelete(params).then(() => {
|
dictApi.dictDelete(params).then((res) => {
|
||||||
|
if (res.code === 200) {
|
||||||
tableRef.value.refresh(true)
|
tableRef.value.refresh(true)
|
||||||
|
} else {
|
||||||
|
res.message && tool.error(res.message)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
refreshStoreDict()
|
refreshStoreDict()
|
||||||
}
|
}
|
||||||
@@ -190,11 +205,15 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="less">
|
||||||
.ant-form-item {
|
.ant-form-item {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
.snowy-button-left {
|
.snowy-button-left {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
.dict-tree-div {
|
||||||
|
height: 700px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,32 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-card
|
<a-card>
|
||||||
:bordered="false"
|
<a-tabs size="large" v-model:activeKey="activeKey">
|
||||||
:active-tab-key="activeKey"
|
<a-tab-pane v-for="item in tabListNoTitle" :key="item.key" :tab="item.tab">
|
||||||
:tab-list="tabListNoTitle"
|
<category :type="item.key" />
|
||||||
@tabChange="(key) => onTabChange(key, 'frmIndex')"
|
</a-tab-pane>
|
||||||
>
|
</a-tabs>
|
||||||
<p v-if="activeKey === 'frmIndex'">
|
|
||||||
<frm-index />
|
|
||||||
</p>
|
|
||||||
<p v-if="activeKey === 'bizIndex'">
|
|
||||||
<biz-index />
|
|
||||||
</p>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name="devDict">
|
<script setup name="devDict">
|
||||||
import frmIndex from './category/frmIndex.vue'
|
import Category from './category/index.vue'
|
||||||
import bizIndex from './category/bizIndex.vue'
|
const activeKey = ref('FRM')
|
||||||
const activeKey = ref('frmIndex')
|
|
||||||
const tabListNoTitle = ref([
|
const tabListNoTitle = ref([
|
||||||
{ key: 'frmIndex', tab: '系统字典' },
|
{ key: 'FRM', tab: '系统字典' },
|
||||||
{ key: 'bizIndex', tab: '业务字典' }
|
{ key: 'BIZ', tab: '业务字典' }
|
||||||
])
|
])
|
||||||
const onTabChange = (value, type) => {
|
|
||||||
if (type === 'key') {
|
|
||||||
key.value = value
|
|
||||||
} else if (type === 'frmIndex') {
|
|
||||||
activeKey.value = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(.ant-card-body) {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<xn-form-container
|
<xn-form-container title="详情" :width="700" :visible="visible" :destroy-on-close="true" @close="onClose">
|
||||||
title="详情"
|
|
||||||
:width="700"
|
|
||||||
:visible="visible"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
@close="onClose"
|
|
||||||
>
|
|
||||||
<a-descriptions :column="1" size="middle" bordered class="mb-2">
|
<a-descriptions :column="1" size="middle" bordered class="mb-2">
|
||||||
<a-descriptions-item label="名称">{{ formData.name }}</a-descriptions-item>
|
<a-descriptions-item label="名称">{{ formData.name }}</a-descriptions-item>
|
||||||
<a-descriptions-item label="请求IP">{{ formData.opIp }}</a-descriptions-item>
|
<a-descriptions-item label="请求IP">{{ formData.opIp }}</a-descriptions-item>
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<xn-form-container
|
<xn-form-container title="详情" :width="700" :visible="visible" :destroy-on-close="true" @close="onClose">
|
||||||
title="详情"
|
|
||||||
:width="700"
|
|
||||||
:visible="visible"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
@close="onClose"
|
|
||||||
>
|
|
||||||
<a-descriptions :column="1" size="middle" bordered class="mb-2">
|
<a-descriptions :column="1" size="middle" bordered class="mb-2">
|
||||||
<a-descriptions-item label="名称">{{ formData.name }}</a-descriptions-item>
|
<a-descriptions-item label="名称">{{ formData.name }}</a-descriptions-item>
|
||||||
<a-descriptions-item label="IP地址">{{ formData.opIp }}</a-descriptions-item>
|
<a-descriptions-item label="IP地址">{{ formData.opIp }}</a-descriptions-item>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<template #label>
|
<template #label>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template #title>
|
<template #title>
|
||||||
类型为内外链条时,输入https开头的链接即可(例:https://xiaonuo.vip),正常路由前面必须有反斜杠!
|
类型为内外链时,输入https开头的链接即可(例:https://xiaonuo.vip),正常路由前面必须有反斜杠!
|
||||||
</template>
|
</template>
|
||||||
<question-circle-outlined />
|
<question-circle-outlined />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name="sysResourceMenuForm">
|
<script setup name="sysResourceMenuForm">
|
||||||
import { required } from '@/utils/formRules'
|
import { required, rules } from '@/utils/formRules'
|
||||||
import { cloneDeep } from 'lodash-es'
|
import { cloneDeep } from 'lodash-es'
|
||||||
import SnowflakeId from 'snowflake-id'
|
import SnowflakeId from 'snowflake-id'
|
||||||
import tool from '@/utils/tool'
|
import tool from '@/utils/tool'
|
||||||
@@ -197,20 +197,20 @@
|
|||||||
// 图标选择器回调
|
// 图标选择器回调
|
||||||
const iconCallBack = (value) => {
|
const iconCallBack = (value) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
formRef.value.clearValidate("icon")
|
formRef.value.clearValidate('icon')
|
||||||
}
|
}
|
||||||
formData.value.icon = value
|
formData.value.icon = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认要校验的
|
// 默认要校验的
|
||||||
const formRules = {
|
const formRules = {
|
||||||
title: [required('请输入菜单名称')],
|
title: [required('请输入菜单名称'), rules.horizontalChart],
|
||||||
parentId: [required('请选择上级菜单')],
|
parentId: [required('请选择上级菜单')],
|
||||||
menuType: [required('请选择菜单类型')],
|
menuType: [required('请选择菜单类型')],
|
||||||
path: [required('请输入路由地址')],
|
path: [required('请输入路由地址')],
|
||||||
name: [required('请输入组件中name属性')],
|
name: [required('请输入组件中name属性')],
|
||||||
module: [required('请选择模块')],
|
module: [required('请选择模块')],
|
||||||
component: [required('请输入组件地址')],
|
component: [required('请输入组件地址'), rules.initialNotBackslashChart],
|
||||||
visible: [required('请选择是否可见')]
|
visible: [required('请选择是否可见')]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,8 +101,7 @@
|
|||||||
// firstShowMap = {} // 重置单元格合并映射
|
// firstShowMap = {} // 重置单元格合并映射
|
||||||
// 如果有数据,我们再不去反复的查询
|
// 如果有数据,我们再不去反复的查询
|
||||||
if (echoDatalist.value.length > 0) {
|
if (echoDatalist.value.length > 0) {
|
||||||
let data = echoDatalist.value.find((f) => f.id === moduleId.value).menu
|
loadDatas.value = echoDatalist.value.find((f) => f.id === moduleId.value).menu
|
||||||
loadDatas.value = data
|
|
||||||
} else {
|
} else {
|
||||||
// 获取表格数据
|
// 获取表格数据
|
||||||
spinningLoading.value = true
|
spinningLoading.value = true
|
||||||
@@ -144,11 +143,11 @@
|
|||||||
if (module.menu) {
|
if (module.menu) {
|
||||||
// 加入回显内容
|
// 加入回显内容
|
||||||
module.menu.forEach((item) => {
|
module.menu.forEach((item) => {
|
||||||
const menueCheck = ref(0)
|
const menusCheck = ref(0)
|
||||||
if (resEcho.grantInfoList.length > 0) {
|
if (resEcho.grantInfoList.length > 0) {
|
||||||
resEcho.grantInfoList.forEach((grant) => {
|
resEcho.grantInfoList.forEach((grant) => {
|
||||||
if (item.id === grant.menuId) {
|
if (item.id === grant.menuId) {
|
||||||
menueCheck.value++
|
menusCheck.value++
|
||||||
// 处理按钮
|
// 处理按钮
|
||||||
if (grant.buttonInfo) {
|
if (grant.buttonInfo) {
|
||||||
grant.buttonInfo.forEach((button) => {
|
grant.buttonInfo.forEach((button) => {
|
||||||
@@ -163,15 +162,23 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 回显前面的2个
|
// 回显前面的2个
|
||||||
if (menueCheck.value > 0) {
|
if (menusCheck.value > 0) {
|
||||||
item.parentCheck = true
|
item.parentCheck = true
|
||||||
item.nameCheck = true
|
item.nameCheck = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 排序
|
// 排序
|
||||||
module.menu = module.menu.sort((a, b) => {
|
module.menu.sort((a, b) => {
|
||||||
return a.parentId - b.parentId
|
// 首先比较parentName属性
|
||||||
|
let nameComparison = b.parentName.localeCompare(a.parentName)
|
||||||
|
if (nameComparison !== 0) {
|
||||||
|
// 如果parentName不同,直接返回parentName的比较结果
|
||||||
|
return nameComparison
|
||||||
|
} else {
|
||||||
|
// 如果name相同,则比较parentId属性,直接返回parentId的差值
|
||||||
|
return Number(a.parentId) - Number(b.parentId)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
// 缓存加入索引
|
// 缓存加入索引
|
||||||
module.menu.forEach((item, index) => {
|
module.menu.forEach((item, index) => {
|
||||||
@@ -200,12 +207,11 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
const checkAllChildNotChecked = (record) => {
|
const checkAllChildNotChecked = (record) => {
|
||||||
const allChecked = checkFieldKeys.every((key) => {
|
return checkFieldKeys.every((key) => {
|
||||||
// 遍历所有的字段
|
// 遍历所有的字段
|
||||||
const child = record[key]
|
const child = record[key]
|
||||||
return child.every((field) => !field.check)
|
return child.every((field) => !field.check)
|
||||||
})
|
})
|
||||||
return allChecked
|
|
||||||
}
|
}
|
||||||
const changeChildCheckBox = (record, evt) => {
|
const changeChildCheckBox = (record, evt) => {
|
||||||
let checked = evt.target.checked
|
let checked = evt.target.checked
|
||||||
@@ -298,7 +304,7 @@
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
/* 重写复选框的样式 */
|
/* 重写复选框的样式 */
|
||||||
.ant-checkbox-wrapper {
|
.ant-checkbox-wrapper {
|
||||||
margin-left: 0px !important;
|
margin-left: 0 !important;
|
||||||
padding-top: 2px !important;
|
padding-top: 2px !important;
|
||||||
padding-bottom: 2px !important;
|
padding-bottom: 2px !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,20 @@
|
|||||||
class="mt-4"
|
class="mt-4"
|
||||||
size="middle"
|
size="middle"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="loadDatas"
|
:data-source="tableLoadData"
|
||||||
:row-key="(record) => record.api"
|
:row-key="(record) => record.api"
|
||||||
|
:pagination="pagination"
|
||||||
|
@change="handleTableChange"
|
||||||
bordered
|
bordered
|
||||||
>
|
>
|
||||||
<template #headerCell="{ column }">
|
<template #headerCell="{ column }">
|
||||||
<template v-if="column.key === 'api'">
|
<template v-if="column.key === 'prefix'">
|
||||||
<a-checkbox @update:checked="(val) => onCheckAllChange(val)"> 接口 </a-checkbox>
|
<a-checkbox :checked="allChecked" @update:checked="(val) => onCheckAllChange(val)">
|
||||||
|
{{ column.title }}
|
||||||
|
</a-checkbox>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'suffix'">
|
||||||
|
<a-checkbox :checked="allChecked" @update:checked="(val) => onCheckAllChange(val)"> 接口 </a-checkbox>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'dataScope'">
|
<template v-if="column.key === 'dataScope'">
|
||||||
<span>{{ column.title }}</span>
|
<span>{{ column.title }}</span>
|
||||||
@@ -54,16 +61,21 @@
|
|||||||
<template #icon><SearchOutlined /></template>
|
<template #icon><SearchOutlined /></template>
|
||||||
搜索
|
搜索
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button size="small" class="xn-wd90" @click="handleReset(clearFilters)"> 重置 </a-button>
|
<a-button size="small" class="xn-wd90" @click="handleReset(clearFilters, confirm)"> 重置 </a-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #customFilterIcon="{ filtered }">
|
<template #customFilterIcon="{ filtered }">
|
||||||
<search-outlined :style="{ color: filtered ? '#108ee9' : undefined }" />
|
<search-outlined :style="{ color: filtered ? '#108ee9' : undefined }" />
|
||||||
</template>
|
</template>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.dataIndex === 'api'">
|
<template v-if="column.dataIndex === 'prefix'">
|
||||||
|
<a-checkbox :checked="record.parentCheck" @update:checked="(val) => changeParentApi(record, val)">
|
||||||
|
{{ record.prefix }}
|
||||||
|
</a-checkbox>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'suffix'">
|
||||||
<a-checkbox :checked="record.check" @update:checked="(val) => changeApi(record, val)">
|
<a-checkbox :checked="record.check" @update:checked="(val) => changeApi(record, val)">
|
||||||
{{ record.api }}
|
{{ record.suffix }}
|
||||||
</a-checkbox>
|
</a-checkbox>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.dataIndex === 'dataScope'">
|
<template v-if="column.dataIndex === 'dataScope'">
|
||||||
@@ -109,6 +121,7 @@
|
|||||||
import roleApi from '@/api/sys/roleApi'
|
import roleApi from '@/api/sys/roleApi'
|
||||||
import ScopeDefineOrg from './scopeDefineOrg.vue'
|
import ScopeDefineOrg from './scopeDefineOrg.vue'
|
||||||
import { userStore } from '@/store/user'
|
import { userStore } from '@/store/user'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const spinningLoading = ref(false)
|
const spinningLoading = ref(false)
|
||||||
@@ -117,7 +130,7 @@
|
|||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
const CustomValue = 'SCOPE_ORG_DEFINE'
|
const CustomValue = 'SCOPE_ORG_DEFINE'
|
||||||
// 抽屉的宽度
|
// 抽屉的宽度
|
||||||
const drawerWidth = 1000
|
const drawerWidth = 1050
|
||||||
// 自动获取宽度,默认获取浏览器的宽度的90%
|
// 自动获取宽度,默认获取浏览器的宽度的90%
|
||||||
//(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth) * 0.9
|
//(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth) * 0.9
|
||||||
let loadDatas = ref([])
|
let loadDatas = ref([])
|
||||||
@@ -128,13 +141,39 @@
|
|||||||
searchedColumn: ''
|
searchedColumn: ''
|
||||||
})
|
})
|
||||||
const searchInput = ref()
|
const searchInput = ref()
|
||||||
|
// 分页
|
||||||
|
const pagination = ref({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
showSizeChanger: true,
|
||||||
|
defaultPageSize: 10,
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100']
|
||||||
|
})
|
||||||
|
const firstShowMap = ref({})
|
||||||
|
// 全选
|
||||||
|
const allChecked = ref(false)
|
||||||
|
const tableLoadData = ref([])
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
key: 'api',
|
key: 'prefix',
|
||||||
|
title: '接口前缀',
|
||||||
|
dataIndex: 'prefix',
|
||||||
|
width: 140,
|
||||||
|
customCell: (row, index) => {
|
||||||
|
const indexArr = firstShowMap.value[row.prefix]
|
||||||
|
if (index === indexArr[0]) {
|
||||||
|
return { rowSpan: indexArr.length }
|
||||||
|
}
|
||||||
|
return { rowSpan: 0 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'suffix',
|
||||||
title: '接口',
|
title: '接口',
|
||||||
dataIndex: 'api',
|
dataIndex: 'suffix',
|
||||||
width: 380,
|
width: 290,
|
||||||
customFilterDropdown: true,
|
customFilterDropdown: true,
|
||||||
onFilter: (value, record) => record.api.includes(value),
|
onFilter: (value, record) => record.api.includes(value),
|
||||||
onFilterDropdownOpenChange: (visible) => {
|
onFilterDropdownOpenChange: (visible) => {
|
||||||
@@ -161,16 +200,27 @@
|
|||||||
}
|
}
|
||||||
const resOwn = await roleApi.roleOwnPermission(param)
|
const resOwn = await roleApi.roleOwnPermission(param)
|
||||||
// 数据转换
|
// 数据转换
|
||||||
echoModuleData(res, resOwn)
|
loadDatas.value = echoModuleData(res, resOwn)
|
||||||
|
pagination.value.total = loadDatas.value.length
|
||||||
|
allChecked.value = loadDatas.value.every((item) => item.parentCheck)
|
||||||
spinningLoading.value = false
|
spinningLoading.value = false
|
||||||
}
|
}
|
||||||
|
// table事件触发
|
||||||
|
const handleTableChange = (pageInfo) => {
|
||||||
|
Object.assign(pagination.value, pageInfo)
|
||||||
|
}
|
||||||
// 数据转换
|
// 数据转换
|
||||||
const echoModuleData = (res, resOwn) => {
|
const echoModuleData = (res, resOwn) => {
|
||||||
|
let list = []
|
||||||
res.forEach((api) => {
|
res.forEach((api) => {
|
||||||
|
const apiArr = splitByThirdSlash(api)
|
||||||
const obj = {
|
const obj = {
|
||||||
api: api,
|
api: api,
|
||||||
|
prefix: apiArr[0],
|
||||||
|
suffix: apiArr[1],
|
||||||
dataScope: datascope(api),
|
dataScope: datascope(api),
|
||||||
check: false
|
check: false,
|
||||||
|
parentCheck: false
|
||||||
}
|
}
|
||||||
if (resOwn.grantInfoList.length > 0) {
|
if (resOwn.grantInfoList.length > 0) {
|
||||||
resOwn.grantInfoList.forEach((item) => {
|
resOwn.grantInfoList.forEach((item) => {
|
||||||
@@ -189,8 +239,10 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
loadDatas.value.push(obj)
|
list.push(obj)
|
||||||
})
|
})
|
||||||
|
// 设置父节点check状态
|
||||||
|
return setParentDataCheckedStatus(list)
|
||||||
}
|
}
|
||||||
const datascope = (id) => {
|
const datascope = (id) => {
|
||||||
return [
|
return [
|
||||||
@@ -277,6 +329,11 @@
|
|||||||
const onOpen = (record) => {
|
const onOpen = (record) => {
|
||||||
grantPermissionParam.id = record.id
|
grantPermissionParam.id = record.id
|
||||||
visible.value = true
|
visible.value = true
|
||||||
|
firstShowMap.value = {}
|
||||||
|
pagination.value.current = 1
|
||||||
|
pagination.value.pageSize = 10
|
||||||
|
pagination.value.total = 0
|
||||||
|
allChecked.value = false
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
// 关闭抽屉
|
// 关闭抽屉
|
||||||
@@ -288,15 +345,41 @@
|
|||||||
}
|
}
|
||||||
// 全选
|
// 全选
|
||||||
const onCheckAllChange = (value) => {
|
const onCheckAllChange = (value) => {
|
||||||
|
allChecked.value = value
|
||||||
spinningLoading.value = true
|
spinningLoading.value = true
|
||||||
loadDatas.value.forEach((data) => {
|
loadDatas.value.forEach((data) => {
|
||||||
changeApi(data, value)
|
changeApi(data, value, false)
|
||||||
|
data.parentCheck = value
|
||||||
spinningLoading.value = false
|
spinningLoading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// 选中接口前缀
|
||||||
|
const changeParentApi = (record, val) => {
|
||||||
|
loadDatas.value.forEach((data) => {
|
||||||
|
if (data.prefix === record.prefix) {
|
||||||
|
data.check = val
|
||||||
|
data.parentCheck = val
|
||||||
|
if (val) {
|
||||||
|
let isChecked = data.dataScope.some((item) => item.check)
|
||||||
|
if (!isChecked) {
|
||||||
|
data.dataScope[0].check = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data.dataScope.forEach((item) => {
|
||||||
|
item.check = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
data.dataScope.forEach((item) => {
|
||||||
|
if (item.value === 'SCOPE_ORG_DEFINE') {
|
||||||
|
item.scopeDefineOrgIdList = []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
allChecked.value = loadDatas.value.every((item) => item.parentCheck)
|
||||||
|
}
|
||||||
// 选中接口
|
// 选中接口
|
||||||
const changeApi = (record, val) => {
|
const changeApi = (record, val, isLoadData = true) => {
|
||||||
record.check = val
|
record.check = val
|
||||||
if (val) {
|
if (val) {
|
||||||
let checkStatus = 0
|
let checkStatus = 0
|
||||||
@@ -317,6 +400,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
isLoadData && (loadDatas.value = setParentDataCheckedStatus(loadDatas.value))
|
||||||
}
|
}
|
||||||
// 设置选中状态
|
// 设置选中状态
|
||||||
const changeChildCheckBox = (record, evt) => {
|
const changeChildCheckBox = (record, evt) => {
|
||||||
@@ -382,8 +466,9 @@
|
|||||||
state.searchedColumn = dataIndex
|
state.searchedColumn = dataIndex
|
||||||
}
|
}
|
||||||
// 标题接口列搜索重置
|
// 标题接口列搜索重置
|
||||||
const handleReset = (clearFilters) => {
|
const handleReset = (clearFilters, confirm) => {
|
||||||
clearFilters()
|
clearFilters()
|
||||||
|
confirm()
|
||||||
state.searchText = ''
|
state.searchText = ''
|
||||||
}
|
}
|
||||||
// 标题数据范围列radio-group事件
|
// 标题数据范围列radio-group事件
|
||||||
@@ -398,6 +483,42 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// 设置父节点check状态
|
||||||
|
const setParentDataCheckedStatus = (records) => {
|
||||||
|
const cloneRecords = cloneDeep(records)
|
||||||
|
cloneRecords.forEach((item) => {
|
||||||
|
let childrenList = records.filter((f) => f.prefix === item.prefix)
|
||||||
|
item.parentCheck = childrenList.every((e) => e.check)
|
||||||
|
})
|
||||||
|
return cloneRecords
|
||||||
|
}
|
||||||
|
// 字符串分割
|
||||||
|
function splitByThirdSlash(str) {
|
||||||
|
const arr = str.split('/').filter(Boolean)
|
||||||
|
const leftPart = '/' + arr.slice(0, 2).join('/')
|
||||||
|
const rightPart = '/' + arr.slice(2).join('/')
|
||||||
|
return [leftPart, rightPart]
|
||||||
|
}
|
||||||
|
// 监听分页及数据变化
|
||||||
|
watch(
|
||||||
|
() => [pagination.value, loadDatas.value],
|
||||||
|
(val) => {
|
||||||
|
const start = (pagination.value.current - 1) * pagination.value.pageSize
|
||||||
|
const end = start + pagination.value.pageSize
|
||||||
|
const tableData = loadDatas.value.slice(start, end)
|
||||||
|
firstShowMap.value = {}
|
||||||
|
// 生成map
|
||||||
|
tableData?.forEach((item, index) => {
|
||||||
|
if (firstShowMap.value[item.prefix]) {
|
||||||
|
firstShowMap.value[item.prefix].push(index)
|
||||||
|
} else {
|
||||||
|
firstShowMap.value[item.prefix] = [index]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
tableLoadData.value = tableData
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
// 调用这个函数将子组件的一些数据和方法暴露出去
|
// 调用这个函数将子组件的一些数据和方法暴露出去
|
||||||
defineExpose({
|
defineExpose({
|
||||||
onOpen
|
onOpen
|
||||||
|
|||||||
@@ -104,8 +104,8 @@
|
|||||||
// firstShowMap = {} // 重置单元格合并映射
|
// firstShowMap = {} // 重置单元格合并映射
|
||||||
// 如果有数据,我们再不去反复的查询
|
// 如果有数据,我们再不去反复的查询
|
||||||
if (echoDatalist.value.length > 0) {
|
if (echoDatalist.value.length > 0) {
|
||||||
let data = echoDatalist.value.find((f) => f.id === moduleId.value).menu
|
// 这里必须保持联动,不可克隆
|
||||||
loadDatas.value = data
|
loadDatas.value = echoDatalist.value.find((f) => f.id === moduleId.value).menu
|
||||||
} else {
|
} else {
|
||||||
// 获取表格数据
|
// 获取表格数据
|
||||||
spinningLoading.value = true
|
spinningLoading.value = true
|
||||||
@@ -142,11 +142,11 @@
|
|||||||
if (module.menu) {
|
if (module.menu) {
|
||||||
// 加入回显内容
|
// 加入回显内容
|
||||||
module.menu.forEach((item) => {
|
module.menu.forEach((item) => {
|
||||||
const menueCheck = ref(0)
|
const menusCheck = ref(0)
|
||||||
if (resEcho.grantInfoList.length > 0) {
|
if (resEcho.grantInfoList.length > 0) {
|
||||||
resEcho.grantInfoList.forEach((grant) => {
|
resEcho.grantInfoList.forEach((grant) => {
|
||||||
if (item.id === grant.menuId) {
|
if (item.id === grant.menuId) {
|
||||||
menueCheck.value++
|
menusCheck.value++
|
||||||
// 处理按钮
|
// 处理按钮
|
||||||
if (grant.buttonInfo.length > 0) {
|
if (grant.buttonInfo.length > 0) {
|
||||||
grant.buttonInfo.forEach((button) => {
|
grant.buttonInfo.forEach((button) => {
|
||||||
@@ -161,15 +161,23 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 回显前面的2个
|
// 回显前面的2个
|
||||||
if (menueCheck.value > 0) {
|
if (menusCheck.value > 0) {
|
||||||
item.parentCheck = true
|
item.parentCheck = true
|
||||||
item.nameCheck = true
|
item.nameCheck = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 排序
|
// 排序
|
||||||
module.menu = module.menu.sort((a, b) => {
|
module.menu.sort((a, b) => {
|
||||||
return a.parentId - b.parentId
|
// 首先比较parentName属性
|
||||||
|
let nameComparison = b.parentName.localeCompare(a.parentName)
|
||||||
|
if (nameComparison !== 0) {
|
||||||
|
// 如果parentName不同,直接返回parentName的比较结果
|
||||||
|
return nameComparison
|
||||||
|
} else {
|
||||||
|
// 如果name相同,则比较parentId属性,直接返回parentId的差值
|
||||||
|
return Number(a.parentId) - Number(b.parentId)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
// 缓存加入索引
|
// 缓存加入索引
|
||||||
module.menu.forEach((item, index) => {
|
module.menu.forEach((item, index) => {
|
||||||
@@ -198,12 +206,11 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
const checkAllChildNotChecked = (record) => {
|
const checkAllChildNotChecked = (record) => {
|
||||||
const allChecked = checkFieldKeys.every((key) => {
|
return checkFieldKeys.every((key) => {
|
||||||
// 遍历所有的字段
|
// 遍历所有的字段
|
||||||
const child = record[key]
|
const child = record[key]
|
||||||
return child.every((field) => !field.check)
|
return child.every((field) => !field.check)
|
||||||
})
|
})
|
||||||
return allChecked
|
|
||||||
}
|
}
|
||||||
const changeChildCheckBox = (record, evt) => {
|
const changeChildCheckBox = (record, evt) => {
|
||||||
let checked = evt.target.checked
|
let checked = evt.target.checked
|
||||||
@@ -303,7 +310,7 @@
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
/* 重写复选框的样式 */
|
/* 重写复选框的样式 */
|
||||||
.ant-checkbox-wrapper {
|
.ant-checkbox-wrapper {
|
||||||
margin-left: 0px !important;
|
margin-left: 0 !important;
|
||||||
padding-top: 2px !important;
|
padding-top: 2px !important;
|
||||||
padding-bottom: 2px !important;
|
padding-bottom: 2px !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
:tree-data="treeData"
|
:tree-data="treeData"
|
||||||
:field-names="treeFieldNames"
|
:field-names="treeFieldNames"
|
||||||
checkable
|
checkable
|
||||||
|
check-strictly
|
||||||
:selectable="false"
|
:selectable="false"
|
||||||
@check="treeCheck"
|
@check="treeCheck"
|
||||||
>
|
>
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
|
|
||||||
<script setup="props, context" name="scopeDefineOrg">
|
<script setup="props, context" name="scopeDefineOrg">
|
||||||
import roleApi from '@/api/sys/roleApi'
|
import roleApi from '@/api/sys/roleApi'
|
||||||
|
import { checkOrUnCheckChildren } from '@/utils/treeHandler'
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
let defaultExpandedKeys = ref([])
|
let defaultExpandedKeys = ref([])
|
||||||
let checkedKeys = ref([])
|
let checkedKeys = ref([])
|
||||||
@@ -83,8 +85,8 @@
|
|||||||
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
|
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
|
||||||
|
|
||||||
// 选中触发
|
// 选中触发
|
||||||
const treeCheck = (checkedKeys) => {
|
const treeCheck = (checkedKeys, { checked, node }) => {
|
||||||
resultDataModel.defineOrgIdData.scopeDefineOrgIdList = checkedKeys
|
resultDataModel.defineOrgIdData.scopeDefineOrgIdList = checkOrUnCheckChildren(checked, node, checkedKeys)
|
||||||
}
|
}
|
||||||
// 定义emit事件
|
// 定义emit事件
|
||||||
const emit = defineEmits({
|
const emit = defineEmits({
|
||||||
|
|||||||
@@ -118,17 +118,18 @@
|
|||||||
</a-row>
|
</a-row>
|
||||||
|
|
||||||
<a-form-item label="任职信息" name="positionJson">
|
<a-form-item label="任职信息" name="positionJson">
|
||||||
<a-button type="primary" class="childAddButton" @click="addDomains()">
|
|
||||||
<PlusOutlined />
|
|
||||||
增加任职
|
|
||||||
</a-button>
|
|
||||||
<a-row :gutter="10" class="form-row">
|
<a-row :gutter="10" class="form-row">
|
||||||
<a-col :span="7" class="form-row-con"> 机构 </a-col>
|
<a-col :span="7" class="form-row-con"> 机构 </a-col>
|
||||||
<a-col :span="7" class="form-row-con"> 职位 </a-col>
|
<a-col :span="7" class="form-row-con"> 职位 </a-col>
|
||||||
<a-col :span="7" class="form-row-con"> 主管 </a-col>
|
<a-col :span="7" class="form-row-con"> 主管 </a-col>
|
||||||
<a-col :span="3" class="form-row-con"> 操作 </a-col>
|
<a-col :span="3" class="form-row-con">
|
||||||
|
<a-button type="primary" @click="addDomains()" size="small">
|
||||||
|
<PlusOutlined />
|
||||||
|
增加
|
||||||
|
</a-button>
|
||||||
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<div v-for="(positionInfo, index) in formData.positionJson" class="form-div">
|
<div :key="positionInfo" v-for="(positionInfo, index) in formData.positionJson">
|
||||||
<a-row :gutter="10">
|
<a-row :gutter="10">
|
||||||
<a-col :span="7">
|
<a-col :span="7">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
@@ -549,19 +550,14 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.childAddButton {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.form-row {
|
.form-row {
|
||||||
background-color: var(--item-hover-bg);
|
background-color: var(--item-hover-bg);
|
||||||
margin-left: 0px !important;
|
margin-left: 0 !important;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
.form-row-con {
|
.form-row-con {
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
}
|
}
|
||||||
.form-div {
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -17,13 +17,20 @@
|
|||||||
class="mt-4"
|
class="mt-4"
|
||||||
size="middle"
|
size="middle"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="loadDatas"
|
:data-source="tableLoadData"
|
||||||
bordered
|
bordered
|
||||||
:row-key="(record) => record.api"
|
:row-key="(record) => record.api"
|
||||||
|
:pagination="pagination"
|
||||||
|
@change="handleTableChange"
|
||||||
>
|
>
|
||||||
<template #headerCell="{ column }">
|
<template #headerCell="{ column }">
|
||||||
<template v-if="column.key === 'api'">
|
<template v-if="column.key === 'prefix'">
|
||||||
<a-checkbox @update:checked="(val) => onCheckAllChange(val)"> 接口 </a-checkbox>
|
<a-checkbox :checked="allChecked" @update:checked="(val) => onCheckAllChange(val)">
|
||||||
|
{{ column.title }}
|
||||||
|
</a-checkbox>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'suffix'">
|
||||||
|
<a-checkbox :checked="allChecked" @update:checked="(val) => onCheckAllChange(val)"> 接口 </a-checkbox>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'dataScope'">
|
<template v-if="column.key === 'dataScope'">
|
||||||
<span>{{ column.title }}</span>
|
<span>{{ column.title }}</span>
|
||||||
@@ -54,16 +61,21 @@
|
|||||||
<template #icon><SearchOutlined /></template>
|
<template #icon><SearchOutlined /></template>
|
||||||
搜索
|
搜索
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button size="small" class="xn-wd90" @click="handleReset(clearFilters)"> 重置 </a-button>
|
<a-button size="small" class="xn-wd90" @click="handleReset(clearFilters, confirm)"> 重置 </a-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #customFilterIcon="{ filtered }">
|
<template #customFilterIcon="{ filtered }">
|
||||||
<search-outlined :style="{ color: filtered ? '#108ee9' : undefined }" />
|
<search-outlined :style="{ color: filtered ? '#108ee9' : undefined }" />
|
||||||
</template>
|
</template>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.dataIndex === 'api'">
|
<template v-if="column.dataIndex === 'prefix'">
|
||||||
|
<a-checkbox :checked="record.parentCheck" @update:checked="(val) => changeParentApi(record, val)">
|
||||||
|
{{ record.prefix }}
|
||||||
|
</a-checkbox>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'suffix'">
|
||||||
<a-checkbox :checked="record.check" @update:checked="(val) => changeApi(record, val)">
|
<a-checkbox :checked="record.check" @update:checked="(val) => changeApi(record, val)">
|
||||||
{{ record.api }}
|
{{ record.suffix }}
|
||||||
</a-checkbox>
|
</a-checkbox>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.dataIndex === 'dataScope'">
|
<template v-if="column.dataIndex === 'dataScope'">
|
||||||
@@ -110,6 +122,7 @@
|
|||||||
import roleApi from '@/api/sys/roleApi'
|
import roleApi from '@/api/sys/roleApi'
|
||||||
import ScopeDefineOrg from './scopeDefineOrg.vue'
|
import ScopeDefineOrg from './scopeDefineOrg.vue'
|
||||||
import { userStore } from '@/store/user'
|
import { userStore } from '@/store/user'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const spinningLoading = ref(false)
|
const spinningLoading = ref(false)
|
||||||
@@ -118,7 +131,7 @@
|
|||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
const CustomValue = 'SCOPE_ORG_DEFINE'
|
const CustomValue = 'SCOPE_ORG_DEFINE'
|
||||||
// 抽屉的宽度
|
// 抽屉的宽度
|
||||||
const drawerWidth = 1000
|
const drawerWidth = 1050
|
||||||
// 自动获取宽度,默认获取浏览器的宽度的90%
|
// 自动获取宽度,默认获取浏览器的宽度的90%
|
||||||
//(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth) * 0.9
|
//(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth) * 0.9
|
||||||
const loadDatas = ref([])
|
const loadDatas = ref([])
|
||||||
@@ -129,13 +142,39 @@
|
|||||||
searchedColumn: ''
|
searchedColumn: ''
|
||||||
})
|
})
|
||||||
const searchInput = ref()
|
const searchInput = ref()
|
||||||
|
// 分页
|
||||||
|
const pagination = ref({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
showSizeChanger: true,
|
||||||
|
defaultPageSize: 10,
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100']
|
||||||
|
})
|
||||||
|
const firstShowMap = ref({})
|
||||||
|
// 全选
|
||||||
|
const allChecked = ref(false)
|
||||||
|
const tableLoadData = ref([])
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
key: 'api',
|
key: 'prefix',
|
||||||
|
title: '接口前缀',
|
||||||
|
dataIndex: 'prefix',
|
||||||
|
width: 140,
|
||||||
|
customCell: (row, index) => {
|
||||||
|
const indexArr = firstShowMap.value[row.prefix]
|
||||||
|
if (index === indexArr[0]) {
|
||||||
|
return { rowSpan: indexArr.length }
|
||||||
|
}
|
||||||
|
return { rowSpan: 0 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'suffix',
|
||||||
title: '接口',
|
title: '接口',
|
||||||
dataIndex: 'api',
|
dataIndex: 'suffix',
|
||||||
width: 380,
|
width: 290,
|
||||||
customFilterDropdown: true,
|
customFilterDropdown: true,
|
||||||
onFilter: (value, record) => record.api.includes(value),
|
onFilter: (value, record) => record.api.includes(value),
|
||||||
onFilterDropdownOpenChange: (visible) => {
|
onFilterDropdownOpenChange: (visible) => {
|
||||||
@@ -162,17 +201,29 @@
|
|||||||
}
|
}
|
||||||
const resOwn = await userApi.userOwnPermission(param)
|
const resOwn = await userApi.userOwnPermission(param)
|
||||||
// 数据转换
|
// 数据转换
|
||||||
echoModuleData(res, resOwn)
|
loadDatas.value = echoModuleData(res, resOwn)
|
||||||
|
pagination.value.total = loadDatas.value.length
|
||||||
|
allChecked.value = loadDatas.value.every((item) => item.parentCheck)
|
||||||
spinningLoading.value = false
|
spinningLoading.value = false
|
||||||
}
|
}
|
||||||
|
// table事件触发
|
||||||
|
const handleTableChange = (pageInfo) => {
|
||||||
|
Object.assign(pagination.value, pageInfo)
|
||||||
|
}
|
||||||
// 数据转换
|
// 数据转换
|
||||||
const echoModuleData = (res, resOwn) => {
|
const echoModuleData = (res, resOwn) => {
|
||||||
|
let list = []
|
||||||
res.forEach((api) => {
|
res.forEach((api) => {
|
||||||
|
const apiArr = splitByThirdSlash(api)
|
||||||
const obj = {
|
const obj = {
|
||||||
api: api,
|
api: api,
|
||||||
|
prefix: apiArr[0],
|
||||||
|
suffix: apiArr[1],
|
||||||
dataScope: datascope(api),
|
dataScope: datascope(api),
|
||||||
check: false
|
check: false,
|
||||||
|
parentCheck: false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resOwn.grantInfoList.length > 0) {
|
if (resOwn.grantInfoList.length > 0) {
|
||||||
resOwn.grantInfoList.forEach((item) => {
|
resOwn.grantInfoList.forEach((item) => {
|
||||||
if (item.apiUrl === subStrApi(api)) {
|
if (item.apiUrl === subStrApi(api)) {
|
||||||
@@ -190,8 +241,10 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
loadDatas.value.push(obj)
|
list.push(obj)
|
||||||
})
|
})
|
||||||
|
// 设置父节点check状态
|
||||||
|
return setParentDataCheckedStatus(list)
|
||||||
}
|
}
|
||||||
const datascope = (id) => {
|
const datascope = (id) => {
|
||||||
return [
|
return [
|
||||||
@@ -278,6 +331,11 @@
|
|||||||
const onOpen = (record) => {
|
const onOpen = (record) => {
|
||||||
grantPermissionParam.id = record.id
|
grantPermissionParam.id = record.id
|
||||||
visible.value = true
|
visible.value = true
|
||||||
|
firstShowMap.value = {}
|
||||||
|
pagination.value.current = 1
|
||||||
|
pagination.value.pageSize = 10
|
||||||
|
pagination.value.total = 0
|
||||||
|
allChecked.value = false
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
// 关闭抽屉
|
// 关闭抽屉
|
||||||
@@ -289,15 +347,41 @@
|
|||||||
}
|
}
|
||||||
// 全选
|
// 全选
|
||||||
const onCheckAllChange = (value) => {
|
const onCheckAllChange = (value) => {
|
||||||
|
allChecked.value = value
|
||||||
spinningLoading.value = true
|
spinningLoading.value = true
|
||||||
loadDatas.value.forEach((data) => {
|
loadDatas.value.forEach((data) => {
|
||||||
changeApi(data, value)
|
changeApi(data, value, false)
|
||||||
|
data.parentCheck = value
|
||||||
spinningLoading.value = false
|
spinningLoading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// 选中接口前缀
|
||||||
|
const changeParentApi = (record, val) => {
|
||||||
|
loadDatas.value.forEach((data) => {
|
||||||
|
if (data.prefix === record.prefix) {
|
||||||
|
data.check = val
|
||||||
|
data.parentCheck = val
|
||||||
|
if (val) {
|
||||||
|
let isChecked = data.dataScope.some((item) => item.check)
|
||||||
|
if (!isChecked) {
|
||||||
|
data.dataScope[0].check = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data.dataScope.forEach((item) => {
|
||||||
|
item.check = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
data.dataScope.forEach((item) => {
|
||||||
|
if (item.value === 'SCOPE_ORG_DEFINE') {
|
||||||
|
item.scopeDefineOrgIdList = []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
allChecked.value = loadDatas.value.every((item) => item.parentCheck)
|
||||||
|
}
|
||||||
// 选中接口
|
// 选中接口
|
||||||
const changeApi = (record, val) => {
|
const changeApi = (record, val, isLoadData = true) => {
|
||||||
record.check = val
|
record.check = val
|
||||||
if (val) {
|
if (val) {
|
||||||
let checkStatus = 0
|
let checkStatus = 0
|
||||||
@@ -318,6 +402,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
isLoadData && (loadDatas.value = setParentDataCheckedStatus(loadDatas.value))
|
||||||
}
|
}
|
||||||
// 设置选中状态
|
// 设置选中状态
|
||||||
const changeChildCheckBox = (record, evt) => {
|
const changeChildCheckBox = (record, evt) => {
|
||||||
@@ -383,8 +468,9 @@
|
|||||||
state.searchedColumn = dataIndex
|
state.searchedColumn = dataIndex
|
||||||
}
|
}
|
||||||
// 标题接口列搜索重置
|
// 标题接口列搜索重置
|
||||||
const handleReset = (clearFilters) => {
|
const handleReset = (clearFilters, confirm) => {
|
||||||
clearFilters()
|
clearFilters()
|
||||||
|
confirm()
|
||||||
state.searchText = ''
|
state.searchText = ''
|
||||||
}
|
}
|
||||||
// 标题数据范围列radio-group事件
|
// 标题数据范围列radio-group事件
|
||||||
@@ -399,6 +485,42 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// 设置父节点check状态
|
||||||
|
const setParentDataCheckedStatus = (records) => {
|
||||||
|
const cloneRecords = cloneDeep(records)
|
||||||
|
cloneRecords.forEach((item) => {
|
||||||
|
let childrenList = records.filter((f) => f.prefix === item.prefix)
|
||||||
|
item.parentCheck = childrenList.every((e) => e.check)
|
||||||
|
})
|
||||||
|
return cloneRecords
|
||||||
|
}
|
||||||
|
// 字符串分割
|
||||||
|
function splitByThirdSlash(str) {
|
||||||
|
const arr = str.split('/').filter(Boolean)
|
||||||
|
const leftPart = '/' + arr.slice(0, 2).join('/')
|
||||||
|
const rightPart = '/' + arr.slice(2).join('/')
|
||||||
|
return [leftPart, rightPart]
|
||||||
|
}
|
||||||
|
// 监听分页及数据变化
|
||||||
|
watch(
|
||||||
|
() => [pagination.value, loadDatas.value],
|
||||||
|
(val) => {
|
||||||
|
const start = (pagination.value.current - 1) * pagination.value.pageSize
|
||||||
|
const end = start + pagination.value.pageSize
|
||||||
|
const tableData = loadDatas.value.slice(start, end)
|
||||||
|
firstShowMap.value = {}
|
||||||
|
// 生成map
|
||||||
|
tableData?.forEach((item, index) => {
|
||||||
|
if (firstShowMap.value[item.prefix]) {
|
||||||
|
firstShowMap.value[item.prefix].push(index)
|
||||||
|
} else {
|
||||||
|
firstShowMap.value[item.prefix] = [index]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
tableLoadData.value = tableData
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
// 调用这个函数将子组件的一些数据和方法暴露出去
|
// 调用这个函数将子组件的一些数据和方法暴露出去
|
||||||
defineExpose({
|
defineExpose({
|
||||||
onOpen
|
onOpen
|
||||||
|
|||||||
@@ -105,8 +105,7 @@
|
|||||||
// firstShowMap = {} // 重置单元格合并映射
|
// firstShowMap = {} // 重置单元格合并映射
|
||||||
// 如果有数据,我们再不去反复的查询
|
// 如果有数据,我们再不去反复的查询
|
||||||
if (echoDatalist.value.length > 0) {
|
if (echoDatalist.value.length > 0) {
|
||||||
let data = echoDatalist.value.find((f) => f.id === moduleId.value).menu
|
loadDatas.value = echoDatalist.value.find((f) => f.id === moduleId.value).menu
|
||||||
loadDatas.value = data
|
|
||||||
} else {
|
} else {
|
||||||
// 获取表格数据
|
// 获取表格数据
|
||||||
spinningLoading.value = true
|
spinningLoading.value = true
|
||||||
@@ -143,11 +142,11 @@
|
|||||||
if (module.menu) {
|
if (module.menu) {
|
||||||
// 加入回显内容
|
// 加入回显内容
|
||||||
module.menu.forEach((item) => {
|
module.menu.forEach((item) => {
|
||||||
const menueCheck = ref(0)
|
const menusCheck = ref(0)
|
||||||
if (resEcho.grantInfoList.length > 0) {
|
if (resEcho.grantInfoList.length > 0) {
|
||||||
resEcho.grantInfoList.forEach((grant) => {
|
resEcho.grantInfoList.forEach((grant) => {
|
||||||
if (item.id === grant.menuId) {
|
if (item.id === grant.menuId) {
|
||||||
menueCheck.value++
|
menusCheck.value++
|
||||||
// 处理按钮
|
// 处理按钮
|
||||||
if (grant.buttonInfo.length > 0) {
|
if (grant.buttonInfo.length > 0) {
|
||||||
grant.buttonInfo.forEach((button) => {
|
grant.buttonInfo.forEach((button) => {
|
||||||
@@ -162,15 +161,23 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 回显前面的2个
|
// 回显前面的2个
|
||||||
if (menueCheck.value > 0) {
|
if (menusCheck.value > 0) {
|
||||||
item.parentCheck = true
|
item.parentCheck = true
|
||||||
item.nameCheck = true
|
item.nameCheck = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 排序
|
// 排序
|
||||||
module.menu = module.menu.sort((a, b) => {
|
module.menu.sort((a, b) => {
|
||||||
return a.parentId - b.parentId
|
// 首先比较parentName属性
|
||||||
|
let nameComparison = b.parentName.localeCompare(a.parentName)
|
||||||
|
if (nameComparison !== 0) {
|
||||||
|
// 如果parentName不同,直接返回parentName的比较结果
|
||||||
|
return nameComparison
|
||||||
|
} else {
|
||||||
|
// 如果name相同,则比较parentId属性,直接返回parentId的差值
|
||||||
|
return Number(a.parentId) - Number(b.parentId)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
// 缓存加入索引
|
// 缓存加入索引
|
||||||
module.menu.forEach((item, index) => {
|
module.menu.forEach((item, index) => {
|
||||||
@@ -199,12 +206,11 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
const checkAllChildNotChecked = (record) => {
|
const checkAllChildNotChecked = (record) => {
|
||||||
const allChecked = checkFieldKeys.every((key) => {
|
return checkFieldKeys.every((key) => {
|
||||||
// 遍历所有的字段
|
// 遍历所有的字段
|
||||||
const child = record[key]
|
const child = record[key]
|
||||||
return child.every((field) => !field.check)
|
return child.every((field) => !field.check)
|
||||||
})
|
})
|
||||||
return allChecked
|
|
||||||
}
|
}
|
||||||
const changeChildCheckBox = (record, evt) => {
|
const changeChildCheckBox = (record, evt) => {
|
||||||
let checked = evt.target.checked
|
let checked = evt.target.checked
|
||||||
@@ -305,7 +311,7 @@
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
/* 重写复选框的样式 */
|
/* 重写复选框的样式 */
|
||||||
.ant-checkbox-wrapper {
|
.ant-checkbox-wrapper {
|
||||||
margin-left: 0px !important;
|
margin-left: 0 !important;
|
||||||
padding-top: 2px !important;
|
padding-top: 2px !important;
|
||||||
padding-bottom: 2px !important;
|
padding-bottom: 2px !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
:tree-data="treeData"
|
:tree-data="treeData"
|
||||||
:field-names="treeFieldNames"
|
:field-names="treeFieldNames"
|
||||||
checkable
|
checkable
|
||||||
|
check-strictly
|
||||||
:selectable="false"
|
:selectable="false"
|
||||||
@check="treeCheck"
|
@check="treeCheck"
|
||||||
>
|
>
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
|
|
||||||
<script setup="props, context" name="userScopeDefineOrg">
|
<script setup="props, context" name="userScopeDefineOrg">
|
||||||
import userApi from '@/api/sys/userApi'
|
import userApi from '@/api/sys/userApi'
|
||||||
|
import { checkOrUnCheckChildren } from '@/utils/treeHandler'
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
let defaultExpandedKeys = ref([])
|
let defaultExpandedKeys = ref([])
|
||||||
let checkedKeys = ref([])
|
let checkedKeys = ref([])
|
||||||
@@ -83,8 +85,8 @@
|
|||||||
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
|
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
|
||||||
|
|
||||||
// 选中触发
|
// 选中触发
|
||||||
const treeCheck = (checkedKeys) => {
|
const treeCheck = (checkedKeys, { checked, node }) => {
|
||||||
resultDataModel.defineOrgIdData.scopeDefineOrgIdList = checkedKeys
|
resultDataModel.defineOrgIdData.scopeDefineOrgIdList = checkOrUnCheckChildren(checked, node, checkedKeys)
|
||||||
}
|
}
|
||||||
// 定义emit事件
|
// 定义emit事件
|
||||||
const emit = defineEmits({
|
const emit = defineEmits({
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
|
|||||||
queryWrapper.lambda().eq(BizUser::getUserStatus, bizUserExportParam.getUserStatus());
|
queryWrapper.lambda().eq(BizUser::getUserStatus, bizUserExportParam.getUserStatus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String fileName = "SNOWY2.0系统B端人员信息清单.xlsx";
|
String fileName = "SNOWY系统B端人员信息清单.xlsx";
|
||||||
List<BizUser> bizUserList = this.list(queryWrapper);
|
List<BizUser> bizUserList = this.list(queryWrapper);
|
||||||
if(ObjectUtil.isEmpty(bizUserList)) {
|
if(ObjectUtil.isEmpty(bizUserList)) {
|
||||||
throw new CommonException("无数据可导出");
|
throw new CommonException("无数据可导出");
|
||||||
@@ -575,7 +575,7 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
|
|||||||
// 生成doc
|
// 生成doc
|
||||||
XWPFDocument doc = WordExportUtil.exportWord07(destTemplateFile.getAbsolutePath(), map);
|
XWPFDocument doc = WordExportUtil.exportWord07(destTemplateFile.getAbsolutePath(), map);
|
||||||
// 生成临时导出文件
|
// 生成临时导出文件
|
||||||
resultFile = FileUtil.file(FileUtil.getTmpDir() + File.separator + "SNOWY2.0系统B端人员信息_" + bizUser.getName() + ".docx");
|
resultFile = FileUtil.file(FileUtil.getTmpDir() + File.separator + "SNOWY系统B端人员信息_" + bizUser.getName() + ".docx");
|
||||||
// 写入
|
// 写入
|
||||||
BufferedOutputStream outputStream = FileUtil.getOutputStream(resultFile);
|
BufferedOutputStream outputStream = FileUtil.getOutputStream(resultFile);
|
||||||
doc.write(outputStream);
|
doc.write(outputStream);
|
||||||
|
|||||||
@@ -996,7 +996,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
|||||||
try {
|
try {
|
||||||
InputStream inputStream = POICacheManager.getFile("userImportTemplate.xlsx");
|
InputStream inputStream = POICacheManager.getFile("userImportTemplate.xlsx");
|
||||||
byte[] bytes = IoUtil.readBytes(inputStream);
|
byte[] bytes = IoUtil.readBytes(inputStream);
|
||||||
CommonDownloadUtil.download("SNOWY2.0系统B端用户导入模板.xlsx", bytes, response);
|
CommonDownloadUtil.download("SNOWY系统B端用户导入模板.xlsx", bytes, response);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(">>> 下载用户导入模板失败:", e);
|
log.error(">>> 下载用户导入模板失败:", e);
|
||||||
CommonResponseUtil.renderError(response, "下载用户导入模板失败");
|
CommonResponseUtil.renderError(response, "下载用户导入模板失败");
|
||||||
@@ -1181,7 +1181,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
|||||||
queryWrapper.lambda().eq(SysUser::getUserStatus, sysUserExportParam.getUserStatus());
|
queryWrapper.lambda().eq(SysUser::getUserStatus, sysUserExportParam.getUserStatus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String fileName = "SNOWY2.0系统B端用户信息清单.xlsx";
|
String fileName = "SNOWY系统B端用户信息清单.xlsx";
|
||||||
List<SysUser> sysUserList = this.list(queryWrapper);
|
List<SysUser> sysUserList = this.list(queryWrapper);
|
||||||
if(ObjectUtil.isEmpty(sysUserList)) {
|
if(ObjectUtil.isEmpty(sysUserList)) {
|
||||||
throw new CommonException("无数据可导出");
|
throw new CommonException("无数据可导出");
|
||||||
@@ -1335,7 +1335,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
|||||||
// 生成doc
|
// 生成doc
|
||||||
XWPFDocument doc = WordExportUtil.exportWord07(destTemplateFile.getAbsolutePath(), map);
|
XWPFDocument doc = WordExportUtil.exportWord07(destTemplateFile.getAbsolutePath(), map);
|
||||||
// 生成临时导出文件
|
// 生成临时导出文件
|
||||||
resultFile = FileUtil.file(FileUtil.getTmpDir() + File.separator + "SNOWY2.0系统B端用户信息_" + sysUser.getName() + ".docx");
|
resultFile = FileUtil.file(FileUtil.getTmpDir() + File.separator + "SNOWY系统B端用户信息_" + sysUser.getName() + ".docx");
|
||||||
// 写入
|
// 写入
|
||||||
BufferedOutputStream outputStream = FileUtil.getOutputStream(resultFile);
|
BufferedOutputStream outputStream = FileUtil.getOutputStream(resultFile);
|
||||||
doc.write(outputStream);
|
doc.write(outputStream);
|
||||||
|
|||||||
@@ -19,12 +19,11 @@ import cn.dev33.satoken.router.SaHttpMethod;
|
|||||||
import cn.dev33.satoken.router.SaRouter;
|
import cn.dev33.satoken.router.SaRouter;
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.date.DateTime;
|
import cn.hutool.core.date.DateTime;
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
import cn.hutool.core.util.*;
|
||||||
import cn.hutool.core.util.CharsetUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
import cn.hutool.core.util.EnumUtil;
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
|
||||||
import cn.hutool.http.ContentType;
|
import cn.hutool.http.ContentType;
|
||||||
import cn.hutool.http.Header;
|
import cn.hutool.http.Header;
|
||||||
import cn.hutool.json.JSONObject;
|
import cn.hutool.json.JSONObject;
|
||||||
@@ -35,13 +34,14 @@ import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerIntercept
|
|||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import org.apache.ibatis.mapping.DatabaseIdProvider;
|
import org.apache.ibatis.mapping.DatabaseIdProvider;
|
||||||
import org.apache.ibatis.reflection.MetaObject;
|
import org.apache.ibatis.reflection.MetaObject;
|
||||||
import org.apache.ibatis.reflection.ReflectionException;
|
import org.apache.ibatis.reflection.ReflectionException;
|
||||||
|
import org.aspectj.lang.JoinPoint;
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.Before;
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
@@ -54,12 +54,8 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|||||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
import org.springframework.jdbc.support.JdbcUtils;
|
import org.springframework.jdbc.support.JdbcUtils;
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.ResourceUtils;
|
import org.springframework.util.ResourceUtils;
|
||||||
import org.springframework.web.method.HandlerMethod;
|
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
|
||||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
import vip.xiaonuo.auth.core.util.StpClientUtil;
|
import vip.xiaonuo.auth.core.util.StpClientUtil;
|
||||||
@@ -72,6 +68,9 @@ import vip.xiaonuo.common.listener.CommonDataChangeEventCenter;
|
|||||||
import vip.xiaonuo.common.listener.CommonDataChangeListener;
|
import vip.xiaonuo.common.listener.CommonDataChangeListener;
|
||||||
import vip.xiaonuo.common.pojo.CommonResult;
|
import vip.xiaonuo.common.pojo.CommonResult;
|
||||||
import vip.xiaonuo.common.pojo.CommonWrapperInterface;
|
import vip.xiaonuo.common.pojo.CommonWrapperInterface;
|
||||||
|
import vip.xiaonuo.common.util.CommonIpAddressUtil;
|
||||||
|
import vip.xiaonuo.common.util.CommonJoinPointUtil;
|
||||||
|
import vip.xiaonuo.common.util.CommonServletUtil;
|
||||||
import vip.xiaonuo.common.util.CommonTimeFormatUtil;
|
import vip.xiaonuo.common.util.CommonTimeFormatUtil;
|
||||||
import vip.xiaonuo.core.handler.GlobalExceptionUtil;
|
import vip.xiaonuo.core.handler.GlobalExceptionUtil;
|
||||||
import vip.xiaonuo.sys.core.enums.SysBuildInEnum;
|
import vip.xiaonuo.sys.core.enums.SysBuildInEnum;
|
||||||
@@ -98,9 +97,6 @@ public class GlobalConfigure implements WebMvcConfigurer {
|
|||||||
|
|
||||||
private static final String COMMON_REPEAT_SUBMIT_CACHE_KEY = "common-repeatSubmit:";
|
private static final String COMMON_REPEAT_SUBMIT_CACHE_KEY = "common-repeatSubmit:";
|
||||||
|
|
||||||
@Resource
|
|
||||||
private CommonCacheOperator commonCacheOperator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 无需登录的接口地址集合
|
* 无需登录的接口地址集合
|
||||||
*/
|
*/
|
||||||
@@ -283,6 +279,7 @@ public class GlobalConfigure implements WebMvcConfigurer {
|
|||||||
* @author xuyuxiang
|
* @author xuyuxiang
|
||||||
* @date 2022/6/21 17:01
|
* @date 2022/6/21 17:01
|
||||||
**/
|
**/
|
||||||
|
@SuppressWarnings("ALL")
|
||||||
@Primary
|
@Primary
|
||||||
@Bean
|
@Bean
|
||||||
public RedisTemplate<String, Object> redisTemplate(@Autowired(required = false) RedisConnectionFactory redisConnectionFactory) {
|
public RedisTemplate<String, Object> redisTemplate(@Autowired(required = false) RedisConnectionFactory redisConnectionFactory) {
|
||||||
@@ -311,41 +308,52 @@ public class GlobalConfigure implements WebMvcConfigurer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加节流防抖拦截器
|
* 节流防抖的AOP
|
||||||
*
|
*
|
||||||
* @author xuyuxiang
|
* @author xuyuxiang
|
||||||
* @date 2022/6/20 15:18
|
* @date 2022/9/15 21:24
|
||||||
**/
|
*/
|
||||||
@Override
|
@Component
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
@Aspect
|
||||||
registry.addInterceptor(new HandlerInterceptor() {
|
public static class CommonNoRepeatAop {
|
||||||
@Override
|
|
||||||
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
|
/**
|
||||||
@NonNull Object handler) throws Exception {
|
* 切入点
|
||||||
if (handler instanceof HandlerMethod) {
|
*
|
||||||
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
* @author xuyuxiang
|
||||||
Method method = handlerMethod.getMethod();
|
* @date 2022/9/15 21:27
|
||||||
CommonNoRepeat annotation = method.getAnnotation(CommonNoRepeat.class);
|
*/
|
||||||
if (ObjectUtil.isNotEmpty(annotation)) {
|
@Pointcut("@annotation(vip.xiaonuo.common.annotation.CommonNoRepeat)")
|
||||||
JSONObject repeatSubmitJsonObject = this.isRepeatSubmit(request, annotation);
|
private void noRepeatPointcut() {
|
||||||
if (repeatSubmitJsonObject.getBool("repeat")) {
|
|
||||||
response.setCharacterEncoding(CharsetUtil.UTF_8);
|
|
||||||
response.setContentType(ContentType.JSON.toString());
|
|
||||||
response.getWriter().write(JSONUtil.toJsonStr(CommonResult.error("请求过于频繁,请" + repeatSubmitJsonObject.getStr("time") + "后再试")));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public JSONObject isRepeatSubmit(HttpServletRequest request, CommonNoRepeat annotation) {
|
/**
|
||||||
JSONObject jsonObject = JSONUtil.createObj();
|
* 执行校验
|
||||||
jsonObject.set("repeatParam", JSONUtil.toJsonStr(request.getParameterMap()));
|
*
|
||||||
jsonObject.set("repeatTime", DateUtil.current());
|
* @author xuyuxiang
|
||||||
|
* @date 2022/9/15 21:27
|
||||||
|
*/
|
||||||
|
@Before("noRepeatPointcut()")
|
||||||
|
public void doBefore(JoinPoint joinPoint) {
|
||||||
|
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||||
|
Method method = methodSignature.getMethod();
|
||||||
|
CommonNoRepeat commonNoRepeat = method.getAnnotation(CommonNoRepeat.class);
|
||||||
|
HttpServletRequest request = CommonServletUtil.getRequest();
|
||||||
String url = request.getRequestURI();
|
String url = request.getRequestURI();
|
||||||
// 获取该接口缓存的限流数据
|
CommonCacheOperator commonCacheOperator = SpringUtil.getBean(CommonCacheOperator.class);
|
||||||
Object cacheObj = commonCacheOperator.get(COMMON_REPEAT_SUBMIT_CACHE_KEY + url);
|
JSONObject jsonObject = JSONUtil.createObj();
|
||||||
|
jsonObject.set("repeatParam", CommonJoinPointUtil.getArgsJsonString(joinPoint));
|
||||||
|
jsonObject.set("repeatTime", DateUtil.current());
|
||||||
|
// 获取该接口缓存的限流数据,跟当前ip以及登录用户有关
|
||||||
|
String cacheKey = COMMON_REPEAT_SUBMIT_CACHE_KEY + CommonIpAddressUtil.getIp(request) + StrUtil.COLON;
|
||||||
|
Object loginId = StpUtil.getLoginIdDefaultNull();
|
||||||
|
if(ObjectUtil.isNotEmpty(loginId)) {
|
||||||
|
cacheKey = cacheKey + Convert.toStr(loginId) + StrUtil.COLON + url;
|
||||||
|
} else {
|
||||||
|
cacheKey = cacheKey + url;
|
||||||
|
}
|
||||||
|
Object cacheObj = commonCacheOperator.get(cacheKey);
|
||||||
if (ObjectUtil.isNotEmpty(cacheObj)) {
|
if (ObjectUtil.isNotEmpty(cacheObj)) {
|
||||||
JSONObject cacheJsonObject = JSONUtil.parseObj(cacheObj);
|
JSONObject cacheJsonObject = JSONUtil.parseObj(cacheObj);
|
||||||
if(cacheJsonObject.containsKey(url)) {
|
if(cacheJsonObject.containsKey(url)) {
|
||||||
@@ -353,22 +361,18 @@ public class GlobalConfigure implements WebMvcConfigurer {
|
|||||||
// 如果与上次参数一致,且时间间隔小于要求的限流时长,则判定为重复提交
|
// 如果与上次参数一致,且时间间隔小于要求的限流时长,则判定为重复提交
|
||||||
if (jsonObject.getStr("repeatParam").equals(existRepeatJsonObject.getStr("repeatParam"))) {
|
if (jsonObject.getStr("repeatParam").equals(existRepeatJsonObject.getStr("repeatParam"))) {
|
||||||
long interval = jsonObject.getLong("repeatTime") - existRepeatJsonObject.getLong("repeatTime");
|
long interval = jsonObject.getLong("repeatTime") - existRepeatJsonObject.getLong("repeatTime");
|
||||||
if(interval < annotation.interval()) {
|
if(interval < commonNoRepeat.interval()) {
|
||||||
long secondsParam = (annotation.interval() - interval) / 1000;
|
long secondsParam = (commonNoRepeat.interval() - interval) / 1000;
|
||||||
if(secondsParam == 0) {
|
if(secondsParam > 0) {
|
||||||
return JSONUtil.createObj().set("repeat", false);
|
throw new CommonException("请求过于频繁,请" + CommonTimeFormatUtil.formatSeconds(secondsParam) + "后再试");
|
||||||
} else {
|
|
||||||
return JSONUtil.createObj().set("repeat", true).set("time", CommonTimeFormatUtil.formatSeconds(secondsParam));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 缓存最新的该接口的限流数据,为防止缓存的数据过多,缓存时效为1小时
|
// 缓存最新的该接口的限流数据,跟当前ip以及登录用户有关,为防止缓存的数据过多,缓存时效为1小时
|
||||||
commonCacheOperator.put(COMMON_REPEAT_SUBMIT_CACHE_KEY + url, JSONUtil.createObj().set(url, jsonObject), 60 * 60);
|
commonCacheOperator.put(cacheKey, JSONUtil.createObj().set(url, jsonObject), 60 * 60);
|
||||||
return JSONUtil.createObj().set("repeat", false);
|
|
||||||
}
|
}
|
||||||
}).addPathPatterns("/**");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -411,7 +415,7 @@ public class GlobalConfigure implements WebMvcConfigurer {
|
|||||||
* @author xuyuxiang
|
* @author xuyuxiang
|
||||||
* @date 2022/9/15 21:27
|
* @date 2022/9/15 21:27
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("ALL")
|
||||||
private Object processWrapping(ProceedingJoinPoint proceedingJoinPoint, Object originResult) throws IllegalAccessException, InstantiationException {
|
private Object processWrapping(ProceedingJoinPoint proceedingJoinPoint, Object originResult) throws IllegalAccessException, InstantiationException {
|
||||||
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
|
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
|
||||||
Method method = methodSignature.getMethod();
|
Method method = methodSignature.getMethod();
|
||||||
@@ -462,7 +466,7 @@ public class GlobalConfigure implements WebMvcConfigurer {
|
|||||||
* @author xuyuxiang
|
* @author xuyuxiang
|
||||||
* @date 2022/9/15 21:36
|
* @date 2022/9/15 21:36
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("ALL")
|
||||||
private JSONObject wrapPureObject(Object originModel, Class<? extends CommonWrapperInterface<?>>[] baseWrapperClasses) {
|
private JSONObject wrapPureObject(Object originModel, Class<? extends CommonWrapperInterface<?>>[] baseWrapperClasses) {
|
||||||
JSONObject jsonObject = JSONUtil.parseObj(originModel);
|
JSONObject jsonObject = JSONUtil.parseObj(originModel);
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user