菜单管理

This commit is contained in:
zhang zhuo 2025-06-23 14:08:00 +08:00
parent f3b06f0a2a
commit d5789311da
10 changed files with 115 additions and 11 deletions

View File

@ -17,5 +17,8 @@ export default {
option: async function(data={}){
return await http.get("menu/option", data);
},
quick: async function(data = {}){
return await http.post("menu/quick", data);
},
}
}

View File

@ -0,0 +1,3 @@
<template>
<svg t="1715153887933" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10406" width="256" height="256"><path d="M924.8 385.6c-22.6-53.4-54.9-101.3-96-142.4-41.1-41.1-89-73.4-142.4-96C631.1 123.8 572.5 112 512 112s-119.1 11.8-174.4 35.2c-53.4 22.6-101.3 54.9-142.4 96-41.1 41.1-73.4 89-96 142.4C75.8 440.9 64 499.5 64 560c0 132.7 58.3 257.7 159.9 343.1l1.7 1.4c5.8 4.8 13.1 7.5 20.6 7.5h531.7c7.5 0 14.8-2.7 20.6-7.5l1.7-1.4C901.7 817.7 960 692.7 960 560c0-60.5-11.9-119.1-35.2-174.4zM761.4 836H262.6C184.5 765.5 140 665.6 140 560c0-99.4 38.7-192.8 109-263 70.3-70.3 163.7-109 263-109 99.4 0 192.8 38.7 263 109 70.3 70.3 109 163.7 109 263 0 105.6-44.5 205.5-122.6 276z" p-id="10407"></path><path d="M623.5 421.5c-3.1-3.1-8.2-3.1-11.3 0L527.7 506c-18.7-5-39.4-0.2-54.1 14.5-21.9 21.9-21.9 57.3 0 79.2 21.9 21.9 57.3 21.9 79.2 0 14.7-14.7 19.5-35.4 14.5-54.1l84.5-84.5c3.1-3.1 3.1-8.2 0-11.3l-28.3-28.3zM490 320h44c4.4 0 8-3.6 8-8v-80c0-4.4-3.6-8-8-8h-44c-4.4 0-8 3.6-8 8v80c0 4.4 3.6 8 8 8z m260 218v44c0 4.4 3.6 8 8 8h80c4.4 0 8-3.6 8-8v-44c0-4.4-3.6-8-8-8h-80c-4.4 0-8 3.6-8 8z m12.7-197.2l-31.1-31.1c-3.1-3.1-8.2-3.1-11.3 0l-56.6 56.6c-3.1 3.1-3.1 8.2 0 11.3l31.1 31.1c3.1 3.1 8.2 3.1 11.3 0l56.6-56.6c3.1-3.1 3.1-8.2 0-11.3z m-458.6-31.1c-3.1-3.1-8.2-3.1-11.3 0l-31.1 31.1c-3.1 3.1-3.1 8.2 0 11.3l56.6 56.6c3.1 3.1 8.2 3.1 11.3 0l31.1-31.1c3.1-3.1 3.1-8.2 0-11.3l-56.6-56.6zM262 530h-80c-4.4 0-8 3.6-8 8v44c0 4.4 3.6 8 8 8h80c4.4 0 8-3.6 8-8v-44c0-4.4-3.6-8-8-8z" p-id="10408"></path></svg>
</template>

View File

@ -0,0 +1,3 @@
<template>
<svg t="1750647180512" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="974" width="256" height="256"><path d="M349.09 34.91H162.91a128 128 0 0 0-128 128v186.18a128 128 0 0 0 128 128h186.18a128 128 0 0 0 128-128V162.91a128 128 0 0 0-128-128z m-186.18 69.817h186.18c32.117 0 58.183 26.066 58.183 58.182v186.182c0 32.116-26.066 58.182-58.182 58.182H162.909c-32.116 0-58.182-26.066-58.182-58.182V162.909c0-32.116 26.066-58.182 58.182-58.182zM349.09 546.91H162.91a128 128 0 0 0-128 128v186.182a128 128 0 0 0 128 128h186.18a128 128 0 0 0 128-128V674.909a128 128 0 0 0-128-128z m-186.18 69.818h186.18c32.117 0 58.183 26.066 58.183 58.182v186.182c0 32.116-26.066 58.182-58.182 58.182H162.909c-32.116 0-58.182-26.066-58.182-58.182V674.909c0-32.116 26.066-58.182 58.182-58.182zM861.09 34.91H674.91a128 128 0 0 0-128 128v186.182a128 128 0 0 0 128 128h186.18a128 128 0 0 0 128-128V162.909a128 128 0 0 0-128-128z m-186.18 69.818h186.18c32.117 0 58.183 26.066 58.183 58.182v186.182c0 32.116-26.066 58.182-58.182 58.182H674.909c-32.116 0-58.182-26.066-58.182-58.182V162.909c0-32.116 26.066-58.182 58.182-58.182zM861.09 546.91H674.91a128 128 0 0 0-128 128v186.182a128 128 0 0 0 128 128h186.18a128 128 0 0 0 128-128V674.909a128 128 0 0 0-128-128z m-186.18 69.818h186.18c32.117 0 58.183 26.066 58.183 58.182v186.182c0 32.116-26.066 58.182-58.182 58.182H674.909c-32.116 0-58.182-26.066-58.182-58.182V674.909c0-32.116 26.066-58.182 58.182-58.182z" p-id="975"></path></svg>
</template>

View File

@ -0,0 +1,3 @@
<template>
<svg t="1750647881180" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8218" ><path d="M402.773788 1023.786668a38.058508 38.058508 0 0 1-28.885213-13.525277 35.583852 35.583852 0 0 1-7.338636-29.695877l64.938396-358.995837H146.34819a36.607847 36.607847 0 0 1-28.586547-59.434419L556.501148 13.657543a35.626518 35.626518 0 0 1 28.586547-13.653276 38.229174 38.229174 0 0 1 27.733218 12.159949 35.839851 35.839851 0 0 1 8.831963 27.733218l-33.066528 362.23849h288.980129a36.565181 36.565181 0 0 1 27.647885 60.501082l-475.304687 548.435048a35.626518 35.626518 0 0 1-27.135887 12.671947zM537.770559 154.456956l-315.262686 393.982359h252.88428a36.607847 36.607847 0 0 1 35.882517 43.178487l-49.194462 270.548206 335.486602-386.857055h-249.044296a36.522514 36.522514 0 0 1-36.56518-39.850501l25.599893-281.086829 0.213332 0.085333z" p-id="8219"></path></svg>
</template>

View File

@ -0,0 +1,3 @@
<template>
<svg t="1750647322351" class="icon" viewBox="0 0 1194 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="972" width="256" height="256"><path d="M455.111111 14.222222a270.222222 270.222222 0 1 0 0 540.444445 270.222222 270.222222 0 0 0 0-540.444445z m0 85.333334a184.888889 184.888889 0 1 1 0 369.777777 184.888889 184.888889 0 0 1 0-369.777777z" p-id="973"></path><path d="M739.555556 14.222222a270.222222 270.222222 0 0 1 12.344888 540.16L739.555556 554.666667v-85.333334a184.888889 184.888889 0 1 0-95.061334-343.495111l-10.467555 6.769778-48.753778-69.973333A269.084444 269.084444 0 0 1 739.555556 14.222222z" p-id="974"></path><path d="M455.111111 469.333333c244.849778 0 440.888889 224.028444 440.888889 497.777778a42.666667 42.666667 0 1 1-85.333333 0c0-228.977778-160.540444-412.444444-355.555556-412.444444S99.555556 738.133333 99.555556 967.111111a42.666667 42.666667 0 1 1-85.333334 0c0-273.749333 196.039111-497.777778 440.888889-497.777778z" p-id="975"></path><path d="M739.555556 469.333333c244.849778 0 440.888889 224.028444 440.888888 497.777778a42.666667 42.666667 0 1 1-85.333333 0c0-228.977778-160.540444-412.444444-355.555555-412.444444a42.666667 42.666667 0 1 1 0-85.333334z" fill="#333333" p-id="976"></path></svg>
</template>

View File

@ -6,8 +6,8 @@
<el-menu-item v-if="!hasChildren(navMenu)" :index="navMenu.path">
<a v-if="navMenu.meta&&navMenu.meta.type=='link'" :href="navMenu.path" target="_blank"
@click.stop='()=>{}'></a>
<el-icon v-if="navMenu.meta&&navMenu.meta.icon">
<component :is="navMenu.meta.icon || 'el-icon-menu'"/>
<el-icon v-if="navMenu.meta&&navMenu.meta?.icon">
<component :is="navMenu.meta?.icon || 'el-icon-menu'"/>
</el-icon>
<template #title>
<span>{{ navMenu.meta.title }}</span>
@ -16,8 +16,8 @@
</el-menu-item>
<el-sub-menu v-else :index="navMenu.path">
<template #title>
<el-icon v-if="navMenu.meta&&navMenu.meta.icon">
<component :is="navMenu.meta.icon || 'el-icon-menu'"/>
<el-icon v-if="navMenu.meta&&navMenu.meta?.icon">
<component :is="navMenu.meta?.icon || 'el-icon-menu'"/>
</el-icon>
<span>{{ navMenu.meta.title }}</span>
<span v-if="navMenu.meta.tag" class="menu-tag">{{ navMenu.meta.tag }}</span>

View File

@ -11,7 +11,7 @@
<li v-for="item in menu" :key="item" :class="pmenu.path==item.path?'active':''"
@click="showMenu(item)">
<el-icon>
<component :is="item.meta.icon || 'el-icon-menu'"/>
<component :is="item.meta?.icon || 'el-icon-menu'"/>
</el-icon>
<span>{{ item.meta.title }}</span>
</li>
@ -185,7 +185,7 @@
<li v-for="item in menu" :key="item" :class="pmenu.path==item.path?'active':''"
@click="showMenu(item)">
<el-icon>
<component :is="item.meta.icon || el-icon-menu"/>
<component :is="item.meta?.icon || 'el-icon-menu'"/>
</el-icon>
<p>{{ item.meta.title }}</p>
</li>

View File

@ -32,10 +32,15 @@ axios.interceptors.response.use((response) => {
} else if (res.code == 2) { // 权限不足拦截
ElNotification.error({title: '权限不足', message: res.msg});
return Promise.reject(res)
} else { // 登录失效拦截
} else if (res.code == 3) { // 登录失效拦截
ElNotification.error({title: '登录失效', message: res.msg});
router.replace({path: '/login'}).then(r => {
});
}else {
ElNotification.error({
title: '请求错误',
message: `Status:${response.status},未知错误!`
});
}
}, (error) => {
if (error.response) {

View File

@ -6,9 +6,9 @@
<el-input placeholder="输入关键字进行过滤" v-model="menuFilterText" clearable></el-input>
</el-header>
<el-main class="nopadding">
<el-tree ref="menuRef" class="menu" node-key="id" :data="menuList" :props="menuProps"
<el-tree ref="menuRef" class="menu" node-key="menu_id" :data="menuList" :props="menuProps"
highlight-current :expand-on-click-node="false" check-strictly show-checkbox
:filter-node-method="menuFilterNode" @node-click="menuClick">
:filter-node-method="menuFilterNode" @node-click="menuClick" :check-on-click-leaf="false">
<template #default="{node, data}">
<span class="custom-tree-node el-tree-node__label">
<span class="label">
@ -22,13 +22,14 @@
</span>
<span class="do">
<el-icon @click.stop="add(node, data)"><el-icon-plus/></el-icon>
<el-icon @click.stop="quick(node, data)"><pi-icon-quick/></el-icon>
</span>
</span>
</template>
</el-tree>
</el-main>
<el-footer style="height:51px;">
<el-button type="primary" size="small" icon="el-icon-plus" @click="add()"></el-button>
<el-button type="primary" size="small" icon="el-icon-plus" @click="add"></el-button>
<el-button type="danger" size="small" plain icon="el-icon-delete" @click="delMenu"></el-button>
</el-footer>
</el-container>
@ -38,11 +39,13 @@
<save ref="saveRef" :menu="menuList"></save>
</el-main>
</el-container>
<quick-dialog v-if="quickShow" ref="quickRef" @success="success" @closed="quickShow = false"></quick-dialog>
</el-container>
</template>
<script setup>
import {ref, watch, getCurrentInstance} from "vue";
import quickDialog from "./quick"
import {ref, watch, getCurrentInstance, nextTick} from "vue";
import save from './save'
import api from "@/api/index";
@ -53,6 +56,7 @@ defineOptions({
const menuRef = ref(null)
const saveRef = ref(null)
const mainRef = ref(null)
const quickRef = ref(null)
const {proxy} = getCurrentInstance()
let menuloading = ref(false)
@ -64,6 +68,7 @@ let menuProps = ref({
})
let menuFilterText = ref("")
let newMenuIndex = ref(1);
let quickShow = ref(false)
watch(menuFilterText, (val) => {
menuRef.value.filter(val);
@ -123,6 +128,18 @@ async function add(node, data) {
saveRef.value.setData(newMenuData)
}
function quick(node, data) {
quickShow.value = true
nextTick(() => {
quickRef.value.open(data.menu_id)
})
}
function success() {
quickShow.value = false
reload()
}
async function delMenu() {
var CheckedNodes = menuRef.value.getCheckedNodes()
if (CheckedNodes.length == 0) {

View File

@ -0,0 +1,67 @@
<template>
<el-dialog :title="title" v-model="visible" :width="500" destroy-on-close @closed="emit('closed')" :close-on-click-modal="false">
<el-form :model="form" ref="dialogForm" label-width="100px" label-position="right">
<el-form-item label="菜单名称" prop="title">
<el-input v-model="form.title" clearable></el-input>
</el-form-item>
<el-form-item label="菜单图标" prop="icon">
<pi-icon v-model="form.icon" clearable></pi-icon>
</el-form-item>
<el-form-item label="页面名称" prop="name">
<el-input v-model="form.name" clearable></el-input>
</el-form-item>
<el-form-item label="路由地址" prop="path">
<el-input v-model="form.path" clearable></el-input>
</el-form-item>
<el-form-item label="权限标识" prop="flag">
<el-input v-model="form.flag" clearable></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible=false" > </el-button>
<el-button type="primary" :loading="isSaveing" @click="submit()"> </el-button>
</template>
</el-dialog>
</template>
<script setup>
import piIcon from '@/components/piIcon'
import {ref, getCurrentInstance, defineEmits} from "vue"
import api from "@/api"
const emit = defineEmits(['closed', 'success']);
defineExpose({
open
})
const {proxy} = getCurrentInstance()
const title = ref("快速添加")
let visible = ref(false)
let isSaveing = ref(false)
let form = ref({
pid: 0,
title: '',
name: '',
path: '',
flag: '',
icon: ''
})
function open(pid) {
form.value.pid = pid || 0
visible.value = true
}
async function submit() {
const res = await api.system.menu.quick(form.value)
proxy.$message.success(res.msg)
visible.value = false
emit('success')
}
</script>
<style scoped>
</style>