workspace.jsx
7.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// 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;