This commit is contained in:
jacky 2024-04-08 21:37:21 +08:00
parent 83758dfee9
commit e350dbe696
49 changed files with 13449 additions and 0 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

186
src/view/login/index.vue Normal file
View File

@ -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>

507
src/view/person/person.vue Normal file
View File

@ -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>

437
src/view/product/product.vue Executable file
View File

@ -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>

111
src/view/product/productForm.vue Executable file
View File

@ -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 () => {
// urlID find createupdate idurl
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>

View File

@ -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>

View File

@ -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 () => {
// urlID find createupdate idurl
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>

View File

@ -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>

View File

@ -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 () => {
// urlID find createupdate idurl
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>

View File

@ -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>

View File

@ -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 () => {
// urlID find createupdate idurl
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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

237
src/view/system/state.vue Normal file
View File

@ -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>

View File

@ -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>

View File

@ -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 () => {
// urlID find createupdate idurl
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>

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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>
`
)
}
}
}