From: root Date: Tue, 8 Sep 2026 08:35:48 +0000 (+0800) Subject: refactor: 按 zip 设计就地重构站点(单文档 SPA + 领域方案库/设备模板/页面样式库)+ 防闲置换页 X-Git-Url: http://acesimba.cloud/gitweb/?a=commitdiff_plain;h=9a1ef006dc8f081a84eb6a46fc1c861e6466bd66;p=StationBuilder.git refactor: 按 zip 设计就地重构站点(单文档 SPA + 领域方案库/设备模板/页面样式库)+ 防闲置换页 - app/page.tsx 重写为单文档 SPA(go(pageKey) 客户端导航):领域方案库、设备模板、页面样式库、内部工具配置、领域配置 - 新增 app/company-theme.tsx(AntD ConfigProvider 主题);layout.tsx 引入主题与 reset.css;globals.css 视觉对齐 - 配置 vite.local.config.ts:dev:local 用纯 vinext(),规避 Cloudflare workerd 下 antd CJS interop 报错(_interopRequireDefault is not a function) - .npmrc 设 node-linker=hoisted;package.json 与 pnpm 锁定文件更新 - 新增 scripts/warmup.mjs(开机预热全部页面)+ scripts/keepalive.mjs(每 60s 探活,防止闲置被换页导致打开缓慢近一分钟) - 新增 public/wise-header/ 资源、CHANGELOG-20260902.md 注:data/db/StationBuilder.sqlite 为运行数据,按规则本次不提交;表结构由 db-sidecar 以 CREATE TABLE IF NOT EXISTS 自举,克隆后首次启动自动补齐。 --- diff --git a/.npmrc b/.npmrc index 0edab15..071bd4b 100644 --- a/.npmrc +++ b/.npmrc @@ -2,3 +2,4 @@ audit=false fund=false update-notifier=false cache=.sites-runtime/npm-cache +node-linker=hoisted diff --git a/CHANGELOG-20260902.md b/CHANGELOG-20260902.md new file mode 100644 index 0000000..d34f140 --- /dev/null +++ b/CHANGELOG-20260902.md @@ -0,0 +1,53 @@ +# StationBuilder 修改说明 + +更新时间:2026-09-02 + +本工程是在原 StationBuilder 工程代码上直接完成的界面和交互重构,并非独立设计演示页面。 + +## 主要修改 + +- Header 参照 WISE-IoT 工单系统重构,产品名称改为 `StationBuilder | 建站工具 v0.1`。 +- 本地化 WISE-IoT Logo、搜索、通知、主题、收藏、设置及 Powered by WISE-IoT SVG 资源。 +- 左侧菜单改为“站点管理、方案中心、系统设定”三组,并移除业务菜单图标。 +- 菜单字体改为 Noto Sans SC;一级菜单 19px/56px,二级菜单 17px/50px。 +- 删除原侧栏底部“内部工具”区域。 +- 新增“内部工具配置”页面,用于配置 Wordbuddy AI、默认模型和工具版本。 +- 新增“领域配置”页面,用于新增和删除领域分类。 +- “项目管理”调整为“站点管理”,“资源中心”调整为“方案中心”。 +- 重构站点项目、领域方案、设备模板和页面样式列表。 +- 修复站点列表复选框后的黑点。 +- 扩展领域方案名称列。 +- 设备模板第一列直接显示模板名称,不再显示缩略图。 +- 页面样式第一列显示样式方案名称,缩略图移动到独立列。 +- 表单、状态页签、查询栏、表格、分页、详情抽屉和操作按钮统一为公司规范。 + +## 本地运行 + +环境要求:Node.js 22.13 或更高版本。 + +```powershell +npm ci +node db-sidecar.mjs +``` + +另开一个终端: + +```powershell +npm run dev:local +``` + +访问: + +```text +http://127.0.0.1:37821/t/stationbuilder/ +``` + +如需使用 AI 功能,还需根据内部运行环境配置并启动 `ai-sidecar.mjs`。 + +## 验证 + +- TypeScript UI 类型检查通过。 +- Vite/Vinext 生产构建通过。 +- 已在本地浏览器检查主要页面和菜单交互。 + +更完整的逐项说明见随 ZIP 一并交付的 `StationBuilder-修改说明-20260902.md`。 diff --git a/app/company-theme.tsx b/app/company-theme.tsx new file mode 100644 index 0000000..fc2ba6d --- /dev/null +++ b/app/company-theme.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { ConfigProvider } from "antd"; +import zhCN from "antd/locale/zh_CN"; + +const companyTheme = { + token: { + colorPrimary: "#008cd6", + colorInfo: "#008cd6", + colorSuccess: "#42d067", + colorWarning: "#ff990b", + colorError: "#f05b49", + colorText: "#323233", + colorTextSecondary: "#89898a", + colorBorder: "#d9d9d9", + colorBgLayout: "#ffffff", + colorBgContainer: "#ffffff", + borderRadius: 4, + controlHeight: 32, + fontFamily: + 'Roboto, "Noto Sans SC", "Microsoft YaHei", Arial, sans-serif', + fontSize: 14, + boxShadow: "none", + boxShadowSecondary: "0 4px 16px rgba(0, 0, 0, 0.12)", + }, + components: { + Button: { + primaryShadow: "none", + defaultShadow: "none", + fontWeight: 600, + paddingInline: 24, + }, + Drawer: { + colorBgElevated: "#ffffff", + }, + Table: { + headerBg: "#f7f7f7", + headerColor: "#323233", + rowHoverBg: "#f1f8fc", + borderColor: "#e5e5e5", + }, + Input: { + activeBorderColor: "#008cd6", + hoverBorderColor: "#008cd6", + }, + }, +} as const; + +export default function CompanyTheme({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/app/globals.css b/app/globals.css index c1684e9..2dec9a8 100644 --- a/app/globals.css +++ b/app/globals.css @@ -165,7 +165,9 @@ .prompt-view{margin:0;background:#0f1115;color:#e6edf3;border:1px solid #2a2f36;border-radius:6px;padding:14px;font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace;font-size:11.5px;line-height:1.6;white-space:pre-wrap;word-break:break-word;max-height:60vh;overflow:auto} /* ===== 站点列表:多选与删除 ===== */ -.data-table input[type=checkbox]{width:16px;height:16px;accent-color:var(--blue);cursor:pointer;vertical-align:middle} +.data-table input[type=checkbox]{display:block;width:16px;height:16px;padding:0;margin:0 auto;appearance:none;border:1px solid #aeb4b9;border-radius:2px;box-shadow:none;background:#fff;cursor:pointer;vertical-align:middle} +.data-table input[type=checkbox]:checked{border-color:var(--blue);background-color:var(--blue);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.2 6.6 11 12.5 4.8' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;background-size:14px 14px} +.data-table input[type=checkbox]:focus-visible{outline:2px solid #9bd8f3;outline-offset:1px} .data-table thead th:first-child{width:34px;text-align:center} .link.danger{color:var(--red)} .link.danger:hover{text-decoration:underline} @@ -200,6 +202,119 @@ .style-pick-thumb img{width:100%;height:100%;object-fit:contain} .style-pick-thumb.none{color:#aaa;font-size:13px} .style-pick span{font-size:11px;line-height:1.3} + +/* ========================================================================== */ +/* Company workspace standard — measured from the WISE-IoT service desk UI. */ +/* This final layer intentionally normalizes legacy prototype styles while the */ +/* remaining feature pages are migrated to shared company components. */ +/* ========================================================================== */ +:root{ + --company-primary:#008cd6; + --company-primary-hover:#0078b8; + --company-nav-active:#c3e4f6; + --company-text:#323233; + --company-muted:#89898a; + --company-border:#d9d9d9; + --company-line:#e5e5e5; + --company-surface:#fff; + --company-soft:#f5f5f5; + --company-success:#42d067; + --company-warning:#ff990b; + --blue:var(--company-primary); + --blue2:var(--company-primary); + --line:var(--company-border); + --soft:var(--company-soft); + --text:var(--company-text); + --muted:var(--company-muted); +} +html,body{font-family:Roboto,"Noto Sans SC","Microsoft YaHei",Arial,sans-serif;font-size:14px;line-height:1.5;color:var(--company-text);background:#fff} +.app-shell{min-height:100vh;padding-top:56px;padding-left:200px;background:#fff} +.topbar{height:56px;border-top:4px solid var(--company-primary);border-bottom:1px solid #c8c8c8;background:#fff;background-image:none;box-shadow:none;padding:0 20px;gap:10px} +.topbar>strong{font-size:22px;color:#0b4f76;letter-spacing:-.3px}.topbar .brand-mark{font-size:20px;color:var(--company-primary)} +.top-actions{gap:14px}.top-actions a{color:var(--company-primary);text-decoration:none}.top-actions small{font-size:10px;color:#666} +.hamburger{font-size:18px;color:#666}.divider{height:22px} +.side-nav{top:56px;width:200px;background:#f5f5f5;border-right:1px solid #ddd} +.module-switch{padding:0;border-bottom:1px solid #ddd;display:flex;flex-direction:column;gap:0} +.module-switch>button{height:50px;border:0;border-radius:0;background:transparent;padding:0 20px;gap:10px;font-size:14px;font-weight:500} +.module-switch>button>b{width:18px;height:auto;background:transparent;color:#777} +.module-switch>button>span{display:block;font-weight:500}.module-switch>button small{display:none} +.module-switch>button:hover{background:#e9f3f8}.module-switch>button.active{background:var(--company-nav-active);color:var(--company-primary)} +.module-switch>button.active>b{background:transparent;color:var(--company-primary)} +.side-nav>small{font-size:12px;color:#6f6f70;padding:18px 20px 6px;letter-spacing:0;text-transform:none} +.side-nav>button{height:44px;padding:0 20px;border-left:0;font-size:14px;gap:10px} +.side-nav>button.active{background:var(--company-nav-active);color:var(--company-primary);border-left:0;font-weight:600} +.side-nav>button em{width:20px;height:20px;font-size:11px} +.site-switcher{height:32px;width:calc(100% - 32px);margin:6px 16px;border-color:var(--company-border);border-radius:4px;font-size:13px} +.side-help{margin:auto 16px 16px;border:0;border-top:1px solid #ddd;background:transparent;padding:14px 4px 0} +.workspace{min-height:calc(100vh - 56px);padding-bottom:72px;background:#fff}.crumb{display:none} +.page-heading{min-height:64px;padding:10px 24px;background:#f3f3f3;border-bottom:1px solid var(--company-border)} +.page-heading h1{font-size:24px;line-height:36px;font-weight:700;margin:0;color:var(--company-text)} +.page-heading p{font-size:13px;color:var(--company-muted);margin:0}.page-heading>div:last-child{display:flex;gap:8px} +.content{padding:0 24px 24px;background:#fff} +.button.ant-btn{min-width:112px;height:32px;margin:0;border-radius:4px;padding:0 24px;font-size:14px;font-weight:600;box-shadow:none} +.button.ant-btn-primary{background:var(--company-primary);border-color:var(--company-primary)}.button.ant-btn-primary:hover{background:var(--company-primary-hover)!important;border-color:var(--company-primary-hover)!important} +.button.ant-btn-default,.button.secondary{background:#fff;color:#5f5f60;border-color:#c8c8c8}.button.ant-btn-default:hover,.button.secondary:hover{color:var(--company-primary)!important;border-color:var(--company-primary)!important;background:#fff!important} +.steps{height:64px;border-top:0;border-bottom:1px solid var(--company-border);padding:0 24px;background:#fff}.steps button{padding:0 20px;font-size:14px}.steps button>i{width:26px;height:26px}.steps button.active>i{background:var(--company-primary);border-color:var(--company-primary)} +.panel{border:1px solid var(--company-border);border-radius:0;background:#fff;margin:16px 0;box-shadow:none}.panel-head{min-height:48px;padding:0 16px;background:#fff}.panel-head>strong{font-size:16px}.panel-body{padding:16px} +.company-status-tabs{height:56px;display:flex;align-items:flex-end;border-bottom:1px solid var(--company-border);gap:0;padding-top:8px} +.company-status-tabs button{height:48px;min-width:120px;padding:0 24px;border:0;border-bottom:3px solid transparent;background:#fff;color:var(--company-muted);font-size:15px} +.company-status-tabs button span{margin-right:8px}.company-status-tabs button b{display:inline-grid;place-items:center;min-width:24px;height:20px;padding:0 6px;border-radius:10px;background:#eee;color:#999;font-size:12px} +.company-status-tabs button.active{color:var(--company-primary);border-bottom-color:var(--company-primary);font-weight:600}.company-status-tabs button.active b{background:#dff4eb;color:#25a85b} +.company-list-panel{margin-top:16px}.company-list-panel>.panel-body{padding:0 16px 12px} +.company-filterbar{height:auto;min-height:56px;padding:12px 0;gap:8px}.company-filterbar .search-field{width:240px;height:32px;border:1px solid var(--company-border);border-radius:4px;padding:0 10px;background:#fff}.company-filterbar .search-field:focus-within{border-color:var(--company-primary)} +.company-filterbar select{height:32px;min-width:160px;border:1px solid var(--company-border);border-radius:4px;background:#fff;padding:0 28px 0 10px} +.filter-actions{margin-left:auto;display:flex;gap:8px}.filter-actions .button{min-width:96px} +.company-table{table-layout:fixed}.company-table th{height:44px;background:#f7f7f7;font-size:14px;font-weight:600;padding:0 12px}.company-table td{height:50px;padding:8px 12px;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.company-table tbody tr{cursor:pointer}.company-table tbody tr:hover td{background:#f1f8fc}.company-table th:first-child,.company-table td:first-child{width:44px;overflow:visible;text-overflow:clip}.company-table th:last-child,.company-table td:last-child{width:70px;text-align:center} +.company-record-link{font-size:14px}.company-record-link b{color:var(--company-primary)} +.status.ant-tag{height:24px;line-height:21px;border-radius:12px;padding:0 10px;margin:0;font-size:13px}.status.ok{color:var(--company-success);background:rgb(66 208 103 / 20%);border-color:var(--company-success)}.status.warn{color:var(--company-warning);background:rgb(255 153 11 / 20%);border-color:var(--company-warning)}.status.bad{color:#f05b49;background:rgb(240 91 73 / 16%);border-color:#f05b49}.status.muted{color:#777;background:#f3f3f3;border-color:#c9c9c9} +.pagination{height:56px;padding:12px 0 0;gap:0}.pagination span{border:1px solid var(--company-border);height:32px;line-height:30px;padding:0 12px;margin-right:8px}.pagination button,.pagination select{height:32px;border:1px solid var(--company-border);background:#fff}.pagination button{width:34px}.pagination button.active{background:#fff;color:var(--company-text)}.pagination select{margin-left:8px;padding:0 10px} +.company-detail .detail-status{margin-bottom:20px}.company-detail h3{font-size:16px;margin:18px 0 8px}.company-detail dl{display:grid;grid-template-columns:110px 1fr;margin:0}.company-detail dt,.company-detail dd{margin:0;padding:12px 0;border-bottom:1px solid #eee}.company-detail dt{color:var(--company-muted)} +.ant-drawer .ant-drawer-header{min-height:64px;border-bottom:1px solid var(--company-border)}.ant-drawer .ant-drawer-title{font-size:20px;font-weight:700}.ant-drawer .ant-drawer-body{padding:20px 24px} +.company-form-page{display:grid;grid-template-columns:220px minmax(0,860px);gap:32px;align-items:start;padding-top:20px} +.form-section-nav{position:sticky;top:76px;display:grid;grid-template-columns:32px 1fr;padding:4px 0;color:var(--company-text)}.form-section-nav>b{grid-row:1/5;width:24px;height:24px;border-radius:4px;background:var(--company-primary);color:#fff;display:grid;place-items:center}.form-section-nav>span{font-size:16px;font-weight:600;line-height:24px;margin-bottom:10px}.form-section-nav>a{grid-column:2;color:#555;text-decoration:none;padding:8px 0 8px 12px;border-left:1px solid #ddd}.form-section-nav>a:hover{color:var(--company-primary)} +.single-column-form{width:100%}.single-column-form .panel{margin:0}.single-column-form .panel-body{padding:20px 24px 32px}.single-column-form .form-grid{display:grid;grid-template-columns:1fr!important;gap:20px}.single-column-form .form-grid>*{grid-column:1!important}.single-column-form .field{max-width:680px;gap:8px}.single-column-form .field>span{font-size:14px}.single-column-form .field input,.single-column-form .field select{height:40px;border-radius:4px;padding:0 12px}.single-column-form .field textarea{min-height:112px;border-radius:4px;padding:10px 12px}.single-column-form .field textarea.small{min-height:88px}.single-column-form .field small{font-size:12px;color:var(--company-muted)} +.domain-select{display:grid;grid-template-columns:1fr auto;gap:12px;align-items:center}.domain-select .text-button{margin:0}.domain-add{max-width:520px}.domain-add .button{min-width:88px}.field-assist{display:flex;justify-content:flex-start;margin-top:4px}.field-assist .button{min-width:140px}.form-section-heading{max-width:680px;border-top:1px solid var(--company-border);padding-top:20px;margin-top:4px}.form-section-heading h3{font-size:16px;margin:0 0 4px}.form-section-heading p{margin:0;color:var(--company-muted);font-size:13px}.form-guidance{max-width:680px;padding:12px 14px;background:#f5f8fa;border-left:3px solid var(--company-primary);display:flex;flex-direction:column;gap:5px}.form-guidance span{color:#666;font-size:13px}.save-indicator{color:var(--company-muted);font-size:13px} +.upload{height:96px;border-color:#bfc7cd;background:#fafafa;border-radius:4px}.upload b{font-size:20px}.upload span{font-size:14px}.upload small{font-size:12px} +.ai-assist-intro{display:flex;gap:14px;padding:16px;background:#f3f8fb;border:1px solid #d9eaf3}.ai-assist-intro>.anticon{font-size:24px;color:var(--company-primary)}.ai-assist-intro h3{font-size:16px;margin:0 0 6px}.ai-assist-intro p{margin:0;color:#666;line-height:1.7}.drawer-actions{display:flex;gap:8px;margin:18px 0}.drawer-actions .button{min-width:120px} +.result-header{min-height:72px;border:0;border-bottom:1px solid var(--company-border);padding:12px 0;margin:0}.result-header h2{font-size:20px}.result-header .site-icon{border-radius:4px;background:var(--company-primary);color:#fff;border:0;font-size:16px}.result-actions{display:flex;gap:8px}.result-actions .button{min-width:112px} +.kpi-row{border-bottom:1px solid var(--company-border);gap:0;margin:0 0 16px}.kpi-row>div{height:72px;border:0;border-right:1px solid #eee;padding:12px 16px}.kpi-row>div:last-child{border-right:0}.kpi-row span{color:var(--company-muted)}.kpi-row b{font-size:22px} +.result-layout{grid-template-columns:1fr}.result-pages{display:grid;grid-template-columns:1fr;gap:0}.result-pages article{display:grid;grid-template-columns:300px 1fr;min-height:180px;border:0;border-bottom:1px solid var(--company-border);padding:16px 0}.result-pages article>div:last-child{padding:0 20px}.result-pages h3{font-size:16px;margin:14px 0 8px}.result-pages p{font-size:13px;line-height:1.6}.result-pages footer{gap:8px}.result-pages footer .button{min-width:96px;height:32px;margin:0}.dashboard-thumb-live{min-height:160px;border:1px solid var(--company-border);background:#f5f5f5} +.text-button{color:var(--company-primary);font-size:14px}.modal,.login-card{border-radius:4px}.modal{box-shadow:0 6px 24px #0002}.toast{top:68px;border-radius:4px;background:#323233} +.ai-fab{box-shadow:0 4px 12px #0003}.ai-dialog{border-radius:4px}.ai-dialog header{background:var(--company-primary)} +@media(max-width:1200px){.company-form-page{grid-template-columns:180px minmax(0,1fr);gap:20px}.single-column-form .field,.form-section-heading,.form-guidance{max-width:100%}} +@media(max-width:900px){.app-shell{padding-left:0}.side-nav{display:none}.workspace{min-width:0}.company-form-page{grid-template-columns:1fr}.form-section-nav{display:none}.content{padding:0 16px 20px}.page-heading{padding:10px 16px}.company-status-tabs{overflow-x:auto}.company-status-tabs button{min-width:100px}.company-filterbar{align-items:stretch;flex-wrap:wrap}.filter-actions{margin-left:0;width:100%}.result-pages article{grid-template-columns:1fr}.sticky-actions{left:0}} + +/* Company navigation IA: global menu contains business modules only. */ +.side-nav{display:none!important} +.app-shell{padding-left:200px} +.company-side-nav{position:fixed;z-index:35;top:56px;bottom:0;left:0;width:200px;background:#f0f0f0;border-right:1px solid #ddd;display:flex;flex-direction:column;color:#323233;font-family:"Noto Sans SC","Microsoft YaHei",sans-serif} +.side-menu{padding:0;overflow:auto;flex:1}.menu-section{margin:0}.menu-section button{width:100%;border:0;background:transparent;color:inherit;text-align:left;font-family:inherit;font-weight:500} +.menu-section-title{height:56px;padding:0 20px;display:flex;align-items:center;justify-content:space-between;font-size:19px;line-height:56px} +.menu-section-title>span{display:block}.menu-section-title>.anticon{font-size:11px;color:#323233} +.menu-section-title:hover{background:#e5e5e5}.menu-section.current>.menu-section-title{color:#323233;font-weight:500} +.menu-section-items button{height:50px;padding:0 20px 0 40px;display:flex;align-items:center;font-size:17px;line-height:50px;color:#323233} +.menu-section-items button:hover{background:#dcecf5;color:var(--company-primary)} +.menu-section-items button.active{background:#c3e4f6;color:var(--company-primary);font-weight:500} +.company-side-footer{position:relative;margin:auto 16px 16px;padding:14px 4px 0;border-top:1px solid #ddd;display:flex;flex-direction:column;gap:8px;font-size:11px} +.company-side-footer>b{color:#515a6e}.company-side-footer>button{height:26px;padding:0;border:0;background:transparent;display:flex;align-items:center;justify-content:space-between;color:#89898a;text-align:left} +.company-model-menu{position:absolute;left:0;right:0;bottom:30px;padding:6px;background:#fff;border:1px solid #d9d9d9;box-shadow:0 4px 12px #00000018} +.company-model-menu button{width:100%;height:32px;border:0;background:#fff;display:flex;justify-content:space-between;align-items:center;padding:0 8px}.company-model-menu button:hover{background:#f5f5f5}.company-model-menu span{color:var(--company-primary);font-size:11px} +.sticky-actions{left:200px} +@media(max-width:720px){.company-side-nav{display:none}.app-shell{padding-left:0}.sticky-actions{left:0}} + +/* Resource center list pages */ +.resource-list-panel>.panel-body{padding:0 16px 12px;overflow-x:auto} +.resource-table{min-width:980px;table-layout:auto}.resource-table th,.resource-table td{vertical-align:middle}.resource-table th:last-child,.resource-table td:last-child{width:64px;text-align:center} +.resource-table th:nth-child(3),.resource-table td:nth-child(3){width:76px}.resource-table th:nth-child(4),.resource-table td:nth-child(4),.resource-table th:nth-child(5),.resource-table td:nth-child(5),.resource-table th:nth-child(6),.resource-table td:nth-child(6){width:70px;text-align:center} +.resource-table th:nth-child(7),.resource-table td:nth-child(7){width:96px}.resource-table th:nth-child(8),.resource-table td:nth-child(8){width:180px} +.domain-resource-table th:first-child,.domain-resource-table td:first-child{width:260px;min-width:260px;text-align:left}.domain-resource-table .company-record-link{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left} +.resource-detail>p{margin:8px 0;color:#666;line-height:1.8;white-space:pre-wrap}.resource-detail-image{display:block;width:100%;max-height:220px;object-fit:contain;border:1px solid var(--company-border);background:#f5f5f5;margin-bottom:20px} +.device-resource-table{min-width:760px}.device-resource-table th:nth-child(1),.device-resource-table td:nth-child(1){width:280px;min-width:280px;text-align:left}.device-resource-table th:nth-child(2),.device-resource-table td:nth-child(2){width:auto}.device-resource-table th:nth-child(3),.device-resource-table td:nth-child(3){width:90px;text-align:center}.device-resource-table th:nth-child(4),.device-resource-table td:nth-child(4){width:110px}.device-resource-table th:nth-child(5),.device-resource-table td:nth-child(5){width:64px}.device-resource-table .company-record-link{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left} +.resource-name-cell{display:flex;align-items:center;gap:12px;min-width:0}.resource-name-cell>img,.resource-placeholder{flex:none;width:48px;height:36px;border:1px solid var(--company-border);background:#f5f5f5;object-fit:cover;border-radius:2px}.resource-placeholder{display:grid;place-items:center;color:#999}.resource-name-cell .company-record-link{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left} +.resource-point-list{border-top:1px solid #eee}.resource-point-list>div{height:40px;border-bottom:1px solid #eee;display:flex;align-items:center;justify-content:space-between}.resource-point-list span{color:var(--company-muted)}.resource-point-list>p{color:var(--company-muted)} +.resource-back{height:44px;display:flex;align-items:center;border-bottom:1px solid var(--company-border);margin:0 0 16px}.resource-back .text-button{margin:0} +.style-resource-panel{margin-top:16px}.style-resource-table{min-width:820px}.style-resource-table th:nth-child(1),.style-resource-table td:nth-child(1){width:260px;min-width:260px;text-align:left}.style-resource-table th:nth-child(2),.style-resource-table td:nth-child(2){width:140px}.style-resource-table th:nth-child(3),.style-resource-table td:nth-child(3){width:190px}.style-resource-table th:nth-child(4),.style-resource-table td:nth-child(4){width:130px}.style-resource-table th:nth-child(5),.style-resource-table td:nth-child(5){width:64px;text-align:center}.style-resource-table .company-record-link{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}.style-list-thumbnail{display:block;width:96px;height:54px;object-fit:cover;border:1px solid var(--company-border);background:#f5f5f5;border-radius:2px} +.style-drawer>img{display:block;width:100%;max-height:440px;object-fit:contain;background:#f5f5f5;border:1px solid var(--company-border)}.style-drawer dl{display:grid;grid-template-columns:110px 1fr;margin-top:20px}.style-drawer dt,.style-drawer dd{margin:0;padding:12px 0;border-bottom:1px solid #eee}.style-drawer dt{color:var(--company-muted)} /* 生成过程只读终端 */ .gen-right{display:flex;flex-direction:column;gap:14px} .console-panel .panel-body{padding:0} @@ -299,6 +414,8 @@ .domain-manage-row .muted{font-size:11px} .text-button.danger{color:#d92d20} .text-button.danger:hover{text-decoration:underline} +.domain-create-dialog h2{margin:0 0 6px;font-size:20px}.domain-create-dialog p{margin:0 0 16px;color:var(--company-muted)}.domain-create-dialog .dlg-actions{margin-top:20px}.domain-create-dialog .button{min-width:96px;margin:0} +.system-settings-page{max-width:920px;padding-top:16px}.system-settings-page>.panel{margin:0}.system-settings-page .panel-head{height:52px}.settings-form{max-width:680px;display:flex;flex-direction:column;gap:20px;padding:8px 8px 16px}.settings-form .field{gap:8px}.settings-form .field>span{font-size:14px}.settings-form input,.settings-form select{width:100%;height:40px;border:1px solid var(--company-border);border-radius:4px;padding:0 12px;background:#fff}.settings-form input[readonly]{background:#f5f5f5;color:#666}.settings-note{padding:14px 16px;background:#f5f8fa;border-left:3px solid var(--company-primary);display:flex;flex-direction:column;gap:4px}.settings-note span{color:#666}.settings-actions{display:flex;justify-content:flex-start}.settings-actions .button{margin:0}.settings-table-wrap{overflow-x:auto}.settings-domain-table{table-layout:auto;min-width:680px}.settings-domain-table th:first-child,.settings-domain-table td:first-child{width:260px;min-width:260px;text-align:left}.settings-domain-table th:nth-child(2),.settings-domain-table td:nth-child(2){width:auto}.settings-domain-table th:nth-child(3),.settings-domain-table td:nth-child(3){width:130px}.settings-domain-table th:nth-child(4),.settings-domain-table td:nth-child(4){width:100px;text-align:center}.settings-domain-table .text-button{margin:0} /* 领域方案库 · 左侧导航栏「领域分类」行内编辑(删除× / 新增+) */ .domain-nav-row{display:flex;align-items:stretch;flex-wrap:nowrap} @@ -369,3 +486,26 @@ /* 全局页脚 · 备案信息 */ .site-footer{margin-left:0;padding:14px 20px;text-align:center;color:#9aa0a6;font-size:12px;background:#fff;border-top:1px solid var(--line)} + +/* WISE-IoT Desk header — aligned to the reference application's native header. */ +.topbar.wise-header{height:56px;padding:0 8px 0 12px;gap:0;border-top:4px solid #008cd6;border-bottom:1px solid #c8c8c8;background:#fff;box-shadow:none;overflow:hidden;color:#787878} +.wise-header button{border:0;background:transparent;padding:0;box-shadow:none} +.wise-menu-button{flex:0 0 24px;width:24px;height:24px;margin-right:12px;color:#787878;display:grid;place-items:center} +.wise-menu-button img{display:block;width:24px;height:24px} +.wise-logo-link{flex:0 0 123px;width:123px;height:40px;display:block} +.wise-logo-link img{display:block;width:123px;height:40px} +.wise-product-switch{flex:0 0 auto;height:48px;margin-left:12px;display:flex;align-items:center;color:#787878;white-space:nowrap} +.wise-product-switch>img:first-child{width:24px;height:24px;margin-right:8px} +.wise-product-switch>span,.wise-product-switch>strong{font-family:Roboto,"Noto Sans SC","Microsoft YaHei",Arial,sans-serif;font-size:16px;line-height:24px;font-weight:700;color:#787878} +.wise-product-switch>strong{margin-left:6px} +.wise-product-switch>img:last-child{width:24px;height:24px} +.wise-header-actions{height:48px;margin-left:auto;display:flex;align-items:center;justify-content:flex-end;flex:0 0 auto} +.wise-header-actions>button:not(.wise-avatar){flex:0 0 40px;width:40px;height:40px;display:grid;place-items:center;border-radius:0} +.wise-header-actions>button:not(.wise-avatar):hover{background:#f5f5f5} +.wise-header-actions>button>img{display:block;width:24px;height:24px} +.wise-avatar{flex:0 0 24px;width:24px;height:24px;margin:0 8px;border-radius:50%!important;background:#dff3fc!important;color:#008cd6!important;font-size:12px;line-height:24px;text-align:center} +.wise-powered-divider{flex:0 0 1px;width:1px;height:24px;background:#dedede;margin:0 4px 0 5px} +.wise-powered-logo{display:block;flex:0 0 122px;width:122px;height:48px;object-fit:contain} +@media(max-width:1120px){.wise-powered-divider,.wise-powered-logo{display:none}.wise-avatar{margin-right:8px}} +@media(max-width:700px){.wise-header-secondary{display:none!important}.wise-product-switch>strong{display:none}.wise-header-actions>button:not(.wise-avatar){width:36px;flex-basis:36px}} +@media(max-width:560px){.wise-logo-link{display:none}.wise-product-switch{margin-left:4px}.wise-header-actions>button:nth-child(1),.wise-header-actions>button:nth-child(2){display:none}} diff --git a/app/layout.tsx b/app/layout.tsx index 8eb8893..62e24cd 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,4 +1,7 @@ import type { Metadata } from "next"; +import "antd/dist/reset.css"; import "./globals.css"; -export const metadata: Metadata = {title:"iStation StationBuilder UI 原型",description:"iStation 团队内部 AI 站点与领域方案生成工具",other:{"codex-preview":"development"},icons:{icon:"/favicon.svg",shortcut:"/favicon.svg"}}; -export default function RootLayout({children}:Readonly<{children:React.ReactNode}>){return {children}} +import CompanyTheme from "./company-theme"; + +export const metadata: Metadata = {title:"iStation StationBuilder",description:"iStation 团队内部 AI 站点与领域方案生成工具",other:{"codex-preview":"development"},icons:{icon:"/favicon.svg",shortcut:"/favicon.svg"}}; +export default function RootLayout({children}:Readonly<{children:React.ReactNode}>){return {children}} diff --git a/app/page.tsx b/app/page.tsx index e1636e1..aa509bf 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,8 +1,10 @@ "use client"; import { useEffect, useMemo, useRef, useState, type PointerEvent as ReactPointerEvent } from "react"; +import { Button as AntButton, Drawer, Dropdown, Tag } from "antd"; +import { CaretDownOutlined, CaretRightOutlined, DeleteOutlined, DownloadOutlined, EditOutlined, EllipsisOutlined, EyeOutlined, RobotOutlined } from "@ant-design/icons"; -type PageKey = "login" | "projects" | "basic" | "assets" | "functions" | "generate" | "result" | "preview" | "domains" | "domainDetail" | "styleLibrary" | "deviceTemplates"; +type PageKey = "login" | "projects" | "basic" | "assets" | "functions" | "generate" | "result" | "preview" | "domains" | "domainDetail" | "styleLibrary" | "deviceTemplates" | "toolSettings" | "domainSettings"; type ModalKey = "" | "feedback" | "download" | "history" | "prompt" | "completePrompt"; const meta: Record = { @@ -14,10 +16,12 @@ const meta: Record = generate:{label:"生成确认",icon:"↻",subtitle:"确认生成范围,调用 Cube Skill 生成页面和模拟数据"}, result:{label:"站点结果",icon:"◫",subtitle:"集中查看、预览、校验并下载站点交付物"}, preview:{label:"Cube 页面预览",icon:"▣",subtitle:"检查单个 Cube 页面效果,并通过反馈快速重新生成"}, - domains:{label:"领域方案库",icon:"◆",subtitle:"沉淀各领域标准方案,供客户站点直接使用或快速微调"}, + domains:{label:"领域方案",icon:"◆",subtitle:"管理可复用的领域标准方案及其版本与配置内容"}, domainDetail:{label:"方案详情",icon:"◇",subtitle:"查看领域资产、运维流程、Cube 页面、菜单及知识内容"}, - deviceTemplates:{label:"设备模版",icon:"⬡",subtitle:"独立管理设备模版与采集点,供领域方案选用"}, - styleLibrary:{label:"页面样式库",icon:"❖",subtitle:"沉淀可复用的 Cube 仪表盘页面样式(配色与视觉主题),供站点页面快速套用"}, + deviceTemplates:{label:"设备模板",icon:"⬡",subtitle:"管理设备模板及采集点配置,供领域方案和站点复用"}, + styleLibrary:{label:"页面样式",icon:"❖",subtitle:"管理可复用的 Cube 页面视觉样式与预览资源"}, + toolSettings:{label:"内部工具配置",icon:"",subtitle:"配置 StationBuilder 使用的内部能力、AI 服务和默认模型"}, + domainSettings:{label:"领域配置",icon:"",subtitle:"维护领域分类,供领域方案与站点项目统一使用"}, }; @@ -53,8 +57,8 @@ type DomainPage = { id:number; name:string; description:string; json_text:string type DomainStepKey = "basic" | "assets" | "functions" | "opsflow" | "knowledge" | "generate" | "expert"; // ---- helpers ---- -function Status({children}:{children:React.ReactNode}){const v=String(children);const tone=/完成|正常|成功|通过|推荐|已生成|已上传|generated/.test(v)?"ok":/生成中|校验中|告警|running/.test(v)?"warn":/失败|异常|failed/.test(v)?"bad":"muted";return {children}} -function Button({children,secondary,onClick,disabled,title}:{children:React.ReactNode;secondary?:boolean;onClick?:()=>void;disabled?:boolean;title?:string}){return } +function Status({children}:{children:React.ReactNode}){const v=String(children);const tone=/不可用|失败|异常|failed/.test(v)?"bad":/完成|正常|成功|通过|推荐|可用|已配置|已生成|已上传|generated/.test(v)?"ok":/生成中|校验中|告警|running/.test(v)?"warn":"muted";return {children}} +function Button({children,secondary,onClick,disabled,title}:{children:React.ReactNode;secondary?:boolean;onClick?:()=>void;disabled?:boolean;title?:string}){return {children}} function Panel({title,tools,children,className=""}:{title?:string;tools?:React.ReactNode;children:React.ReactNode;className?:string}){return
{title&&
{title}
{tools}
}
{children}
} function Field({label,required,hint,children}:{label:string;required?:boolean;hint?:string;children:React.ReactNode}){return } @@ -115,6 +119,9 @@ function countChars(v:string){ return Array.from(v||"").length; } // ---- main ---- export default function Home(){ const [page,setPage]=useState("projects"); + const [projectNavOpen,setProjectNavOpen]=useState(true); + const [resourceNavOpen,setResourceNavOpen]=useState(true); + const [systemNavOpen,setSystemNavOpen]=useState(true); const [toast,setToast]=useState(""); const [modal,setModal]=useState(""); const [promptData,setPromptData]=useState<{name:string;text:string}|null>(null); @@ -489,8 +496,53 @@ export default function Home(){ if(page==="login")return go("projects")}/>; + const projectArea = (["projects","basic","assets","functions","generate","result","preview"] as PageKey[]).includes(page); + const resourceArea = (["domains","domainDetail","deviceTemplates","styleLibrary"] as PageKey[]).includes(page); + const systemArea = (["toolSettings","domainSettings"] as PageKey[]).includes(page); + return
-
⬡
iStationiStation TeamStationBuilder⌄
设计方案wang.yu@advantech.com
iStation Team
+
+ + event.preventDefault()}>WISE-IoT + + +
+ -
iStation StationBuilder / {["domains","domainDetail","styleLibrary","deviceTemplates"].includes(page)?meta[page].label:page==="projects"?"站点列表":`站点详情 / ${meta[page].label}`}

{meta[page].label}

{meta[page].subtitle}

{!["projects","domains","domainDetail","styleLibrary","deviceTemplates"].includes(page)&&}{page==="projects"&&}{page==="domains"&&}{page==="domains"&&openDomainTemplateId!=null&&}{domainFilter!=null&&page==="domains"&&}
{(["basic","assets","functions","generate"] as PageKey[]).includes(page)&&}
+
iStation StationBuilder / {["domains","domainDetail","styleLibrary","deviceTemplates","toolSettings","domainSettings"].includes(page)?meta[page].label:page==="projects"?"站点列表":`站点详情 / ${meta[page].label}`}

{meta[page].label}

{meta[page].subtitle}

{page==="projects"&&}{page==="domains"&&openDomainTemplateId==null&&}{page==="domains"&&openDomainTemplateId!=null&&}{domainFilter!=null&&page==="domains"&&openDomainTemplateId==null&&}{page==="domainSettings"&&}
{(["basic","assets","functions","generate"] as PageKey[]).includes(page)&&}
{page==="projects"&&} {(page==="domains"||page==="domainDetail")&&(openDomainTemplateId!=null ? setOpenDomainTemplateId(null)} onCreated={setOpenDomainTemplateId} viewCompletePrompt={viewDtCompletePrompt} viewCompleteAssetsPrompt={viewDtCompleteAssetsPrompt} viewCompleteCubesPrompt={viewDtCompleteCubesPrompt} pageStyles={pageStyles}/> - : setOpenDomainTemplateId(id)} domainFilter={domainFilter} domainOptions={domainOptions}/>)} - {page==="styleLibrary"&&} - {page==="deviceTemplates"&&} + : setOpenDomainTemplateId(id)} domainFilter={domainFilter} domainOptions={domainOptions}/>)} + {page==="styleLibrary"&&} + {page==="deviceTemplates"&&} + {page==="toolSettings"&&} + {page==="domainSettings"&&} {page==="basic"&&} {page==="assets"&&} {page==="functions"&&} @@ -543,11 +597,12 @@ export default function Home(){ {page==="preview"&&}
{(["basic","assets","functions"] as PageKey[]).includes(page)&&
}
陕ICP备2026022239号-2
+ {addingDomain&&page==="domainSettings"&&
{setAddingDomain(false);setNewDomainName("");}}>
e.stopPropagation()}>

新增领域

创建后可在领域方案和站点基本信息中直接选择。

setNewDomainName(e.target.value)} onKeyDown={async e=>{if(e.key==="Enter")await doAddDomain();if(e.key==="Escape"){setAddingDomain(false);setNewDomainName("");}}}/>
} {toast&&
✓{toast}
}{modal&&setModal("")} notify={notify} promptData={promptData} feedbackCube={feedbackCube} refineCube={refineCube} refining={refining} refineLog={refineLog}/>} {!aiOpen&&} {aiOpen&&
✦
StationBuilder AI模型 · {model}
@@ -577,19 +632,51 @@ function Projects({view,setView,sites,loading,go,loadSite,notify,onDelete}:{view return {all,draft,running,done,partial}; },[sites]); const [selected,setSelected] = useState>(new Set()); + const [query,setQuery] = useState(""); + const [domain,setDomain] = useState(""); + const [statusFilter,setStatusFilter] = useState(""); + const [preview,setPreview] = useState(null); const {confirm, el:confirmEl}=useConfirm(); const toggle = (id:number)=> setSelected(prev=>{ const n=new Set(prev); if(n.has(id)) n.delete(id); else n.add(id); return n; }); - const allSelected = sites.length>0 && selected.size===sites.length; - const toggleAll = ()=> setSelected(allSelected ? new Set() : new Set(sites.map(s=>s.id))); + const domains = useMemo(()=>Array.from(new Set(sites.map(s=>s.domain).filter(Boolean))),[sites]); + const filtered = useMemo(()=>sites.filter(s=>(!query||s.name.toLowerCase().includes(query.toLowerCase()))&&(!domain||s.domain===domain)&&(!statusFilter||s.status===statusFilter)),[sites,query,domain,statusFilter]); + const allSelected = filtered.length>0 && filtered.every(s=>selected.has(s.id)); + const toggleAll = ()=> setSelected(allSelected ? new Set() : new Set(filtered.map(s=>s.id))); const batchDelete = async ()=>{ await onDelete([...selected]); setSelected(new Set()); }; + const statusText=(status:string)=>status==="generated"?"生成完成":status==="partial"?"部分失败":status==="running"?"生成中":"草稿"; + const openProject=(row:SiteSummary)=>go(row.status==="generated"?"result":"basic",row.id); if(loading) return
加载中…
; - return <>{confirmEl}
{[[String(counts.all),"全部项目"],[String(counts.draft),"草稿"],[String(counts.running),"生成中"],[String(counts.done),"生成完成"],[String(counts.partial),"部分失败"]].map(x=>
{x[0]}{x[1]}
)}
{selected.size>0 && }
{view==="table"?{sites.map(row=>)}
站点名称 ↕所属领域设备数量MU 页面Cube 页面状态最近修改操作
toggle(row.id)}/>{row.domain}{row.device_count}{row.mu_count}{row.cube_count}{row.status==="generated"?"生成完成":row.status==="partial"?"部分失败":row.status==="running"?"生成中":"草稿"}{row.updated_at}
:
{sites.map(row=>
go(row.status==="generated"?"result":"basic", row.id)}>
⬡{row.status==="generated"?"生成完成":row.status==="partial"?"部分失败":row.status==="running"?"生成中":"草稿"}

{row.name || "未命名站点"}

{row.domain}

{row.device_count} 设备{row.mu_count} MU{row.cube_count} Cube
更新于 {row.updated_at}
)}
}
共 {sites.length} 条
+ return <>{confirmEl} +
+ {[ + {key:"",label:"全部",count:counts.all}, + {key:"draft",label:"草稿",count:counts.draft}, + {key:"running",label:"生成中",count:counts.running}, + {key:"generated",label:"生成完成",count:counts.done}, + {key:"partial",label:"部分失败",count:counts.partial}, + ].map(item=>)} +
+ +
+ + +
{selected.size>0&&}
+
+ {filtered.map(row=>setPreview(row)}>)}
站点名称所属领域设备数量MU 页面Cube 页面状态最近修改操作
e.stopPropagation()}>toggle(row.id)}/>{row.domain||"—"}{row.device_count}{row.mu_count}{row.cube_count}{statusText(row.status)}{row.updated_at}e.stopPropagation()}>},{key:"delete",label:"删除",danger:true,icon:}],onClick:({key})=>key==="open"?openProject(row):onDelete([row.id])}}>}/>
+ {filtered.length===0&&
没有符合条件的站点
} +
{filtered.length} / 总数
+
+ setPreview(null)} extra={preview&&}> + {preview&&
{statusText(preview.status)}

基础信息

所属领域
{preview.domain||"—"}
设备数量
{preview.device_count}
MU 页面
{preview.mu_count}
Cube 页面
{preview.cube_count}
生成文件
{preview.gen_count}
最近修改
{preview.updated_at}
创建时间
{preview.created_at}
} +
+ } function Basic({site,domains,onChange,onAddDomain,onUpload,saving,notify,onViewCompletePrompt}:{site:SiteDetail|null;domains:string[];onChange:(p:Partial)=>void;onAddDomain:(n:string)=>void;onUpload:(f:File)=>void;saving:boolean;notify:(m:string)=>void;onViewCompletePrompt:()=>void}){ const [newDomain,setNewDomain]=useState(""); const [domainInput,setDomainInput]=useState(site?.domain||""); const [showAdd,setShowAdd]=useState(false); + const [aiOpen,setAiOpen]=useState(false); useEffect(()=>{ if(site) setDomainInput(site.domain||""); },[site?.domain]); const [completing,setCompleting]=useState(false); const [completeLog,setCompleteLog]=useState(""); @@ -623,41 +710,36 @@ function Basic({site,domains,onChange,onAddDomain,onUpload,saving,notify,onViewC if(!site) return
加载中…
; const nameTooLong = countChars(site.name)>20; const domainTooLong = newDomain.length>6; - return
+ return <>
onChange({name:e.target.value})} className={nameTooLong?"invalid":""}/>
{setDomainInput(e.target.value); onChange({domain:e.target.value});}} placeholder="选择或输入新领域"/> {domains.map(d=> - +
{showAdd&&
setNewDomain(e.target.value)}/>
}
-