init
This commit is contained in:
parent
83758dfee9
commit
e350dbe696
|
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<el-sub-menu
|
||||
ref="subMenu"
|
||||
:index="routerInfo.name"
|
||||
>
|
||||
|
||||
<template #title>
|
||||
<div
|
||||
v-if="!isCollapse"
|
||||
class="gva-subMenu"
|
||||
>
|
||||
<el-icon v-if="routerInfo.meta.icon">
|
||||
<component :is="routerInfo.meta.icon" />
|
||||
</el-icon>
|
||||
<span>{{ routerInfo.meta.title }}</span>
|
||||
</div>
|
||||
<template v-else>
|
||||
<el-icon v-if="routerInfo.meta.icon">
|
||||
<component :is="routerInfo.meta.icon" />
|
||||
</el-icon>
|
||||
<span>{{ routerInfo.meta.title }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<slot />
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'AsyncSubmenu',
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
routerInfo: {
|
||||
default: function() {
|
||||
return null
|
||||
},
|
||||
type: Object
|
||||
},
|
||||
isCollapse: {
|
||||
default: function() {
|
||||
return false
|
||||
},
|
||||
type: Boolean
|
||||
},
|
||||
theme: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const activeBackground = ref(props.theme.activeBackground)
|
||||
const activeText = ref(props.theme.activeText)
|
||||
const normalText = ref(props.theme.normalText)
|
||||
// const hoverBackground = ref(props.theme.hoverBackground)
|
||||
// const hoverText = ref(props.theme.hoverText)
|
||||
|
||||
watch(() => props.theme, () => {
|
||||
activeBackground.value = props.theme.activeBackground
|
||||
activeText.value = props.theme.activeText
|
||||
normalText.value = props.theme.normalText
|
||||
// hoverBackground.value = props.theme.hoverBackground
|
||||
// hoverText.value = props.theme.hoverText
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-sub-menu{
|
||||
::v-deep(.el-sub-menu__title){
|
||||
padding: 6px;
|
||||
color: v-bind(normalText);
|
||||
}
|
||||
}
|
||||
|
||||
.is-active:not(.is-opened){
|
||||
::v-deep(.el-sub-menu__title) .gva-subMenu{
|
||||
flex:1;
|
||||
height: 100%;
|
||||
line-height: 44px;
|
||||
background: v-bind(activeBackground) !important;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 2px 1px v-bind(activeBackground) !important;
|
||||
i{
|
||||
color: v-bind(activeText);
|
||||
}
|
||||
span{
|
||||
color: v-bind(activeText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gva-subMenu {
|
||||
padding-left: 4px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<component
|
||||
:is="menuComponent"
|
||||
v-if="!routerInfo.hidden"
|
||||
:is-collapse="isCollapse"
|
||||
:theme="theme"
|
||||
:router-info="routerInfo"
|
||||
>
|
||||
<template v-if="routerInfo.children&&routerInfo.children.length">
|
||||
<AsideComponent
|
||||
v-for="item in routerInfo.children"
|
||||
:key="item.name"
|
||||
:is-collapse="false"
|
||||
:router-info="item"
|
||||
:theme="theme"
|
||||
/>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import MenuItem from './menuItem.vue'
|
||||
import AsyncSubmenu from './asyncSubmenu.vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'AsideComponent',
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
routerInfo: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
isCollapse: {
|
||||
default: function() {
|
||||
return false
|
||||
},
|
||||
type: Boolean
|
||||
},
|
||||
theme: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const menuComponent = computed(() => {
|
||||
if (props.routerInfo.children && props.routerInfo.children.filter(item => !item.hidden).length) {
|
||||
return AsyncSubmenu
|
||||
} else {
|
||||
return MenuItem
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<el-menu-item :index="routerInfo.name">
|
||||
<template v-if="isCollapse">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="light"
|
||||
:content="routerInfo.meta.title"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon v-if="routerInfo.meta.icon">
|
||||
<component :is="routerInfo.meta.icon" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="gva-menu-item">
|
||||
<el-icon v-if="routerInfo.meta.icon">
|
||||
<component :is="routerInfo.meta.icon" />
|
||||
</el-icon>
|
||||
<span class="gva-menu-item-title">{{ routerInfo.meta.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'MenuItem',
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
routerInfo: {
|
||||
default: function() {
|
||||
return null
|
||||
},
|
||||
type: Object
|
||||
},
|
||||
isCollapse: {
|
||||
default: function() {
|
||||
return false
|
||||
},
|
||||
type: Boolean
|
||||
},
|
||||
theme: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const activeBackground = ref(props.theme.activeBackground)
|
||||
const activeText = ref(props.theme.activeText)
|
||||
const normalText = ref(props.theme.normalText)
|
||||
const hoverBackground = ref(props.theme.hoverBackground)
|
||||
const hoverText = ref(props.theme.hoverText)
|
||||
|
||||
watch(() => props.theme, () => {
|
||||
activeBackground.value = props.theme.activeBackground
|
||||
activeText.value = props.theme.activeText
|
||||
normalText.value = props.theme.normalText
|
||||
hoverBackground.value = props.theme.hoverBackground
|
||||
hoverText.value = props.theme.hoverText
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.gva-menu-item{
|
||||
width: 100%;
|
||||
flex:1;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding-left: 4px;
|
||||
|
||||
.gva-menu-item-title {
|
||||
flex:1;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu--collapse{
|
||||
.el-menu-item.is-active{
|
||||
color: v-bind(activeBackground);
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item{
|
||||
color: v-bind(normalText);
|
||||
}
|
||||
|
||||
.el-menu-item.is-active{
|
||||
.gva-menu-item{
|
||||
background: v-bind(activeBackground) !important;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 2px 1px v-bind(activeBackground) !important;
|
||||
i{
|
||||
color: v-bind(activeText);
|
||||
}
|
||||
span{
|
||||
color: v-bind(activeText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item:hover{
|
||||
.gva-menu-item{
|
||||
background: v-bind(hoverBackground);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 2px 1px v-bind(hoverBackground);
|
||||
color: v-bind(hoverText);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,385 @@
|
|||
<template>
|
||||
<div class="router-history">
|
||||
<el-tabs
|
||||
v-model="activeValue"
|
||||
:closable="!(historys.length === 1 && $route.name === defaultRouter)"
|
||||
type="card"
|
||||
@contextmenu.prevent="openContextMenu($event)"
|
||||
@tab-click="changeTab"
|
||||
@tab-remove="removeTab"
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="item in historys"
|
||||
:key="getFmtString(item)"
|
||||
:label="item.meta.title"
|
||||
:name="getFmtString(item)"
|
||||
:tab="item"
|
||||
class="gva-tab"
|
||||
>
|
||||
<template #label>
|
||||
<span
|
||||
:tab="item"
|
||||
:style="{
|
||||
color: activeValue === getFmtString(item) ? userStore.activeColor : '#333',
|
||||
}"
|
||||
><i
|
||||
class="dot"
|
||||
:style="{
|
||||
backgroundColor:
|
||||
activeValue === getFmtString(item) ? userStore.activeColor : '#ddd',
|
||||
}"
|
||||
/>
|
||||
{{ fmtTitle(item.meta.title,item) }}</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!--自定义右键菜单html代码-->
|
||||
<ul
|
||||
v-show="contextMenuVisible"
|
||||
:style="{ left: left + 'px', top: top + 'px' }"
|
||||
class="contextmenu"
|
||||
>
|
||||
<li @click="closeAll">关闭所有</li>
|
||||
<li @click="closeLeft">关闭左侧</li>
|
||||
<li @click="closeRight">关闭右侧</li>
|
||||
<li @click="closeOther">关闭其他</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { emitter } from '@/utils/bus.js'
|
||||
import { computed, onUnmounted, ref, watch, nextTick } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { fmtTitle } from '@/utils/fmtRouterTitle'
|
||||
|
||||
defineOptions({
|
||||
name: 'HistoryComponent',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const getFmtString = (item) => {
|
||||
return item.name + JSON.stringify(item.query) + JSON.stringify(item.params)
|
||||
}
|
||||
|
||||
const historys = ref([])
|
||||
const activeValue = ref('')
|
||||
const contextMenuVisible = ref(false)
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const left = ref(0)
|
||||
const top = ref(0)
|
||||
const isCollapse = ref(false)
|
||||
const isMobile = ref(false)
|
||||
const rightActive = ref('')
|
||||
const defaultRouter = computed(() => userStore.userInfo.authority.defaultRouter)
|
||||
const openContextMenu = (e) => {
|
||||
if (
|
||||
historys.value.length === 1 &&
|
||||
route.name === defaultRouter.value
|
||||
) {
|
||||
return false
|
||||
}
|
||||
let id = ''
|
||||
if (e.srcElement.nodeName === 'SPAN') {
|
||||
id = e.srcElement.offsetParent.id
|
||||
} else {
|
||||
id = e.srcElement.id
|
||||
}
|
||||
if (id) {
|
||||
contextMenuVisible.value = true
|
||||
let width
|
||||
if (isCollapse.value) {
|
||||
width = 54
|
||||
} else {
|
||||
width = 220
|
||||
}
|
||||
if (isMobile.value) {
|
||||
width = 0
|
||||
}
|
||||
left.value = e.clientX - width
|
||||
top.value = e.clientY + 10
|
||||
rightActive.value = id.substring(4)
|
||||
}
|
||||
}
|
||||
const closeAll = () => {
|
||||
historys.value = [
|
||||
{
|
||||
name: defaultRouter.value,
|
||||
meta: {
|
||||
title: '首页',
|
||||
},
|
||||
query: {},
|
||||
params: {},
|
||||
},
|
||||
]
|
||||
router.push({ name: defaultRouter.value })
|
||||
contextMenuVisible.value = false
|
||||
sessionStorage.setItem('historys', JSON.stringify(historys.value))
|
||||
}
|
||||
const closeLeft = () => {
|
||||
let right
|
||||
const rightIndex = historys.value.findIndex((item) => {
|
||||
if (getFmtString(item) === rightActive.value) {
|
||||
right = item
|
||||
}
|
||||
return getFmtString(item) === rightActive.value
|
||||
})
|
||||
const activeIndex = historys.value.findIndex(
|
||||
(item) => getFmtString(item) === activeValue.value
|
||||
)
|
||||
historys.value.splice(0, rightIndex)
|
||||
if (rightIndex > activeIndex) {
|
||||
router.push(right)
|
||||
}
|
||||
sessionStorage.setItem('historys', JSON.stringify(historys.value))
|
||||
}
|
||||
const closeRight = () => {
|
||||
let right
|
||||
const leftIndex = historys.value.findIndex((item) => {
|
||||
if (getFmtString(item) === rightActive.value) {
|
||||
right = item
|
||||
}
|
||||
return getFmtString(item) === rightActive.value
|
||||
})
|
||||
const activeIndex = historys.value.findIndex(
|
||||
(item) => getFmtString(item) === activeValue.value
|
||||
)
|
||||
historys.value.splice(leftIndex + 1, historys.value.length)
|
||||
if (leftIndex < activeIndex) {
|
||||
router.push(right)
|
||||
}
|
||||
sessionStorage.setItem('historys', JSON.stringify(historys.value))
|
||||
}
|
||||
const closeOther = () => {
|
||||
let right
|
||||
historys.value = historys.value.filter((item) => {
|
||||
if (getFmtString(item) === rightActive.value) {
|
||||
right = item
|
||||
}
|
||||
return getFmtString(item) === rightActive.value
|
||||
})
|
||||
router.push(right)
|
||||
sessionStorage.setItem('historys', JSON.stringify(historys.value))
|
||||
}
|
||||
const isSame = (route1, route2) => {
|
||||
if (route1.name !== route2.name) {
|
||||
return false
|
||||
}
|
||||
if (Object.keys(route1.query).length !== Object.keys(route2.query).length || Object.keys(route1.params).length !== Object.keys(route2.params).length) {
|
||||
return false
|
||||
}
|
||||
for (const key in route1.query) {
|
||||
if (route1.query[key] !== route2.query[key]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for (const key in route1.params) {
|
||||
if (route1.params[key] !== route2.params[key]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
const setTab = (route) => {
|
||||
if (!historys.value.some((item) => isSame(item, route))) {
|
||||
const obj = {}
|
||||
obj.name = route.name
|
||||
obj.meta = { ...route.meta }
|
||||
delete obj.meta.matched
|
||||
obj.query = route.query
|
||||
obj.params = route.params
|
||||
historys.value.push(obj)
|
||||
}
|
||||
window.sessionStorage.setItem('activeValue', getFmtString(route))
|
||||
}
|
||||
|
||||
const historyMap = ref({})
|
||||
|
||||
const changeTab = (TabsPaneContext) => {
|
||||
const name = TabsPaneContext?.props?.name
|
||||
if (!name) return
|
||||
const tab = historyMap.value[name]
|
||||
router.push({
|
||||
name: tab.name,
|
||||
query: tab.query,
|
||||
params: tab.params,
|
||||
})
|
||||
}
|
||||
const removeTab = (tab) => {
|
||||
const index = historys.value.findIndex(
|
||||
(item) => getFmtString(item) === tab
|
||||
)
|
||||
if (getFmtString(route) === tab) {
|
||||
if (historys.value.length === 1) {
|
||||
router.push({ name: defaultRouter.value })
|
||||
} else {
|
||||
if (index < historys.value.length - 1) {
|
||||
router.push({
|
||||
name: historys.value[index + 1].name,
|
||||
query: historys.value[index + 1].query,
|
||||
params: historys.value[index + 1].params,
|
||||
})
|
||||
} else {
|
||||
router.push({
|
||||
name: historys.value[index - 1].name,
|
||||
query: historys.value[index - 1].query,
|
||||
params: historys.value[index - 1].params,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
historys.value.splice(index, 1)
|
||||
}
|
||||
|
||||
watch(() => contextMenuVisible.value, () => {
|
||||
if (contextMenuVisible.value) {
|
||||
document.body.addEventListener('click', () => {
|
||||
contextMenuVisible.value = false
|
||||
})
|
||||
} else {
|
||||
document.body.removeEventListener('click', () => {
|
||||
contextMenuVisible.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => route, (to, now) => {
|
||||
if (to.name === 'Login' || to.name === 'Reload') {
|
||||
return
|
||||
}
|
||||
historys.value = historys.value.filter((item) => !item.meta.closeTab)
|
||||
setTab(to)
|
||||
sessionStorage.setItem('historys', JSON.stringify(historys.value))
|
||||
activeValue.value = window.sessionStorage.getItem('activeValue')
|
||||
}, { deep: true })
|
||||
|
||||
watch(() => historys.value, () => {
|
||||
sessionStorage.setItem('historys', JSON.stringify(historys.value))
|
||||
historyMap.value = {}
|
||||
historys.value.forEach((item) => {
|
||||
historyMap.value[getFmtString(item)] = item
|
||||
})
|
||||
emitter.emit('setKeepAlive', historys.value)
|
||||
}, {
|
||||
deep: true
|
||||
})
|
||||
|
||||
const initPage = () => {
|
||||
// 全局监听 关闭当前页面函数
|
||||
emitter.on('closeThisPage', () => {
|
||||
removeTab(getFmtString(route))
|
||||
})
|
||||
// 全局监听 关闭所有页面函数
|
||||
emitter.on('closeAllPage', () => {
|
||||
closeAll()
|
||||
})
|
||||
emitter.on('mobile', (data) => {
|
||||
isMobile.value = data
|
||||
})
|
||||
emitter.on('collapse', (data) => {
|
||||
isCollapse.value = data
|
||||
})
|
||||
|
||||
emitter.on('setQuery', (data) => {
|
||||
const index = historys.value.findIndex(
|
||||
(item) => getFmtString(item) === activeValue.value
|
||||
)
|
||||
historys.value[index].query = data
|
||||
activeValue.value = getFmtString(historys.value[index])
|
||||
const currentUrl = window.location.href.split('?')[0]
|
||||
const currentSearchParams = new URLSearchParams(data).toString()
|
||||
window.history.replaceState({}, '', `${currentUrl}?${currentSearchParams}`)
|
||||
sessionStorage.setItem('historys', JSON.stringify(historys.value))
|
||||
})
|
||||
|
||||
emitter.on('switchTab', async(data) => {
|
||||
const index = historys.value.findIndex((item) => item.name === data.name)
|
||||
if (index < 0) {
|
||||
return
|
||||
}
|
||||
for (const key in data.query) {
|
||||
data.query[key] = String(data.query[key])
|
||||
}
|
||||
for (const key in data.params) {
|
||||
data.params[key] = String(data.params[key])
|
||||
}
|
||||
|
||||
historys.value[index].query = data.query || {}
|
||||
historys.value[index].params = data.params || {}
|
||||
await nextTick()
|
||||
router.push(historys.value[index])
|
||||
})
|
||||
const initHistorys = [
|
||||
{
|
||||
name: defaultRouter.value,
|
||||
meta: {
|
||||
title: '首页',
|
||||
},
|
||||
query: {},
|
||||
params: {},
|
||||
},
|
||||
]
|
||||
setTab(route)
|
||||
historys.value =
|
||||
JSON.parse(sessionStorage.getItem('historys')) || initHistorys
|
||||
if (!window.sessionStorage.getItem('activeValue')) {
|
||||
activeValue.value = getFmtString(route)
|
||||
} else {
|
||||
activeValue.value = window.sessionStorage.getItem('activeValue')
|
||||
}
|
||||
if (window.sessionStorage.getItem('needCloseAll') === 'true') {
|
||||
closeAll()
|
||||
window.sessionStorage.removeItem('needCloseAll')
|
||||
}
|
||||
}
|
||||
initPage()
|
||||
|
||||
onUnmounted(() => {
|
||||
emitter.off('collapse')
|
||||
emitter.off('mobile')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.contextmenu {
|
||||
width: 100px;
|
||||
margin: 0;
|
||||
border: 1px solid #ccc;
|
||||
background: #fff;
|
||||
z-index: 3000;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding: 5px 0;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.el-tabs__item .el-icon-close {
|
||||
color: initial !important;
|
||||
}
|
||||
.el-tabs__item .dot {
|
||||
content: "";
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
margin-right: 8px;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.contextmenu li {
|
||||
margin: 0;
|
||||
padding: 7px 16px;
|
||||
}
|
||||
.contextmenu li:hover {
|
||||
background: #f2f2f2;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- 此文件禁止修改!如果您没有购买授权,请联系wx:shouzi_1994购买授权,未授权状态只需保留此代码 不影响任何正常使用 -->
|
||||
|
||||
<template>
|
||||
<div class="flex gap-4 items-center text-sm text-gray-500 justify-center mb-4">
|
||||
<span>Powered by</span>
|
||||
<span>Web-Admin</span>
|
||||
<slot />
|
||||
<span>Copyright</span>
|
||||
<span>利农天下</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<div @click="clickFull">
|
||||
<div
|
||||
v-if="isShow"
|
||||
class="gvaIcon gvaIcon-fullscreen-expand"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="gvaIcon gvaIcon-fullscreen-shrink"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import screenfull from 'screenfull' // 引入screenfull
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Screenfull',
|
||||
})
|
||||
|
||||
defineProps({
|
||||
width: {
|
||||
type: Number,
|
||||
default: 22
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 22
|
||||
},
|
||||
fill: {
|
||||
type: String,
|
||||
default: '#48576a'
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.on('change', changeFullShow)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
screenfull.off('change')
|
||||
})
|
||||
|
||||
const clickFull = () => {
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
const isShow = ref(true)
|
||||
const changeFullShow = () => {
|
||||
isShow.value = !screenfull.isFullscreen
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.screenfull-svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
margin-right: 32px;
|
||||
fill: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div class="search-component">
|
||||
<div
|
||||
class="gvaIcon gvaIcon-refresh"
|
||||
:class="[reload ? 'reloading' : '']"
|
||||
@click="handleReload"
|
||||
/>
|
||||
<Screenfull class="search-icon" />
|
||||
<div
|
||||
class="gvaIcon gvaIcon-customer-service"
|
||||
@click="toService"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Screenfull from '@/view/layout/screenfull/index.vue'
|
||||
import { emitter } from '@/utils/bus.js'
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'BtnBox',
|
||||
})
|
||||
|
||||
const reload = ref(false)
|
||||
const handleReload = () => {
|
||||
reload.value = true
|
||||
emitter.emit('reload')
|
||||
setTimeout(() => {
|
||||
reload.value = false
|
||||
}, 500)
|
||||
}
|
||||
const toService = () => {
|
||||
window.open('https://support.qq.com/product/371961')
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
.search-component {
|
||||
@apply inline-flex overflow-hidden text-center gap-5 mr-5;
|
||||
div{
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
.el-input__inner {
|
||||
@apply border-b border-solid border-gray-300;
|
||||
}
|
||||
.el-dropdown-link {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.reload {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.reloading{
|
||||
animation:turn 0.5s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes turn {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="drawer-container"
|
||||
icon="setting"
|
||||
@click="showSettingDrawer"
|
||||
/>
|
||||
<el-drawer
|
||||
v-model="drawer"
|
||||
title="系统配置"
|
||||
:direction="direction"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<div class="setting_body">
|
||||
<div class="setting_card">
|
||||
<div class="setting_content">
|
||||
<div class="theme-box">
|
||||
<div
|
||||
class="item"
|
||||
@click="changeMode('light')"
|
||||
>
|
||||
<div class="item-top">
|
||||
<el-icon
|
||||
v-if="userStore.mode === 'light'"
|
||||
class="check"
|
||||
>
|
||||
<check />
|
||||
</el-icon>
|
||||
<img src="https://gw.alipayobjects.com/zos/antfincdn/NQ%24zoisaD2/jpRkZQMyYRryryPNtyIC.svg">
|
||||
</div>
|
||||
<p>
|
||||
简约白
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
@click="changeMode('dark')"
|
||||
>
|
||||
<div class="item-top">
|
||||
<el-icon
|
||||
v-if="userStore.mode === 'dark'"
|
||||
class="check"
|
||||
>
|
||||
<check />
|
||||
</el-icon>
|
||||
<img src="https://gw.alipayobjects.com/zos/antfincdn/XwFOFbLkSM/LCkqqYNmvBEbokSDscrm.svg">
|
||||
</div>
|
||||
<p>
|
||||
商务黑
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
|
||||
defineOptions({
|
||||
name: 'Setting',
|
||||
})
|
||||
|
||||
const drawer = ref(false)
|
||||
const direction = ref('rtl')
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const handleClose = () => {
|
||||
drawer.value = false
|
||||
}
|
||||
const showSettingDrawer = () => {
|
||||
drawer.value = true
|
||||
}
|
||||
const changeMode = (e) => {
|
||||
if (e === null) {
|
||||
userStore.changeSideMode('dark')
|
||||
return
|
||||
}
|
||||
userStore.changeSideMode(e)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.drawer-container {
|
||||
transition: all 0.2s;
|
||||
&:hover{
|
||||
right: 0
|
||||
}
|
||||
position: fixed;
|
||||
right: -20px;
|
||||
bottom: 15%;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
color: #fff;
|
||||
border-radius: 4px 0 0 4px;
|
||||
cursor: pointer;
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0 ,0 ,0, 10%);
|
||||
}
|
||||
.setting_body{
|
||||
padding: 20px;
|
||||
.setting_card{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.setting_content{
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
>.theme-box{
|
||||
display: flex;
|
||||
}
|
||||
>.color-box{
|
||||
div{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.item{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
margin-right: 20px;
|
||||
.item-top{
|
||||
position: relative;
|
||||
}
|
||||
.check{
|
||||
position: absolute;
|
||||
font-size: 20px;
|
||||
color: #00afff;
|
||||
right:10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
p{
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
<template>
|
||||
<div
|
||||
id="userLayout"
|
||||
class="w-full h-full relative"
|
||||
>
|
||||
<div
|
||||
class="rounded-lg flex items-center justify-evenly w-full h-full bg-white md:w-screen md:h-screen md:bg-[#194bfb]"
|
||||
>
|
||||
<div class="md:w-3/5 w-10/12 h-full flex items-center justify-evenly">
|
||||
<div class="oblique h-[130%] w-3/5 bg-white transform -rotate-12 absolute -ml-52" />
|
||||
<!-- 分割斜块 -->
|
||||
<div class="z-[999] pt-12 pb-10 md:w-96 w-full rounded-lg flex flex-col justify-between box-border">
|
||||
<div>
|
||||
<div class="flex items-center justify-center">
|
||||
|
||||
<img
|
||||
class="w-24"
|
||||
:src="$GIN_VUE_ADMIN.appLogo"
|
||||
alt
|
||||
>
|
||||
</div>
|
||||
<div class="mb-9">
|
||||
<p class="text-center text-4xl font-bold">{{ $GIN_VUE_ADMIN.appName }}</p>
|
||||
<!-- <p class="text-center text-sm font-normal text-gray-500 mt-2.5">A management platform using Golang and Vue</p> -->
|
||||
</div>
|
||||
<el-form
|
||||
ref="loginForm"
|
||||
:model="loginFormData"
|
||||
:rules="rules"
|
||||
:validate-on-rule-change="false"
|
||||
@keyup.enter="submitForm"
|
||||
>
|
||||
<el-form-item
|
||||
prop="username"
|
||||
class="mb-6"
|
||||
>
|
||||
<el-input
|
||||
v-model="loginFormData.username"
|
||||
size="large"
|
||||
placeholder="请输入用户名"
|
||||
suffix-icon="user"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
prop="password"
|
||||
class="mb-6"
|
||||
>
|
||||
<el-input
|
||||
v-model="loginFormData.password"
|
||||
show-password
|
||||
size="large"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="loginFormData.openCaptcha"
|
||||
prop="captcha"
|
||||
class="mb-6"
|
||||
>
|
||||
<div class="flex w-full justify-between">
|
||||
<el-input
|
||||
v-model="loginFormData.captcha"
|
||||
placeholder="请输入验证码"
|
||||
size="large"
|
||||
class="flex-1 mr-5"
|
||||
/>
|
||||
<div class="w-1/3 h-11 bg-[#c3d4f2] rounded">
|
||||
<img
|
||||
v-if="picPath"
|
||||
class="w-full h-full"
|
||||
:src="picPath"
|
||||
alt="请输入验证码"
|
||||
@click="loginVerify()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item class="mb-6">
|
||||
<el-button
|
||||
class="shadow shadow-blue-600 h-11 w-full"
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="submitForm"
|
||||
>登 录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden md:block w-1/2 h-full float-right bg-[#194bfb]">
|
||||
<!-- <img
|
||||
class="h-full"
|
||||
src="@/assets/login_right_banner.jpg"
|
||||
alt="banner" > -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { captcha } from '@/api/user'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
|
||||
defineOptions({
|
||||
name: 'Login',
|
||||
})
|
||||
|
||||
// 验证函数
|
||||
const checkUsername = (rule, value, callback) => {
|
||||
if (value.length < 5) {
|
||||
return callback(new Error('请输入正确的用户名'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const checkPassword = (rule, value, callback) => {
|
||||
if (value.length < 6) {
|
||||
return callback(new Error('请输入正确的密码'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取验证码
|
||||
const loginVerify = async() => {
|
||||
const ele = await captcha()
|
||||
rules.captcha.push({
|
||||
max: ele.data.captchaLength,
|
||||
min: ele.data.captchaLength,
|
||||
message: `请输入${ele.data.captchaLength}位验证码`,
|
||||
trigger: 'blur',
|
||||
})
|
||||
picPath.value = ele.data.picPath
|
||||
loginFormData.captchaId = ele.data.captchaId
|
||||
loginFormData.openCaptcha = ele.data.openCaptcha
|
||||
}
|
||||
loginVerify()
|
||||
|
||||
// 登录相关操作
|
||||
const loginForm = ref(null)
|
||||
const picPath = ref('')
|
||||
const loginFormData = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
captcha: '',
|
||||
captchaId: '',
|
||||
openCaptcha: false,
|
||||
})
|
||||
const rules = reactive({
|
||||
username: [{ validator: checkUsername, trigger: 'blur' }],
|
||||
password: [{ validator: checkPassword, trigger: 'blur' }],
|
||||
captcha: [
|
||||
{
|
||||
message: '验证码格式不正确',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
const login = async() => {
|
||||
return await userStore.LoginIn(loginFormData)
|
||||
}
|
||||
const submitForm = () => {
|
||||
loginForm.value.validate(async(v) => {
|
||||
if (v) {
|
||||
const flag = await login()
|
||||
if (!flag) {
|
||||
loginVerify()
|
||||
}
|
||||
} else {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '请正确填写登录信息',
|
||||
showClose: true,
|
||||
})
|
||||
loginVerify()
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
@ -0,0 +1,507 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="grid grid-cols-12 w-full gap-2">
|
||||
<div class="col-span-3 h-full">
|
||||
<div class="w-full h-full bg-white px-4 py-8 rounded-lg shadow-lg box-border">
|
||||
<div class="user-card px-6 text-center bg-white shrink-0">
|
||||
<div class="flex justify-center">
|
||||
<SelectImage
|
||||
v-model="userStore.userInfo.headerImg"
|
||||
file-type="image"
|
||||
/>
|
||||
</div>
|
||||
<div class="py-6 text-center">
|
||||
<p
|
||||
v-if="!editFlag"
|
||||
class="text-3xl flex justify-center items-center gap-4"
|
||||
>
|
||||
{{ userStore.userInfo.nickName }}
|
||||
<el-icon
|
||||
class="cursor-pointer text-sm"
|
||||
color="#66b1ff"
|
||||
@click="openEdit"
|
||||
>
|
||||
<edit />
|
||||
</el-icon>
|
||||
</p>
|
||||
<p
|
||||
v-if="editFlag"
|
||||
class="flex justify-center items-center gap-4"
|
||||
>
|
||||
<el-input v-model="nickName" />
|
||||
<el-icon
|
||||
class="cursor-pointer"
|
||||
color="#67c23a"
|
||||
@click="enterEdit"
|
||||
>
|
||||
<check />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
class="cursor-pointer"
|
||||
color="#f23c3c"
|
||||
@click="closeEdit"
|
||||
>
|
||||
<close />
|
||||
</el-icon>
|
||||
</p>
|
||||
<p class="text-gray-500 mt-2 text-md">这个家伙很懒,什么都没有留下</p>
|
||||
</div>
|
||||
<div class="w-full h-full text-left">
|
||||
<ul class="inline-block h-full w-full">
|
||||
<li class="info-list">
|
||||
<el-icon>
|
||||
<user />
|
||||
</el-icon>
|
||||
{{ userStore.userInfo.nickName }}
|
||||
</li>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="light"
|
||||
content="北京反转极光科技有限公司-技术部-前端事业群"
|
||||
placement="top"
|
||||
>
|
||||
<li class="info-list">
|
||||
<el-icon>
|
||||
<data-analysis />
|
||||
</el-icon>
|
||||
北京反转极光科技有限公司-技术部-前端事业群
|
||||
</li>
|
||||
</el-tooltip>
|
||||
<li class="info-list">
|
||||
<el-icon>
|
||||
<video-camera />
|
||||
</el-icon>
|
||||
中国·北京市·朝阳区
|
||||
</li>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="light"
|
||||
content="GoLang/JavaScript/Vue/Gorm"
|
||||
placement="top"
|
||||
>
|
||||
<li class="info-list">
|
||||
<el-icon>
|
||||
<medal />
|
||||
</el-icon>
|
||||
GoLang/JavaScript/Vue/Gorm
|
||||
</li>
|
||||
</el-tooltip>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-9 ">
|
||||
<div class="bg-white h-full px-4 py-8 rounded-lg shadow-lg box-border">
|
||||
<el-tabs
|
||||
v-model="activeName"
|
||||
@tab-click="handleClick"
|
||||
>
|
||||
<el-tab-pane
|
||||
label="账号绑定"
|
||||
name="second"
|
||||
>
|
||||
<ul>
|
||||
<li class="borderd">
|
||||
<p class="pb-2.5 text-xl text-gray-600">密保手机</p>
|
||||
<p class="pb-2.5 text-lg text-gray-400">
|
||||
已绑定手机:{{ userStore.userInfo.phone }}
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="float-right text-blue-400"
|
||||
@click="changePhoneFlag = true"
|
||||
>立即修改</a>
|
||||
</p>
|
||||
</li>
|
||||
<li class="borderd pt-2.5">
|
||||
<p class="pb-2.5 text-xl text-gray-600">密保邮箱</p>
|
||||
<p class="pb-2.5 text-lg text-gray-400">
|
||||
已绑定邮箱:{{ userStore.userInfo.email }}
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="float-right text-blue-400"
|
||||
@click="changeEmailFlag = true"
|
||||
>立即修改</a>
|
||||
</p>
|
||||
</li>
|
||||
<li class="borderd pt-2.5">
|
||||
<p class="pb-2.5 text-xl text-gray-600">密保问题</p>
|
||||
<p class="pb-2.5 text-lg text-gray-400">
|
||||
未设置密保问题
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="float-right text-blue-400"
|
||||
>去设置</a>
|
||||
</p>
|
||||
</li>
|
||||
<li class="borderd pt-2.5">
|
||||
<p class="pb-2.5 text-xl text-gray-600">修改密码</p>
|
||||
<p class="pb-2.5 text-lg text-gray-400">
|
||||
修改个人密码
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="float-right text-blue-400"
|
||||
@click="showPassword = true"
|
||||
>修改密码</a>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="showPassword"
|
||||
title="修改密码"
|
||||
width="360px"
|
||||
@close="clearPassword"
|
||||
>
|
||||
<el-form
|
||||
ref="modifyPwdForm"
|
||||
:model="pwdModify"
|
||||
:rules="rules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item
|
||||
:minlength="6"
|
||||
label="原密码"
|
||||
prop="password"
|
||||
>
|
||||
<el-input
|
||||
v-model="pwdModify.password"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:minlength="6"
|
||||
label="新密码"
|
||||
prop="newPassword"
|
||||
>
|
||||
<el-input
|
||||
v-model="pwdModify.newPassword"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:minlength="6"
|
||||
label="确认密码"
|
||||
prop="confirmPassword"
|
||||
>
|
||||
<el-input
|
||||
v-model="pwdModify.confirmPassword"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button
|
||||
|
||||
@click="showPassword = false"
|
||||
>取 消</el-button>
|
||||
<el-button
|
||||
|
||||
type="primary"
|
||||
@click="savePassword"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="changePhoneFlag"
|
||||
title="绑定手机"
|
||||
width="600px"
|
||||
>
|
||||
<el-form :model="phoneForm">
|
||||
<el-form-item
|
||||
label="手机号"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-input
|
||||
v-model="phoneForm.phone"
|
||||
placeholder="请输入手机号"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="验证码"
|
||||
label-width="120px"
|
||||
>
|
||||
<div class="flex w-full gap-4">
|
||||
<el-input
|
||||
v-model="phoneForm.code"
|
||||
class="flex-1"
|
||||
autocomplete="off"
|
||||
placeholder="请自行设计短信服务,此处为模拟随便写"
|
||||
style="width:300px"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="time>0"
|
||||
@click="getCode"
|
||||
>{{ time>0?`(${time}s)后重新获取`:'获取验证码' }}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button
|
||||
|
||||
@click="closeChangePhone"
|
||||
>取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
|
||||
@click="changePhone"
|
||||
>更改</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="changeEmailFlag"
|
||||
title="绑定邮箱"
|
||||
width="600px"
|
||||
>
|
||||
<el-form :model="emailForm">
|
||||
<el-form-item
|
||||
label="邮箱"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-input
|
||||
v-model="emailForm.email"
|
||||
placeholder="请输入邮箱"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="验证码"
|
||||
label-width="120px"
|
||||
>
|
||||
<div class="flex w-full gap-4">
|
||||
<el-input
|
||||
v-model="emailForm.code"
|
||||
class="flex-1"
|
||||
placeholder="请自行设计邮件服务,此处为模拟随便写"
|
||||
autocomplete="off"
|
||||
style="width:300px"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="emailTime>0"
|
||||
@click="getEmailCode"
|
||||
>{{ emailTime>0?`(${emailTime}s)后重新获取`:'获取验证码' }}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button
|
||||
|
||||
@click="closeChangeEmail"
|
||||
>取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
|
||||
@click="changeEmail"
|
||||
>更改</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { setSelfInfo, changePassword } from '@/api/user.js'
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import SelectImage from '@/components/selectImage/selectImage.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Person',
|
||||
})
|
||||
|
||||
const activeName = ref('second')
|
||||
const rules = reactive({
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, message: '最少6个字符', trigger: 'blur' },
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, message: '最少6个字符', trigger: 'blur' },
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请输入确认密码', trigger: 'blur' },
|
||||
{ min: 6, message: '最少6个字符', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value !== pwdModify.value.newPassword) {
|
||||
callback(new Error('两次密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
const modifyPwdForm = ref(null)
|
||||
const showPassword = ref(false)
|
||||
const pwdModify = ref({})
|
||||
const nickName = ref('')
|
||||
const editFlag = ref(false)
|
||||
const savePassword = async() => {
|
||||
modifyPwdForm.value.validate((valid) => {
|
||||
if (valid) {
|
||||
changePassword({
|
||||
password: pwdModify.value.password,
|
||||
newPassword: pwdModify.value.newPassword,
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('修改密码成功!')
|
||||
}
|
||||
showPassword.value = false
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const clearPassword = () => {
|
||||
pwdModify.value = {
|
||||
password: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
}
|
||||
modifyPwdForm.value.clearValidate()
|
||||
}
|
||||
|
||||
watch(() => userStore.userInfo.headerImg, async(val) => {
|
||||
const res = await setSelfInfo({ headerImg: val })
|
||||
if (res.code === 0) {
|
||||
userStore.ResetUserInfo({ headerImg: val })
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '设置成功',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const openEdit = () => {
|
||||
nickName.value = userStore.userInfo.nickName
|
||||
editFlag.value = true
|
||||
}
|
||||
|
||||
const closeEdit = () => {
|
||||
nickName.value = ''
|
||||
editFlag.value = false
|
||||
}
|
||||
|
||||
const enterEdit = async() => {
|
||||
const res = await setSelfInfo({
|
||||
nickName: nickName.value
|
||||
})
|
||||
if (res.code === 0) {
|
||||
userStore.ResetUserInfo({ nickName: nickName.value })
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '设置成功',
|
||||
})
|
||||
}
|
||||
nickName.value = ''
|
||||
editFlag.value = false
|
||||
}
|
||||
|
||||
const handleClick = (tab, event) => {
|
||||
console.log(tab, event)
|
||||
}
|
||||
|
||||
const changePhoneFlag = ref(false)
|
||||
const time = ref(0)
|
||||
const phoneForm = reactive({
|
||||
phone: '',
|
||||
code: ''
|
||||
})
|
||||
|
||||
const getCode = async() => {
|
||||
time.value = 60
|
||||
let timer = setInterval(() => {
|
||||
time.value--
|
||||
if (time.value <= 0) {
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const closeChangePhone = () => {
|
||||
changePhoneFlag.value = false
|
||||
phoneForm.phone = ''
|
||||
phoneForm.code = ''
|
||||
}
|
||||
|
||||
const changePhone = async() => {
|
||||
const res = await setSelfInfo({ phone: phoneForm.phone })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('修改成功')
|
||||
userStore.ResetUserInfo({ phone: phoneForm.phone })
|
||||
closeChangePhone()
|
||||
}
|
||||
}
|
||||
|
||||
const changeEmailFlag = ref(false)
|
||||
const emailTime = ref(0)
|
||||
const emailForm = reactive({
|
||||
email: '',
|
||||
code: ''
|
||||
})
|
||||
|
||||
const getEmailCode = async() => {
|
||||
emailTime.value = 60
|
||||
let timer = setInterval(() => {
|
||||
emailTime.value--
|
||||
if (emailTime.value <= 0) {
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const closeChangeEmail = () => {
|
||||
changeEmailFlag.value = false
|
||||
emailForm.email = ''
|
||||
emailForm.code = ''
|
||||
}
|
||||
|
||||
const changeEmail = async() => {
|
||||
const res = await setSelfInfo({ email: emailForm.email })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('修改成功')
|
||||
userStore.ResetUserInfo({ email: emailForm.email })
|
||||
closeChangeEmail()
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.borderd {
|
||||
@apply border-b-2 border-solid border-gray-100 border-t-0 border-r-0 border-l-0;
|
||||
&:last-child{
|
||||
@apply border-b-0;
|
||||
}
|
||||
}
|
||||
|
||||
.info-list{
|
||||
@apply w-full whitespace-nowrap overflow-hidden text-ellipsis py-3 text-lg text-gray-700
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,437 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" :rules="searchRule"
|
||||
@keyup.enter="onSubmit">
|
||||
<el-form-item label="创建日期" prop="createdAt">
|
||||
<template #label>
|
||||
<span>
|
||||
创建日期
|
||||
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
|
||||
<el-icon>
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-date-picker v-model="searchInfo.startCreatedAt" type="datetime" placeholder="开始日期"
|
||||
:disabled-date="time=> searchInfo.endCreatedAt ? time.getTime() > searchInfo.endCreatedAt.getTime() : false"></el-date-picker>
|
||||
—
|
||||
<el-date-picker v-model="searchInfo.endCreatedAt" type="datetime" placeholder="结束日期"
|
||||
:disabled-date="time=> searchInfo.startCreatedAt ? time.getTime() < searchInfo.startCreatedAt.getTime() : false"></el-date-picker>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="onSubmit">查询</el-button>
|
||||
<el-button icon="refresh" @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" icon="plus" @click="openDialog">新增</el-button>
|
||||
<el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length"
|
||||
@click="onDelete">删除</el-button>
|
||||
</div>
|
||||
<el-table ref="multipleTable" style="width: 100%" tooltip-effect="dark" :data="tableData" row-key="ID"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column align="left" label="ID" prop="ID" width="120" />
|
||||
<el-table-column align="left" label="编码" prop="code" width="120" />
|
||||
<el-table-column align="left" label="名称" prop="name" width="120" />
|
||||
<el-table-column align="left" label="分类" prop="categoryId" width="120">
|
||||
<template #default="scope">{{ formatCategory(scope.row.categoryId) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="排序" prop="sortWeight" width="120" />
|
||||
<el-table-column align="left" label="状态" prop="status" width="120">
|
||||
<template #default="scope">{{ formatBoolean(scope.row.status) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="创建时间" width="180">
|
||||
<template #default="scope">{{ formatDate(scope.row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="操作" fixed="right" min-width="240">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link class="table-button" @click="getDetails(scope.row)">
|
||||
<el-icon style="margin-right: 5px">
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
查看详情
|
||||
</el-button>
|
||||
<el-button type="primary" link icon="edit" class="table-button"
|
||||
@click="updateProductFunc(scope.row)">变更</el-button>
|
||||
<el-button type="primary" link icon="delete" @click="deleteRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination layout="total, sizes, prev, pager, next, jumper" :current-page="page" :page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]" :total="total" @current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange" />
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer size="800" v-model="dialogFormVisible" :show-close="false" :before-close="closeDialog">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">{{type==='create'?'添加':'修改'}}</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="enterDialog">确 定</el-button>
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :model="formData" label-position="top" ref="elFormRef" :rules="rule" label-width="80px">
|
||||
<el-form-item label="编码:" prop="code">
|
||||
<el-input v-model="formData.code" :clearable="true" placeholder="请输入编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="名称:" prop="name">
|
||||
<el-input v-model="formData.name" :clearable="true" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类:">
|
||||
<el-select v-model="formData.categoryId">
|
||||
<el-option v-for="(item, index) in formData.categoryOptions" :key="index" :label="item.name"
|
||||
:value="item.ID">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序:" prop="sortWeight">
|
||||
<el-input v-model.number="formData.sortWeight" :clearable="true" placeholder="请输入排序" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-switch v-model="formData.status" active-color="#13ce66" inactive-color="#ff4949" active-text="开启"
|
||||
inactive-text="关闭" clearable></el-switch>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer size="800" v-model="detailShow" :before-close="closeDetailShow" destroy-on-close>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">查看详情</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="ID">
|
||||
{{ formData.ID }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="编码">
|
||||
{{ formData.code }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="名称">
|
||||
{{ formData.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="分类">
|
||||
{{ formatCategory(formData.categoryId) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="排序">
|
||||
{{ formData.sortWeight }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
{{ formatBoolean(formData.status) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(formData.createdAt) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDate(formData.updatedAt) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createProduct,
|
||||
deleteProduct,
|
||||
deleteProductByIds,
|
||||
updateProduct,
|
||||
findProduct,
|
||||
getProductList
|
||||
} from '@/api/product'
|
||||
import { getProductCategoryList } from '@/api/productCategory'
|
||||
|
||||
// 全量引入格式化工具 请按需保留
|
||||
import { getDictFunc, formatDate, formatBoolean, filterDict, ReturnArrImg, onDownloadFile } from '@/utils/format'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Product'
|
||||
})
|
||||
|
||||
// 自动化生成的字典(可能为空)以及字段
|
||||
const formData = ref({
|
||||
code: '',
|
||||
name: '',
|
||||
categoryId: 0,
|
||||
sortWeight: 0,
|
||||
status: false,
|
||||
categoryOptions: []
|
||||
})
|
||||
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
})
|
||||
|
||||
const searchRule = reactive({
|
||||
createdAt: [
|
||||
{ validator: (rule, value, callback) => {
|
||||
if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写结束日期'))
|
||||
} else if (!searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写开始日期'))
|
||||
} else if (searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt && (searchInfo.value.startCreatedAt.getTime() === searchInfo.value.endCreatedAt.getTime() || searchInfo.value.startCreatedAt.getTime() > searchInfo.value.endCreatedAt.getTime())) {
|
||||
callback(new Error('开始日期应当早于结束日期'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: 'change' }
|
||||
],
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
const elSearchFormRef = ref()
|
||||
|
||||
// =========== 表格控制部分 ===========
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
const cateoryList = ref([])
|
||||
|
||||
const formatCategory = (id) => {
|
||||
for (let i = 0; i < cateoryList.value.length; i++) {
|
||||
if (cateoryList.value[i].ID === id) {
|
||||
return cateoryList.value[i].name
|
||||
}
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// 重置
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const onSubmit = () => {
|
||||
elSearchFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
if (searchInfo.value.status === '') {
|
||||
searchInfo.value.status = null
|
||||
}
|
||||
getTableData()
|
||||
})
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 修改页面容量
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const getCategoryData = async() => {
|
||||
const resp = await getProductCategoryList({ page: 1, pageSize: 100 })
|
||||
if (resp.code === 0 && resp.data.list) {
|
||||
cateoryList.value = resp.data.list
|
||||
}
|
||||
}
|
||||
|
||||
getCategoryData()
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getProductList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
// ============== 表格控制部分结束 ===============
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
const setOptions = async() => {
|
||||
}
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
setOptions()
|
||||
|
||||
// 多选数据
|
||||
const multipleSelection = ref([])
|
||||
// 多选
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteRow = (row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteProductFunc(row)
|
||||
})
|
||||
}
|
||||
|
||||
// 多选删除
|
||||
const onDelete = async() => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const IDs = []
|
||||
if (multipleSelection.value.length === 0) {
|
||||
ElMessage({
|
||||
type: 'warning',
|
||||
message: '请选择要删除的数据'
|
||||
})
|
||||
return
|
||||
}
|
||||
multipleSelection.value &&
|
||||
multipleSelection.value.map(item => {
|
||||
IDs.push(item.ID)
|
||||
})
|
||||
const res = await deleteProductByIds({ IDs })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === IDs.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 行为控制标记(弹窗内部需要增还是改)
|
||||
const type = ref('')
|
||||
|
||||
// 更新行
|
||||
const updateProductFunc = async(row) => {
|
||||
const res = await findProduct({ ID: row.ID })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.reproduct
|
||||
formData.value.categoryOptions = cateoryList.value
|
||||
console.log(formData)
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteProductFunc = async (row) => {
|
||||
const res = await deleteProduct({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗控制标记
|
||||
const dialogFormVisible = ref(false)
|
||||
|
||||
// 查看详情控制标记
|
||||
const detailShow = ref(false)
|
||||
|
||||
// 打开详情弹窗
|
||||
const openDetailShow = () => {
|
||||
detailShow.value = true
|
||||
}
|
||||
|
||||
// 打开详情
|
||||
const getDetails = async (row) => {
|
||||
// 打开弹窗
|
||||
const res = await findProduct({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.reproduct
|
||||
openDetailShow()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭详情弹窗
|
||||
const closeDetailShow = () => {
|
||||
detailShow.value = false
|
||||
formData.value = {
|
||||
code: '',
|
||||
name: '',
|
||||
categoryId: 0,
|
||||
sortWeight: 0,
|
||||
status: false,
|
||||
}
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
code: '',
|
||||
name: '',
|
||||
categoryId: 0,
|
||||
sortWeight: 0,
|
||||
status: false,
|
||||
}
|
||||
}
|
||||
// 弹窗确定
|
||||
const enterDialog = async() => {
|
||||
elFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createProduct(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateProduct(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createProduct(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-form-box">
|
||||
<el-form :model="formData" ref="elFormRef" label-position="right" :rules="rule" label-width="80px">
|
||||
<el-form-item label="编码:" prop="code">
|
||||
<el-input v-model="formData.code" :clearable="true" placeholder="请输入编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="名称:" prop="name">
|
||||
<el-input v-model="formData.name" :clearable="true" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类:" prop="categoryId">
|
||||
<el-input v-model.number="formData.categoryId" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序:" prop="sortWeight">
|
||||
<el-input v-model.number="formData.sortWeight" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-switch v-model="formData.status" active-color="#13ce66" inactive-color="#ff4949" active-text="开启" inactive-text="关闭" clearable ></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<el-button type="primary" @click="back">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createProduct,
|
||||
updateProduct,
|
||||
findProduct
|
||||
} from '@/api/product'
|
||||
|
||||
defineOptions({
|
||||
name: 'ProductForm'
|
||||
})
|
||||
|
||||
// 自动获取字典
|
||||
import { getDictFunc } from '@/utils/format'
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const type = ref('')
|
||||
const formData = ref({
|
||||
code: '',
|
||||
name: '',
|
||||
categoryId: 0,
|
||||
sortWeight: 0,
|
||||
status: false,
|
||||
})
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
|
||||
// 初始化方法
|
||||
const init = async () => {
|
||||
// 建议通过url传参获取目标数据ID 调用 find方法进行查询数据操作 从而决定本页面是create还是update 以下为id作为url参数示例
|
||||
if (route.query.id) {
|
||||
const res = await findProduct({ ID: route.query.id })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.reproduct
|
||||
type.value = 'update'
|
||||
}
|
||||
} else {
|
||||
type.value = 'create'
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
// 保存按钮
|
||||
const save = async() => {
|
||||
elFormRef.value?.validate( async (valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createProduct(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateProduct(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createProduct(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 返回按钮
|
||||
const back = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,341 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" icon="plus" @click="openDialog">新增</el-button>
|
||||
<el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length"
|
||||
@click="onDelete">删除</el-button>
|
||||
</div>
|
||||
<el-table ref="multipleTable" style="width: 100%" tooltip-effect="dark" :data="tableData" row-key="ID"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column align="left" label="ID" prop="ID" width="120" />
|
||||
<el-table-column align="left" label="父级ID" prop="parentId" width="120" />
|
||||
<el-table-column align="left" label="分类名称" prop="name" width="120" />
|
||||
<el-table-column align="left" label="操作" fixed="right" min-width="240">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link class="table-button" @click="getDetails(scope.row)">
|
||||
<el-icon style="margin-right: 5px">
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
查看详情
|
||||
</el-button>
|
||||
<el-button type="primary" link icon="edit" class="table-button"
|
||||
@click="updateProductCategoryFunc(scope.row)">变更</el-button>
|
||||
<el-button type="primary" link icon="delete" @click="deleteRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination layout="total, sizes, prev, pager, next, jumper" :current-page="page" :page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]" :total="total" @current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange" />
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer size="800" v-model="dialogFormVisible" :show-close="false" :before-close="closeDialog">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">{{type==='create'?'添加':'修改'}}</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="enterDialog">确 定</el-button>
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :model="formData" label-position="top" ref="elFormRef" :rules="rule" label-width="80px">
|
||||
<el-form-item label="父级ID:" prop="parentId">
|
||||
<el-input v-model.number="formData.parentId" :clearable="true" placeholder="请输入父级ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类名称:" prop="name">
|
||||
<el-input v-model="formData.name" :clearable="true" placeholder="请输入分类名称" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer size="800" v-model="detailShow" :before-close="closeDetailShow" destroy-on-close>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">查看详情</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="ID">
|
||||
{{ formData.ID }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="父级ID">
|
||||
{{ formData.parentId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="分类名称">
|
||||
{{ formData.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(formData.createdAt) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDate(formData.updatedAt) }}
|
||||
</el-descriptions-item></el-descriptions>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createProductCategory,
|
||||
deleteProductCategory,
|
||||
deleteProductCategoryByIds,
|
||||
updateProductCategory,
|
||||
findProductCategory,
|
||||
getProductCategoryList
|
||||
} from '@/api/productCategory'
|
||||
|
||||
// 全量引入格式化工具 请按需保留
|
||||
import { getDictFunc, formatDate, formatBoolean, filterDict, ReturnArrImg, onDownloadFile } from '@/utils/format'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'ProductCategory'
|
||||
})
|
||||
|
||||
// 自动化生成的字典(可能为空)以及字段
|
||||
const formData = ref({
|
||||
name: '',
|
||||
parentId: 0,
|
||||
})
|
||||
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
})
|
||||
|
||||
const searchRule = reactive({
|
||||
createdAt: [
|
||||
{ validator: (rule, value, callback) => {
|
||||
if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写结束日期'))
|
||||
} else if (!searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写开始日期'))
|
||||
} else if (searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt && (searchInfo.value.startCreatedAt.getTime() === searchInfo.value.endCreatedAt.getTime() || searchInfo.value.startCreatedAt.getTime() > searchInfo.value.endCreatedAt.getTime())) {
|
||||
callback(new Error('开始日期应当早于结束日期'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: 'change' }
|
||||
],
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
const elSearchFormRef = ref()
|
||||
|
||||
// =========== 表格控制部分 ===========
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
|
||||
// 重置
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const onSubmit = () => {
|
||||
elSearchFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
getTableData()
|
||||
})
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 修改页面容量
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getProductCategoryList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
// ============== 表格控制部分结束 ===============
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
const setOptions = async() => {
|
||||
}
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
setOptions()
|
||||
|
||||
// 多选数据
|
||||
const multipleSelection = ref([])
|
||||
// 多选
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteRow = (row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteProductCategoryFunc(row)
|
||||
})
|
||||
}
|
||||
|
||||
// 多选删除
|
||||
const onDelete = async() => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const IDs = []
|
||||
if (multipleSelection.value.length === 0) {
|
||||
ElMessage({
|
||||
type: 'warning',
|
||||
message: '请选择要删除的数据'
|
||||
})
|
||||
return
|
||||
}
|
||||
multipleSelection.value &&
|
||||
multipleSelection.value.map(item => {
|
||||
IDs.push(item.ID)
|
||||
})
|
||||
const res = await deleteProductCategoryByIds({ IDs })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === IDs.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 行为控制标记(弹窗内部需要增还是改)
|
||||
const type = ref('')
|
||||
|
||||
// 更新行
|
||||
const updateProductCategoryFunc = async(row) => {
|
||||
const res = await findProductCategory({ ID: row.ID })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.reproductCategory
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteProductCategoryFunc = async(row) => {
|
||||
const res = await deleteProductCategory({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗控制标记
|
||||
const dialogFormVisible = ref(false)
|
||||
|
||||
// 查看详情控制标记
|
||||
const detailShow = ref(false)
|
||||
|
||||
// 打开详情弹窗
|
||||
const openDetailShow = () => {
|
||||
detailShow.value = true
|
||||
}
|
||||
|
||||
// 打开详情
|
||||
const getDetails = async(row) => {
|
||||
// 打开弹窗
|
||||
const res = await findProductCategory({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.reproductCategory
|
||||
openDetailShow()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭详情弹窗
|
||||
const closeDetailShow = () => {
|
||||
detailShow.value = false
|
||||
formData.value = {
|
||||
name: '',
|
||||
parentId: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
name: '',
|
||||
parentId: 0,
|
||||
}
|
||||
}
|
||||
// 弹窗确定
|
||||
const enterDialog = async() => {
|
||||
elFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createProductCategory(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateProductCategory(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createProductCategory(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-form-box">
|
||||
<el-form :model="formData" ref="elFormRef" label-position="right" :rules="rule" label-width="80px">
|
||||
<el-form-item label="父级Id:" prop="parentId">
|
||||
<el-input v-model.number="formData.parentId" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类名称:" prop="name">
|
||||
<el-input v-model="formData.name" :clearable="true" placeholder="请输入name字段" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<el-button type="primary" @click="back">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createProductCategory,
|
||||
updateProductCategory,
|
||||
findProductCategory
|
||||
} from '@/api/productCategory'
|
||||
|
||||
defineOptions({
|
||||
name: 'ProductCategoryForm'
|
||||
})
|
||||
|
||||
// 自动获取字典
|
||||
import { getDictFunc } from '@/utils/format'
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const type = ref('')
|
||||
const formData = ref({
|
||||
name: '',
|
||||
parentId: 0,
|
||||
})
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
|
||||
// 初始化方法
|
||||
const init = async () => {
|
||||
// 建议通过url传参获取目标数据ID 调用 find方法进行查询数据操作 从而决定本页面是create还是update 以下为id作为url参数示例
|
||||
if (route.query.id) {
|
||||
const res = await findProductCategory({ ID: route.query.id })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.reproductCategory
|
||||
type.value = 'update'
|
||||
}
|
||||
} else {
|
||||
type.value = 'create'
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
// 保存按钮
|
||||
const save = async() => {
|
||||
elFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createProductCategory(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateProductCategory(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createProductCategory(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 返回按钮
|
||||
const back = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,497 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" :rules="searchRule"
|
||||
@keyup.enter="onSubmit">
|
||||
<el-form-item label="创建日期" prop="createdAt">
|
||||
<template #label>
|
||||
<span>
|
||||
创建日期
|
||||
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
|
||||
<el-icon>
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-date-picker v-model="searchInfo.startCreatedAt" type="datetime" placeholder="开始日期"
|
||||
:disabled-date="time=> searchInfo.endCreatedAt ? time.getTime() > searchInfo.endCreatedAt.getTime() : false"></el-date-picker>
|
||||
—
|
||||
<el-date-picker v-model="searchInfo.endCreatedAt" type="datetime" placeholder="结束日期"
|
||||
:disabled-date="time=> searchInfo.startCreatedAt ? time.getTime() < searchInfo.startCreatedAt.getTime() : false"></el-date-picker>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="onSubmit">查询</el-button>
|
||||
<el-button icon="refresh" @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" icon="plus" @click="openDialog">新增</el-button>
|
||||
<el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length"
|
||||
@click="onDelete">删除</el-button>
|
||||
</div>
|
||||
<el-table ref="multipleTable" style="width: 100%" tooltip-effect="dark" :data="tableData" row-key="ID"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column align="left" label="ID" prop="ID" width="80" />
|
||||
<el-table-column align="left" label="产品" prop="productId" width="120">
|
||||
<template #default="scope">{{ formatProduct(scope.row.productId) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="发布日期" width="100">
|
||||
<template #default="scope">{{ formatOnlyDate(scope.row.publishDate) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="规格" prop="norm" width="120" />
|
||||
<el-table-column align="left" label="地区" prop="areaFullname" width="120" />
|
||||
<el-table-column align="left" label="价格类型" prop="priceType" width="80" />
|
||||
<el-table-column align="left" label="单位" prop="priceUnit" width="60" />
|
||||
<el-table-column align="left" label="最低价格" prop="priceMin" width="120" />
|
||||
<el-table-column align="left" label="最高价格" prop="priceMax" width="120" />
|
||||
<el-table-column align="left" label="状态" prop="status" width="80">
|
||||
<template #default="scope">{{ formatBoolean(scope.row.status) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="备注" prop="remark" width="120" />
|
||||
|
||||
<el-table-column align="left" label="创建时间" width="180">
|
||||
<template #default="scope">{{ formatDate(scope.row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="left" label="操作" fixed="right" min-width="240">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link class="table-button" @click="getDetails(scope.row)">
|
||||
<el-icon style="margin-right: 5px">
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
查看详情
|
||||
</el-button>
|
||||
<el-button type="primary" link icon="edit" class="table-button"
|
||||
@click="updateProductPriceFunc(scope.row)">变更</el-button>
|
||||
<el-button type="primary" link icon="delete" @click="deleteRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination layout="total, sizes, prev, pager, next, jumper" :current-page="page" :page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]" :total="total" @current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange" />
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer size="800" v-model="dialogFormVisible" :show-close="false" :before-close="closeDialog">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">{{type==='create'?'添加':'修改'}}</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="enterDialog">确 定</el-button>
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :model="formData" label-position="top" ref="elFormRef" :rules="rule" label-width="80px">
|
||||
<el-form-item label="产品Id:" prop="productId">
|
||||
<el-input v-model.number="formData.productId" :clearable="true" placeholder="请输入产品Id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发布日期:" prop="publishDate">
|
||||
<el-date-picker v-model="formData.publishDate" type="date" style="width:100%" placeholder="选择日期"
|
||||
:clearable="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格:" prop="norm">
|
||||
<el-input v-model="formData.norm" :clearable="true" placeholder="请输入规格" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区Id:" prop="areaId">
|
||||
<el-input v-model.number="formData.areaId" :clearable="true" placeholder="请输入地区Id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区:" prop="areaFullname">
|
||||
<el-input v-model="formData.areaFullname" :clearable="true" placeholder="请输入地区" />
|
||||
</el-form-item>
|
||||
<el-form-item label="价格类型:" prop="priceType">
|
||||
<el-input v-model="formData.priceType" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单位:" prop="priceUnit">
|
||||
<el-input v-model="formData.priceUnit" :clearable="true" placeholder="请输入单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最低价格:" prop="priceMin">
|
||||
<el-input-number v-model="formData.priceMin" style="width:100%" :precision="2" :clearable="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最高价格:" prop="priceMax">
|
||||
<el-input-number v-model="formData.priceMax" style="width:100%" :precision="2" :clearable="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-switch v-model="formData.status" active-color="#13ce66" inactive-color="#ff4949" active-text="是"
|
||||
inactive-text="否" clearable></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注:" prop="remark">
|
||||
<el-input v-model="formData.remark" :clearable="true" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer size="800" v-model="detailShow" :before-close="closeDetailShow" destroy-on-close>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">查看详情</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="ID">
|
||||
{{ formData.ID }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="产品">
|
||||
{{ formatProduct(formData.productId) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="发布日期">
|
||||
{{ formatOnlyDate(formData.publishDate) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="规格">
|
||||
{{ formData.norm }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="地区Id">
|
||||
{{ formData.areaId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="地区">
|
||||
{{ formData.areaFullname }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="价格类型">
|
||||
{{ formData.priceType }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单位">
|
||||
{{ formData.priceUnit }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最低价格">
|
||||
{{ formData.priceMin }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最高价格">
|
||||
{{ formData.priceMax }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
{{ formatBoolean(formData.status) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">
|
||||
{{ formData.remark }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(formData.createdAt) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDate(formData.updatedAt) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getProductList } from '@/api/product'
|
||||
import {
|
||||
createProductPrice,
|
||||
deleteProductPrice,
|
||||
deleteProductPriceByIds,
|
||||
updateProductPrice,
|
||||
findProductPrice,
|
||||
getProductPriceList
|
||||
} from '@/api/productPrice'
|
||||
|
||||
// 全量引入格式化工具 请按需保留
|
||||
import { formatDate, formatOnlyDate, formatBoolean } from '@/utils/format'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'ProductPrice'
|
||||
})
|
||||
|
||||
// 自动化生成的字典(可能为空)以及字段
|
||||
const formData = ref({
|
||||
productId: 0,
|
||||
publishDate: new Date(),
|
||||
norm: '',
|
||||
areaId: 0,
|
||||
areaFullname: '',
|
||||
priceType: false,
|
||||
priceUnit: '',
|
||||
priceMin: 0,
|
||||
priceMax: 0,
|
||||
status: false,
|
||||
remark: '',
|
||||
})
|
||||
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
})
|
||||
|
||||
const searchRule = reactive({
|
||||
createdAt: [
|
||||
{ validator: (rule, value, callback) => {
|
||||
if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写结束日期'))
|
||||
} else if (!searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写开始日期'))
|
||||
} else if (searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt && (searchInfo.value.startCreatedAt.getTime() === searchInfo.value.endCreatedAt.getTime() || searchInfo.value.startCreatedAt.getTime() > searchInfo.value.endCreatedAt.getTime())) {
|
||||
callback(new Error('开始日期应当早于结束日期'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: 'change' }
|
||||
],
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
const elSearchFormRef = ref()
|
||||
|
||||
// =========== 表格控制部分 ===========
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
const productList = ref([])
|
||||
|
||||
const formatProduct = (id) => {
|
||||
for (let i = 0; i < productList.value.length; i++) {
|
||||
if (productList.value[i].ID === id) {
|
||||
return productList.value[i].name
|
||||
}
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// 重置
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const onSubmit = () => {
|
||||
elSearchFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
if (searchInfo.value.priceType === '') {
|
||||
searchInfo.value.priceType = null
|
||||
}
|
||||
if (searchInfo.value.status === '') {
|
||||
searchInfo.value.status = null
|
||||
}
|
||||
getTableData()
|
||||
})
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 修改页面容量
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const getProductData = async() => {
|
||||
const resp = await getProductList({ page: 1, pageSize: 100 })
|
||||
if (resp.code === 0 && resp.data.list) {
|
||||
productList.value = resp.data.list
|
||||
}
|
||||
}
|
||||
|
||||
getProductData()
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getProductPriceList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
// ============== 表格控制部分结束 ===============
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
const setOptions = async() => {
|
||||
}
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
setOptions()
|
||||
|
||||
// 多选数据
|
||||
const multipleSelection = ref([])
|
||||
// 多选
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteRow = (row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteProductPriceFunc(row)
|
||||
})
|
||||
}
|
||||
|
||||
// 多选删除
|
||||
const onDelete = async() => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const IDs = []
|
||||
if (multipleSelection.value.length === 0) {
|
||||
ElMessage({
|
||||
type: 'warning',
|
||||
message: '请选择要删除的数据'
|
||||
})
|
||||
return
|
||||
}
|
||||
multipleSelection.value &&
|
||||
multipleSelection.value.map(item => {
|
||||
IDs.push(item.ID)
|
||||
})
|
||||
const res = await deleteProductPriceByIds({ IDs })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === IDs.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 行为控制标记(弹窗内部需要增还是改)
|
||||
const type = ref('')
|
||||
|
||||
// 更新行
|
||||
const updateProductPriceFunc = async(row) => {
|
||||
const res = await findProductPrice({ ID: row.ID })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.reproductPrice
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteProductPriceFunc = async(row) => {
|
||||
const res = await deleteProductPrice({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗控制标记
|
||||
const dialogFormVisible = ref(false)
|
||||
|
||||
// 查看详情控制标记
|
||||
const detailShow = ref(false)
|
||||
|
||||
// 打开详情弹窗
|
||||
const openDetailShow = () => {
|
||||
detailShow.value = true
|
||||
}
|
||||
|
||||
// 打开详情
|
||||
const getDetails = async(row) => {
|
||||
// 打开弹窗
|
||||
const res = await findProductPrice({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.reproductPrice
|
||||
openDetailShow()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭详情弹窗
|
||||
const closeDetailShow = () => {
|
||||
detailShow.value = false
|
||||
formData.value = {
|
||||
productId: 0,
|
||||
publishDate: new Date(),
|
||||
norm: '',
|
||||
areaId: 0,
|
||||
areaFullname: '',
|
||||
priceType: false,
|
||||
priceUnit: '',
|
||||
priceMin: 0,
|
||||
priceMax: 0,
|
||||
status: false,
|
||||
remark: '',
|
||||
}
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
productId: 0,
|
||||
publishDate: new Date(),
|
||||
norm: '',
|
||||
areaId: 0,
|
||||
areaFullname: '',
|
||||
priceType: false,
|
||||
priceUnit: '',
|
||||
priceMin: 0,
|
||||
priceMax: 0,
|
||||
status: false,
|
||||
remark: '',
|
||||
}
|
||||
}
|
||||
// 弹窗确定
|
||||
const enterDialog = async() => {
|
||||
elFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createProductPrice(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateProductPrice(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createProductPrice(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-form-box">
|
||||
<el-form :model="formData" ref="elFormRef" label-position="right" :rules="rule" label-width="80px">
|
||||
<el-form-item label="产品Id:" prop="productId">
|
||||
<el-input v-model.number="formData.productId" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发布日期:" prop="publishDate">
|
||||
<el-date-picker v-model="formData.publishDate" type="date" placeholder="选择日期" :clearable="true"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="规格:" prop="norm">
|
||||
<el-input v-model="formData.norm" :clearable="true" placeholder="请输入规格" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区Id:" prop="areaId">
|
||||
<el-input v-model.number="formData.areaId" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区:" prop="areaFullname">
|
||||
<el-input v-model="formData.areaFullname" :clearable="true" placeholder="请输入地区" />
|
||||
</el-form-item>
|
||||
<el-form-item label="价格类型:" prop="priceType">
|
||||
<el-switch v-model="formData.priceType" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="单位:" prop="priceUnit">
|
||||
<el-input v-model="formData.priceUnit" :clearable="true" placeholder="请输入单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最低价格:" prop="priceMin">
|
||||
<el-input-number v-model="formData.priceMin" :precision="2" :clearable="true"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="最高价格:" prop="priceMax">
|
||||
<el-input-number v-model="formData.priceMax" :precision="2" :clearable="true"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-switch v-model="formData.status" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注:" prop="remark">
|
||||
<el-input v-model="formData.remark" :clearable="true" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<el-button type="primary" @click="back">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createProductPrice,
|
||||
updateProductPrice,
|
||||
findProductPrice
|
||||
} from '@/api/productPrice'
|
||||
|
||||
defineOptions({
|
||||
name: 'ProductPriceForm'
|
||||
})
|
||||
|
||||
// 自动获取字典
|
||||
import { getDictFunc } from '@/utils/format'
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const type = ref('')
|
||||
const formData = ref({
|
||||
productId: 0,
|
||||
publishDate: new Date(),
|
||||
norm: '',
|
||||
areaId: 0,
|
||||
areaFullname: '',
|
||||
priceType: false,
|
||||
priceUnit: '',
|
||||
priceMin: 0,
|
||||
priceMax: 0,
|
||||
status: false,
|
||||
remark: '',
|
||||
})
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
|
||||
// 初始化方法
|
||||
const init = async () => {
|
||||
// 建议通过url传参获取目标数据ID 调用 find方法进行查询数据操作 从而决定本页面是create还是update 以下为id作为url参数示例
|
||||
if (route.query.id) {
|
||||
const res = await findProductPrice({ ID: route.query.id })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.reproductPrice
|
||||
type.value = 'update'
|
||||
}
|
||||
} else {
|
||||
type.value = 'create'
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
// 保存按钮
|
||||
const save = async() => {
|
||||
elFormRef.value?.validate( async (valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createProductPrice(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateProductPrice(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createProductPrice(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 返回按钮
|
||||
const back = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,641 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" :rules="searchRule"
|
||||
@keyup.enter="onSubmit">
|
||||
<el-form-item label="创建日期" prop="createdAt">
|
||||
<template #label>
|
||||
<span>
|
||||
创建日期
|
||||
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
|
||||
<el-icon>
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-date-picker v-model="searchInfo.startCreatedAt" type="datetime" placeholder="开始日期"
|
||||
:disabled-date="time=> searchInfo.endCreatedAt ? time.getTime() > searchInfo.endCreatedAt.getTime() : false"></el-date-picker>
|
||||
—
|
||||
<el-date-picker v-model="searchInfo.endCreatedAt" type="datetime" placeholder="结束日期"
|
||||
:disabled-date="time=> searchInfo.startCreatedAt ? time.getTime() < searchInfo.startCreatedAt.getTime() : false"></el-date-picker>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="onSubmit">查询</el-button>
|
||||
<el-button icon="refresh" @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" icon="plus" @click="openDialog">新增</el-button>
|
||||
<el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length"
|
||||
@click="onDelete">删除</el-button>
|
||||
</div>
|
||||
<el-table ref="multipleTable" style="width: 100%" tooltip-effect="dark" :data="tableData" row-key="ID"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column align="left" label="ID" prop="ID" width="60" />
|
||||
<el-table-column align="left" label="产品" prop="productId" width="80">
|
||||
<template #default="scope">{{ formatProduct(scope.row.productId) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="产品价格Id" prop="productPriceId" width="120" />
|
||||
<el-table-column align="left" label="公司价格Id" prop="companyPriceId" width="120" />
|
||||
<el-table-column align="left" label="规格" prop="norm" width="120" />
|
||||
<el-table-column align="left" label="地区Id" prop="areaId" width="80" />
|
||||
<el-table-column align="left" label="地区" prop="areaFullname" width="120" />
|
||||
<el-table-column align="left" label="品牌" prop="producter" width="120" />
|
||||
<el-table-column align="left" label="价格类型" prop="priceType" width="80">
|
||||
<template #default="scope">{{ formatPriceType(scope.row.priceType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="价格" prop="price" width="120" />
|
||||
<el-table-column align="left" label="单位" prop="priceUnit" width="60" />
|
||||
<el-table-column align="left" label="公司" prop="companyName" width="220" />
|
||||
<el-table-column align="left" label="发布日期" width="120">
|
||||
<template #default="scope">{{ formatOnlyDate(scope.row.publishDate) }}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="left" label="创建时间" width="180">
|
||||
<template #default="scope">{{ formatDate(scope.row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="更新时间" width="180">
|
||||
<template #default="scope">{{ formatDate(scope.row.updatedAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="爬虫表.Id" prop="crawTableId" width="220">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="getCrawlData(scope.row)">
|
||||
{{ scope.row.crawTable }}.{{ scope.row.crawTableId }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="left" label="操作" fixed="right" min-width="240">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link class="table-button" @click="getDetails(scope.row)">
|
||||
<el-icon style="margin-right: 5px">
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
查看详情
|
||||
</el-button>
|
||||
<el-button type="primary" link icon="edit" class="table-button"
|
||||
@click="updateSourcePriceFunc(scope.row)">变更</el-button>
|
||||
<el-button type="primary" link icon="delete" @click="deleteRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination layout="total, sizes, prev, pager, next, jumper" :current-page="page" :page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]" :total="total" @current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange" />
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer size="800" v-model="dialogFormVisible" :show-close="false" :before-close="closeDialog">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">{{type==='create'?'添加':'修改'}}</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="enterDialog">确 定</el-button>
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :model="formData" label-position="top" ref="elFormRef" :rules="rule" label-width="80px">
|
||||
<el-form-item label="爬虫表:" prop="crawTable">
|
||||
<el-input v-model="formData.crawTable" :clearable="true" placeholder="请输入爬虫表" />
|
||||
</el-form-item>
|
||||
<el-form-item label="爬虫表Id:" prop="crawTableId">
|
||||
<el-input v-model.number="formData.crawTableId" :clearable="true" placeholder="请输入爬虫表Id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品Id:" prop="productId">
|
||||
<el-input v-model.number="formData.productId" :clearable="true" placeholder="请输入产品Id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品价格Id:" prop="productPriceId">
|
||||
<el-input v-model.number="formData.productPriceId" :clearable="true" placeholder="请输入产品价格Id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司价格Id:" prop="companyPriceId">
|
||||
<el-input v-model.number="formData.companyPriceId" :clearable="true" placeholder="请输入公司价格Id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格:" prop="norm">
|
||||
<el-input v-model="formData.norm" :clearable="true" placeholder="请输入规格" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区Id:" prop="areaId">
|
||||
<el-input v-model.number="formData.areaId" :clearable="true" placeholder="请输入地区Id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区:" prop="areaFullname">
|
||||
<el-input v-model="formData.areaFullname" :clearable="true" placeholder="请输入地区" />
|
||||
</el-form-item>
|
||||
<el-form-item label="品牌:" prop="producter">
|
||||
<el-input v-model="formData.producter" :clearable="true" placeholder="请输入品牌" />
|
||||
</el-form-item>
|
||||
<el-form-item label="价格类型:" prop="priceType">
|
||||
<el-switch v-model="formData.priceType" active-color="#13ce66" inactive-color="#ff4949" active-text="是"
|
||||
inactive-text="否" clearable></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="价格:" prop="price">
|
||||
<el-input-number v-model="formData.price" style="width:100%" :precision="2" :clearable="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单位:" prop="priceUnit">
|
||||
<el-input v-model="formData.priceUnit" :clearable="true" placeholder="请输入单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司:" prop="companyName">
|
||||
<el-input v-model="formData.companyName" :clearable="true" placeholder="请输入公司" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发布日期:" prop="publishDate">
|
||||
<el-date-picker v-model="formData.publishDate" type="date" style="width:100%" placeholder="选择日期"
|
||||
:clearable="true" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer size="800" v-model="detailShow" :before-close="closeDetailShow" destroy-on-close>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">查看详情</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="ID">
|
||||
{{ formData.ID }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="爬虫表">
|
||||
{{ formData.crawTable }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="爬虫表Id">
|
||||
{{ formData.crawTableId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="产品Id">
|
||||
{{ formatProduct(formData.productId) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="产品价格Id">
|
||||
{{ formData.productPriceId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="公司价格Id">
|
||||
{{ formData.companyPriceId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="规格">
|
||||
{{ formData.norm }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="地区Id">
|
||||
{{ formData.areaId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="地区">
|
||||
{{ formData.areaFullname }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="品牌">
|
||||
{{ formData.producter }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="价格类型">
|
||||
{{ formatPriceType(formData.priceType) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="价格">
|
||||
{{ formData.price }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单位">
|
||||
{{ formData.priceUnit }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="公司">
|
||||
{{ formData.companyName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="发布日期">
|
||||
{{ formatOnlyDate(formData.publishDate) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(formData.createdAt) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDate(formData.updatedAt) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer size="800" v-model="crawl100ppiDetail" :before-close="closeCrawl100ppiShow" destroy-on-close>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">查看爬虫数据详情</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="ID">
|
||||
{{ formData.ID }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="产品Code">
|
||||
{{ formData.code }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="产品名称">
|
||||
{{ formData.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="规格">
|
||||
{{ formData.norm }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="品牌">
|
||||
{{ formData.producer }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="价格类型">
|
||||
{{ formData.priceType }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单位">
|
||||
{{ formData.priceUnit }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="价格">
|
||||
{{ formData.price }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="地区">
|
||||
{{ formData.areaDetail }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="公司">
|
||||
{{ formData.dealer }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="发布日期">
|
||||
{{ formData.publishDate }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="数据唯一值">
|
||||
{{ formData.uniqueCode }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="抓取时间">
|
||||
{{ formData.crawlTime }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
{{ formData.status }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="同步失败原因">
|
||||
{{ formData.syncError }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(formData.createdAt) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDate(formData.updatedAt) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { findCrawl100ppi } from '@/api/crawl100ppi'
|
||||
import { getProductList } from '@/api/product'
|
||||
import {
|
||||
createSourcePrice,
|
||||
deleteSourcePrice,
|
||||
deleteSourcePriceByIds,
|
||||
updateSourcePrice,
|
||||
findSourcePrice,
|
||||
getSourcePriceList
|
||||
} from '@/api/sourcePrice'
|
||||
|
||||
// 全量引入格式化工具 请按需保留
|
||||
import { formatDate, formatOnlyDate, formatPriceType } from '@/utils/format'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'SourcePrice'
|
||||
})
|
||||
|
||||
// 自动化生成的字典(可能为空)以及字段
|
||||
const formData = ref({
|
||||
crawTable: '',
|
||||
crawTableId: 0,
|
||||
productId: 0,
|
||||
productPriceId: 0,
|
||||
companyPriceId: 0,
|
||||
norm: '',
|
||||
areaId: 0,
|
||||
areaFullname: '',
|
||||
producter: '',
|
||||
priceType: false,
|
||||
price: 0,
|
||||
priceUnit: '',
|
||||
companyName: '',
|
||||
publishDate: new Date(),
|
||||
})
|
||||
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
})
|
||||
|
||||
const searchRule = reactive({
|
||||
createdAt: [
|
||||
{ validator: (rule, value, callback) => {
|
||||
if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写结束日期'))
|
||||
} else if (!searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写开始日期'))
|
||||
} else if (searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt && (searchInfo.value.startCreatedAt.getTime() === searchInfo.value.endCreatedAt.getTime() || searchInfo.value.startCreatedAt.getTime() > searchInfo.value.endCreatedAt.getTime())) {
|
||||
callback(new Error('开始日期应当早于结束日期'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: 'change' }
|
||||
],
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
const elSearchFormRef = ref()
|
||||
|
||||
// =========== 表格控制部分 ===========
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
const productList = ref([])
|
||||
|
||||
const formatProduct = (id) => {
|
||||
for (let i = 0; i < productList.value.length; i++) {
|
||||
if (productList.value[i].ID === id) {
|
||||
return productList.value[i].name
|
||||
}
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
const formmatStatus = (stauts) => {
|
||||
switch (status) {
|
||||
case '1': return '待同步'
|
||||
case '2': return '已同步'
|
||||
case '3': return '同步失败'
|
||||
}
|
||||
}
|
||||
|
||||
// 重置
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const onSubmit = () => {
|
||||
elSearchFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
if (searchInfo.value.priceType === '') {
|
||||
searchInfo.value.priceType = null
|
||||
}
|
||||
getTableData()
|
||||
})
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 修改页面容量
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const getProductData = async() => {
|
||||
const resp = await getProductList({ page: 1, pageSize: 100 })
|
||||
if (resp.code === 0 && resp.data.list) {
|
||||
productList.value = resp.data.list
|
||||
}
|
||||
}
|
||||
|
||||
getProductData()
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getSourcePriceList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
// ============== 表格控制部分结束 ===============
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
const setOptions = async() => {
|
||||
}
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
setOptions()
|
||||
|
||||
// 多选数据
|
||||
const multipleSelection = ref([])
|
||||
// 多选
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteRow = (row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteSourcePriceFunc(row)
|
||||
})
|
||||
}
|
||||
|
||||
// 多选删除
|
||||
const onDelete = async() => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const IDs = []
|
||||
if (multipleSelection.value.length === 0) {
|
||||
ElMessage({
|
||||
type: 'warning',
|
||||
message: '请选择要删除的数据'
|
||||
})
|
||||
return
|
||||
}
|
||||
multipleSelection.value &&
|
||||
multipleSelection.value.map(item => {
|
||||
IDs.push(item.ID)
|
||||
})
|
||||
const res = await deleteSourcePriceByIds({ IDs })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === IDs.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 行为控制标记(弹窗内部需要增还是改)
|
||||
const type = ref('')
|
||||
|
||||
// 更新行
|
||||
const updateSourcePriceFunc = async(row) => {
|
||||
const res = await findSourcePrice({ ID: row.ID })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.resourcePrice
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteSourcePriceFunc = async(row) => {
|
||||
const res = await deleteSourcePrice({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗控制标记
|
||||
const dialogFormVisible = ref(false)
|
||||
|
||||
// 查看详情控制标记
|
||||
const detailShow = ref(false)
|
||||
|
||||
const crawl100ppiDetail = ref(false)
|
||||
|
||||
// 打开详情弹窗
|
||||
const openDetailShow = () => {
|
||||
detailShow.value = true
|
||||
}
|
||||
|
||||
// 打开详情
|
||||
const getDetails = async(row) => {
|
||||
// 打开弹窗
|
||||
const res = await findSourcePrice({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.resourcePrice
|
||||
openDetailShow()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭详情弹窗
|
||||
const closeDetailShow = () => {
|
||||
detailShow.value = false
|
||||
formData.value = {
|
||||
crawTable: '',
|
||||
crawTableId: 0,
|
||||
productId: 0,
|
||||
productPriceId: 0,
|
||||
companyPriceId: 0,
|
||||
norm: '',
|
||||
areaId: 0,
|
||||
areaFullname: '',
|
||||
producter: '',
|
||||
priceType: false,
|
||||
price: 0,
|
||||
priceUnit: '',
|
||||
companyName: '',
|
||||
publishDate: new Date(),
|
||||
}
|
||||
}
|
||||
|
||||
const openCrawl100ppiShow = () => {
|
||||
crawl100ppiDetail.value = true
|
||||
}
|
||||
|
||||
// 关闭详情弹窗
|
||||
const closeCrawl100ppiShow = () => {
|
||||
crawl100ppiDetail.value = false
|
||||
formData.value = {
|
||||
code: '',
|
||||
name: '',
|
||||
norm: '',
|
||||
producer: '',
|
||||
priceType: '',
|
||||
priceUnit: '',
|
||||
price: '',
|
||||
areaDetail: '',
|
||||
dealer: '',
|
||||
publishDate: '',
|
||||
uniqueCode: '',
|
||||
crawlTime: '',
|
||||
status: 0,
|
||||
syncError: '',
|
||||
}
|
||||
}
|
||||
// 打开爬虫详情
|
||||
const getCrawlData = async(row) => {
|
||||
// 打开弹窗
|
||||
if (row.crawTable === 'nzhq_crawl_100ppi') {
|
||||
const res = await findCrawl100ppi({ ID: row.crawTableId })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.recrawl100ppi
|
||||
openCrawl100ppiShow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
crawTable: '',
|
||||
crawTableId: 0,
|
||||
productId: 0,
|
||||
productPriceId: 0,
|
||||
companyPriceId: 0,
|
||||
norm: '',
|
||||
areaId: 0,
|
||||
areaFullname: '',
|
||||
producter: '',
|
||||
priceType: false,
|
||||
price: 0,
|
||||
priceUnit: '',
|
||||
companyName: '',
|
||||
publishDate: new Date(),
|
||||
}
|
||||
}
|
||||
// 弹窗确定
|
||||
const enterDialog = async() => {
|
||||
elFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createSourcePrice(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateSourcePrice(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createSourcePrice(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-form-box">
|
||||
<el-form :model="formData" ref="elFormRef" label-position="right" :rules="rule" label-width="80px">
|
||||
<el-form-item label="爬虫表:" prop="crawTable">
|
||||
<el-input v-model="formData.crawTable" :clearable="true" placeholder="请输入爬虫表" />
|
||||
</el-form-item>
|
||||
<el-form-item label="爬虫表Id:" prop="crawTableId">
|
||||
<el-input v-model.number="formData.crawTableId" :clearable="true" placeholder="请输入爬虫表Id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品Id:" prop="productId">
|
||||
<el-input v-model.number="formData.productId" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品价格Id:" prop="productPriceId">
|
||||
<el-input v-model.number="formData.productPriceId" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司价格Id:" prop="companyPriceId">
|
||||
<el-input v-model.number="formData.companyPriceId" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格:" prop="norm">
|
||||
<el-input v-model="formData.norm" :clearable="true" placeholder="请输入规格" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区Id:" prop="areaId">
|
||||
<el-input v-model.number="formData.areaId" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区:" prop="areaFullname">
|
||||
<el-input v-model="formData.areaFullname" :clearable="true" placeholder="请输入地区" />
|
||||
</el-form-item>
|
||||
<el-form-item label="品牌:" prop="producter">
|
||||
<el-input v-model="formData.producter" :clearable="true" placeholder="请输入品牌" />
|
||||
</el-form-item>
|
||||
<el-form-item label="价格类型:" prop="priceType">
|
||||
<el-switch v-model="formData.priceType" active-color="#13ce66" inactive-color="#ff4949" active-text="是"
|
||||
inactive-text="否" clearable></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="价格:" prop="price">
|
||||
<el-input-number v-model="formData.price" :precision="2" :clearable="true"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="单位:" prop="priceUnit">
|
||||
<el-input v-model="formData.priceUnit" :clearable="true" placeholder="请输入单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司:" prop="companyName">
|
||||
<el-input v-model="formData.companyName" :clearable="true" placeholder="请输入公司" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发布日期:" prop="publishDate">
|
||||
<el-date-picker v-model="formData.publishDate" type="date" placeholder="选择日期"
|
||||
:clearable="true"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<el-button type="primary" @click="back">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createSourcePrice,
|
||||
updateSourcePrice,
|
||||
findSourcePrice
|
||||
} from '@/api/sourcePrice'
|
||||
|
||||
defineOptions({
|
||||
name: 'SourcePriceForm'
|
||||
})
|
||||
|
||||
// 自动获取字典
|
||||
import { getDictFunc } from '@/utils/format'
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const type = ref('')
|
||||
const formData = ref({
|
||||
crawTable: '',
|
||||
crawTableId: 0,
|
||||
productId: 0,
|
||||
productPriceId: 0,
|
||||
companyPriceId: 0,
|
||||
norm: '',
|
||||
areaId: 0,
|
||||
areaFullname: '',
|
||||
producter: '',
|
||||
priceType: false,
|
||||
price: 0,
|
||||
priceUnit: '',
|
||||
companyName: '',
|
||||
publishDate: new Date(),
|
||||
})
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
|
||||
// 初始化方法
|
||||
const init = async () => {
|
||||
// 建议通过url传参获取目标数据ID 调用 find方法进行查询数据操作 从而决定本页面是create还是update 以下为id作为url参数示例
|
||||
if (route.query.id) {
|
||||
const res = await findSourcePrice({ ID: route.query.id })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.resourcePrice
|
||||
type.value = 'update'
|
||||
}
|
||||
} else {
|
||||
type.value = 'create'
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
// 保存按钮
|
||||
const save = async() => {
|
||||
elFormRef.value?.validate( async (valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createSourcePrice(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateSourcePrice(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createSourcePrice(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 返回按钮
|
||||
const back = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,512 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
:inline="true"
|
||||
:model="searchInfo"
|
||||
>
|
||||
<el-form-item label="路径">
|
||||
<el-input
|
||||
v-model="searchInfo.path"
|
||||
placeholder="路径"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input
|
||||
v-model="searchInfo.description"
|
||||
placeholder="描述"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="API组">
|
||||
<el-input
|
||||
v-model="searchInfo.apiGroup"
|
||||
placeholder="api组"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="请求">
|
||||
<el-select
|
||||
v-model="searchInfo.method"
|
||||
clearable
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in methodOptions"
|
||||
:key="item.value"
|
||||
:label="`${item.label}(${item.value})`"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="search"
|
||||
@click="onSubmit"
|
||||
>查询</el-button>
|
||||
<el-button
|
||||
icon="refresh"
|
||||
@click="onReset"
|
||||
>重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="openDialog('addApi')"
|
||||
>新增</el-button>
|
||||
<el-icon
|
||||
class="cursor-pointer"
|
||||
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=7&vd_source=f2640257c21e3b547a790461ed94875e')"
|
||||
><VideoCameraFilled /></el-icon>
|
||||
<el-button
|
||||
icon="delete"
|
||||
:disabled="!apis.length"
|
||||
@click="onDelete"
|
||||
>删除</el-button>
|
||||
<el-button
|
||||
icon="Refresh"
|
||||
@click="onFresh"
|
||||
>刷新缓存</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
@sort-change="sortChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
width="55"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="id"
|
||||
min-width="60"
|
||||
prop="ID"
|
||||
sortable="custom"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="API路径"
|
||||
min-width="150"
|
||||
prop="path"
|
||||
sortable="custom"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="API分组"
|
||||
min-width="150"
|
||||
prop="apiGroup"
|
||||
sortable="custom"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="API简介"
|
||||
min-width="150"
|
||||
prop="description"
|
||||
sortable="custom"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="请求"
|
||||
min-width="150"
|
||||
prop="method"
|
||||
sortable="custom"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
{{ scope.row.method }} / {{ methodFilter(scope.row.method) }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
align="left"
|
||||
fixed="right"
|
||||
label="操作"
|
||||
width="200"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="edit"
|
||||
|
||||
type="primary"
|
||||
link
|
||||
@click="editApiFunc(scope.row)"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
icon="delete"
|
||||
|
||||
type="primary"
|
||||
link
|
||||
@click="deleteApiFunc(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:before-close="closeDialog"
|
||||
:title="dialogTitle"
|
||||
>
|
||||
<warning-bar title="新增API,需要在角色管理内配置权限才可使用" />
|
||||
<el-form
|
||||
ref="apiForm"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item
|
||||
label="路径"
|
||||
prop="path"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.path"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="请求"
|
||||
prop="method"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.method"
|
||||
placeholder="请选择"
|
||||
style="width:100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in methodOptions"
|
||||
:key="item.value"
|
||||
:label="`${item.label}(${item.value})`"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="api分组"
|
||||
prop="apiGroup"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.apiGroup"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="api简介"
|
||||
prop="description"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.description"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="enterDialog"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
getApiById,
|
||||
getApiList,
|
||||
createApi,
|
||||
updateApi,
|
||||
deleteApi,
|
||||
deleteApisByIds,
|
||||
freshCasbin
|
||||
} from '@/api/api'
|
||||
import { toSQLLine } from '@/utils/stringFun'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { VideoCameraFilled } from '@element-plus/icons-vue'
|
||||
import { toDoc } from '@/utils/doc'
|
||||
|
||||
defineOptions({
|
||||
name: 'Api',
|
||||
})
|
||||
|
||||
const methodFilter = (value) => {
|
||||
const target = methodOptions.value.filter(item => item.value === value)[0]
|
||||
return target && `${target.label}`
|
||||
}
|
||||
|
||||
const apis = ref([])
|
||||
const form = ref({
|
||||
path: '',
|
||||
apiGroup: '',
|
||||
method: '',
|
||||
description: ''
|
||||
})
|
||||
const methodOptions = ref([
|
||||
{
|
||||
value: 'POST',
|
||||
label: '创建',
|
||||
type: 'success'
|
||||
},
|
||||
{
|
||||
value: 'GET',
|
||||
label: '查看',
|
||||
type: ''
|
||||
},
|
||||
{
|
||||
value: 'PUT',
|
||||
label: '更新',
|
||||
type: 'warning'
|
||||
},
|
||||
{
|
||||
value: 'DELETE',
|
||||
label: '删除',
|
||||
type: 'danger'
|
||||
}
|
||||
])
|
||||
|
||||
const type = ref('')
|
||||
const rules = ref({
|
||||
path: [{ required: true, message: '请输入api路径', trigger: 'blur' }],
|
||||
apiGroup: [
|
||||
{ required: true, message: '请输入组名称', trigger: 'blur' }
|
||||
],
|
||||
method: [
|
||||
{ required: true, message: '请选择请求方式', trigger: 'blur' }
|
||||
],
|
||||
description: [
|
||||
{ required: true, message: '请输入api介绍', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
}
|
||||
// 搜索
|
||||
|
||||
const onSubmit = () => {
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 排序
|
||||
const sortChange = ({ prop, order }) => {
|
||||
if (prop) {
|
||||
if (prop === 'ID') {
|
||||
prop = 'id'
|
||||
}
|
||||
searchInfo.value.orderKey = toSQLLine(prop)
|
||||
searchInfo.value.desc = order === 'descending'
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getApiList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
// 批量操作
|
||||
const handleSelectionChange = (val) => {
|
||||
apis.value = val
|
||||
}
|
||||
|
||||
const onDelete = async() => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const ids = apis.value.map(item => item.ID)
|
||||
const res = await deleteApisByIds({ ids })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: res.msg
|
||||
})
|
||||
if (tableData.value.length === ids.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
const onFresh = async() => {
|
||||
ElMessageBox.confirm('确定要刷新缓存吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const res = await freshCasbin()
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: res.msg
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗相关
|
||||
const apiForm = ref(null)
|
||||
const initForm = () => {
|
||||
apiForm.value.resetFields()
|
||||
form.value = {
|
||||
path: '',
|
||||
apiGroup: '',
|
||||
method: '',
|
||||
description: ''
|
||||
}
|
||||
}
|
||||
|
||||
const dialogTitle = ref('新增Api')
|
||||
const dialogFormVisible = ref(false)
|
||||
const openDialog = (key) => {
|
||||
switch (key) {
|
||||
case 'addApi':
|
||||
dialogTitle.value = '新增Api'
|
||||
break
|
||||
case 'edit':
|
||||
dialogTitle.value = '编辑Api'
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
type.value = key
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
const closeDialog = () => {
|
||||
initForm()
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
|
||||
const editApiFunc = async(row) => {
|
||||
const res = await getApiById({ id: row.ID })
|
||||
form.value = res.data.api
|
||||
openDialog('edit')
|
||||
}
|
||||
|
||||
const enterDialog = async() => {
|
||||
apiForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
switch (type.value) {
|
||||
case 'addApi':
|
||||
{
|
||||
const res = await createApi(form.value)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '添加成功',
|
||||
showClose: true
|
||||
})
|
||||
}
|
||||
getTableData()
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
break
|
||||
case 'edit':
|
||||
{
|
||||
const res = await updateApi(form.value)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '编辑成功',
|
||||
showClose: true
|
||||
})
|
||||
}
|
||||
getTableData()
|
||||
closeDialog()
|
||||
}
|
||||
break
|
||||
default:
|
||||
// eslint-disable-next-line no-lone-blocks
|
||||
{
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '未知操作',
|
||||
showClose: true
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const deleteApiFunc = async(row) => {
|
||||
ElMessageBox.confirm('此操作将永久删除所有角色下该api, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async() => {
|
||||
const res = await deleteApi(row)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功!'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.warning {
|
||||
color: #dc143c;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,468 @@
|
|||
<template>
|
||||
<div class="authority">
|
||||
<warning-bar title="注:右上角头像下拉可切换角色" />
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="addAuthority(0)"
|
||||
>新增角色</el-button>
|
||||
<el-icon
|
||||
class="cursor-pointer"
|
||||
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=8&vd_source=f2640257c21e3b547a790461ed94875e')"
|
||||
><VideoCameraFilled /></el-icon>
|
||||
</div>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
|
||||
row-key="authorityId"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column
|
||||
label="角色ID"
|
||||
min-width="180"
|
||||
prop="authorityId"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="角色名称"
|
||||
min-width="180"
|
||||
prop="authorityName"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="操作"
|
||||
width="460"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="setting"
|
||||
|
||||
type="primary"
|
||||
link
|
||||
@click="openDrawer(scope.row)"
|
||||
>设置权限</el-button>
|
||||
<el-button
|
||||
icon="plus"
|
||||
|
||||
type="primary"
|
||||
link
|
||||
@click="addAuthority(scope.row.authorityId)"
|
||||
>新增子角色</el-button>
|
||||
<el-button
|
||||
icon="copy-document"
|
||||
|
||||
type="primary"
|
||||
link
|
||||
@click="copyAuthorityFunc(scope.row)"
|
||||
>拷贝</el-button>
|
||||
<el-button
|
||||
icon="edit"
|
||||
|
||||
type="primary"
|
||||
link
|
||||
@click="editAuthority(scope.row)"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
icon="delete"
|
||||
|
||||
type="primary"
|
||||
link
|
||||
@click="deleteAuth(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<!-- 新增角色弹窗 -->
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:title="dialogTitle"
|
||||
>
|
||||
<el-form
|
||||
ref="authorityForm"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item
|
||||
label="父级角色"
|
||||
prop="parentId"
|
||||
>
|
||||
<el-cascader
|
||||
v-model="form.parentId"
|
||||
style="width:100%"
|
||||
:disabled="dialogType==='add'"
|
||||
:options="AuthorityOption"
|
||||
:props="{ checkStrictly: true,label:'authorityName',value:'authorityId',disabled:'disabled',emitPath:false}"
|
||||
:show-all-levels="false"
|
||||
filterable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="角色ID"
|
||||
prop="authorityId"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.authorityId"
|
||||
:disabled="dialogType==='edit'"
|
||||
autocomplete="off"
|
||||
maxlength="15"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="角色姓名"
|
||||
prop="authorityName"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.authorityName"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="enterDialog"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-drawer
|
||||
v-if="drawer"
|
||||
v-model="drawer"
|
||||
:with-header="false"
|
||||
size="40%"
|
||||
title="角色配置"
|
||||
>
|
||||
<el-tabs
|
||||
:before-leave="autoEnter"
|
||||
type="border-card"
|
||||
>
|
||||
<el-tab-pane label="角色菜单">
|
||||
<Menus
|
||||
ref="menus"
|
||||
:row="activeRow"
|
||||
@changeRow="changeRow"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="角色api">
|
||||
<Apis
|
||||
ref="apis"
|
||||
:row="activeRow"
|
||||
@changeRow="changeRow"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="资源权限">
|
||||
<Datas
|
||||
ref="datas"
|
||||
:authority="tableData"
|
||||
:row="activeRow"
|
||||
@changeRow="changeRow"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
getAuthorityList,
|
||||
deleteAuthority,
|
||||
createAuthority,
|
||||
updateAuthority,
|
||||
copyAuthority
|
||||
} from '@/api/authority'
|
||||
|
||||
import Menus from '@/view/superAdmin/authority/components/menus.vue'
|
||||
import Apis from '@/view/superAdmin/authority/components/apis.vue'
|
||||
import Datas from '@/view/superAdmin/authority/components/datas.vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { toDoc } from '@/utils/doc'
|
||||
import { VideoCameraFilled } from '@element-plus/icons-vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Authority'
|
||||
})
|
||||
|
||||
const mustUint = (rule, value, callback) => {
|
||||
if (!/^[0-9]*[1-9][0-9]*$/.test(value)) {
|
||||
return callback(new Error('请输入正整数'))
|
||||
}
|
||||
return callback()
|
||||
}
|
||||
|
||||
const AuthorityOption = ref([
|
||||
{
|
||||
authorityId: 0,
|
||||
authorityName: '根角色'
|
||||
}
|
||||
])
|
||||
const drawer = ref(false)
|
||||
const dialogType = ref('add')
|
||||
const activeRow = ref({})
|
||||
|
||||
const dialogTitle = ref('新增角色')
|
||||
const dialogFormVisible = ref(false)
|
||||
const apiDialogFlag = ref(false)
|
||||
const copyForm = ref({})
|
||||
|
||||
const form = ref({
|
||||
authorityId: 0,
|
||||
authorityName: '',
|
||||
parentId: 0
|
||||
})
|
||||
const rules = ref({
|
||||
authorityId: [
|
||||
{ required: true, message: '请输入角色ID', trigger: 'blur' },
|
||||
{ validator: mustUint, trigger: 'blur', message: '必须为正整数' }
|
||||
],
|
||||
authorityName: [
|
||||
{ required: true, message: '请输入角色名', trigger: 'blur' }
|
||||
],
|
||||
parentId: [
|
||||
{ required: true, message: '请选择父角色', trigger: 'blur' },
|
||||
]
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(999)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getAuthorityList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
const changeRow = (key, value) => {
|
||||
activeRow.value[key] = value
|
||||
}
|
||||
const menus = ref(null)
|
||||
const apis = ref(null)
|
||||
const datas = ref(null)
|
||||
const autoEnter = (activeName, oldActiveName) => {
|
||||
const paneArr = [menus, apis, datas]
|
||||
if (oldActiveName) {
|
||||
if (paneArr[oldActiveName].value.needConfirm) {
|
||||
paneArr[oldActiveName].value.enterAndNext()
|
||||
paneArr[oldActiveName].value.needConfirm = false
|
||||
}
|
||||
}
|
||||
}
|
||||
// 拷贝角色
|
||||
const copyAuthorityFunc = (row) => {
|
||||
setOptions()
|
||||
dialogTitle.value = '拷贝角色'
|
||||
dialogType.value = 'copy'
|
||||
for (const k in form.value) {
|
||||
form.value[k] = row[k]
|
||||
}
|
||||
copyForm.value = row
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
const openDrawer = (row) => {
|
||||
drawer.value = true
|
||||
activeRow.value = row
|
||||
}
|
||||
// 删除角色
|
||||
const deleteAuth = (row) => {
|
||||
ElMessageBox.confirm('此操作将永久删除该角色, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async() => {
|
||||
const res = await deleteAuthority({ authorityId: row.authorityId })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功!'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '已取消删除'
|
||||
})
|
||||
})
|
||||
}
|
||||
// 初始化表单
|
||||
const authorityForm = ref(null)
|
||||
const initForm = () => {
|
||||
if (authorityForm.value) {
|
||||
authorityForm.value.resetFields()
|
||||
}
|
||||
form.value = {
|
||||
authorityId: 0,
|
||||
authorityName: '',
|
||||
parentId: 0
|
||||
}
|
||||
}
|
||||
// 关闭窗口
|
||||
const closeDialog = () => {
|
||||
initForm()
|
||||
dialogFormVisible.value = false
|
||||
apiDialogFlag.value = false
|
||||
}
|
||||
// 确定弹窗
|
||||
|
||||
const enterDialog = () => {
|
||||
authorityForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
form.value.authorityId = Number(form.value.authorityId)
|
||||
switch (dialogType.value) {
|
||||
case 'add':
|
||||
{
|
||||
const res = await createAuthority(form.value)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '添加成功!'
|
||||
})
|
||||
getTableData()
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'edit':
|
||||
{
|
||||
const res = await updateAuthority(form.value)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '添加成功!'
|
||||
})
|
||||
getTableData()
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'copy': {
|
||||
const data = {
|
||||
authority: {
|
||||
authorityId: 0,
|
||||
authorityName: '',
|
||||
datauthorityId: [],
|
||||
parentId: 0
|
||||
},
|
||||
oldAuthorityId: 0
|
||||
}
|
||||
data.authority.authorityId = form.value.authorityId
|
||||
data.authority.authorityName = form.value.authorityName
|
||||
data.authority.parentId = form.value.parentId
|
||||
data.authority.dataAuthorityId = copyForm.value.dataAuthorityId
|
||||
data.oldAuthorityId = copyForm.value.authorityId
|
||||
const res = await copyAuthority(data)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '复制成功!'
|
||||
})
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initForm()
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
const setOptions = () => {
|
||||
AuthorityOption.value = [
|
||||
{
|
||||
authorityId: 0,
|
||||
authorityName: '根角色'
|
||||
}
|
||||
]
|
||||
setAuthorityOptions(tableData.value, AuthorityOption.value, false)
|
||||
}
|
||||
const setAuthorityOptions = (AuthorityData, optionsData, disabled) => {
|
||||
form.value.authorityId = String(form.value.authorityId)
|
||||
AuthorityData &&
|
||||
AuthorityData.forEach(item => {
|
||||
if (item.children && item.children.length) {
|
||||
const option = {
|
||||
authorityId: item.authorityId,
|
||||
authorityName: item.authorityName,
|
||||
disabled: disabled || item.authorityId === form.value.authorityId,
|
||||
children: []
|
||||
}
|
||||
setAuthorityOptions(
|
||||
item.children,
|
||||
option.children,
|
||||
disabled || item.authorityId === form.value.authorityId
|
||||
)
|
||||
optionsData.push(option)
|
||||
} else {
|
||||
const option = {
|
||||
authorityId: item.authorityId,
|
||||
authorityName: item.authorityName,
|
||||
disabled: disabled || item.authorityId === form.value.authorityId
|
||||
}
|
||||
optionsData.push(option)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 增加角色
|
||||
const addAuthority = (parentId) => {
|
||||
initForm()
|
||||
dialogTitle.value = '新增角色'
|
||||
dialogType.value = 'add'
|
||||
form.value.parentId = parentId
|
||||
setOptions()
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
// 编辑角色
|
||||
const editAuthority = (row) => {
|
||||
setOptions()
|
||||
dialogTitle.value = '编辑角色'
|
||||
dialogType.value = 'edit'
|
||||
for (const key in form.value) {
|
||||
form.value[key] = row[key]
|
||||
}
|
||||
setOptions()
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.authority {
|
||||
.el-input-number {
|
||||
margin-left: 15px;
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tree-content{
|
||||
margin-top: 10px;
|
||||
height: calc(100vh - 158px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="sticky top-0.5 z-10 bg-white">
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
class="w-3/5"
|
||||
placeholder="筛选"
|
||||
/>
|
||||
<el-button
|
||||
class="float-right"
|
||||
type="primary"
|
||||
@click="authApiEnter"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
<div class="tree-content">
|
||||
<el-scrollbar>
|
||||
<el-tree
|
||||
ref="apiTree"
|
||||
:data="apiTreeData"
|
||||
:default-checked-keys="apiTreeIds"
|
||||
:props="apiDefaultProps"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
node-key="onlyId"
|
||||
show-checkbox
|
||||
:filter-node-method="filterNode"
|
||||
@check="nodeChange"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getAllApis } from '@/api/api'
|
||||
import { UpdateCasbin, getPolicyPathByAuthorityId } from '@/api/casbin'
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
defineOptions({
|
||||
name: 'Apis',
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
row: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const apiDefaultProps = ref({
|
||||
children: 'children',
|
||||
label: 'description'
|
||||
})
|
||||
const filterText = ref('')
|
||||
const apiTreeData = ref([])
|
||||
const apiTreeIds = ref([])
|
||||
const activeUserId = ref('')
|
||||
const init = async() => {
|
||||
const res2 = await getAllApis()
|
||||
const apis = res2.data.apis
|
||||
|
||||
apiTreeData.value = buildApiTree(apis)
|
||||
const res = await getPolicyPathByAuthorityId({
|
||||
authorityId: props.row.authorityId
|
||||
})
|
||||
activeUserId.value = props.row.authorityId
|
||||
apiTreeIds.value = []
|
||||
res.data.paths && res.data.paths.forEach(item => {
|
||||
apiTreeIds.value.push('p:' + item.path + 'm:' + item.method)
|
||||
})
|
||||
}
|
||||
|
||||
init()
|
||||
|
||||
const needConfirm = ref(false)
|
||||
const nodeChange = () => {
|
||||
needConfirm.value = true
|
||||
}
|
||||
// 暴露给外层使用的切换拦截统一方法
|
||||
const enterAndNext = () => {
|
||||
authApiEnter()
|
||||
}
|
||||
|
||||
// 创建api树方法
|
||||
const buildApiTree = (apis) => {
|
||||
const apiObj = {}
|
||||
apis &&
|
||||
apis.forEach(item => {
|
||||
item.onlyId = 'p:' + item.path + 'm:' + item.method
|
||||
if (Object.prototype.hasOwnProperty.call(apiObj, item.apiGroup)) {
|
||||
apiObj[item.apiGroup].push(item)
|
||||
} else {
|
||||
Object.assign(apiObj, { [item.apiGroup]: [item] })
|
||||
}
|
||||
})
|
||||
const apiTree = []
|
||||
for (const key in apiObj) {
|
||||
const treeNode = {
|
||||
ID: key,
|
||||
description: key + '组',
|
||||
children: apiObj[key]
|
||||
}
|
||||
apiTree.push(treeNode)
|
||||
}
|
||||
return apiTree
|
||||
}
|
||||
|
||||
// 关联关系确定
|
||||
const apiTree = ref(null)
|
||||
const authApiEnter = async() => {
|
||||
const checkArr = apiTree.value.getCheckedNodes(true)
|
||||
var casbinInfos = []
|
||||
checkArr && checkArr.forEach(item => {
|
||||
var casbinInfo = {
|
||||
path: item.path,
|
||||
method: item.method
|
||||
}
|
||||
casbinInfos.push(casbinInfo)
|
||||
})
|
||||
const res = await UpdateCasbin({
|
||||
authorityId: activeUserId.value,
|
||||
casbinInfos
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: 'api设置成功' })
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
needConfirm,
|
||||
enterAndNext
|
||||
})
|
||||
|
||||
const filterNode = (value, data) => {
|
||||
if (!value) return true
|
||||
return data.description.indexOf(value) !== -1
|
||||
}
|
||||
watch(filterText, (val) => {
|
||||
apiTree.value.filter(val)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
<template>
|
||||
<div>
|
||||
<warning-bar
|
||||
title="此功能仅用于创建角色和角色的many2many关系表,具体使用还须自己结合表实现业务,详情参考示例代码(客户示例)。此功能不建议使用,建议使用插件市场【组织管理功能(点击前往)】来管理资源权限。"
|
||||
/>
|
||||
<div class="sticky top-0.5 z-10 bg-white my-4">
|
||||
<el-button
|
||||
class="float-left"
|
||||
type="primary"
|
||||
@click="all"
|
||||
>全选</el-button>
|
||||
<el-button
|
||||
class="float-left"
|
||||
type="primary"
|
||||
@click="self"
|
||||
>本角色</el-button>
|
||||
<el-button
|
||||
class="float-left"
|
||||
type="primary"
|
||||
@click="selfAndChildren"
|
||||
>本角色及子角色</el-button>
|
||||
<el-button
|
||||
class="float-right"
|
||||
type="primary"
|
||||
@click="authDataEnter"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
<div class="clear-both pt-4">
|
||||
<el-checkbox-group
|
||||
v-model="dataAuthorityId"
|
||||
@change="selectAuthority"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="(item,key) in authoritys"
|
||||
:key="key"
|
||||
:label="item"
|
||||
>{{ item.authorityName }}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { setDataAuthority } from '@/api/authority'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
defineOptions({
|
||||
name: 'Datas'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
row: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
},
|
||||
authority: {
|
||||
default: function() {
|
||||
return []
|
||||
},
|
||||
type: Array
|
||||
}
|
||||
})
|
||||
|
||||
const authoritys = ref([])
|
||||
const needConfirm = ref(false)
|
||||
// 平铺角色
|
||||
const roundAuthority = (authoritysData) => {
|
||||
authoritysData && authoritysData.forEach(item => {
|
||||
const obj = {}
|
||||
obj.authorityId = item.authorityId
|
||||
obj.authorityName = item.authorityName
|
||||
authoritys.value.push(obj)
|
||||
if (item.children && item.children.length) {
|
||||
roundAuthority(item.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const dataAuthorityId = ref([])
|
||||
const init = () => {
|
||||
roundAuthority(props.authority)
|
||||
props.row.dataAuthorityId && props.row.dataAuthorityId.forEach(item => {
|
||||
const obj = authoritys.value && authoritys.value.filter(au => au.authorityId === item.authorityId) && authoritys.value.filter(au => au.authorityId === item.authorityId)[0]
|
||||
dataAuthorityId.value.push(obj)
|
||||
})
|
||||
}
|
||||
|
||||
init()
|
||||
|
||||
// 暴露给外层使用的切换拦截统一方法
|
||||
const enterAndNext = () => {
|
||||
authDataEnter()
|
||||
}
|
||||
|
||||
const emit = defineEmits(['changeRow'])
|
||||
const all = () => {
|
||||
dataAuthorityId.value = [...authoritys.value]
|
||||
emit('changeRow', 'dataAuthorityId', dataAuthorityId.value)
|
||||
needConfirm.value = true
|
||||
}
|
||||
const self = () => {
|
||||
dataAuthorityId.value = authoritys.value.filter(item => item.authorityId === props.row.authorityId)
|
||||
emit('changeRow', 'dataAuthorityId', dataAuthorityId.value)
|
||||
needConfirm.value = true
|
||||
}
|
||||
const selfAndChildren = () => {
|
||||
const arrBox = []
|
||||
getChildrenId(props.row, arrBox)
|
||||
dataAuthorityId.value = authoritys.value.filter(item => arrBox.indexOf(item.authorityId) > -1)
|
||||
emit('changeRow', 'dataAuthorityId', dataAuthorityId.value)
|
||||
needConfirm.value = true
|
||||
}
|
||||
const getChildrenId = (row, arrBox) => {
|
||||
arrBox.push(row.authorityId)
|
||||
row.children && row.children.forEach(item => {
|
||||
getChildrenId(item, arrBox)
|
||||
})
|
||||
}
|
||||
// 提交
|
||||
const authDataEnter = async() => {
|
||||
const res = await setDataAuthority(props.row)
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '资源设置成功' })
|
||||
}
|
||||
}
|
||||
|
||||
// 选择
|
||||
const selectAuthority = () => {
|
||||
emit('changeRow', 'dataAuthorityId', dataAuthorityId.value)
|
||||
needConfirm.value = true
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
enterAndNext,
|
||||
needConfirm
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="sticky top-0.5 z-10 bg-white">
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
class="w-3/5"
|
||||
placeholder="筛选"
|
||||
/>
|
||||
<el-button
|
||||
class="float-right"
|
||||
type="primary"
|
||||
@click="relation"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
<div class="tree-content clear-both">
|
||||
<el-scrollbar>
|
||||
<el-tree
|
||||
ref="menuTree"
|
||||
:data="menuTreeData"
|
||||
:default-checked-keys="menuTreeIds"
|
||||
:props="menuDefaultProps"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
node-key="ID"
|
||||
show-checkbox
|
||||
:filter-node-method="filterNode"
|
||||
@check="nodeChange"
|
||||
>
|
||||
<template #default="{ node , data }">
|
||||
<span class="custom-tree-node">
|
||||
<span>{{ node.label }}</span>
|
||||
<span>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
|
||||
:style="{color:row.defaultRouter === data.name?'#E6A23C':'#85ce61'}"
|
||||
:disabled="!node.checked"
|
||||
@click="() => setDefault(data)"
|
||||
>
|
||||
{{ row.defaultRouter === data.name?"首页":"设为首页" }}
|
||||
</el-button>
|
||||
</span>
|
||||
<span v-if="data.menuBtn.length">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
|
||||
@click="() => OpenBtn(data)"
|
||||
>
|
||||
分配按钮
|
||||
</el-button>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="btnVisible"
|
||||
title="分配按钮"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-table
|
||||
ref="btnTableRef"
|
||||
:data="btnData"
|
||||
row-key="ID"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
width="55"
|
||||
/>
|
||||
<el-table-column
|
||||
label="按钮名称"
|
||||
prop="name"
|
||||
/>
|
||||
<el-table-column
|
||||
label="按钮备注"
|
||||
prop="desc"
|
||||
/>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="enterDialog"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getBaseMenuTree, getMenuAuthority, addMenuAuthority } from '@/api/menu'
|
||||
import {
|
||||
updateAuthority
|
||||
} from '@/api/authority'
|
||||
import { getAuthorityBtnApi, setAuthorityBtnApi } from '@/api/authorityBtn'
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
defineOptions({
|
||||
name: 'Menus'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
row: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['changeRow'])
|
||||
const filterText = ref('')
|
||||
const menuTreeData = ref([])
|
||||
const menuTreeIds = ref([])
|
||||
const needConfirm = ref(false)
|
||||
const menuDefaultProps = ref({
|
||||
children: 'children',
|
||||
label: function(data) {
|
||||
return data.meta.title
|
||||
}
|
||||
})
|
||||
|
||||
const init = async() => {
|
||||
// 获取所有菜单树
|
||||
const res = await getBaseMenuTree()
|
||||
menuTreeData.value = res.data.menus
|
||||
const res1 = await getMenuAuthority({ authorityId: props.row.authorityId })
|
||||
const menus = res1.data.menus
|
||||
const arr = []
|
||||
menus.forEach(item => {
|
||||
// 防止直接选中父级造成全选
|
||||
if (!menus.some(same => same.parentId === item.menuId)) {
|
||||
arr.push(Number(item.menuId))
|
||||
}
|
||||
})
|
||||
menuTreeIds.value = arr
|
||||
}
|
||||
|
||||
init()
|
||||
|
||||
const setDefault = async(data) => {
|
||||
const res = await updateAuthority({ authorityId: props.row.authorityId, AuthorityName: props.row.authorityName, parentId: props.row.parentId, defaultRouter: data.name })
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '设置成功' })
|
||||
emit('changeRow', 'defaultRouter', res.data.authority.defaultRouter)
|
||||
}
|
||||
}
|
||||
const nodeChange = () => {
|
||||
needConfirm.value = true
|
||||
}
|
||||
// 暴露给外层使用的切换拦截统一方法
|
||||
const enterAndNext = () => {
|
||||
relation()
|
||||
}
|
||||
// 关联树 确认方法
|
||||
const menuTree = ref(null)
|
||||
const relation = async() => {
|
||||
const checkArr = menuTree.value.getCheckedNodes(false, true)
|
||||
const res = await addMenuAuthority({
|
||||
menus: checkArr,
|
||||
authorityId: props.row.authorityId
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '菜单设置成功!'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ enterAndNext, needConfirm })
|
||||
|
||||
const btnVisible = ref(false)
|
||||
|
||||
const btnData = ref([])
|
||||
const multipleSelection = ref([])
|
||||
const btnTableRef = ref()
|
||||
let menuID = ''
|
||||
const OpenBtn = async(data) => {
|
||||
menuID = data.ID
|
||||
const res = await getAuthorityBtnApi({ menuID: menuID, authorityId: props.row.authorityId })
|
||||
if (res.code === 0) {
|
||||
openDialog(data)
|
||||
await nextTick()
|
||||
if (res.data.selected) {
|
||||
res.data.selected.forEach(id => {
|
||||
btnData.value.some(item => {
|
||||
if (item.ID === id) {
|
||||
btnTableRef.value.toggleRowSelection(item, true)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
const openDialog = (data) => {
|
||||
btnVisible.value = true
|
||||
btnData.value = data.menuBtn
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
btnVisible.value = false
|
||||
}
|
||||
const enterDialog = async() => {
|
||||
const selected = multipleSelection.value.map(item => item.ID)
|
||||
const res = await setAuthorityBtnApi({
|
||||
menuID,
|
||||
selected,
|
||||
authorityId: props.row.authorityId
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '设置成功' })
|
||||
btnVisible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const filterNode = (value, data) => {
|
||||
if (!value) return true
|
||||
// console.log(data.mate.title)
|
||||
return data.meta.title.indexOf(value) !== -1
|
||||
}
|
||||
|
||||
watch(filterText, (val) => {
|
||||
menuTree.value.filter(val)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-tree-node{
|
||||
span+span{
|
||||
@apply ml-3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
<template>
|
||||
<div>
|
||||
<warning-bar
|
||||
title="获取字典且缓存方法已在前端utils/dictionary 已经封装完成 不必自己书写 使用方法查看文件内注释"
|
||||
/>
|
||||
<div class="dict-box flex gap-4">
|
||||
<div class="w-64 bg-white p-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text font-bold">字典列表</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="openDialog"
|
||||
>
|
||||
新增
|
||||
</el-button>
|
||||
</div>
|
||||
<el-scrollbar
|
||||
class="mt-4"
|
||||
style="height: calc(100vh - 300px)"
|
||||
>
|
||||
<div
|
||||
v-for="dictionary in dictionaryData"
|
||||
:key="dictionary.ID"
|
||||
class="rounded flex justify-between items-center px-2 py-4 cursor-pointer mt-2 hover:bg-blue-50 hover:text-gray-800 group bg-gray-50"
|
||||
:class="selectID === dictionary.ID && 'active'"
|
||||
@click="toDetail(dictionary)"
|
||||
>
|
||||
<span class="max-w-[160px] truncate">{{ dictionary.name }}</span>
|
||||
<div>
|
||||
<el-icon
|
||||
class="group-hover:text-blue-500"
|
||||
:class="selectID === dictionary.ID ? 'text-white-800':'text-blue-500'"
|
||||
@click.stop="updateSysDictionaryFunc(dictionary)"
|
||||
>
|
||||
<Edit />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
class="ml-2 group-hover:text-red-500"
|
||||
:class="selectID === dictionary.ID ? 'text-white-800':'text-red-500'"
|
||||
@click="deleteSysDictionaryFunc(dictionary)"
|
||||
>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<div class="flex-1 bg-white">
|
||||
<sysDictionaryDetail :sys-dictionary-i-d="selectID" />
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:before-close="closeDialog"
|
||||
:title="type==='create'?'添加字典':'修改字典'"
|
||||
>
|
||||
<el-form
|
||||
ref="dialogForm"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item
|
||||
label="字典名(中)"
|
||||
prop="name"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.name"
|
||||
placeholder="请输入字典名(中)"
|
||||
clearable
|
||||
:style="{ width: '100%' }"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="字典名(英)"
|
||||
prop="type"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.type"
|
||||
placeholder="请输入字典名(英)"
|
||||
clearable
|
||||
:style="{ width: '100%' }"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="状态"
|
||||
prop="status"
|
||||
required
|
||||
>
|
||||
<el-switch
|
||||
v-model="formData.status"
|
||||
active-text="开启"
|
||||
inactive-text="停用"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="描述"
|
||||
prop="desc"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.desc"
|
||||
placeholder="请输入描述"
|
||||
clearable
|
||||
:style="{ width: '100%' }"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
<el-button
|
||||
|
||||
type="primary"
|
||||
@click="enterDialog"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createSysDictionary,
|
||||
deleteSysDictionary,
|
||||
updateSysDictionary,
|
||||
findSysDictionary,
|
||||
getSysDictionaryList,
|
||||
} from '@/api/sysDictionary' // 此处请自行替换地址
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
import sysDictionaryDetail from './sysDictionaryDetail.vue'
|
||||
import { Edit } from '@element-plus/icons-vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'SysDictionary',
|
||||
})
|
||||
|
||||
const selectID = ref(1)
|
||||
|
||||
const formData = ref({
|
||||
name: null,
|
||||
type: null,
|
||||
status: true,
|
||||
desc: null,
|
||||
})
|
||||
const rules = ref({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典名(中)',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典名(英)',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
desc: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入描述',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const dictionaryData = ref([])
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const res = await getSysDictionaryList()
|
||||
if (res.code === 0) {
|
||||
dictionaryData.value = res.data
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
const toDetail = (row) => {
|
||||
selectID.value = row.ID
|
||||
}
|
||||
|
||||
const dialogFormVisible = ref(false)
|
||||
const type = ref('')
|
||||
const updateSysDictionaryFunc = async(row) => {
|
||||
const res = await findSysDictionary({ ID: row.ID, status: row.status })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.resysDictionary
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
name: null,
|
||||
type: null,
|
||||
status: true,
|
||||
desc: null,
|
||||
}
|
||||
}
|
||||
const deleteSysDictionaryFunc = async(row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const res = await deleteSysDictionary({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功',
|
||||
})
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const dialogForm = ref(null)
|
||||
const enterDialog = async() => {
|
||||
dialogForm.value.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createSysDictionary(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateSysDictionary(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createSysDictionary(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('操作成功')
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.dict-box{
|
||||
height: calc(100vh - 240px);
|
||||
}
|
||||
.active {
|
||||
background-color: var(--el-color-primary) !important;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,344 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list justify-between">
|
||||
<span class="text font-bold">字典详细内容</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="openDialog"
|
||||
>新增字典项</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
tooltip-effect="dark"
|
||||
row-key="ID"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
width="55"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="日期"
|
||||
width="180"
|
||||
>
|
||||
<template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="展示值"
|
||||
prop="label"
|
||||
/>
|
||||
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="字典值"
|
||||
prop="value"
|
||||
/>
|
||||
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="扩展值"
|
||||
prop="extend"
|
||||
/>
|
||||
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="启用状态"
|
||||
prop="status"
|
||||
width="120"
|
||||
>
|
||||
<template #default="scope">{{ formatBoolean(scope.row.status) }}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="排序标记"
|
||||
prop="sort"
|
||||
width="120"
|
||||
/>
|
||||
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="操作"
|
||||
width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="edit"
|
||||
@click="updateSysDictionaryDetailFunc(scope.row)"
|
||||
>变更</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="delete"
|
||||
@click="deleteSysDictionaryDetailFunc(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:before-close="closeDialog"
|
||||
:title="type==='create'?'添加字典项':'修改字典项'"
|
||||
>
|
||||
<el-form
|
||||
ref="dialogForm"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item
|
||||
label="展示值"
|
||||
prop="label"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.label"
|
||||
placeholder="请输入展示值"
|
||||
clearable
|
||||
:style="{width: '100%'}"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="字典值"
|
||||
prop="value"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.value"
|
||||
placeholder="请输入字典值"
|
||||
clearable
|
||||
:style="{width: '100%'}"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="扩展值"
|
||||
prop="extend"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.extend"
|
||||
placeholder="请输入扩展值"
|
||||
clearable
|
||||
:style="{width: '100%'}"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="启用状态"
|
||||
prop="status"
|
||||
required
|
||||
>
|
||||
<el-switch
|
||||
v-model="formData.status"
|
||||
active-text="开启"
|
||||
inactive-text="停用"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="排序标记"
|
||||
prop="sort"
|
||||
>
|
||||
<el-input-number
|
||||
v-model.number="formData.sort"
|
||||
placeholder="排序标记"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="enterDialog"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createSysDictionaryDetail,
|
||||
deleteSysDictionaryDetail,
|
||||
updateSysDictionaryDetail,
|
||||
findSysDictionaryDetail,
|
||||
getSysDictionaryDetailList
|
||||
} from '@/api/sysDictionaryDetail' // 此处请自行替换地址
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { formatBoolean, formatDate } from '@/utils/format'
|
||||
|
||||
defineOptions({
|
||||
name: 'SysDictionaryDetail'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
sysDictionaryID: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const formData = ref({
|
||||
label: null,
|
||||
value: null,
|
||||
status: true,
|
||||
sort: null
|
||||
})
|
||||
const rules = ref({
|
||||
label: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入展示值',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
value: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典值',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sort: [
|
||||
{
|
||||
required: true,
|
||||
message: '排序标记',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getSysDictionaryDetailList({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
sysDictionaryID: props.sysDictionaryID
|
||||
})
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
const type = ref('')
|
||||
const dialogFormVisible = ref(false)
|
||||
const updateSysDictionaryDetailFunc = async(row) => {
|
||||
const res = await findSysDictionaryDetail({ ID: row.ID })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.reSysDictionaryDetail
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
label: null,
|
||||
value: null,
|
||||
status: true,
|
||||
sort: null,
|
||||
sysDictionaryID: props.sysDictionaryID
|
||||
}
|
||||
}
|
||||
const deleteSysDictionaryDetailFunc = async(row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const res = await deleteSysDictionaryDetail({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const dialogForm = ref(null)
|
||||
const enterDialog = async() => {
|
||||
dialogForm.value.validate(async valid => {
|
||||
formData.value.sysDictionaryID = props.sysDictionaryID
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createSysDictionaryDetail(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateSysDictionaryDetail(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createSysDictionaryDetail(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
watch(() => props.sysDictionaryID, () => {
|
||||
getTableData()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<keep-alive :include="routerStore.keepAliveRouters">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
const routerStore = useRouterStore()
|
||||
|
||||
defineOptions({
|
||||
name: 'SuperAdmin'
|
||||
})
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,763 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="addMenu('0')"
|
||||
>新增根菜单</el-button>
|
||||
<el-icon
|
||||
class="cursor-pointer"
|
||||
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT/?p=4&vd_source=f2640257c21e3b547a790461ed94875e')"
|
||||
><VideoCameraFilled /></el-icon>
|
||||
</div>
|
||||
|
||||
<!-- 由于此处菜单跟左侧列表一一对应所以不需要分页 pageSize默认999 -->
|
||||
<el-table
|
||||
:data="tableData"
|
||||
row-key="ID"
|
||||
>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="ID"
|
||||
min-width="100"
|
||||
prop="ID"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="展示名称"
|
||||
min-width="120"
|
||||
prop="authorityName"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.meta.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="图标"
|
||||
min-width="140"
|
||||
prop="authorityName"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div
|
||||
v-if="scope.row.meta.icon"
|
||||
class="icon-column"
|
||||
>
|
||||
<el-icon>
|
||||
<component :is="scope.row.meta.icon" />
|
||||
</el-icon>
|
||||
<span>{{ scope.row.meta.icon }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="路由Name"
|
||||
show-overflow-tooltip
|
||||
min-width="160"
|
||||
prop="name"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="路由Path"
|
||||
show-overflow-tooltip
|
||||
min-width="160"
|
||||
prop="path"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="是否隐藏"
|
||||
min-width="100"
|
||||
prop="hidden"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.hidden?"隐藏":"显示" }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="父节点"
|
||||
min-width="90"
|
||||
prop="parentId"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="排序"
|
||||
min-width="70"
|
||||
prop="sort"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="文件路径"
|
||||
min-width="360"
|
||||
prop="component"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
fixed="right"
|
||||
label="操作"
|
||||
width="300"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="plus"
|
||||
@click="addMenu(scope.row.ID)"
|
||||
>添加子菜单</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="edit"
|
||||
@click="editMenu(scope.row.ID)"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
|
||||
type="primary"
|
||||
link
|
||||
icon="delete"
|
||||
@click="deleteMenu(scope.row.ID)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:before-close="handleClose"
|
||||
:title="dialogTitle"
|
||||
>
|
||||
<warning-bar title="新增菜单,需要在角色管理内配置权限才可使用" />
|
||||
<el-form
|
||||
v-if="dialogFormVisible"
|
||||
ref="menuForm"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
label-width="85px"
|
||||
>
|
||||
<el-form-item
|
||||
label="路由Name"
|
||||
prop="path"
|
||||
style="width:30%"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
autocomplete="off"
|
||||
placeholder="唯一英文字符串"
|
||||
@change="changeName"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
prop="path"
|
||||
style="width:30%"
|
||||
>
|
||||
<template #label>
|
||||
<span style="display: inline-flex;align-items: center;">
|
||||
<span>路由Path</span>
|
||||
<el-checkbox
|
||||
v-model="checkFlag"
|
||||
style="margin-left:12px;height: auto"
|
||||
>添加参数</el-checkbox>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<el-input
|
||||
v-model="form.path"
|
||||
:disabled="!checkFlag"
|
||||
autocomplete="off"
|
||||
placeholder="建议只在后方拼接参数"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="是否隐藏"
|
||||
style="width:30%"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.hidden"
|
||||
placeholder="是否在列表隐藏"
|
||||
>
|
||||
<el-option
|
||||
:value="false"
|
||||
label="否"
|
||||
/>
|
||||
<el-option
|
||||
:value="true"
|
||||
label="是"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="父节点ID"
|
||||
style="width:30%"
|
||||
>
|
||||
<el-cascader
|
||||
v-model="form.parentId"
|
||||
style="width:100%"
|
||||
:disabled="!isEdit"
|
||||
:options="menuOption"
|
||||
:props="{ checkStrictly: true,label:'title',value:'ID',disabled:'disabled',emitPath:false}"
|
||||
:show-all-levels="false"
|
||||
filterable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="文件路径"
|
||||
prop="component"
|
||||
style="width:60%"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.component"
|
||||
autocomplete="off"
|
||||
placeholder="页面:view/xxx/xx.vue 插件:plugin/xx/xx.vue"
|
||||
@blur="fmtComponent"
|
||||
/>
|
||||
<span style="font-size:12px;margin-right:12px;">如果菜单包含子菜单,请创建router-view二级路由页面或者</span><el-button
|
||||
style="margin-top:4px"
|
||||
@click="form.component = 'view/routerHolder.vue'"
|
||||
>点我设置</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="展示名称"
|
||||
prop="meta.title"
|
||||
style="width:30%"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.meta.title"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="图标"
|
||||
prop="meta.icon"
|
||||
style="width:30%"
|
||||
>
|
||||
<icon
|
||||
:meta="form.meta"
|
||||
style="width:100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="排序标记"
|
||||
prop="sort"
|
||||
style="width:30%"
|
||||
>
|
||||
<el-input
|
||||
v-model.number="form.sort"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
prop="meta.activeName"
|
||||
style="width:30%"
|
||||
>
|
||||
<template #label>
|
||||
<div>
|
||||
<span> 高亮菜单 </span>
|
||||
<el-tooltip
|
||||
content="注:当到达此路由时候,指定左侧菜单指定name会处于活跃状态(亮起),可为空,为空则为本路由Name。"
|
||||
placement="top"
|
||||
effect="light"
|
||||
>
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-input
|
||||
v-model="form.meta.activeName"
|
||||
:placeholder="form.name"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="KeepAlive"
|
||||
prop="meta.keepAlive"
|
||||
style="width:30%"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.meta.keepAlive"
|
||||
style="width:100%"
|
||||
placeholder="是否keepAlive缓存页面"
|
||||
>
|
||||
<el-option
|
||||
:value="false"
|
||||
label="否"
|
||||
/>
|
||||
<el-option
|
||||
:value="true"
|
||||
label="是"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="CloseTab"
|
||||
prop="meta.closeTab"
|
||||
style="width:30%"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.meta.closeTab"
|
||||
style="width:100%"
|
||||
placeholder="是否自动关闭tab"
|
||||
>
|
||||
<el-option
|
||||
:value="false"
|
||||
label="否"
|
||||
/>
|
||||
<el-option
|
||||
:value="true"
|
||||
label="是"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item style="width:30%">
|
||||
<template #label>
|
||||
<div>
|
||||
<span> 是否为基础页面 </span>
|
||||
<el-tooltip
|
||||
content="此项选择为是,则不会展示左侧菜单以及顶部信息。"
|
||||
placement="top"
|
||||
effect="light"
|
||||
>
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-select
|
||||
v-model="form.meta.defaultMenu"
|
||||
style="width:100%"
|
||||
placeholder="是否为基础页面"
|
||||
>
|
||||
<el-option
|
||||
:value="false"
|
||||
label="否"
|
||||
/>
|
||||
<el-option
|
||||
:value="true"
|
||||
label="是"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="edit"
|
||||
@click="addParameter(form)"
|
||||
>新增菜单参数</el-button>
|
||||
<el-icon
|
||||
class="cursor-pointer"
|
||||
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=9&vd_source=f2640257c21e3b547a790461ed94875e')"
|
||||
><VideoCameraFilled /></el-icon>
|
||||
</div>
|
||||
<el-table
|
||||
:data="form.parameters"
|
||||
style="width: 100%;margin-top: 12px;"
|
||||
>
|
||||
<el-table-column
|
||||
align="left"
|
||||
prop="type"
|
||||
label="参数类型"
|
||||
width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-select
|
||||
v-model="scope.row.type"
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option
|
||||
key="query"
|
||||
value="query"
|
||||
label="query"
|
||||
/>
|
||||
<el-option
|
||||
key="params"
|
||||
value="params"
|
||||
label="params"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
prop="key"
|
||||
label="参数key"
|
||||
width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-input v-model="scope.row.key" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
prop="value"
|
||||
label="参数值"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-input v-model="scope.row.value" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-button
|
||||
type="danger"
|
||||
|
||||
icon="delete"
|
||||
@click="deleteParameter(form.parameters,scope.$index)"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="flex items-center gap-2 mt-3">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="edit"
|
||||
@click="addBtn(form)"
|
||||
>新增可控按钮
|
||||
</el-button>
|
||||
<el-icon
|
||||
class="cursor-pointer"
|
||||
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=11&vd_source=f2640257c21e3b547a790461ed94875e')"
|
||||
><VideoCameraFilled /></el-icon>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="form.menuBtn"
|
||||
style="width: 100%;margin-top: 12px;"
|
||||
>
|
||||
<el-table-column
|
||||
align="left"
|
||||
prop="name"
|
||||
label="按钮名称"
|
||||
width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-input v-model="scope.row.name" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
prop="name"
|
||||
label="备注"
|
||||
width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-input v-model="scope.row.desc" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-button
|
||||
type="danger"
|
||||
|
||||
icon="delete"
|
||||
@click="deleteBtn(form.menuBtn,scope.$index)"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="enterDialog"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
updateBaseMenu,
|
||||
getMenuList,
|
||||
addBaseMenu,
|
||||
deleteBaseMenu,
|
||||
getBaseMenuById
|
||||
} from '@/api/menu'
|
||||
import icon from '@/view/superAdmin/menu/icon.vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { canRemoveAuthorityBtnApi } from '@/api/authorityBtn'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { QuestionFilled, VideoCameraFilled } from '@element-plus/icons-vue'
|
||||
|
||||
import { toDoc } from '@/utils/doc'
|
||||
|
||||
defineOptions({
|
||||
name: 'Menus',
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
path: [{ required: true, message: '请输入菜单name', trigger: 'blur' }],
|
||||
component: [
|
||||
{ required: true, message: '请输入文件路径', trigger: 'blur' }
|
||||
],
|
||||
'meta.title': [
|
||||
{ required: true, message: '请输入菜单展示名称', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(999)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getMenuList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
// 新增参数
|
||||
const addParameter = (form) => {
|
||||
if (!form.parameters) {
|
||||
form.parameters = []
|
||||
}
|
||||
form.parameters.push({
|
||||
type: 'query',
|
||||
key: '',
|
||||
value: ''
|
||||
})
|
||||
}
|
||||
|
||||
const fmtComponent = () => {
|
||||
form.value.component = form.value.component.replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
// 删除参数
|
||||
const deleteParameter = (parameters, index) => {
|
||||
parameters.splice(index, 1)
|
||||
}
|
||||
|
||||
// 新增可控按钮
|
||||
const addBtn = (form) => {
|
||||
if (!form.menuBtn) {
|
||||
form.menuBtn = []
|
||||
}
|
||||
form.menuBtn.push({
|
||||
name: '',
|
||||
desc: '',
|
||||
})
|
||||
}
|
||||
// 删除可控按钮
|
||||
const deleteBtn = async(btns, index) => {
|
||||
const btn = btns[index]
|
||||
if (btn.ID === 0) {
|
||||
btns.splice(index, 1)
|
||||
return
|
||||
}
|
||||
const res = await canRemoveAuthorityBtnApi({ id: btn.ID })
|
||||
if (res.code === 0) {
|
||||
btns.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const form = ref({
|
||||
ID: 0,
|
||||
path: '',
|
||||
name: '',
|
||||
hidden: false,
|
||||
parentId: '',
|
||||
component: '',
|
||||
meta: {
|
||||
activeName: '',
|
||||
title: '',
|
||||
icon: '',
|
||||
defaultMenu: false,
|
||||
closeTab: false,
|
||||
keepAlive: false
|
||||
},
|
||||
parameters: [],
|
||||
menuBtn: []
|
||||
})
|
||||
const changeName = () => {
|
||||
form.value.path = form.value.name
|
||||
}
|
||||
|
||||
const handleClose = (done) => {
|
||||
initForm()
|
||||
done()
|
||||
}
|
||||
// 删除菜单
|
||||
const deleteMenu = (ID) => {
|
||||
ElMessageBox.confirm('此操作将永久删除所有角色下该菜单, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async() => {
|
||||
const res = await deleteBaseMenu({ ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功!'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '已取消删除'
|
||||
})
|
||||
})
|
||||
}
|
||||
// 初始化弹窗内表格方法
|
||||
const menuForm = ref(null)
|
||||
const checkFlag = ref(false)
|
||||
const initForm = () => {
|
||||
checkFlag.value = false
|
||||
menuForm.value.resetFields()
|
||||
form.value = {
|
||||
ID: 0,
|
||||
path: '',
|
||||
name: '',
|
||||
hidden: false,
|
||||
parentId: '',
|
||||
component: '',
|
||||
meta: {
|
||||
title: '',
|
||||
icon: '',
|
||||
defaultMenu: false,
|
||||
closeTab: false,
|
||||
keepAlive: false
|
||||
}
|
||||
}
|
||||
}
|
||||
// 关闭弹窗
|
||||
|
||||
const dialogFormVisible = ref(false)
|
||||
const closeDialog = () => {
|
||||
initForm()
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
// 添加menu
|
||||
const enterDialog = async() => {
|
||||
menuForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
let res
|
||||
if (isEdit.value) {
|
||||
res = await updateBaseMenu(form.value)
|
||||
} else {
|
||||
res = await addBaseMenu(form.value)
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: isEdit.value ? '编辑成功' : '添加成功!'
|
||||
})
|
||||
getTableData()
|
||||
}
|
||||
initForm()
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const menuOption = ref([
|
||||
{
|
||||
ID: '0',
|
||||
title: '根菜单'
|
||||
}
|
||||
])
|
||||
const setOptions = () => {
|
||||
menuOption.value = [
|
||||
{
|
||||
ID: '0',
|
||||
title: '根目录'
|
||||
}
|
||||
]
|
||||
setMenuOptions(tableData.value, menuOption.value, false)
|
||||
}
|
||||
const setMenuOptions = (menuData, optionsData, disabled) => {
|
||||
menuData &&
|
||||
menuData.forEach(item => {
|
||||
if (item.children && item.children.length) {
|
||||
const option = {
|
||||
title: item.meta.title,
|
||||
ID: String(item.ID),
|
||||
disabled: disabled || item.ID === form.value.ID,
|
||||
children: []
|
||||
}
|
||||
setMenuOptions(
|
||||
item.children,
|
||||
option.children,
|
||||
disabled || item.ID === form.value.ID
|
||||
)
|
||||
optionsData.push(option)
|
||||
} else {
|
||||
const option = {
|
||||
title: item.meta.title,
|
||||
ID: String(item.ID),
|
||||
disabled: disabled || item.ID === form.value.ID
|
||||
}
|
||||
optionsData.push(option)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 添加菜单方法,id为 0则为添加根菜单
|
||||
const isEdit = ref(false)
|
||||
const dialogTitle = ref('新增菜单')
|
||||
const addMenu = (id) => {
|
||||
dialogTitle.value = '新增菜单'
|
||||
form.value.parentId = String(id)
|
||||
isEdit.value = false
|
||||
setOptions()
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
// 修改菜单方法
|
||||
const editMenu = async(id) => {
|
||||
dialogTitle.value = '编辑菜单'
|
||||
const res = await getBaseMenuById({ id })
|
||||
form.value = res.data.menu
|
||||
isEdit.value = true
|
||||
setOptions()
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.warning {
|
||||
color: #dc143c;
|
||||
}
|
||||
.icon-column{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.el-icon{
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,323 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form
|
||||
:inline="true"
|
||||
:model="searchInfo"
|
||||
>
|
||||
<el-form-item label="请求方法">
|
||||
<el-input
|
||||
v-model="searchInfo.method"
|
||||
placeholder="搜索条件"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="请求路径">
|
||||
<el-input
|
||||
v-model="searchInfo.path"
|
||||
placeholder="搜索条件"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结果状态码">
|
||||
<el-input
|
||||
v-model="searchInfo.status"
|
||||
placeholder="搜索条件"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="search"
|
||||
@click="onSubmit"
|
||||
>查询</el-button>
|
||||
<el-button
|
||||
icon="refresh"
|
||||
@click="onReset"
|
||||
>重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button
|
||||
icon="delete"
|
||||
:disabled="!multipleSelection.length"
|
||||
@click="onDelete"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
tooltip-effect="dark"
|
||||
row-key="ID"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
align="left"
|
||||
type="selection"
|
||||
width="55"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="操作人"
|
||||
width="140"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.user.userName }}({{ scope.row.user.nickName }})</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="日期"
|
||||
width="180"
|
||||
>
|
||||
<template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="状态码"
|
||||
prop="status"
|
||||
width="120"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-tag type="success">{{ scope.row.status }}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="请求IP"
|
||||
prop="ip"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="请求方法"
|
||||
prop="method"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="请求路径"
|
||||
prop="path"
|
||||
width="240"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="请求"
|
||||
prop="path"
|
||||
width="80"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-popover
|
||||
v-if="scope.row.body"
|
||||
placement="left-start"
|
||||
>
|
||||
<div class="popover-box">
|
||||
<pre>{{ fmtBody(scope.row.body) }}</pre>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="cursor: pointer;"><warning /></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
|
||||
<span v-else>无</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="响应"
|
||||
prop="path"
|
||||
width="80"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-popover
|
||||
v-if="scope.row.resp"
|
||||
placement="left-start"
|
||||
>
|
||||
<div class="popover-box">
|
||||
<pre>{{ fmtBody(scope.row.resp) }}</pre>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="cursor: pointer;"><warning /></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
<span v-else>无</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="操作"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="delete"
|
||||
type="primary"
|
||||
link
|
||||
@click="deleteSysOperationRecordFunc(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
deleteSysOperationRecord,
|
||||
getSysOperationRecordList,
|
||||
deleteSysOperationRecordByIds
|
||||
} from '@/api/sysOperationRecord' // 此处请自行替换地址
|
||||
import { formatDate } from '@/utils/format'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
defineOptions({
|
||||
name: 'SysOperationRecord'
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
}
|
||||
// 条件搜索前端看此方法
|
||||
const onSubmit = () => {
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
if (searchInfo.value.status === '') {
|
||||
searchInfo.value.status = null
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getSysOperationRecordList({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
...searchInfo.value,
|
||||
})
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
const multipleSelection = ref([])
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
const onDelete = async() => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const ids = []
|
||||
multipleSelection.value &&
|
||||
multipleSelection.value.forEach(item => {
|
||||
ids.push(item.ID)
|
||||
})
|
||||
const res = await deleteSysOperationRecordByIds({ ids })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === ids.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
const deleteSysOperationRecordFunc = async(row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const res = await deleteSysOperationRecord({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
const fmtBody = (value) => {
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (err) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.table-expand {
|
||||
padding-left: 60px;
|
||||
font-size: 0;
|
||||
label {
|
||||
width: 90px;
|
||||
color: #99a9bf;
|
||||
.el-form-item {
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.popover-box {
|
||||
background: #112435;
|
||||
color: #f08047;
|
||||
height: 600px;
|
||||
width: 420px;
|
||||
overflow: auto;
|
||||
}
|
||||
.popover-box::-webkit-scrollbar {
|
||||
display: none; /* Chrome Safari */
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,515 @@
|
|||
<template>
|
||||
<div>
|
||||
<warning-bar title="注:右上角头像下拉可切换角色" />
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="addUser"
|
||||
>新增用户</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
row-key="ID"
|
||||
>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="头像"
|
||||
min-width="75"
|
||||
>
|
||||
<template #default="scope">
|
||||
<CustomPic
|
||||
style="margin-top:8px"
|
||||
:pic-src="scope.row.headerImg"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="ID"
|
||||
min-width="50"
|
||||
prop="ID"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="用户名"
|
||||
min-width="150"
|
||||
prop="userName"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="昵称"
|
||||
min-width="150"
|
||||
prop="nickName"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="手机号"
|
||||
min-width="180"
|
||||
prop="phone"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="邮箱"
|
||||
min-width="180"
|
||||
prop="email"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="用户角色"
|
||||
min-width="200"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-cascader
|
||||
v-model="scope.row.authorityIds"
|
||||
:options="authOptions"
|
||||
:show-all-levels="false"
|
||||
collapse-tags
|
||||
:props="{ multiple:true,checkStrictly: true,label:'authorityName',value:'authorityId',disabled:'disabled',emitPath:false}"
|
||||
:clearable="false"
|
||||
@visible-change="(flag)=>{changeAuthority(scope.row,flag,0)}"
|
||||
@remove-tag="(removeAuth)=>{changeAuthority(scope.row,false,removeAuth)}"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="启用"
|
||||
min-width="150"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.enable"
|
||||
inline-prompt
|
||||
:active-value="1"
|
||||
:inactive-value="2"
|
||||
@change="()=>{switchEnable(scope.row)}"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="250"
|
||||
fixed="right"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="delete"
|
||||
@click="deleteUserFunc(scope.row)"
|
||||
>删除</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="edit"
|
||||
@click="openEdit(scope.row)"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="magic-stick"
|
||||
@click="resetPasswordFunc(scope.row)"
|
||||
>重置密码</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="addUserDialog"
|
||||
title="用户"
|
||||
:show-close="false"
|
||||
:close-on-press-escape="false"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div style="height:60vh;overflow:auto;padding:0 12px;">
|
||||
<el-form
|
||||
ref="userForm"
|
||||
:rules="rules"
|
||||
:model="userInfo"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item
|
||||
v-if="dialogFlag === 'add'"
|
||||
label="用户名"
|
||||
prop="userName"
|
||||
>
|
||||
<el-input v-model="userInfo.userName" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="dialogFlag === 'add'"
|
||||
label="密码"
|
||||
prop="password"
|
||||
>
|
||||
<el-input v-model="userInfo.password" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="昵称"
|
||||
prop="nickName"
|
||||
>
|
||||
<el-input v-model="userInfo.nickName" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="手机号"
|
||||
prop="phone"
|
||||
>
|
||||
<el-input v-model="userInfo.phone" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="邮箱"
|
||||
prop="email"
|
||||
>
|
||||
<el-input v-model="userInfo.email" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="用户角色"
|
||||
prop="authorityId"
|
||||
>
|
||||
<el-cascader
|
||||
v-model="userInfo.authorityIds"
|
||||
style="width:100%"
|
||||
:options="authOptions"
|
||||
:show-all-levels="false"
|
||||
:props="{ multiple:true,checkStrictly: true,label:'authorityName',value:'authorityId',disabled:'disabled',emitPath:false}"
|
||||
:clearable="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="启用"
|
||||
prop="disabled"
|
||||
>
|
||||
<el-switch
|
||||
v-model="userInfo.enable"
|
||||
inline-prompt
|
||||
:active-value="1"
|
||||
:inactive-value="2"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="头像"
|
||||
label-width="80px"
|
||||
>
|
||||
<div
|
||||
style="display:inline-block"
|
||||
@click="openHeaderChange"
|
||||
>
|
||||
<img
|
||||
v-if="userInfo.headerImg"
|
||||
alt="头像"
|
||||
class="header-img-box"
|
||||
:src="(userInfo.headerImg && userInfo.headerImg.slice(0, 4) !== 'http')?path+userInfo.headerImg:userInfo.headerImg"
|
||||
>
|
||||
<div
|
||||
v-else
|
||||
class="header-img-box"
|
||||
>从媒体库选择</div>
|
||||
<ChooseImg
|
||||
ref="chooseImg"
|
||||
:target="userInfo"
|
||||
:target-key="`headerImg`"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeAddUserDialog">取 消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="enterAddUserDialog"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import {
|
||||
getUserList,
|
||||
setUserAuthorities,
|
||||
register,
|
||||
deleteUser
|
||||
} from '@/api/user'
|
||||
|
||||
import { getAuthorityList } from '@/api/authority'
|
||||
import CustomPic from '@/components/customPic/index.vue'
|
||||
import ChooseImg from '@/components/chooseImg/index.vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { setUserInfo, resetPassword } from '@/api/user.js'
|
||||
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
defineOptions({
|
||||
name: 'User',
|
||||
})
|
||||
|
||||
const path = ref(import.meta.env.VITE_BASE_API + '/')
|
||||
// 初始化相关
|
||||
const setAuthorityOptions = (AuthorityData, optionsData) => {
|
||||
AuthorityData &&
|
||||
AuthorityData.forEach(item => {
|
||||
if (item.children && item.children.length) {
|
||||
const option = {
|
||||
authorityId: item.authorityId,
|
||||
authorityName: item.authorityName,
|
||||
children: []
|
||||
}
|
||||
setAuthorityOptions(item.children, option.children)
|
||||
optionsData.push(option)
|
||||
} else {
|
||||
const option = {
|
||||
authorityId: item.authorityId,
|
||||
authorityName: item.authorityName
|
||||
}
|
||||
optionsData.push(option)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getUserList({ page: page.value, pageSize: pageSize.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => tableData.value, () => {
|
||||
setAuthorityIds()
|
||||
})
|
||||
|
||||
const initPage = async() => {
|
||||
getTableData()
|
||||
const res = await getAuthorityList({ page: 1, pageSize: 999 })
|
||||
setOptions(res.data.list)
|
||||
}
|
||||
|
||||
initPage()
|
||||
|
||||
const resetPasswordFunc = (row) => {
|
||||
ElMessageBox.confirm(
|
||||
'是否将此用户密码重置为123456?',
|
||||
'警告',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
).then(async() => {
|
||||
const res = await resetPassword({
|
||||
ID: row.ID,
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: res.msg,
|
||||
})
|
||||
} else {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: res.msg,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
const setAuthorityIds = () => {
|
||||
tableData.value && tableData.value.forEach((user) => {
|
||||
user.authorityIds = user.authorities && user.authorities.map(i => {
|
||||
return i.authorityId
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const chooseImg = ref(null)
|
||||
const openHeaderChange = () => {
|
||||
chooseImg.value.open()
|
||||
}
|
||||
|
||||
const authOptions = ref([])
|
||||
const setOptions = (authData) => {
|
||||
authOptions.value = []
|
||||
setAuthorityOptions(authData, authOptions.value)
|
||||
}
|
||||
|
||||
const deleteUserFunc = async(row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const res = await deleteUser({ id: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('删除成功')
|
||||
await getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗相关
|
||||
const userInfo = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
nickName: '',
|
||||
headerImg: '',
|
||||
authorityId: '',
|
||||
authorityIds: [],
|
||||
enable: 1,
|
||||
})
|
||||
|
||||
const rules = ref({
|
||||
userName: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 5, message: '最低5位字符', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入用户密码', trigger: 'blur' },
|
||||
{ min: 6, message: '最低6位字符', trigger: 'blur' }
|
||||
],
|
||||
nickName: [
|
||||
{ required: true, message: '请输入用户昵称', trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ pattern: /^1([38][0-9]|4[014-9]|[59][0-35-9]|6[2567]|7[0-8])\d{8}$/, message: '请输入合法手机号', trigger: 'blur' },
|
||||
],
|
||||
email: [
|
||||
{ pattern: /^([0-9A-Za-z\-_.]+)@([0-9a-z]+\.[a-z]{2,3}(\.[a-z]{2})?)$/g, message: '请输入正确的邮箱', trigger: 'blur' },
|
||||
],
|
||||
authorityId: [
|
||||
{ required: true, message: '请选择用户角色', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
const userForm = ref(null)
|
||||
const enterAddUserDialog = async() => {
|
||||
userInfo.value.authorityId = userInfo.value.authorityIds[0]
|
||||
userForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
const req = {
|
||||
...userInfo.value
|
||||
}
|
||||
if (dialogFlag.value === 'add') {
|
||||
const res = await register(req)
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '创建成功' })
|
||||
await getTableData()
|
||||
closeAddUserDialog()
|
||||
}
|
||||
}
|
||||
if (dialogFlag.value === 'edit') {
|
||||
const res = await setUserInfo(req)
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '编辑成功' })
|
||||
await getTableData()
|
||||
closeAddUserDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const addUserDialog = ref(false)
|
||||
const closeAddUserDialog = () => {
|
||||
userForm.value.resetFields()
|
||||
userInfo.value.headerImg = ''
|
||||
userInfo.value.authorityIds = []
|
||||
addUserDialog.value = false
|
||||
}
|
||||
|
||||
const dialogFlag = ref('add')
|
||||
|
||||
const addUser = () => {
|
||||
dialogFlag.value = 'add'
|
||||
addUserDialog.value = true
|
||||
}
|
||||
|
||||
const tempAuth = {}
|
||||
const changeAuthority = async(row, flag, removeAuth) => {
|
||||
if (flag) {
|
||||
if (!removeAuth) {
|
||||
tempAuth[row.ID] = [...row.authorityIds]
|
||||
}
|
||||
return
|
||||
}
|
||||
await nextTick()
|
||||
const res = await setUserAuthorities({
|
||||
ID: row.ID,
|
||||
authorityIds: row.authorityIds
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '角色设置成功' })
|
||||
} else {
|
||||
if (!removeAuth) {
|
||||
row.authorityIds = [...tempAuth[row.ID]]
|
||||
delete tempAuth[row.ID]
|
||||
} else {
|
||||
row.authorityIds = [removeAuth, ...row.authorityIds]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const openEdit = (row) => {
|
||||
dialogFlag.value = 'edit'
|
||||
userInfo.value = JSON.parse(JSON.stringify(row))
|
||||
addUserDialog.value = true
|
||||
}
|
||||
|
||||
const switchEnable = async(row) => {
|
||||
userInfo.value = JSON.parse(JSON.stringify(row))
|
||||
await nextTick()
|
||||
const req = {
|
||||
...userInfo.value
|
||||
}
|
||||
const res = await setUserInfo(req)
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: `${req.enable === 2 ? '禁用' : '启用'}成功` })
|
||||
await getTableData()
|
||||
userInfo.value.headerImg = ''
|
||||
userInfo.value.authorityIds = []
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.header-img-box {
|
||||
@apply w-52 h-52 border border-solid border-gray-300 rounded-xl flex justify-center items-center cursor-pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-row
|
||||
:gutter="15"
|
||||
class="py-1"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-card
|
||||
v-if="state.os"
|
||||
class="card_item"
|
||||
>
|
||||
<template #header>
|
||||
<div>Runtime</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">os:</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="state.os.goos"
|
||||
/>
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">cpu nums:</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="state.os.numCpu"
|
||||
/>
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">compiler:</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="state.os.compiler"
|
||||
/>
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">go version:</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="state.os.goVersion"
|
||||
/>
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">goroutine nums:</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="state.os.numGoroutine"
|
||||
/>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card
|
||||
v-if="state.disk"
|
||||
class="card_item"
|
||||
>
|
||||
<template #header>
|
||||
<div>Disk</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">total (MB)</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="state.disk.totalMb"
|
||||
/>
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">used (MB)</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="state.disk.usedMb"
|
||||
/>
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">total (GB)</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="state.disk.totalGb"
|
||||
/>
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">used (GB)</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="state.disk.usedGb"
|
||||
/>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-progress
|
||||
type="dashboard"
|
||||
:percentage="state.disk.usedPercent"
|
||||
:color="colors"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row
|
||||
:gutter="15"
|
||||
class="py-1"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-card
|
||||
v-if="state.cpu"
|
||||
class="card_item"
|
||||
:body-style="{ height: '180px', 'overflow-y': 'scroll' }"
|
||||
>
|
||||
<template #header>
|
||||
<div>CPU</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">physical number of cores:</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="state.cpu.cores"
|
||||
/>
|
||||
</el-row>
|
||||
<el-row
|
||||
v-for="(item, index) in state.cpu.cpus"
|
||||
:key="index"
|
||||
:gutter="10"
|
||||
>
|
||||
<el-col :span="12">core {{ index }}:</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
><el-progress
|
||||
type="line"
|
||||
:percentage="+item.toFixed(0)"
|
||||
:color="colors"
|
||||
/></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card
|
||||
v-if="state.ram"
|
||||
class="card_item"
|
||||
>
|
||||
<template #header>
|
||||
<div>Ram</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">total (MB)</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="state.ram.totalMb"
|
||||
/>
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">used (MB)</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="state.ram.usedMb"
|
||||
/>
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">total (GB)</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="state.ram.totalMb / 1024"
|
||||
/>
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">used (GB)</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="(state.ram.usedMb / 1024).toFixed(2)"
|
||||
/>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-progress
|
||||
type="dashboard"
|
||||
:percentage="state.ram.usedPercent"
|
||||
:color="colors"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getSystemState } from '@/api/system'
|
||||
import { onUnmounted, ref } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'State',
|
||||
})
|
||||
|
||||
const timer = ref(null)
|
||||
const state = ref({})
|
||||
const colors = ref([
|
||||
{ color: '#5cb87a', percentage: 20 },
|
||||
{ color: '#e6a23c', percentage: 40 },
|
||||
{ color: '#f56c6c', percentage: 80 }
|
||||
])
|
||||
|
||||
const reload = async() => {
|
||||
const { data } = await getSystemState()
|
||||
state.value = data.server
|
||||
}
|
||||
|
||||
reload()
|
||||
timer.value = setInterval(() => {
|
||||
reload()
|
||||
}, 1000 * 10)
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer.value)
|
||||
timer.value = null
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.card_item {
|
||||
@apply h-80 text-xl;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,387 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline"
|
||||
@keyup.enter="onSubmit">
|
||||
<el-form-item label="ID" prop="id">
|
||||
<template #label>
|
||||
<span>ID</span>
|
||||
</template>
|
||||
<el-input v-model="searchInfo.id" type="input" />
|
||||
</el-form-item>
|
||||
<el-form-item label="上级Id" prop="pid">
|
||||
<template #label>
|
||||
<span>上级Id</span>
|
||||
</template>
|
||||
<el-input v-model="searchInfo.pid" type="input" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区级别" prop="level">
|
||||
<template #label>
|
||||
<span>地区级别</span>
|
||||
</template>
|
||||
<el-input v-model="searchInfo.level" type="input" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="onSubmit">查询</el-button>
|
||||
<el-button icon="refresh" @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" icon="plus" @click="openDialog">新增</el-button>
|
||||
<el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length"
|
||||
@click="onDelete">删除</el-button>
|
||||
</div>
|
||||
<el-table ref="multipleTable" style="width: 100%" tooltip-effect="dark" :data="tableData" row-key="ID"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" />
|
||||
|
||||
<el-table-column align="left" label="ID" prop="ID" width="120" />
|
||||
<el-table-column align="left" label="上级Id" prop="pid" width="120" />
|
||||
<el-table-column align="left" label="地区名称" prop="name" width="300" />
|
||||
<el-table-column align="left" label="是否显示" prop="visible" width="120">
|
||||
<template #default="scope">{{ formatBoolean(scope.row.visible) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="排序值" prop="weight" width="120" />
|
||||
<el-table-column align="left" label="地区级别" prop="level" width="120" />
|
||||
<el-table-column align="left" label="操作" fixed="right" min-width="240">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link class="table-button" @click="getDetails(scope.row)">
|
||||
<el-icon style="margin-right: 5px">
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
查看详情
|
||||
</el-button>
|
||||
<el-button type="primary" link icon="edit" class="table-button"
|
||||
@click="updateSystemAreaFunc(scope.row)">变更</el-button>
|
||||
<el-button type="primary" link icon="delete" @click="deleteRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination layout="total, sizes, prev, pager, next, jumper" :current-page="page" :page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]" :total="total" @current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange" />
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer v-model="dialogFormVisible" size="800" :show-close="false" :before-close="closeDialog">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">{{ type==='create'?'添加':'修改' }}</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="enterDialog">确 定</el-button>
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form ref="elFormRef" :model="formData" label-position="top" :rules="rule" label-width="80px">
|
||||
<el-form-item label="上级Id:" prop="pid">
|
||||
<el-input v-model.number="formData.pid" :clearable="true" placeholder="请输入上级Id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区名称:" prop="name">
|
||||
<el-input v-model="formData.name" :clearable="true" placeholder="请输入地区名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否显示:" prop="visible">
|
||||
<el-switch v-model="formData.visible" active-color="#13ce66" inactive-color="#ff4949" active-text="是"
|
||||
inactive-text="否" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序值:" prop="weight">
|
||||
<el-input v-model.number="formData.weight" :clearable="true" placeholder="请输入排序值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区级别:" prop="level">
|
||||
<el-input v-model.number="formData.level" :clearable="true" placeholder="请输入地区级别" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer v-model="detailShow" size="800" :before-close="closeDetailShow" destroy-on-close>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">查看详情</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="ID">
|
||||
{{ formData.ID }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="上级Id">
|
||||
{{ formData.pid }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="地区名称">
|
||||
{{ formData.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="是否显示">
|
||||
{{ formatBoolean(formData.visible) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="排序值">
|
||||
{{ formData.weight }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="地区级别">
|
||||
{{ formData.level }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createSystemArea,
|
||||
deleteSystemArea,
|
||||
deleteSystemAreaByIds,
|
||||
updateSystemArea,
|
||||
findSystemArea,
|
||||
getSystemAreaList
|
||||
} from '@/api/systemArea'
|
||||
|
||||
// 全量引入格式化工具 请按需保留
|
||||
import { formatBoolean } from '@/utils/format'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'SystemArea'
|
||||
})
|
||||
|
||||
// 自动化生成的字典(可能为空)以及字段
|
||||
const formData = ref({
|
||||
id: 0,
|
||||
pid: 0,
|
||||
name: '',
|
||||
visible: false,
|
||||
weight: 0,
|
||||
level: 0,
|
||||
})
|
||||
|
||||
// 验证规则
|
||||
const rule = reactive({})
|
||||
|
||||
const elFormRef = ref()
|
||||
const elSearchFormRef = ref()
|
||||
|
||||
// =========== 表格控制部分 ===========
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
|
||||
// 重置
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const onSubmit = () => {
|
||||
elSearchFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
if (searchInfo.value.visible === '') {
|
||||
searchInfo.value.visible = null
|
||||
}
|
||||
if (searchInfo.value.pid === '') {
|
||||
searchInfo.value.pid = null
|
||||
}
|
||||
getTableData()
|
||||
})
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 修改页面容量
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getSystemAreaList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
// ============== 表格控制部分结束 ===============
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
const setOptions = async() => {
|
||||
}
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
setOptions()
|
||||
|
||||
// 多选数据
|
||||
const multipleSelection = ref([])
|
||||
// 多选
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteRow = (row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteSystemAreaFunc(row)
|
||||
})
|
||||
}
|
||||
|
||||
// 多选删除
|
||||
const onDelete = async() => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const IDs = []
|
||||
if (multipleSelection.value.length === 0) {
|
||||
ElMessage({
|
||||
type: 'warning',
|
||||
message: '请选择要删除的数据'
|
||||
})
|
||||
return
|
||||
}
|
||||
multipleSelection.value &&
|
||||
multipleSelection.value.map(item => {
|
||||
IDs.push(item.ID)
|
||||
})
|
||||
const res = await deleteSystemAreaByIds({ IDs })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === IDs.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 行为控制标记(弹窗内部需要增还是改)
|
||||
const type = ref('')
|
||||
|
||||
// 更新行
|
||||
const updateSystemAreaFunc = async(row) => {
|
||||
const res = await findSystemArea({ ID: row.ID })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.resystemArea
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteSystemAreaFunc = async(row) => {
|
||||
const res = await deleteSystemArea({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗控制标记
|
||||
const dialogFormVisible = ref(false)
|
||||
|
||||
// 查看详情控制标记
|
||||
const detailShow = ref(false)
|
||||
|
||||
// 打开详情弹窗
|
||||
const openDetailShow = () => {
|
||||
detailShow.value = true
|
||||
}
|
||||
|
||||
// 打开详情
|
||||
const getDetails = async(row) => {
|
||||
// 打开弹窗
|
||||
const res = await findSystemArea({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.resystemArea
|
||||
openDetailShow()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭详情弹窗
|
||||
const closeDetailShow = () => {
|
||||
detailShow.value = false
|
||||
formData.value = {
|
||||
pid: 0,
|
||||
name: '',
|
||||
visible: false,
|
||||
weight: 0,
|
||||
level: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
pid: 0,
|
||||
name: '',
|
||||
visible: false,
|
||||
weight: 0,
|
||||
level: 0,
|
||||
}
|
||||
}
|
||||
// 弹窗确定
|
||||
const enterDialog = async() => {
|
||||
elFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createSystemArea(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateSystemArea(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createSystemArea(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-form-box">
|
||||
<el-form :model="formData" ref="elFormRef" label-position="right" :rules="rule" label-width="80px">
|
||||
<el-form-item label="上级Id:" prop="pid">
|
||||
<el-input v-model.number="formData.pid" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区名称:" prop="name">
|
||||
<el-input v-model="formData.name" :clearable="true" placeholder="请输入地区名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否显示:" prop="visible">
|
||||
<el-switch v-model="formData.visible" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序值:" prop="weight">
|
||||
<el-input v-model.number="formData.weight" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区级别:" prop="level">
|
||||
<el-input v-model.number="formData.level" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<el-button type="primary" @click="back">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createSystemArea,
|
||||
updateSystemArea,
|
||||
findSystemArea
|
||||
} from '@/api/systemArea'
|
||||
|
||||
defineOptions({
|
||||
name: 'SystemAreaForm'
|
||||
})
|
||||
|
||||
// 自动获取字典
|
||||
import { getDictFunc } from '@/utils/format'
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const type = ref('')
|
||||
const formData = ref({
|
||||
pid: 0,
|
||||
name: '',
|
||||
visible: false,
|
||||
weight: 0,
|
||||
level: 0,
|
||||
})
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
|
||||
// 初始化方法
|
||||
const init = async () => {
|
||||
// 建议通过url传参获取目标数据ID 调用 find方法进行查询数据操作 从而决定本页面是create还是update 以下为id作为url参数示例
|
||||
if (route.query.id) {
|
||||
const res = await findSystemArea({ ID: route.query.id })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.resystemArea
|
||||
type.value = 'update'
|
||||
}
|
||||
} else {
|
||||
type.value = 'create'
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
// 保存按钮
|
||||
const save = async() => {
|
||||
elFormRef.value?.validate( async (valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createSystemArea(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateSystemArea(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createSystemArea(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 返回按钮
|
||||
const back = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
<template>
|
||||
<div>
|
||||
<warning-bar title="id , created_at , updated_at , deleted_at 会自动生成请勿重复创建。搜索时如果条件为LIKE只支持字符串" />
|
||||
<el-form
|
||||
ref="fieldDialogFrom"
|
||||
:model="middleDate"
|
||||
label-width="120px"
|
||||
label-position="right"
|
||||
:rules="rules"
|
||||
class="grid grid-cols-2"
|
||||
>
|
||||
<el-form-item
|
||||
label="字段名称"
|
||||
prop="fieldName"
|
||||
>
|
||||
<el-input
|
||||
v-model="middleDate.fieldName"
|
||||
autocomplete="off"
|
||||
style="width:80%"
|
||||
/>
|
||||
<el-button
|
||||
style="width:18%;margin-left:2%"
|
||||
@click="autoFill"
|
||||
>
|
||||
<span style="font-size: 12px">自动填充</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="字段中文名"
|
||||
prop="fieldDesc"
|
||||
>
|
||||
<el-input
|
||||
v-model="middleDate.fieldDesc"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="字段JSON"
|
||||
prop="fieldJson"
|
||||
>
|
||||
<el-input
|
||||
v-model="middleDate.fieldJson"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="数据库字段名"
|
||||
prop="columnName"
|
||||
>
|
||||
<el-input
|
||||
v-model="middleDate.columnName"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="数据库字段描述"
|
||||
prop="comment"
|
||||
>
|
||||
<el-input
|
||||
v-model="middleDate.comment"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="字段类型"
|
||||
prop="fieldType"
|
||||
>
|
||||
<el-select
|
||||
v-model="middleDate.fieldType"
|
||||
style="width:100%"
|
||||
placeholder="请选择字段类型"
|
||||
clearable
|
||||
@change="clearOther"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in typeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="item.disabled"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="middleDate.fieldType === 'enum' ? '枚举值' : '类型长度'"
|
||||
prop="dataTypeLong"
|
||||
>
|
||||
<el-input
|
||||
v-model="middleDate.dataTypeLong"
|
||||
:placeholder="middleDate.fieldType === 'enum'?`例:'北京','天津'`:'数据库类型长度'"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="字段查询条件"
|
||||
prop="fieldSearchType"
|
||||
>
|
||||
<el-select
|
||||
v-model="middleDate.fieldSearchType"
|
||||
:disabled="middleDate.fieldType === 'json'"
|
||||
style="width:100%"
|
||||
placeholder="请选择字段查询条件"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in typeSearchOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="canSelect(item.value)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="关联字典"
|
||||
prop="dictType"
|
||||
>
|
||||
<el-select
|
||||
v-model="middleDate.dictType"
|
||||
style="width:100%"
|
||||
:disabled="middleDate.fieldType!=='int'&&middleDate.fieldType!=='string'"
|
||||
placeholder="请选择字典"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in dictOptions"
|
||||
:key="item.type"
|
||||
:label="`${item.type}(${item.name})`"
|
||||
:value="item.type"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="主键">
|
||||
<el-checkbox v-model="middleDate.primaryKey" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否排序">
|
||||
<el-switch v-model="middleDate.sort" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否必填">
|
||||
<el-switch v-model="middleDate.require" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否可清空">
|
||||
<el-switch v-model="middleDate.clearable" />
|
||||
</el-form-item>
|
||||
<el-form-item label="校验失败文案">
|
||||
<el-input v-model="middleDate.errorText" />
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { toLowerCase, toSQLLine } from '@/utils/stringFun'
|
||||
import { getSysDictionaryList } from '@/api/sysDictionary'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'FieldDialog'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
dialogMiddle: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
typeOptions: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
typeSearchOptions: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const middleDate = ref({})
|
||||
const dictOptions = ref([])
|
||||
|
||||
const rules = ref({
|
||||
fieldName: [
|
||||
{ required: true, message: '请输入字段英文名', trigger: 'blur' }
|
||||
],
|
||||
fieldDesc: [
|
||||
{ required: true, message: '请输入字段中文名', trigger: 'blur' }
|
||||
],
|
||||
fieldJson: [
|
||||
{ required: true, message: '请输入字段格式化json', trigger: 'blur' }
|
||||
],
|
||||
columnName: [
|
||||
{ required: true, message: '请输入数据库字段', trigger: 'blur' }
|
||||
],
|
||||
fieldType: [
|
||||
{ required: true, message: '请选择字段类型', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const init = async() => {
|
||||
middleDate.value = props.dialogMiddle
|
||||
const dictRes = await getSysDictionaryList({
|
||||
page: 1,
|
||||
pageSize: 999999
|
||||
})
|
||||
|
||||
dictOptions.value = dictRes.data
|
||||
}
|
||||
init()
|
||||
|
||||
const autoFill = () => {
|
||||
middleDate.value.fieldJson = toLowerCase(middleDate.value.fieldName)
|
||||
middleDate.value.columnName = toSQLLine(middleDate.value.fieldJson)
|
||||
}
|
||||
|
||||
const canSelect = (item) => {
|
||||
const fieldType = middleDate.value.fieldType
|
||||
if (fieldType !== 'string' && item === 'LIKE') {
|
||||
return true
|
||||
}
|
||||
|
||||
if ((fieldType !== 'int' && fieldType !== 'time.Time' && fieldType !== 'float64') && (item === 'BETWEEN' || item === 'NOT BETWEEN')) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const clearOther = () => {
|
||||
middleDate.value.fieldSearchType = ''
|
||||
middleDate.value.dictType = ''
|
||||
}
|
||||
|
||||
const fieldDialogFrom = ref(null)
|
||||
defineExpose({ fieldDialogFrom })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane
|
||||
v-for="(item, key) in previewCode"
|
||||
:key="key"
|
||||
:label="key"
|
||||
:name="key"
|
||||
>
|
||||
<div
|
||||
:id="key"
|
||||
class="h-[50vh] bg-white px-5 overflow-y-scroll"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { marked } from 'marked'
|
||||
import hljs from 'highlight.js'
|
||||
import 'highlight.js/styles/atom-one-dark.css'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
previewCode: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const activeName = ref('')
|
||||
onMounted(() => {
|
||||
marked.setOptions({
|
||||
renderer: new marked.Renderer(),
|
||||
highlight: function(code) {
|
||||
return hljs.highlightAuto(code).value
|
||||
},
|
||||
pedantic: false,
|
||||
gfm: true,
|
||||
tables: true,
|
||||
breaks: false,
|
||||
sanitize: false,
|
||||
smartLists: true,
|
||||
smartypants: false,
|
||||
xhtml: false,
|
||||
langPrefix: 'hljs language-'
|
||||
})
|
||||
for (const key in props.previewCode) {
|
||||
if (activeName.value === '') {
|
||||
activeName.value = key
|
||||
}
|
||||
document.getElementById(key).innerHTML = marked(props.previewCode[key])
|
||||
}
|
||||
})
|
||||
|
||||
const selectText = () => {
|
||||
const element = document.getElementById(activeName.value)
|
||||
if (document.body.createTextRange) {
|
||||
const range = document.body.createTextRange()
|
||||
range.moveToElementText(element)
|
||||
range.select()
|
||||
} else if (window.getSelection) {
|
||||
const selection = window.getSelection()
|
||||
const range = document.createRange()
|
||||
range.selectNodeContents(element)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
} else {
|
||||
alert('none')
|
||||
}
|
||||
}
|
||||
const copy = () => {
|
||||
selectText()
|
||||
document.execCommand('copy')
|
||||
ElMessage.success('复制成功')
|
||||
}
|
||||
|
||||
defineExpose({ copy })
|
||||
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,227 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="goAutoCode(null)"
|
||||
>新增</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData">
|
||||
<el-table-column
|
||||
type="selection"
|
||||
width="55"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="id"
|
||||
width="60"
|
||||
prop="ID"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="日期"
|
||||
width="180"
|
||||
>
|
||||
<template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="结构体名"
|
||||
min-width="150"
|
||||
prop="structName"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="结构体描述"
|
||||
min-width="150"
|
||||
prop="structCNName"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="表名称"
|
||||
min-width="150"
|
||||
prop="tableName"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="回滚标记"
|
||||
min-width="150"
|
||||
prop="flag"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
v-if="scope.row.flag"
|
||||
type="danger"
|
||||
|
||||
effect="dark"
|
||||
>
|
||||
已回滚
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-else
|
||||
|
||||
type="success"
|
||||
effect="dark"
|
||||
>
|
||||
未回滚
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="操作"
|
||||
min-width="240"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
:disabled="scope.row.flag === 1"
|
||||
@click="rollbackFunc(scope.row,true)"
|
||||
>回滚(删表)</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
:disabled="scope.row.flag === 1"
|
||||
@click="rollbackFunc(scope.row,false)"
|
||||
>回滚(不删表)</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="goAutoCode(scope.row)"
|
||||
>复用</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="deleteRow(scope.row)"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getSysHistory, rollback, delSysHistory } from '@/api/autoCode.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
import { formatDate } from '@/utils/format'
|
||||
|
||||
defineOptions({
|
||||
name: 'AutoCodeAdmin'
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getSysHistory({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value
|
||||
})
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
const deleteRow = async(row) => {
|
||||
ElMessageBox.confirm('此操作将删除本历史, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const res = await delSysHistory({ id: Number(row.ID) })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('删除成功')
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
const rollbackFunc = async(row, flag) => {
|
||||
if (flag) {
|
||||
ElMessageBox.confirm(`此操作将删除自动创建的文件和api(会删除表!!!), 是否继续?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
ElMessageBox.confirm(`此操作将删除自动创建的文件和api(会删除表!!!), 请继续确认!!!`, '会删除表', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
ElMessageBox.confirm(`此操作将删除自动创建的文件和api(会删除表!!!), 请继续确认!!!`, '会删除表', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const res = await rollback({ id: Number(row.ID), deleteTable: flag })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('回滚成功')
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
} else {
|
||||
ElMessageBox.confirm(`此操作将删除自动创建的文件和api, 是否继续?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const res = await rollback({ id: Number(row.ID), deleteTable: flag })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('回滚成功')
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const goAutoCode = (row) => {
|
||||
if (row) {
|
||||
router.push({ name: 'autoCodeEdit', params: {
|
||||
id: row.ID
|
||||
}})
|
||||
} else {
|
||||
router.push({ name: 'autoCode' })
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
<template>
|
||||
<div>
|
||||
<warning-bar
|
||||
href="https://www.bilibili.com/video/BV1kv4y1g7nT?p=3"
|
||||
title="此功能为开发环境使用,不建议发布到生产,具体使用效果请看视频https://www.bilibili.com/video/BV1kv4y1g7nT?p=3"
|
||||
/>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list gap-3 flex items-center">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="openDialog('addApi')"
|
||||
>新增</el-button>
|
||||
<el-icon
|
||||
class="cursor-pointer"
|
||||
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=3&vd_source=f2640257c21e3b547a790461ed94875e')"
|
||||
><VideoCameraFilled /></el-icon>
|
||||
</div>
|
||||
<el-table :data="tableData">
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="id"
|
||||
width="60"
|
||||
prop="ID"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="包名"
|
||||
width="150"
|
||||
prop="packageName"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="展示名"
|
||||
width="150"
|
||||
prop="label"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="描述"
|
||||
min-width="150"
|
||||
prop="desc"
|
||||
/>
|
||||
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="操作"
|
||||
width="200"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="delete"
|
||||
|
||||
type="primary"
|
||||
link
|
||||
@click="deleteApiFunc(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:before-close="closeDialog"
|
||||
title="创建Package"
|
||||
>
|
||||
<warning-bar title="新增Pkg用于自动化代码使用" />
|
||||
<el-form
|
||||
ref="pkgForm"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item
|
||||
label="包名"
|
||||
prop="packageName"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.packageName"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="展示名"
|
||||
prop="label"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.label"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="描述"
|
||||
prop="desc"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.desc"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="enterDialog"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createPackageApi,
|
||||
getPackageApi,
|
||||
deletePackageApi,
|
||||
} from '@/api/autoCode'
|
||||
import { ref } from 'vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { toDoc } from '@/utils/doc'
|
||||
import { VideoCameraFilled } from '@element-plus/icons-vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'AutoPkg',
|
||||
})
|
||||
|
||||
const form = ref({
|
||||
packageName: '',
|
||||
label: '',
|
||||
desc: '',
|
||||
})
|
||||
|
||||
const validateNum = (rule, value, callback) => {
|
||||
if ((/^\d+$/.test(value[0]))) {
|
||||
callback(new Error('不能够以数字开头'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const rules = ref({
|
||||
packageName: [
|
||||
{ required: true, message: '请输入包名', trigger: 'blur' },
|
||||
{ validator: validateNum, trigger: 'blur' }
|
||||
],
|
||||
})
|
||||
|
||||
const dialogFormVisible = ref(false)
|
||||
const openDialog = () => {
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
form.value = {
|
||||
packageName: '',
|
||||
label: '',
|
||||
desc: '',
|
||||
}
|
||||
}
|
||||
|
||||
const pkgForm = ref(null)
|
||||
const enterDialog = async() => {
|
||||
pkgForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
const res = await createPackageApi(form.value)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '添加成功',
|
||||
showClose: true
|
||||
})
|
||||
}
|
||||
getTableData()
|
||||
closeDialog()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const tableData = ref([])
|
||||
const getTableData = async() => {
|
||||
const table = await getPackageApi()
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.pkgs
|
||||
}
|
||||
}
|
||||
|
||||
const deleteApiFunc = async(row) => {
|
||||
ElMessageBox.confirm('此操作仅删除数据库中的pkg存储,后端相应目录结构请自行删除与数据库保持一致!', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async() => {
|
||||
const res = await deletePackageApi(row)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功!'
|
||||
})
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getTableData()
|
||||
</script>
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-table-box">
|
||||
<el-form
|
||||
label-width="140px"
|
||||
class="w-[680px]"
|
||||
>
|
||||
<el-form-item label="插件名">
|
||||
<el-input
|
||||
v-model="form.plugName"
|
||||
placeholder="必填(英文大写字母开头)"
|
||||
@blur="titleCase"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="路由组">
|
||||
<el-input
|
||||
v-model="form.routerGroup"
|
||||
placeholder="将会作为插件路由组使用"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="使用全局属性">
|
||||
<el-checkbox v-model="form.hasGlobal" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.hasGlobal"
|
||||
label="全局属性"
|
||||
>
|
||||
<div
|
||||
v-for="(i,k) in form.global"
|
||||
:key="k"
|
||||
class="plug-row"
|
||||
>
|
||||
<span>
|
||||
<el-input
|
||||
v-model="i.key"
|
||||
placeholder="key 必填"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<el-select
|
||||
v-model="i.type"
|
||||
placeholder="type 必填"
|
||||
>
|
||||
<el-option
|
||||
label="string"
|
||||
value="string"
|
||||
/>
|
||||
<el-option
|
||||
label="int"
|
||||
value="int"
|
||||
/>
|
||||
<el-option
|
||||
label="float32"
|
||||
value="float32"
|
||||
/>
|
||||
<el-option
|
||||
label="float64"
|
||||
value="float64"
|
||||
/>
|
||||
<el-option
|
||||
label="bool"
|
||||
value="bool"
|
||||
/>
|
||||
</el-select>
|
||||
</span>
|
||||
<span>
|
||||
<el-input
|
||||
v-model="i.desc"
|
||||
placeholder="备注 必填"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<el-button
|
||||
:icon="Plus"
|
||||
circle
|
||||
@click="addkv(form.global)"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<el-button
|
||||
:icon="Minus"
|
||||
circle
|
||||
@click="minkv(form.global,k)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="使用Request">
|
||||
<el-checkbox v-model="form.hasRequest" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.hasRequest"
|
||||
label="Request"
|
||||
>
|
||||
<div
|
||||
v-for="(i,k) in form.request"
|
||||
:key="k"
|
||||
class="plug-row"
|
||||
>
|
||||
<span>
|
||||
<el-input
|
||||
v-model="i.key"
|
||||
placeholder="key 必填"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<el-select
|
||||
v-model="i.type"
|
||||
placeholder="type 必填"
|
||||
>
|
||||
<el-option
|
||||
label="string"
|
||||
value="string"
|
||||
/>
|
||||
<el-option
|
||||
label="int"
|
||||
value="int"
|
||||
/>
|
||||
<el-option
|
||||
label="float32"
|
||||
value="float32"
|
||||
/>
|
||||
<el-option
|
||||
label="float64"
|
||||
value="float64"
|
||||
/>
|
||||
<el-option
|
||||
label="bool"
|
||||
value="bool"
|
||||
/>
|
||||
</el-select>
|
||||
</span>
|
||||
<span>
|
||||
<el-input
|
||||
v-model="i.desc"
|
||||
placeholder="备注 必填"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<el-button
|
||||
:icon="Plus"
|
||||
circle
|
||||
@click="addkv(form.request)"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<el-button
|
||||
:icon="Minus"
|
||||
circle
|
||||
@click="minkv(form.request,k)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="使用Response">
|
||||
<el-checkbox v-model="form.hasResponse" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.hasResponse"
|
||||
label="Response"
|
||||
>
|
||||
<div
|
||||
v-for="(i,k) in form.response"
|
||||
:key="k"
|
||||
class="plug-row"
|
||||
>
|
||||
<span>
|
||||
<el-input
|
||||
v-model="i.key"
|
||||
placeholder="key 必填"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<el-select
|
||||
v-model="i.type"
|
||||
placeholder="type 必填"
|
||||
>
|
||||
<el-option
|
||||
label="string"
|
||||
value="string"
|
||||
/>
|
||||
<el-option
|
||||
label="int"
|
||||
value="int"
|
||||
/>
|
||||
<el-option
|
||||
label="float32"
|
||||
value="float32"
|
||||
/>
|
||||
<el-option
|
||||
label="float64"
|
||||
value="float64"
|
||||
/>
|
||||
<el-option
|
||||
label="bool"
|
||||
value="bool"
|
||||
/>
|
||||
</el-select>
|
||||
</span>
|
||||
<span>
|
||||
<el-input
|
||||
v-model="i.desc"
|
||||
placeholder="备注 必填"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<el-button
|
||||
:icon="Plus"
|
||||
circle
|
||||
@click="addkv(form.response)"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<el-button
|
||||
:icon="Minus"
|
||||
circle
|
||||
@click="minkv(form.response,k)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="createPlug"
|
||||
>创建</el-button>
|
||||
<el-icon
|
||||
class="cursor-pointer ml-3"
|
||||
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=13&vd_source=f2640257c21e3b547a790461ed94875e')"
|
||||
><VideoCameraFilled /></el-icon>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { toUpperCase } from '@/utils/stringFun'
|
||||
|
||||
import {
|
||||
Plus,
|
||||
Minus, VideoCameraFilled
|
||||
} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
import { createPlugApi } from '@/api/autoCode.js'
|
||||
|
||||
import { reactive } from 'vue'
|
||||
import { toDoc } from '@/utils/doc'
|
||||
|
||||
const form = reactive({
|
||||
plugName: '',
|
||||
routerGroup: '',
|
||||
hasGlobal: true,
|
||||
hasRequest: true,
|
||||
hasResponse: true,
|
||||
global: [{
|
||||
key: '',
|
||||
type: '',
|
||||
desc: '',
|
||||
}],
|
||||
request: [{
|
||||
key: '',
|
||||
type: '',
|
||||
desc: '',
|
||||
}],
|
||||
response: [{
|
||||
key: '',
|
||||
type: '',
|
||||
desc: '',
|
||||
}]
|
||||
})
|
||||
|
||||
const titleCase = () => {
|
||||
form.plugName = toUpperCase(form.plugName)
|
||||
}
|
||||
|
||||
const createPlug = async() => {
|
||||
if (!form.plugName || !form.routerGroup) {
|
||||
ElMessage.error('插件名称和插件路由组为必填项')
|
||||
return
|
||||
}
|
||||
if (form.hasGlobal) {
|
||||
const intercept = form.global.some(i => {
|
||||
if (!i.key || !i.type) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
if (intercept) {
|
||||
ElMessage.error('全局属性的key和type为必填项')
|
||||
return
|
||||
}
|
||||
}
|
||||
if (form.hasRequest) {
|
||||
const intercept = form.request.some(i => {
|
||||
if (!i.key || !i.type) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
if (intercept) {
|
||||
ElMessage.error('请求属性的key和type为必填项')
|
||||
return
|
||||
}
|
||||
}
|
||||
if (form.hasResponse) {
|
||||
const intercept = form.response.some(i => {
|
||||
if (!i.key || !i.type) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
if (intercept) {
|
||||
ElMessage.error('响应属性的key和type为必填项')
|
||||
return
|
||||
}
|
||||
}
|
||||
const res = await createPlugApi(form)
|
||||
if (res.code === 0) {
|
||||
ElMessageBox('创建成功,插件已自动写入后端plugin目录下,请按照自己的逻辑进行创造')
|
||||
}
|
||||
}
|
||||
|
||||
const addkv = (arr) => {
|
||||
arr.push({
|
||||
key: '',
|
||||
value: '',
|
||||
})
|
||||
}
|
||||
|
||||
const minkv = (arr, key) => {
|
||||
if (arr.length === 1) {
|
||||
ElMessage.warning('至少有一个全局属性')
|
||||
return
|
||||
}
|
||||
arr.splice(key, 1)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plug-row{
|
||||
@apply flex items-center w-full;
|
||||
&+&{
|
||||
@apply mt-3;
|
||||
}
|
||||
&>span{
|
||||
@apply ml-2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,723 @@
|
|||
<template>
|
||||
<div>
|
||||
<WarningBar
|
||||
title="本功能提供同步的表格导出功能,大数据量的异步表格导出功能,可以选择点我定制"
|
||||
href="https://flipped-aurora.feishu.cn/docx/KwjxdnvatozgwIxGV0rcpkZSn4d"
|
||||
/>
|
||||
<div class="gva-search-box">
|
||||
<el-form
|
||||
ref="elSearchFormRef"
|
||||
:inline="true"
|
||||
:model="searchInfo"
|
||||
class="demo-form-inline"
|
||||
:rules="searchRule"
|
||||
@keyup.enter="onSubmit"
|
||||
>
|
||||
<el-form-item
|
||||
label="创建日期"
|
||||
prop="createdAt"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
创建日期
|
||||
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-date-picker
|
||||
v-model="searchInfo.startCreatedAt"
|
||||
type="datetime"
|
||||
placeholder="开始日期"
|
||||
:disabled-date="time=> searchInfo.endCreatedAt ? time.getTime() > searchInfo.endCreatedAt.getTime() : false"
|
||||
/>
|
||||
—
|
||||
<el-date-picker
|
||||
v-model="searchInfo.endCreatedAt"
|
||||
type="datetime"
|
||||
placeholder="结束日期"
|
||||
:disabled-date="time=> searchInfo.startCreatedAt ? time.getTime() < searchInfo.startCreatedAt.getTime() : false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="模板名称"
|
||||
prop="name"
|
||||
>
|
||||
<el-input
|
||||
v-model="searchInfo.name"
|
||||
placeholder="搜索条件"
|
||||
/>
|
||||
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="表名称"
|
||||
prop="tableName"
|
||||
>
|
||||
<el-input
|
||||
v-model="searchInfo.tableName"
|
||||
placeholder="搜索条件"
|
||||
/>
|
||||
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="模板标识"
|
||||
prop="templateID"
|
||||
>
|
||||
<el-input
|
||||
v-model="searchInfo.templateID"
|
||||
placeholder="搜索条件"
|
||||
/>
|
||||
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="search"
|
||||
@click="onSubmit"
|
||||
>查询</el-button>
|
||||
<el-button
|
||||
icon="refresh"
|
||||
@click="onReset"
|
||||
>重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="openDialog"
|
||||
>新增</el-button>
|
||||
|
||||
<el-button
|
||||
icon="delete"
|
||||
style="margin-left: 10px;"
|
||||
:disabled="!multipleSelection.length"
|
||||
@click="onDelete"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
style="width: 100%"
|
||||
tooltip-effect="dark"
|
||||
:data="tableData"
|
||||
row-key="ID"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
width="55"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="日期"
|
||||
width="180"
|
||||
>
|
||||
<template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="数据库"
|
||||
prop="name"
|
||||
width="120"
|
||||
>
|
||||
<template #defalut="scope">
|
||||
<span>{{ scope.row.dbNname || "GVA库" }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="模板名称"
|
||||
prop="name"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="表名称"
|
||||
prop="tableName"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="模板标识"
|
||||
prop="templateID"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="模板信息"
|
||||
prop="templateInfo"
|
||||
min-width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="操作"
|
||||
min-width="120"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="edit"
|
||||
class="table-button"
|
||||
@click="updateSysExportTemplateFunc(scope.row)"
|
||||
>变更</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="delete"
|
||||
@click="deleteRow(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:before-close="closeDialog"
|
||||
:title="type==='create'?'添加':'修改'"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-scrollbar height="500px">
|
||||
<el-form
|
||||
ref="elFormRef"
|
||||
:model="formData"
|
||||
label-position="right"
|
||||
:rules="rule"
|
||||
label-width="100px"
|
||||
>
|
||||
|
||||
<el-form-item
|
||||
label="业务库"
|
||||
prop="dbName"
|
||||
>
|
||||
<template #label>
|
||||
<el-tooltip
|
||||
content="注:需要提前到db-list自行配置多数据库,如未配置需配置后重启服务方可使用。若无法选择,请到config.yaml中设置disabled:false,选择导入导出的目标库。"
|
||||
placement="bottom"
|
||||
effect="light"
|
||||
>
|
||||
<div> 业务库 <el-icon><QuestionFilled /></el-icon> </div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-select
|
||||
v-model="formData.dbName"
|
||||
clearable
|
||||
placeholder="选择业务库"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in dbList"
|
||||
:key="item.aliasName"
|
||||
:value="item.aliasName"
|
||||
:label="item.aliasName"
|
||||
:disabled="item.disable"
|
||||
>
|
||||
<div>
|
||||
<span>{{ item.aliasName }}</span>
|
||||
<span style="float:right;color:#8492a6;font-size:13px">{{ item.dbName }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
label="模板名称:"
|
||||
prop="name"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.name"
|
||||
:clearable="true"
|
||||
placeholder="请输入模板名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="表名称:"
|
||||
prop="tableName"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.tableName"
|
||||
:clearable="true"
|
||||
placeholder="请输入要导出的表名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="模板标识:"
|
||||
prop="templateID"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.templateID"
|
||||
:clearable="true"
|
||||
placeholder="模板标识为前端组件需要挂在的标识属性"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="模板信息:"
|
||||
prop="templateInfo"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.templateInfo"
|
||||
type="textarea"
|
||||
:rows="12"
|
||||
:clearable="true"
|
||||
:placeholder="templatePlaceholder"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="默认导出条数:"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="formData.limit"
|
||||
:step="1"
|
||||
:step-strictly="true"
|
||||
:precision="0"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="默认排序条件:"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.order"
|
||||
placeholder="例:id desc"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="导出条件:"
|
||||
>
|
||||
<div
|
||||
v-for="(condition,key) in formData.conditions"
|
||||
:key="key"
|
||||
class="flex gap-4 w-full mb-2"
|
||||
>
|
||||
<el-input
|
||||
v-model="condition.from"
|
||||
placeholder="需要从查询条件取的json key"
|
||||
/>
|
||||
<el-input
|
||||
v-model="condition.column"
|
||||
placeholder="表对应的column"
|
||||
/>
|
||||
<el-select
|
||||
v-model="condition.operator"
|
||||
placeholder="请选择查询条件"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in typeSearchOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="delete"
|
||||
@click="() => formData.conditions.splice(key, 1)"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
<div class="flex justify-end w-full">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="addCondition"
|
||||
>添加条件</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="enterDialog"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createSysExportTemplate,
|
||||
deleteSysExportTemplate,
|
||||
deleteSysExportTemplateByIds,
|
||||
updateSysExportTemplate,
|
||||
findSysExportTemplate,
|
||||
getSysExportTemplateList
|
||||
} from '@/api/exportTemplate.js'
|
||||
|
||||
// 全量引入格式化工具 请按需保留
|
||||
import { formatDate } from '@/utils/format'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { getDB } from '@/api/autoCode'
|
||||
|
||||
defineOptions({
|
||||
name: 'ExportTemplate'
|
||||
})
|
||||
|
||||
const templatePlaceholder = `模板信息格式:key标识数据库column列名称,value标识导出excel列名称,如下:
|
||||
{
|
||||
"table_name1":"第一列",
|
||||
"table_name2":"第二列",
|
||||
"table_name3":"第三列",
|
||||
"table_name4":"第四列",
|
||||
}
|
||||
`
|
||||
|
||||
// 自动化生成的字典(可能为空)以及字段
|
||||
const formData = ref({
|
||||
name: '',
|
||||
tableName: '',
|
||||
templateID: '',
|
||||
templateInfo: '',
|
||||
limit: 0,
|
||||
order: '',
|
||||
conditions: [],
|
||||
})
|
||||
|
||||
const typeSearchOptions = ref([
|
||||
{
|
||||
label: '=',
|
||||
value: '='
|
||||
},
|
||||
{
|
||||
label: '<>',
|
||||
value: '<>'
|
||||
},
|
||||
{
|
||||
label: '>',
|
||||
value: '>'
|
||||
},
|
||||
{
|
||||
label: '<',
|
||||
value: '<'
|
||||
},
|
||||
{
|
||||
label: 'LIKE',
|
||||
value: 'LIKE'
|
||||
},
|
||||
{
|
||||
label: 'BETWEEN',
|
||||
value: 'BETWEEN'
|
||||
},
|
||||
{
|
||||
label: 'NOT BETWEEN',
|
||||
value: 'NOT BETWEEN'
|
||||
}
|
||||
])
|
||||
|
||||
const addCondition = () => {
|
||||
formData.value.conditions.push({
|
||||
from: '',
|
||||
column: '',
|
||||
operator: ''
|
||||
})
|
||||
}
|
||||
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
name: [{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: ['input', 'blur'],
|
||||
},
|
||||
{
|
||||
whitespace: true,
|
||||
message: '不能只输入空格',
|
||||
trigger: ['input', 'blur'],
|
||||
}
|
||||
],
|
||||
tableName: [{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: ['input', 'blur'],
|
||||
},
|
||||
{
|
||||
whitespace: true,
|
||||
message: '不能只输入空格',
|
||||
trigger: ['input', 'blur'],
|
||||
}
|
||||
],
|
||||
templateID: [{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: ['input', 'blur'],
|
||||
},
|
||||
{
|
||||
whitespace: true,
|
||||
message: '不能只输入空格',
|
||||
trigger: ['input', 'blur'],
|
||||
}
|
||||
],
|
||||
templateInfo: [{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: ['input', 'blur'],
|
||||
},
|
||||
{
|
||||
whitespace: true,
|
||||
message: '不能只输入空格',
|
||||
trigger: ['input', 'blur'],
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
const searchRule = reactive({
|
||||
createdAt: [
|
||||
{ validator: (rule, value, callback) => {
|
||||
if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写结束日期'))
|
||||
} else if (!searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写开始日期'))
|
||||
} else if (searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt && (searchInfo.value.startCreatedAt.getTime() === searchInfo.value.endCreatedAt.getTime() || searchInfo.value.startCreatedAt.getTime() > searchInfo.value.endCreatedAt.getTime())) {
|
||||
callback(new Error('开始日期应当早于结束日期'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: 'change' }
|
||||
],
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
const elSearchFormRef = ref()
|
||||
|
||||
// =========== 表格控制部分 ===========
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
const dbList = ref([])
|
||||
|
||||
const getDbFunc = async() => {
|
||||
const res = await getDB()
|
||||
if (res.code === 0) {
|
||||
dbList.value = res.data.dbList
|
||||
}
|
||||
}
|
||||
|
||||
getDbFunc()
|
||||
|
||||
// 重置
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const onSubmit = () => {
|
||||
elSearchFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
getTableData()
|
||||
})
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 修改页面容量
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getSysExportTemplateList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
// ============== 表格控制部分结束 ===============
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
const setOptions = async() => {
|
||||
}
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
setOptions()
|
||||
|
||||
// 多选数据
|
||||
const multipleSelection = ref([])
|
||||
// 多选
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteRow = (row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteSysExportTemplateFunc(row)
|
||||
})
|
||||
}
|
||||
|
||||
// 多选删除
|
||||
const onDelete = async() => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const ids = []
|
||||
if (multipleSelection.value.length === 0) {
|
||||
ElMessage({
|
||||
type: 'warning',
|
||||
message: '请选择要删除的数据'
|
||||
})
|
||||
return
|
||||
}
|
||||
multipleSelection.value &&
|
||||
multipleSelection.value.map(item => {
|
||||
ids.push(item.ID)
|
||||
})
|
||||
const res = await deleteSysExportTemplateByIds({ ids })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === ids.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 行为控制标记(弹窗内部需要增还是改)
|
||||
const type = ref('')
|
||||
|
||||
// 更新行
|
||||
const updateSysExportTemplateFunc = async(row) => {
|
||||
const res = await findSysExportTemplate({ ID: row.ID })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.resysExportTemplate
|
||||
if (!formData.value.conditions) {
|
||||
formData.value.conditions = []
|
||||
}
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteSysExportTemplateFunc = async(row) => {
|
||||
const res = await deleteSysExportTemplate({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗控制标记
|
||||
const dialogFormVisible = ref(false)
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
name: '',
|
||||
tableName: '',
|
||||
templateID: '',
|
||||
templateInfo: '',
|
||||
limit: 0,
|
||||
order: '',
|
||||
conditions: [],
|
||||
}
|
||||
}
|
||||
// 弹窗确定
|
||||
const enterDialog = async() => {
|
||||
// 判断 formData.templateInfo 是否为标准json格式 如果不是标准json 则辅助调整
|
||||
try {
|
||||
JSON.parse(formData.value.templateInfo)
|
||||
} catch (error) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '模板信息格式不正确,请检查'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const reqData = JSON.parse(JSON.stringify(formData.value))
|
||||
for (let i = 0; i < reqData.conditions.length; i++) {
|
||||
if (!reqData.conditions[i].from || !reqData.conditions[i].column || !reqData.conditions[i].operator) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '请填写完整的导出条件'
|
||||
})
|
||||
return
|
||||
}
|
||||
reqData.conditions[i].templateID = reqData.templateID
|
||||
}
|
||||
|
||||
elFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createSysExportTemplate(reqData)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateSysExportTemplate(reqData)
|
||||
break
|
||||
default:
|
||||
res = await createSysExportTemplate(reqData)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<div style="height:80vh">
|
||||
<iframe
|
||||
width="100%"
|
||||
height="100%"
|
||||
:src="`${basePath}:${basePort}/form-generator/#/`"
|
||||
frameborder="0"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
const basePath = ref(import.meta.env.VITE_BASE_PATH)
|
||||
const basePort = ref(import.meta.env.VITE_SERVER_PORT)
|
||||
defineOptions({
|
||||
name: 'FormGenerator'
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<keep-alive :include="routerStore.keepAliveRouters">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
const routerStore = useRouterStore()
|
||||
|
||||
defineOptions({
|
||||
name: 'System'
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-upload
|
||||
drag
|
||||
:action="`${path}/autoCode/installPlugin`"
|
||||
:show-file-list="false"
|
||||
:on-success="handleSuccess"
|
||||
:on-error="handleSuccess"
|
||||
name="plug"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
拖拽或<em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
请把安装包的zip拖拽至此处上传
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { ElMessage } from 'element-plus'
|
||||
const userStore = useUserStore()
|
||||
const path = ref(import.meta.env.VITE_BASE_API)
|
||||
|
||||
const handleSuccess = (res) => {
|
||||
if (res.code === 0) {
|
||||
let msg = ``
|
||||
res.data && res.data.forEach((item, index) => {
|
||||
msg += `${index + 1}.${item.msg}\n`
|
||||
})
|
||||
alert(msg)
|
||||
} else {
|
||||
ElMessage.error(res.msg)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div class="p-5 bg-white">
|
||||
<WarningBar title="目前只支持标准插件(通过插件模板生成的标准目录插件),非标准插件请自行打包" />
|
||||
<div class="flex items-center gap-3">
|
||||
<el-input
|
||||
v-model="plugName"
|
||||
placeholder="插件模板处填写的【插件名】"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="pubPlugin"
|
||||
>打包插件</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { pubPlug } from '@/api/autoCode.js'
|
||||
import { ElMessage } from 'element-plus'
|
||||
const plugName = ref('')
|
||||
|
||||
const pubPlugin = async() => {
|
||||
const res = await pubPlug({ plugName: plugName.value })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success(res.msg)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
|
@ -0,0 +1,557 @@
|
|||
<template>
|
||||
<div class="system">
|
||||
<el-form
|
||||
ref="form"
|
||||
:model="config"
|
||||
label-width="240px"
|
||||
>
|
||||
<!-- System start -->
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item
|
||||
title="系统配置"
|
||||
name="1"
|
||||
>
|
||||
<el-form-item label="端口值">
|
||||
<el-input v-model.number="config.system.addr" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库类型">
|
||||
<el-select
|
||||
v-model="config.system['db-type']"
|
||||
style="width:100%"
|
||||
>
|
||||
<el-option value="mysql" />
|
||||
<el-option value="pgsql" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Oss类型">
|
||||
<el-select
|
||||
v-model="config.system['oss-type']"
|
||||
style="width:100%"
|
||||
>
|
||||
<el-option value="local" />
|
||||
<el-option value="qiniu" />
|
||||
<el-option value="tencent-cos" />
|
||||
<el-option value="aliyun-oss" />
|
||||
<el-option value="huawei-obs" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="多点登录拦截">
|
||||
<el-checkbox v-model="config.system['use-multipoint']">开启</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item label="开启redis">
|
||||
<el-checkbox v-model="config.system['use-redis']">开启</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item label="限流次数">
|
||||
<el-input-number v-model.number="config.system['iplimit-count']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="限流时间">
|
||||
<el-input-number v-model.number="config.system['iplimit-time']" />
|
||||
</el-form-item>
|
||||
<el-tooltip
|
||||
content="请修改完成后,注意一并修改前端env环境下的VITE_BASE_PATH"
|
||||
placement="top-start"
|
||||
>
|
||||
<el-form-item label="全局路由前缀">
|
||||
<el-input v-model="config.system['router-prefix']" />
|
||||
</el-form-item>
|
||||
</el-tooltip>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item
|
||||
title="jwt签名"
|
||||
name="2"
|
||||
>
|
||||
<el-form-item label="jwt签名">
|
||||
<el-input v-model="config.jwt['signing-key']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="有效期">
|
||||
<el-input v-model="config.jwt['expires-time']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="缓冲期">
|
||||
<el-input v-model="config.jwt['buffer-time']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="签发者">
|
||||
<el-input v-model="config.jwt.issuer" />
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item
|
||||
title="Zap日志配置"
|
||||
name="3"
|
||||
>
|
||||
<el-form-item label="级别">
|
||||
<el-input v-model.number="config.zap.level" />
|
||||
</el-form-item>
|
||||
<el-form-item label="输出">
|
||||
<el-input v-model="config.zap.format" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日志前缀">
|
||||
<el-input v-model="config.zap.prefix" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日志文件夹">
|
||||
<el-input v-model="config.zap.director" />
|
||||
</el-form-item>
|
||||
<el-form-item label="编码级">
|
||||
<el-input v-model="config.zap['encode-level']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="栈名">
|
||||
<el-input v-model="config.zap['stacktrace-key']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日志留存时间(默认以天为单位)">
|
||||
<el-input v-model.number="config.zap['max-age']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="显示行">
|
||||
<el-checkbox v-model="config.zap['show-line']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="输出控制台">
|
||||
<el-checkbox v-model="config.zap['log-in-console']" />
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item
|
||||
title="Redis admin数据库配置"
|
||||
name="4"
|
||||
>
|
||||
<el-form-item label="库">
|
||||
<el-input v-model.number="config.redis.db" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址">
|
||||
<el-input v-model="config.redis.addr" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="config.redis.password" />
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item
|
||||
title="Mongo 数据库配置"
|
||||
name="14"
|
||||
>
|
||||
<el-form-item label="collection name(表名,一般不写)">
|
||||
<el-input v-model="config.mongo.coll" />
|
||||
</el-form-item>
|
||||
<el-form-item label="mongodb options">
|
||||
<el-input v-model="config.mongo.options" />
|
||||
</el-form-item>
|
||||
<el-form-item label="database name(数据库名)">
|
||||
<el-input v-model="config.mongo.database" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="config.mongo.username" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="config.mongo.password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最小连接池">
|
||||
<el-input v-model="config.mongo['min-pool-size']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最大连接池">
|
||||
<el-input v-model="config.mongo['max-pool-size']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="socket超时时间">
|
||||
<el-input v-model="config.mongo['socket-timeout-ms']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="连接超时时间">
|
||||
<el-input v-model="config.mongo['socket-timeout-ms']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否开启zap日志">
|
||||
<el-checkbox v-model="config.mongo['is-zap']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="hosts">
|
||||
<template v-for="(item,k) in config.mongo.hosts">
|
||||
<div
|
||||
v-for="(_,k2) in item"
|
||||
:key="k2"
|
||||
>
|
||||
<el-form-item
|
||||
:key="k+k2"
|
||||
:label="k2"
|
||||
>
|
||||
<el-input v-model="item[k2]" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item
|
||||
title="邮箱配置"
|
||||
name="5"
|
||||
>
|
||||
<el-form-item label="接收者邮箱">
|
||||
<el-input
|
||||
v-model="config.email.to"
|
||||
placeholder="可多个,以逗号分隔"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="端口">
|
||||
<el-input v-model.number="config.email.port" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发送者邮箱">
|
||||
<el-input v-model="config.email.from" />
|
||||
</el-form-item>
|
||||
<el-form-item label="host">
|
||||
<el-input v-model="config.email.host" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否为ssl">
|
||||
<el-checkbox v-model="config.email['is-ssl']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="secret">
|
||||
<el-input v-model="config.email.secret" />
|
||||
</el-form-item>
|
||||
<el-form-item label="测试邮件">
|
||||
<el-button @click="email">测试邮件</el-button>
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item
|
||||
title="验证码配置"
|
||||
name="7"
|
||||
>
|
||||
<el-form-item label="字符长度">
|
||||
<el-input v-model.number="config.captcha['key-long']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图片宽度">
|
||||
<el-input v-model.number="config.captcha['img-width']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图片高度">
|
||||
<el-input v-model.number="config.captcha['img-height']" />
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item
|
||||
title="数据库配置"
|
||||
name="9"
|
||||
>
|
||||
<template v-if="config.system['db-type'] === 'mysql'">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="config.mysql.username" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="config.mysql.password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址">
|
||||
<el-input v-model="config.mysql.path" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库">
|
||||
<el-input v-model="config.mysql['db-name']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="前缀">
|
||||
<el-input v-model="config.mysql['refix']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="复数表">
|
||||
<el-switch v-model="config.mysql['singular']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="引擎">
|
||||
<el-input v-model="config.mysql['engine']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="maxIdleConns">
|
||||
<el-input v-model.number="config.mysql['max-idle-conns']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="maxOpenConns">
|
||||
<el-input v-model.number="config.mysql['max-open-conns']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="写入日志">
|
||||
<el-checkbox v-model="config.mysql['log-zap']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日志模式">
|
||||
<el-input v-model="config.mysql['log-mode']" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="config.system['db-type'] === 'pgsql'">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="config.pgsql.username" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="config.pgsql.password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址">
|
||||
<el-input v-model="config.pgsql.path" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库">
|
||||
<el-input v-model="config.pgsql.dbname" />
|
||||
</el-form-item>
|
||||
<el-form-item label="前缀">
|
||||
<el-input v-model="config.pgsql['refix']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="复数表">
|
||||
<el-switch v-model="config.pgsql['singular']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="引擎">
|
||||
<el-input v-model="config.pgsql['engine']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="maxIdleConns">
|
||||
<el-input v-model.number="config.pgsql['max-idle-conns']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="maxOpenConns">
|
||||
<el-input v-model.number="config.pgsql['max-open-conns']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="写入日志">
|
||||
<el-checkbox v-model="config.pgsql['log-zap']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日志模式">
|
||||
<el-input v-model="config.pgsql['log-mode']" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item
|
||||
title="oss配置"
|
||||
name="10"
|
||||
>
|
||||
<template v-if="config.system['oss-type'] === 'local'">
|
||||
<h2>本地文件配置</h2>
|
||||
<el-form-item label="本地文件访问路径">
|
||||
<el-input v-model="config.local.path" />
|
||||
</el-form-item>
|
||||
<el-form-item label="本地文件存储路径">
|
||||
<el-input v-model="config.local['store-path']" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="config.system['oss-type'] === 'qiniu'">
|
||||
<h2>qiniu上传配置</h2>
|
||||
<el-form-item label="存储区域">
|
||||
<el-input v-model="config.qiniu.zone" />
|
||||
</el-form-item>
|
||||
<el-form-item label="空间名称">
|
||||
<el-input v-model="config.qiniu.bucket" />
|
||||
</el-form-item>
|
||||
<el-form-item label="CDN加速域名">
|
||||
<el-input v-model="config.qiniu['img-path']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否使用https">
|
||||
<el-checkbox v-model="config.qiniu['use-https']">开启</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item label="accessKey">
|
||||
<el-input v-model="config.qiniu['access-key']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="secretKey">
|
||||
<el-input v-model="config.qiniu['secret-key']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="上传是否使用CDN上传加速">
|
||||
<el-checkbox v-model="config.qiniu['use-cdn-domains']">开启</el-checkbox>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="config.system['oss-type'] === 'tencent-cos'">
|
||||
<h2>腾讯云COS上传配置</h2>
|
||||
<el-form-item label="存储桶名称">
|
||||
<el-input v-model="config['tencent-cos']['bucket']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属地域">
|
||||
<el-input v-model="config['tencent-cos'].region" />
|
||||
</el-form-item>
|
||||
<el-form-item label="secretID">
|
||||
<el-input v-model="config['tencent-cos']['secret-id']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="secretKey">
|
||||
<el-input v-model="config['tencent-cos']['secret-key']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="路径前缀">
|
||||
<el-input v-model="config['tencent-cos']['path-prefix']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="访问域名">
|
||||
<el-input v-model="config['tencent-cos']['base-url']" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="config.system['oss-type'] === 'aliyun-oss'">
|
||||
<h2>阿里云OSS上传配置</h2>
|
||||
<el-form-item label="区域">
|
||||
<el-input v-model="config['aliyun-oss'].endpoint" />
|
||||
</el-form-item>
|
||||
<el-form-item label="accessKeyId">
|
||||
<el-input v-model="config['aliyun-oss']['access-key-id']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="accessKeySecret">
|
||||
<el-input v-model="config['aliyun-oss']['access-key-secret']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="存储桶名称">
|
||||
<el-input v-model="config['aliyun-oss']['bucket-name']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="访问域名">
|
||||
<el-input v-model="config['aliyun-oss']['bucket-url']" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="config.system['oss-type'] === 'huawei-obs'">
|
||||
<h2>华为云Obs上传配置</h2>
|
||||
<el-form-item label="路径">
|
||||
<el-input v-model="config['hua-wei-obs'].path" />
|
||||
</el-form-item>
|
||||
<el-form-item label="存储桶名称">
|
||||
<el-input v-model="config['hua-wei-obs'].bucket" />
|
||||
</el-form-item>
|
||||
<el-form-item label="区域">
|
||||
<el-input v-model="config['hua-wei-obs'].endpoint" />
|
||||
</el-form-item>
|
||||
<el-form-item label="accessKey">
|
||||
<el-input v-model="config['hua-wei-obs']['access-key']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="secretKey">
|
||||
<el-input v-model="config['hua-wei-obs']['secret-key']" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item
|
||||
title="Excel上传配置"
|
||||
name="11"
|
||||
>
|
||||
<el-form-item label="合成目标地址">
|
||||
<el-input v-model="config.excel.dir" />
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item
|
||||
title="自动化代码配置"
|
||||
name="12"
|
||||
>
|
||||
<el-form-item label="是否自动重启(linux)">
|
||||
<el-checkbox v-model="config.autocode['transfer-restart']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="root(项目根路径)">
|
||||
<el-input
|
||||
v-model="config.autocode.root"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Server(后端代码地址)">
|
||||
<el-input v-model="config.autocode['transfer-restart']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SApi(后端api文件夹地址)">
|
||||
<el-input v-model="config.autocode['server-api']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SInitialize(后端Initialize文件夹)">
|
||||
<el-input v-model="config.autocode['server-initialize']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SModel(后端Model文件地址)">
|
||||
<el-input v-model="config.autocode['server-model']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SRequest(后端Request文件夹地址)">
|
||||
<el-input v-model="config.autocode['server-request']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SRouter(后端Router文件夹地址)">
|
||||
<el-input v-model="config.autocode['server-router']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SService(后端Service文件夹地址)">
|
||||
<el-input v-model="config.autocode['server-service']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Web(前端文件夹地址)">
|
||||
<el-input v-model="config.autocode.web" />
|
||||
</el-form-item>
|
||||
<el-form-item label="WApi(后端WApi文件夹地址)">
|
||||
<el-input v-model="config.autocode['web-api']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="WForm(后端WForm文件夹地址)">
|
||||
<el-input v-model="config.autocode['web-form']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="WTable(后端WTable文件夹地址)">
|
||||
<el-input v-model="config.autocode['web-table']" />
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-form>
|
||||
<div class="mt-4">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="update"
|
||||
>立即更新</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="reload"
|
||||
>重启服务(开发中)</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getSystemConfig, setSystemConfig } from '@/api/system'
|
||||
import { emailTest } from '@/api/email'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
defineOptions({
|
||||
name: 'Config'
|
||||
})
|
||||
|
||||
const activeNames = reactive([])
|
||||
const config = ref({
|
||||
system: {
|
||||
'iplimit-count': 0,
|
||||
'iplimit-time': 0
|
||||
},
|
||||
jwt: {},
|
||||
mysql: {},
|
||||
pgsql: {},
|
||||
excel: {},
|
||||
autocode: {},
|
||||
redis: {},
|
||||
mongo: {
|
||||
coll: '',
|
||||
options: '',
|
||||
database: '',
|
||||
username: '',
|
||||
password: '',
|
||||
'min-pool-size': '',
|
||||
'max-pool-size': '',
|
||||
'socket-timeout-ms': '',
|
||||
'connect-timeout-ms': '',
|
||||
'is-zap': '',
|
||||
hosts: [
|
||||
{
|
||||
host: '',
|
||||
port: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
qiniu: {},
|
||||
'tencent-cos': {},
|
||||
'aliyun-oss': {},
|
||||
'hua-wei-obs': {},
|
||||
captcha: {},
|
||||
zap: {},
|
||||
local: {},
|
||||
email: {},
|
||||
timer: {
|
||||
detail: {}
|
||||
}
|
||||
})
|
||||
|
||||
const initForm = async() => {
|
||||
const res = await getSystemConfig()
|
||||
if (res.code === 0) {
|
||||
config.value = res.data.config
|
||||
}
|
||||
}
|
||||
initForm()
|
||||
const reload = () => {}
|
||||
const update = async() => {
|
||||
const res = await setSystemConfig({ config: config.value })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '配置文件设置成功'
|
||||
})
|
||||
await initForm()
|
||||
}
|
||||
}
|
||||
const email = async() => {
|
||||
const res = await emailTest()
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '邮件发送成功'
|
||||
})
|
||||
await initForm()
|
||||
} else {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '邮件发送失败'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.system {
|
||||
@apply bg-white p-9 rounded;
|
||||
h2 {
|
||||
@apply p-2.5 my-2.5 text-lg shadow;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
const child_process = require('child_process')
|
||||
|
||||
export default function GvaPositionServer() {
|
||||
return {
|
||||
name: 'gva-position-server',
|
||||
apply: 'serve',
|
||||
configureServer(server) {
|
||||
server.middlewares.use((req, _, next) => {
|
||||
if (req._parsedUrl.pathname === '/gvaPositionCode') {
|
||||
const path =
|
||||
req._parsedUrl.query && req._parsedUrl.query.split('=')[1]
|
||||
if (path && path !== 'null') {
|
||||
if (process.env.VITE_EDITOR === 'webstorm') {
|
||||
const linePath = path.split(':')[1]
|
||||
const filePath = path.split(':')[0]
|
||||
const platform = os()
|
||||
if (platform === 'win32') {
|
||||
child_process.exec(
|
||||
`webstorm64.exe --line ${linePath} ${filePath}`
|
||||
)
|
||||
} else {
|
||||
child_process.exec(
|
||||
`webstorm --line ${linePath} ${filePath}`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
child_process.exec('code -r -g ' + path)
|
||||
}
|
||||
}
|
||||
}
|
||||
next()
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function os() {
|
||||
'use strict'
|
||||
const os = require('os')
|
||||
const platform = os.platform()
|
||||
return platform
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import * as path from 'path'
|
||||
export default function fullImportPlugin() {
|
||||
let config
|
||||
return {
|
||||
name: 'fullImportElementPlus',
|
||||
async configResolved(conf) {
|
||||
config = conf
|
||||
},
|
||||
transform(code, id) {
|
||||
const sourcePath = path.join(config.root, 'src/main.js').split(path.sep).join('/')
|
||||
const targetPath = id.split(path.sep).join('/')
|
||||
if (sourcePath === targetPath) {
|
||||
const name = 'ElementPlus'
|
||||
// 引入 ElementPlus 和 样式
|
||||
code = code.replace(`import { createApp } from 'vue'`, ($1) => $1 + `\nimport ${name} from 'element-plus'`)
|
||||
code = code.replace(`import './style/element_visiable.scss'`, ($1) => $1 + `\nimport 'element-plus/theme-chalk/src/index.scss'`)
|
||||
code = code.replace('.mount(', ($1) => `.use(${name})` + $1)
|
||||
return code
|
||||
}
|
||||
return code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
export default function GvaPosition() {
|
||||
return {
|
||||
name: 'gva-position',
|
||||
apply: 'serve',
|
||||
transform(code, id) {
|
||||
const index = id.lastIndexOf('.')
|
||||
const ext = id.substr(index + 1)
|
||||
if (ext.toLowerCase() === 'vue') {
|
||||
return codeLineTrack(code, id)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const codeLineTrack = (code, id) => {
|
||||
const lineList = code.split('\n')
|
||||
const newList = []
|
||||
lineList.forEach((item, index) => {
|
||||
newList.push(addLineAttr(item, index + 1, id)) // 添加位置属性,index+1为具体的代码行号
|
||||
})
|
||||
return newList.join('\n')
|
||||
}
|
||||
|
||||
const addLineAttr = (lineStr, line, id) => {
|
||||
if (!/^\s+</.test(lineStr)) {
|
||||
return lineStr
|
||||
}
|
||||
|
||||
const reg = /((((^(\s)+\<))|(^\<))[\w-]+)|(<\/template)/g
|
||||
let leftTagList = lineStr.match(reg)
|
||||
if (leftTagList) {
|
||||
leftTagList = Array.from(new Set(leftTagList))
|
||||
leftTagList.forEach((item) => {
|
||||
const skip = [
|
||||
'KeepAlive',
|
||||
'template',
|
||||
'keep-alive',
|
||||
'transition',
|
||||
'el-',
|
||||
'El',
|
||||
'router-view',
|
||||
]
|
||||
if (item && !skip.some((i) => item.indexOf(i) > -1)) {
|
||||
const reg = new RegExp(`${item}`)
|
||||
const location = `${item} code-location="${id}:${line}"`
|
||||
lineStr = lineStr.replace(reg, location)
|
||||
}
|
||||
})
|
||||
}
|
||||
return lineStr
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import { readFileSync, readdirSync } from 'fs'
|
||||
const svgTitle = /<svg([^>+].*?)>/
|
||||
const clearHeightWidth = /(width|height)="([^>+].*?)"/g
|
||||
const hasViewBox = /(viewBox="[^>+].*?")/g
|
||||
const clearReturn = /(\r)|(\n)/g
|
||||
function findSvgFile(dir) {
|
||||
const svgRes = []
|
||||
const dirents = readdirSync(dir, {
|
||||
withFileTypes: true
|
||||
})
|
||||
for (const dirent of dirents) {
|
||||
if (dirent.isDirectory()) {
|
||||
svgRes.push(...findSvgFile(dir + dirent.name + '/'))
|
||||
} else {
|
||||
const svg = readFileSync(dir + dirent.name)
|
||||
.toString()
|
||||
.replace(clearReturn, '')
|
||||
.replace(svgTitle, ($1, $2) => {
|
||||
let width = 0
|
||||
let height = 0
|
||||
let content = $2.replace(clearHeightWidth, (s1, s2, s3) => {
|
||||
if (s2 === 'width') {
|
||||
width = s3
|
||||
} else if (s2 === 'height') {
|
||||
height = s3
|
||||
}
|
||||
return ''
|
||||
})
|
||||
if (!hasViewBox.test($2)) {
|
||||
content += `viewBox="0 0 ${width} ${height}"`
|
||||
}
|
||||
return `<symbol id="${dirent.name.replace('.svg', '')}" ${content}>`
|
||||
})
|
||||
.replace('</svg>', '</symbol>')
|
||||
svgRes.push(svg)
|
||||
}
|
||||
}
|
||||
return svgRes
|
||||
}
|
||||
export const svgBuilder = (path) => {
|
||||
if (path === '') return
|
||||
const res = findSvgFile(path)
|
||||
return {
|
||||
name: 'svg-transform',
|
||||
transformIndexHtml(html) {
|
||||
return html.replace(
|
||||
'<body>',
|
||||
`
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
|
||||
${res.join('')}
|
||||
</svg>
|
||||
`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue