refactor(menu): 优化列表操作列

This commit is contained in:
2025-10-22 13:51:47 +08:00
parent b18007ac29
commit 63ba8967be
+396 -396
View File
@@ -1,396 +1,396 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<!--工具栏--> <!--工具栏-->
<div class="head-container"> <div class="head-container">
<div v-if="crud.props.searchToggle"> <div v-if="crud.props.searchToggle">
<!-- 搜索 --> <!-- 搜索 -->
<el-input <el-input
v-model="query.blurry" v-model="query.blurry"
clearable clearable
size="small" size="small"
placeholder="模糊搜索" placeholder="模糊搜索"
style="width: 200px;" style="width: 200px;"
class="filter-item" class="filter-item"
@keyup.enter.native="crud.toQuery" @keyup.enter.native="crud.toQuery"
/> />
<date-range-picker v-model="query.createTime" class="date-item" /> <date-range-picker v-model="query.createTime" class="date-item" />
<rrOperation /> <rrOperation />
</div> </div>
<crudOperation :permission="permission" /> <crudOperation :permission="permission" />
</div> </div>
<!--表单渲染--> <!--表单渲染-->
<el-dialog <el-dialog
append-to-body append-to-body
:close-on-click-modal="false" :close-on-click-modal="false"
:before-close="crud.cancelCU" :before-close="crud.cancelCU"
:visible.sync="crud.status.cu > 0" :visible.sync="crud.status.cu > 0"
:title="crud.status.title" :title="crud.status.title"
width="580px" width="580px"
> >
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px"> <el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="菜单类型" prop="type"> <el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="form.type" size="mini" style="width: 178px"> <el-radio-group v-model="form.type" size="mini" style="width: 178px">
<el-radio-button label="0">目录</el-radio-button> <el-radio-button label="0">目录</el-radio-button>
<el-radio-button label="1">菜单</el-radio-button> <el-radio-button label="1">菜单</el-radio-button>
<el-radio-button label="2">按钮</el-radio-button> <el-radio-button label="2">按钮</el-radio-button>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="菜单图标" prop="icon"> <el-form-item v-show="form.type.toString() !== '2'" label="菜单图标" prop="icon">
<el-popover <el-popover
placement="bottom-start" placement="bottom-start"
width="450" width="450"
trigger="click" trigger="click"
@show="$refs['iconSelect'].reset()" @show="$refs['iconSelect'].reset()"
> >
<IconSelect ref="iconSelect" @selected="selected" /> <IconSelect ref="iconSelect" @selected="selected" />
<el-input slot="reference" v-model="form.icon" style="width: 450px;" placeholder="点击选择图标" readonly> <el-input slot="reference" v-model="form.icon" style="width: 450px;" placeholder="点击选择图标" readonly>
<svg-icon <svg-icon
v-if="form.icon" v-if="form.icon"
slot="prefix" slot="prefix"
:icon-class="form.icon" :icon-class="form.icon"
class="el-input__icon" class="el-input__icon"
style="height: 32px;width: 16px;" style="height: 32px;width: 16px;"
/> />
<i v-else slot="prefix" class="el-icon-search el-input__icon" /> <i v-else slot="prefix" class="el-icon-search el-input__icon" />
</el-input> </el-input>
</el-popover> </el-popover>
</el-form-item> </el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="外链菜单" prop="iFrame"> <el-form-item v-show="form.type.toString() !== '2'" label="外链菜单" prop="iFrame">
<el-radio-group v-model="form.iFrame" size="mini"> <el-radio-group v-model="form.iFrame" size="mini">
<el-radio-button label="true">是</el-radio-button> <el-radio-button label="true">是</el-radio-button>
<el-radio-button label="false">否</el-radio-button> <el-radio-button label="false">否</el-radio-button>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-show="form.type.toString() === '1'" label="菜单缓存" prop="cache"> <el-form-item v-show="form.type.toString() === '1'" label="菜单缓存" prop="cache">
<el-radio-group v-model="form.cache" size="mini"> <el-radio-group v-model="form.cache" size="mini">
<el-radio-button label="true">是</el-radio-button> <el-radio-button label="true">是</el-radio-button>
<el-radio-button label="false">否</el-radio-button> <el-radio-button label="false">否</el-radio-button>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-show="form.type.toString() !== '2'" label="菜单可见" prop="hidden"> <el-form-item v-show="form.type.toString() !== '2'" label="菜单可见" prop="hidden">
<el-radio-group v-model="form.hidden" size="mini"> <el-radio-group v-model="form.hidden" size="mini">
<el-radio-button label="false">是</el-radio-button> <el-radio-button label="false">是</el-radio-button>
<el-radio-button label="true">否</el-radio-button> <el-radio-button label="true">否</el-radio-button>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-if="form.type.toString() !== '2'" label="菜单标题" prop="title"> <el-form-item v-if="form.type.toString() !== '2'" label="菜单标题" prop="title">
<el-input <el-input
v-model="form.title" v-model="form.title"
:style=" form.type.toString() === '0' ? 'width: 450px' : 'width: 178px'" :style=" form.type.toString() === '0' ? 'width: 450px' : 'width: 178px'"
placeholder="菜单标题" placeholder="菜单标题"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="form.type.toString() === '2'" label="按钮名称" prop="title"> <el-form-item v-if="form.type.toString() === '2'" label="按钮名称" prop="title">
<el-input v-model="form.title" placeholder="按钮名称" style="width: 178px;" /> <el-input v-model="form.title" placeholder="按钮名称" style="width: 178px;" />
</el-form-item> </el-form-item>
<el-form-item v-show="form.type.toString() !== '0'" label="权限标识" prop="permission"> <el-form-item v-show="form.type.toString() !== '0'" label="权限标识" prop="permission">
<el-input <el-input
v-model="form.permission" v-model="form.permission"
:disabled="form.iFrame.toString() === 'true'" :disabled="form.iFrame.toString() === 'true'"
placeholder="权限标识" placeholder="权限标识"
style="width: 178px;" style="width: 178px;"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="form.type.toString() !== '2'" label="路由地址" prop="path"> <el-form-item v-if="form.type.toString() !== '2'" label="路由地址" prop="path">
<el-input v-model="form.path" placeholder="路由地址" style="width: 178px;" /> <el-input v-model="form.path" placeholder="路由地址" style="width: 178px;" />
</el-form-item> </el-form-item>
<el-form-item label="菜单排序" prop="menuSort"> <el-form-item label="菜单排序" prop="menuSort">
<el-input-number <el-input-number
v-model.number="form.menuSort" v-model.number="form.menuSort"
:min="0" :min="0"
:max="999" :max="999"
controls-position="right" controls-position="right"
style="width: 178px;" style="width: 178px;"
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-show="form.iFrame.toString() !== 'true' && form.type.toString() === '1'" v-show="form.iFrame.toString() !== 'true' && form.type.toString() === '1'"
label="组件名称" label="组件名称"
prop="componentName" prop="componentName"
> >
<el-input v-model="form.componentName" style="width: 450px;" placeholder="匹配组件内Name字段" /> <el-input v-model="form.componentName" style="width: 450px;" placeholder="匹配组件内Name字段" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-show="form.iFrame.toString() !== 'true' && form.type.toString() === '1'" v-show="form.iFrame.toString() !== 'true' && form.type.toString() === '1'"
label="组件路径" label="组件路径"
prop="component" prop="component"
> >
<el-input v-model="form.component" style="width: 450px;" placeholder="组件路径" /> <el-input v-model="form.component" style="width: 450px;" placeholder="组件路径" />
</el-form-item> </el-form-item>
<el-form-item label="上级类目" prop="pid"> <el-form-item label="上级类目" prop="pid">
<treeselect <treeselect
v-model="form.pid" v-model="form.pid"
:options="menus" :options="menus"
:load-options="loadMenus" :load-options="loadMenus"
style="width: 450px;" style="width: 450px;"
placeholder="选择上级类目" placeholder="选择上级类目"
/> />
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button @click="crud.cancelCU">取消</el-button> <el-button @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button> <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
</div> </div>
</el-dialog> </el-dialog>
<!--表格渲染--> <!--表格渲染-->
<el-table <el-table
ref="table" ref="table"
v-loading="crud.loading" v-loading="crud.loading"
lazy lazy
:load="queryMenuByArgs" :load="queryMenuByArgs"
:data="crud.data" :data="crud.data"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}" :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
row-key="id" row-key="id"
@select="crud.selectChange" @select="crud.selectChange"
@select-all="crud.selectAllChange" @select-all="crud.selectAllChange"
@selection-change="crud.selectionChangeHandler" @selection-change="crud.selectionChangeHandler"
> >
<el-table-column type="selection" width="55" /> <el-table-column type="selection" width="55" />
<el-table-column :show-overflow-tooltip="true" label="菜单标题" width="220px" prop="title" /> <el-table-column :show-overflow-tooltip="true" label="菜单标题" width="220px" prop="title" />
<el-table-column prop="icon" label="图标" align="center" width="60px"> <el-table-column prop="icon" label="图标" align="center" width="60px">
<template slot-scope="scope"> <template slot-scope="scope">
<svg-icon :icon-class="scope.row.icon ? scope.row.icon : ''" /> <svg-icon :icon-class="scope.row.icon ? scope.row.icon : ''" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="menuSort" align="center" label="排序"> <el-table-column prop="menuSort" align="center" label="排序">
<template slot-scope="scope"> <template slot-scope="scope">
{{ scope.row.menuSort }} {{ scope.row.menuSort }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="permission" label="权限标识" /> <el-table-column :show-overflow-tooltip="true" prop="permission" label="权限标识" />
<el-table-column :show-overflow-tooltip="true" prop="component" label="组件路径" /> <el-table-column :show-overflow-tooltip="true" prop="component" label="组件路径" />
<el-table-column prop="iFrame" label="外链" width="75px"> <el-table-column prop="iFrame" label="外链" width="75px">
<template slot-scope="scope"> <template slot-scope="scope">
<span v-if="scope.row.iFrame">是</span> <span v-if="scope.row.iFrame">是</span>
<span v-else>否</span> <span v-else>否</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="cache" label="缓存" width="75px"> <el-table-column prop="cache" label="缓存" width="75px">
<template slot-scope="scope"> <template slot-scope="scope">
<span v-if="scope.row.cache">是</span> <span v-if="scope.row.cache">是</span>
<span v-else>否</span> <span v-else>否</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="hidden" label="可见" width="75px"> <el-table-column prop="hidden" label="可见" width="75px">
<template slot-scope="scope"> <template slot-scope="scope">
<span v-if="scope.row.hidden">否</span> <span v-if="scope.row.hidden">否</span>
<span v-else>是</span> <span v-else>是</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="createTime" label="创建日期" width="135px" /> <el-table-column prop="createTime" label="创建日期" width="135px" />
<el-table-column <el-table-column
v-if="checkPer(['admin','menu:edit','menu:del'])" v-if="checkPer(['admin','menu:edit','menu:del'])"
label="操作" label="操作"
width="150px" width="150px"
align="center" align="center"
fixed="right" fixed="right"
> >
<template slot-scope="scope"> <template slot-scope="scope">
<udOperation <udOperation
:data="scope.row" :data="scope.row"
:permission="permission" :permission="permission"
msg="确定删除吗,如果存在下级节点则一并删除,此操作不能撤销!" msg="确定删除吗,如果存在下级节点则一并删除,此操作不能撤销!"
> >
<el-button <el-button
v-permission="permission.edit" v-permission="permission.edit"
style="margin-right: -8px" style="margin-right: -8px"
size="mini" size="mini"
type="text" type="text"
@click.stop="onCopyClick(scope.row)" @click.stop="onCopyClick(scope.row)"
>复制 >复制
</el-button> </el-button>
</udOperation> </udOperation>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
</template> </template>
<script> <script>
import crudMenu from '@/api/system/menu' import crudMenu from '@/api/system/menu'
import IconSelect from '@/components/IconSelect' import IconSelect from '@/components/IconSelect'
import Treeselect, { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect' import Treeselect, { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css' import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import CRUD, { crud, form, header, presenter } from '@crud/crud' import CRUD, { crud, form, header, presenter } from '@crud/crud'
import rrOperation from '@crud/RR.operation' import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation' import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation' import udOperation from '@crud/UD.operation'
import DateRangePicker from '@/components/DateRangePicker' import DateRangePicker from '@/components/DateRangePicker'
import CsMessage from '@/utils/elementui/CsMessage' import CsMessage from '@/utils/elementui/CsMessage'
// crud交由presenter持有 // crud交由presenter持有
const defaultForm = { const defaultForm = {
id: null, id: null,
title: null, title: null,
menuSort: 999, menuSort: 999,
path: null, path: null,
component: null, component: null,
componentName: null, componentName: null,
iFrame: false, iFrame: false,
roles: [], roles: [],
pid: 0, pid: 0,
icon: null, icon: null,
cache: false, cache: false,
hidden: false, hidden: false,
type: 0, type: 0,
permission: null permission: null
} }
export default { export default {
name: 'Menu', name: 'Menu',
components: { Treeselect, IconSelect, crudOperation, rrOperation, udOperation, DateRangePicker }, components: { Treeselect, IconSelect, crudOperation, rrOperation, udOperation, DateRangePicker },
cruds() { cruds() {
return CRUD({ title: '菜单', url: 'api/menu', crudMethod: { ...crudMenu }}) return CRUD({ title: '菜单', url: 'api/menu', crudMethod: { ...crudMenu }})
}, },
mixins: [presenter(), header(), form(defaultForm), crud()], mixins: [presenter(), header(), form(defaultForm), crud()],
data() { data() {
return { return {
menus: [], menus: [],
permission: { permission: {
add: ['admin', 'menu:add'], add: ['admin', 'menu:add'],
edit: ['admin', 'menu:edit'], edit: ['admin', 'menu:edit'],
del: ['admin', 'menu:del'] del: ['admin', 'menu:del']
}, },
rules: { rules: {
title: [ title: [
{ required: true, message: '请输入标题', trigger: 'blur' } { required: true, message: '请输入标题', trigger: 'blur' }
], ],
path: [ path: [
{ required: true, message: '请输入地址', trigger: 'blur' } { required: true, message: '请输入地址', trigger: 'blur' }
] ]
} }
} }
}, },
methods: { methods: {
// 新增与编辑前做的操作 // 新增与编辑前做的操作
[CRUD.HOOK.afterToCU](crud, form) { [CRUD.HOOK.afterToCU](crud, form) {
this.menus = [] this.menus = []
if (form.id != null) { if (form.id != null) {
if (form.pid === null) { if (form.pid === null) {
form.pid = 0 form.pid = 0
} }
this.getSupDepts(form.id) this.getSupDepts(form.id)
} else { } else {
this.menus.push({ id: 0, label: '顶级类目', children: null }) this.menus.push({ id: 0, label: '顶级类目', children: null })
} }
}, },
queryMenuByArgs(tree, treeNode, resolve) { queryMenuByArgs(tree, treeNode, resolve) {
const params = { const params = {
page: 1, page: 1,
size: 9999999, size: 9999999,
args: { pid: tree.id } args: { pid: tree.id }
} }
setTimeout(() => { setTimeout(() => {
crudMenu.queryMenuByArgs(params).then(res => { crudMenu.queryMenuByArgs(params).then(res => {
resolve(res.content) resolve(res.content)
}) })
}, 100) }, 100)
}, },
getSupDepts(id) { getSupDepts(id) {
crudMenu.queryMenuSuperior(id).then(res => { crudMenu.queryMenuSuperior(id).then(res => {
const children = res.map(function(obj) { const children = res.map(function(obj) {
if (!obj.leaf && !obj.children) { if (!obj.leaf && !obj.children) {
obj.children = null obj.children = null
} }
return obj return obj
}) })
this.menus = [{ id: 0, label: '顶级类目', children: children }] this.menus = [{ id: 0, label: '顶级类目', children: children }]
}) })
}, },
loadMenus({ action, parentNode, callback }) { loadMenus({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) { if (action === LOAD_CHILDREN_OPTIONS) {
crudMenu.queryMenuListByPid(parentNode.id).then(res => { crudMenu.queryMenuListByPid(parentNode.id).then(res => {
parentNode.children = res.map(function(obj) { parentNode.children = res.map(function(obj) {
if (!obj.leaf) { if (!obj.leaf) {
obj.children = null obj.children = null
} }
return obj return obj
}) })
setTimeout(() => { setTimeout(() => {
callback() callback()
}, 100) }, 100)
}) })
} }
}, },
// 选中图标 // 选中图标
selected(name) { selected(name) {
this.form.icon = name this.form.icon = name
}, },
onCopyClick(row) { onCopyClick(row) {
let data = {} let data = {}
if (row.type === 0) { if (row.type === 0) {
// 目录 // 目录
data = { data = {
id: null, id: null,
title: row.title + 'Copy', title: row.title + 'Copy',
menuSort: 1, menuSort: 1,
path: row.path + 'Copy', path: row.path + 'Copy',
component: null, component: null,
componentName: null, componentName: null,
iFrame: row.IFrame, iFrame: row.IFrame,
roles: [], roles: [],
pid: 0, pid: 0,
icon: row.icon, icon: row.icon,
cache: row.cache, cache: row.cache,
hidden: row.hidden, hidden: row.hidden,
type: row.type, type: row.type,
permission: null permission: null
} }
} else if (row.type === 1) { } else if (row.type === 1) {
// 菜单 // 菜单
data = { data = {
id: null, id: null,
title: row.title + 'Copy', title: row.title + 'Copy',
menuSort: row.menuSort, menuSort: row.menuSort,
path: row.path + 'Copy', path: row.path + 'Copy',
component: row.component, component: row.component,
componentName: row.componentName + 'Copy', componentName: row.componentName + 'Copy',
iFrame: row.IFrame, iFrame: row.IFrame,
roles: [], roles: [],
pid: row.pid, pid: row.pid,
icon: row.icon, icon: row.icon,
cache: row.cache, cache: row.cache,
hidden: row.hidden, hidden: row.hidden,
type: row.type, type: row.type,
permission: row.permission permission: row.permission
} }
} else if (row.type === 2) { } else if (row.type === 2) {
// 按钮 // 按钮
data = { data = {
id: null, id: null,
title: row.title + 'Copy', title: row.title + 'Copy',
menuSort: row.menuSort, menuSort: row.menuSort,
path: null, path: null,
component: null, component: null,
componentName: null, componentName: null,
iFrame: row.IFrame, iFrame: row.IFrame,
roles: [], roles: [],
pid: row.pid, pid: row.pid,
icon: null, icon: null,
cache: row.cache, cache: row.cache,
hidden: row.hidden, hidden: row.hidden,
type: row.type, type: row.type,
permission: row.permission permission: row.permission
} }
} }
const that = this const that = this
crudMenu.add(data).then(() => { crudMenu.add(data).then(() => {
setTimeout(() => { setTimeout(() => {
CsMessage.Success('复制成功') CsMessage.Success('复制成功')
that.crud.toQuery() that.crud.toQuery()
}, 1000) }, 1000)
}) })
} }
} }
} }
</script> </script>
<style rel="stylesheet/scss" lang="scss" scoped> <style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-input-number .el-input__inner { ::v-deep .el-input-number .el-input__inner {
text-align: left; text-align: left;
} }
::v-deep .vue-treeselect__control, ::v-deep .vue-treeselect__placeholder, ::v-deep .vue-treeselect__single-value { ::v-deep .vue-treeselect__control, ::v-deep .vue-treeselect__placeholder, ::v-deep .vue-treeselect__single-value {
height: 30px; height: 30px;
line-height: 30px; line-height: 30px;
} }
</style> </style>