feat: 新增角色管理模块,优化API与交互体验
Some checks failed
Release pipeline / publish (push) Has been cancelled
Release pipeline / Always run job (push) Has been cancelled

1.  新增角色管理后台页面、路由与国际化文案
2.  重构API请求错误处理逻辑,统一拦截业务与HTTP错误
3.  新增确认弹窗组合式函数,区分取消与请求错误场景
4.  完善表格按钮权限与显示控制逻辑
5.  更新API参数规范与文档说明
6.  修复部分页面分页数据解析问题
This commit is contained in:
sheng
2026-05-28 19:16:05 +08:00
parent ba43de8f4b
commit a61036e5dc
14 changed files with 999 additions and 45 deletions

View File

@@ -0,0 +1,151 @@
<template>
<el-drawer
:visible.sync="visibleProxy"
:title="$t(title)"
:size="width"
:close-on-click-modal="false"
direction="rtl"
@close="onClose"
>
<div class="perm-drawer__body" v-loading="treeLoading">
<el-tree
v-if="treeReady"
ref="permTree"
node-key="menu_id"
:data="treeData"
:props="{ label: 'name', children: 'children' }"
:default-expand-all="false"
:expand-on-click-node="false"
:check-strictly="true"
show-checkbox
@check="onTreeCheck"
>
<span slot-scope="{ node, data }" class="perm-drawer__tree-node">
<span :class="{ 'perm-drawer__tree-node--disabled': !data.status }">
<i v-if="data.icon" :class="`fa fa-${data.icon}`" />
<i v-else-if="data.children" :class="`el-icon-${node.expanded ? 'folder-opened' : 'folder'}`" />
<i v-else class="el-icon-document" />
{{ $t(data.name) }}
</span>
</span>
</el-tree>
</div>
<div class="perm-drawer__footer">
<el-button size="mini" @click="onCancel">
{{ $t(cancelText) }}
</el-button>
<el-button type="primary" size="mini" :loading="submitting" @click="onSubmit">
{{ $t(confirmText) }}
</el-button>
</div>
</el-drawer>
</template>
<script>
import { i18nMixin } from '@/composables/useI18n'
import util from '@/libs/util'
import { getMenuAll } from '@/api/menu'
import { giveRoleMenu, getRoleMenu } from '@/api/system-administration/role'
export default {
name: 'RolePermDrawer',
mixins: [i18nMixin('page.system_administration.user_management.role')],
props: {
visible: { type: Boolean, default: false },
roleId: { type: Number, default: 0 },
title: { type: String, default: '' },
confirmText: { type: String, default: '' },
cancelText: { type: String, default: '' },
width: { type: String, default: '360px' }
},
data () {
return {
treeLoading: false,
submitting: false,
treeReady: false,
treeData: [],
checkedMenuIds: []
}
},
computed: {
visibleProxy: {
get () { return this.visible },
set (val) { this.$emit('update:visible', val) }
}
},
watch: {
visible (val) {
if (val) { this.loadData() }
}
},
methods: {
async loadData () {
this.treeReady = false
this.treeLoading = true
try {
const [menuRes, roleMenuRes] = await Promise.all([
getMenuAll()
])
const menuData = Array.isArray(menuRes) ? menuRes : (menuRes.data || [])
const roleData = Array.isArray(roleMenuRes) ? roleMenuRes : (roleMenuRes.data || [])
this.checkedMenuIds = roleData.map(item => item.menu_id)
this.treeData = util.formatDataToTree(menuData)
this.treeReady = true
await this.$nextTick()
await new Promise(resolve => setTimeout(resolve, 50))
this.$refs.permTree.setCheckedKeys(this.checkedMenuIds)
} finally {
this.treeLoading = false
}
},
onTreeCheck () {},
onClose () {
this.treeReady = false
this.treeData = []
this.checkedMenuIds = []
},
onCancel () {
this.visibleProxy = false
},
async onSubmit () {
this.submitting = true
try {
const menuIds = this.$refs.permTree.getCheckedKeys()
await giveRoleMenu({ role_id: this.roleId, role_menu: menuIds })
this.$message.success(this.$t(this.key('operation_success')))
this.visibleProxy = false
this.$emit('saved')
} finally {
this.submitting = false
}
}
}
}
</script>
<style scoped>
.perm-drawer__body {
height: calc(100vh - 140px);
overflow-y: auto;
padding: 10px 20px;
}
.perm-drawer__footer {
position: absolute;
bottom: 0;
right: 0;
left: 0;
padding: 10px 20px;
border-top: 1px solid #e8e8e8;
background: #fff;
text-align: right;
}
.perm-drawer__tree-node {
display: flex;
align-items: center;
gap: 5px;
}
.perm-drawer__tree-node--disabled {
color: #c0c4cc;
}
</style>