| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- import {
- MEMBER_TYPE_COLORS,
- MEMBER_TYPE_LABELS,
- STATUS_DISABLED,
- STATUS_ENABLED,
- STATUS_OPTIONS,
- } from '@/defines';
- import { BindRolesDrawer } from '@/pages/Admin/_shared/BindRolesDrawer';
- import { UserPermDrawer } from '@/pages/Admin/_shared/UserPermDrawer';
- import {
- useProductRolesBase,
- useUserProductRoles,
- } from '@/pages/Admin/_shared/useUserProductRoles';
- import { fetchDeptTree } from '@/services/dept';
- import {
- fetchResetPassword,
- fetchUpdateUserStatus,
- fetchUserCredentials,
- fetchUserList,
- } from '@/services/user';
- import { unixTimeFormat } from '@/utils/timeUtils';
- import { StopOutlined } from '@ant-design/icons';
- import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
- import { useIntl } from '@umijs/max';
- import { Button, Popconfirm, Tag, message } from 'antd';
- import { useEffect, useMemo, useRef, useState } from 'react';
- import { UserForm } from './components/Form';
- import { UserCredentialModal } from './components/UserCredentialModal';
- const flattenDeptTree = (items: API.DeptItem[]): { label: string; value: number }[] =>
- items.flatMap((d) => [
- { label: d.name, value: d.id },
- ...(d.children ? flattenDeptTree(d.children) : []),
- ]);
- export default function UserPage() {
- const intl = useIntl();
- const actionRef = useRef<ActionType>();
- const [pageUsers, setPageUsers] = useState<API.UserItem[]>([]);
- const [deptOptions, setDeptOptions] = useState<{ label: string; value: number }[]>([]);
- const [rolesDrawer, setRolesDrawer] = useState<{ open: boolean; userId: number }>({
- open: false,
- userId: 0,
- });
- const [permDrawer, setPermDrawer] = useState<{ open: boolean; userId: number }>({
- open: false,
- userId: 0,
- });
- const [refreshKey, setRefreshKey] = useState(0);
- const [credData, setCredData] = useState<API.FetchUserCredentialsResp | null>(null);
- const [credOpen, setCredOpen] = useState(false);
- useEffect(() => {
- fetchDeptTree().then((res) => {
- setDeptOptions([{ label: '无部门', value: 0 }, ...flattenDeptTree(res.data ?? [])]);
- });
- }, []);
- const userIds = useMemo(() => pageUsers.map((u) => u.id), [pageUsers]);
- const productRolesBase = useProductRolesBase();
- const { userProductInfo } = useUserProductRoles(userIds, productRolesBase, refreshKey);
- const handleToggleStatus = async (r: API.UserItem) => {
- const res = await fetchUpdateUserStatus({
- id: r.id,
- status: r.status === STATUS_ENABLED ? STATUS_DISABLED : STATUS_ENABLED,
- });
- if (!res.success) return;
- message.success('操作成功');
- actionRef.current?.reload();
- };
- const showCredentials = async (ticket: string) => {
- const res = await fetchUserCredentials({ ticket });
- if (!res.success || !res.data) return;
- setCredData(res.data);
- setCredOpen(true);
- };
- const handleCreateSuccess = async (ticket?: string) => {
- actionRef.current?.reload();
- if (ticket) await showCredentials(ticket);
- };
- const handleResetPassword = async (r: API.UserItem) => {
- const res = await fetchResetPassword({ userId: r.id });
- if (!res.success || !res.data) return;
- message.success('密码已重置');
- await showCredentials(res.data.credentialsTicket);
- };
- const columns: ProColumns<API.UserItem>[] = [
- { title: '用户名', dataIndex: 'username', width: 200 },
- { title: '昵称', dataIndex: 'nickname', width: 200 },
- {
- title: '状态',
- dataIndex: 'status',
- width: 80,
- valueType: 'select',
- fieldProps: { options: STATUS_OPTIONS },
- render: (_, r) => (
- <Tag color={r.status === STATUS_ENABLED ? 'success' : 'error'}>
- {r.status === STATUS_ENABLED ? '启用' : '禁用'}
- </Tag>
- ),
- },
- {
- title: '部门',
- dataIndex: 'deptId',
- width: 150,
- valueType: 'select',
- fieldProps: { options: deptOptions, showSearch: true },
- render: (_, r) => {
- if (!r.deptId)
- return <span className="text-(--ant-color-text-quaternary) text-xs">无部门</span>;
- const dept = deptOptions.find((d) => d.value === r.deptId);
- return (
- dept?.label ?? <span className="text-(--ant-color-text-quaternary) text-xs">无部门</span>
- );
- },
- },
- {
- title: '产品角色',
- key: 'productRoles',
- search: false,
- render: (_, r) => {
- const info = userProductInfo.get(r.id) ?? [];
- if (info.length === 0)
- return <span className="text-(--ant-color-text-quaternary) text-xs">无</span>;
- return (
- <div className="flex flex-col gap-1">
- {info.map((p) => (
- <div
- key={p.productName}
- className="inline-flex flex-row gap-1 rounded border border-dashed border-(--ant-color-border) bg-(--ant-color-fill-quaternary) px-2 py-2 self-start"
- >
- <Tag color="purple" className="mr-0 self-center">
- {p.productName}
- {p.productStatus !== STATUS_ENABLED && (
- <StopOutlined className="ml-1 text-(--ant-color-error)!" />
- )}
- </Tag>
- <Tag
- color={MEMBER_TYPE_COLORS[p.memberType ?? ''] ?? 'default'}
- className="mr-0 self-center"
- >
- {MEMBER_TYPE_LABELS[p.memberType ?? ''] ?? p.memberType}
- </Tag>
- <div className="flex flex-row flex-wrap gap-1 items-center">
- {p.roleNames.length > 0 ? (
- p.roleNames.map((name) => (
- <Tag key={name} className="mr-0">
- {name}
- </Tag>
- ))
- ) : (
- <span className="text-(--ant-color-text-quaternary) text-xs whitespace-nowrap">
- 无角色
- </span>
- )}
- </div>
- </div>
- ))}
- </div>
- );
- },
- },
- {
- title: '创建时间',
- dataIndex: 'createTime',
- width: 180,
- search: false,
- render: (_, r) => unixTimeFormat(r.createTime),
- },
- {
- title: '操作',
- valueType: 'option',
- width: 320,
- render: (_, r) => [
- <UserForm
- key="edit"
- mode="edit"
- initialValues={r}
- trigger={<a>编辑</a>}
- onSuccess={() => actionRef.current?.reload()}
- />,
- <UserForm
- key="copy"
- mode="copy"
- initialValues={r}
- trigger={<a>复制</a>}
- onSuccess={handleCreateSuccess}
- />,
- <a key="roles" onClick={() => setRolesDrawer({ open: true, userId: r.id })}>
- 分配角色
- </a>,
- <a key="perms" onClick={() => setPermDrawer({ open: true, userId: r.id })}>
- 设置权限
- </a>,
- <Popconfirm
- key="reset"
- title={`确认重置「${r.username}」的密码?`}
- onConfirm={() => handleResetPassword(r)}
- >
- <a>重置密码</a>
- </Popconfirm>,
- <Popconfirm
- key="status"
- title={
- r.status === STATUS_ENABLED
- ? `确认冻结「${r.username}」?`
- : `确认解冻「${r.username}」?`
- }
- onConfirm={() => handleToggleStatus(r)}
- >
- <a className={r.status === STATUS_ENABLED ? 'text-(--ant-color-error)' : ''}>
- {r.status === STATUS_ENABLED ? '冻结' : '解冻'}
- </a>
- </Popconfirm>,
- ],
- },
- ];
- return (
- <PageContainer title={intl.formatMessage({ id: 'admin.user.title' })}>
- <ProTable<API.UserItem>
- actionRef={actionRef}
- columns={columns}
- rowKey="id"
- search={{ span: { xs: 24, sm: 12, md: 8, lg: 6, xl: 4, xxl: 4 } }}
- request={fetchUserList}
- onDataSourceChange={setPageUsers}
- scroll={{ x: 1280 }}
- tableLayout="fixed"
- pagination={{
- defaultPageSize: 20,
- pageSizeOptions: [10, 20, 50, 100],
- showSizeChanger: true,
- }}
- toolBarRender={() => [
- <UserForm
- key="create"
- mode="add"
- trigger={<Button type="primary">新建用户</Button>}
- onSuccess={handleCreateSuccess}
- />,
- ]}
- />
- <BindRolesDrawer
- {...rolesDrawer}
- onClose={() => setRolesDrawer((p) => ({ ...p, open: false }))}
- onSuccess={() => setRefreshKey((k) => k + 1)}
- />
- <UserPermDrawer
- {...permDrawer}
- onClose={() => setPermDrawer((p) => ({ ...p, open: false }))}
- />
- <UserCredentialModal open={credOpen} data={credData} onClose={() => setCredOpen(false)} />
- </PageContainer>
- );
- }
|