| | |
| | | import { useCallback, useEffect, useState } from 'react'; |
| | | import { useCallback, useEffect, useMemo, useState } from 'react'; |
| | | import { ProFormCascader, ProFormSelect } from '@ant-design/pro-components'; |
| | | import { |
| | | App as AntdApp, |
| | | Button, |
| | |
| | | Form, |
| | | Input, |
| | | InputNumber, |
| | | Select, |
| | | Space, |
| | | Spin, |
| | | } from 'antd'; |
| | | import { createEmployee, getEmployeeDetail, updateEmployee } from '../server'; |
| | | import type { EmployeeFormValues, EmployeeVO, SelectOption } from '../types'; |
| | | import { createEmployee, getEmployeeDetail, updateEmployee } from '../service'; |
| | | import type { |
| | | EmployeeFormValues, |
| | | EmployeeVO, |
| | | OrgTreeNode, |
| | | SelectOption, |
| | | } from '../types'; |
| | | |
| | | interface OrgCascaderOption { |
| | | value: number; |
| | | label: string; |
| | | children?: OrgCascaderOption[]; |
| | | } |
| | | |
| | | /** 去掉最外层公司节点,只保留公司下的组织用于选择。 */ |
| | | const getSelectableOrgNodes = (nodes: OrgTreeNode[]): OrgTreeNode[] => |
| | | nodes.flatMap((node) => (node.children?.length ? node.children : [node])); |
| | | |
| | | /** 组织树转为级联选择选项,按层级展示。 */ |
| | | const toCascaderOptions = (nodes: OrgTreeNode[]): OrgCascaderOption[] => |
| | | nodes.map((node) => ({ |
| | | value: node.id, |
| | | label: node.orgName, |
| | | children: node.children?.length ? toCascaderOptions(node.children) : undefined, |
| | | })); |
| | | |
| | | /** 在组织树中查找某个组织 ID 的完整路径,用于编辑 / 详情时回填级联值。 */ |
| | | const findOrgPath = (nodes: OrgTreeNode[], targetId: number): number[] | null => { |
| | | for (const node of nodes) { |
| | | if (node.id === targetId) return [node.id]; |
| | | |
| | | const childPath = node.children ? findOrgPath(node.children, targetId) : null; |
| | | if (childPath) return [node.id, ...childPath]; |
| | | } |
| | | |
| | | return null; |
| | | }; |
| | | |
| | | export type UserFormMode = 'create' | 'edit' | 'detail'; |
| | | |
| | |
| | | mode: UserFormMode; |
| | | /** 编辑 / 详情对象;新增时为空。 */ |
| | | record: EmployeeVO | null; |
| | | orgOptions: SelectOption[]; |
| | | /** 组织树,用于所属组织的二级联动选择。 */ |
| | | orgTree: OrgTreeNode[]; |
| | | roleOptions: SelectOption[]; |
| | | onClose: () => void; |
| | | onSuccess: () => void; |
| | | } |
| | | |
| | | const toFormValues = (employee: EmployeeVO): EmployeeFormValues => ({ |
| | | const toFormValues = ( |
| | | employee: EmployeeVO, |
| | | orgTree: OrgTreeNode[], |
| | | ): EmployeeFormValues => ({ |
| | | name: employee.name, |
| | | phone: employee.phone, |
| | | orgId: employee.orgId, |
| | | orgPath: findOrgPath(getSelectableOrgNodes(orgTree), employee.orgId) ?? [], |
| | | roleId: employee.roleId, |
| | | position: employee.position, |
| | | email: employee.email, |
| | |
| | | open, |
| | | mode, |
| | | record, |
| | | orgOptions, |
| | | orgTree, |
| | | roleOptions, |
| | | onClose, |
| | | onSuccess, |
| | |
| | | const [loading, setLoading] = useState(false); |
| | | const { message } = AntdApp.useApp(); |
| | | const isDetail = mode === 'detail'; |
| | | const orgCascaderOptions = useMemo( |
| | | () => toCascaderOptions(getSelectableOrgNodes(orgTree)), |
| | | [orgTree], |
| | | ); |
| | | |
| | | useEffect(() => { |
| | | if (!open) return; |
| | |
| | | setLoading(true); |
| | | try { |
| | | const data = await getEmployeeDetail({ id: record.id }); |
| | | if (active) form.setFieldsValue(toFormValues(data)); |
| | | if (active) form.setFieldsValue(toFormValues(data, orgTree)); |
| | | } catch { |
| | | // 错误已由 request 统一提示 |
| | | if (active) onClose(); |
| | |
| | | } |
| | | |
| | | if (mode === 'edit' && record) { |
| | | form.setFieldsValue(toFormValues(record)); |
| | | form.setFieldsValue(toFormValues(record, orgTree)); |
| | | } |
| | | }; |
| | | |
| | |
| | | return () => { |
| | | active = false; |
| | | }; |
| | | }, [form, mode, onClose, open, record]); |
| | | }, [form, mode, onClose, open, orgTree, record]); |
| | | |
| | | const handleSubmit = useCallback(async () => { |
| | | if (isDetail) return; |
| | |
| | | } |
| | | |
| | | setSubmitting(true); |
| | | |
| | | const { orgPath, ...rest } = values; |
| | | const orgId = orgPath[orgPath.length - 1]; |
| | | |
| | | try { |
| | | if (mode === 'edit' && record) { |
| | | await updateEmployee({ id: record.id, ...values }); |
| | | await updateEmployee({ id: record.id, ...rest, orgId }); |
| | | message.success('员工信息已更新'); |
| | | } else { |
| | | await createEmployee(values); |
| | | await createEmployee({ ...rest, orgId }); |
| | | message.success('员工创建成功'); |
| | | } |
| | | |
| | |
| | | > |
| | | <Input placeholder="请输入手机号" maxLength={11} disabled={isDetail} /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="orgId" |
| | | <ProFormCascader |
| | | name="orgPath" |
| | | label="所属组织" |
| | | rules={[{ required: true, message: '请选择所属组织' }]} |
| | | > |
| | | <Select |
| | | showSearch |
| | | optionFilterProp="label" |
| | | options={orgOptions} |
| | | placeholder="请选择所属组织" |
| | | disabled={isDetail} |
| | | /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | placeholder="请选择所属组织" |
| | | disabled={isDetail} |
| | | fieldProps={{ |
| | | options: orgCascaderOptions, |
| | | changeOnSelect: true, |
| | | showSearch: true, |
| | | style: { width: '100%' }, |
| | | }} |
| | | /> |
| | | <ProFormSelect |
| | | name="roleId" |
| | | label="角色" |
| | | rules={[{ required: true, message: '请选择角色' }]} |
| | | placeholder="请选择角色" |
| | | disabled={isDetail} |
| | | showSearch |
| | | options={roleOptions} |
| | | fieldProps={{ optionFilterProp: 'label', style: { width: '100%' } }} |
| | | /> |
| | | <Form.Item |
| | | name="holdLimit" |
| | | label="客户持有上限" |
| | | rules={[{ required: true, message: '请输入客户持有上限' }]} |
| | | > |
| | | <Select |
| | | showSearch |
| | | optionFilterProp="label" |
| | | options={roleOptions} |
| | | placeholder="请选择角色" |
| | | <InputNumber |
| | | min={0} |
| | | precision={0} |
| | | placeholder="请输入客户持有上限" |
| | | style={{ width: '100%' }} |
| | | disabled={isDetail} |
| | | /> |
| | | </Form.Item> |
| | |
| | | </Form.Item> |
| | | <Form.Item name="region" label="负责区域"> |
| | | <Input placeholder="请输入负责区域" disabled={isDetail} /> |
| | | </Form.Item> |
| | | <Form.Item name="holdLimit" label="客户持有上限"> |
| | | <InputNumber |
| | | min={0} |
| | | precision={0} |
| | | style={{ width: '100%' }} |
| | | disabled={isDetail} |
| | | /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="email" |