|
@@ -275,6 +275,8 @@
|
|
|
<el-button type="text" size="mini" @click="uncheckAllMenuNodes">全不选</el-button>
|
|
<el-button type="text" size="mini" @click="uncheckAllMenuNodes">全不选</el-button>
|
|
|
<el-button type="text" size="mini" @click="expandAllMenuNodes">展开全部</el-button>
|
|
<el-button type="text" size="mini" @click="expandAllMenuNodes">展开全部</el-button>
|
|
|
<el-button type="text" size="mini" @click="collapseAllMenuNodes">收起全部</el-button>
|
|
<el-button type="text" size="mini" @click="collapseAllMenuNodes">收起全部</el-button>
|
|
|
|
|
+ <el-button type="text" size="mini" @click="checkAllMenuNodes">全选</el-button>
|
|
|
|
|
+ <el-button type="text" size="mini" @click="uncheckAllMenuNodes">取消全选</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
<el-tree
|
|
<el-tree
|
|
|
v-if="menuDialog.treeReady"
|
|
v-if="menuDialog.treeReady"
|
|
@@ -290,7 +292,7 @@
|
|
|
<el-tag v-if="data.menuType === 'M'" size="mini" style="margin-left:6px">目录</el-tag>
|
|
<el-tag v-if="data.menuType === 'M'" size="mini" style="margin-left:6px">目录</el-tag>
|
|
|
<el-tag v-else-if="data.menuType === 'C'" type="success" size="mini" style="margin-left:6px">菜单</el-tag>
|
|
<el-tag v-else-if="data.menuType === 'C'" type="success" size="mini" style="margin-left:6px">菜单</el-tag>
|
|
|
<el-tag v-else-if="data.menuType === 'F'" type="warning" size="mini" style="margin-left:6px">按钮</el-tag>
|
|
<el-tag v-else-if="data.menuType === 'F'" type="warning" size="mini" style="margin-left:6px">按钮</el-tag>
|
|
|
- <el-tag v-if="data.visible === '0'" type="success" size="mini" style="margin-left:6px">已分配</el-tag>
|
|
|
|
|
|
|
+ <el-tag v-if="isMenuAssigned(data)" type="success" size="mini" style="margin-left:6px">已分配</el-tag>
|
|
|
<el-tag v-else type="info" size="mini" style="margin-left:6px">未分配</el-tag>
|
|
<el-tag v-else type="info" size="mini" style="margin-left:6px">未分配</el-tag>
|
|
|
</span>
|
|
</span>
|
|
|
</el-tree>
|
|
</el-tree>
|
|
@@ -424,6 +426,7 @@ export default {
|
|
|
treeData: [],
|
|
treeData: [],
|
|
|
treeReady: false,
|
|
treeReady: false,
|
|
|
checkedKeys: [],
|
|
checkedKeys: [],
|
|
|
|
|
+ checkedKeysFromApi: [],
|
|
|
checkedKeySet: null,
|
|
checkedKeySet: null,
|
|
|
initialAssignedIds: null,
|
|
initialAssignedIds: null,
|
|
|
loading: false,
|
|
loading: false,
|
|
@@ -638,11 +641,27 @@ export default {
|
|
|
const n = Number(id)
|
|
const n = Number(id)
|
|
|
return Number.isNaN(n) ? id : n
|
|
return Number.isNaN(n) ? id : n
|
|
|
},
|
|
},
|
|
|
|
|
+ /** 租户库已分配:优先 checkedKeys,其次 visible=0(不再依赖 status,避免按钮类菜单回显失败) */
|
|
|
|
|
+ isMenuAssigned(menu) {
|
|
|
|
|
+ if (!menu) return false
|
|
|
|
|
+ const menuId = this.normalizeMenuId(menu.menuId)
|
|
|
|
|
+ if (menuId != null && this.menuDialog.checkedKeySet && this.menuDialog.checkedKeySet.has(menuId)) {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ return menu.visible === '0' || menu.visible === 0
|
|
|
|
|
+ },
|
|
|
|
|
+ normalizeMenuFlatList(menus) {
|
|
|
|
|
+ return (menus || []).map(m => ({
|
|
|
|
|
+ ...m,
|
|
|
|
|
+ menuId: this.normalizeMenuId(m.menuId),
|
|
|
|
|
+ parentId: m.parentId != null && m.parentId !== '' ? this.normalizeMenuId(m.parentId) : m.parentId
|
|
|
|
|
+ }))
|
|
|
|
|
+ },
|
|
|
collectAssignedMenuIds(nodes) {
|
|
collectAssignedMenuIds(nodes) {
|
|
|
const ids = []
|
|
const ids = []
|
|
|
const walk = (list) => {
|
|
const walk = (list) => {
|
|
|
(list || []).forEach(n => {
|
|
(list || []).forEach(n => {
|
|
|
- if (n.visible === '0') {
|
|
|
|
|
|
|
+ if (this.isMenuAssigned(n)) {
|
|
|
const mid = this.normalizeMenuId(n.menuId)
|
|
const mid = this.normalizeMenuId(n.menuId)
|
|
|
if (mid != null) ids.push(mid)
|
|
if (mid != null) ids.push(mid)
|
|
|
}
|
|
}
|
|
@@ -652,19 +671,19 @@ export default {
|
|
|
walk(nodes)
|
|
walk(nodes)
|
|
|
return ids
|
|
return ids
|
|
|
},
|
|
},
|
|
|
- applyMenuTreeChecked(menus) {
|
|
|
|
|
|
|
+ applyMenuTreeChecked(checkedKeys) {
|
|
|
const tree = this.$refs.menuTree
|
|
const tree = this.$refs.menuTree
|
|
|
const d = this.menuDialog
|
|
const d = this.menuDialog
|
|
|
if (!tree) return
|
|
if (!tree) return
|
|
|
- // 勿用 setCheckedKeys(父节点):会级联勾选未分配子节点,导致提交 diff 为空
|
|
|
|
|
|
|
+ const keys = (checkedKeys || []).map(id => this.normalizeMenuId(id)).filter(id => id != null)
|
|
|
tree.setCheckedKeys([])
|
|
tree.setCheckedKeys([])
|
|
|
- this.collectAssignedMenuIds(menus).forEach(menuId => {
|
|
|
|
|
|
|
+ keys.forEach(menuId => {
|
|
|
const node = tree.getNode(menuId)
|
|
const node = tree.getNode(menuId)
|
|
|
if (node) node.setChecked(true, false)
|
|
if (node) node.setChecked(true, false)
|
|
|
})
|
|
})
|
|
|
- const keys = tree.getCheckedKeys().concat(tree.getHalfCheckedKeys()).map(id => this.normalizeMenuId(id))
|
|
|
|
|
- d.checkedKeys = keys
|
|
|
|
|
- d.checkedKeySet = new Set(keys)
|
|
|
|
|
|
|
+ const currentKeys = tree.getCheckedKeys().concat(tree.getHalfCheckedKeys()).map(id => this.normalizeMenuId(id))
|
|
|
|
|
+ d.checkedKeys = currentKeys
|
|
|
|
|
+ d.checkedKeySet = new Set(currentKeys)
|
|
|
},
|
|
},
|
|
|
walkMenuTreeNodes(nodes, fn) {
|
|
walkMenuTreeNodes(nodes, fn) {
|
|
|
(nodes || []).forEach(n => {
|
|
(nodes || []).forEach(n => {
|
|
@@ -680,21 +699,26 @@ export default {
|
|
|
})
|
|
})
|
|
|
return ids
|
|
return ids
|
|
|
},
|
|
},
|
|
|
- checkAllMenuNodes() {
|
|
|
|
|
|
|
+ setAllMenuNodesChecked(checked) {
|
|
|
const tree = this.$refs.menuTree
|
|
const tree = this.$refs.menuTree
|
|
|
if (!tree) return
|
|
if (!tree) return
|
|
|
- tree.setCheckedKeys(this.collectAllMenuIds(this.menuDialog.treeData))
|
|
|
|
|
|
|
+ // 与回显一致:逐节点 setChecked,避免 setCheckedKeys(父节点) 级联导致 diff 异常
|
|
|
|
|
+ this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
|
|
|
|
|
+ const node = tree.getNode(this.normalizeMenuId(n.menuId))
|
|
|
|
|
+ if (node) node.setChecked(checked, false)
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ checkAllMenuNodes() {
|
|
|
|
|
+ this.setAllMenuNodesChecked(true)
|
|
|
},
|
|
},
|
|
|
uncheckAllMenuNodes() {
|
|
uncheckAllMenuNodes() {
|
|
|
- const tree = this.$refs.menuTree
|
|
|
|
|
- if (!tree) return
|
|
|
|
|
- tree.setCheckedKeys([])
|
|
|
|
|
|
|
+ this.setAllMenuNodesChecked(false)
|
|
|
},
|
|
},
|
|
|
expandAllMenuNodes() {
|
|
expandAllMenuNodes() {
|
|
|
const tree = this.$refs.menuTree
|
|
const tree = this.$refs.menuTree
|
|
|
if (!tree) return
|
|
if (!tree) return
|
|
|
this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
|
|
this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
|
|
|
- const node = tree.getNode(n.menuId)
|
|
|
|
|
|
|
+ const node = tree.getNode(this.normalizeMenuId(n.menuId))
|
|
|
if (node) node.expanded = true
|
|
if (node) node.expanded = true
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
@@ -702,10 +726,22 @@ export default {
|
|
|
const tree = this.$refs.menuTree
|
|
const tree = this.$refs.menuTree
|
|
|
if (!tree) return
|
|
if (!tree) return
|
|
|
this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
|
|
this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
|
|
|
- const node = tree.getNode(n.menuId)
|
|
|
|
|
|
|
+ const node = tree.getNode(this.normalizeMenuId(n.menuId))
|
|
|
if (node) node.expanded = false
|
|
if (node) node.expanded = false
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
|
|
|
+ checkAllMenuNodes() {
|
|
|
|
|
+ const tree = this.$refs.menuTree
|
|
|
|
|
+ if (!tree) return
|
|
|
|
|
+ const allKeys = []
|
|
|
|
|
+ this.walkMenuTreeNodes(this.menuDialog.treeData, n => allKeys.push(n.menuId))
|
|
|
|
|
+ tree.setCheckedKeys(allKeys)
|
|
|
|
|
+ },
|
|
|
|
|
+ uncheckAllMenuNodes() {
|
|
|
|
|
+ const tree = this.$refs.menuTree
|
|
|
|
|
+ if (!tree) return
|
|
|
|
|
+ tree.setCheckedKeys([])
|
|
|
|
|
+ },
|
|
|
onMenuDialogOpened() {
|
|
onMenuDialogOpened() {
|
|
|
if (this.menuDialog.companyId) {
|
|
if (this.menuDialog.companyId) {
|
|
|
this.loadTenantMenuTree()
|
|
this.loadTenantMenuTree()
|
|
@@ -721,16 +757,19 @@ export default {
|
|
|
if (res.code !== 200) {
|
|
if (res.code !== 200) {
|
|
|
return Promise.reject(new Error(res.msg || '加载菜单失败'))
|
|
return Promise.reject(new Error(res.msg || '加载菜单失败'))
|
|
|
}
|
|
}
|
|
|
- const flat = res.menus || []
|
|
|
|
|
- d.initialAssignedIds = new Set(
|
|
|
|
|
- flat.filter(m => m.visible === '0').map(m => this.normalizeMenuId(m.menuId)).filter(id => id != null)
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ const flat = this.normalizeMenuFlatList(res.menus || [])
|
|
|
|
|
+ const checkedFromApi = (res.checkedKeys || [])
|
|
|
|
|
+ .map(id => this.normalizeMenuId(id))
|
|
|
|
|
+ .filter(id => id != null)
|
|
|
|
|
+ d.checkedKeysFromApi = checkedFromApi
|
|
|
|
|
+ d.initialAssignedIds = new Set(checkedFromApi)
|
|
|
|
|
+ d.checkedKeySet = new Set(checkedFromApi)
|
|
|
// 与模板维护页相同:扁平数据 + handleTree(menuId) 建树,保证层级一致
|
|
// 与模板维护页相同:扁平数据 + handleTree(menuId) 建树,保证层级一致
|
|
|
const tree = this.handleTree(flat, 'menuId')
|
|
const tree = this.handleTree(flat, 'menuId')
|
|
|
d.treeData = tree
|
|
d.treeData = tree
|
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
|
d.treeReady = true
|
|
d.treeReady = true
|
|
|
- this.$nextTick(() => this.applyMenuTreeChecked(tree))
|
|
|
|
|
|
|
+ this.$nextTick(() => this.applyMenuTreeChecked(checkedFromApi))
|
|
|
})
|
|
})
|
|
|
}).catch(err => {
|
|
}).catch(err => {
|
|
|
this.$message.error(err.message || '加载菜单失败')
|
|
this.$message.error(err.message || '加载菜单失败')
|
|
@@ -749,6 +788,7 @@ export default {
|
|
|
treeData: [],
|
|
treeData: [],
|
|
|
treeReady: false,
|
|
treeReady: false,
|
|
|
checkedKeys: [],
|
|
checkedKeys: [],
|
|
|
|
|
+ checkedKeysFromApi: [],
|
|
|
checkedKeySet: null,
|
|
checkedKeySet: null,
|
|
|
initialAssignedIds: null,
|
|
initialAssignedIds: null,
|
|
|
loading: false,
|
|
loading: false,
|
|
@@ -889,28 +929,30 @@ export default {
|
|
|
this.pricingDialog.submitting = false
|
|
this.pricingDialog.submitting = false
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
|
- /** 提交菜单编辑 */
|
|
|
|
|
|
|
+ /** 提交菜单编辑:后端为全量替换,须提交当前完整勾选列表 */
|
|
|
submitMenuEdit() {
|
|
submitMenuEdit() {
|
|
|
const tree = this.$refs.menuTree
|
|
const tree = this.$refs.menuTree
|
|
|
if (!tree) return
|
|
if (!tree) return
|
|
|
- const initialAssigned = this.menuDialog.initialAssignedIds || new Set()
|
|
|
|
|
- const currentSelected = tree.getCheckedKeys()
|
|
|
|
|
|
|
+ const selected = tree.getCheckedKeys()
|
|
|
.concat(tree.getHalfCheckedKeys())
|
|
.concat(tree.getHalfCheckedKeys())
|
|
|
.map(id => this.normalizeMenuId(id))
|
|
.map(id => this.normalizeMenuId(id))
|
|
|
.filter(id => id != null)
|
|
.filter(id => id != null)
|
|
|
- const currentSet = new Set(currentSelected)
|
|
|
|
|
- const selected = currentSelected.filter(id => !initialAssigned.has(id))
|
|
|
|
|
- const unSelected = [...initialAssigned].filter(id => !currentSet.has(id))
|
|
|
|
|
- if (selected.length === 0 && unSelected.length === 0) {
|
|
|
|
|
- this.$message.info('菜单无变化')
|
|
|
|
|
- this.menuDialog.visible = false
|
|
|
|
|
|
|
+ const uniqueSelected = [...new Set(selected)]
|
|
|
|
|
+ if (uniqueSelected.length === 0) {
|
|
|
|
|
+ this.$confirm('未勾选任何菜单,保存后将清空该租户已分配的菜单,是否继续?', '提示', {
|
|
|
|
|
+ type: 'warning'
|
|
|
|
|
+ }).then(() => {
|
|
|
|
|
+ this.doSubmitMenuEdit(uniqueSelected)
|
|
|
|
|
+ }).catch(() => {})
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+ this.doSubmitMenuEdit(uniqueSelected)
|
|
|
|
|
+ },
|
|
|
|
|
+ doSubmitMenuEdit(selected) {
|
|
|
this.menuDialog.submitting = true
|
|
this.menuDialog.submitting = true
|
|
|
editTenantMenu(this.menuDialog.companyId, {
|
|
editTenantMenu(this.menuDialog.companyId, {
|
|
|
flag: this.menuDialog.flag,
|
|
flag: this.menuDialog.flag,
|
|
|
- selected: selected,
|
|
|
|
|
- unSelected: unSelected
|
|
|
|
|
|
|
+ selected
|
|
|
}).then(res => {
|
|
}).then(res => {
|
|
|
if (res.code === 200) {
|
|
if (res.code === 200) {
|
|
|
this.$message.success('菜单更新成功')
|
|
this.$message.success('菜单更新成功')
|