|
|
@@ -245,17 +245,20 @@
|
|
|
</el-dialog>
|
|
|
|
|
|
<!-- ===== 菜单编辑弹窗 ===== -->
|
|
|
- <el-dialog :title="menuDialog.title" :visible.sync="menuDialog.visible" width="560px" append-to-body destroy-on-close>
|
|
|
- <div v-loading="menuDialog.loading" style="min-height:100px">
|
|
|
+ <el-dialog :title="menuDialog.title" :visible.sync="menuDialog.visible" width="560px" append-to-body destroy-on-close @opened="onMenuDialogOpened">
|
|
|
+ <div v-loading="menuDialog.loading" class="menu-tree-scroll">
|
|
|
+ <div v-if="menuDialog.treeReady && menuDialog.treeData.length" class="menu-tree-toolbar">
|
|
|
+ <el-button type="text" size="mini" @click="expandAllMenuNodes">展开全部</el-button>
|
|
|
+ <el-button type="text" size="mini" @click="collapseAllMenuNodes">收起全部</el-button>
|
|
|
+ </div>
|
|
|
<el-tree
|
|
|
- v-if="menuDialog.treeData.length"
|
|
|
+ v-if="menuDialog.treeReady"
|
|
|
ref="menuTree"
|
|
|
:data="menuDialog.treeData"
|
|
|
show-checkbox
|
|
|
node-key="id"
|
|
|
- :default-checked-keys="menuDialog.checkedKeys"
|
|
|
- :props="{ label: 'label', children: 'children' }"
|
|
|
- default-expand-all
|
|
|
+ :props="menuTreeProps"
|
|
|
+ :expand-on-click-node="false"
|
|
|
>
|
|
|
<span slot-scope="{ data }" class="custom-tree-node">
|
|
|
<span>{{ data.label }}</span>
|
|
|
@@ -263,7 +266,7 @@
|
|
|
<el-tag v-else type="info" size="mini" style="margin-left:8px">隐藏</el-tag>
|
|
|
</span>
|
|
|
</el-tree>
|
|
|
- <el-empty v-if="!menuDialog.loading && !menuDialog.treeData.length" description="暂无菜单数据" />
|
|
|
+ <el-empty v-if="!menuDialog.loading && menuDialog.treeReady && !menuDialog.treeData.length" description="暂无菜单数据" />
|
|
|
</div>
|
|
|
<div slot="footer">
|
|
|
<el-button @click="menuDialog.visible = false">取 消</el-button>
|
|
|
@@ -376,16 +379,18 @@ export default {
|
|
|
operateType: [{ required: true, message: '请选择操作类型', trigger: 'change' }],
|
|
|
amount: [{ required: true, message: '请输入金额', trigger: 'blur' }]
|
|
|
},
|
|
|
+ menuTreeProps: { label: 'label', children: 'children' },
|
|
|
// 菜单编辑弹窗
|
|
|
menuDialog: {
|
|
|
visible: false,
|
|
|
title: '',
|
|
|
- flag: 'sys', // sys=管理端菜单, com=销售菜单
|
|
|
+ flag: 'sys',
|
|
|
companyId: null,
|
|
|
companyName: '',
|
|
|
treeData: [],
|
|
|
- checkedKeys: [], // 初始勾选的菜单ID(visible=0)
|
|
|
- allMenuIds: [], // 所有菜单ID
|
|
|
+ treeReady: false,
|
|
|
+ checkedKeys: [],
|
|
|
+ checkedKeySet: null,
|
|
|
loading: false,
|
|
|
submitting: false
|
|
|
},
|
|
|
@@ -583,6 +588,76 @@ export default {
|
|
|
this.exportLoading = false
|
|
|
}).catch(() => { this.exportLoading = false })
|
|
|
},
|
|
|
+ collectCheckedFromTree(nodes) {
|
|
|
+ const ids = []
|
|
|
+ const walk = (list) => {
|
|
|
+ (list || []).forEach(n => {
|
|
|
+ if (n.visible === '0') ids.push(n.id)
|
|
|
+ if (n.children && n.children.length) walk(n.children)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ walk(nodes)
|
|
|
+ return ids
|
|
|
+ },
|
|
|
+ applyMenuTreeChecked(menus) {
|
|
|
+ const tree = this.$refs.menuTree
|
|
|
+ const d = this.menuDialog
|
|
|
+ if (!tree) return
|
|
|
+ tree.setCheckedKeys(this.collectCheckedFromTree(menus))
|
|
|
+ const keys = tree.getCheckedKeys().concat(tree.getHalfCheckedKeys())
|
|
|
+ d.checkedKeys = keys
|
|
|
+ d.checkedKeySet = new Set(keys)
|
|
|
+ },
|
|
|
+ walkMenuTreeNodes(nodes, fn) {
|
|
|
+ (nodes || []).forEach(n => {
|
|
|
+ fn(n)
|
|
|
+ if (n.children && n.children.length) this.walkMenuTreeNodes(n.children, fn)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ expandAllMenuNodes() {
|
|
|
+ const tree = this.$refs.menuTree
|
|
|
+ if (!tree) return
|
|
|
+ this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
|
|
|
+ const node = tree.getNode(n.id)
|
|
|
+ if (node) node.expanded = true
|
|
|
+ })
|
|
|
+ },
|
|
|
+ collapseAllMenuNodes() {
|
|
|
+ const tree = this.$refs.menuTree
|
|
|
+ if (!tree) return
|
|
|
+ this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
|
|
|
+ const node = tree.getNode(n.id)
|
|
|
+ if (node) node.expanded = false
|
|
|
+ })
|
|
|
+ },
|
|
|
+ onMenuDialogOpened() {
|
|
|
+ if (this.menuDialog.companyId) {
|
|
|
+ this.loadTenantMenuTree()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ loadTenantMenuTree() {
|
|
|
+ const d = this.menuDialog
|
|
|
+ if (!d.companyId) return
|
|
|
+ d.loading = true
|
|
|
+ d.treeReady = false
|
|
|
+ d.treeData = []
|
|
|
+ getTenantMenuTree(d.companyId, d.flag).then(res => {
|
|
|
+ if (res.code !== 200) {
|
|
|
+ return Promise.reject(new Error(res.msg || '加载菜单失败'))
|
|
|
+ }
|
|
|
+ const menus = res.menus || []
|
|
|
+ // 延迟挂载树,先出弹窗再渲染,默认全部收起
|
|
|
+ d.treeData = menus
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ d.treeReady = true
|
|
|
+ this.$nextTick(() => this.applyMenuTreeChecked(menus))
|
|
|
+ })
|
|
|
+ }).catch(err => {
|
|
|
+ this.$message.error(err.message || '加载菜单失败')
|
|
|
+ }).finally(() => {
|
|
|
+ d.loading = false
|
|
|
+ })
|
|
|
+ },
|
|
|
/** 编辑菜单(管理端/销售端) */
|
|
|
handleEditMenu(row, flag) {
|
|
|
this.menuDialog = {
|
|
|
@@ -592,44 +667,12 @@ export default {
|
|
|
companyId: row.id,
|
|
|
companyName: row.tenantName,
|
|
|
treeData: [],
|
|
|
+ treeReady: false,
|
|
|
checkedKeys: [],
|
|
|
- allMenuIds: [],
|
|
|
- loading: true,
|
|
|
+ checkedKeySet: null,
|
|
|
+ loading: false,
|
|
|
submitting: false
|
|
|
}
|
|
|
- getTenantMenuTree(row.id, flag).then(res => {
|
|
|
- if (res.code === 200) {
|
|
|
- const menus = res.menus || []
|
|
|
- this.menuDialog.treeData = menus
|
|
|
- // 递归提取所有菜单ID + visible=0的ID(勾选状态)
|
|
|
- const allIds = []
|
|
|
- const checkedIds = []
|
|
|
- const walk = (nodes) => {
|
|
|
- if (!nodes) return
|
|
|
- nodes.forEach(n => {
|
|
|
- allIds.push(n.id)
|
|
|
- if (n.visible === '0') checkedIds.push(n.id)
|
|
|
- if (n.children && n.children.length) walk(n.children)
|
|
|
- })
|
|
|
- }
|
|
|
- walk(menus)
|
|
|
- this.menuDialog.checkedKeys = checkedIds
|
|
|
- this.menuDialog.allMenuIds = allIds
|
|
|
- // 等tree渲染后捕获级联展开的实际勾选状态作为基准
|
|
|
- this.$nextTick(() => {
|
|
|
- const tree = this.$refs.menuTree
|
|
|
- if (tree) {
|
|
|
- this.menuDialog.checkedKeys = tree.getCheckedKeys().concat(tree.getHalfCheckedKeys())
|
|
|
- }
|
|
|
- })
|
|
|
- } else {
|
|
|
- this.$message.error(res.msg || '加载菜单失败')
|
|
|
- }
|
|
|
- }).catch(() => {
|
|
|
- this.$message.error('加载菜单失败')
|
|
|
- }).finally(() => {
|
|
|
- this.menuDialog.loading = false
|
|
|
- })
|
|
|
},
|
|
|
/** 模块定价 */
|
|
|
handleModulePricing(row) {
|
|
|
@@ -704,11 +747,10 @@ export default {
|
|
|
if (!tree) return
|
|
|
// 级联模式:纳入全选+半选节点作为当前勾选状态
|
|
|
const currentChecked = tree.getCheckedKeys().concat(tree.getHalfCheckedKeys())
|
|
|
- const originalChecked = this.menuDialog.checkedKeys
|
|
|
- // selected = 新勾选的(当前有,原来没有)
|
|
|
- const selected = currentChecked.filter(id => !originalChecked.includes(id))
|
|
|
- // unSelected = 新取消的(原来有,当前没有)
|
|
|
- const unSelected = originalChecked.filter(id => !currentChecked.includes(id))
|
|
|
+ const currentSet = new Set(currentChecked)
|
|
|
+ const originalSet = this.menuDialog.checkedKeySet || new Set(this.menuDialog.checkedKeys)
|
|
|
+ const selected = currentChecked.filter(id => !originalSet.has(id))
|
|
|
+ const unSelected = this.menuDialog.checkedKeys.filter(id => !currentSet.has(id))
|
|
|
if (selected.length === 0 && unSelected.length === 0) {
|
|
|
this.$message.info('菜单无变化')
|
|
|
this.menuDialog.visible = false
|
|
|
@@ -740,5 +782,18 @@ export default {
|
|
|
.mb8 { margin-bottom: 8px; }
|
|
|
.mb16 { margin-bottom: 16px; }
|
|
|
.filter-card { padding-bottom: 0; }
|
|
|
-.custom-tree-node { display: flex; align-items: center; font-size: 13px; }
|
|
|
+.menu-tree-scroll {
|
|
|
+ min-height: 120px;
|
|
|
+ max-height: 420px;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+.menu-tree-toolbar {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+.custom-tree-node {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
</style>
|