You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 lines
14 KiB

<template>
<el-card>
<div v-if="showTip" class="tip-row">线上版本没有开放动态路由功能</div>
<el-row class="button-group">
<template v-if="canAdd">
<el-button icon="el-icon-plus" size="small" type="primary" @click="addRoot">添加根节点</el-button>
<el-button icon="el-icon-plus" size="small" type="primary" @click="addChild">添加子节点</el-button>
</template>
<el-button v-if="canDel" icon="el-icon-delete" size="small" type="danger" @click="del">删 除</el-button>
<el-dropdown placement="bottom" size="small" @command="more">
<el-button plain size="small">
更多操作
<i class="el-icon-arrow-down el-icon--right"/>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :command="{action:'expand',level:0}">展开全部</el-dropdown-item>
<el-dropdown-item :command="{action:'collapse',level:0}">收起全部</el-dropdown-item>
<el-dropdown-item :command="{action:'expand',level:1}">仅展开一级</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-row>
<el-row :gutter="100" style="margin-top: 20px;">
<el-col :xs="24" :sm="11" :md="9" :lg="8" :xl="6">
<el-alert show-icon :closable="false">
<template v-slot:title>
当前选择编辑:
<el-tag v-if="curNode" size="mini" closable @close="curNode = null">
{{ getNodeTitle(curNode) }}
</el-tag>
</template>
</el-alert>
<el-input
v-model="search"
placeholder="输入菜单名搜索"
suffix-icon="el-icon-search"
size="small"
style="margin-top: 10px"
@input="v => $refs.tree.filter(v)"
/>
<div style="max-height: 660px;overflow: auto;margin-top: 10px">
<el-tree
ref="tree"
:data="menus"
node-key="id"
highlight-current
accordion
:expand-on-click-node="false"
:filter-node-method="filterNodeMethod"
@node-click="nodeClick"
>
<span slot-scope="{node}" class="el-tree-node__label">
<v-icon :icon="getNodeInfo(node).icon"/>
{{ getNodeTitle(node) }}
</span>
</el-tree>
</div>
</el-col>
<el-col :xs="24" :sm="13" :md="15" :lg="13" :xl="11">
<abstract-form v-if="showForm" :model="form" :rules="rules">
<el-form-item label="类型">
<el-select
v-if="showTypeSelector"
:value="form.type"
placeholder="请选择类型"
@change="typeChange"
>
<v-icon slot="prefix" :icon="getNodeInfo({data:form}).icon"/>
<el-option
v-for="{label,value,icon} in nodeType"
:key="value"
:label="label"
:value="value"
:disabled="value === 3"
>
<v-icon :icon="icon"/>
{{ label }}
</el-option>
</el-select>
<template v-else>
<v-icon :icon="getNodeInfo({data:form}).icon"/>
{{ getNodeInfo({data: form}).label }}
</template>
</el-form-item>
<el-form-item v-if="form.pid" label="上级菜单">
<v-icon :icon="getParentNodeInfo().icon"/>
{{ getParentNodeInfo().title }}
</el-form-item>
<el-form-item label="名称" prop="title">
<el-input v-if="form.type === 3" v-model="form.name" maxlength="10"/>
<template v-else>{{ form.meta.title }}</template>
</el-form-item>
<el-form-item label="路径" prop="path">
<el-input v-model="form.path" maxlength="100"/>
</el-form-item>
<!--菜单独有配置项-->
<template v-if="form.type !== 3">
<!--叶子菜单独有配置项-->
<template v-if="form.type === 2">
<el-form-item label="路由名称" prop="name">
<el-input v-model="form.name" maxlength="50"/>
</el-form-item>
<el-form-item label="菜单组件" prop="component">
<el-input v-model="form.component" maxlength="50"/>
</el-form-item>
</template>
<el-form-item label="meta" prop="meta">
<json-editor v-model="form.metaStr" @input="metaChange"/>
</el-form-item>
</template>
<el-form-item label="admin独有">
<el-switch v-model="form.admin" active-text="是" inactive-text="否"/>
</el-form-item>
<el-form-item label="是否启用">
<el-switch v-model="form.enable" active-text="是" inactive-text="否"/>
</el-form-item>
<el-form-item v-if="showSaveBtn">
<el-button
:loading="loading"
icon="el-icon-edit-outline"
size="small"
type="primary"
@click="save"
>
</el-button>
</el-form-item>
</abstract-form>
</el-col>
</el-row>
</el-card>
</template>
<script>
import treePageMixin from '@/mixin/treePageMixin'
import AbstractForm from "@/component/abstract/Form"
import JsonEditor from "@/component/editor/JsonEditor"
import {add, update, del} from '@/api/system/resource'
import {isEmpty, mergeObj, resetObj} from "@/util"
import {wic} from "@/util/auth"
import {expandControl} from "@/util/element-ui/elTree"
import {elSuccess, elError, elConfirm} from "@/util/message"
import {nodeType, getNodeInfo, getNodeTitle} from "./common"
export default {
name: "menuManagement",
mixins: [treePageMixin],
components: {AbstractForm, JsonEditor},
data() {
const validateTitle = (r, v, c) => {
const value = getNodeTitle({data: this.form})
c(isEmpty(value) ? '名称不能为空' : undefined)
}
return {
showTip: process.env.NODE_ENV !== 'development',
loading: false,
search: null,
type: 'add',// add or edit
nodeType,
form: {
id: null,
pid: null,
type: null,
name: null,
path: null,
component: null,
meta: {
title: null,
dynamicTitle: null,
hidden: false,
alwaysShow: false,
sort: null,
icon: null,
affix: false,
noCache: false,
activeMenu: null,
noAuth: false,
iframe: null,
usePathKey: false,
useFullPathKey: false,
commonModule: null
},
metaStr: null,
admin: false,
enable: true
},
rules: {
title: [{validator: validateTitle, trigger: 'change'}],
path: [{required: true, message: '路径不能为空', trigger: 'change'}],
}
}
},
computed: {
...wic({add, update, del}),
menus() {
return this.$store.state.resource.resourceTree
},
showForm() {
return this.type === 'add' && this.form.type === 0 || this.curNode
},
showTypeSelector() {
//0.根节点的类型不能被改变
const isRoot = this.form.type === 0
//1.非数据接口
const noApiNode = this.form.type !== 3
//2.其下无子节点
const noChild = this.type === 'add' && isRoot
|| this.type === 'edit' && this.curNode.childNodes.length <= 0
//3.父节点只能为顶部或聚合菜单
const parentIsFolderMenu =
isRoot
? true
: this.type === 'add' && [0, 1].includes(this.curNode.data.type)
|| this.type === 'edit' && [0, 1].includes(this.curNode.parent.data.type)
return !isRoot && noApiNode && noChild && parentIsFolderMenu
},
showSaveBtn() {
return this.type === 'add' && this.canAdd || this.type === 'edit' && this.canUpdate
}
},
methods: {
getNodeInfo,
getNodeTitle,
getParentNodeInfo() {
const node = this.type === 'add' ? this.curNode : this.curNode.parent
const {icon} = getNodeInfo(node)
return {icon, title: getNodeTitle(node)}
},
filterNodeMethod(value, data) {
if (isEmpty(value)) return true
return getNodeTitle({data}).includes(value)
},
afterNodeClick(data, node, ref, clear) {
if (clear) return this.clearForm()
this.type = 'edit'
this.$refs.form && this.$refs.form.clearValidate()
mergeObj(this.form, data)
if (data.meta) {
this.form.metaStr = JSON.stringify(data.meta, null, 2)
}
},
//json编辑器值改变时
metaChange(v) {
try {
v = JSON.parse(v)
}
catch (e) {
return
}
resetObj(this.form.meta)
mergeObj(this.form.meta, v)
},
//表单中菜单类型选择器改变时
typeChange(v) {
//从顶部或聚合改为页面菜单时,清空component
if (v === 2 && [0, 1].includes(this.form.type)) {
this.form.component = null
}
this.form.type = v
},
addRoot() {
if (this.loading) return
this.curNode = null
this.type = 'add'
this.clearForm()
this.form.pid = 0
this.form.type = 0
},
addChild() {
if (this.loading) return
if (!this.curNode) return elError('请选择一个节点')
const {data: {id, type}} = this.curNode
if (type === 3) return elError('数据接口不能作为父节点')
this.type = 'add'
this.clearForm()
this.form.pid = id
//如果父节点是叶子菜单,那么只能添加接口
this.form.type = type === 2 ? 3 : 1
},
del() {
if (this.loading) return
if (!this.curNode) return elError('请选择要删除的节点')
elConfirm(`确定删除【${getNodeTitle(this.curNode)}】?`)
.then(() => {
this.loading = true
return del.request(this.curNode.data.id)
})
.then(() => {
elSuccess('删除成功')
this.init()
})
.finally(() => this.loading = false)
},
more({action, level}) {
if (this.loading) return
expandControl(this.$refs.tree, action, level)
},
init() {
this.curNode = null
this.clearForm()
return this.$store.dispatch('resource/init')
},
clearForm() {
resetObj(this.form)
this.form.id = null
this.form.pid = null
this.form.type = null
this.form.metaStr = JSON.stringify({title: ''}, null, 2)
this.form.meta.sort = null
this.form.enable = true
},
save() {
if (this.loading) return
this.$refs.form.validate(v => {
if (!v) return
this.loading = true
const data = this.transformForm()
const promise = this.type === 'add' ? add.request(data) : update.request(data)
promise
.then(({data, msg}) => {
elSuccess(msg)
return this.init()
})
.finally(() => this.loading = false)
})
},
//将this.form转换为真正的提交数据(meta替换为metaStr)
transformForm() {
return {...this.form, meta: this.form.metaStr, metaStr: undefined}
}
}
}
</script>