From 4416c52fbdae4a3b8e8580f78200841e452c4d82 Mon Sep 17 00:00:00 2001 From: zichun Date: Thu, 7 May 2026 17:42:40 +0800 Subject: [PATCH] chore(prototype): add UI prototype files and reference screenshots --- prototype/XLY-ERP.html | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ prototype/src/app.jsx | 9 +++++++++ prototype/src/data.jsx | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ prototype/src/icons.jsx | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ prototype/src/login.jsx | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ prototype/src/meganav.jsx | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ prototype/src/primitives.jsx | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ prototype/src/screen-home.jsx | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ prototype/src/screen-module.jsx | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ prototype/src/screen-userdetail.jsx | 275 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ prototype/src/screen-userlist.jsx | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ prototype/src/sidebar.jsx | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ prototype/src/tabs.jsx | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ prototype/src/workspace.jsx | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ prototype/uploads/pasted-1777540186759-0.png | Bin 0 -> 359020 bytes prototype/uploads/模块 1.png | Bin 0 -> 415889 bytes prototype/uploads/用户2.png | Bin 0 -> 205841 bytes prototype/uploads/用户修改 1.png | Bin 0 -> 219323 bytes prototype/uploads/用户修改 2.png | Bin 0 -> 220894 bytes prototype/uploads/用户查询.png | Bin 0 -> 587472 bytes prototype/uploads/登录 1.png | Bin 0 -> 87053 bytes prototype/uploads/登录 2.png | Bin 0 -> 96782 bytes 22 files changed, 1912 insertions(+), 0 deletions(-) create mode 100644 prototype/XLY-ERP.html create mode 100644 prototype/src/app.jsx create mode 100644 prototype/src/data.jsx create mode 100644 prototype/src/icons.jsx create mode 100644 prototype/src/login.jsx create mode 100644 prototype/src/meganav.jsx create mode 100644 prototype/src/primitives.jsx create mode 100644 prototype/src/screen-home.jsx create mode 100644 prototype/src/screen-module.jsx create mode 100644 prototype/src/screen-userdetail.jsx create mode 100644 prototype/src/screen-userlist.jsx create mode 100644 prototype/src/sidebar.jsx create mode 100644 prototype/src/tabs.jsx create mode 100644 prototype/src/workspace.jsx create mode 100644 prototype/uploads/pasted-1777540186759-0.png create mode 100644 prototype/uploads/模块 1.png create mode 100644 prototype/uploads/用户2.png create mode 100644 prototype/uploads/用户修改 1.png create mode 100644 prototype/uploads/用户修改 2.png create mode 100644 prototype/uploads/用户查询.png create mode 100644 prototype/uploads/登录 1.png create mode 100644 prototype/uploads/登录 2.png 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 @@ + + + + +XLY-ERP · 印刷制造管理平台 + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + diff --git a/prototype/src/app.jsx b/prototype/src/app.jsx new file mode 100644 index 0000000..c2b299f --- /dev/null +++ b/prototype/src/app.jsx @@ -0,0 +1,9 @@ +// Top-level app: login → workspace. + +const App = () => { + const [session, setSession] = React.useState(null); + if (!session) return ; + return 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 }) => ( + + {paths} + + ); +}; + +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 */} +
+
+ + {/* Three overlapping registration squares — print-press metaphor */} + + + + {/* registration cross */} + + + +
+
+ XLY-ERP +
+
+ 印刷制造管理平台 +
+
+ +
+ {/* User */} +
+ + + setUser(e.target.value)} + placeholder="请输入用户名" + style={loginStyles.input} + /> +
+ {/* Password */} +
+ + + setPass(e.target.value)} + placeholder="请输入密码" + style={loginStyles.input} + /> +
+ {/* Company picker */} +
+ + {companyOpen ? ( +
+ {XLY.COMPANIES.map((c, i) => { + const sel = c.id === company; + return ( +
{ setCompany(c.id); setCompanyOpen(false); }} + style={{ + padding: "9px 12px", color: "#fff", fontSize: 13, + cursor: "pointer", + background: sel ? "var(--accent)" : "transparent", + borderTop: i === 0 ? "none" : "1px solid rgba(255,255,255,0.04)", + }} + onMouseEnter={(e) => { if (!sel) e.currentTarget.style.background = "rgba(255,255,255,0.06)"; }} + onMouseLeave={(e) => { if (!sel) e.currentTarget.style.background = "transparent"; }} + > + {c.name} +
+ ); + })} +
+ ) : null} +
+ {/* Submit */} + +
+
+ + {/* 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} +
+
{c.title}
+
{c.desc}
+
+
+ ))} +
+
+
+ ); +}; + +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} /> +
+
+ + set("showCategory", v)} disabled={disabled} options={["待处理", "已处理", "全部"]} /> + + + + + + 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 /> + + + + set("language", v)} disabled={disabled} required options={XLY.LANGUAGES} /> + + + 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) => )} + + + + + + {colDefs.map((c) => ( + + ))} + + + + {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)"; }} + > + + + + + + + + + + + + + + ); + })} + +
+ 0 && filtered.every((u) => checkedRows[u.id])} + onChange={(v) => { + const next = { ...checkedRows }; + filtered.forEach((u) => { next[u.id] = v; }); + setCheckedRows(next); + }} + /> + toggleSort("seq")}>序号 toggleSort(c.key)}> + + {c.label} + {sort.key === c.key ? ( + + {sort.dir === "asc" ? : } + + ) : ( + + )} + +
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 Binary files /dev/null and b/prototype/uploads/pasted-1777540186759-0.png differ diff --git a/prototype/uploads/模块 1.png b/prototype/uploads/模块 1.png new file mode 100644 index 0000000..4f470bd Binary files /dev/null and b/prototype/uploads/模块 1.png differ diff --git a/prototype/uploads/用户2.png b/prototype/uploads/用户2.png new file mode 100644 index 0000000..fbca61e Binary files /dev/null and b/prototype/uploads/用户2.png differ diff --git a/prototype/uploads/用户修改 1.png b/prototype/uploads/用户修改 1.png new file mode 100644 index 0000000..e9cd0ea Binary files /dev/null and b/prototype/uploads/用户修改 1.png differ diff --git a/prototype/uploads/用户修改 2.png b/prototype/uploads/用户修改 2.png new file mode 100644 index 0000000..49378d9 Binary files /dev/null and b/prototype/uploads/用户修改 2.png differ diff --git a/prototype/uploads/用户查询.png b/prototype/uploads/用户查询.png new file mode 100644 index 0000000..1c55eb5 Binary files /dev/null and b/prototype/uploads/用户查询.png differ diff --git a/prototype/uploads/登录 1.png b/prototype/uploads/登录 1.png new file mode 100644 index 0000000..4c2e6bb Binary files /dev/null and b/prototype/uploads/登录 1.png differ diff --git a/prototype/uploads/登录 2.png b/prototype/uploads/登录 2.png new file mode 100644 index 0000000..6fbfbab Binary files /dev/null and b/prototype/uploads/登录 2.png differ -- libgit2 0.22.2