feat: 新增用户管理页面
This commit is contained in:
parent
aec8444121
commit
768bde6e14
@ -63,6 +63,10 @@ const SUPPORTED_ADMIN_MENU_NAMES = new Set([
|
|||||||
'SystemRoleCreate',
|
'SystemRoleCreate',
|
||||||
'SystemRoleDelete',
|
'SystemRoleDelete',
|
||||||
'SystemRoleEdit',
|
'SystemRoleEdit',
|
||||||
|
'SystemUser',
|
||||||
|
'SystemUserCreate',
|
||||||
|
'SystemUserDelete',
|
||||||
|
'SystemUserEdit',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function isSupportedAdminMenuName(name?: null | string | symbol) {
|
export function isSupportedAdminMenuName(name?: null | string | symbol) {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export * from './dept';
|
export * from './dept';
|
||||||
export * from './menu';
|
export * from './menu';
|
||||||
export * from './role';
|
export * from './role';
|
||||||
|
export * from './user';
|
||||||
|
|||||||
74
apps/web-antdv-next/src/api/system/user.ts
Normal file
74
apps/web-antdv-next/src/api/system/user.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace SystemUserApi {
|
||||||
|
export interface SystemUser {
|
||||||
|
[key: string]: any;
|
||||||
|
createTime?: string;
|
||||||
|
dept?: null | {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
deptId?: null | string;
|
||||||
|
deptName?: string;
|
||||||
|
homePath: string;
|
||||||
|
id: string;
|
||||||
|
password?: string;
|
||||||
|
realName: string;
|
||||||
|
roleIds: string[];
|
||||||
|
roleNames: string[];
|
||||||
|
roles?: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
roleCode: string;
|
||||||
|
status: 0 | 1;
|
||||||
|
}>;
|
||||||
|
status: 0 | 1;
|
||||||
|
timezone: string;
|
||||||
|
updateTime?: string;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SystemUserInput = Partial<Omit<SystemUser, 'id' | 'roles'>> & {
|
||||||
|
roleIds?: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户列表数据
|
||||||
|
* @param params 用户查询参数
|
||||||
|
*/
|
||||||
|
async function getUserList(params: Recordable<any>) {
|
||||||
|
return requestClient.get<Array<SystemUserApi.SystemUser>>(
|
||||||
|
'/system/user/list',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建用户
|
||||||
|
* @param data 用户数据
|
||||||
|
*/
|
||||||
|
async function createUser(data: SystemUserApi.SystemUserInput) {
|
||||||
|
return requestClient.post('/system/user', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户
|
||||||
|
* @param id 用户 ID
|
||||||
|
* @param data 用户数据
|
||||||
|
*/
|
||||||
|
async function updateUser(id: string, data: SystemUserApi.SystemUserInput) {
|
||||||
|
return requestClient.put(`/system/user/${id}`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除用户
|
||||||
|
* @param id 用户 ID
|
||||||
|
*/
|
||||||
|
async function deleteUser(id: string) {
|
||||||
|
return requestClient.delete(`/system/user/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createUser, deleteUser, getUserList, updateUser };
|
||||||
@ -1,5 +1,22 @@
|
|||||||
{
|
{
|
||||||
"title": "System Management",
|
"title": "System Management",
|
||||||
|
"user": {
|
||||||
|
"allDept": "All",
|
||||||
|
"createTime": "Create Time",
|
||||||
|
"dept": "Department",
|
||||||
|
"deptTree": "Department Tree",
|
||||||
|
"homePath": "Home Path",
|
||||||
|
"list": "User List",
|
||||||
|
"name": "User",
|
||||||
|
"password": "Password",
|
||||||
|
"passwordPlaceholder": "Leave blank when editing to keep the current password; blank new users default to 123456",
|
||||||
|
"realName": "Real Name",
|
||||||
|
"roles": "Roles",
|
||||||
|
"status": "Status",
|
||||||
|
"timezone": "Timezone",
|
||||||
|
"title": "User Management",
|
||||||
|
"username": "Username"
|
||||||
|
},
|
||||||
"dept": {
|
"dept": {
|
||||||
"name": "Department",
|
"name": "Department",
|
||||||
"title": "Department Management",
|
"title": "Department Management",
|
||||||
|
|||||||
@ -1,5 +1,22 @@
|
|||||||
{
|
{
|
||||||
"title": "系统管理",
|
"title": "系统管理",
|
||||||
|
"user": {
|
||||||
|
"allDept": "全部",
|
||||||
|
"createTime": "创建时间",
|
||||||
|
"dept": "所属部门",
|
||||||
|
"deptTree": "部门树",
|
||||||
|
"homePath": "首页路径",
|
||||||
|
"list": "用户列表",
|
||||||
|
"name": "用户",
|
||||||
|
"password": "密码",
|
||||||
|
"passwordPlaceholder": "编辑时留空则不修改密码;新增留空默认 123456",
|
||||||
|
"realName": "真实姓名",
|
||||||
|
"roles": "角色",
|
||||||
|
"status": "状态",
|
||||||
|
"timezone": "时区",
|
||||||
|
"title": "用户管理",
|
||||||
|
"username": "用户名"
|
||||||
|
},
|
||||||
"dept": {
|
"dept": {
|
||||||
"list": "部门列表",
|
"list": "部门列表",
|
||||||
"createTime": "创建时间",
|
"createTime": "创建时间",
|
||||||
|
|||||||
@ -12,6 +12,15 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'System',
|
name: 'System',
|
||||||
path: '/system',
|
path: '/system',
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
path: '/system/user',
|
||||||
|
name: 'SystemUser',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:account',
|
||||||
|
title: $t('system.user.title'),
|
||||||
|
},
|
||||||
|
component: () => import('#/views/system/user/list.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/system/role',
|
path: '/system/role',
|
||||||
name: 'SystemRole',
|
name: 'SystemRole',
|
||||||
|
|||||||
150
apps/web-antdv-next/src/views/system/user/data.ts
Normal file
150
apps/web-antdv-next/src/views/system/user/data.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
import { getDeptList } from '#/api/system/dept';
|
||||||
|
import { getRoleList } from '#/api/system/role';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
const statusOptions = [
|
||||||
|
{ label: $t('common.enabled'), value: 1 },
|
||||||
|
{ label: $t('common.disabled'), value: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
|
async function getRoleOptions() {
|
||||||
|
const res = await getRoleList({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 1000,
|
||||||
|
status: 1,
|
||||||
|
});
|
||||||
|
const items = (res as any)?.items || [];
|
||||||
|
return items.map((role: any) => ({
|
||||||
|
label: role.name,
|
||||||
|
value: role.id,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'username',
|
||||||
|
label: $t('system.user.username'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(2, $t('ui.formRules.minLength', [$t('system.user.username'), 2]))
|
||||||
|
.max(
|
||||||
|
30,
|
||||||
|
$t('ui.formRules.maxLength', [$t('system.user.username'), 30]),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: $t('system.user.passwordPlaceholder'),
|
||||||
|
},
|
||||||
|
fieldName: 'password',
|
||||||
|
label: $t('system.user.password'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'realName',
|
||||||
|
label: $t('system.user.realName'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(2, $t('ui.formRules.minLength', [$t('system.user.realName'), 2]))
|
||||||
|
.max(
|
||||||
|
30,
|
||||||
|
$t('ui.formRules.maxLength', [$t('system.user.realName'), 30]),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
componentProps: {
|
||||||
|
api: getRoleOptions,
|
||||||
|
mode: 'multiple',
|
||||||
|
},
|
||||||
|
fieldName: 'roleIds',
|
||||||
|
label: $t('system.user.roles'),
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
api: getDeptList,
|
||||||
|
childrenField: 'children',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
fieldName: 'deptId',
|
||||||
|
label: $t('system.user.dept'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '/workspace',
|
||||||
|
},
|
||||||
|
defaultValue: '/workspace',
|
||||||
|
fieldName: 'homePath',
|
||||||
|
label: $t('system.user.homePath'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: 'Asia/Shanghai',
|
||||||
|
},
|
||||||
|
defaultValue: 'Asia/Shanghai',
|
||||||
|
fieldName: 'timezone',
|
||||||
|
label: $t('system.user.timezone'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
options: statusOptions,
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
defaultValue: 1,
|
||||||
|
fieldName: 'status',
|
||||||
|
label: $t('system.user.status'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'username',
|
||||||
|
label: $t('system.user.username'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'realName',
|
||||||
|
label: $t('system.user.realName'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: statusOptions,
|
||||||
|
},
|
||||||
|
fieldName: 'status',
|
||||||
|
label: $t('system.user.status'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
api: getRoleOptions,
|
||||||
|
},
|
||||||
|
fieldName: 'roleId',
|
||||||
|
label: $t('system.user.roles'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RangePicker',
|
||||||
|
fieldName: 'createTime',
|
||||||
|
label: $t('system.user.createTime'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
353
apps/web-antdv-next/src/views/system/user/list.vue
Normal file
353
apps/web-antdv-next/src/views/system/user/list.vue
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { TableColumnType } from 'antdv-next';
|
||||||
|
import type { DataNode } from 'antdv-next/dist/tree/index';
|
||||||
|
|
||||||
|
import type { SystemUserApi } from '#/api';
|
||||||
|
import type { SystemDeptApi } from '#/api/system/dept';
|
||||||
|
import type {
|
||||||
|
KtTableApi,
|
||||||
|
KtTableButton,
|
||||||
|
KtTableContext,
|
||||||
|
KtTableRowAction,
|
||||||
|
} from '#/components/ktTable';
|
||||||
|
|
||||||
|
import { computed, h, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useAccess } from '@vben/access';
|
||||||
|
import { Page, useVbenDrawer } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Button, message, Spin, Switch, Tag, Tree } from 'antdv-next';
|
||||||
|
|
||||||
|
import { deleteUser, getUserList, updateUser } from '#/api';
|
||||||
|
import { getDeptList } from '#/api/system/dept';
|
||||||
|
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useGridFormSchema } from './data';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { hasAccessByCodes } = useAccess();
|
||||||
|
const deptTree = ref<SystemDeptApi.SystemDept[]>([]);
|
||||||
|
const deptLoading = ref(false);
|
||||||
|
const selectedDeptId = ref<string>();
|
||||||
|
const selectedDeptKeys = computed(() =>
|
||||||
|
selectedDeptId.value ? [selectedDeptId.value] : [],
|
||||||
|
);
|
||||||
|
const deptTreeData = computed<DataNode[]>(() => mapDeptTree(deptTree.value));
|
||||||
|
|
||||||
|
function hasPermission(code: string) {
|
||||||
|
return hasAccessByCodes([code]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: Array<TableColumnType<SystemUserApi.SystemUser>> = [
|
||||||
|
{
|
||||||
|
dataIndex: 'username',
|
||||||
|
fixed: 'left',
|
||||||
|
key: 'username',
|
||||||
|
title: $t('system.user.username'),
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'realName',
|
||||||
|
key: 'realName',
|
||||||
|
title: $t('system.user.realName'),
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'roleNames',
|
||||||
|
key: 'roleNames',
|
||||||
|
title: $t('system.user.roles'),
|
||||||
|
width: 220,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'deptName',
|
||||||
|
key: 'deptName',
|
||||||
|
title: $t('system.user.dept'),
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'center',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
title: $t('system.user.status'),
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'homePath',
|
||||||
|
key: 'homePath',
|
||||||
|
title: $t('system.user.homePath'),
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'timezone',
|
||||||
|
key: 'timezone',
|
||||||
|
title: $t('system.user.timezone'),
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
key: 'createTime',
|
||||||
|
title: $t('system.user.createTime'),
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const api: KtTableApi<SystemUserApi.SystemUser> = {
|
||||||
|
list: async (params) => {
|
||||||
|
const { pageNo, pageSize, ...formValues } = params;
|
||||||
|
|
||||||
|
return await getUserList({
|
||||||
|
page: pageNo,
|
||||||
|
pageSize,
|
||||||
|
...(selectedDeptId.value ? { deptId: selectedDeptId.value } : {}),
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttons: Array<KtTableButton<SystemUserApi.SystemUser>> = [
|
||||||
|
{
|
||||||
|
icon: () => h(Plus, { class: 'kt-table__button-icon' }),
|
||||||
|
key: 'create',
|
||||||
|
label: $t('ui.actionTitle.create', [$t('system.user.name')]),
|
||||||
|
onClick: onCreate,
|
||||||
|
permissionCodes: ['System:User:Create'],
|
||||||
|
type: 'primary',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const rowActions: Array<KtTableRowAction<SystemUserApi.SystemUser>> = [
|
||||||
|
{
|
||||||
|
key: 'edit',
|
||||||
|
label: $t('common.edit'),
|
||||||
|
onClick: onEdit,
|
||||||
|
permissionCodes: ['System:User:Edit'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
confirm: (row) => `确认删除「${row.username}」吗?`,
|
||||||
|
danger: true,
|
||||||
|
disabled: (row) => row.username === 'admin',
|
||||||
|
key: 'delete',
|
||||||
|
label: $t('common.delete'),
|
||||||
|
onClick: onDelete,
|
||||||
|
permissionCodes: ['System:User:Delete'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const [registerTable, tableApi] = useKtTable<SystemUserApi.SystemUser>({
|
||||||
|
api,
|
||||||
|
buttons,
|
||||||
|
columns,
|
||||||
|
formOptions: {
|
||||||
|
fieldMappingTime: [['createTime', ['startTime', 'endTime']]],
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
},
|
||||||
|
rowActions,
|
||||||
|
tableTitle: $t('system.user.list'),
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadDeptTree();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadDeptTree() {
|
||||||
|
deptLoading.value = true;
|
||||||
|
try {
|
||||||
|
deptTree.value = await getDeptList();
|
||||||
|
} finally {
|
||||||
|
deptLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDeptSelect(keys: Array<number | string>) {
|
||||||
|
selectedDeptId.value = keys.length > 0 ? String(keys[0]) : undefined;
|
||||||
|
tableApi.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearDeptFilter() {
|
||||||
|
selectedDeptId.value = undefined;
|
||||||
|
tableApi.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDeptTree(depts: SystemDeptApi.SystemDept[]): DataNode[] {
|
||||||
|
return depts.map((dept) => ({
|
||||||
|
children: dept.children ? mapDeptTree(dept.children) : undefined,
|
||||||
|
key: dept.id,
|
||||||
|
title: dept.name,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onStatusSwitchChange(
|
||||||
|
checked: boolean | number | string,
|
||||||
|
row: SystemUserApi.SystemUser,
|
||||||
|
) {
|
||||||
|
const nextStatus = Number(checked) as SystemUserApi.SystemUser['status'];
|
||||||
|
if (nextStatus === row.status) return;
|
||||||
|
|
||||||
|
await updateUser(row.id, { status: nextStatus });
|
||||||
|
await tableApi.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEdit(row: SystemUserApi.SystemUser) {
|
||||||
|
formDrawerApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDelete(
|
||||||
|
row: SystemUserApi.SystemUser,
|
||||||
|
context?: KtTableContext<SystemUserApi.SystemUser>,
|
||||||
|
) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.username]),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteUser(row.id);
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.username]),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
await (context || tableApi).reload();
|
||||||
|
} catch {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRefresh() {
|
||||||
|
tableApi.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCreate() {
|
||||||
|
formDrawerApi.setData({ deptId: selectedDeptId.value }).open();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormDrawer @success="onRefresh" />
|
||||||
|
<div class="system-user-page">
|
||||||
|
<aside class="system-user-page__dept">
|
||||||
|
<div class="system-user-page__dept-header">
|
||||||
|
<span class="system-user-page__dept-title">
|
||||||
|
{{ $t('system.user.deptTree') }}
|
||||||
|
</span>
|
||||||
|
<Button size="small" type="link" @click="clearDeptFilter">
|
||||||
|
{{ $t('system.user.allDept') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Spin :spinning="deptLoading">
|
||||||
|
<Tree
|
||||||
|
block-node
|
||||||
|
:selected-keys="selectedDeptKeys"
|
||||||
|
:tree-data="deptTreeData"
|
||||||
|
default-expand-all
|
||||||
|
@select="onDeptSelect"
|
||||||
|
/>
|
||||||
|
</Spin>
|
||||||
|
</aside>
|
||||||
|
<main class="system-user-page__table">
|
||||||
|
<KtTable @register="registerTable">
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'roleNames'">
|
||||||
|
<div class="system-user-list__roles">
|
||||||
|
<Tag
|
||||||
|
v-for="roleName in record.roleNames || []"
|
||||||
|
:key="roleName"
|
||||||
|
color="processing"
|
||||||
|
>
|
||||||
|
{{ roleName }}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'deptName'">
|
||||||
|
{{ record.deptName || '-' }}
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'status'">
|
||||||
|
<Switch
|
||||||
|
v-if="record && hasPermission('System:User:Edit')"
|
||||||
|
:checked="record.status"
|
||||||
|
:checked-value="1"
|
||||||
|
:un-checked-value="0"
|
||||||
|
@change="(checked) => onStatusSwitchChange(checked, record)"
|
||||||
|
/>
|
||||||
|
<Tag v-else :color="record.status === 1 ? 'success' : 'default'">
|
||||||
|
{{
|
||||||
|
record.status === 1
|
||||||
|
? $t('common.enabled')
|
||||||
|
: $t('common.disabled')
|
||||||
|
}}
|
||||||
|
</Tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</KtTable>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.system-user-page {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
&__dept {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 240px;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: hsl(var(--card));
|
||||||
|
border: 1px solid hsl(var(--border));
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__dept-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-bottom: 1px solid hsl(var(--border));
|
||||||
|
}
|
||||||
|
|
||||||
|
&__dept-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
&__table {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-spin-nested-loading),
|
||||||
|
:deep(.ant-spin-container) {
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-spin-container) {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-user-list {
|
||||||
|
&__roles {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
77
apps/web-antdv-next/src/views/system/user/modules/form.vue
Normal file
77
apps/web-antdv-next/src/views/system/user/modules/form.vue
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { SystemUserApi } from '#/api/system/user';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenDrawer } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createUser, updateUser } from '#/api/system/user';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useFormSchema } from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
|
||||||
|
const formData = ref<SystemUserApi.SystemUser>();
|
||||||
|
const id = ref<string>();
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: useFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) return;
|
||||||
|
|
||||||
|
const values = await formApi.getValues();
|
||||||
|
if (id.value && !values.password) {
|
||||||
|
delete values.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawerApi.lock();
|
||||||
|
try {
|
||||||
|
await (id.value ? updateUser(id.value, values) : createUser(values));
|
||||||
|
emit('success');
|
||||||
|
drawerApi.close();
|
||||||
|
} finally {
|
||||||
|
drawerApi.lock(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen) {
|
||||||
|
if (!isOpen) return;
|
||||||
|
|
||||||
|
const data = drawerApi.getData<SystemUserApi.SystemUser>();
|
||||||
|
formData.value = data || undefined;
|
||||||
|
id.value = data?.id;
|
||||||
|
formApi.resetForm();
|
||||||
|
formApi.setValues({
|
||||||
|
...data,
|
||||||
|
homePath: data?.homePath || '/workspace',
|
||||||
|
password: '',
|
||||||
|
status: data?.status ?? 1,
|
||||||
|
timezone: data?.timezone || 'Asia/Shanghai',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const getDrawerTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', [$t('system.user.name')])
|
||||||
|
: $t('ui.actionTitle.create', [$t('system.user.name')]);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Drawer :title="getDrawerTitle">
|
||||||
|
<Form class="system-user-form" />
|
||||||
|
</Drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.system-user-form {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue
Block a user