From 0642bc9c44d1c8277ada33059c4a3e6156335b30 Mon Sep 17 00:00:00 2001
From: zzw <228544128@qq.com>
Date: 星期二, 06 十月 2026 14:48:37 +0800
Subject: [PATCH] feat: use role-only menu permissions
---
emergency-repair-web/src/views/system/MenuPermission.vue | 147 +++++++++++++++++++++++++++++++++---------------
1 files changed, 101 insertions(+), 46 deletions(-)
diff --git a/emergency-repair-web/src/views/system/MenuPermission.vue b/emergency-repair-web/src/views/system/MenuPermission.vue
index ad5033f..e69de15 100644
--- a/emergency-repair-web/src/views/system/MenuPermission.vue
+++ b/emergency-repair-web/src/views/system/MenuPermission.vue
@@ -1,85 +1,140 @@
<template>
<div class="page">
<div class="page-title">
- <div><h2>鑿滃崟鏉冮檺绠$悊</h2><p>鍚屼竴鑿滃崟蹇呴』鍚屾椂鑾峰緱绉熸埛鎺堟潈鍜岃鑹叉巿鏉冿紝鐢ㄦ埛鎵嶈兘璁块棶锛涚涓�鐗� admin 鐩存帴鏄剧ず鍏ㄩ儴鑿滃崟</p></div>
+ <div><h2>鑿滃崟鏉冮檺绠$悊</h2><p>鎸夎鑹茶缃彍鍗曞拰鎸夐挳鏉冮檺锛屼繚瀛樺悗鍒囨崲瑙掕壊浼氳嚜鍔ㄥ弽鏄俱��</p></div>
</div>
<div class="toolbar">
- <span>绉熸埛</span>
- <el-select v-model="tenantId" style="width:220px" @change="loadPermissions"><el-option v-for="item in tenants" :key="item.id" :label="item.name" :value="item.id"></el-option></el-select>
<span>瑙掕壊</span>
- <el-select v-model="roleId" style="width:220px" @change="loadPermissions"><el-option v-for="item in roles" :key="item.id" :label="item.name" :value="item.id"></el-option></el-select>
+ <el-select v-model="roleId" style="width:260px" @change="loadPermissions"><el-option v-for="item in roles" :key="item.id" :label="item.name" :value="item.id"></el-option></el-select>
<div class="spacer"></div>
- <el-tag type="success">鏈�缁堝彲鐢ㄨ彍鍗曪細{{ effectiveCount }} 涓�</el-tag>
- <el-button type="primary" :loading="saving" @click="save">淇濆瓨鍙屾巿鏉�</el-button>
+ <el-tag type="success">宸查�夋潈闄愶細{{ selectedRoleIds.length }} 涓�</el-tag>
+ <el-button type="primary" :loading="saving" @click="save">淇濆瓨瑙掕壊鏉冮檺</el-button>
</div>
- <div class="permission-grid">
- <section class="permission-panel">
- <div class="panel-head"><h3>绉熸埛鑿滃崟鎺堟潈</h3><el-checkbox v-model="tenantCheckAll" @change="checkAll('tenant')">鍏ㄩ��</el-checkbox></div>
- <el-tree ref="tenantTree" :data="menus" node-key="id" show-checkbox check-strictly default-expand-all :props="{ label: 'name', children: 'children' }"></el-tree>
- </section>
- <section class="permission-panel">
- <div class="panel-head"><h3>瑙掕壊鑿滃崟鎺堟潈</h3><el-checkbox v-model="roleCheckAll" @change="checkAll('role')">鍏ㄩ��</el-checkbox></div>
- <el-tree ref="roleTree" :data="menus" node-key="id" show-checkbox check-strictly default-expand-all :props="{ label: 'name', children: 'children' }"></el-tree>
- </section>
- <section class="permission-panel intersection-panel">
- <div class="panel-head"><h3>浜ら泦棰勮</h3></div>
- <div class="intersection-list"><el-tag v-for="item in effectiveMenus" :key="item.id" size="small">{{ item.name }}</el-tag></div>
- </section>
- </div>
+ <section class="permission-panel">
+ <div class="panel-head"><h3>瑙掕壊鑿滃崟鍙婃寜閽潈闄�</h3><el-checkbox v-model="roleCheckAll" @change="checkAll">鍏ㄩ��</el-checkbox></div>
+ <el-tree ref="roleTree" :data="menus" node-key="id" show-checkbox default-expand-all :render-content="renderContent" :props="{ label: 'name', children: 'children' }" @check="syncCheckAllState"></el-tree>
+ </section>
</div>
</template>
<script>
import api from '@/api'
export default {
name: 'MenuPermission',
- data() { return { menus: [], flatMenus: [], roles: [], tenants: [], tenantId: 1, roleId: null, saving: false, tenantCheckAll: false, roleCheckAll: false, selectedTenantIds: [], selectedRoleIds: [] } },
+ data() {
+ return {
+ menus: [],
+ flatMenus: [],
+ roles: [],
+ roleId: null,
+ saving: false,
+ roleCheckAll: false,
+ selectedRoleIds: []
+ }
+ },
computed: {
- allMenuIds() { return this.flatMenus.map(item => item.id) },
- effectiveCount() { return this.effectiveMenus.length },
- effectiveMenus() { return this.flatMenus.filter(item => this.selectedTenantIds.includes(item.id) && this.selectedRoleIds.includes(item.id)) }
+ allMenuIds() { return this.flatMenus.map(item => String(item.id)) }
},
created() { this.loadBase() },
methods: {
loadBase() {
- Promise.all([api.get('/system/menu/nav'), api.get('/system/menu/list'), api.get('/system/roles'), api.get('/system/tenants')]).then(([tree, flat, roles, tenants]) => {
- this.menus = tree.data || []; this.flatMenus = flat.data || []; this.roles = roles.data || []; this.tenants = tenants.data || []
+ Promise.all([api.get('/system/menu/tree'), api.get('/system/menu/list'), api.get('/system/roles')]).then(([tree, flat, roles]) => {
+ this.menus = tree.data || []
+ this.flatMenus = flat.data || []
+ this.roles = roles.data || []
if (!this.roleId && this.roles.length) this.roleId = this.roles[0].id
this.$nextTick(this.loadPermissions)
})
},
loadPermissions() {
- if (!this.roleId || !this.tenantId) return
- api.get('/system/menu-permissions', { params: { roleId: this.roleId, tenantId: this.tenantId } }).then(res => {
+ if (!this.roleId) {
+ this.selectedRoleIds = []
+ this.roleCheckAll = false
+ if (this.$refs.roleTree) this.$refs.roleTree.setCheckedKeys([])
+ return
+ }
+ api.get('/system/menu-permissions', { params: { roleId: this.roleId } }).then(res => {
const data = res.data || {}
- this.selectedTenantIds = data.tenantMenuIds || []
- this.selectedRoleIds = data.roleMenuIds || []
+ this.selectedRoleIds = (data.roleMenuIds || []).map(String)
this.$nextTick(() => {
- this.$refs.tenantTree.setCheckedKeys(this.selectedTenantIds)
- this.$refs.roleTree.setCheckedKeys(this.selectedRoleIds)
+ this.$refs.roleTree.setCheckedKeys(this.displayCheckedKeys(this.selectedRoleIds))
+ this.syncCheckAllState()
})
})
},
- checkAll(target) {
- const checked = target === 'tenant' ? this.tenantCheckAll : this.roleCheckAll
- const ref = target === 'tenant' ? this.$refs.tenantTree : this.$refs.roleTree
- ref.setCheckedKeys(checked ? this.allMenuIds : [])
+ renderContent(h, { data }) {
+ const label = data.menuType === 2 ? data.name + ' [鎸夐挳 ' + (data.permissions || '') + ']' : data.name
+ return h('span', { class: { 'perm-button-node': data.menuType === 2 } }, label)
+ },
+ displayCheckedKeys(ids) {
+ const selected = new Set((ids || []).map(String))
+ const children = {}
+ const menuIds = new Set(this.flatMenus.map(item => String(item.id)))
+ this.flatMenus.forEach(item => {
+ const id = String(item.id)
+ const pid = item.pid == null ? '0' : String(item.pid)
+ if (!children[pid]) children[pid] = []
+ children[pid].push(id)
+ })
+ const result = []
+ const collectFullySelected = id => {
+ if (!selected.has(id)) return false
+ const childIds = children[id] || []
+ if (!childIds.length) {
+ result.push(id)
+ return true
+ }
+ const allChildrenSelected = childIds.map(collectFullySelected).every(Boolean)
+ if (allChildrenSelected) result.push(id)
+ return allChildrenSelected
+ }
+ this.flatMenus.filter(item => item.pid == null || String(item.pid) === '0' || !menuIds.has(String(item.pid))).forEach(item => collectFullySelected(String(item.id)))
+ return result
+ },
+ checkedMenuIds(ref) { return [...new Set([...(ref.getCheckedKeys(false) || []), ...(ref.getHalfCheckedKeys() || [])].map(String))] },
+ syncCheckAllState() {
+ const ref = this.$refs.roleTree
+ if (!ref) return
+ this.roleCheckAll = this.checkedMenuIds(ref).length === this.allMenuIds.length
+ },
+ checkAll() {
+ this.$refs.roleTree.setCheckedKeys(this.roleCheckAll ? this.allMenuIds : [])
+ this.$nextTick(this.syncCheckAllState)
},
save() {
- const tenantMenuIds = this.$refs.tenantTree.getCheckedKeys()
- const roleMenuIds = this.$refs.roleTree.getCheckedKeys()
+ if (!this.roleId) {
+ this.$message.warning('璇峰厛閫夋嫨瑙掕壊')
+ return
+ }
+ const roleMenuIds = this.checkedMenuIds(this.$refs.roleTree)
this.saving = true
- api.post('/system/menu-permissions', { tenantId: this.tenantId, roleId: this.roleId, tenantMenuIds, roleMenuIds }).then(() => {
- this.selectedTenantIds = tenantMenuIds; this.selectedRoleIds = roleMenuIds; this.$message.success('鑿滃崟鏉冮檺宸蹭繚瀛�')
+ api.post('/system/menu-permissions', { roleId: this.roleId, roleMenuIds }).then(() => {
+ this.selectedRoleIds = roleMenuIds
+ this.$message.success('瑙掕壊鑿滃崟鏉冮檺宸蹭繚瀛�')
}).finally(() => { this.saving = false })
}
}
}
</script>
<style scoped>
-.permission-grid { display: grid; grid-template-columns: 1fr 1fr 320px; gap: 12px; }
-.permission-panel { min-height: calc(100vh - 180px); padding: 14px; border: 1px solid #dce4ec; border-radius: 4px; background: #fff; overflow: auto; }
-.panel-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
-.panel-head h3 { margin: 0; color: #31465d; font-size: 15px; }
-.intersection-list { display: flex; flex-wrap: wrap; gap: 6px; }
-@media (max-width: 1100px) { .permission-grid { grid-template-columns: 1fr; } }
+::v-deep .perm-button-node { color: #b8791f; }
+
+.permission-panel {
+ min-height: calc(100vh - 220px);
+ padding: 14px;
+ border: 1px solid #dce4ec;
+ border-radius: 4px;
+ background: #fff;
+ overflow: auto;
+}
+.panel-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 12px;
+}
+.panel-head h3 {
+ margin: 0;
+ color: #31465d;
+ font-size: 15px;
+}
</style>
--
Gitblit v1.9.1