453 lines
14 KiB
TypeScript
453 lines
14 KiB
TypeScript
import {
|
|
Alert,
|
|
Button,
|
|
Card,
|
|
type CardProps,
|
|
Col,
|
|
message,
|
|
Popconfirm,
|
|
Row,
|
|
Space, Table, type TableProps, Tag,
|
|
Tree,
|
|
type TreeDataNode, type TreeProps, Typography
|
|
} from "antd";
|
|
import {BankOutlined, DeleteOutlined, PlusOutlined, TeamOutlined, UserOutlined} from "@ant-design/icons";
|
|
import {useEffect, useRef, useState} from "react";
|
|
import {queryDept, createDept, updateDept, deleteDept, deptUsers} from "@/api/system/sys_dept.ts";
|
|
import type {ISysDept} from "@/domain/iSysDept.ts";
|
|
import {isArray, omit} from 'lodash';
|
|
import type {FormColumn} from "@/components/XinFormField/FieldRender/typings.ts";
|
|
import XinForm, {type XinFormRef} from "@/components/XinForm";
|
|
import * as React from "react";
|
|
import {useTranslation} from "react-i18next";
|
|
import AuthButton from "@/components/AuthButton";
|
|
import useAuth from "@/hooks/useAuth.ts";
|
|
import type ISysUser from "@/domain/iSysUser.ts";
|
|
|
|
const deptMap = new Map<string, ISysDept>();
|
|
|
|
interface TableParams {
|
|
page: number;
|
|
pageSize: number;
|
|
total: number;
|
|
}
|
|
|
|
const Dept = () => {
|
|
const {t} = useTranslation();
|
|
const {auth} = useAuth();
|
|
/** 部门信息表单 */
|
|
const formRef = useRef<XinFormRef<ISysDept>>(null);
|
|
/** 新增表单 */
|
|
const modalFormRef = useRef<XinFormRef<ISysDept>>(null);
|
|
/** 当前选中的部门 key */
|
|
const [selectKey, setSelectKey] = useState<string>('');
|
|
/** 当前(多选框)选择的部门 keys */
|
|
const [checkedKeys , setCheckedKeys] = useState<React.Key[]>([]);
|
|
/** 标签页 KEY */
|
|
const [tabKey, setTabKey] = useState<string>('info');
|
|
/** 标签页 Tab */
|
|
const tabList: CardProps['tabList'] = [
|
|
{
|
|
key: 'info',
|
|
label: t("system.dept.tab.info")
|
|
},
|
|
{
|
|
key: 'users',
|
|
label: t("system.dept.tab.users"),
|
|
disabled: !auth("system.dept.users")
|
|
},
|
|
];
|
|
/** 部门数据 */
|
|
const [deptData, setDeptData] = useState<TreeDataNode[]>([]);
|
|
/** 部门用户列表数据 */
|
|
const [users, setUsers] = useState<ISysUser[]>([]);
|
|
/** 部门用户列表表格参数 */
|
|
const [tableParams, setTableParams] = useState<TableParams>({ page: 1, pageSize: 10, total: 0 });
|
|
/** 加载状态 */
|
|
const [loading, setLoading] = useState<boolean>(false);
|
|
const [tableLoading, setTableLoading] = useState<boolean>(false);
|
|
/** 获取部门用户列表 */
|
|
const selectUsers = async (id: number, params?: TableParams) => {
|
|
try {
|
|
setTableLoading(true);
|
|
const data = await deptUsers(id, omit(params || tableParams, 'total'));
|
|
setUsers(data.data.data!.data);
|
|
setTableParams({
|
|
...tableParams,
|
|
total: data.data.data!.total
|
|
})
|
|
} finally {
|
|
setTableLoading(false);
|
|
}
|
|
}
|
|
/** 刷新部门数据 */
|
|
const refreshDept = async () => {
|
|
setLoading(true);
|
|
/** 转换部门类型 */
|
|
const convertDeptToTreeData = (depts: ISysDept[]): TreeDataNode[] => {
|
|
if (!depts?.length) return [];
|
|
return depts.map(dept => {
|
|
deptMap.set(dept.id!.toString(), omit(dept, 'children'));
|
|
return {
|
|
title: dept.name || t("system.dept.tab.users"),
|
|
key: dept.id?.toString() || '',
|
|
icon: dept.type === 0 ? <BankOutlined /> : dept.type === 1 ? <TeamOutlined /> : <UserOutlined />,
|
|
children: convertDeptToTreeData(dept.children || [])
|
|
}
|
|
});
|
|
};
|
|
try {
|
|
const res = await queryDept();
|
|
if(res.data.data && res.data.data.length > 0) {
|
|
deptMap.clear();
|
|
setDeptData(convertDeptToTreeData(res.data.data));
|
|
if(!selectKey || !deptMap.has(selectKey)) {
|
|
setSelectKey(res.data.data[0].id!.toString());
|
|
formRef.current?.setFieldsValue(omit(res.data.data[0], 'children'));
|
|
if(auth("system.dept.users")) {
|
|
await selectUsers(res.data.data[0].id!);
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
/** 新增部门点击事件 */
|
|
const addChange = (children: boolean = false) => {
|
|
modalFormRef.current?.resetFields();
|
|
modalFormRef.current?.setFieldsValue({
|
|
parent_id: children ? Number(selectKey) : 0,
|
|
sort: 0,
|
|
status: 0,
|
|
type: 0
|
|
})
|
|
modalFormRef.current?.open();
|
|
}
|
|
/** 部门选择事件 */
|
|
const onSelect: TreeProps['onSelect'] = (key) => {
|
|
if(key && key.length >= 1) {
|
|
setSelectKey(key[0].toString());
|
|
const deptInfo = deptMap.get(key[0].toString());
|
|
if (deptInfo) {
|
|
formRef.current?.setFieldsValue(deptInfo);
|
|
}
|
|
selectUsers(Number(key[0])).then();
|
|
}
|
|
}
|
|
/** 部门多选框选中事件 */
|
|
const onCheck: TreeProps['onCheck'] = (checkedKeys) => {
|
|
if(isArray(checkedKeys)) {
|
|
setCheckedKeys(checkedKeys);
|
|
}else {
|
|
setCheckedKeys(checkedKeys.checked);
|
|
}
|
|
}
|
|
/** 表单提交 */
|
|
const onSubmit = async (data: ISysDept, update: boolean = false) => {
|
|
try {
|
|
setLoading(true);
|
|
if(!update) {
|
|
await createDept(data);
|
|
message.success(t("system.dept.createSuccess"));
|
|
modalFormRef.current?.close();
|
|
}else {
|
|
await updateDept(Number(selectKey), data);
|
|
message.success(t("system.dept.updateSuccess"));
|
|
}
|
|
await refreshDept();
|
|
}finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
/** 批量删除 */
|
|
const onDeleteConfirm = async () => {
|
|
try {
|
|
setLoading(true);
|
|
await deleteDept(checkedKeys);
|
|
await refreshDept();
|
|
setCheckedKeys([]);
|
|
message.success(t("system.dept.deleteSuccess"));
|
|
}finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
/** 表单列数据 */
|
|
const columns: FormColumn<ISysDept>[] = [
|
|
{
|
|
title: t("system.dept.column.name"),
|
|
valueType: 'text',
|
|
dataIndex: 'name',
|
|
rules: [{required: true, message: t("system.dept.column.name.required")}],
|
|
},
|
|
{
|
|
title: t("system.dept.column.code"),
|
|
valueType: 'text',
|
|
dataIndex: 'code',
|
|
rules: [{required: true, message: t("system.dept.column.code.required")}],
|
|
},
|
|
{
|
|
title: t("system.dept.column.type"),
|
|
valueType: 'radioButton',
|
|
dataIndex: 'type',
|
|
fieldProps: {
|
|
options: [
|
|
{ value: 0, label: t("system.dept.column.type.0") },
|
|
{ value: 1, label: t("system.dept.column.type.1") },
|
|
{ value: 2, label: t("system.dept.column.type.2") },
|
|
],
|
|
},
|
|
rules: [{required: true, message: t("system.dept.column.type.required")}],
|
|
},
|
|
{
|
|
title: t("system.dept.column.parent"),
|
|
valueType: 'treeSelect',
|
|
dataIndex: 'parent_id',
|
|
fieldProps: {
|
|
treeData: [
|
|
{
|
|
title: t("system.dept.column.parent.0"),
|
|
value: 0,
|
|
children: deptData
|
|
}
|
|
],
|
|
fieldNames: { label: 'title', value: 'key' },
|
|
disabled: true
|
|
},
|
|
rules: [{required: true, message: t("system.dept.column.parent.required")}],
|
|
},
|
|
{
|
|
title: t("system.dept.column.email"),
|
|
valueType: 'text',
|
|
dataIndex: 'email',
|
|
},
|
|
{
|
|
title: t("system.dept.column.address"),
|
|
valueType: 'text',
|
|
dataIndex: 'address',
|
|
},
|
|
{
|
|
title: t("system.dept.column.phone"),
|
|
valueType: 'text',
|
|
dataIndex: 'phone',
|
|
},
|
|
{
|
|
title: t("system.dept.column.sort"),
|
|
valueType: 'digit',
|
|
dataIndex: 'sort',
|
|
rules: [{required: true, message: t("system.dept.column.sort.required")}],
|
|
},
|
|
{
|
|
title: t("system.dept.column.status"),
|
|
valueType: 'radioButton',
|
|
dataIndex: 'status',
|
|
fieldProps: {
|
|
options: [
|
|
{ value: 0, label: t("system.dept.column.status.0") },
|
|
{ value: 1, label: t("system.dept.column.status.1") },
|
|
]
|
|
},
|
|
rules: [{required: true, message: t("system.dept.column.status.required")}],
|
|
},
|
|
{
|
|
title: t("system.dept.column.remark"),
|
|
valueType: 'textarea',
|
|
dataIndex: 'remark',
|
|
},
|
|
];
|
|
/** 部门用户列表表格列 */
|
|
const usersColumns: TableProps<ISysUser>['columns'] = [
|
|
{
|
|
title: t("system.dept.users.column.id"),
|
|
dataIndex: 'id',
|
|
key: 'id',
|
|
align: 'center',
|
|
},
|
|
{
|
|
title: t("system.dept.users.column.username"),
|
|
dataIndex: 'username',
|
|
key: 'username',
|
|
align: 'center',
|
|
},
|
|
{
|
|
title: t("system.dept.users.column.nickname"),
|
|
dataIndex: 'nickname',
|
|
key: 'nickname',
|
|
align: 'center',
|
|
},
|
|
{
|
|
title: t("system.dept.users.column.nickname"),
|
|
dataIndex: 'email',
|
|
key: 'email',
|
|
align: 'center',
|
|
},
|
|
{
|
|
title: t("system.dept.users.column.mobile"),
|
|
dataIndex: 'mobile',
|
|
key: 'mobile',
|
|
align: 'center',
|
|
},
|
|
{
|
|
title: t("system.dept.users.column.status"),
|
|
dataIndex: 'status',
|
|
key: 'status',
|
|
align: 'center',
|
|
render: (value) => (
|
|
<>
|
|
{value === 1 && <Tag color={'success'}>{t("system.dept.users.column.status.0")}</Tag>}
|
|
{value === 0 && <Tag color={'error'}>{t("system.dept.users.column.status.1")}</Tag>}
|
|
</>
|
|
)
|
|
},
|
|
];
|
|
/** 初始化 */
|
|
useEffect(() => { refreshDept() }, []);
|
|
|
|
return (
|
|
<>
|
|
<div className={'mb-5'}>
|
|
<Typography.Title level={3}>{t("system.dept.page.title")}</Typography.Title>
|
|
<Typography.Text type="secondary">{t("system.dept.page.description")}</Typography.Text>
|
|
</div>
|
|
<Row gutter={[20, 20]}>
|
|
<Col xxl={12} lg={12} xs={24}>
|
|
<XinForm<ISysDept>
|
|
layoutType={'ModalForm'}
|
|
formRef={modalFormRef}
|
|
modalProps={{
|
|
title: t("system.dept.createModalTitle"),
|
|
styles: { body: { paddingTop: 20} },
|
|
width: 800
|
|
}}
|
|
onFinish={async (data: ISysDept) => {
|
|
await onSubmit(data, false);
|
|
return true;
|
|
}}
|
|
columns={columns}
|
|
layout={'vertical'}
|
|
grid
|
|
rowProps={{gutter: [30, 0]}}
|
|
colProps={{ span: 12 }}
|
|
/>
|
|
<Card
|
|
title={(
|
|
<Space>
|
|
<AuthButton auth={"system.dept.create"}>
|
|
<Button
|
|
loading={loading}
|
|
children={t("system.dept.createButton")}
|
|
icon={<PlusOutlined />}
|
|
type={'primary'}
|
|
onClick={() => addChange()}
|
|
/>
|
|
</AuthButton>
|
|
<AuthButton auth={"system.dept.create"}>
|
|
<Button
|
|
loading={loading}
|
|
children={t("system.dept.createChildrenButton")}
|
|
icon={<PlusOutlined />}
|
|
type={'primary'}
|
|
onClick={() => addChange(true)}
|
|
/>
|
|
</AuthButton>
|
|
</Space>
|
|
)}
|
|
variant={'borderless'}
|
|
loading={loading}
|
|
styles={{ body: { minHeight: '70vh' } }}
|
|
>
|
|
{checkedKeys.length > 0 && (
|
|
<Alert
|
|
style={{ marginBottom: 20 }}
|
|
description={t("system.dept.checkedMessage", {checked: checkedKeys.length})}
|
|
type="info"
|
|
action={
|
|
<Space>
|
|
<Button size="small" type="primary" onClick={()=> setCheckedKeys([])}>
|
|
{t("system.dept.unselect")}
|
|
</Button>
|
|
<AuthButton auth={"system.dept.delete"}>
|
|
<Popconfirm
|
|
okText={t("system.dept.delete.ok")}
|
|
cancelText={t("system.dept.delete.cancel")}
|
|
title={t("system.dept.delete.title")}
|
|
description={t("system.dept.delete.description")}
|
|
onConfirm={() => onDeleteConfirm()}
|
|
>
|
|
<Button type="primary" icon={<DeleteOutlined />} size={'small'} danger loading={loading}/>
|
|
</Popconfirm>
|
|
</AuthButton>
|
|
</Space>
|
|
}
|
|
/>
|
|
)}
|
|
<Tree
|
|
checkable
|
|
treeData={deptData}
|
|
showIcon={true}
|
|
checkStrictly={true}
|
|
selectedKeys={[selectKey]}
|
|
defaultExpandedKeys={[selectKey]}
|
|
onSelect={onSelect}
|
|
checkedKeys={checkedKeys}
|
|
onCheck={onCheck}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col xxl={12} lg={12} xs={24}>
|
|
<Card
|
|
variant={'borderless'}
|
|
tabList={tabList}
|
|
tabProps={{ accessKey: tabKey }}
|
|
onTabChange={setTabKey}
|
|
styles={{ body: { minHeight: '70vh' } }}
|
|
>
|
|
<XinForm<ISysDept>
|
|
formRef={formRef}
|
|
onFinish={async (data: ISysDept) => {
|
|
await onSubmit(data, true);
|
|
return true;
|
|
}}
|
|
columns={columns}
|
|
layout={'horizontal'}
|
|
style={{ display: tabKey === 'info' ? "block" : "none" }}
|
|
submitter={{
|
|
render: (dom) => (
|
|
<AuthButton auth={"system.dept.update"}>
|
|
{dom['submit']}
|
|
</AuthButton>
|
|
),
|
|
submitText: t("system.dept.saveInfo")
|
|
}}
|
|
/>
|
|
<AuthButton auth={"system.dept.users"}>
|
|
<Table<ISysUser>
|
|
style={{ display: tabKey === 'users' ? "block" : "none" }}
|
|
dataSource={users}
|
|
bordered={true}
|
|
columns={usersColumns}
|
|
loading={tableLoading}
|
|
size={'small'}
|
|
pagination={{
|
|
current: tableParams.page,
|
|
pageSize: tableParams.pageSize,
|
|
total: tableParams.total,
|
|
showSizeChanger: true,
|
|
onChange: (page, pageSize) => {
|
|
const params = { total: tableParams.total, pageSize, page }
|
|
setTableParams(params);
|
|
selectUsers(Number(selectKey), params).then();
|
|
}
|
|
}}
|
|
scroll={{x: 600}}
|
|
/>
|
|
</AuthButton>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Dept;
|