screen-userdetail.jsx 13 KB
// User detail — header form + permission tabs (with checkbox grid).
// Modes: view (read-only) | edit | new

const PERM_TABS = [
  { id: "groups", label: "权限组" },
  { id: "customer", label: "客户查看权限" },
  { id: "supplier", label: "供应商查看权限" },
  { id: "staff", label: "人员查看权限" },
  { id: "process", label: "工序查看权限" },
  { id: "driver", label: "司机查看权限" },
];

const UserDetail = ({ user: initial, mode: initialMode, onSave, onDelete, onClose }) => {
  const [mode, setMode] = React.useState(initialMode || "view");
  const [user, setUser] = React.useState(initial);
  const [tab, setTab] = React.useState("groups");
  const [snapshot, setSnapshot] = React.useState(initial);

  React.useEffect(() => {
    setUser(initial);
    setSnapshot(initial);
    setMode(initialMode || "view");
  }, [initial, initialMode]);

  const set = (k, v) => setUser((s) => ({ ...s, [k]: v }));
  const setPerm = (g, v) => setUser((s) => ({ ...s, permissions: { ...s.permissions, [g]: v } }));
  const setTabPerm = (k, v) => setUser((s) => ({ ...s, tabPerms: { ...s.tabPerms, [k]: v } }));

  const start = (m) => { setSnapshot(user); setMode(m); };
  const save = () => { onSave && onSave(user); setSnapshot(user); setMode("view"); };
  const cancel = () => { setUser(snapshot); setMode("view"); };

  const disabled = mode === "view";
  const isNew = mode === "new";

  const checked = Object.values(user.permissions || {}).filter(Boolean).length;

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", background: "var(--bg-app)" }}>
      {/* Dark sub-toolbar */}
      <div style={{
        display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap", rowGap: 4,
        padding: "4px 8px", background: "var(--bg-toolbar-dark)",
        flex: "none", borderBottom: "1px solid #1a1f2a",
      }}>
        <ToolbarBtnDark icon={<Ic.plus size={12} />} onClick={() => start("new")} disabled={mode !== "view"}>新增</ToolbarBtnDark>
        <ToolbarBtnDark icon={<Ic.edit size={12} />} onClick={() => start("edit")} disabled={mode !== "view"}>修改</ToolbarBtnDark>
        <ToolbarBtnDark icon={<Ic.trash size={12} />} danger disabled={mode !== "view"} onClick={onDelete}>删除</ToolbarBtnDark>
        <ToolbarBtnDark icon={<Ic.save size={12} />} onClick={save} disabled={mode === "view"}>保存</ToolbarBtnDark>
        <ToolbarBtnDark icon={<Ic.cancel size={12} />} onClick={cancel} disabled={mode === "view"}>取消</ToolbarBtnDark>
        <span style={{ width: 1, height: 16, background: "rgba(255,255,255,0.12)", margin: "0 4px" }} />
        <ToolbarBtnDark icon={<Ic.function size={12} />}>功能</ToolbarBtnDark>
        <ToolbarBtnDark icon={<Ic.clipboard size={12} />}>作废</ToolbarBtnDark>
        <ToolbarBtnDark icon={<Ic.key size={12} />}>重置密码</ToolbarBtnDark>
        <ToolbarBtnDark icon={<Ic.undo size={12} />}>取消作废</ToolbarBtnDark>
        <div style={{ flex: 1 }} />
        <span style={{ fontSize: 11, color: "var(--text-on-dark-muted)", marginRight: 8 }}>
          已选权限:<span style={{ color: "#fff", fontWeight: 500 }}>{checked}</span> / {XLY.PERMISSION_GROUPS.length - 1}
        </span>
        <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>
        <button style={{ marginLeft: 8, width: 22, height: 22, border: "none", background: "transparent", color: "var(--text-on-dark-muted)", cursor: "pointer" }}>
          <Ic.settings size={13} />
        </button>
      </div>

      {/* Header form (3 cols × 3 rows) */}
      <div style={{
        background: "#fff", padding: "10px 12px",
        borderBottom: "1px solid var(--border)", flex: "none",
        display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))",
        rowGap: 6, columnGap: 16,
      }}>
        <Field label="创建时间">
          <Input value={user.createdAt} disabled mono />
        </Field>
        <Field label="制单人" required>
          <Input value={user.createdBy} onChange={(v) => set("createdBy", v)} disabled={disabled} required />
        </Field>
        <Field label="员工名" required>
          <Input value={user.employee} onChange={(v) => set("employee", v)} disabled={disabled} required />
        </Field>

        <Field label="用户名" required>
          <Input value={user.account} onChange={(v) => set("account", v)} disabled={disabled} required />
        </Field>
        <Field label="类型" required>
          <Select value={user.type} onChange={(v) => set("type", v)} disabled={disabled} required options={XLY.USER_TYPES} />
        </Field>
        <Field label="用户号" required>
          <Input value={user.empNo} onChange={(v) => set("empNo", v)} disabled={disabled} required mono />
        </Field>

        <Field label="部门">
          <Select value={user.department} onChange={(v) => set("department", v)} disabled={disabled} options={XLY.DEPARTMENTS} />
        </Field>
        <Field label="语言" required>
          <Select value={user.language} onChange={(v) => set("language", v)} disabled={disabled} required options={XLY.LANGUAGES} />
        </Field>
        <Field label="单据修改权限">
          <Select value={user.editPerm || ""} onChange={(v) => set("editPerm", v)} disabled={disabled} options={["全部允许", "仅本人单据", "仅查看"]} />
        </Field>
      </div>

      {/* Permission tabs */}
      <div style={{ display: "flex", alignItems: "stretch", background: "#fff", borderBottom: "1px solid var(--border)", flex: "none" }}>
        {PERM_TABS.map((t) => {
          const active = t.id === tab;
          return (
            <div
              key={t.id}
              onClick={() => setTab(t.id)}
              style={{
                padding: "6px 14px", fontSize: 12, cursor: "pointer",
                color: active ? "var(--accent-strong)" : "var(--text)",
                background: active ? "var(--accent-soft)" : "transparent",
                borderTop: active ? "2px solid var(--accent)" : "2px solid transparent",
                borderRight: "1px solid var(--border)",
                fontWeight: active ? 500 : 400,
              }}
              onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = "var(--bg-row-hover)"; }}
              onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = "transparent"; }}
            >
              {t.label}
            </div>
          );
        })}
      </div>

      {/* Tab content */}
      <div style={{ flex: 1, overflow: "auto", background: "#fff" }}>
        {tab === "groups" ? (
          <PermissionGrid user={user} setPerm={setPerm} disabled={disabled} />
        ) : (
          <ScopeTab tabKey={tab} user={user} setTabPerm={setTabPerm} disabled={disabled} />
        )}
      </div>

      {/* Floating help */}
      <div style={{ position: "absolute", right: 0, top: "50%", transform: "translateY(-50%)" }}>
        <button style={{
          width: 18, height: 40, background: "var(--accent)", color: "#fff", border: "none",
          borderTopLeftRadius: 4, borderBottomLeftRadius: 4, cursor: "pointer", fontSize: 11,
          writingMode: "vertical-rl",
        }}>
          帮助
        </button>
      </div>
    </div>
  );
};

const PermissionGrid = ({ user, setPerm, disabled }) => {
  const [filter, setFilter] = React.useState("");
  const [hovered, setHovered] = React.useState(null);
  return (
    <div>
      <table style={{ borderCollapse: "collapse", width: "100%", fontSize: 12, tableLayout: "fixed" }}>
        <colgroup>
          <col style={{ width: 40 }} />
          <col />
        </colgroup>
        <thead>
          <tr style={{ background: "var(--bg-row-zebra)", borderBottom: "1px solid var(--border)" }}>
            <th style={{ padding: "5px 8px", borderRight: "1px solid var(--border)", textAlign: "center" }}>
              <Checkbox
                checked={Object.values(user.permissions).every(Boolean)}
                onChange={(v) => {
                  XLY.PERMISSION_GROUPS.forEach((g) => { if (g !== "权限分类") setPerm(g, v); });
                }}
                disabled={disabled}
              />
            </th>
            <th style={{ padding: "5px 8px", textAlign: "left", fontWeight: 500 }}>
              <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
                权限分类
                <input
                  value={filter} onChange={(e) => setFilter(e.target.value)}
                  placeholder="过滤..."
                  style={{ height: 20, padding: "0 6px", border: "1px solid var(--border-input)", background: "#fff", fontSize: 11, width: 120, fontFamily: "inherit" }}
                />
              </span>
            </th>
          </tr>
        </thead>
        <tbody>
          {XLY.PERMISSION_GROUPS.filter((g) => g !== "权限分类" && (!filter || g.includes(filter))).map((g, i) => {
            const isHover = hovered === g;
            return (
              <tr
                key={g}
                onMouseEnter={() => setHovered(g)} onMouseLeave={() => setHovered(null)}
                onClick={disabled ? undefined : () => setPerm(g, !user.permissions[g])}
                style={{
                  background: isHover && !disabled ? "var(--bg-row-hover)"
                    : user.permissions[g] ? "var(--accent-soft)"
                    : i % 2 === 0 ? "#fff" : "var(--bg-row-zebra)",
                  cursor: disabled ? "default" : "pointer",
                  height: 24,
                }}
              >
                <td style={{ padding: "3px 0", textAlign: "center", borderRight: "1px solid var(--border)", borderBottom: "1px solid #f0f2f5" }}>
                  <Checkbox
                    checked={!!user.permissions[g]}
                    onChange={(v) => setPerm(g, v)}
                    disabled={disabled}
                  />
                </td>
                <td style={{ padding: "3px 8px", borderBottom: "1px solid #f0f2f5", color: user.permissions[g] ? "var(--accent-strong)" : "var(--text)", fontWeight: user.permissions[g] ? 500 : 400 }}>
                  {g}
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
};

const ScopeTab = ({ tabKey, user, setTabPerm, disabled }) => {
  const map = {
    customer: { label: "客户", items: ["上海印行包装", "锐尚文创", "京华彩印", "广印纸品", "万象图文", "联合包装", "鼎盛印刷", "九洲胶印"] },
    supplier: { label: "供应商", items: ["华东油墨", "正信纸业", "宝洁化工", "日新油墨", "三鼎纸业", "鸿丰胶辊", "永利印材"] },
    staff: { label: "人员", items: ["管广飞", "李斌", "孟威", "王宽明", "潘强", "杨柳"] },
    process: { label: "工序", items: ["印前", "印刷", "覆膜", "模切", "装订", "胶装", "丝网", "烫金", "包装"] },
    driver: { label: "司机", items: ["陈师傅", "李师傅", "王师傅", "钱师傅", "赵师傅"] },
  };
  const cfg = map[tabKey];
  const [filter, setFilter] = React.useState("");
  const enabled = !!user.tabPerms[tabKey];

  return (
    <div style={{ padding: 12 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 10, paddingBottom: 8, borderBottom: "1px solid var(--border)" }}>
        <Checkbox
          checked={enabled}
          onChange={(v) => setTabPerm(tabKey, v)}
          disabled={disabled}
          label={`启用${cfg.label}查看权限`}
        />
        <input
          value={filter} onChange={(e) => setFilter(e.target.value)}
          placeholder={`筛选${cfg.label}...`}
          style={{ height: 24, padding: "0 8px", border: "1px solid var(--border-input)", background: "var(--bg-input)", fontSize: 12, width: 200, fontFamily: "inherit" }}
        />
        <span style={{ flex: 1 }} />
        <span style={{ fontSize: 11, color: "var(--text-muted)" }}>
          共 {cfg.items.length} 个{cfg.label}
        </span>
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 6 }}>
        {cfg.items.filter((x) => !filter || x.includes(filter)).map((it) => (
          <label key={it} style={{
            display: "flex", alignItems: "center", gap: 6,
            padding: "5px 8px", border: "1px solid var(--border)",
            background: enabled ? "#fff" : "var(--bg-disabled)",
            cursor: disabled || !enabled ? "default" : "pointer", fontSize: 12,
          }}>
            <Checkbox disabled={disabled || !enabled} />
            <span style={{ color: enabled ? "var(--text)" : "var(--text-faint)" }}>{it}</span>
          </label>
        ))}
      </div>
    </div>
  );
};

window.UserDetail = UserDetail;