UserDetail.tsx 7.7 KB
import { useEffect, useState } from "react";
import { Form, Input, Select, Button, App as AntApp } from "antd";
import {
  PlusOutlined,
  EditOutlined,
  DeleteOutlined,
  SaveOutlined,
  CloseOutlined,
  KeyOutlined,
} from "@ant-design/icons";
import { createUser, updateUser, type UserDTO } from "@/api/user";
import { USER_TYPES, LANGUAGE_OPTIONS } from "@/utils/data";
import { useAppDispatch, useAppSelector } from "@/store";
import { closeTab, setActiveTab } from "@/store/tabsSlice";
import { fmtDateTime } from "@/utils/format";

type Mode = "view" | "edit" | "new";

interface Props {
  userId?: number;
  mode: Mode;
}

interface FormShape {
  sUserNo: string;
  sUserName: string;
  staffName?: string;
  department?: string;
  sUserType: string;
  sLanguage: string;
  bCanModifyDocs: "全部允许" | "仅本人单据" | "仅查看";
}

export default function UserDetail({ userId, mode: initialMode }: Props) {
  const [form] = Form.useForm<FormShape>();
  const [mode, setMode] = useState<Mode>(initialMode);
  const [submitting, setSubmitting] = useState(false);
  const dispatch = useAppDispatch();
  const { message } = AntApp.useApp();
  const tabs = useAppSelector((s) => s.tabs.tabs);
  const activeTabId = useAppSelector((s) => s.tabs.activeTabId);
  const tab = tabs.find((t) => t.id === activeTabId);
  const snapshot = tab?.meta?.snapshot as
    | {
        sUserNo: string;
        sUserName: string;
        staffName: string | null;
        department: string | null;
        sUserType: string;
        sLanguage: string;
        bCanModifyDocs?: boolean;
        tCreateDate?: string;
        sCreatedBy?: string;
      }
    | undefined;

  useEffect(() => {
    if (mode === "new") {
      form.setFieldsValue({
        sUserNo: "",
        sUserName: "",
        staffName: "",
        department: "",
        sUserType: "普通用户",
        sLanguage: "zh",
        bCanModifyDocs: "仅查看",
      });
    } else if (snapshot) {
      form.setFieldsValue({
        sUserNo: snapshot.sUserNo,
        sUserName: snapshot.sUserName,
        staffName: snapshot.staffName ?? "",
        department: snapshot.department ?? "",
        sUserType: snapshot.sUserType,
        sLanguage: snapshot.sLanguage,
        bCanModifyDocs: snapshot.bCanModifyDocs ? "全部允许" : "仅查看",
      });
    }
  }, [snapshot, mode, form]);

  const disabled = mode === "view";

  const startEdit = () => setMode("edit");
  const startNew = () => setMode("new");

  const cancel = () => {
    if (mode === "new") {
      dispatch(closeTab(activeTabId));
      dispatch(setActiveTab("userlist"));
    } else {
      setMode("view");
    }
  };

  const save = async () => {
    try {
      const values = await form.validateFields();
      const dto: UserDTO = {
        sUserNo: values.sUserNo,
        sUserName: values.sUserName,
        sUserType: values.sUserType,
        sLanguage: values.sLanguage,
        bCanModifyDocs: values.bCanModifyDocs === "全部允许",
        // iStaffId omitted: prototype has no staff picker; backend treats as null.
      };
      setSubmitting(true);
      if (mode === "new") {
        await createUser(dto);
        message.success("新增成功");
        dispatch(closeTab(activeTabId));
        dispatch(setActiveTab("userlist"));
      } else if (userId) {
        await updateUser(userId, dto);
        message.success("保存成功");
        setMode("view");
      }
    } catch {
      // validation or interceptor
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        height: "100%",
        background: "var(--bg-app)",
      }}
    >
      <div className="dark-toolbar">
        <Button
          icon={<PlusOutlined />}
          onClick={startNew}
          disabled={mode !== "view"}
          size="small"
        >
          新增
        </Button>
        <Button
          icon={<EditOutlined />}
          onClick={startEdit}
          disabled={mode !== "view" || !userId}
          size="small"
        >
          修改
        </Button>
        <Button icon={<DeleteOutlined />} disabled={mode !== "view"} danger size="small">
          删除
        </Button>
        <Button
          icon={<SaveOutlined />}
          onClick={save}
          disabled={mode === "view"}
          loading={submitting}
          type="primary"
          size="small"
        >
          保存
        </Button>
        <Button icon={<CloseOutlined />} onClick={cancel} disabled={mode === "view"} size="small">
          取消
        </Button>
        <span style={{ width: 1, height: 16, background: "rgba(255,255,255,0.12)", margin: "0 4px" }} />
        <Button icon={<KeyOutlined />} disabled={mode !== "view"} size="small">
          重置密码
        </Button>
        <div style={{ flex: 1 }} />
        <span
          style={{
            padding: "2px 8px",
            fontSize: 11,
            background:
              mode === "view"
                ? "rgba(39,165,103,0.18)"
                : mode === "new"
                  ? "rgba(58,142,224,0.22)"
                  : "rgba(217,142,31,0.22)",
            color:
              mode === "view"
                ? "#7be0a8"
                : mode === "new"
                  ? "#9cc8f0"
                  : "#ffc77a",
            border: `1px solid ${
              mode === "view"
                ? "rgba(39,165,103,0.4)"
                : mode === "new"
                  ? "rgba(58,142,224,0.4)"
                  : "rgba(217,142,31,0.4)"
            }`,
          }}
        >
          {mode === "view" ? "只读" : mode === "new" ? "新增中" : "编辑中"}
        </span>
      </div>

      <div
        style={{
          flex: 1,
          overflow: "auto",
          background: "#fff",
          padding: "12px 16px",
        }}
      >
        <Form
          form={form}
          layout="vertical"
          disabled={disabled}
          style={{
            display: "grid",
            gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))",
            rowGap: 6,
            columnGap: 16,
          }}
        >
          <Form.Item label="创建时间">
            <Input value={fmtDateTime(snapshot?.tCreateDate)} disabled className="mono" />
          </Form.Item>
          <Form.Item label="制单人">
            <Input value={snapshot?.sCreatedBy ?? ""} disabled />
          </Form.Item>
          <Form.Item
            label="员工名"
            name="staffName"
            help="新增/编辑暂不关联职员;如需关联请使用职员管理"
          >
            <Input disabled />
          </Form.Item>

          <Form.Item label="用户名" name="sUserName" rules={[{ required: true }, { max: 50 }]}>
            <Input />
          </Form.Item>
          <Form.Item label="类型" name="sUserType" rules={[{ required: true }]}>
            <Select options={USER_TYPES.map((t) => ({ value: t, label: t }))} />
          </Form.Item>
          <Form.Item label="用户号" name="sUserNo" rules={[{ required: true }, { max: 50 }]}>
            <Input className="mono" />
          </Form.Item>

          <Form.Item label="部门" name="department">
            <Input disabled />
          </Form.Item>
          <Form.Item label="语言" name="sLanguage" rules={[{ required: true }]}>
            <Select options={LANGUAGE_OPTIONS} />
          </Form.Item>
          <Form.Item label="单据修改权限" name="bCanModifyDocs">
            <Select
              options={[
                { value: "全部允许", label: "全部允许" },
                { value: "仅本人单据", label: "仅本人单据" },
                { value: "仅查看", label: "仅查看" },
              ]}
            />
          </Form.Item>
        </Form>
      </div>
    </div>
  );
}