Files
tms-erp-web/src/views/authority/role.vue
赵忠林 565553b3f7 refactor(table): 统一调整全仓表格操作列宽度及按钮样式
- 将所有操作按钮数量≤3的表格操作列宽度统一调整为180px
- 调整涉及全局默认值、各模块和页面中menuWidth显式设定
- 单独修改/authority/role页面操作列宽度为240px,/system/menu及流程配置对应调为220px
- 通过CSS新规则视觉统一Avue内置操作按钮为el-link链接样式,无功能变更
- 优化通知公告操作列宽度,解决三按钮链接换行问题
- 调整首页Logo图标尺寸及边距以适配设计需求
- 保证所有相关改动经过npm run build验证通过
2026-08-07 12:02:29 +08:00

662 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<basic-container>
<avue-crud
:option="option"
:table-loading="loading"
:data="data"
ref="crud"
v-model="form"
:permission="permissionList"
:before-open="beforeOpen"
@row-del="rowDel"
@row-update="rowUpdate"
@row-save="rowSave"
@search-change="searchChange"
@search-reset="searchReset"
@selection-change="selectionChange"
@current-change="currentChange"
@size-change="sizeChange"
@refresh-change="refreshChange"
@on-load="onLoad"
>
<template #menu-left>
<el-button
type="danger"
icon="el-icon-delete"
v-if="permission.role_delete"
plain
@click="handleDelete"
>删除
</el-button>
<el-button
icon="el-icon-setting"
@click="handleRole"
v-if="userInfo.authority.includes('admin')"
plain
>权限设置
</el-button>
</template>
<template #menu="{ row }">
<el-link
type="primary"
v-if="userInfo.authority.includes('admin')"
style="border: 0; background-color: transparent !important"
@click.stop="handleRowRole(row)"
>权限设置
</el-link>
</template>
</avue-crud>
<el-dialog
title="角色权限配置"
append-to-body
v-model="box"
width="345px"
@close="handleDialogClose"
>
<el-tabs type="border-card">
<el-tab-pane label="菜单权限">
<el-row
justify="space-between"
align="middle"
style="margin-bottom: 12px; background: #f5f7fa; padding: 6px 10px; border-radius: 4px"
>
<span style="display: inline-flex; align-items: center">
<el-switch
v-model="menuLinked"
active-text="节点联动"
size="small"
@change="handleLinkedChange('treeMenu')"
/>
<el-tooltip content="开启后勾选父节点会自动勾选所有子节点,关闭则可独立勾选任意节点" placement="top">
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon>
</el-tooltip>
</span>
<el-button-group>
<el-button size="small" plain @click="handleSelectAll('treeMenu', menuGrantList)"
>全选</el-button
>
<el-button
size="small"
plain
@click="handleInvertSelect('treeMenu', menuGrantList, menuLinked)"
>反选</el-button
>
</el-button-group>
</el-row>
<el-tree
:data="menuGrantList"
show-checkbox
:check-strictly="!menuLinked"
node-key="id"
ref="treeMenu"
:default-checked-keys="menuTreeObj"
:props="props"
>
</el-tree>
</el-tab-pane>
<el-tab-pane label="数据权限">
<el-row
justify="space-between"
align="middle"
style="margin-bottom: 12px; background: #f5f7fa; padding: 6px 10px; border-radius: 4px"
>
<span style="display: inline-flex; align-items: center">
<el-switch
v-model="dataScopeLinked"
active-text="节点联动"
size="small"
@change="handleLinkedChange('treeDataScope')"
/>
<el-tooltip content="开启后勾选父节点会自动勾选所有子节点,关闭则可独立勾选任意节点" placement="top">
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon>
</el-tooltip>
</span>
<el-button-group>
<el-button
size="small"
plain
@click="handleSelectAll('treeDataScope', dataScopeGrantList)"
>全选</el-button
>
<el-button
size="small"
plain
@click="handleInvertSelect('treeDataScope', dataScopeGrantList, dataScopeLinked)"
>反选</el-button
>
</el-button-group>
</el-row>
<el-tree
:data="dataScopeGrantList"
show-checkbox
:check-strictly="!dataScopeLinked"
node-key="id"
ref="treeDataScope"
:default-checked-keys="dataScopeTreeObj"
:props="props"
>
</el-tree>
</el-tab-pane>
<el-tab-pane label="接口权限">
<el-row
justify="space-between"
align="middle"
style="margin-bottom: 12px; background: #f5f7fa; padding: 6px 10px; border-radius: 4px"
>
<span style="display: inline-flex; align-items: center">
<el-switch
v-model="apiScopeLinked"
active-text="节点联动"
size="small"
@change="handleLinkedChange('treeApiScope')"
/>
<el-tooltip content="开启后勾选父节点会自动勾选所有子节点,关闭则可独立勾选任意节点" placement="top">
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon>
</el-tooltip>
</span>
<el-button-group>
<el-button
size="small"
plain
@click="handleSelectAll('treeApiScope', apiScopeGrantList)"
>全选</el-button
>
<el-button
size="small"
plain
@click="handleInvertSelect('treeApiScope', apiScopeGrantList, apiScopeLinked)"
>反选</el-button
>
</el-button-group>
</el-row>
<el-tree
:data="apiScopeGrantList"
show-checkbox
:check-strictly="!apiScopeLinked"
node-key="id"
ref="treeApiScope"
:default-checked-keys="apiScopeTreeObj"
:props="props"
>
</el-tree>
</el-tab-pane>
</el-tabs>
<template #footer>
<span class="dialog-footer">
<el-button @click="box = false"> </el-button>
<el-button type="primary" @click="submit"> </el-button>
</span>
</template>
</el-dialog>
</basic-container>
</template>
<script>
import {
add,
getList,
getRole,
getRoleAlias,
getRoleTreeById,
grant,
grantTree,
remove,
update,
} from '@/api/system/role';
import { mapGetters } from 'vuex';
import website from '@/config/website';
import { validatenull } from '@/utils/validate';
export default {
data() {
return {
form: {},
box: false,
props: {
label: 'title',
value: 'key',
},
menuGrantList: [],
dataScopeGrantList: [],
apiScopeGrantList: [],
apiGrantList: [],
menuTreeObj: [],
dataScopeTreeObj: [],
apiScopeTreeObj: [],
menuLinked: false,
dataScopeLinked: false,
apiScopeLinked: false,
currentRoleIds: [],
selectionList: [],
query: {},
loading: true,
page: {
pageSize: 10,
currentPage: 1,
total: 0,
},
option: {
tip: false,
simplePage: true,
searchShow: true,
searchMenuSpan: 24,
searchIcon: true,
searchIndex: 8,
searchMenuPosition: 'right',
tree: true,
border: true,
index: true,
selection: true,
viewBtn: true,
labelWidth: 'auto',
searchLabelWidth: 160,
menuWidth: 240,
dialogWidth: 600,
dialogClickModal: false,
column: [
{
label: '上级角色',
prop: 'parentId',
dicData: [],
type: 'tree',
hide: true,
span: 24,
props: {
label: 'title',
},
rules: [
{
required: false,
message: '请选择上级角色',
trigger: 'click',
},
],
},
{
label: '角色名称',
prop: 'roleName',
search: true,
span: 24,
rules: [
{
required: true,
message: '请输入角色名称',
trigger: 'blur',
},
],
},
{
label: '所属租户',
prop: 'tenantId',
type: 'tree',
dicUrl: '/blade-system/tenant/select',
addDisplay: false,
editDisplay: false,
viewDisplay: website.tenantMode,
span: 24,
props: {
label: 'tenantName',
value: 'tenantId',
},
hide: !website.tenantMode,
search: website.tenantMode,
rules: [
{
required: true,
message: '请输入所属租户',
trigger: 'click',
},
],
},
{
label: '角色别名',
labelTip: '用于权限校验与标注的属性,可手动录入也可点击现有别名快速录入',
prop: 'roleAlias',
search: true,
span: 24,
rules: [
{
required: true,
message: '请输入角色别名',
trigger: 'blur',
},
],
},
{
label: '现有别名',
labelTip: '当前系统已存在的角色别名,点击可快速录入',
prop: 'currentAlias',
type: 'radio',
dicData: [],
props: {
label: 'roleName',
value: 'roleAlias',
},
hide: true,
span: 24,
},
{
label: '角色排序',
prop: 'sort',
type: 'number',
span: 24,
rules: [
{
required: true,
message: '请输入角色排序',
trigger: 'blur',
},
],
},
],
},
data: [],
};
},
computed: {
...mapGetters(['userInfo', 'permission']),
permissionList() {
return {
addBtn: this.validData(this.permission.role_add, false),
viewBtn: this.validData(this.permission.role_view, false),
delBtn: this.validData(this.permission.role_delete, false),
editBtn: this.validData(this.permission.role_edit, false),
};
},
ids() {
let ids = [];
this.selectionList.forEach(ele => {
ids.push(ele.id);
});
return ids.join(',');
},
idsArray() {
let ids = [];
this.selectionList.forEach(ele => {
ids.push(ele.id);
});
return ids;
},
},
watch: {
'form.currentAlias'() {
const alias = this.form.currentAlias;
if (!validatenull(alias)) {
this.form.roleAlias = alias;
}
},
},
methods: {
initData(roleId) {
getRoleTreeById(roleId).then(res => {
const column = this.findColumn(this.option.column, 'parentId');
column.dicData = res.data.data;
});
},
getAllNodeKeys(nodes) {
let keys = [];
nodes.forEach(node => {
keys.push(node.id);
if (node.children && node.children.length > 0) {
keys = keys.concat(this.getAllNodeKeys(node.children));
}
});
return keys;
},
getLeafKeys(nodes) {
let keys = [];
nodes.forEach(node => {
if (!node.children || node.children.length === 0) {
keys.push(node.id);
} else {
keys = keys.concat(this.getLeafKeys(node.children));
}
});
return keys;
},
handleSelectAll(treeRef, dataList) {
const tree = this.$refs[treeRef];
if (!tree) return;
const allKeys = this.getAllNodeKeys(dataList);
tree.setCheckedKeys(allKeys);
},
handleInvertSelect(treeRef, dataList, isLinked) {
const tree = this.$refs[treeRef];
if (!tree) return;
const checkedKeys = new Set(tree.getCheckedKeys());
if (isLinked) {
const leafKeys = this.getLeafKeys(dataList);
const invertedKeys = leafKeys.filter(key => !checkedKeys.has(key));
tree.setCheckedKeys(invertedKeys);
} else {
const allKeys = this.getAllNodeKeys(dataList);
const invertedKeys = allKeys.filter(key => !checkedKeys.has(key));
tree.setCheckedKeys(invertedKeys);
}
},
handleLinkedChange(treeRef) {
const tree = this.$refs[treeRef];
if (!tree) return;
const checkedKeys = tree.getCheckedKeys();
const halfCheckedKeys = tree.getHalfCheckedKeys();
this.$nextTick(() => {
tree.setCheckedKeys([...checkedKeys, ...halfCheckedKeys]);
});
},
// el-tree 会把 setCheckedKeys 的入参记录为默认勾选集并在树数据重载时回放,
// 复用的树实例需在打开配置时整体覆盖勾选状态,避免残留上一次配置的勾选
applyCheckedKeys(treeRef, keys) {
const tree = this.$refs[treeRef];
if (!tree) return;
tree.setCheckedKeys(keys);
},
submit() {
const menuList = this.$refs.treeMenu.getCheckedKeys();
const dataScopeList = this.$refs.treeDataScope.getCheckedKeys();
const apiScopeList = this.$refs.treeApiScope.getCheckedKeys();
grant(this.currentRoleIds, menuList, dataScopeList, apiScopeList).then(() => {
this.box = false;
this.$message({
type: 'success',
message: '操作成功!',
});
this.currentRoleIds = [];
this.selectionList = [];
this.onLoad(this.page);
});
},
rowSave(row, done, loading) {
add(this.withTenantId(row)).then(
() => {
this.onLoad(this.page);
this.$message({
type: 'success',
message: '操作成功!',
});
done();
},
error => {
window.console.log(error);
loading();
}
);
},
rowUpdate(row, index, done, loading) {
update(this.withTenantId(row)).then(
() => {
this.onLoad(this.page);
this.$message({
type: 'success',
message: '操作成功!',
});
done();
},
error => {
window.console.log(error);
loading();
}
);
},
withTenantId(row) {
return {
...row,
tenantId: row.tenantId || this.userInfo.tenantId || website.tenantId,
};
},
rowDel(row) {
this.$confirm('确定将选择数据删除?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
return remove(row.id);
})
.then(() => {
this.onLoad(this.page);
this.$message({
type: 'success',
message: '操作成功!',
});
});
},
searchReset() {
this.query = {};
this.onLoad(this.page);
},
searchChange(params, done) {
this.query = params;
this.page.currentPage = 1;
this.onLoad(this.page, params);
done();
},
selectionChange(list) {
this.selectionList = list;
},
selectionClear() {
this.selectionList = [];
this.$refs.crud.toggleSelection();
},
beforeOpen(done, type) {
if (['add', 'edit'].includes(type)) {
this.initData(this.form.id);
}
if (['edit', 'view'].includes(type)) {
if (this.form.parentId === '0') {
this.form.parentId = '';
}
}
getRoleAlias().then(res => {
const column = this.findColumn(this.option.column, 'currentAlias');
column.dicData = res.data.data;
});
done();
},
handleRole() {
if (this.selectionList.length === 0) {
this.$message.warning('请选择一条数据');
return;
}
if (this.selectionList.length > 1) {
this.$message.warning('只能选择一条数据');
return;
}
this.menuTreeObj = [];
this.dataScopeTreeObj = [];
this.apiScopeTreeObj = [];
// 保存当前要配置权限的角色ID
this.currentRoleIds = this.idsArray;
grantTree().then(res => {
this.menuGrantList = res.data.data.menu;
this.dataScopeGrantList = res.data.data.dataScope;
this.apiScopeGrantList = res.data.data.apiScope;
getRole(this.ids).then(res => {
this.menuTreeObj = res.data.data.menu;
this.dataScopeTreeObj = res.data.data.dataScope;
this.apiScopeTreeObj = res.data.data.apiScope;
this.box = true;
this.$nextTick(() => {
this.applyCheckedKeys('treeMenu', this.menuTreeObj);
this.applyCheckedKeys('treeDataScope', this.dataScopeTreeObj);
this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj);
});
});
});
},
handleRowRole(row) {
this.menuTreeObj = [];
this.dataScopeTreeObj = [];
this.apiScopeTreeObj = [];
// 保存当前要配置权限的角色ID单个行操作
this.currentRoleIds = [row.id];
grantTree().then(res => {
this.menuGrantList = res.data.data.menu;
this.dataScopeGrantList = res.data.data.dataScope;
this.apiScopeGrantList = res.data.data.apiScope;
getRole(row.id).then(res => {
this.menuTreeObj = res.data.data.menu;
this.dataScopeTreeObj = res.data.data.dataScope;
this.apiScopeTreeObj = res.data.data.apiScope;
this.box = true;
this.$nextTick(() => {
this.applyCheckedKeys('treeMenu', this.menuTreeObj);
this.applyCheckedKeys('treeDataScope', this.dataScopeTreeObj);
this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj);
});
});
});
},
handleDialogClose() {
// 对话框关闭时清空选择状态和权限树数据
this.menuTreeObj = [];
this.dataScopeTreeObj = [];
this.apiScopeTreeObj = [];
this.menuLinked = false;
this.dataScopeLinked = false;
this.apiScopeLinked = false;
this.selectionClear();
},
handleDelete() {
if (this.selectionList.length === 0) {
this.$message.warning('请选择至少一条数据');
return;
}
this.$confirm('确定将选择数据删除?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
return remove(this.ids);
})
.then(() => {
this.onLoad(this.page);
this.$message({
type: 'success',
message: '操作成功!',
});
this.$refs.crud.toggleSelection();
});
},
currentChange(currentPage) {
this.page.currentPage = currentPage;
},
sizeChange(pageSize) {
this.page.pageSize = pageSize;
},
refreshChange() {
this.onLoad(this.page, this.query);
},
onLoad(page, params = {}) {
this.loading = true;
getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
this.data = res.data.data;
this.loading = false;
this.selectionClear();
});
},
},
};
</script>