<template>
|
<div class="page">
|
<div class="page-title">
|
<div><h2>菜单权限管理</h2><p>同一菜单必须同时获得租户授权和角色授权,用户才能访问;第一版 admin 直接显示全部菜单</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>
|
<div class="spacer"></div>
|
<el-tag type="success">最终可用菜单:{{ effectiveCount }} 个</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>
|
</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: [] } },
|
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)) }
|
},
|
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 || []
|
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 => {
|
const data = res.data || {}
|
this.selectedTenantIds = data.tenantMenuIds || []
|
this.selectedRoleIds = data.roleMenuIds || []
|
this.$nextTick(() => {
|
this.$refs.tenantTree.setCheckedKeys(this.selectedTenantIds)
|
this.$refs.roleTree.setCheckedKeys(this.selectedRoleIds)
|
})
|
})
|
},
|
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 : [])
|
},
|
save() {
|
const tenantMenuIds = this.$refs.tenantTree.getCheckedKeys()
|
const roleMenuIds = this.$refs.roleTree.getCheckedKeys()
|
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('菜单权限已保存')
|
}).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; } }
|
</style>
|