feat: 完成角色权限的功能权限和用户分配

This commit is contained in:
Zzz1zz
2025-07-14 01:15:52 +08:00
parent 293beb9f5d
commit c5588374ba
12 changed files with 1562 additions and 133 deletions
+195 -129
View File
@@ -1,6 +1,30 @@
import axios from 'axios'; import axios from 'axios';
import type { UserPermission } from '@/types/api';
import { HttpResponse } from './interceptor'; import { HttpResponse } from './interceptor';
// ==================== 通用类型定义 ====================
export interface BaseEntity {
id: number;
createdAt: string;
updatedAt: string;
}
export interface PaginationParams {
role_id?: number | null;
page?: number;
page_size?: number;
}
export interface PaginationResult {
total?: number;
total_pages?: number;
}
export type UserStatus = 1 | 2; // 1: 启用, 2: 禁用
export type ManagerStatus = 1 | 2; // 1: 是主管, 2: 否
export type DataType = 1 | 2 | 3; // 1: 全部数据权限, 2: 自定义数据权限, 3: 仅本人数据
// ==================== 部门相关类型 ====================
export interface DepartmentFormParams { export interface DepartmentFormParams {
parent_id: number | null; parent_id: number | null;
name: string; name: string;
@@ -12,21 +36,8 @@ export interface DepartmentFormParams {
export interface DepartmentIdData { export interface DepartmentIdData {
departmentId: number | string; departmentId: number | string;
} }
type DepartmentFormRes = HttpResponse<DepartmentIdData>;
// 创建部门 export interface DepartmentItem extends BaseEntity {
export function createDepartment(
params: DepartmentFormParams
): Promise<DepartmentFormRes> {
return axios.post('/department/createDepartment', params);
}
// 更新(编辑)部门
export function updateDepartment(
params: DepartmentFormParams
): Promise<DepartmentFormRes> {
return axios.post('/department/updateDepartment', params);
}
export interface DepartmentItem {
id: number;
systemTenantId: number; systemTenantId: number;
name: string; name: string;
parentId: number; parentId: number;
@@ -35,24 +46,127 @@ export interface DepartmentItem {
path: string; path: string;
remark: string; remark: string;
isDelete: number; isDelete: number;
createdAt: string;
updatedAt: string;
sort: number; sort: number;
children?: DepartmentItem[]; // 递归结构 children?: DepartmentItem[]; // 递归结构
isShowEditIcon?: boolean; // 是否展示编辑icon isShowEditIcon?: boolean; // 是否展示编辑icon
username?: string; username?: string;
parentName?: string; parentName?: string;
} }
export interface DepartmentDetailData {
department: DepartmentItem;
}
// ==================== 用户相关类型 ====================
export interface UserItem {
id: number;
username: string;
realname: string;
email: string;
tel: string;
status: UserStatus;
roleId: number;
departmentId: number;
roleName: string;
deptName: string;
lastLoginTime: string;
}
export interface FormUserItem {
username: string;
realname: string;
tel: string | number;
password: string;
repassword: string;
role_id: number | null;
department_id: number | null;
email: string;
is_manager: ManagerStatus;
}
export interface DisableUserParam {
id: number;
status: UserStatus;
}
export interface UserInfo {
user: FormUserItem;
}
export interface AllUserList {
page: PaginationParams & PaginationResult;
user?: UserItem[];
userList?: UserItem[];
}
// ==================== 角色相关类型 ====================
export interface RoleItem extends BaseEntity {
systemTenantId: number;
name: string;
createUserId?: number;
isDelete?: number;
remark?: string;
dataType: DataType;
}
export interface RoleFormParam {
id?: number;
name: string;
remark?: string;
}
export interface RoleList {
rolesList: RoleItem[];
}
export interface UserPermissionItem {
permission: UserPermission;
}
export interface UserPermissionItemList {
permission: number[];
}
export interface SaveRolePermissionParam {
id: number;
children?: Array<{
id: number;
path: string;
}>;
}
export interface UpdateUserRoleParam {
role_id: number | null;
user_id: number | null;
}
// ==================== 响应类型别名 ====================
export type DepartmentFormRes = HttpResponse<DepartmentIdData>;
export type DepartmentList = DepartmentItem[]; export type DepartmentList = DepartmentItem[];
export type DepartmentListRes = HttpResponse<{ export type DepartmentListRes = HttpResponse<{
departmentList: DepartmentItem[]; departmentList: DepartmentItem[];
}>; }>;
export interface DepartmentDetailData {
department: DepartmentItem;
}
export type DepartmentDetailRes = HttpResponse<DepartmentDetailData>; export type DepartmentDetailRes = HttpResponse<DepartmentDetailData>;
export type UserList = UserItem[];
export type UserListRes = HttpResponse<UserList>;
export type AllUserListRes = HttpResponse<AllUserList>;
export type UserInfoRes = HttpResponse<UserInfo>;
export type RoleListRes = HttpResponse<RoleList>;
export type PermissionRes = HttpResponse<UserPermissionItem>;
export type RolePermissionRes = HttpResponse<UserPermissionItemList>;
// ==================== 部门相关 API ====================
export function createDepartment(
params: DepartmentFormParams
): Promise<DepartmentFormRes> {
return axios.post('/department/createDepartment', params);
}
export function updateDepartment(
params: DepartmentFormParams
): Promise<DepartmentFormRes> {
return axios.post('/department/updateDepartment', params);
}
// 获取部门列表
export function getDepartmentList(): Promise<DepartmentListRes> { export function getDepartmentList(): Promise<DepartmentListRes> {
return axios.get('/department/getDepartmentList'); return axios.get('/department/getDepartmentList');
} }
@@ -61,137 +175,89 @@ export function getDepartmentDetail(id: number): Promise<DepartmentDetailRes> {
return axios.get(`/department/getDepartment?id=${id}`); return axios.get(`/department/getDepartment?id=${id}`);
} }
export interface UserItem {
id: number; // 用户id
username: string; // 用户名称
realname: string; // 姓名
email: string; // 邮箱
tel: string; // 手机号
status: 1 | 2; // 状态:1 启用,2 禁用
roleId: number; // 角色id
departmentId: number; // 部门id
roleName: string; // 角色名称
deptName: string; // 部门名称
lastLoginTime: string; // 最后登录时间
}
export type UserList = UserItem[];
export type UserListRes = HttpResponse<UserList>;
export interface Pagination {
page: number;
page_size: number;
total?: number;
total_pages?: number;
}
export interface AllUserList {
page: Pagination;
user: UserListRes;
}
export type AllUserListRes = HttpResponse<AllUserList>;
// 后台-用户列表(除主管外所有用户)
export function getAllUserList(params): Promise<AllUserListRes> {
return axios.post('/user/getAllUserList', params);
}
// 后台-用户列表(所有用户)
export function getUserList(params): Promise<AllUserListRes> {
return axios.post('/user/getUserList', params);
}
export interface FormUserItem {
username: string; // 用户名
realname: string; // 姓名
tel: string | number; // 手机号
password: string; // 密码
repassword: string; // 确认密码
role_id: number | null; // 角色id
department_id: number | null; // 部门id
email: string; // 邮件
is_manager: 1 | 2; // 是否为主管:1是,2否
}
// 创建用户
export function createUser(params: FormUserItem): Promise<HttpResponse> {
return axios.post('/user/createUser', params);
}
// 更新用户信息
export function updateUser(params: FormUserItem): Promise<HttpResponse> {
return axios.post('/user/updateUser', params);
}
interface UserInfo {
user: FormUserItem;
}
export type UserInfoRES = HttpResponse<UserInfo>;
// 获取用户信息
export function getUserInfo(id: number): Promise<UserInfoRES> {
return axios.get(`/user/getUser?id=${id}`);
}
// 删除用户
export function delUser(id: number): Promise<HttpResponse> {
return axios.get(`/user/delUser?id=${id}`);
}
export interface DisableUserParam {
id: number;
status: number;
}
// 禁用启用用户
export function disableUser(params: DisableUserParam): Promise<HttpResponse> {
return axios.post('/user/disableUser', params);
}
// 部门-向上移动
export function departmentSetUp(id: number): Promise<HttpResponse> { export function departmentSetUp(id: number): Promise<HttpResponse> {
return axios.get(`/department/setUp?id=${id}`); return axios.get(`/department/setUp?id=${id}`);
} }
// 部门-向下移动
export function departmentSetDown(id: number): Promise<HttpResponse> { export function departmentSetDown(id: number): Promise<HttpResponse> {
return axios.get(`/department/setDown?id=${id}`); return axios.get(`/department/setDown?id=${id}`);
} }
// 删除部门
export function delDepartment(id: number): Promise<HttpResponse> { export function delDepartment(id: number): Promise<HttpResponse> {
return axios.get(`/department/delDepartment?id=${id}`); return axios.get(`/department/delDepartment?id=${id}`);
} }
export interface RoleItem { // ==================== 用户相关 API ====================
id: number; // 角色id export function getAllUserList(
systemTenantId: number; // 租户名称 params: PaginationParams
name: string; // 角色名称 ): Promise<AllUserListRes> {
createUserId?: number; // 创建人id return axios.post('/user/getAllUserList', params);
isDelete?: number; // 是否删除
remark?: string;
dataType: number; // 数据权限类型:1全部数据权限,2自定义数据权限,3仅本人数据
createdAt: string;
updatedAt: string;
} }
// 角色列表
export interface RoleList { export function getUserList(params: PaginationParams): Promise<AllUserListRes> {
rolesList: RoleItem[]; return axios.post('/user/getUserList', params);
} }
export type RoleListRes = HttpResponse<RoleList>;
// 角色列表 export function createUser(params: FormUserItem): Promise<HttpResponse> {
return axios.post('/user/createUser', params);
}
export function updateUser(params: FormUserItem): Promise<HttpResponse> {
return axios.post('/user/updateUser', params);
}
export function getUserInfo(id: number): Promise<UserInfoRes> {
return axios.get(`/user/getUser?id=${id}`);
}
export function delUser(id: number): Promise<HttpResponse> {
return axios.get(`/user/delUser?id=${id}`);
}
export function disableUser(params: DisableUserParam): Promise<HttpResponse> {
return axios.post('/user/disableUser', params);
}
// ==================== 角色相关 API ====================
export function getRoleList(): Promise<RoleListRes> { export function getRoleList(): Promise<RoleListRes> {
return axios.get('/role/getRoleList'); return axios.get('/role/getRoleList');
} }
// 创建角色
export interface RoleFormParam {
id?: number; // 角色id
name: string; // 姓名
remark?: string; // 备注
}
export function createRole(params: RoleFormParam): Promise<HttpResponse> { export function createRole(params: RoleFormParam): Promise<HttpResponse> {
return axios.post('/role/createRole', params); return axios.post('/role/createRole', params);
} }
// 更新角色
export function updateRole(params: RoleFormParam): Promise<HttpResponse> { export function updateRole(params: RoleFormParam): Promise<HttpResponse> {
return axios.post('/role/updateRole', params); return axios.post('/role/updateRole', params);
} }
// 删除角色
export function delRole(id: number): Promise<HttpResponse> { export function delRole(id: number): Promise<HttpResponse> {
return axios.get(`/role/delRole?id=${id}`); return axios.get(`/role/delRole?id=${id}`);
} }
// // 项目信息 export function getPermissionList(): Promise<PermissionRes> {
// export function getProjectDetail(id: string) { return axios.get('/role/getPermissionList');
// return axios.get<any>(`/project/getProject?id=${id}`); }
// }
// // 项目列表(分页) export function getRolePermission(id: number): Promise<RolePermissionRes> {
// export function queryPolicyList(params: ProjectParams) { return axios.get(`role/getRolePermission?id=${id}`);
// return axios.post<any>('/project/getProjectList', { params }); }
// }
export function saveRolePermission(
params: SaveRolePermissionParam
): Promise<HttpResponse> {
return axios.post('role/saveRolePermission', params);
}
// 角色下的用户列表
export function getUserListFromRole(
params: PaginationParams
): Promise<AllUserListRes> {
return axios.post('role/getUserList', params);
}
// 角色下的用户列表
export function updateUserRole(
params: UpdateUserRoleParam
): Promise<HttpResponse> {
return axios.post('role/updateUserRole', params);
}
+1 -1
View File
@@ -9,5 +9,5 @@ export default {
'messageBox.switchRoles': '切换角色', 'messageBox.switchRoles': '切换角色',
'messageBox.userCenter': '用户中心', 'messageBox.userCenter': '用户中心',
'messageBox.userSettings': '用户设置', 'messageBox.userSettings': '用户设置',
'messageBox.logout': '登出登录', 'messageBox.logout': '退出登录',
}; };
+16
View File
@@ -97,6 +97,22 @@ export default {
'edit': '编辑', 'edit': '编辑',
'more': '更多', 'more': '更多',
'user': '用户', 'user': '用户',
'role.list': '角色列表',
'role.add': '新增角色',
'role.edit': '编辑角色',
'roleName': '角色名称',
'roleDesc': '角色描述',
'permission': '权限',
'function.permission': '功能权限',
'data.permission': '数据权限',
'user.assignment': '用户分配',
'submit': '保存',
'last.updated': '最后更新',
'all.checked': '全部选中',
'all.clear': '全部清空',
'function.permission.setting': '功能权限设置',
'user.assignment.setting': '用户分配设置',
'change.role': '变更角色',
...localeSettings, ...localeSettings,
...localeMessageBox, ...localeMessageBox,
+3
View File
@@ -72,4 +72,7 @@ export interface UserPermission {
visible: 1 | 2; // 是否可见:1显示、2隐藏 visible: 1 | 2; // 是否可见:1显示、2隐藏
createdAt: string; // 创建时间 createdAt: string; // 创建时间
updatedAt: string; // 更新时间 updatedAt: string; // 更新时间
system_tenant_permission_id?: number; // 租客权限id
tenant_id?: number; // 租客id
children?: UserPermission[] | []; // 子集
} }
+1 -1
View File
@@ -1,3 +1,3 @@
<template> <template>
<div>house-view</div> <div>house-view</div>
</template> </template>
@@ -0,0 +1,98 @@
<template>
<a-modal
:visible="visible"
:title="t('change.role')"
unmount-on-close
@ok="handleOk"
@cancel="handleCancel"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
auto-label-width
layout="vertical"
>
<!-- 角色 -->
<a-form-item :label="$t('change.role')" field="role_id" required>
<a-select
v-model="form.role_id"
:placeholder="$t('please.select', { placeholder: $t('role') })"
>
<a-option
v-for="role in props.roleList"
:key="Number(role.id)"
:value="Number(role.id)"
>
{{ role.name }}</a-option
>
</a-select>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, watch, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import type { FormInstance } from '@arco-design/web-vue';
import { Message } from '@arco-design/web-vue';
import { UpdateUserRoleParam, updateUserRole, RoleItem } from '@/api/setting';
const props = defineProps<{
visible: boolean;
roleId: number | null;
userId: number | null;
roleList: RoleItem[] | null;
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
(e: 'updateUser'): void;
}>();
// 表单数据
const formRef = ref<FormInstance>();
const defaultForm: UpdateUserRoleParam = {
role_id: props.roleId,
user_id: props.userId,
};
const form = reactive<UpdateUserRoleParam>({ ...defaultForm });
const { t } = useI18n();
// 校验规则
const rules = reactive({
role_id: [
{
required: true,
message: t('please.select', { placeholder: t('role') }),
},
],
});
watch(
() => [props.roleId, props.userId],
([newRoleId, newUserId]) => {
form.role_id = newRoleId;
form.user_id = newUserId;
},
{ immediate: true }
);
const handleOk = async () => {
try {
const valid = await formRef.value.validate();
if (valid) return;
const params = { ...form };
const res = await updateUserRole(params);
if (res?.code === 200) {
Message.success(res?.message);
emit('updateUser');
emit('update:visible', false);
}
} catch (error) {
// 捕获异常,暂不处理
}
};
const handleCancel = () => {
emit('update:visible', false);
};
</script>
<style lang="less" scoped></style>
+268
View File
@@ -0,0 +1,268 @@
{
"permission": [
{
"system_tenant_permission_id": 100,
"tenant_id": 1,
"perm_id": 1,
"id": 1,
"parent_id": 0,
"prem_name": "业务",
"level": 1,
"perm_key": "business",
"prem_type": 1,
"path": "/1/",
"sort": 1,
"visible": 1,
"createdAt": "2024-06-20 10:00:00",
"updatedAt": "2024-06-20 10:00:00",
"children": [
{
"system_tenant_permission_id": 101,
"tenant_id": 1,
"perm_id": 2,
"id": 2,
"parent_id": 1,
"prem_name": "项目管理",
"level": 2,
"perm_key": "project_manage",
"prem_type": 1,
"path": "/1/2/",
"sort": 1,
"visible": 1,
"createdAt": "2024-06-20 10:01:00",
"updatedAt": "2024-06-20 10:01:00",
"children": [
{
"system_tenant_permission_id": 102,
"tenant_id": 1,
"perm_id": 3,
"id": 3,
"parent_id": 2,
"prem_name": "查看项目",
"level": 3,
"perm_key": "view_project",
"prem_type": 4,
"path": "/1/2/3/",
"sort": 1,
"visible": 1,
"createdAt": "2024-06-20 10:02:00",
"updatedAt": "2024-06-20 10:02:00",
"children": [
{
"system_tenant_permission_id": 103,
"tenant_id": 1,
"perm_id": 4,
"id": 4,
"parent_id": 3,
"prem_name": "添加收入目标",
"level": 4,
"perm_key": "add_income_target",
"prem_type": 2,
"path": "/1/2/3/4/",
"sort": 1,
"visible": 1,
"createdAt": "2024-06-20 10:03:00",
"updatedAt": "2024-06-20 10:03:00",
"children": []
},
{
"system_tenant_permission_id": 104,
"tenant_id": 1,
"perm_id": 5,
"id": 5,
"parent_id": 3,
"prem_name": "编辑收入目标",
"level": 4,
"perm_key": "edit_income_target",
"prem_type": 2,
"path": "/1/2/3/5/",
"sort": 2,
"visible": 1,
"createdAt": "2024-06-20 10:04:00",
"updatedAt": "2024-06-20 10:04:00",
"children": []
}
]
},
{
"system_tenant_permission_id": 105,
"tenant_id": 1,
"perm_id": 6,
"id": 6,
"parent_id": 2,
"prem_name": "编辑项目",
"level": 3,
"perm_key": "edit_project",
"prem_type": 4,
"path": "/1/2/6/",
"sort": 2,
"visible": 1,
"createdAt": "2024-06-20 10:05:00",
"updatedAt": "2024-06-20 10:05:00",
"children": [
{
"system_tenant_permission_id": 106,
"tenant_id": 1,
"perm_id": 7,
"id": 7,
"parent_id": 6,
"prem_name": "删除收入目标",
"level": 4,
"perm_key": "delete_income_target",
"prem_type": 2,
"path": "/1/2/6/7/",
"sort": 1,
"visible": 1,
"createdAt": "2024-06-20 10:06:00",
"updatedAt": "2024-06-20 10:06:00",
"children": []
}
]
}
]
},
{
"system_tenant_permission_id": 107,
"tenant_id": 1,
"perm_id": 8,
"id": 8,
"parent_id": 1,
"prem_name": "合同管理",
"level": 2,
"perm_key": "contract_manage",
"prem_type": 1,
"path": "/1/8/",
"sort": 2,
"visible": 1,
"createdAt": "2024-06-20 10:07:00",
"updatedAt": "2024-06-20 10:07:00",
"children": [
{
"system_tenant_permission_id": 108,
"tenant_id": 1,
"perm_id": 9,
"id": 9,
"parent_id": 8,
"prem_name": "查看合同",
"level": 3,
"perm_key": "view_contract",
"prem_type": 4,
"path": "/1/8/9/",
"sort": 1,
"visible": 1,
"createdAt": "2024-06-20 10:08:00",
"updatedAt": "2024-06-20 10:08:00",
"children": [
{
"system_tenant_permission_id": 109,
"tenant_id": 1,
"perm_id": 10,
"id": 10,
"parent_id": 9,
"prem_name": "导出合同",
"level": 4,
"perm_key": "export_contract",
"prem_type": 2,
"path": "/1/8/9/10/",
"sort": 1,
"visible": 1,
"createdAt": "2024-06-20 10:09:00",
"updatedAt": "2024-06-20 10:09:00",
"children": []
}
]
}
]
}
]
},
{
"system_tenant_permission_id": 200,
"tenant_id": 1,
"perm_id": 20,
"id": 20,
"parent_id": 0,
"prem_name": "房源视图",
"level": 1,
"perm_key": "house_view",
"prem_type": 1,
"path": "/20/",
"sort": 2,
"visible": 1,
"createdAt": "2024-06-20 11:00:00",
"updatedAt": "2024-06-20 11:00:00",
"children": [
{
"system_tenant_permission_id": 201,
"tenant_id": 1,
"perm_id": 21,
"id": 21,
"parent_id": 20,
"prem_name": "新建房源",
"level": 2,
"perm_key": "add_house",
"prem_type": 2,
"path": "/20/21/",
"sort": 1,
"visible": 1,
"createdAt": "2024-06-20 11:01:00",
"updatedAt": "2024-06-20 11:01:00",
"children": []
},
{
"system_tenant_permission_id": 202,
"tenant_id": 1,
"perm_id": 22,
"id": 22,
"parent_id": 20,
"prem_name": "导入房源",
"level": 2,
"perm_key": "import_house",
"prem_type": 3,
"path": "/20/22/",
"sort": 2,
"visible": 1,
"createdAt": "2024-06-20 11:02:00",
"updatedAt": "2024-06-20 11:02:00",
"children": [
{
"system_tenant_permission_id": 203,
"tenant_id": 1,
"perm_id": 23,
"id": 23,
"parent_id": 22,
"prem_name": "批量导入",
"level": 3,
"perm_key": "batch_import",
"prem_type": 3,
"path": "/20/22/23/",
"sort": 1,
"visible": 1,
"createdAt": "2024-06-20 11:03:00",
"updatedAt": "2024-06-20 11:03:00",
"children": [
{
"system_tenant_permission_id": 204,
"tenant_id": 1,
"perm_id": 24,
"id": 24,
"parent_id": 23,
"prem_name": "导入校验",
"level": 4,
"perm_key": "import_check",
"prem_type": 2,
"path": "/20/22/23/24/",
"sort": 1,
"visible": 1,
"createdAt": "2024-06-20 11:04:00",
"updatedAt": "2024-06-20 11:04:00",
"children": []
}
]
}
]
}
]
}
]
}
@@ -0,0 +1,145 @@
<template>
<div class="function_permission_collapse">
<div class="top">
<h4 class="title">{{ $t('function.permission.setting') }}</h4>
<a-space>
<a-button @click="selectAll">全部选中</a-button>
<a-button @click="clearAll">全部清空</a-button>
</a-space>
</div>
<div class="collapse_wrap">
<a-collapse accordion :active-key="defaultActiveKey">
<a-collapse-item
v-for="item in props.permissionList"
:key="item.id"
:header="item.prem_name"
>
<div class="collapse-item-body">
<PermissionNode
v-for="child in item.children"
:key="child.id"
:node="child"
:parent-node="item"
:checked-keys="checkedKeys"
@update:checked-keys="(val) => (checkedKeys = val)"
/>
</div>
</a-collapse-item>
</a-collapse>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { getRolePermission } from '@/api/setting';
import PermissionNode from './permission-node.vue';
const props = defineProps<{
roleId: number | null;
permissionList: any;
}>();
const emit = defineEmits(['update:checkedKeys']);
const checkedKeys = ref<number[]>([]);
const defaultActiveKey = ref<number[]>([]);
const getRolePermissionData = async (id: number) => {
try {
const { code, data } = await getRolePermission(id);
if (code === 200 && data) {
const { permission } = data;
if (!permission || permission.length === 0) {
checkedKeys.value = [];
return;
}
// 如果返回的是权限对象数组,提取ID
checkedKeys.value = Array.isArray(permission)
? permission.map((p: any) => (typeof p === 'object' ? p.id : p))
: [];
}
} catch (error) {
console.error('获取角色权限失败:', error);
}
};
// 递归收集所有权限ID(支持无限层级)
function collectAllIds(list: any[]): number[] {
const ids: number[] = [];
const traverse = (items: any[]) => {
items.forEach((item: any) => {
ids.push(item.id);
if (item.children && item.children.length > 0) {
traverse(item.children);
}
});
};
traverse(list);
return ids;
}
function selectAll() {
if (!props.permissionList || props.permissionList.length === 0) {
return;
}
checkedKeys.value = collectAllIds(props.permissionList);
}
function clearAll() {
checkedKeys.value = [];
}
watch(
() => checkedKeys.value,
(val) => {
emit('update:checkedKeys', checkedKeys.value);
},
{ immediate: true }
);
watch(
() => props.roleId,
(val, oldval) => {
if (val && val !== oldval) {
getRolePermissionData(val);
}
},
{ immediate: true }
);
// 监听权限列表变化,设置默认展开项
watch(
() => props.permissionList,
(newList) => {
if (newList && newList.length > 0) {
defaultActiveKey.value = [newList[0].id];
console.log(defaultActiveKey.value, 'defaultActiveKey');
}
},
{ immediate: true }
);
</script>
<style lang="less" scoped>
.function_permission_collapse {
.top {
display: flex;
align-items: center;
justify-content: space-between;
.title {
font-size: 16px;
text-align: left;
}
}
.collapse_wrap {
padding-bottom: 20px;
margin-top: 10px;
height: 100%;
}
}
</style>
@@ -0,0 +1,237 @@
<template>
<div class="permission-node" :class="'level-' + node.level">
<!-- 非按钮权限 -->
<a-checkbox
v-if="node.prem_type !== 2"
:indeterminate="isIndeterminate"
:model-value="isChecked"
@change="onCheck($event)"
>
{{ node.prem_name }}
</a-checkbox>
<!-- 递归渲染所有子节点(非按钮权限) -->
<div v-if="normalChildren.length" class="children">
<PermissionNode
v-for="child in normalChildren"
:key="child.id"
:node="child"
:parent-node="node"
:checked-keys="checkedKeys"
@update:checked-keys="updateCheckedKeys"
/>
</div>
<!-- 按钮权限一行展示 -->
<div v-if="buttonChildren.length" class="btn-permission-row">
<a-button
v-for="btn in buttonChildren"
:key="btn.id"
size="mini"
:type="checkedKeys.includes(btn.id) ? 'outline' : undefined"
@click="toggleButtonPermission(btn.id)"
>
{{ btn.prem_name }}
</a-button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
node: any;
checkedKeys: number[];
parentNode?: any;
}>();
const emit = defineEmits(['update:checkedKeys']);
// 递归获取所有子节点的ID(包括子节点的子节点)
function getAllChildrenIds(node: any): number[] {
let ids: number[] = [node.id];
if (node.children && node.children.length > 0) {
node.children.forEach((child: any) => {
ids = ids.concat(getAllChildrenIds(child));
});
}
return ids;
}
// 递归获取所有父节点的ID
function getAllParentIds(node: any, parentNode: any): number[] {
const ids: number[] = [];
let currentParent = parentNode;
while (currentParent) {
if (!ids.includes(currentParent.id)) {
ids.push(currentParent.id);
}
currentParent = currentParent.parentNode;
}
return ids;
}
// 移除空的父节点(当所有子节点都未选中时)
function removeEmptyParents(node: any, keys: number[]) {
const parent = props.parentNode;
if (!parent || !parent.children) return;
// 检查当前父节点的所有子节点是否都没有被选中
const hasCheckedChildren = parent.children.some((child: any) => {
const childIds = getAllChildrenIds(child);
return childIds.some((id) => keys.includes(id));
});
if (!hasCheckedChildren) {
// 移除父节点
const parentIndex = keys.indexOf(parent.id);
if (parentIndex !== -1) {
keys.splice(parentIndex, 1);
}
// 递归检查上级父节点
if (parent.parentNode) {
removeEmptyParents(parent, keys);
}
}
}
// 检查当前节点是否被选中
const isChecked = computed(() => {
if (!props.node.children || props.node.children.length === 0) {
return props.checkedKeys.includes(props.node.id);
}
// 对于有子节点的节点,只有当所有子节点都被选中时才认为被选中
const allChildrenIds = getAllChildrenIds(props.node);
return allChildrenIds.every((id) => props.checkedKeys.includes(id));
});
// 检查是否为半选状态
const isIndeterminate = computed(() => {
if (!props.node.children || props.node.children.length === 0) return false;
const allChildrenIds = getAllChildrenIds(props.node);
const checkedCount = allChildrenIds.filter((id) =>
props.checkedKeys.includes(id)
).length;
return checkedCount > 0 && checkedCount < allChildrenIds.length;
});
// 过滤出非按钮权限的子节点
const normalChildren = computed(() =>
(props.node.children || []).filter((child: any) => child.prem_type !== 2)
);
// 过滤出按钮权限的子节点
const buttonChildren = computed(() =>
(props.node.children || []).filter((child: any) => child.prem_type === 2)
);
// 处理复选框变化
function onCheck(checked: boolean) {
let newKeys = [...props.checkedKeys];
const allChildrenIds = getAllChildrenIds(props.node);
if (checked) {
// 选中当前节点及其所有子节点
allChildrenIds.forEach((id) => {
if (!newKeys.includes(id)) {
newKeys.push(id);
}
});
// 选中所有父节点
const parentIds = getAllParentIds(props.node, props.parentNode);
parentIds.forEach((id) => {
if (!newKeys.includes(id)) {
newKeys.push(id);
}
});
} else {
// 取消选中当前节点及其所有子节点
newKeys = newKeys.filter((id) => !allChildrenIds.includes(id));
// 检查并移除空的父节点
removeEmptyParents(props.node, newKeys);
}
emit('update:checkedKeys', newKeys);
}
// 切换按钮权限
function toggleButtonPermission(id: number) {
const newKeys = [...props.checkedKeys];
const index = newKeys.indexOf(id);
if (index > -1) {
// 取消选中
newKeys.splice(index, 1);
removeEmptyParents(props.node, newKeys);
} else {
// 选中
newKeys.push(id);
// 选中所有父节点
const parentIds = getAllParentIds(props.node, props.parentNode);
parentIds.forEach((parentId) => {
if (!newKeys.includes(parentId)) {
newKeys.push(parentId);
}
});
}
emit('update:checkedKeys', newKeys);
}
// 更新选中的权限
function updateCheckedKeys(newKeys: number[]) {
emit('update:checkedKeys', newKeys);
}
</script>
<style scoped>
.permission-node {
margin-top: 8px;
}
.level-1 {
margin-left: 0;
font-weight: bold;
}
.level-2 {
margin-left: 16px;
}
.level-3 {
margin-left: 32px;
}
.level-4 {
margin-left: 48px;
}
.level-5 {
margin-left: 64px;
}
.level-6 {
margin-left: 80px;
}
.children {
margin-top: 4px;
}
.btn-permission-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-left: 32px;
margin-top: 4px;
}
</style>
@@ -0,0 +1,150 @@
<template>
<a-modal
:visible="visible"
:title="isEdit ? $t('role.edit') : $t('role.add')"
unmount-on-close
@ok="handleOk"
@cancel="handleCancel"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
auto-label-width
layout="vertical"
>
<!-- 角色名称 -->
<a-form-item :label="$t('roleName')" field="name" required>
<a-input
v-model="form.name"
:placeholder="$t('please.enter', { placeholder: $t('roleName') })"
/>
</a-form-item>
<!-- 角色描述 -->
<a-form-item :label="$t('roleDesc')" field="remark">
<a-textarea
v-model="form.remark"
:auto-size="{ minRows: 3, maxRows: 8 }"
:placeholder="$t('please.enter', { placeholder: $t('roleDesc') })"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, watch, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import type { FormInstance } from '@arco-design/web-vue';
import { Message } from '@arco-design/web-vue';
import {
RoleFormParam,
RoleItem,
updateRole,
createRole,
} from '@/api/setting';
const props = defineProps<{
visible: boolean;
isEdit: boolean;
record?: RoleItem | null;
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
(e: 'submitted'): void;
}>();
// 表单数据
const formRef = ref<FormInstance>();
const defaultForm: RoleFormParam = {
name: '',
remark: '',
};
const form = reactive<RoleFormParam>({ ...defaultForm });
const { t } = useI18n();
// 校验规则
const rules = reactive({
name: [
{
required: true,
message: t('please.enter', { placeholder: t('roleName') }),
},
],
remark: [
{
message: t('please.enter', { placeholder: t('roleDesc') }),
},
],
});
watch(
() => props.visible,
(val) => {
if (val) {
if (props.isEdit && props.record) {
const record = props.record as RoleItem & {
roleId?: number;
departmentId?: number;
isManager?: number;
};
Object.assign(form, {
...record,
});
} else {
Object.assign(form, defaultForm);
}
}
},
{ immediate: true }
);
const handleOk = async () => {
try {
const valid = await formRef.value.validate();
if (valid) return;
const params = { ...form };
const res = props?.isEdit
? await updateRole(params)
: await createRole(params);
if (res?.code === 200) {
Message.success(res?.message);
emit('submitted');
emit('update:visible', false);
}
} catch (err) {
// 捕获异常,暂不处理
}
};
const handleCancel = () => {
emit('update:visible', false);
};
</script>
<style lang="less" scoped>
:deep(label.arco-form-item-label) {
width: 100% !important;
}
.label_wrap {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.left {
display: flex;
align-items: center;
.required {
color: #f53f3f;
margin-right: 4px;
font-size: 16px;
line-height: 1;
}
}
.right {
display: flex;
align-items: center;
gap: 4px;
}
}
</style>
+3 -1
View File
@@ -133,6 +133,7 @@
FormUserItem, FormUserItem,
disableUser, disableUser,
delUser, delUser,
UserStatus,
} from '@/api/setting'; } from '@/api/setting';
import useDepartmentList from '@/composables/useDepartmentList'; import useDepartmentList from '@/composables/useDepartmentList';
import useDataTable from '@/composables/useDataTable'; import useDataTable from '@/composables/useDataTable';
@@ -329,7 +330,8 @@
}; };
const handleDisableUser = async (id: number, currentStatus: number) => { const handleDisableUser = async (id: number, currentStatus: number) => {
try { try {
const param = { id, status: currentStatus === 1 ? 2 : 1 }; const status: UserStatus = currentStatus === 1 ? 2 : 1;
const param = { id, status };
const { code, message } = await disableUser(param); const { code, message } = await disableUser(param);
if (code === 200) { if (code === 200) {
Message.success(message); Message.success(message);
+445 -1
View File
@@ -1,3 +1,447 @@
<template> <template>
<div>帅哥kilo</div> <div class="setting_role_list">
<Breadcrumb
:items="['menu.setting', 'menu.setting.user', 'menu.setting.role']"
/>
<a-row :gutter="12" :wrap="false">
<a-col flex="310px">
<a-card class="full_height" :bordered="false">
<template #title>
<div class="card-title_between">
<p class="title">{{ $t('role.list') }}</p>
<a-button type="primary" @click="handleAddRole">
<template #icon>
<icon-plus />
</template>
{{ $t('role.add') }}
</a-button>
</div>
</template>
<a-spin style="display: block" :loading="loading">
<ul class="role_list">
<li
v-for="role in roleList"
:key="role.id"
:class="[
'role_item',
{ active: currentRoleActiveId === role.id },
]"
@click="handleClickRoleItem(role.id)"
>
<p class="name">{{ role.name }}</p>
<div v-if="currentRoleActiveId === role.id" class="actions">
<span @click="handleEditRole(role)"><icon-edit /></span>
<span>
<a-popconfirm
:ok-text="$t('cancel')"
:cancel-text="$t('delete')"
:ok-button-props="{ type: 'outline' }"
:cancel-button-props="{
type: 'primary',
status: 'danger',
}"
:content="
$t('please.confirm.delete.some', { name: $t('role') })
"
type="error"
@cancel="handleDelRole(role.id)"
>
<icon-delete />
</a-popconfirm>
</span>
</div>
</li>
</ul>
</a-spin>
<roleFormDrawer
v-model:visible="showDrawer"
:record="roleItemInfo"
:is-edit="isEdit"
@submitted="onRoleSubmit"
@update:checked-keys="(val) => (checkedKeys = val)"
/>
</a-card>
</a-col>
<a-col flex="auto" class="full_height">
<a-tabs class="tabs_card" @change="handleTabChange">
<template #extra>
<p style="margin-right: 10px">
<span>{{ $t('last.updated') }}</span>
<span>{{ roleItemInfo ? roleItemInfo.updatedAt : '' }}</span>
</p>
<a-button type="primary" @click="handleSubmit">{{
$t('submit')
}}</a-button>
</template>
<a-tab-pane key="1" :title="$t('function.permission')">
<permissionCollapse
:role-id="currentRoleActiveId"
:permission-list="permissionList"
@update:checked-keys="(val) => (checkedKeys = val)"
/>
</a-tab-pane>
<a-tab-pane key="2" :title="$t('data.permission')">
Content of Tab Panel 2
</a-tab-pane>
<a-tab-pane key="3" :title="$t('user.assignment')">
<h4>{{ $t('user.assignment.setting') }}</h4>
<DataTable
style="margin-top: 20px"
:data="renderData"
:columns="projectColumn"
:loading="loading"
:total="total"
:current="current"
:page-size="pageSize"
pagination
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
>
<template #realname="{ record }">
<span>{{ record.realname }}</span>
<icon-idcard v-if="record.isManager === 1" />
</template>
<template #status="{ record }">
<a-tag :color="record.status == 1 ? 'green' : 'red'">
{{ record.status == 1 ? $t('enable') : $t('disable') }}</a-tag
>
</template>
<template #operate="{ record }">
<a-button @click="changeRole(record?.id)">{{
$t('change.role')
}}</a-button>
</template>
</DataTable>
<changeRoleDrawer
v-model:visible="changeRoleDrawerVisible"
:role-id="currentRoleActiveId"
:user-id="selectedUserId"
:role-list="roleList"
@update-user="onChangeRole"
/>
</a-tab-pane>
</a-tabs>
</a-col>
</a-row>
</div>
</template> </template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import {
getRoleList,
RoleItem,
delRole,
saveRolePermission,
getPermissionList,
getUserListFromRole,
UserList,
} from '@/api/setting';
import useLoading from '@/hooks/loading';
import { Message } from '@arco-design/web-vue';
import useDataTable from '@/composables/useDataTable';
import type { TableColumnData } from '@arco-design/web-vue';
import { useI18n } from 'vue-i18n';
import permissionCollapse from './components/permission-collapse.vue';
import roleFormDrawer from './components/role-form-drawer.vue';
import changeRoleDrawer from './components/change-role-drawer.vue';
const { loading, setLoading } = useLoading(true);
const { t } = useI18n();
const roleList = ref<RoleItem[]>([]);
const currentRoleActiveId = ref<number | null>(null);
const showDrawer = ref<boolean>(false);
const isEdit = ref<boolean>(false);
const roleItemInfo = ref<RoleItem | null>(null);
const checkedKeys = ref<number[]>([]);
const permissionList = ref<any[]>([]);
const changeRoleDrawerVisible = ref<boolean>(false);
const selectedUserId = ref<number | null>(null);
// 使用 DataTable composable
const {
data: renderData,
columns: projectColumn,
// loading,
total,
current,
pageSize,
setData,
setColumns,
// setLoading,
setTotal,
setCurrent,
setPageSize,
} = useDataTable({
initialPage: 1,
initialPageSize: 10,
pageSizeOptions: [10, 20, 50, 100],
});
const userListData = ref<UserList>([]);
const columns: TableColumnData[] = [
{
title: t('username'),
dataIndex: 'username',
key: 'username',
width: 120,
ellipsis: true,
},
{
title: t('name'),
dataIndex: 'realname',
key: 'realname',
width: 150,
ellipsis: true,
slotName: 'realname',
},
{
title: t('department'),
dataIndex: 'deptName',
key: 'departmentId',
},
{
title: t('role'),
dataIndex: 'roleName',
key: 'roleName',
},
{
title: t('status'),
dataIndex: 'status',
key: 'status',
slotName: 'status',
},
{
title: t('lastLogin'),
dataIndex: 'lastLoginTime',
key: 'lastLoginTime',
render: ({ record }) => record.lastLoginTime || '-',
},
{
title: '操作',
key: 'operate',
slotName: 'operate',
width: 200,
},
];
// 获取权限列表数据
const getPermissionListData = async () => {
try {
const { code, data } = await getPermissionList();
if (code === 200 && data) {
// 根据 permission-collapse 组件的使用,API 返回的是权限数组
permissionList.value = Array.isArray(data?.permission)
? data.permission
: [];
}
} catch (error) {
console.error('获取权限列表失败:', error);
}
};
// 根据权限ID获取权限信息(包括路径)
const getPermissionById = (id: number): any => {
const findPermission = (list: any[]): any => {
// 用数组方法替换 for...of 循环
return list.reduce((found, item) => {
if (found) return found;
if (item.id === id) {
return item;
}
if (item.children && item.children.length > 0) {
return findPermission(item.children) || found;
}
return found;
}, null);
};
return findPermission(permissionList.value);
};
const getUserListFromRoleData = async () => {
try {
setLoading(true);
const params = {
role_id: currentRoleActiveId.value,
page: current.value,
page_size: pageSize.value,
};
const { code, data } = await getUserListFromRole(params);
if (code === 200) {
const { page, userList } = data;
userListData.value = Array.isArray(userList) ? userList : [];
setData(userListData.value);
setTotal(typeof page.total === 'number' ? page.total : 0);
}
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
const getRoleListData = async (isInit: boolean) => {
try {
setLoading(true);
const { code, data } = await getRoleList();
if (code === 200) {
roleList.value = Array.isArray(data?.rolesList) ? data.rolesList : [];
if (isInit) {
roleItemInfo.value = roleList.value[0];
currentRoleActiveId.value = roleItemInfo.value
? roleItemInfo.value.id
: null;
getUserListFromRoleData();
}
}
} catch (error) {
// 错误处理(保留注释,避免空代码块)
} finally {
setLoading(false);
}
};
const handleAddRole = () => {
isEdit.value = false;
showDrawer.value = true;
roleItemInfo.value = null;
};
const handleClickRoleItem = (id: number) => {
if (id === currentRoleActiveId.value) return;
currentRoleActiveId.value = id;
getUserListFromRoleData();
};
const handleEditRole = (role: RoleItem) => {
roleItemInfo.value = role;
isEdit.value = true;
showDrawer.value = true;
};
const handleDelRole = async (id: number) => {
try {
const { code, message } = await delRole(Number(id));
if (code === 200) {
Message.success(message);
getRoleListData(false);
}
} catch (error) {
// 处理错误
console.error(error);
}
};
const onRoleSubmit = () => {
getRoleListData(false);
};
function handleSubmit() {
if (!currentRoleActiveId.value) {
Message.warning('请先选择一个角色');
return;
}
if (checkedKeys.value.length === 0) {
Message.warning('请至少选择一个权限');
return;
}
// 根据选中的权限ID获取完整的权限信息
const permissionData = checkedKeys.value
.map((key) => {
const permission = getPermissionById(key);
return {
id: key,
path: permission?.path || '',
};
})
.filter((item) => item.id);
// 调用保存角色权限的API
saveRolePermission({
id: currentRoleActiveId.value,
children: permissionData,
})
.then(({ code, message }) => {
if (code === 200) {
Message.success(message || '权限保存成功');
} else {
Message.error(message || '权限保存失败');
}
})
.catch((error) => {
Message.error('权限保存失败');
console.error('保存角色权限失败:', error);
});
}
const handleTabChange = (key) => {
if (key === '3') {
// 占位注释,避免空代码块
}
};
// 分页事件处理
const onPageChange = (page: number) => {
setCurrent(page);
current.value = page;
getUserListFromRoleData();
};
const onPageSizeChange = (size: number) => {
setPageSize(size);
pageSize.value = size;
getUserListFromRoleData();
};
const changeRole = (id: number) => {
selectedUserId.value = id;
changeRoleDrawerVisible.value = true;
};
const onChangeRole = () => {
getUserListFromRoleData();
};
onMounted(() => {
getRoleListData(true);
getPermissionListData();
setColumns(columns);
});
</script>
<style lang="less" scoped>
.tabs_card {
background-color: #fff;
padding: 0 20px;
}
.role_list {
.role_item {
display: flex;
align-items: center;
justify-content: space-between;
height: 42px;
line-height: 42px;
padding: 0 16px;
border-bottom: 1px solid #efefef;
&.active {
background-color: #efefef;
.name {
cursor: pointer;
color: #2d5cf6;
}
}
.name {
color: #000;
font-size: 14px;
text-align: left;
flex: 1;
cursor: pointer;
}
.actions {
& > span {
width: 16px;
margin-left: 10px;
cursor: pointer;
&:last-child {
margin-right: 0;
}
}
}
}
}
</style>