| | |
| | | } 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, |
| | |
| | | const actionRef = useRef<ActionType>(); |
| | | const lastQueryRef = useRef<TenantEmployeeListRequest>({}); |
| | | const { message, modal } = AntdApp.useApp(); |
| | | const [form] = Form.useForm<EmployeeFormValues>(); |
| | | const [orgOptions, setOrgOptions] = useState<SelectOption[]>([]); |
| | | const [orgTree, setOrgTree] = useState<OrgTreeNode[]>([]); |
| | | 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); |
| | |
| | | const [orgs, roles] = await Promise.all([getOrgTree(), getRoleOptions()]); |
| | | if (!active) return; |
| | | |
| | | setOrgOptions(flattenOrgOptions(orgs)); |
| | | setOrgTree(orgs); |
| | | setRoleOptions(roles.map((role) => ({ label: role.roleName, value: role.id }))); |
| | | } catch { |
| | | // 错误已由 request 统一提示 |
| | |
| | | }; |
| | | }, []); |
| | | |
| | | const orgOptions = useMemo(() => flattenOrgOptions(orgTree), [orgTree]); |
| | | |
| | | const orgNameMap = useMemo( |
| | | () => new Map(orgOptions.map((option) => [option.value, option.label])), |
| | | [orgOptions], |
| | |
| | | ); |
| | | |
| | | 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, |
| | | }); |
| | | setFormOpen(true); |
| | | }, |
| | | [form], |
| | | ); |
| | | const handleFormSuccess = useCallback(() => { |
| | | closeForm(); |
| | | actionRef.current?.reload(); |
| | | }, [closeForm]); |
| | | |
| | | const handleDetail = useCallback(async (record: EmployeeVO) => { |
| | | setDetail(null); |
| | | setDetailOpen(true); |
| | | setDetailLoading(true); |
| | | const openEdit = useCallback((record: EmployeeVO) => { |
| | | setActiveEmployee(record); |
| | | setFormMode('edit'); |
| | | setFormOpen(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( |
| | |
| | | }, |
| | | [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); |
| | |
| | | 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 ( |
| | |
| | | rowKey="id" |
| | | actionRef={actionRef} |
| | | columns={columns} |
| | | cardProps={{ className: 'table-card' }} |
| | | search={{ labelWidth: 'auto', defaultCollapsed: false }} |
| | | scroll={{ x: 1290 }} |
| | | options={false} |
| | |
| | | }} |
| | | /> |
| | | |
| | | <Drawer |
| | | title={editing ? '编辑员工' : '新增员工'} |
| | | width={420} |
| | | <UserFormDrawer |
| | | open={formOpen} |
| | | forceRender |
| | | mode={formMode} |
| | | record={activeEmployee} |
| | | orgTree={orgTree} |
| | | 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="请选择所属组织" |
| | | /> |
| | | </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> |
| | | onSuccess={handleFormSuccess} |
| | | /> |
| | | |
| | | <Modal |
| | | title="导入员工" |