workspace.jsx 7.37 KB
// Workspace shell: top bar + sidebar + tabs + screen routing.

const Workspace = ({ session, onLogout }) => {
  const [users, setUsers] = React.useState(() => XLY.buildUsers());
  const [expanded, setExpanded] = React.useState({ kpi: true, quote: true, sys: true });
  const [searchQ, setSearchQ] = React.useState("");
  const [activeNodeId, setActiveNodeId] = React.useState("home");
  const [sidebarOpen, setSidebarOpen] = React.useState(true);

  const [tabs, setTabs] = React.useState([
    { id: "home", label: "主页", screen: "home", icon: "home", closable: false },
  ]);
  const [activeTabId, setActiveTabId] = React.useState("home");
  const [megaOpen, setMegaOpen] = React.useState(false);

  const openTab = (tab) => {
    setTabs((ts) => ts.find((t) => t.id === tab.id) ? ts : [...ts, tab]);
    setActiveTabId(tab.id);
  };
  const closeTab = (id) => {
    setTabs((ts) => {
      const i = ts.findIndex((t) => t.id === id);
      const next = ts.filter((t) => t.id !== id);
      if (id === activeTabId) {
        const fallback = next[Math.max(0, i - 1)] || next[0];
        if (fallback) setActiveTabId(fallback.id);
      }
      return next;
    });
  };

  const onNodeClick = (node) => {
    setActiveNodeId(node.id);
    if (node.screen === "userlist") openTab({ id: "userlist", label: "用户列表", screen: "userlist" });
    else if (node.screen === "module") openTab({ id: "module", label: "系统模块配置", screen: "module" });
    else if (node.id === "home") openTab({ id: "home", label: "主页", screen: "home", closable: false });
    else if (node.id === "quote-01") openTab({ id: "module", label: "系统模块配置", screen: "module" });
    else openTab({ id: node.id, label: node.label, screen: "stub", stubLabel: node.label });
  };

  const openUserDetail = (user, mode = "view") => {
    const id = `user-${user.id}`;
    const label = `用户信息单据 · ${user.employee}`;
    setTabs((ts) => {
      if (ts.find((t) => t.id === id)) return ts;
      return [...ts, { id, label, screen: "userdetail", userId: user.id, mode }];
    });
    setActiveTabId(id);
  };
  const createUser = () => {
    const newU = {
      id: `new-${Date.now()}`, seq: users.length + 1, employee: "", empNo: "", account: "",
      department: "", type: "普通用户", language: "中文", disabled: false,
      lastLogin: "", createdBy: session.user, createdAt: new Date().toISOString().slice(0, 19).replace("T", " "),
      permissions: XLY.PERMISSION_GROUPS.reduce((a, g) => (a[g] = false, a), {}),
      tabPerms: {},
    };
    setUsers((us) => [...us, newU]);
    openUserDetail(newU, "new");
  };

  const saveUser = (u) => setUsers((us) => us.map((x) => x.id === u.id ? u : x));
  const activeTab = tabs.find((t) => t.id === activeTabId);

  return (
    <div style={{ height: "100vh", width: "100vw", display: "flex", flexDirection: "column", background: "var(--bg-app)" }}>
      {/* Top bar */}
      <div style={{
        height: 36, flex: "none",
        background: "var(--bg-topbar)", color: "var(--text-on-dark)",
        display: "flex", alignItems: "center", padding: "0 10px",
        borderBottom: "1px solid #1a1f2a",
      }}>
        <button onClick={() => setMegaOpen(true)} style={topBtn} title="全部导航">
          <Ic.menu size={14} /> 全部导航
        </button>
        <button onClick={() => { setActiveTabId("home"); setSidebarOpen(true); }} style={topBtn} title="主页">
          <Ic.home size={14} /> 主页
        </button>
        <div style={{ flex: 1, minWidth: 0, display: "flex", alignItems: "center", gap: 8, paddingLeft: 12, overflow: "hidden" }}>
          <Ic.Brand size={22} />
          <span style={{ color: "#fff", fontSize: 13, fontWeight: 500, letterSpacing: 1, whiteSpace: "nowrap", flex: "none" }}>XLY-ERP</span>
          <span style={{ color: "var(--text-on-dark-muted)", fontSize: 11, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", minWidth: 0 }}>· {session.company}</span>
        </div>
        <button style={topBtn}><Ic.search size={14} /></button>
        <button style={topBtn}>
          <span style={{ position: "relative", display: "inline-flex" }}>
            <Ic.bell size={14} />
            <span style={{ position: "absolute", top: -2, right: -3, width: 6, height: 6, background: "var(--danger)", borderRadius: 3 }} />
          </span>
        </button>
        <button style={topBtn}>
          <Ic.building size={14} /> {session.user}(超级管理员)
          <Ic.chevronDown size={10} style={{ marginLeft: 2 }} />
        </button>
        <button onClick={onLogout} style={topBtn} title="登出">
          <span style={{ fontSize: 14, lineHeight: 1 }}>···</span>
        </button>
      </div>

      {/* Tab strip */}
      <TabStrip tabs={tabs} activeTabId={activeTabId} onSelect={setActiveTabId} onClose={closeTab} />

      {/* Body */}
      <div style={{ flex: 1, display: "flex", minHeight: 0, overflow: "hidden" }}>
        {sidebarOpen && activeTabId === "home" ? (
          <div style={{ width: 230, flex: "none", height: "100%" }}>
            <Sidebar
              tree={XLY.NAV_TREE}
              activeNodeId={activeNodeId}
              expanded={expanded} setExpanded={setExpanded}
              onNodeClick={onNodeClick}
              query={searchQ} setQuery={setSearchQ}
            />
          </div>
        ) : null}
        <div style={{ flex: 1, position: "relative", minWidth: 0 }}>
          {activeTab ? <ScreenRouter tab={activeTab} users={users} onOpenUser={openUserDetail} onCreateUser={createUser} onSaveUser={saveUser} session={session} onOpenScreen={(s, label) => openTab({ id: s, label, screen: s })} /> : null}
        </div>
      </div>

      {/* Status bar */}
      <div style={{
        height: 22, flex: "none", background: "#fff", borderTop: "1px solid var(--border)",
        display: "flex", alignItems: "center", justifyContent: "space-between",
        padding: "0 12px", fontSize: 11, color: "var(--text-muted)",
      }}>
        <span>就绪 · 当前用户 {session.user} · {session.company}</span>
        <span>XLY-ERP v8.6.2 · © 2017–2026 XLY 软件股份</span>
      </div>
      {megaOpen ? (
        <MegaNav
          onClose={() => setMegaOpen(false)}
          onOpen={(screen, label) => openTab({ id: screen, label, screen })}
        />
      ) : null}
    </div>
  );
};

const ScreenRouter = ({ tab, users, onOpenUser, onCreateUser, onSaveUser, session, onOpenScreen }) => {
  if (tab.screen === "home") return <Home user={session.user} onOpenScreen={onOpenScreen} />;
  if (tab.screen === "userlist") return <UserList users={users} onOpenUser={onOpenUser} onCreateUser={onCreateUser} />;
  if (tab.screen === "module") return <ModuleScreen />;
  if (tab.screen === "userdetail") {
    const u = users.find((x) => x.id === tab.userId);
    if (!u) return <Empty>用户不存在</Empty>;
    return <UserDetail user={u} mode={tab.mode} onSave={onSaveUser} />;
  }
  return <Empty>{tab.stubLabel || tab.label} · 模块开发中</Empty>;
};

const Empty = ({ children }) => (
  <div style={{ height: "100%", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--text-faint)", fontSize: 13, background: "var(--bg-app)" }}>
    {children}
  </div>
);

const topBtn = {
  display: "inline-flex", alignItems: "center", gap: 5,
  height: 28, padding: "0 10px", background: "transparent",
  color: "var(--text-on-dark)", border: "none", cursor: "pointer",
  fontSize: 12,
};

window.Workspace = Workspace;