first commit
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
import {Avatar, Button, Form, Input, message, Modal, Tag, Tooltip, Typography} from 'antd';
|
||||
import React, {useEffect, useMemo, useState} from 'react';
|
||||
import XinTable from '@/components/XinTable';
|
||||
import type {XinTableColumn, XinTableProps} from "@/components/XinTable/typings.ts";
|
||||
import type ISysUser from "@/domain/iSysUser.ts";
|
||||
import AuthButton from "@/components/AuthButton";
|
||||
import type {DeptFieldType, ResetPasswordType, RoleFieldType} from "@/api/system/sys_user";
|
||||
import {deptField, resetPassword, roleField} from "@/api/system/sys_user";
|
||||
import {RedoOutlined} from "@ant-design/icons";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import useAuth from "@/hooks/useAuth.ts";
|
||||
const { Title, Text } = Typography;
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const Table: React.FC = () => {
|
||||
const {t} = useTranslation();
|
||||
const { auth } = useAuth();
|
||||
/** 角色选项数据 */
|
||||
const [roles, setRoles] = useState<RoleFieldType[]>([]);
|
||||
/** 部门选项数据 */
|
||||
const [departments, setDepartments] = useState<DeptFieldType[]>([]);
|
||||
/** 部门选项数据映射 */
|
||||
const departmentMap: Map<number, string> = useMemo(() => {
|
||||
const map: Map<number, string> = new Map();
|
||||
const buildMap = (dept: DeptFieldType[]) => {
|
||||
dept.forEach(item => {
|
||||
map.set(item.dept_id, item.name);
|
||||
if (item.children && item.children.length > 0) {
|
||||
buildMap(item.children);
|
||||
}
|
||||
})
|
||||
}
|
||||
buildMap(departments);
|
||||
return map;
|
||||
}, [departments]);
|
||||
|
||||
/** 高级表格列配置 */
|
||||
const columns: XinTableColumn<ISysUser>[] = [
|
||||
{
|
||||
title: t("system.user.id"),
|
||||
dataIndex: 'id',
|
||||
hideInForm: true,
|
||||
sorter: true,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t("system.user.username"),
|
||||
dataIndex: 'username',
|
||||
valueType: 'text',
|
||||
align: 'center',
|
||||
required: true,
|
||||
rules: [{required: true, message: t("system.user.username.required")}],
|
||||
},
|
||||
{
|
||||
title: t("system.user.nickname"),
|
||||
dataIndex: 'nickname',
|
||||
valueType: 'text',
|
||||
align: 'center',
|
||||
required: true,
|
||||
rules: [{required: true, message: t("system.user.nickname.required")}],
|
||||
},
|
||||
{
|
||||
title: t("system.user.sex"),
|
||||
dataIndex: 'sex',
|
||||
valueType: 'radio',
|
||||
filters: [
|
||||
{ text: t("system.user.sex.0"), value: 0 },
|
||||
{ text: t("system.user.sex.1"), value: 1 },
|
||||
],
|
||||
align: 'center',
|
||||
hideInSearch: true,
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ value: 0, label: t("system.user.sex.0") },
|
||||
{ value: 1, label: t("system.user.sex.1") },
|
||||
]
|
||||
},
|
||||
render: (value: number) => {
|
||||
return value === 0 ? t("system.user.sex.0") : t("system.user.sex.1");
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("system.user.email"),
|
||||
dataIndex: 'email',
|
||||
valueType: 'text',
|
||||
align: 'center',
|
||||
required: true,
|
||||
rules: [{required: true, message: t("system.user.email.required")}],
|
||||
},
|
||||
{
|
||||
title: t("system.user.role"),
|
||||
dataIndex: 'role_id',
|
||||
valueType: 'select',
|
||||
align: 'center',
|
||||
hideInSearch: true,
|
||||
required: true,
|
||||
rules: [{required: true, message: t("system.user.role.required")}],
|
||||
render: (value: number[]) => (
|
||||
<>
|
||||
{value.map(item => (
|
||||
<Tag color={'magenta'}>
|
||||
{roles.find(r => r.role_id === item)?.name || '-'}
|
||||
</Tag>
|
||||
))}
|
||||
</>
|
||||
),
|
||||
fieldProps: {
|
||||
mode: 'multiple',
|
||||
options: roles.map(r => ({ label: r.name, value: r.role_id })),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("system.user.dept"),
|
||||
dataIndex: 'dept_id',
|
||||
valueType: 'treeSelect',
|
||||
align: 'center',
|
||||
required: true,
|
||||
rules: [{required: true, message: t("system.user.dept.required")}],
|
||||
render: (value) => (
|
||||
// 嵌套寻找部门名称
|
||||
<Tag color={'volcano'}>{departmentMap.get(value) || '-'}</Tag>
|
||||
),
|
||||
fieldProps: {
|
||||
treeData: departments,
|
||||
fieldNames: {label: 'name', value: 'dept_id'}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("system.user.status"),
|
||||
dataIndex: 'status',
|
||||
valueType: 'radioButton',
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ value: 0, label: t("system.user.status.0") },
|
||||
{ value: 1, label: t("system.user.status.1") },
|
||||
]
|
||||
},
|
||||
render: (value: number) => {
|
||||
return value === 1
|
||||
? <Tag color="success">{t("system.user.status.1")}</Tag>
|
||||
: <Tag color="error">{t("system.user.status.0")}</Tag>;
|
||||
},
|
||||
required: true,
|
||||
rules: [{required: true, message: t("system.user.status.required")}],
|
||||
filters: [
|
||||
{ text: t("system.user.status.0"), value: 0 },
|
||||
{ text: t("system.user.status.1"), value: 1 },
|
||||
],
|
||||
hideInSearch: true,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t("system.user.mobile"),
|
||||
dataIndex: 'mobile',
|
||||
valueType: 'text',
|
||||
required: true,
|
||||
rules: [{required: true, message: t("system.user.mobile.required")}],
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t("system.user.avatar"),
|
||||
dataIndex: 'avatar_url',
|
||||
hideInSearch: true,
|
||||
hideInForm: true,
|
||||
render: (_, entity: ISysUser) => <Avatar size={'small'} src={entity.avatar_url}></Avatar>,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t("system.user.password"),
|
||||
dataIndex: 'password',
|
||||
valueType: 'password',
|
||||
hideInTable: true,
|
||||
hideInSearch: true,
|
||||
hideInUpdate: true,
|
||||
rules: [{required: true, message: t("system.user.password.required")}],
|
||||
fieldProps: {autoComplete: 'new-password'},
|
||||
},
|
||||
{
|
||||
title: t("system.user.rePassword"),
|
||||
dataIndex: 'rePassword',
|
||||
valueType: 'password',
|
||||
hideInTable: true,
|
||||
hideInSearch: true,
|
||||
hideInUpdate: true,
|
||||
rules: [{required: true, message: t("system.user.rePassword.required")}],
|
||||
fieldProps: {autoComplete: 'new-password'},
|
||||
},
|
||||
{
|
||||
title: t("system.user.created_at"),
|
||||
hideInForm: true,
|
||||
hideInSearch: true,
|
||||
dataIndex: 'created_at',
|
||||
align: 'center',
|
||||
render: (value: string) => value ? dayjs(value).fromNow() : '-',
|
||||
},
|
||||
{
|
||||
title: t("system.user.updated_at"),
|
||||
hideInForm: true,
|
||||
hideInSearch: true,
|
||||
dataIndex: 'updated_at',
|
||||
align: 'center',
|
||||
render: (value: string) => value ? dayjs(value).fromNow() : '-',
|
||||
},
|
||||
];
|
||||
|
||||
/** 修改密码相关状态 */
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [resetUserId, setResetUserId] = useState<number>();
|
||||
const [buttonLoading, setButtonLoading] = useState<boolean>(false);
|
||||
|
||||
/** 打开重置密码框 */
|
||||
const showRedoModal = (id: number) => {
|
||||
setIsModalOpen(true);
|
||||
setResetUserId(id);
|
||||
};
|
||||
|
||||
/** 提交重置密码 */
|
||||
const handleRedoSubmit = async (values: ResetPasswordType) => {
|
||||
try {
|
||||
setButtonLoading(true);
|
||||
await resetPassword({...values, id: resetUserId!});
|
||||
setIsModalOpen(false);
|
||||
message.success(t("system.user.resetSuccess"));
|
||||
} finally {
|
||||
setButtonLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if(auth('system.user.dept')) {
|
||||
deptField().then(res => setDepartments(res.data.data!));
|
||||
}
|
||||
if(auth('system.user.role')) {
|
||||
roleField().then(res => setRoles(res.data.data!));
|
||||
}
|
||||
}, []);
|
||||
|
||||
/** 操作栏之后渲染 */
|
||||
const operateRender: XinTableProps['operateRender'] = (record, dom) => ([
|
||||
<>
|
||||
{record.id !== 1 &&
|
||||
<AuthButton auth={'system.user.resetPassword'}>
|
||||
<Tooltip title={t("system.user.resetPassword")}>
|
||||
<Button
|
||||
variant={'solid'}
|
||||
color={'pink'}
|
||||
icon={<RedoOutlined/>}
|
||||
size={'small'}
|
||||
onClick={() => showRedoModal(record.id!)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</AuthButton>
|
||||
}
|
||||
</>,
|
||||
dom.del,
|
||||
dom.edit
|
||||
]);
|
||||
|
||||
/** 表格配置 */
|
||||
const tableProps: XinTableProps<ISysUser> = {
|
||||
api: '/system/user',
|
||||
columns,
|
||||
rowKey: 'id',
|
||||
accessName: 'system.user',
|
||||
operateRender,
|
||||
scroll: { x: 1400 },
|
||||
editShow: (i) => i.id !== 1,
|
||||
deleteShow: (i) => i.id !== 1,
|
||||
formProps: {
|
||||
grid: true,
|
||||
colProps: {span: 12},
|
||||
rowProps: {gutter: [30, 0]},
|
||||
layout: 'vertical'
|
||||
},
|
||||
modalProps: {
|
||||
width: 800,
|
||||
},
|
||||
cardProps: {
|
||||
variant: 'borderless'
|
||||
},
|
||||
pagination: {
|
||||
size: 'small',
|
||||
style: {
|
||||
marginBottom: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={'mb-5'}>
|
||||
<Title level={3}>{t("system.user.page.title")}</Title>
|
||||
<Text type="secondary">{t("system.user.page.description")}</Text>
|
||||
</div>
|
||||
<XinTable<ISysUser> {...tableProps} />
|
||||
<Modal
|
||||
title={t("system.user.resetPassword")}
|
||||
closable={{'aria-label': 'Custom Close Button'}}
|
||||
open={isModalOpen}
|
||||
footer={null}
|
||||
styles={{body: {paddingTop: 20}}}
|
||||
onCancel={() => setIsModalOpen(false)}
|
||||
>
|
||||
<Form<ResetPasswordType> autoComplete="off" layout={'vertical'} onFinish={handleRedoSubmit}>
|
||||
<Form.Item
|
||||
label={t("system.user.password")}
|
||||
name="password"
|
||||
rules={[{required: true, message: t("system.user.password.required")}]}
|
||||
>
|
||||
<Input.Password/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("system.user.rePassword")}
|
||||
name="rePassword"
|
||||
rules={[{required: true, message: t("system.user.rePassword.required")}]}
|
||||
>
|
||||
<Input.Password/>
|
||||
</Form.Item>
|
||||
<Form.Item label={null} style={{marginTop: 30}}>
|
||||
<Button type="primary" block size={'large'} htmlType="submit" loading={buttonLoading}>
|
||||
{t("system.user.resetButton")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Table;
|
||||
Reference in New Issue
Block a user