Files
leaser_front/src/views/setting/user/role.vue
T

497 lines
14 KiB
Vue
Raw Normal View History

2025-06-22 23:55:55 +08:00
<template>
<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 v-model:active-key="currentTabsKey" class="tabs_card">
<template #extra>
<p style="margin-right: 10px">
<span>{{ $t('last.updated') }}</span>
<span>{{ roleItemInfo ? roleItemInfo.updatedAt : '' }}</span>
</p>
<a-button
v-if="currentTabsKey !== '3'"
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')">
<h4>{{ $t('data.permission.setting') }}</h4>
<div style="padding: 20px 0">
<a-radio-group v-model="dataType">
<a-radio :value="1">{{ $t('all.data.permission') }}</a-radio>
<a-radio :value="2">{{
$t('customize.data.permission')
}}</a-radio>
<a-radio :value="3">{{ $t('self.data.permission') }}</a-radio>
</a-radio-group>
<a-row
v-if="dataType === 2"
:gutter="12"
style="margin-top: 20px"
>
<a-col :span="12">
<a-card>
<h4 style="margin-bottom: 10px">选择组织架构</h4>
<DepartmentTree
v-model:expanded-keys="expandedKeys"
v-model:selected-keys="selectedKeys"
:data="departmentListData"
checkable
/>
</a-card>
</a-col>
<a-col :span="12">
<a-card>
<h4 style="margin-bottom: 10px">选择项目</h4>
<div style="margin-top: 20px"> TODO 后面再对接 </div>
</a-card>
</a-col>
</a-row>
</div>
</a-tab-pane>
<a-tab-pane key="3" :title="$t('user.assignment')">
<h4>{{ $t('user.assignment.setting') }}</h4>
<div style="padding-bottom: 20px">
<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>
</div>
<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>
2025-06-22 23:55:55 +08:00
</template>
<script lang="ts" setup>
import { ref, onMounted, watch } 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 DepartmentTree from '@/components/common/DepartmentTree.vue';
import useDepartmentList from '@/composables/useDepartmentList';
import permissionCollapse from './components/permission-collapse.vue';
import roleFormDrawer from './components/role-form-drawer.vue';
import changeRoleDrawer from './components/change-role-drawer.vue';
const { departmentListData, allDepartmentKeys } = useDepartmentList();
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);
const currentTabsKey = ref<string>('1');
const dataType = ref<number>(1);
const expandedKeys = ref<(string | number)[]>([]);
const selectedKeys = ref<(string | number)[]>([]);
// 使用 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',
align: 'center',
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);
};
watch(
allDepartmentKeys,
(val) => {
expandedKeys.value = val;
},
{ immediate: true }
);
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 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>