UserList.tsx 7.07 KB
import { useEffect, useState, useCallback } from "react";
import { Button, Select, Input, Table, Checkbox, App as AntApp } from "antd";
import {
  ReloadOutlined,
  PlusOutlined,
  ExportOutlined,
  SearchOutlined,
} from "@ant-design/icons";
import type { ColumnsType } from "antd/es/table";
import { listUsers, type UserListVO } from "@/api/user";
import { USER_LIST_FIELDS, USER_LIST_MATCHES } from "@/utils/data";
import { fmtDateTime } from "@/utils/format";
import { useAppDispatch } from "@/store";
import { openTab } from "@/store/tabsSlice";

type Scope = "all" | "active" | "disabled";

export default function UserList() {
  const dispatch = useAppDispatch();
  const { message } = AntApp.useApp();

  const [scope, setScope] = useState<Scope>("all");
  const [field, setField] = useState("员工名");
  const [match, setMatch] = useState("包含");
  const [query, setQuery] = useState("");
  const [loading, setLoading] = useState(false);
  const [rows, setRows] = useState<UserListVO[]>([]);
  const [total, setTotal] = useState(0);
  const [selected, setSelected] = useState<number[]>([]);

  const reload = useCallback(
    async (override?: { field?: string; match?: string; value?: string }) => {
      setLoading(true);
      try {
        const page = await listUsers({
          field: override?.field ?? field,
          match: override?.match ?? match,
          value: override?.value ?? query,
          pageNum: 1,
          pageSize: 100,
        });
        setRows(page.records ?? []);
        setTotal(page.total ?? 0);
      } catch {
        // interceptor already showed message
      } finally {
        setLoading(false);
      }
    },
    [field, match, query]
  );

  useEffect(() => {
    void reload();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const filtered = rows.filter((r) => {
    if (scope === "active") return !r.bDeleted;
    if (scope === "disabled") return r.bDeleted;
    return true;
  });

  const onCreate = () => {
    dispatch(
      openTab({
        id: "user-new",
        label: "用户信息单据 · 新建",
        screen: "userdetail",
        meta: { mode: "new" },
      })
    );
  };

  const onOpenUser = (u: UserListVO) => {
    dispatch(
      openTab({
        id: `user-${u.iIncrement}`,
        label: `用户信息单据 · ${u.staffName ?? u.sUserName}`,
        screen: "userdetail",
        meta: { mode: "view", userId: u.iIncrement, snapshot: u },
      })
    );
  };

  const columns: ColumnsType<UserListVO> = [
    {
      title: "序号",
      dataIndex: "iIncrement",
      width: 60,
      render: (_, __, i) => <span className="mono" style={{ color: "var(--text-muted)" }}>{i + 1}</span>,
    },
    { title: "员工名", dataIndex: "staffName", width: 100, render: (v, r) => v ?? r.sUserName },
    { title: "员工号", dataIndex: "sUserNo", width: 100 },
    { title: "用户号", dataIndex: "sUserName", width: 100, render: (v) => <span className="mono">{v}</span> },
    { title: "部门", dataIndex: "department", width: 120, render: (v) => v ?? "—" },
    { title: "用户类型", dataIndex: "sUserType", width: 110 },
    { title: "语言", dataIndex: "sLanguage", width: 70 },
    {
      title: "作废",
      dataIndex: "bDeleted",
      width: 60,
      render: (v: boolean) => <Checkbox checked={!!v} disabled />,
    },
    {
      title: "登录日期",
      dataIndex: "tLastLoginDate",
      width: 150,
      render: (v) => <span className="mono" style={{ fontSize: 11 }}>{fmtDateTime(v)}</span>,
    },
    { title: "制单人", dataIndex: "sCreatedBy", width: 100 },
    {
      title: "制单日期",
      dataIndex: "tCreateDate",
      width: 150,
      render: (v) => <span className="mono" style={{ fontSize: 11 }}>{fmtDateTime(v)}</span>,
    },
  ];

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        height: "100%",
        background: "var(--bg-app)",
      }}
    >
      <div className="blue-toolbar">
        <Button icon={<ReloadOutlined />} onClick={() => reload()} size="small">
          刷新
        </Button>
        <Button icon={<PlusOutlined />} type="primary" onClick={onCreate} size="small">
          新增
        </Button>
        <Button icon={<ExportOutlined />} size="small" onClick={() => message.info("导出功能开发中")}>
          导出 Excel
        </Button>
        <span style={{ width: 1, height: 18, background: "var(--border)", margin: "0 2px" }} />
        <Select
          size="small"
          value={scope}
          onChange={setScope}
          style={{ width: 110 }}
          options={[
            { value: "all", label: "全部用户" },
            { value: "active", label: "启用用户" },
            { value: "disabled", label: "禁用用户" },
          ]}
        />
        <Select
          size="small"
          value={field}
          onChange={setField}
          style={{ width: 100 }}
          options={USER_LIST_FIELDS}
        />
        <Select
          size="small"
          value={match}
          onChange={setMatch}
          style={{ width: 90 }}
          options={USER_LIST_MATCHES}
        />
        <Input
          size="small"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          onPressEnter={() => reload()}
          style={{ width: 180 }}
        />
        <Button
          icon={<SearchOutlined />}
          type="primary"
          size="small"
          onClick={() => reload()}
        >
          查找
        </Button>
        <Button
          size="small"
          onClick={() => {
            setQuery("");
            setScope("all");
            void reload({ value: "" });
          }}
        >
          清空
        </Button>
        <div style={{ flex: 1 }} />
        <span style={{ fontSize: 11, color: "var(--text-muted)" }}>
          {selected.length > 0 && (
            <>
              已选 <span style={{ color: "var(--accent-strong)", fontWeight: 500 }}>{selected.length}</span> 条 /{" "}
            </>
          )}
          共 {total} 条记录
        </span>
      </div>

      <div style={{ flex: 1, overflow: "auto", background: "#fff" }}>
        <Table<UserListVO>
          rowKey="iIncrement"
          columns={columns}
          dataSource={filtered}
          loading={loading}
          size="small"
          pagination={false}
          rowSelection={{
            selectedRowKeys: selected,
            onChange: (keys) => setSelected(keys as number[]),
          }}
          onRow={(r) => ({
            onDoubleClick: () => onOpenUser(r),
            style: { cursor: "pointer", color: r.bDeleted ? "var(--text-faint)" : "var(--text)" },
          })}
        />
      </div>

      <div
        style={{
          padding: "6px 12px",
          background: "#fff",
          borderTop: "1px solid var(--border)",
          fontSize: 11,
          color: "var(--text-muted)",
          display: "flex",
          justifyContent: "space-between",
        }}
      >
        <span>
          当前显示:共 {filtered.length} 条单据 共 {total} 条记录
        </span>
        <span>双击行查看详情</span>
      </div>
    </div>
  );
}