diff --git a/prototype/XLY-ERP.html b/prototype/XLY-ERP.html
new file mode 100644
index 0000000..0a90927
--- /dev/null
+++ b/prototype/XLY-ERP.html
@@ -0,0 +1,86 @@
+
+
+
+ setSession(null)} />;
+};
+
+ReactDOM.createRoot(document.getElementById("root")).render();
diff --git a/prototype/src/data.jsx b/prototype/src/data.jsx
new file mode 100644
index 0000000..826450d
--- /dev/null
+++ b/prototype/src/data.jsx
@@ -0,0 +1,193 @@
+// Seed data — plausible printing-industry ERP tenant.
+// Original company name; not derived from the reference product.
+
+const COMPANIES = [
+ { id: "std", name: "标准版 (Standard Edition) / 8s" },
+ { id: "ent", name: "企业版 (Enterprise Edition) / 8s" },
+ { id: "trial", name: "试用版 (Trial Edition) / 30d" },
+];
+
+// Sidebar tree — printing-industry processes
+const NAV_TREE = [
+ { id: "home", label: "首页", icon: "home", leaf: true },
+ {
+ id: "kpi", label: "KPI 流程作业单", icon: "doc",
+ children: [
+ {
+ id: "quote", label: "估价管理流程",
+ children: [
+ { id: "quote-01", label: "01/04 【新增】新报价单", leaf: true, badge: "估价" },
+ { id: "quote-02", label: "02/04 审核报价单->客户确认...", leaf: true },
+ { id: "quote-03", label: "03/04 客户确认->二次确认", leaf: true },
+ { id: "quote-04", label: "04/04 报价单->销售订单", leaf: true },
+ { id: "quote-05", label: "04/04 报价单->转拼版单", leaf: true },
+ { id: "quote-06", label: "04/07 主管审核报价单", leaf: true },
+ { id: "quote-07", label: "05/07 业务确认报价单", leaf: true },
+ ],
+ },
+ { id: "order", label: "订单生产流程" },
+ { id: "panel", label: "自动拼版流程" },
+ { id: "ship", label: "销售送货流程" },
+ { id: "purch", label: "物料采购流程" },
+ { id: "issue", label: "物料领用流程" },
+ { id: "outproc", label: "发外加工流程" },
+ { id: "qc", label: "质量管理流程" },
+ { id: "fin", label: "财务收付款流程" },
+ ],
+ },
+ { id: "crm", label: "CRM 管理", icon: "folder" },
+ { id: "plm", label: "PLM 管理", icon: "folder" },
+ { id: "prod", label: "产品管理", icon: "folder" },
+ { id: "sales", label: "销售管理", icon: "folder" },
+ { id: "mfg", label: "生产管理", icon: "folder" },
+ { id: "exec", label: "生产执行", icon: "folder" },
+ { id: "mold", label: "模具管理", icon: "folder" },
+ {
+ id: "sys", label: "系统管理", icon: "settings",
+ children: [
+ { id: "roles", label: "角色管理", leaf: true },
+ { id: "menucfg", label: "菜单配置", leaf: true },
+ { id: "log", label: "操作日志", leaf: true },
+ ],
+ },
+];
+
+// Permission-group rows (left column of permission tab)
+const PERMISSION_GROUPS = [
+ "权限分类", "默认显示(必选)", "禁止查看价格", "客服报单",
+ "报价组员工", "物控组员工", "供应链 PMC", "允许查看订单价格",
+ "储运员工", "外接供应商", "品质组员工", "技术中心员工",
+ "机修组员工", "生产排计划员工", "外发组员工",
+ "模切车间", "装订车间", "粘接工车间", "品质部管理", "精品车间",
+ "人事组", "统计组", "机修主管", "样品开发员工", "设计开发", "总经办",
+ "财务部", "销售员", "采购员", "仓库管理员",
+];
+
+const DEPARTMENTS = [
+ "工艺技术", "印刷车间", "机修", "机务部", "财务部",
+ "装订车间", "总经办公室", "总务部", "供应链", "质量管理部",
+ "模切车间", "计划组", "样品开发", "设计部", "仓库",
+];
+
+const USER_TYPES = ["超级管理员", "高级管理员", "普通用户", "外部用户", "只读用户"];
+const LANGUAGES = ["中文", "英文", "繁体"];
+
+// Plausible Chinese-name pinyin pairs
+const NAME_POOL = [
+ ["管广飞", "ggf"], ["李斌", "lib"], ["系统管理员", "admin"], ["朱财喜", "zhucx"],
+ ["林杰华", "ljh"], ["汪鑫", "wx"], ["钱昉", "qianb"], ["张冠飞", "zgf"],
+ ["孟威", "mengw"], ["杭仁萍", "hangrp"], ["王月", "wy"], ["王宽明", "wkm"],
+ ["潘强", "pq"], ["耿广东", "ggd"], ["余涛", "yt"], ["梁赵军", "lzj"],
+ ["曹佳怡", "cjy"], ["陈思琪", "csq"], ["张红英", "zhy"], ["吕欣彦", "lxy"],
+ ["陈雪婷", "cxt"], ["路鑫", "luxin"], ["陆鑫·储运部", "ZY0006"],
+ ["朱晓兵", "zhuxb"], ["孟丽花", "menglh"], ["彭敏", "pengm"], ["顾鹏", "gp"],
+ ["田雨", "ty"], ["黄文豪", "hwh"], ["邓佳", "dj"], ["孙浩然", "shr"],
+ ["徐瑞", "xr"], ["许云", "xy"], ["何晨曦", "hcx"], ["林婉君", "lwj"],
+ ["杨柳", "yl"], ["蒋婷", "jt"],
+];
+
+const seededRandom = (seed) => {
+ let s = seed;
+ return () => { s = (s * 9301 + 49297) % 233280; return s / 233280; };
+};
+
+const pad = (n) => String(n).padStart(2, "0");
+const dt = (y, m, d, hh, mm, ss) =>
+ `${y}-${pad(m)}-${pad(d)} ${pad(hh)}:${pad(mm)}:${pad(ss)}`;
+
+const buildUsers = () => {
+ const rand = seededRandom(7);
+ return NAME_POOL.map((nm, i) => {
+ const r = rand();
+ return {
+ id: i + 1,
+ seq: i + 1,
+ employee: nm[0],
+ empNo: nm[1],
+ account: nm[1],
+ department: DEPARTMENTS[Math.floor(rand() * DEPARTMENTS.length)],
+ type: USER_TYPES[Math.floor(rand() * USER_TYPES.length)],
+ language: r < 0.15 ? "英文" : "中文",
+ disabled: r < 0.08,
+ lastLogin: dt(2026, 1 + Math.floor(rand() * 4), 1 + Math.floor(rand() * 27),
+ 8 + Math.floor(rand() * 10), Math.floor(rand() * 60), Math.floor(rand() * 60)),
+ createdBy: ["超级管理员", "机仁萍", "李丹", "YFZ", "LJH", "孟琰"][Math.floor(rand() * 6)],
+ createdAt: dt(2023 + Math.floor(rand() * 3), 1 + Math.floor(rand() * 12),
+ 1 + Math.floor(rand() * 27), 8 + Math.floor(rand() * 10),
+ Math.floor(rand() * 60), Math.floor(rand() * 60)),
+ // Default: a small subset of permission groups checked
+ permissions: PERMISSION_GROUPS.reduce((acc, g) => {
+ acc[g] = rand() < 0.18; return acc;
+ }, {}),
+ tabPerms: { customer: rand() < 0.4, supplier: rand() < 0.3, staff: rand() < 0.3, process: rand() < 0.3, driver: rand() < 0.2 },
+ };
+ });
+};
+
+// Mega-nav grid: top-level sections (left rail) → category columns → leaf items
+const MEGA_NAV = [
+ { id: "sales-mgmt", icon: "folder", label: "销售管理" },
+ { id: "dcs", icon: "folder", label: "DCS 系统" },
+ { id: "prod-mgmt", icon: "folder", label: "产品管理" },
+ { id: "prod-ops", icon: "folder", label: "生产运营" },
+ { id: "prod-exec", icon: "folder", label: "生产执行" },
+ { id: "mold", icon: "folder", label: "模具管理" },
+ { id: "purch", icon: "folder", label: "采购管理" },
+ { id: "matwh", icon: "folder", label: "材料库存" },
+ { id: "fgwh", icon: "folder", label: "成品库存" },
+ { id: "outsrc", icon: "folder", label: "外协管理" },
+ { id: "logistics", icon: "folder", label: "物流管理" },
+ { id: "qc", icon: "folder", label: "质量管理" },
+ { id: "fin", icon: "folder", label: "财务管理" },
+ { id: "cost-pro", icon: "folder", label: "成本管理(专)" },
+ { id: "cost", icon: "folder", label: "成本管理" },
+ { id: "equip", icon: "folder", label: "设备管理" },
+ { id: "hr", icon: "folder", label: "人事行政" },
+ { id: "oa", icon: "folder", label: "OA 系统" },
+ { id: "base", icon: "folder", label: "基础设置" },
+ { id: "sys", icon: "settings", label: "系统设置", active: true },
+];
+
+const MEGA_COLUMNS = {
+ "sys": [
+ { title: "期初设置", items: [
+ { label: "客户期初" }, { label: "供应商期初" }, { label: "材料期初" }, { label: "产品期初" }, { label: "数据导入" }, { label: "离线导出下载" },
+ ]},
+ { title: "用户管理", items: [
+ { label: "用户列表", screen: "userlist", featured: true },
+ { label: "系统权限" }, { label: "系统权限程查表" }, { label: "权限组" },
+ ]},
+ { title: "系统参数", items: [
+ { label: "系统参数" }, { label: "财务结准" }, { label: "系统常量配置" },
+ ]},
+ { title: "计算方案", items: [
+ { label: "方案列表" }, { label: "计算参数" },
+ ]},
+ { title: "日志", items: [
+ { label: "个性化模块" }, { label: "操作日志" }, { label: "异常清除KPI任务表" }, { label: "MYSQL监听器" },
+ ]},
+ { title: "开发平台", items: [
+ { label: "自定义开发范例" }, { label: "系统功能模块设置" }, { label: "ERC流程清单" }, { label: "功能模块界面设置" }, { label: "模拟收付款业务处理" },
+ ]},
+ { title: "API 对接管理", items: [
+ { label: "调用第三方接口(TOKEN配置)" }, { label: "调用第三方接口(接口定义)" }, { label: "被第三方调用(生成token)" }, { label: "数据同步" }, { label: "被第三方调用(API定义)" },
+ ]},
+ { title: "系统模块", items: [
+ { label: "系统模块配置", screen: "module", featured: true },
+ { label: "菜单配置" }, { label: "模块字段配置" },
+ ]},
+ ],
+ // Fallback for other sections — show a placeholder column
+};
+
+window.XLY = {
+ COMPANIES,
+ NAV_TREE,
+ MEGA_NAV,
+ MEGA_COLUMNS,
+ PERMISSION_GROUPS,
+ DEPARTMENTS,
+ USER_TYPES,
+ LANGUAGES,
+ buildUsers,
+};
diff --git a/prototype/src/icons.jsx b/prototype/src/icons.jsx
new file mode 100644
index 0000000..cbf66f5
--- /dev/null
+++ b/prototype/src/icons.jsx
@@ -0,0 +1,66 @@
+// Tiny inline SVG icons. All sized via currentColor.
+const Ic = {};
+
+const make = (name, paths, vb = "0 0 16 16") => {
+ Ic[name] = ({ size = 14, color, style, ...rest }) => (
+
+ );
+};
+
+make("plus", <>>);
+make("edit", <>>);
+make("trash", <>>);
+make("save", <>>);
+make("cancel", <>>);
+make("refresh", <>>);
+make("export", <>>);
+make("import", <>>);
+make("search", <>>);
+make("close", <>>);
+make("chevronDown", <>>);
+make("chevronRight", <>>);
+make("chevronLeft", <>>);
+make("triangle", <>>);
+make("triangleR", <>>);
+make("user", <>>);
+make("lock", <>>);
+make("building", <>>);
+make("home", <>>);
+make("menu", <>>);
+make("bell", <>>);
+make("settings", <>>);
+make("function", <>>);
+make("key", <>>);
+make("redo", <>>);
+make("undo", <>>);
+make("clipboard", <>>);
+make("doc", <>>);
+make("folder", <>>);
+make("expand", <>>);
+make("dot", <>>);
+make("filter", <>>);
+make("sortAsc", <>>);
+
+// Brand mark — overlapping registration squares (original geometric mark)
+Ic.Brand = ({ size = 28, accent = "#3a8ee0" }) => (
+
+);
+
+window.Ic = Ic;
diff --git a/prototype/src/login.jsx b/prototype/src/login.jsx
new file mode 100644
index 0000000..6ea35a1
--- /dev/null
+++ b/prototype/src/login.jsx
@@ -0,0 +1,163 @@
+// Login screen. Original logo (geometric registration-mark), original product name.
+
+const Login = ({ onLogin }) => {
+ const [user, setUser] = React.useState("admin");
+ const [pass, setPass] = React.useState("••••••••");
+ const [company, setCompany] = React.useState("std");
+ const [companyOpen, setCompanyOpen] = React.useState(false);
+ const [submitting, setSubmitting] = React.useState(false);
+
+ const submit = (e) => {
+ e && e.preventDefault();
+ if (!user || !pass) return;
+ setSubmitting(true);
+ setTimeout(() => onLogin({ user, company: XLY.COMPANIES.find(c => c.id === company)?.name }), 480);
+ };
+
+ return (
+
+
+ {/* Logo + wordmark */}
+
+
+
+
+
+ XLY-ERP
+
+
+ 印刷制造管理平台
+
+
+
+
+
+
+ {/* Footer */}
+
+ XLY 软件 · 印刷制造管理平台 · 版权所有 © 2017–2026
+
+
+ );
+};
+
+const loginStyles = {
+ fieldWrap: {
+ display: "flex", alignItems: "center", height: 38,
+ background: "rgba(255,255,255,0.95)",
+ border: "1px solid rgba(255,255,255,0.15)",
+ marginBottom: 10,
+ },
+ fieldIcon: { width: 36, display: "inline-flex", alignItems: "center", justifyContent: "center", color: "rgba(0,0,0,0.5)" },
+ fieldDivider: { width: 1, height: 18, background: "rgba(0,0,0,0.12)" },
+ input: {
+ flex: 1, height: "100%", border: "none", background: "transparent",
+ paddingLeft: 10, paddingRight: 10, fontSize: 13, color: "#1a2332",
+ outline: "none", fontFamily: "inherit",
+ },
+};
+
+window.Login = Login;
diff --git a/prototype/src/meganav.jsx b/prototype/src/meganav.jsx
new file mode 100644
index 0000000..e780209
--- /dev/null
+++ b/prototype/src/meganav.jsx
@@ -0,0 +1,108 @@
+// Full-viewport "全部导航" mega-menu, modeled after the reference screenshot.
+const MegaNav = ({ onClose, onOpen }) => {
+ const [activeSection, setActiveSection] = React.useState("sys");
+ const cols = XLY.MEGA_COLUMNS[activeSection];
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+
+
+ {/* Body */}
+
+ {/* Left rail */}
+
+ {XLY.MEGA_NAV.map((s) => {
+ const active = s.id === activeSection;
+ return (
+
setActiveSection(s.id)}
+ style={{
+ display: "flex", alignItems: "center", gap: 8,
+ padding: "8px 14px", cursor: "pointer", fontSize: 12,
+ background: active ? "var(--accent)" : "transparent",
+ color: active ? "#fff" : "var(--text-on-dark)",
+ borderLeft: active ? "3px solid #fff" : "3px solid transparent",
+ }}
+ onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = "rgba(255,255,255,0.06)"; }}
+ onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = "transparent"; }}
+ >
+
+ {s.label}
+
+ );
+ })}
+
+
+ {/* Columns */}
+
+ {cols ? (
+
+ {cols.map((col) => (
+
+
+ {col.title}
+
+
+ {col.items.map((it, i) => {
+ const clickable = !!it.screen;
+ return (
+
{ onOpen(it.screen, it.label); onClose(); } : undefined}
+ style={{
+ fontSize: 12,
+ color: it.featured ? "var(--accent)" : "var(--text-on-dark-muted)",
+ cursor: clickable ? "pointer" : "default",
+ display: "inline-flex", alignItems: "center", gap: 4,
+ padding: "1px 0",
+ }}
+ onMouseEnter={(e) => { if (clickable) e.currentTarget.style.color = "#fff"; }}
+ onMouseLeave={(e) => { if (clickable) e.currentTarget.style.color = it.featured ? "var(--accent)" : "var(--text-on-dark-muted)"; }}
+ >
+ {it.label}
+ {it.featured ? ★ : null}
+
+ );
+ })}
+
+
+ ))}
+
+ ) : (
+
+ {XLY.MEGA_NAV.find((s) => s.id === activeSection)?.label} · 模块开发中
+
+ )}
+
+
+
+ );
+};
+
+window.MegaNav = MegaNav;
diff --git a/prototype/src/primitives.jsx b/prototype/src/primitives.jsx
new file mode 100644
index 0000000..8d16dde
--- /dev/null
+++ b/prototype/src/primitives.jsx
@@ -0,0 +1,167 @@
+// Form primitives shared across screens. Tight, dense, faithful enterprise look.
+
+const fieldStyles = {
+ row: { display: "flex", alignItems: "center", gap: 0, minWidth: 0 },
+ label: {
+ width: 88, flex: "none", textAlign: "right", paddingRight: 8,
+ color: "var(--text)", fontSize: 12, lineHeight: "26px",
+ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
+ },
+ labelReq: { color: "var(--required)" },
+ control: {
+ flex: 1, minWidth: 0, height: "var(--input-h)",
+ border: "1px solid var(--border-input)",
+ background: "var(--bg-input)",
+ padding: "0 6px", fontSize: 12, color: "var(--text)",
+ borderRadius: 0,
+ },
+ controlDisabled: { background: "var(--bg-disabled)", color: "var(--text-muted)" },
+ controlReq: { background: "#fff8e1" }, // matches reference: required cells get a yellow tint
+};
+
+const Field = ({ label, required, children, labelWidth, span = 1, valign = "center" }) => (
+
+
+ {required ? * : null}
+ {label}:
+
+ {children}
+
+);
+
+const Input = ({ value, onChange, disabled, required, placeholder, mono, style }) => (
+ onChange(e.target.value) : undefined}
+ disabled={disabled}
+ placeholder={placeholder}
+ style={{
+ ...fieldStyles.control,
+ ...(disabled ? fieldStyles.controlDisabled : {}),
+ ...(required && !disabled ? fieldStyles.controlReq : {}),
+ ...(mono ? { fontFamily: '"JetBrains Mono", Menlo, Consolas, monospace', fontSize: 11 } : {}),
+ ...style,
+ }}
+ />
+);
+
+const Select = ({ value, onChange, options, disabled, required, style }) => (
+
+
+
+
+
+
+);
+
+const Checkbox = ({ checked, onChange, disabled, label, size = 13 }) => (
+
+);
+
+// Top-bar style (light blue) toolbar button — used in module config screen
+const ToolbarBtnLight = ({ children, icon, onClick, disabled, primary, danger, success }) => {
+ const [hover, setHover] = React.useState(false);
+ let bg = "#fff", border = "var(--border-input)", color = "var(--text)";
+ if (primary) { bg = hover ? "#3a9ae8" : "#5cabe8"; border = "#3a8ad6"; color = "#fff"; }
+ else if (danger) { bg = hover ? "#e85a5a" : "#f07070"; border = "#d04141"; color = "#fff"; }
+ else if (success) { bg = hover ? "#38b777" : "#4cc488"; border = "#27a567"; color = "#fff"; }
+ else if (hover && !disabled) { bg = "var(--accent-soft)"; border = "var(--accent)"; }
+ return (
+
+ );
+};
+
+// Dark-band toolbar button (used in user detail, matches the dark sub-toolbar in references)
+const ToolbarBtnDark = ({ children, icon, onClick, disabled, danger }) => {
+ const [hover, setHover] = React.useState(false);
+ return (
+
+ );
+};
+
+const Divider = ({ vertical }) =>
+ vertical
+ ?
+ : ;
+
+Object.assign(window, {
+ Field, Input, Select, Checkbox, ToolbarBtnLight, ToolbarBtnDark, Divider, fieldStyles,
+});
diff --git a/prototype/src/screen-home.jsx b/prototype/src/screen-home.jsx
new file mode 100644
index 0000000..e10eb89
--- /dev/null
+++ b/prototype/src/screen-home.jsx
@@ -0,0 +1,61 @@
+// Simple home — gives the workspace something useful when first opened.
+
+const Home = ({ user, onOpenScreen }) => {
+ const cards = [
+ { id: "userlist", title: "用户列表", desc: "管理系统账号、角色与权限分组", icon: },
+ { id: "module", title: "系统模块配置", desc: "配置 KPI 流程作业单与业务模块", icon: },
+ ];
+ const stats = [
+ { label: "今日待办", value: 12, tone: "var(--accent)" },
+ { label: "进行中报价", value: 47, tone: "var(--warning)" },
+ { label: "本月订单", value: 286, tone: "var(--success)" },
+ { label: "待审核", value: 8, tone: "var(--danger)" },
+ ];
+ return (
+
+
+
欢迎回来,{user || "admin"}
+
+ XLY-ERP 印刷制造管理平台 · {new Date().toLocaleDateString("zh-CN", { year: "numeric", month: "long", day: "numeric", weekday: "long" })}
+
+
+
+ {stats.map((s) => (
+
+
{s.label}
+
+ {s.value}
+
+
+ ))}
+
+
+
+ 快捷入口
+
+
+ {cards.map((c) => (
+
onOpenScreen(c.id, c.title)}
+ style={{
+ background: "#fff", padding: 14,
+ cursor: "pointer", display: "flex", alignItems: "center", gap: 10,
+ }}
+ onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-row-hover)"}
+ onMouseLeave={(e) => e.currentTarget.style.background = "#fff"}
+ >
+
{c.icon}
+
+
+ ))}
+
+
+
+ );
+};
+
+window.Home = Home;
diff --git a/prototype/src/screen-module.jsx b/prototype/src/screen-module.jsx
new file mode 100644
index 0000000..41fe514
--- /dev/null
+++ b/prototype/src/screen-module.jsx
@@ -0,0 +1,212 @@
+// Module configuration screen — KPI process tree on left, dense form on right.
+// Form fields mirror the structural pattern in 模块 1.png but with original copy.
+
+const moduleSections = [
+ {
+ title: "估价管理流程", processes: [
+ { id: "01", code: "01/04", label: "【新增】新报价单", active: true },
+ { id: "02", code: "02/04", label: "审核报价单->客户确认->二次确认" },
+ { id: "03", code: "03/04", label: "客户确认->二次确认" },
+ { id: "04", code: "04/04", label: "报价单->销售订单" },
+ { id: "05", code: "04/04", label: "报价单->转拼版单" },
+ { id: "06", code: "04/07", label: "主管审核报价单" },
+ { id: "07", code: "05/07", label: "业务确认报价单" },
+ ],
+ },
+ { title: "订单生产流程", collapsed: true },
+ { title: "自动拼版流程", collapsed: true },
+ { title: "销售送货流程", collapsed: true },
+ { title: "物料采购流程", collapsed: true },
+ { title: "物料领用流程", collapsed: true },
+ { title: "发外加工流程", collapsed: true },
+ { title: "质量管理流程", collapsed: true },
+ { title: "财务收付款流程", collapsed: true },
+];
+
+const ModuleScreen = () => {
+ const [mode, setMode] = React.useState("view"); // 'view' | 'edit'
+ const [activeProcess] = React.useState("01");
+
+ const initial = {
+ displayCategory: "印刷业务",
+ deptEn: "Pricing Personnel",
+ deptCn: "报价员",
+ permissionVisible: true,
+ seq: "1",
+ saveProc: "Sp_Check_sQtt",
+ storageUrl: "/indexPage/quotationPackTl...",
+ showOpt: false,
+ nodes: "-1752556899000998290E",
+ deptId: "",
+ flowId: "20250303094458291296...",
+ uniqueId: "10125124011501607650...",
+ flowEnName: "",
+ flowReqName: "",
+ titleCn: "01/04【新增】新报价单",
+ mobileLogo: "",
+ nameEn: "Quotation Document",
+ nameNotice: "常规产品报价单据",
+ showCategory: "待处理",
+ deptManager: "报价人员",
+ storeProc: "Sp_Calc_sQtt",
+ quickShow: "",
+ addId: "",
+ modType: "quotation/quotation/quotati",
+ calcProc: "Sp_Quotation_CalcDataPac",
+ saveAfterProc: "Sp_beforeSave_sQtt",
+ parent: "1752562484000281572E",
+ };
+
+ const [form, setForm] = React.useState(initial);
+ const set = (k, v) => setForm((s) => ({ ...s, [k]: v }));
+
+ const startEdit = () => setMode("edit");
+ const save = () => { setMode("view"); };
+ const cancel = () => { setForm(initial); setMode("view"); };
+
+ const disabled = mode === "view";
+
+ return (
+
+ {/* Light blue toolbar */}
+
+
} primary disabled={mode === "edit"}>添加节点
+
} primary disabled={mode === "edit"}>添加子节点
+
} primary disabled={mode === "edit"} onClick={startEdit}>修 改
+
} primary disabled={mode === "edit"}>复制节点
+
} success disabled={mode !== "edit"} onClick={save}>保 存
+
} disabled={mode !== "edit"} onClick={cancel}>取 消
+
} danger disabled={mode === "edit"}>删除节点
+
+
} primary>生成监听器单
+
+
+ {/* Body: form only (tree lives in main sidebar) */}
+
+ {/* Form panel */}
+
+
+
+
+
+
+
+
+ set("storeProc", v)} disabled={disabled} mono />
+
+
+ set("modType", v)} disabled={disabled} mono />
+
+
+
+ set("deptEn", v)} disabled={disabled} />
+
+
+ set("deptId", v)} disabled={disabled} mono />
+
+
+
+
+ set("addId", v)} disabled={disabled} mono />
+
+
+
+
+ set("permissionVisible", v)} disabled={disabled} />
+
+
+
+
+
+
+
+
+ set("flowReqName", v)} disabled={disabled} />
+
+
+
+ set("seq", v)} disabled={disabled} required />
+
+
+
+
+
+ set("calcProc", v)} disabled={disabled} mono />
+
+
+ set("saveAfterProc", v)} disabled={disabled} mono />
+
+
+
+ set("saveProc", v)} disabled={disabled} mono />
+
+
+ set("titleCn", v)} disabled={disabled} />
+
+
+ set("deleteProc", v)} disabled={disabled} mono />
+
+
+
+
+
+
+ set("storageUrl", v)} disabled={disabled} mono />
+
+
+ set("mobileLogo", v)} disabled={disabled} />
+
+
+ set("nameEn", v)} disabled={disabled} />
+
+
+ set("nameNotice", v)} disabled={disabled} />
+
+
+
+
+ set("showOpt", v)} disabled={disabled} />
+
+
+
+
+
+
+
+
+
+
+ } disabled={disabled}>上传
+
+ 关联文件(PDF / 图片,≤ 5 MB)
+
+
+
+
+
+ 当前节点:{activeProcess} 【新增】新报价单
+ 状态:{mode === "edit" ? "编辑中" : "只读"}
+
+
+
+
+ );
+};
+
+window.ModuleScreen = ModuleScreen;
diff --git a/prototype/src/screen-userdetail.jsx b/prototype/src/screen-userdetail.jsx
new file mode 100644
index 0000000..0b01b59
--- /dev/null
+++ b/prototype/src/screen-userdetail.jsx
@@ -0,0 +1,275 @@
+// 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 (
+
+ {/* Dark sub-toolbar */}
+
+
} onClick={() => start("new")} disabled={mode !== "view"}>新增
+
} onClick={() => start("edit")} disabled={mode !== "view"}>修改
+
} danger disabled={mode !== "view"} onClick={onDelete}>删除
+
} onClick={save} disabled={mode === "view"}>保存
+
} onClick={cancel} disabled={mode === "view"}>取消
+
+
}>功能
+
}>作废
+
}>重置密码
+
}>取消作废
+
+
+ 已选权限:{checked} / {XLY.PERMISSION_GROUPS.length - 1}
+
+
+ {mode === "view" ? "只读" : mode === "new" ? "新增中" : "编辑中"}
+
+
+
+
+ {/* Header form (3 cols × 3 rows) */}
+
+
+
+
+
+ set("createdBy", v)} disabled={disabled} required />
+
+
+ set("employee", v)} disabled={disabled} required />
+
+
+
+ set("account", v)} disabled={disabled} required />
+
+
+
+
+ set("empNo", v)} disabled={disabled} required mono />
+
+
+
+
+
+
+
+
+
+
+ {/* Permission tabs */}
+
+ {PERM_TABS.map((t) => {
+ const active = t.id === tab;
+ return (
+
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}
+
+ );
+ })}
+
+
+ {/* Tab content */}
+
+ {tab === "groups" ? (
+
+ ) : (
+
+ )}
+
+
+ {/* Floating help */}
+
+
+
+
+ );
+};
+
+const PermissionGrid = ({ user, setPerm, disabled }) => {
+ const [filter, setFilter] = React.useState("");
+ const [hovered, setHovered] = React.useState(null);
+ return (
+
+
+
+
+
+
+
+
+ |
+ {
+ XLY.PERMISSION_GROUPS.forEach((g) => { if (g !== "权限分类") setPerm(g, v); });
+ }}
+ disabled={disabled}
+ />
+ |
+
+
+ 权限分类
+ 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" }}
+ />
+
+ |
+
+
+
+ {XLY.PERMISSION_GROUPS.filter((g) => g !== "权限分类" && (!filter || g.includes(filter))).map((g, i) => {
+ const isHover = hovered === g;
+ return (
+ 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,
+ }}
+ >
+ |
+ setPerm(g, v)}
+ disabled={disabled}
+ />
+ |
+
+ {g}
+ |
+
+ );
+ })}
+
+
+
+ );
+};
+
+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 (
+
+
+ setTabPerm(tabKey, v)}
+ disabled={disabled}
+ label={`启用${cfg.label}查看权限`}
+ />
+ 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" }}
+ />
+
+
+ 共 {cfg.items.length} 个{cfg.label}
+
+
+
+ {cfg.items.filter((x) => !filter || x.includes(filter)).map((it) => (
+
+ ))}
+
+
+ );
+};
+
+window.UserDetail = UserDetail;
diff --git a/prototype/src/screen-userlist.jsx b/prototype/src/screen-userlist.jsx
new file mode 100644
index 0000000..ac4aae7
--- /dev/null
+++ b/prototype/src/screen-userlist.jsx
@@ -0,0 +1,216 @@
+// User list — searchable/filterable table.
+
+const UserList = ({ users, onOpenUser, onCreateUser }) => {
+ const [scope, setScope] = React.useState("all");
+ const [field, setField] = React.useState("empName");
+ const [matchMode, setMatchMode] = React.useState("contains");
+ const [query, setQuery] = React.useState("");
+ const [selected, setSelected] = React.useState(null);
+ const [checkedRows, setCheckedRows] = React.useState({});
+ const [sort, setSort] = React.useState({ key: null, dir: "asc" });
+
+ const toggleRow = (id) => setCheckedRows((s) => ({ ...s, [id]: !s[id] }));
+ const checkedCount = Object.values(checkedRows).filter(Boolean).length;
+
+ const filtered = React.useMemo(() => {
+ let rows = users;
+ if (scope === "active") rows = rows.filter((u) => !u.disabled);
+ if (scope === "disabled") rows = rows.filter((u) => u.disabled);
+ if (query) {
+ const q = query.toLowerCase();
+ const cmp = (a, b) => {
+ if (matchMode === "exact") return a === b;
+ if (matchMode === "starts") return a.startsWith(b);
+ return a.includes(b);
+ };
+ rows = rows.filter((u) => {
+ const v = String(u[field === "empName" ? "employee" : field === "account" ? "account" : field === "empNo" ? "empNo" : field === "department" ? "department" : "employee"] || "").toLowerCase();
+ return cmp(v, q);
+ });
+ }
+ if (sort.key) {
+ const dir = sort.dir === "asc" ? 1 : -1;
+ rows = [...rows].sort((a, b) => String(a[sort.key]).localeCompare(String(b[sort.key]), "zh") * dir);
+ }
+ return rows;
+ }, [users, scope, query, field, matchMode, sort]);
+
+ const toggleSort = (k) => setSort((s) => ({ key: k, dir: s.key === k && s.dir === "asc" ? "desc" : "asc" }));
+
+ return (
+
+ {/* Filter toolbar */}
+
+
}>刷新
+
} primary onClick={onCreateUser}>新增
+
}>导出 Excel
+
+
+
+
+
setQuery(e.target.value)}
+ placeholder=""
+ style={{ height: 26, width: 180, border: "1px solid var(--border-input)", padding: "0 6px", fontSize: 12, background: "var(--bg-input)" }}
+ />
+
} primary>查 找
+
{ setQuery(""); setScope("all"); }}>清 空
+
+
+ {checkedCount > 0 ? <>已选 {checkedCount} 条 / > : null}
+ 共 {users.length} 条记录
+
+
+
+ {/* Table */}
+
+
+
+
+
+ {colDefs.map((c) => )}
+
+
+
+ |
+ 0 && filtered.every((u) => checkedRows[u.id])}
+ onChange={(v) => {
+ const next = { ...checkedRows };
+ filtered.forEach((u) => { next[u.id] = v; });
+ setCheckedRows(next);
+ }}
+ />
+ |
+ toggleSort("seq")}>序号 |
+ {colDefs.map((c) => (
+ toggleSort(c.key)}>
+
+ {c.label}
+ {sort.key === c.key ? (
+
+ {sort.dir === "asc" ? : }
+
+ ) : (
+
+ )}
+
+ |
+ ))}
+
+
+
+ {filtered.map((u, idx) => {
+ const isSel = selected === u.id;
+ return (
+ setSelected(u.id)}
+ onDoubleClick={() => onOpenUser(u)}
+ style={{
+ background: isSel ? "var(--selected)" : idx % 2 === 0 ? "#fff" : "var(--bg-row-zebra)",
+ cursor: "pointer", height: 24,
+ color: u.disabled ? "var(--text-faint)" : "var(--text)",
+ }}
+ onMouseEnter={(e) => { if (!isSel) e.currentTarget.style.background = "var(--bg-row-hover)"; }}
+ onMouseLeave={(e) => { if (!isSel) e.currentTarget.style.background = idx % 2 === 0 ? "#fff" : "var(--bg-row-zebra)"; }}
+ >
+ | e.stopPropagation()}>
+ toggleRow(u.id)} />
+ |
+ {u.seq} |
+ {u.employee} |
+ {u.empNo} |
+ {u.account} |
+ {u.department} |
+ {u.type} |
+ {u.language} |
+
+
+ |
+ {u.lastLogin} |
+ {u.createdBy} |
+ {u.createdAt} |
+
+ );
+ })}
+
+
+ {filtered.length === 0 ? (
+
+ 没有匹配的记录
+
+ ) : null}
+
+
+ {/* Footer */}
+
+
当前显示:共 {filtered.length} 条单据 共 {filtered.length} 条记录
+
+
+ 1
+
+
+
+
+
+ );
+};
+
+const colDefs = [
+ { key: "employee", label: "员工名", w: 100 },
+ { key: "empNo", label: "员工号", w: 90 },
+ { key: "account", label: "用户号", w: 90 },
+ { key: "department", label: "部门", w: 110 },
+ { key: "type", label: "用户类型", w: 110 },
+ { key: "language", label: "语言", w: 60 },
+ { key: "disabled", label: "作废", w: 50 },
+ { key: "lastLogin", label: "登录日期", w: 145 },
+ { key: "createdBy", label: "制单人", w: 90 },
+ { key: "createdAt", label: "制单日期", w: 145 },
+];
+
+const selectStyle = {
+ height: 26, padding: "0 6px", border: "1px solid var(--border-input)",
+ background: "#fff", fontSize: 12, color: "var(--text)", fontFamily: "inherit",
+};
+
+const thStyle = {
+ padding: "5px 8px", textAlign: "left", fontWeight: 500,
+ borderRight: "1px solid var(--border)", borderBottom: "1px solid var(--border)",
+ whiteSpace: "nowrap",
+};
+
+const tdStyle = {
+ padding: "3px 8px", borderRight: "1px solid var(--border)", borderBottom: "1px solid #f0f2f5",
+ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
+};
+
+const pgBtn = {
+ width: 22, height: 22, border: "1px solid var(--border-input)", background: "#fff", cursor: "pointer",
+ display: "inline-flex", alignItems: "center", justifyContent: "center",
+};
+
+window.UserList = UserList;
diff --git a/prototype/src/sidebar.jsx b/prototype/src/sidebar.jsx
new file mode 100644
index 0000000..f89dec2
--- /dev/null
+++ b/prototype/src/sidebar.jsx
@@ -0,0 +1,123 @@
+// Sidebar — search + collapsible tree, dense Windows-explorer style.
+
+const Sidebar = ({ tree, activeNodeId, expanded, setExpanded, onNodeClick, query, setQuery }) => {
+ return (
+
+ {/* Search bar */}
+
+
+
setQuery(e.target.value)}
+ placeholder="请输入您想要搜索的关键字"
+ style={{
+ width: "100%", height: 26, paddingLeft: 8, paddingRight: 26,
+ border: "1px solid var(--border-input)", background: "var(--bg-input)",
+ fontSize: 12, color: "var(--text)",
+ }}
+ />
+
+
+
+
+
+ {/* Tree */}
+
+ {tree.map((node) => (
+
+ ))}
+
+
+ );
+};
+
+const matches = (node, q) => {
+ if (!q) return true;
+ const t = q.toLowerCase();
+ if ((node.label || "").toLowerCase().includes(t)) return true;
+ if (node.children) return node.children.some((c) => matches(c, q));
+ return false;
+};
+
+const TreeNode = ({ node, depth, activeNodeId, expanded, setExpanded, onNodeClick, query }) => {
+ const hasChildren = node.children && node.children.length > 0;
+ const isExpanded = expanded[node.id] || (query && matches(node, query) && hasChildren);
+ const isActive = activeNodeId === node.id;
+ const visible = matches(node, query);
+ if (!visible) return null;
+
+ const click = () => {
+ if (hasChildren) {
+ setExpanded((s) => ({ ...s, [node.id]: !isExpanded }));
+ }
+ if (node.leaf || !hasChildren) {
+ onNodeClick(node);
+ }
+ };
+
+ return (
+
+
{ if (!isActive) e.currentTarget.style.background = "var(--bg-row-hover)"; }}
+ onMouseLeave={(e) => { if (!isActive) e.currentTarget.style.background = "transparent"; }}
+ >
+ {hasChildren ? (
+
+ {isExpanded ? : }
+
+ ) : (
+
+
+
+ )}
+
+ {node.label}
+
+
+ {isExpanded && hasChildren ? (
+
+ {node.children.map((child) => (
+
+ ))}
+
+ ) : null}
+
+ );
+};
+
+window.Sidebar = Sidebar;
diff --git a/prototype/src/tabs.jsx b/prototype/src/tabs.jsx
new file mode 100644
index 0000000..7972409
--- /dev/null
+++ b/prototype/src/tabs.jsx
@@ -0,0 +1,67 @@
+// Top-bar tab strip — IDE-style, multi-tab open/close/switch.
+
+const TabStrip = ({ tabs, activeTabId, onSelect, onClose }) => {
+ return (
+
+ {tabs.map((t) => {
+ const active = t.id === activeTabId;
+ return ;
+ })}
+
+ );
+};
+
+const Tab = ({ tab, active, onSelect, onClose }) => {
+ const [hover, setHover] = React.useState(false);
+ return (
+ onSelect(tab.id)}
+ onMouseEnter={() => setHover(true)}
+ onMouseLeave={() => setHover(false)}
+ style={{
+ position: "relative",
+ display: "flex", alignItems: "center", gap: 6,
+ height: "100%", padding: "0 12px",
+ background: active ? "var(--bg-tab-active)" : hover ? "rgba(255,255,255,0.5)" : "transparent",
+ borderTop: active ? "2px solid var(--accent)" : "2px solid transparent",
+ borderLeft: "1px solid var(--border)",
+ borderRight: "1px solid var(--border)",
+ marginRight: -1,
+ cursor: "pointer", fontSize: 12,
+ color: active ? "var(--text)" : "var(--text-muted)",
+ fontWeight: active ? 500 : 400,
+ whiteSpace: "nowrap",
+ }}
+ >
+ {tab.label}
+ {tab.closable !== false ? (
+ { e.stopPropagation(); onClose(tab.id); }}
+ style={{
+ display: "inline-flex", alignItems: "center", justifyContent: "center",
+ width: 14, height: 14, color: "var(--text-faint)",
+ borderRadius: 2,
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = "var(--danger)";
+ e.currentTarget.style.color = "#fff";
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = "transparent";
+ e.currentTarget.style.color = "var(--text-faint)";
+ }}
+ >
+
+
+ ) : null}
+
+ );
+};
+
+window.TabStrip = TabStrip;
diff --git a/prototype/src/workspace.jsx b/prototype/src/workspace.jsx
new file mode 100644
index 0000000..6e72fd1
--- /dev/null
+++ b/prototype/src/workspace.jsx
@@ -0,0 +1,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 (
+
+ {/* Top bar */}
+
+
+
+
+
+ XLY-ERP
+ · {session.company}
+
+
+
+
+
+
+
+ {/* Tab strip */}
+
+
+ {/* Body */}
+
+ {sidebarOpen && activeTabId === "home" ? (
+
+
+
+ ) : null}
+
+ {activeTab ? openTab({ id: s, label, screen: s })} /> : null}
+
+
+
+ {/* Status bar */}
+
+ 就绪 · 当前用户 {session.user} · {session.company}
+ XLY-ERP v8.6.2 · © 2017–2026 XLY 软件股份
+
+ {megaOpen ? (
+
setMegaOpen(false)}
+ onOpen={(screen, label) => openTab({ id: screen, label, screen })}
+ />
+ ) : null}
+
+ );
+};
+
+const ScreenRouter = ({ tab, users, onOpenUser, onCreateUser, onSaveUser, session, onOpenScreen }) => {
+ if (tab.screen === "home") return ;
+ if (tab.screen === "userlist") return ;
+ if (tab.screen === "module") return ;
+ if (tab.screen === "userdetail") {
+ const u = users.find((x) => x.id === tab.userId);
+ if (!u) return 用户不存在;
+ return ;
+ }
+ return {tab.stubLabel || tab.label} · 模块开发中;
+};
+
+const Empty = ({ children }) => (
+
+ {children}
+
+);
+
+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;
diff --git a/prototype/uploads/pasted-1777540186759-0.png b/prototype/uploads/pasted-1777540186759-0.png
new file mode 100644
index 0000000..b8080ee
--- /dev/null
+++ b/prototype/uploads/pasted-1777540186759-0.png
diff --git a/prototype/uploads/模块 1.png b/prototype/uploads/模块 1.png
new file mode 100644
index 0000000..4f470bd
--- /dev/null
+++ b/prototype/uploads/模块 1.png
diff --git a/prototype/uploads/用户2.png b/prototype/uploads/用户2.png
new file mode 100644
index 0000000..fbca61e
--- /dev/null
+++ b/prototype/uploads/用户2.png
diff --git a/prototype/uploads/用户修改 1.png b/prototype/uploads/用户修改 1.png
new file mode 100644
index 0000000..e9cd0ea
--- /dev/null
+++ b/prototype/uploads/用户修改 1.png
diff --git a/prototype/uploads/用户修改 2.png b/prototype/uploads/用户修改 2.png
new file mode 100644
index 0000000..49378d9
--- /dev/null
+++ b/prototype/uploads/用户修改 2.png
diff --git a/prototype/uploads/用户查询.png b/prototype/uploads/用户查询.png
new file mode 100644
index 0000000..1c55eb5
--- /dev/null
+++ b/prototype/uploads/用户查询.png
diff --git a/prototype/uploads/登录 1.png b/prototype/uploads/登录 1.png
new file mode 100644
index 0000000..4c2e6bb
--- /dev/null
+++ b/prototype/uploads/登录 1.png
diff --git a/prototype/uploads/登录 2.png b/prototype/uploads/登录 2.png
new file mode 100644
index 0000000..6fbfbab
--- /dev/null
+++ b/prototype/uploads/登录 2.png