菜单管理
This commit is contained in:
parent
2a2d05b44f
commit
f3b06f0a2a
|
|
@ -5,5 +5,5 @@ VITE_APP_TITLE=里派基础框架
|
||||||
VITE_APP_ENV='development'
|
VITE_APP_ENV='development'
|
||||||
|
|
||||||
# 开发环境
|
# 开发环境
|
||||||
VITE_API_BASE='https://dev.api.leapy.cn/merchant/'
|
VITE_API_BASE='https://server.leapy.cn/admin/'
|
||||||
VITE_WS_URL='wss://dev.api.leapy.cn/mms'
|
VITE_WS_URL='wss://dev.api.leapy.cn/mms'
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,5 @@ VITE_APP_TITLE=里派基础框架
|
||||||
VITE_APP_ENV='production'
|
VITE_APP_ENV='production'
|
||||||
|
|
||||||
# 生产环境
|
# 生产环境
|
||||||
VITE_API_BASE='https://dev.api.leapy.cn/merchant/'
|
VITE_API_BASE='https://server.leapy.cn/admin/'
|
||||||
VITE_WS_URL='wss://dev.api.leapy.cn/mms'
|
VITE_WS_URL='wss://dev.api.leapy.cn/mms'
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,18 @@ import http from "@/utils/request"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
captcha: async function (data = {}) {
|
captcha: async function (data = {}) {
|
||||||
return await http.get("v1/captcha", data);
|
return await http.get("captcha", data);
|
||||||
},
|
},
|
||||||
login: async function (data = {}) {
|
login: async function (data = {}) {
|
||||||
return await http.post("v1/login", data);
|
return await http.post("login", data);
|
||||||
},
|
},
|
||||||
info: async function () {
|
info: async function () {
|
||||||
return await http.get("v1/info");
|
return await http.get("info");
|
||||||
},
|
},
|
||||||
menu: async function (data = {}) {
|
menu: async function (data = {}) {
|
||||||
return await http.get("v1/menu", data)
|
return await http.get("menu", data)
|
||||||
},
|
},
|
||||||
logout: async function(){
|
logout: async function(){
|
||||||
return await http.get("v1/logout");
|
return await http.get("logout");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import http from "@/utils/request"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
menu: {
|
||||||
|
list: async function(data={}){
|
||||||
|
return await http.get("menu/list", data);
|
||||||
|
},
|
||||||
|
add: async function(data = {}){
|
||||||
|
return await http.post("menu/add", data);
|
||||||
|
},
|
||||||
|
edit: async function(data = {}){
|
||||||
|
return await http.put("menu/edit", data);
|
||||||
|
},
|
||||||
|
del: async function(data = {}){
|
||||||
|
return await http.delete("menu/del", data);
|
||||||
|
},
|
||||||
|
option: async function(data={}){
|
||||||
|
return await http.get("menu/option", data);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
<template>
|
||||||
|
<div class="pi-icon-select">
|
||||||
|
<div class="pi-icon-select__wrapper" :class="{'hasValue':value}" @click="open">
|
||||||
|
<el-input :prefix-icon="value||'el-icon-plus'" v-model="value" :disabled="disabled" readonly></el-input>
|
||||||
|
</div>
|
||||||
|
<el-dialog title="图标选择器" v-model="dialogVisible" :width="760" destroy-on-close append-to-body>
|
||||||
|
<div class="pi-icon-select__dialog">
|
||||||
|
<el-form :rules="{}">
|
||||||
|
<el-form-item prop="searchText">
|
||||||
|
<el-input class="pi-icon-select__search-input" prefix-icon="el-icon-search" v-model="searchText"
|
||||||
|
placeholder="搜索" size="large" clearable/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-tabs>
|
||||||
|
<el-tab-pane v-for="item in data" :key="item.name" lazy>
|
||||||
|
<template #label>
|
||||||
|
{{ item.name }}
|
||||||
|
<el-tag size="small" type="info">{{ item.icons.length }}</el-tag>
|
||||||
|
</template>
|
||||||
|
<div class="pi-icon-select__list">
|
||||||
|
<el-scrollbar>
|
||||||
|
<ul @click="selectIcon">
|
||||||
|
<el-empty v-if="item.icons.length==0" :image-size="100"
|
||||||
|
description="未查询到相关图标"/>
|
||||||
|
<li v-for="icon in item.icons" :key="icon">
|
||||||
|
<span :data-icon="icon"></span>
|
||||||
|
<el-icon>
|
||||||
|
<component :is="icon"/>
|
||||||
|
</el-icon>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="clear" text>清除</el-button>
|
||||||
|
<el-button @click="dialogVisible=false">取消</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref, watch, onMounted, getCurrentInstance} from "vue";
|
||||||
|
import config from "@/config/icon"
|
||||||
|
|
||||||
|
const {proxy} = getCurrentInstance()
|
||||||
|
const prop = defineProps({
|
||||||
|
modelValue: {type: String, default: ""},
|
||||||
|
disabled: {type: Boolean, default: false},
|
||||||
|
})
|
||||||
|
|
||||||
|
let value = ref("")
|
||||||
|
let dialogVisible = ref(false)
|
||||||
|
let data = ref([])
|
||||||
|
let searchText = ref("")
|
||||||
|
|
||||||
|
|
||||||
|
watch(() => prop.modelValue, (val) => {
|
||||||
|
value.value = val
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(value, (val) => {
|
||||||
|
proxy.$emit('update:modelValue', val)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(searchText, (val) => {
|
||||||
|
search(val)
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
value.value = prop.modelValue
|
||||||
|
data.value.push(...config.icons)
|
||||||
|
})
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
if (prop.disabled) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectIcon(e) {
|
||||||
|
if (e.target.tagName != 'SPAN') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
value.value = e.target.dataset.icon
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
value.value = ""
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function search(text) {
|
||||||
|
if (text) {
|
||||||
|
const filterData = JSON.parse(JSON.stringify(config.icons))
|
||||||
|
filterData.forEach(t => {
|
||||||
|
t.icons = t.icons.filter(n => n.includes(text))
|
||||||
|
})
|
||||||
|
data.value = filterData
|
||||||
|
} else {
|
||||||
|
data.value = JSON.parse(JSON.stringify(config.icons))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pi-icon-select {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-icon-select__wrapper {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-icon-select__wrapper:deep(.el-input__wrapper).is-focus {
|
||||||
|
box-shadow: 0 0 0 1px var(--el-input-hover-border-color) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-icon-select__wrapper:deep(.el-input__inner) {
|
||||||
|
flex-grow: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-icon-select__wrapper:deep(.el-input__icon) {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-icon-select__wrapper.hasValue:deep(.el-input__icon) {
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-icon-select__list {
|
||||||
|
height: 270px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-icon-select__list ul {
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-icon-select__list li {
|
||||||
|
display: inline-block;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
margin: 5px;
|
||||||
|
vertical-align: top;
|
||||||
|
transition: all 0.1s;
|
||||||
|
border-radius: 4px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-icon-select__list li span {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-icon-select__list li i {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 26px;
|
||||||
|
color: #6d7882;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-icon-select__list li:hover {
|
||||||
|
box-shadow: 0 0 1px 4px var(--el-color-primary);
|
||||||
|
background: var(--el-color-primary-light-9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-icon-select__list li:hover i {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
//图标选择器配置
|
||||||
|
import * as elIconsLib from '@element-plus/icons-vue'
|
||||||
|
import * as piIconsLib from '@/assets/icons'
|
||||||
|
|
||||||
|
//el-icon图标
|
||||||
|
const elIcons = [];
|
||||||
|
for (let icon in elIconsLib) {
|
||||||
|
let iconName = `elIcon${icon}`
|
||||||
|
iconName = iconName.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
|
||||||
|
elIcons.push(iconName)
|
||||||
|
}
|
||||||
|
|
||||||
|
//pi-icon图标
|
||||||
|
const piIcons = [];
|
||||||
|
for (let icon in piIconsLib.default) {
|
||||||
|
let iconName = `piIcon${icon}`
|
||||||
|
iconName = iconName.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
|
||||||
|
piIcons.push(iconName)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
icons: [{
|
||||||
|
name: '默认',
|
||||||
|
icons: elIcons
|
||||||
|
}, {
|
||||||
|
name: '扩展',
|
||||||
|
icons: piIcons
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
@ -64,8 +64,8 @@
|
||||||
</div>
|
</div>
|
||||||
<el-dropdown class="user panel-item" trigger="click" @command="handleUser">
|
<el-dropdown class="user panel-item" trigger="click" @command="handleUser">
|
||||||
<div class="user-avatar">
|
<div class="user-avatar">
|
||||||
<el-avatar :size="30" :src="avatar">{{ realnameF }}</el-avatar>
|
<el-avatar :size="30" :src="avatar">{{ nicknameF }}</el-avatar>
|
||||||
<label>{{ realname }}</label>
|
<label>{{ nickname }}</label>
|
||||||
<el-icon class="el-icon--right">
|
<el-icon class="el-icon--right">
|
||||||
<el-icon-arrow-down/>
|
<el-icon-arrow-down/>
|
||||||
</el-icon>
|
</el-icon>
|
||||||
|
|
@ -126,8 +126,8 @@ const settingDialog = ref(false)
|
||||||
const userInfo = tools.data.get("USER_INFO");
|
const userInfo = tools.data.get("USER_INFO");
|
||||||
let msgTab = ref("notice")
|
let msgTab = ref("notice")
|
||||||
|
|
||||||
let realname = userInfo.realname;
|
let nickname = userInfo.nickname || userInfo.username;
|
||||||
let realnameF = realname.substring(0, 1);
|
let nicknameF = nickname.substring(0, 1);
|
||||||
let avatar = userInfo.avatar
|
let avatar = userInfo.avatar
|
||||||
|
|
||||||
// mounted
|
// mounted
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name="index">
|
<script setup>
|
||||||
import {ref, watch, computed, nextTick} from 'vue'
|
import {ref, watch, computed, nextTick} from 'vue'
|
||||||
import SideM from './components/sideM.vue';
|
import SideM from './components/sideM.vue';
|
||||||
import Topbar from './components/topbar.vue';
|
import Topbar from './components/topbar.vue';
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,12 @@ axios.interceptors.request.use((config) => {
|
||||||
//响应拦截
|
//响应拦截
|
||||||
axios.interceptors.response.use((response) => {
|
axios.interceptors.response.use((response) => {
|
||||||
let res = response.data
|
let res = response.data
|
||||||
if (res.code == 200) {
|
if (res.code == 0) {
|
||||||
return Promise.resolve(res)
|
return Promise.resolve(res)
|
||||||
} else if (res.code == 400) { // 操作失败拦截
|
} else if (res.code == 1) { // 操作失败拦截
|
||||||
ElNotification.error({title: '操作失败', message: res.msg});
|
ElNotification.error({title: '操作失败', message: res.msg});
|
||||||
return Promise.reject(res)
|
return Promise.reject(res)
|
||||||
} else if (res.code == 500) { // 权限不足拦截
|
} else if (res.code == 2) { // 权限不足拦截
|
||||||
ElNotification.error({title: '权限不足', message: res.msg});
|
ElNotification.error({title: '权限不足', message: res.msg});
|
||||||
return Promise.reject(res)
|
return Promise.reject(res)
|
||||||
} else { // 登录失效拦截
|
} else { // 登录失效拦截
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,10 @@ const tools = {
|
||||||
return CryptoJS.MD5(data).toString()
|
return CryptoJS.MD5(data).toString()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
makeMenu: function (menus, parent_id = 0) {
|
makeMenu: function (menus, pid = 0) {
|
||||||
const arr = [];
|
const arr = [];
|
||||||
for (let item of menus) {
|
for (let item of menus) {
|
||||||
if (item.parent_id === parent_id) {
|
if (item.pid === pid) {
|
||||||
// 数据格式处理
|
// 数据格式处理
|
||||||
const tmp = {
|
const tmp = {
|
||||||
name: item['name'],
|
name: item['name'],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
<template>
|
||||||
|
<el-container>
|
||||||
|
<el-aside width="300px" v-loading="menuloading">
|
||||||
|
<el-container>
|
||||||
|
<el-header>
|
||||||
|
<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"
|
||||||
|
highlight-current :expand-on-click-node="false" check-strictly show-checkbox
|
||||||
|
:filter-node-method="menuFilterNode" @node-click="menuClick">
|
||||||
|
<template #default="{node, data}">
|
||||||
|
<span class="custom-tree-node el-tree-node__label">
|
||||||
|
<span class="label">
|
||||||
|
{{ node.label }}
|
||||||
|
<el-button v-if="data.type == 0" link size="small"
|
||||||
|
style="color: #e6a23c;margin-left: 5px;">菜单</el-button>
|
||||||
|
<el-button v-if="data.type == 1" link size="small"
|
||||||
|
style="color: #79bbff;margin-left: 5px;">按钮</el-button>
|
||||||
|
<el-button v-if="data.type == 2" link size="small"
|
||||||
|
style="color: #67c23a;margin-left: 5px;">接口</el-button>
|
||||||
|
</span>
|
||||||
|
<span class="do">
|
||||||
|
<el-icon @click.stop="add(node, data)"><el-icon-plus/></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="danger" size="small" plain icon="el-icon-delete" @click="delMenu"></el-button>
|
||||||
|
</el-footer>
|
||||||
|
</el-container>
|
||||||
|
</el-aside>
|
||||||
|
<el-container>
|
||||||
|
<el-main class="nopadding" style="padding:20px;" ref="mainRef">
|
||||||
|
<save ref="saveRef" :menu="menuList"></save>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref, watch, getCurrentInstance} from "vue";
|
||||||
|
import save from './save'
|
||||||
|
import api from "@/api/index";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "systemMenu"
|
||||||
|
})
|
||||||
|
|
||||||
|
const menuRef = ref(null)
|
||||||
|
const saveRef = ref(null)
|
||||||
|
const mainRef = ref(null)
|
||||||
|
|
||||||
|
const {proxy} = getCurrentInstance()
|
||||||
|
let menuloading = ref(false)
|
||||||
|
let menuList = ref([])
|
||||||
|
let menuProps = ref({
|
||||||
|
label: (data) => {
|
||||||
|
return data.title
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let menuFilterText = ref("")
|
||||||
|
let newMenuIndex = ref(1);
|
||||||
|
|
||||||
|
watch(menuFilterText, (val) => {
|
||||||
|
menuRef.value.filter(val);
|
||||||
|
})
|
||||||
|
|
||||||
|
getMenu()
|
||||||
|
|
||||||
|
async function getMenu() {
|
||||||
|
menuloading.value = true
|
||||||
|
var res = await api.system.menu.list();
|
||||||
|
menuloading.value = false
|
||||||
|
menuList.value = treeData(res.data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function treeData(menus, menu_id) {
|
||||||
|
if (menus == undefined || menus.length <= 0) return [];
|
||||||
|
const arr = [];
|
||||||
|
for (let item of menus) {
|
||||||
|
// 数据格式处理
|
||||||
|
const tmp = item;
|
||||||
|
if (item.pid == menu_id) {
|
||||||
|
var children = treeData(menus, item.menu_id);
|
||||||
|
if (children.length > 0) {
|
||||||
|
tmp['children'] = children
|
||||||
|
}
|
||||||
|
arr.push(tmp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
function menuClick(data) {
|
||||||
|
saveRef.value.setData(data)
|
||||||
|
mainRef.value.$el.scrollTop = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function menuFilterNode(value, data) {
|
||||||
|
if (!value) return true;
|
||||||
|
var targetText = data.title;
|
||||||
|
return targetText.indexOf(value) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add(node, data) {
|
||||||
|
var newMenuName = "未命名" + newMenuIndex.value++;
|
||||||
|
var newMenuData = {
|
||||||
|
menu_id: "",
|
||||||
|
pid: data ? data.menu_id : 0,
|
||||||
|
name: newMenuName,
|
||||||
|
path: "",
|
||||||
|
title: newMenuName,
|
||||||
|
type: 0,
|
||||||
|
rank: 1
|
||||||
|
}
|
||||||
|
menuloading.value = false
|
||||||
|
menuRef.value.append(newMenuData, node)
|
||||||
|
menuRef.value.setCurrentKey(newMenuData.menu_id)
|
||||||
|
saveRef.value.setData(newMenuData)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function delMenu() {
|
||||||
|
var CheckedNodes = menuRef.value.getCheckedNodes()
|
||||||
|
if (CheckedNodes.length == 0) {
|
||||||
|
proxy.$message.warning("请选择需要删除的项")
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var confirm = await proxy.$confirm('确认删除已选择的菜单吗?', '提示', {
|
||||||
|
type: 'warning',
|
||||||
|
confirmButtonText: '删除',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
confirmButtonClass: 'el-button--danger'
|
||||||
|
}).catch(() => {
|
||||||
|
})
|
||||||
|
if (confirm != 'confirm') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
menuloading.value = true
|
||||||
|
var ids = CheckedNodes.map(item => item.menu_id)
|
||||||
|
var res = await api.system.menu.del({menu_id: ids.toString()})
|
||||||
|
menuloading.value = false
|
||||||
|
proxy.$message.success(res.msg)
|
||||||
|
CheckedNodes.forEach(item => {
|
||||||
|
var node = menuRef.value.getNode(item)
|
||||||
|
if (node == null) {
|
||||||
|
reload()
|
||||||
|
} else if (node.isCurrent) {
|
||||||
|
saveRef.value.setData({})
|
||||||
|
}
|
||||||
|
menuRef.value.remove(item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
getMenu()
|
||||||
|
saveRef.value.setData(menuList.value[0])
|
||||||
|
mainRef.value.$el.scrollTop = 0
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.custom-tree-node {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 14px;
|
||||||
|
padding-right: 24px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tree-node .label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tree-node .label .el-tag {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tree-node .do {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tree-node .do i {
|
||||||
|
margin-left: 5px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tree-node .do i:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tree-node:hover .do {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
<template>
|
||||||
|
<el-row :gutter="40">
|
||||||
|
<el-col v-if="form.parentId===''">
|
||||||
|
<el-empty description="请选择左侧菜单后操作" :image-size="100"></el-empty>
|
||||||
|
</el-col>
|
||||||
|
<template v-else>
|
||||||
|
<el-col :lg="20">
|
||||||
|
<h2>{{ form.title || "新增菜单" }}</h2>
|
||||||
|
<el-form :model="form" :rules="rules" ref="dialogForm" label-width="80px" label-position="left">
|
||||||
|
<el-form-item label="显示名称" prop="title">
|
||||||
|
<el-input v-model="form.title" clearable placeholder="菜单显示名字"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="上级菜单" prop="pid">
|
||||||
|
<el-cascader ref="parentId" v-model="form.pid" :options="menuOptions" :props="menuProps"
|
||||||
|
:show-all-levels="false" placeholder="顶级菜单" clearable></el-cascader>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="类型" prop="type">
|
||||||
|
<el-radio-group v-model="form.type">
|
||||||
|
<el-radio-button :value="0">菜单</el-radio-button>
|
||||||
|
<el-radio-button :value="1">按钮</el-radio-button>
|
||||||
|
<el-radio-button :value="2">接口</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="form.type == 0" label="菜单图标" prop="icon">
|
||||||
|
<pi-icon v-model="form.icon" clearable></pi-icon>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="form.type == 0" label="页面名称" prop="name">
|
||||||
|
<el-input v-model="form.name" clearable></el-input>
|
||||||
|
<div class="el-form-item-msg">
|
||||||
|
系统唯一且与内置组件名一致,否则导致缓存失效。如类型为Iframe的菜单,别名将代替源地址显示在地址栏
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="form.type == 0" label="路由地址" prop="path">
|
||||||
|
<el-input v-model="form.path" clearable></el-input>
|
||||||
|
<div class="el-form-item-msg">首位不需要填写 "/", 自动匹配页面路径</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="form.type == 0" label="是否隐藏" prop="hidden">
|
||||||
|
<el-checkbox v-model="form.hidden" :true-value="1" :false-value="0">隐藏菜单</el-checkbox>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="form.type == 0" label="页面顺序" prop="rank">
|
||||||
|
<el-input v-model="form.rank" clearable></el-input>
|
||||||
|
<div class="el-form-item-msg">数值越大越靠前</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="form.type == 2" label="请求方式" prop="method">
|
||||||
|
<el-select v-model="form.method">
|
||||||
|
<el-option
|
||||||
|
v-for="item in methodOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="form.type == 1 || form.type == 2" label="权限标识" prop="flag">
|
||||||
|
<el-input v-model="form.flag" clearable></el-input>
|
||||||
|
<div class="el-form-item-msg">权限分隔符":"分割</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="save" :loading="loading">保 存</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-col>
|
||||||
|
</template>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import piIcon from '@/components/piIcon'
|
||||||
|
import {ref, watch, getCurrentInstance} from "vue";
|
||||||
|
import api from "@/api/index";
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
setData
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
menu: {
|
||||||
|
type: Object, default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const {proxy} = getCurrentInstance()
|
||||||
|
|
||||||
|
let form = ref({
|
||||||
|
menu_id: "",
|
||||||
|
pid: "",
|
||||||
|
belong: 0,
|
||||||
|
name: "",
|
||||||
|
path: "",
|
||||||
|
title: "",
|
||||||
|
icon: "",
|
||||||
|
type: 0,
|
||||||
|
flag: "",
|
||||||
|
hidden: "",
|
||||||
|
method: "",
|
||||||
|
rank: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
let menuOptions = ref([])
|
||||||
|
let menuProps = ref({
|
||||||
|
value: 'menu_id',
|
||||||
|
label: 'title',
|
||||||
|
checkStrictly: true
|
||||||
|
})
|
||||||
|
|
||||||
|
let rules = ref([])
|
||||||
|
let loading = ref(false)
|
||||||
|
let methodOptions = ref([
|
||||||
|
{
|
||||||
|
label: "get",
|
||||||
|
value: "get"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "post",
|
||||||
|
value: "post"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "put",
|
||||||
|
value: "put"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "delete",
|
||||||
|
value: "delete"
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
watch(() => props.menu, () => {
|
||||||
|
menuOptions.value = treeToMap(props.menu)
|
||||||
|
}, {deep: true})
|
||||||
|
|
||||||
|
function treeToMap(tree) {
|
||||||
|
const map = []
|
||||||
|
tree.forEach(item => {
|
||||||
|
var obj = {
|
||||||
|
menu_id: item.menu_id,
|
||||||
|
pid: item.pid,
|
||||||
|
title: item.title,
|
||||||
|
children: item.children && item.children.length > 0 ? treeToMap(item.children) : null
|
||||||
|
}
|
||||||
|
map.push(obj)
|
||||||
|
})
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
loading.value = true
|
||||||
|
if (Array.isArray(form.value.pid)) {
|
||||||
|
form.value.pid = form.value.pid[form.value.pid.length - 1] ?? 0
|
||||||
|
}
|
||||||
|
const res = form.value.menu_id == "" ? await api.system.menu.add(form.value) :
|
||||||
|
await api.system.menu.edit(form.value);
|
||||||
|
loading.value = false
|
||||||
|
|
||||||
|
proxy.$message.success(res.msg)
|
||||||
|
if (form.value.menu_id == "") {
|
||||||
|
form.value.menu_id = res.data.menu_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setData(data) {
|
||||||
|
form.value = data
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
h2 {
|
||||||
|
font-size: 17px;
|
||||||
|
color: #3c4a54;
|
||||||
|
padding: 0 0 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] h2 {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue