UserFormPage.tsx 6.16 KB
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { Alert, Button, Card, Form, Result, Space, Spin, message } from 'antd';
import { usersApi } from '../../api/users';
import type { CreateUserReq, UpdateUserReq, UserDetail } from '../../api/users';
import { BizError, isBizError } from '../../api/errors';
import { ERROR_MESSAGES } from './usersConstants';
import UserFormFields from './UserFormFields';
import UserPermissionPanel from './UserPermissionPanel';

interface Props {
  mode: 'create' | 'edit';
}

interface FormValues {
  username?: string;
  userCode?: string;
  userType?: 'NORMAL' | 'SUPER_ADMIN';
  language?: 'zh-CN' | 'en-US' | 'zh-TW';
  canEditDocument?: boolean;
  employeeId?: number;
}

export default function UserFormPage({ mode }: Props) {
  const navigate = useNavigate();
  const params = useParams<{ userId?: string }>();
  const userId = params.userId ? Number(params.userId) : undefined;

  const [form] = Form.useForm<FormValues>();
  const [loadingInitial, setLoadingInitial] = useState(mode === 'edit');
  const [submitting, setSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState<string | null>(null);
  const [notFound, setNotFound] = useState(false);
  const [permissionCategoryIds, setPermissionCategoryIds] = useState<number[]>([]);
  const [originalDetail, setOriginalDetail] = useState<UserDetail | null>(null);

  useEffect(() => {
    if (mode !== 'edit' || userId == null) return;
    let cancelled = false;
    (async () => {
      try {
        const detail = await usersApi.get(userId);
        if (cancelled) return;
        setOriginalDetail(detail);
        form.setFieldsValue({
          username: detail.username,
          userCode: detail.userCode,
          userType: detail.userType,
          language: detail.language as FormValues['language'],
          canEditDocument: false, // detail VO 当前不返回,默认 false
          employeeId: detail.employeeId ?? undefined,
        });
        setPermissionCategoryIds(detail.permissionCategoryIds ?? []);
      } catch (e) {
        if (cancelled) return;
        if (isBizError(e) && e.code === 40401) {
          setNotFound(true);
        } else if (isBizError(e)) {
          setErrorMessage(e.message || (ERROR_MESSAGES.UNKNOWN as string));
        } else {
          setErrorMessage(ERROR_MESSAGES.UNKNOWN as string);
        }
      } finally {
        if (!cancelled) setLoadingInitial(false);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [mode, userId, form]);

  const handleSubmit = async (values: FormValues) => {
    setSubmitting(true);
    setErrorMessage(null);
    form.setFields([
      { name: 'username', errors: [] },
      { name: 'userCode', errors: [] },
    ]);

    try {
      if (mode === 'create') {
        await usersApi.create({
          username: values.username!,
          userCode: values.userCode!,
          userType: values.userType!,
          language: values.language!,
          canEditDocument: !!values.canEditDocument,
          employeeId: values.employeeId,
          permissionCategoryIds,
        });
        message.success('新增用户成功');
      } else if (userId != null) {
        const patch: UpdateUserReq = {
          userCode: values.userCode,
          userType: values.userType,
          language: values.language,
          canEditDocument: values.canEditDocument,
          employeeId: values.employeeId,
          permissionCategoryIds,
        };
        await usersApi.update(userId, patch);
        message.success('保存成功');
      }
      navigate('/users');
    } catch (e) {
      handleBizError(e);
    } finally {
      setSubmitting(false);
    }
  };

  const handleBizError = (e: unknown) => {
    if (!isBizError(e)) {
      setErrorMessage(ERROR_MESSAGES.UNKNOWN as string);
      return;
    }
    const be = e as BizError;
    if (be.code === 40901) {
      form.setFields([{ name: 'username', errors: [ERROR_MESSAGES[40901] as string] }]);
    } else if (be.code === 40902) {
      form.setFields([{ name: 'userCode', errors: [ERROR_MESSAGES[40902] as string] }]);
    } else if (be.code === 40004) {
      setErrorMessage(ERROR_MESSAGES[40004] as string);
    } else if (be.code === 40401) {
      setNotFound(true);
    } else if (be.code === -1) {
      setErrorMessage(ERROR_MESSAGES.NETWORK as string);
    } else {
      setErrorMessage(be.message || (ERROR_MESSAGES.UNKNOWN as string));
    }
  };

  if (notFound) {
    return (
      <div data-testid="user-not-found">
        <Result
          status="404"
          title="用户不存在"
          extra={
            <Button type="primary" onClick={() => navigate('/users')}>
              返回列表
            </Button>
          }
        />
      </div>
    );
  }

  return (
    <div data-testid={mode === 'create' ? 'user-form-create' : 'user-form-edit'} style={{ padding: 16 }}>
      <Space style={{ marginBottom: 12 }}>
        <Button
          type="primary"
          loading={submitting}
          onClick={() => form.submit()}
          data-testid="form-save"
        >
          保存
        </Button>
        <Button onClick={() => navigate('/users')} data-testid="form-cancel">
          取消
        </Button>
      </Space>
      {errorMessage && (
        <Alert
          type="error"
          message={errorMessage}
          showIcon
          style={{ marginBottom: 12 }}
          data-testid="form-error-alert"
        />
      )}
      <Spin spinning={loadingInitial}>
        <Card>
          <Form<FormValues>
            form={form}
            layout="vertical"
            onFinish={handleSubmit}
            initialValues={{
              userType: 'NORMAL',
              language: 'zh-CN',
              canEditDocument: false,
            }}
            disabled={submitting || loadingInitial}
            data-testid="user-form"
          >
            <UserFormFields mode={mode} disabled={submitting || loadingInitial} />
          </Form>
          <UserPermissionPanel
            value={permissionCategoryIds}
            onChange={setPermissionCategoryIds}
            disabled={submitting || loadingInitial}
          />
        </Card>
      </Spin>
    </div>
  );
}