wsj
5 days ago be8c526a454aa9081000d15f26bebdef8c183aea
src/pages/system/users/index.tsx
@@ -7,35 +7,20 @@
} from '@ant-design/icons';
import { ProTable } from '@ant-design/pro-components';
import type { ActionType } from '@ant-design/pro-components';
import {
  App as AntdApp,
  Button,
  Descriptions,
  Drawer,
  Form,
  Input,
  InputNumber,
  Modal,
  Select,
  Space,
  Tag,
  Upload,
} from 'antd';
import { App as AntdApp, Button, Modal, Upload } from 'antd';
import type { UploadFile } from 'antd';
import { createUserColumns, getEmployeeStatusMeta } from './columns';
import { createUserColumns } from './columns';
import UserFormDrawer from './components/UserFormDrawer';
import type { UserFormMode } from './components/UserFormDrawer';
import {
  createEmployee,
  deleteEmployee,
  exportEmployees,
  getEmployeeDetail,
  getEmployeeList,
  getOrgTree,
  getRoleOptions,
  importEmployees,
  updateEmployee,
} from './server';
import type {
  EmployeeFormValues,
  EmployeeListQuery,
  EmployeeVO,
  OrgTreeNode,
@@ -54,15 +39,11 @@
  const actionRef = useRef<ActionType>();
  const lastQueryRef = useRef<TenantEmployeeListRequest>({});
  const { message, modal } = AntdApp.useApp();
  const [form] = Form.useForm<EmployeeFormValues>();
  const [orgOptions, setOrgOptions] = useState<SelectOption[]>([]);
  const [roleOptions, setRoleOptions] = useState<SelectOption[]>([]);
  const [formOpen, setFormOpen] = useState(false);
  const [editing, setEditing] = useState<EmployeeVO | null>(null);
  const [submitting, setSubmitting] = useState(false);
  const [detail, setDetail] = useState<EmployeeVO | null>(null);
  const [detailOpen, setDetailOpen] = useState(false);
  const [detailLoading, setDetailLoading] = useState(false);
  const [formMode, setFormMode] = useState<UserFormMode>('create');
  const [activeEmployee, setActiveEmployee] = useState<EmployeeVO | null>(null);
  const [importOpen, setImportOpen] = useState(false);
  const [importFileList, setImportFileList] = useState<UploadFile[]>([]);
  const [importing, setImporting] = useState(false);
@@ -101,48 +82,31 @@
  );
  const openCreate = useCallback(() => {
    setEditing(null);
    form.resetFields();
    setActiveEmployee(null);
    setFormMode('create');
    setFormOpen(true);
  }, [form]);
  }, []);
  const closeForm = useCallback(() => {
    setFormOpen(false);
    setEditing(null);
    setActiveEmployee(null);
  }, []);
  const openEdit = useCallback(
    (record: EmployeeVO) => {
      setEditing(record);
      form.setFieldsValue({
        name: record.name,
        phone: record.phone,
        orgId: record.orgId,
        roleId: record.roleId,
        position: record.position,
        email: record.email,
        holdLimit: record.holdLimit,
        region: record.region,
      });
  const handleFormSuccess = useCallback(() => {
    closeForm();
    actionRef.current?.reload();
  }, [closeForm]);
  const openEdit = useCallback((record: EmployeeVO) => {
    setActiveEmployee(record);
    setFormMode('edit');
      setFormOpen(true);
    },
    [form],
  );
  }, []);
  const handleDetail = useCallback(async (record: EmployeeVO) => {
    setDetail(null);
    setDetailOpen(true);
    setDetailLoading(true);
    try {
      const data = await getEmployeeDetail({ id: record.id });
      setDetail(data);
    } catch {
      // 错误已由 request 统一提示
      setDetailOpen(false);
    } finally {
      setDetailLoading(false);
    }
  const openDetail = useCallback((record: EmployeeVO) => {
    setActiveEmployee(record);
    setFormMode('detail');
    setFormOpen(true);
  }, []);
  const handleDelete = useCallback(
@@ -162,35 +126,6 @@
    },
    [message, modal],
  );
  const handleSubmit = useCallback(async () => {
    let values: EmployeeFormValues;
    try {
      values = await form.validateFields();
    } catch {
      return;
    }
    setSubmitting(true);
    try {
      if (editing) {
        await updateEmployee({ id: editing.id, ...values });
        message.success('员工信息已更新');
      } else {
        await createEmployee(values);
        message.success('员工创建成功');
      }
      setFormOpen(false);
      setEditing(null);
      form.resetFields();
      actionRef.current?.reload();
    } catch {
      // 错误已由 request 统一提示
    } finally {
      setSubmitting(false);
    }
  }, [editing, form, message]);
  const handleExport = useCallback(async () => {
    setExporting(true);
@@ -240,11 +175,11 @@
        roleOptions,
        orgNameMap,
        roleNameMap,
        onDetail: handleDetail,
        onDetail: openDetail,
        onEdit: openEdit,
        onDelete: handleDelete,
      }),
    [orgOptions, roleOptions, orgNameMap, roleNameMap, handleDetail, openEdit, handleDelete],
    [orgOptions, roleOptions, orgNameMap, roleNameMap, openDetail, openEdit, handleDelete],
  );
  return (
@@ -307,118 +242,15 @@
        }}
      />
      <Drawer
        title={editing ? '编辑员工' : '新增员工'}
        width={420}
      <UserFormDrawer
        open={formOpen}
        forceRender
        mode={formMode}
        record={activeEmployee}
        orgOptions={orgOptions}
        roleOptions={roleOptions}
        onClose={closeForm}
        footer={
          <Space style={{ display: 'flex', justifyContent: 'flex-end' }}>
            <Button onClick={closeForm}>取消</Button>
            <Button type="primary" loading={submitting} onClick={handleSubmit}>
              {editing ? '保存' : '创建'}
            </Button>
          </Space>
        }
      >
        <Form form={form} layout="vertical" preserve={false}>
          <Form.Item
            name="name"
            label="姓名"
            rules={[{ required: true, message: '请输入姓名' }]}
          >
            <Input placeholder="请输入员工姓名" />
          </Form.Item>
          <Form.Item
            name="phone"
            label="手机号"
            rules={[
              { required: true, message: '请输入手机号' },
              { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' },
            ]}
          >
            <Input placeholder="请输入手机号" maxLength={11} />
          </Form.Item>
          <Form.Item
            name="orgId"
            label="所属组织"
            rules={[{ required: true, message: '请选择所属组织' }]}
          >
            <Select
              showSearch
              optionFilterProp="label"
              options={orgOptions}
              placeholder="请选择所属组织"
        onSuccess={handleFormSuccess}
            />
          </Form.Item>
          <Form.Item
            name="roleId"
            label="角色"
            rules={[{ required: true, message: '请选择角色' }]}
          >
            <Select
              showSearch
              optionFilterProp="label"
              options={roleOptions}
              placeholder="请选择角色"
            />
          </Form.Item>
          <Form.Item name="position" label="职位">
            <Input placeholder="请输入职位" />
          </Form.Item>
          <Form.Item name="region" label="负责区域">
            <Input placeholder="请输入负责区域" />
          </Form.Item>
          <Form.Item name="holdLimit" label="客户持有上限">
            <InputNumber min={0} precision={0} style={{ width: '100%' }} />
          </Form.Item>
          <Form.Item
            name="email"
            label="邮箱"
            rules={[{ type: 'email', message: '请输入正确的邮箱' }]}
          >
            <Input placeholder="请输入邮箱" />
          </Form.Item>
        </Form>
      </Drawer>
      <Drawer
        title="员工详情"
        width={480}
        open={detailOpen}
        loading={detailLoading}
        onClose={() => {
          setDetailOpen(false);
          setDetail(null);
        }}
      >
        {detail && (
          <Descriptions column={1} bordered size="small">
            <Descriptions.Item label="姓名">{detail.name}</Descriptions.Item>
            <Descriptions.Item label="手机号">{detail.phone}</Descriptions.Item>
            <Descriptions.Item label="所属组织">
              {orgNameMap.get(detail.orgId) ?? `#${detail.orgId}`}
            </Descriptions.Item>
            <Descriptions.Item label="角色">
              {roleNameMap.get(detail.roleId) ?? `#${detail.roleId}`}
            </Descriptions.Item>
            <Descriptions.Item label="职位">{detail.position || '-'}</Descriptions.Item>
            <Descriptions.Item label="邮箱">{detail.email || '-'}</Descriptions.Item>
            <Descriptions.Item label="负责区域">{detail.region || '-'}</Descriptions.Item>
            <Descriptions.Item label="客户持有上限">
              {detail.holdLimit ?? '-'}
            </Descriptions.Item>
            <Descriptions.Item label="状态">
              <Tag color={getEmployeeStatusMeta(detail.status).color} bordered={false}>
                {getEmployeeStatusMeta(detail.status).text}
              </Tag>
            </Descriptions.Item>
            <Descriptions.Item label="入职日期">{detail.hireDate || '-'}</Descriptions.Item>
            <Descriptions.Item label="创建时间">{detail.createdAt || '-'}</Descriptions.Item>
          </Descriptions>
        )}
      </Drawer>
      <Modal
        title="导入员工"