--- /dev/null
+# StationBuilder UI 校验说明
+
+校验依据:用户提供的 `skills.zip` 中 `web-ui-builder` v1.3.0。直接修改现有 `StationBuilder-full-20260915` 工程,不另建静态设计页面;业务接口、数据库和已确认的建站流程不变。
+
+## 已修正
+
+- 将组件库统一至 Ant Design 6.6.4,图标统一至 @ant-design/icons 6.3.4,同步 npm 和 pnpm 锁文件。
+- `app/theme.json` 与附件的品牌配置逐项一致:主色 #008cd6,正文基准 16px,控件圆角 4px,字体、状态色和各组件 token 均取自该配置。
+- 移除旧 `--company-*`、`--brand-*` 别名,业务样式使用官方 `--ant-*` 变量;统一间距、字号与字重,减少旧样式覆盖新组件的冲突。
+- 可见按钮、输入框、复选框、单选框、下拉框及数据表格统一使用 antd。文件选择器保留浏览器原生能力;树状画布、模拟场景和仪表板内容不当作普通表单控件改写。
+- 面板和字段改为 Card / Form.Item;弹窗改为 Modal,保留原关闭与确认回调、内嵌确认弹窗及编辑内容。抽屉适配 v6 的 size / destroyOnHidden。
+- 状态筛选和节点编辑等 Tab 的业务内容归属 `items[].children`,避免只有标签条而没有 Tab 内容的实现。
+- 常见操作分组保持单行,最多三个可见操作;多余操作放入“更多”,同组至多一个主按钮。
+- 面板工具栏和仪表板“导入 / 新增”也接入统一操作分组。隐藏文件选择器、搜索框等辅助内容保持挂载,不会因“更多”收纳而失效;主按钮识别不依赖生产构建后的函数名称。
+- 消息提示使用 `App.useApp()` 的主题化实例;接入附件的 Select 渐变、浮层和表头设计覆盖。
+- 为菜单、缩略图和画布工具按钮增加尺寸兼容层,保留响应式规则;不在兼容层建立另一套字体和配色。将业务样式统一在根布局加载,避免客户端晚加载的旧样式覆盖品牌规则。
+
+## 保留的特殊内容
+
+用户此前明确要求复刻的 WISE-IoT Header、原站图标与无图标主菜单结构保留,不换成附件默认的另一套产品框架。数字孪生示意画面、仪表板图表配色、客户应用色系、运行日志、代码展示及导出仪表板 HTML 属于内容或用户配置数据,不全部替换为普通 UI 状态色。
+
+## 校验结果与限制
+
+- TypeScript UI 类型检查通过。
+- 23 项 UI 适配及既有业务回归测试通过,覆盖单选筛选、表格行/单元格点击、Tab 内容归属、弹窗关闭和操作分组,以及模板、资产、菜单数据逻辑和按钮尺寸兼容规则。
+- 品牌配置完整指纹校验通过,非仅校验少数主色值。
+- 当前预览服务已恢复,可访问站点管理、领域方案和设备模板路由。只做开发预览和类型/单元测试,未自动生成正式发布包。
+- **视觉验收尚未完成**:浏览器连接工具持续报告 `Unable to load browser request-header policy`,无法读取截图、计算样式或逐项实际点击验证。因此目前不能宣称全部页面已严格符合,也不能宣称 1920×1080 下所有布局已经验收。
+
+## 本地预览与复核
+
+预览地址:http://127.0.0.1:37931/t/stationbuilder/?page=projects
+
+建议视觉复核:站点层级与新增、五步建站、手动资产树与三个节点 Tab、设备模板和报警规则抽屉、领域能力四区块、仪表板列表、菜单配置。重点检查 1920×1080 下的标题、控件高度、文本截断、操作分组和浮层显示。
+
+可重复执行的检查:
+
+```sh
+node node_modules/typescript/bin/tsc -p tsconfig.ui.json --noEmit
+node --test tests/ui-system.test.mjs tests/site-menu-model.test.mjs tests/site-workspace.test.mjs
+```
--- /dev/null
+# StationBuilder UI 调整说明(web-ui-builder 1.3.0)
+
+## 本次实际修改
+
+- 在现有工程中接入新安装 Skill 的原始品牌资产,不另做独立设计页面。业务入口仍是 app/page.tsx。
+- src/theme.json、src/antd.less、src/theme/overrides.css 保持与 Skill 一致;清除昨天重复的主题与覆盖副本,避免两套品牌配置分叉。副本可从 work/ui-style-backup-20260917/app 恢复。
+- 修正官方 CSS token 的序列化:lineHeight 等无单位值不再错误输出 px,字体、高度、内边距等真实长度仍输出 px。
+- 外层主菜单采用 Skill SideMenu,保留现有名称、顺序和无图标要求。支持分组展开/收起及 Header 按钮折叠菜单,宽度由组件决定,内容通过 flex 自然伸缩。
+- 保留已确认的 WISE-IoT Header 文字、原站图标与结构,不替换成 Skill 默认的另一套 Header。
+- 站点、领域方案、设备模板、页面样式及领域仪表板管理等搜索区域改用 Skill SearchInput;分页列表改用 Skill Pagination,支持每页 10/20/50/100 条。
+- 统一表格的字号、行高、表头与表体间距、悬停色;统一卡片标题、搜索工具栏与底部按钮栏。按钮仍保持最多三个、单排、一个主操作,其他操作保留在“更多”中。
+- 领域方案四区块在宽屏上保持四列,分配剩余视口高度,内容内部滚动;窄屏降为两列或一列。
+- 业务逻辑不作重新设计:保留模板创建站点、站点层级、五步配置、资产树、节点三个 Tab、设备模板直接编辑、报警规则抽屉及第四步至第五步的菜单生成规则。
+- 新增 Less 与 SVG 转 React 插件,保留原有 React/antd/Vite 版本;生产和本地 Vite 配置均支持品牌 SVG 图标。
+
+## 验证范围
+
+- UI TypeScript 检查通过;26 项品牌与业务回归测试通过。
+- Skill 环境检查通过;四个页面地址及业务组件、品牌 CSS/图标模块加载检查通过(HTTP 200),本地开发预览已恢复。HTTP 检查不等同于浏览器视觉验收。
+- 品牌三个原始文件指纹校验通过,新增测试防止无单位行高再次被写成 px。
+- 完整部署工程类型检查仍存在归档中缺失 Cloudflare 类型和 .openai/hosting.json 等问题,本次未补造部署环境,也不把它等同于 UI 类型检查通过。
+- 浏览器连接持续返回 Unable to load browser request-header policy,无法逐页读取截图并点击。因此视觉验收、1920×1080 实际截图验收及抽屉真实点击验收尚未完成,不能宣称全页面像素级符合。
+- 本次不生成正式发布包,不改数据库内容。
+- 本地预览直接挂载 app/page.tsx 的客户端入口,绕过归档工程 RSC 开发渲染超时;数据接口仍代理到原有 sidecar。正式 vite.config.ts 的 vinext/部署逻辑保留,并非重新做一套设计页面。
+
+## 可重复检查
+
+```sh
+node node_modules/typescript/bin/tsc -p tsconfig.ui.json --noEmit
+node --test tests/brand-token-css.test.mjs tests/ui-system.test.mjs tests/site-menu-model.test.mjs tests/site-workspace.test.mjs
+```
+
+本地开发预览:http://127.0.0.1:37931/t/stationbuilder/?page=projects
+
+请优先复核:站点管理列表、领域方案四区块、设备模板编辑及报警规则抽屉、五步配置中的页面选择和菜单配置。
--- /dev/null
+/* StationBuilder business layout on top of the Skill's unmodified brand files.
+ Keep the approved WISE-IoT header artwork and the existing configuration flow. */
+.sb-brand-shell.app-shell { padding-left:0; }
+.sb-brand-shell>.body { min-width:0; }
+.sb-brand-shell .sb-brand-nav { position:sticky; top:56px; align-self:flex-start; height:calc(100dvh - 56px); display:flex; flex-direction:column; background:var(--ant-color-border); overflow:auto; }
+.sb-brand-nav>nav { flex:1; min-height:0; }
+.sb-brand-nav>nav>.ant-menu { height:auto; min-height:100%; font-family:var(--ant-font-family); font-size:var(--ant-font-size); }
+.sb-brand-nav .ant-menu-item,.sb-brand-nav .ant-menu-submenu-title { font-weight:400; }
+.sb-brand-nav>.design-doc-link { position:static; margin:var(--ant-padding-sm) var(--ant-padding); height:var(--ant-control-height); flex-shrink:0; border-radius:var(--ant-border-radius); background:var(--ant-color-primary); color:var(--ant-color-text-light-solid); }
+.sb-brand-shell .workspace.main { display:flex; flex-direction:column; padding:0; min-height:calc(100dvh - 56px); }
+.sb-brand-shell .workspace>.page-heading { padding:var(--ant-padding) var(--ant-padding-lg); min-height:80px; background:var(--ant-color-bg-layout); gap:var(--ant-padding); }
+.sb-brand-shell .page-heading h1 { font-size:var(--ant-font-size-heading-4); line-height:var(--ant-line-height-heading-4); font-weight:var(--ant-font-weight-strong); }
+.sb-brand-shell .page-heading p { font-size:var(--ant-font-size-sm); line-height:var(--ant-line-height-sm); margin-top:var(--ant-padding-xxs); color:var(--ant-color-text-secondary); }
+.sb-brand-shell .page-heading>div:last-child { display:flex; flex-wrap:nowrap; align-items:center; gap:var(--ant-padding-xs); }
+.sb-brand-shell .workspace>.content { flex:1; min-width:0; padding:var(--ant-padding) var(--ant-padding-lg) var(--ant-padding-lg); }
+.sb-brand-shell .workspace>.sticky-actions { position:sticky; inset:auto; bottom:0; width:100%; min-height:64px; height:auto; margin:0; padding:var(--ant-padding) var(--ant-padding-lg); border-top:1px solid var(--ant-color-border); background:var(--ant-color-bg-container); z-index:20; }
+.sb-brand-shell .station-config-footer,.sb-brand-shell .sticky-actions>div { flex-wrap:nowrap; gap:var(--ant-padding-xs); }
+.sb-brand-shell .filterbar.company-filterbar,.sb-brand-shell .station-list-toolbar { display:flex; align-items:center; gap:var(--ant-padding-xs); padding-block:var(--ant-padding); min-height:64px; height:auto; }
+.sb-brand-shell .station-list-toolbar>div:last-child,.sb-brand-shell .filter-actions { margin-left:auto; display:flex; align-items:center; flex-wrap:nowrap; gap:var(--ant-padding-xs); }
+.sb-brand-shell .search-input.ant-input-affix-wrapper { flex-shrink:0; max-width:100%; height:var(--ant-control-height); }
+.sb-brand-shell .page-footer>.pagination { height:auto; min-height:0; padding:0; margin:0; border:0; background:transparent; }
+.sb-brand-shell .page-footer .page-size { min-width:128px; }
+.sb-brand-shell .station-ui-table .ant-table-cell { height:auto; line-height:var(--ant-line-height); padding-inline:var(--ant-padding); }
+.sb-brand-shell .station-ui-table .ant-table-thead>tr>th { padding-block:var(--ant-padding-sm); text-align:left; border-bottom:2px solid var(--ant-color-primary); }
+.sb-brand-shell .station-ui-table .ant-table-tbody>tr>td { padding-block:var(--ant-table-cell-padding-block); }
+.sb-brand-shell .panel.ant-card>.ant-card-head.panel-head { display:block; min-height:48px; padding:0 var(--ant-padding-lg); }
+.sb-brand-shell .panel.ant-card .ant-card-head-wrapper { gap:var(--ant-padding); }
+.sb-brand-shell .panel.ant-card .ant-card-head-title { font-size:var(--ant-font-size); font-weight:var(--ant-font-weight-strong); }
+.sb-brand-shell .panel.ant-card>.ant-card-body { padding:var(--ant-padding-lg); }
+.sb-brand-shell .company-list-panel.ant-card>.ant-card-body { padding:0 var(--ant-padding) var(--ant-padding-sm); }
+.sb-brand-shell .station-ui-tabs>.ant-tabs-nav { margin:0; background:var(--ant-color-fill-quaternary); }
+.sb-brand-shell .station-ui-tabs>.ant-tabs-nav .ant-tabs-tab { padding:var(--ant-padding-sm) var(--ant-padding-lg); }
+.sb-brand-shell .station-ui-tabs>.ant-tabs-content-holder { background:var(--ant-color-bg-layout); }
+.sb-brand-shell .station-ui-tabs .ant-tabs-tab-btn b,.sb-brand-shell .station-ui-tabs .ant-tabs-tab-btn i { display:inline-flex; align-items:center; min-width:16px; line-height:16px; padding:0 4px; margin-left:8px; border:1px solid var(--ant-color-border); border-radius:9999px; }
+.sb-brand-shell .station-ui-tabs .ant-tabs-tab-active .ant-tabs-tab-btn b { border-color:var(--ant-color-success); }
+.sb-brand-shell .domain-capability-grid { gap:var(--ant-padding-sm); }
+.sb-brand-shell .domain-capability-detail { height:calc(100dvh - 176px); min-height:800px; }
+.sb-brand-shell .domain-capability-grid { flex:1; min-height:0; }
+.sb-brand-shell .domain-capability-card { height:100%; min-height:0; max-height:none; }
+.sb-brand-shell .domain-capability-card>footer { padding:var(--ant-padding-xs); min-height:48px; flex-wrap:nowrap; }
+.sb-brand-shell .domain-capability-card>footer .ant-btn { padding-inline:var(--ant-padding-xs); }
+.sb-brand-shell .dashboard-manager-actions { display:flex; flex-wrap:nowrap; align-items:center; gap:var(--ant-padding-xs); }
+@media (max-width:1440px) {
+ .sb-brand-shell .domain-capability-detail { height:auto; min-height:0; }
+ .sb-brand-shell .domain-capability-grid { grid-template-columns:repeat(2,minmax(0,1fr)); }
+ .sb-brand-shell .domain-capability-card { height:520px; min-height:0; }
+}
+@media (max-width:1000px) {
+ .sb-brand-shell .workspace>.content { padding:var(--ant-padding); }
+ .sb-brand-shell .station-list-toolbar,.sb-brand-shell .filterbar.company-filterbar { overflow-x:auto; }
+ .sb-brand-shell .domain-capability-grid { grid-template-columns:1fr; }
+}
--- /dev/null
+/** Serialize official antd tokens without turning unitless values into lengths. */
+export function tokenCssValue(name: string, value: string | number): string {
+ if (typeof value !== "number") return value;
+ if (/^(lineHeight|fontWeight|opacity|zIndex|sizeStep|sizeUnit)/i.test(name)) return String(value);
+ const length = /^(fontSize|borderRadius|controlHeight|size|padding|margin)|(?:Size|Height|Width|Radius|Padding|Margin)/;
+ return length.test(name) ? `${value}px` : String(value);
+}
"use client";
-import { ConfigProvider } from "antd";
+import { App, ConfigProvider, theme, type ThemeConfig } from "antd";
import zhCN from "antd/locale/zh_CN";
+import brandTheme from "../src/theme.json";
+import { tokenCssValue as cssValue } from "./brand-token-css";
-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 const companyTheme: ThemeConfig = { ...brandTheme, cssVar: { key: "stationbuilder" } };
+const kebab = (name: string) => name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
+// Body, plain layout elements and portals share antd's official, resolved variables.
+const resolved = theme.getDesignToken(companyTheme);
+const declarations = Object.entries(resolved).filter(([, value]) => typeof value === "string" || typeof value === "number").map(([name, value]) => "--ant-" + kebab(name) + ":" + cssValue(name, value as string | number));
+for (const [component, tokens] of Object.entries(brandTheme.components)) {
+ for (const [name, value] of Object.entries(tokens)) declarations.push("--ant-" + kebab(component) + "-" + kebab(name) + ":" + cssValue(name, value));
+}
+const rootVariables = ":root{" + declarations.join(";") + "}";
export default function CompanyTheme({ children }: { children: React.ReactNode }) {
- return (
- <ConfigProvider locale={zhCN} theme={companyTheme}>
- {children}
- </ConfigProvider>
- );
+ return <ConfigProvider locale={zhCN} theme={companyTheme} button={{ autoInsertSpace: false }}>
+ <style data-station-brand-tokens>{rootVariables}</style>
+ <App>{children}</App>
+ </ConfigProvider>;
}
@import "tailwindcss";
-:root{--blue:#009fe3;--blue2:#007cc3;--pale:#e7f5fc;--line:#d9dde2;--soft:#f4f5f6;--text:#333;--muted:#777;--green:#00b96b;--orange:#f3a000;--red:#f05b49}*{box-sizing:border-box}html,body{margin:0;min-height:100%;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Microsoft YaHei",sans-serif;color:var(--text);font-size:13px;background:var(--soft)}button,input,select,textarea{font:inherit;color:inherit}button{cursor:pointer}.app-shell{min-height:100vh;padding-top:56px;padding-left:190px}.topbar{position:fixed;z-index:50;left:0;right:0;top:0;height:56px;border-top:3px solid transparent;background-image:linear-gradient(#fff,#fff),linear-gradient(90deg,#008cd6,#009fe3);background-origin:border-box;background-clip:padding-box,border-box;border-bottom:1px solid #cfd4d9;display:flex;align-items:center;padding:0 12px;gap:9px;box-shadow:0 1px 3px #00000012}.hamburger{border:0;background:transparent;font-size:17px;color:#777}.brand-mark,.login-logo{color:#078be0;font-size:24px;line-height:1;font-weight:bold}.topbar>strong{font-size:16px}.divider{height:19px;border-left:1px solid #d4d7da;margin:0 2px}.top-actions{margin-left:auto;display:flex;align-items:center;gap:16px;color:#666}.top-actions>span{font-size:15px;position:relative}.top-actions i{position:absolute;width:5px;height:5px;background:red;border-radius:50%;right:-2px;top:-2px}.top-actions small{font-size:8px;line-height:1.2}.avatar{width:23px;height:23px;border-radius:50%;background:#d7f1fb;color:#168abf;display:grid!important;place-items:center;font-size:10px!important}.site-switcher{margin:2px 9px 4px;height:30px;border:1px solid #cfd4d9;border-radius:4px;background:#fff;color:#333;font-size:11px;padding:0 6px;width:calc(100% - 18px)}.site-switcher:focus{outline:1px solid var(--blue)}.side-nav{position:fixed;z-index:35;top:56px;bottom:0;left:0;width:190px;background:#f3f4f5;border-right:1px solid #d2d6da;display:flex;flex-direction:column;padding:0}.side-title{height:48px;padding:17px 15px 0;font-weight:bold;font-size:14px}.side-title span{float:right;color:#999}.side-nav>button{height:41px;border:0;background:transparent;text-align:left;padding:0 14px;display:flex;align-items:center;gap:10px;border-left:3px solid transparent;font-size:14px}.side-nav>button:hover{background:rgba(0,0,0,.04);color:#007fbe}.side-nav>button.active{background:rgba(0,140,214,.1);color:#008cd6;border-left-color:var(--blue)}.side-nav>button b{width:18px;text-align:center}.side-nav>button em{font-style:normal;width:21px;height:21px;border:1px solid #bfc5ca;border-radius:50%;display:grid;place-items:center;font-size:10px;background:white}.side-nav>button.active em{background:var(--blue);border-color:var(--blue);color:white}.side-nav>small{font-size:10px;color:#999;padding:20px 15px 8px;text-transform:uppercase;letter-spacing:.8px}.side-help{position:relative;margin:auto 12px 12px;border:1px solid var(--line);background:white;padding:11px;display:flex;flex-direction:column;gap:5px;font-size:10px}.side-help span{color:#888}
-.model-trigger{display:flex;align-items:center;justify-content:space-between;cursor:pointer;color:#888}
-.model-trigger:hover{color:#007fbd}
-.model-trigger .caret{font-size:8px;margin-left:4px}
+*{box-sizing:border-box}html,body{margin:0;min-height:100%;font-family:var(--ant-font-family);color:var(--ant-color-text-base);font-size:var(--ant-font-size-sm);background:var(--ant-color-bg-layout)}button,input,select,textarea{font:inherit;color:inherit}button{cursor:pointer}.app-shell{min-height:100vh;padding-top:56px;padding-left:192px}.topbar{position:fixed;z-index:50;left:0;right:0;top:0;height:56px;border-top:3px solid transparent;background-image:linear-gradient(var(--ant-color-bg-container),var(--ant-color-bg-container)),linear-gradient(90deg,var(--ant-color-primary),var(--ant-color-primary));background-origin:border-box;background-clip:padding-box,border-box;border-bottom:1px solid var(--ant-color-border);display:flex;align-items:center;padding:0 12px;gap:8px;box-shadow:0 1px 3px color-mix(in srgb,var(--ant-color-text-base) 7%,transparent)}.hamburger{border:0;background:transparent;font-size:var(--ant-font-size);color:var(--ant-color-text-secondary)}.brand-mark,.login-logo{color:var(--ant-color-primary);font-size:var(--ant-font-size-heading-3);line-height:1;font-weight:600}.topbar>strong{font-size:var(--ant-font-size)}.divider{height:19px;border-left:1px solid var(--ant-color-border);margin:0 4px}.top-actions{margin-left:auto;display:flex;align-items:center;gap:16px;color:var(--ant-color-text-secondary)}.top-actions>span{font-size:var(--ant-font-size);position:relative}.top-actions i{position:absolute;width:5px;height:5px;background:var(--ant-color-error);border-radius:50%;right:-2px;top:-2px}.top-actions small{font-size:var(--ant-font-size-sm);line-height:1.2}.avatar{width:23px;height:23px;border-radius:50%;background:var(--ant-color-primary-bg);color:var(--ant-color-primary);display:grid!important;place-items:center;font-size:var(--ant-font-size-sm)!important}.site-switcher{margin:4px 8px 4px;height:30px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);background:var(--ant-color-bg-container);color:var(--ant-color-text-base);font-size:var(--ant-font-size-sm);padding:0 8px;width:calc(100% - 18px)}.site-switcher:focus{outline:1px solid var(--ant-color-primary)}.side-nav{position:fixed;z-index:35;top:56px;bottom:0;left:0;width:190px;background:var(--ant-color-bg-layout);border-right:1px solid var(--ant-color-border);display:flex;flex-direction:column;padding:0}.side-title{height:48px;padding:16px 16px 0;font-weight:600;font-size:var(--ant-font-size)}.side-title span{float:right;color:var(--ant-color-text-secondary)}.side-nav>button{height:41px;border:0;background:transparent;text-align:left;padding:0 16px;display:flex;align-items:center;gap:12px;border-left:3px solid transparent;font-size:var(--ant-font-size)}.side-nav>button:hover{background:var(--ant-color-fill-quaternary);color:var(--ant-color-primary-hover)}.side-nav>button.active{background:var(--ant-color-primary-bg);color:var(--ant-color-primary);border-left-color:var(--ant-color-primary)}.side-nav>button b{width:18px;text-align:center}.side-nav>button em{font-style:normal;width:21px;height:21px;border:1px solid var(--ant-color-border);border-radius:50%;display:grid;place-items:center;font-size:var(--ant-font-size-sm);background:var(--ant-color-bg-container)}.side-nav>button.active em{background:var(--ant-color-primary);border-color:var(--ant-color-primary);color:var(--ant-color-bg-container)}.side-nav>small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);padding:20px 16px 8px;text-transform:uppercase;letter-spacing:.8px}.side-help{position:relative;margin:auto 12px 12px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);padding:12px;display:flex;flex-direction:column;gap:4px;font-size:var(--ant-font-size-sm)}.side-help span{color:var(--ant-color-text-secondary)}
+.model-trigger{display:flex;align-items:center;justify-content:space-between;cursor:pointer;color:var(--ant-color-text-secondary)}
+.model-trigger:hover{color:var(--ant-color-primary-hover)}
+.model-trigger .caret{font-size:var(--ant-font-size-sm);margin-left:4px}
.model-backdrop{position:fixed;inset:0;z-index:60}
-.model-popover{position:absolute;left:0;right:0;bottom:calc(100% + 6px);z-index:61;background:#fff;border:1px solid #d2d6d9;border-radius:6px;box-shadow:0 8px 22px #0002;padding:8px;display:flex;flex-direction:column;gap:5px}
-.model-popover-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:2px}
-.model-popover-head>span{font-weight:bold;color:#007fbd;font-size:10px}
-.model-popover-head>small{color:#aeb4b8;font-size:8px}
-.model-row{display:flex;align-items:center;gap:7px;padding:5px 7px;border:1px solid #e6e9eb;border-radius:4px;background:#fafbfb;font-size:10px;cursor:pointer}
-.model-row input[type=radio]{margin:0;accent-color:#009fe3}
-.model-row .model-name{font-weight:bold;color:#333;flex:1}
-.model-row .model-tag{color:#1f9d5f;font-size:8px;background:#eafaf1;border:1px solid #c8eccf;padding:1px 5px;border-radius:8px;white-space:nowrap}
-.model-row .model-rate{color:#888;font-size:9px;min-width:34px;text-align:right}
-.model-row.disabled{opacity:.45;cursor:not-allowed;background:#f4f4f4}
-.model-row.disabled .model-tag{color:#9aa1a5;background:#eceeef;border-color:#dfe2e3}
-.model-row:not(.disabled):hover{border-color:#8ccce9;background:#eef7fb}.workspace{min-height:calc(100vh - 56px);padding-bottom:64px}.crumb{height:34px;padding:11px 20px 0;color:#8b8f93;font-size:11px}.crumb b{color:var(--blue)}.page-heading{min-height:60px;padding:3px 20px 11px;display:flex;align-items:center;justify-content:space-between}.page-heading h1{font-size:21px;margin:0 0 5px}.page-heading p{margin:0;color:#81868b}.button{border:1px solid var(--blue2);background:var(--blue2);color:white;border-radius:2px;min-width:92px;height:31px;padding:0 14px;margin-left:8px}.button:hover{background:#006ca9}.button.secondary{background:white;color:#555;border-color:#cdd2d6}.button.secondary:hover{border-color:var(--blue);color:var(--blue)}.button:disabled{opacity:.55;cursor:not-allowed}.steps{height:70px;background:#fff;border-top:1px solid var(--line);border-bottom:1px solid var(--line);display:grid;grid-template-columns:repeat(4,1fr);padding:0 22px}.steps button{border:0;background:transparent;display:flex;align-items:center;position:relative;text-align:left;color:#999;padding:0 18px}.steps button>i{font-style:normal;width:29px;height:29px;border-radius:50%;border:1px solid #cfd4d9;display:grid;place-items:center;margin-right:10px;background:#fff}.steps button span{display:flex;flex-direction:column;gap:4px}.steps button small{font-size:8px}.steps button>b{position:absolute;right:0;font-size:23px;color:#c3c7ca}.steps button.active{color:#222}.steps button.active>i{background:var(--blue);border-color:var(--blue);color:#fff}.steps button.done>i{background:#39b980;border-color:#39b980;color:white}.steps button.locked{cursor:default}.content{padding:14px 20px}.panel{background:white;border:1px solid var(--line);margin-bottom:14px}.panel-head{min-height:43px;border-bottom:1px solid var(--line);padding:8px 12px;display:flex;justify-content:space-between;align-items:center}.panel-head>strong{font-size:14px}.panel-head .button{height:28px}.panel-body{padding:12px}.summary-strip{display:grid;grid-template-columns:repeat(5,1fr);height:64px;background:white;border:1px solid var(--line);margin-bottom:14px}.summary-strip div{display:flex;align-items:center;justify-content:center;gap:9px;border-right:1px solid var(--line)}.summary-strip div:last-child{border:0}.summary-strip b{font-size:23px}.summary-strip span{color:#777}.filterbar{height:45px;display:flex;align-items:center;gap:7px;padding-bottom:10px;border-bottom:1px solid var(--line)}.filterbar>label{width:205px;height:30px;border:1px solid #cdd2d6;display:flex;align-items:center;padding:0 8px;color:#999}.filterbar input{border:0;outline:0;width:100%;padding-left:8px}.filterbar select,.preview-toolbar select{height:30px;border:1px solid #cdd2d6;background:white;padding:0 25px 0 9px}.filterbar>span{flex:1}.view-switch{display:flex}.view-switch button,.icon-button{width:31px;height:29px;border:1px solid #cbd0d4;background:white}.view-switch button.active{background:var(--blue);color:white;border-color:var(--blue)}.icon-button{border:0;font-size:16px}.data-table{width:100%;border-collapse:collapse}.data-table th{height:35px;background:#f6f7f8;color:#4b4f53;text-align:left;font-size:11px;font-weight:bold;border-bottom:1px solid var(--line);padding:0 10px}.data-table td{height:48px;border-bottom:1px solid #e3e5e7;padding:7px 10px;font-size:11px}.data-table tr:hover td{background:#f7fbfd}.link{border:0;background:transparent;color:#008ed2;padding:0}.site-icon{display:inline-grid;place-items:center;width:30px;height:30px;margin-right:8px;color:#098dd5;border:1px solid #c8d8e0;background:#f7fcff;font-size:18px;vertical-align:middle}.site-icon.big{width:40px;height:40px;font-size:26px}.status{display:inline-flex;align-items:center;justify-content:center;border-radius:10px;padding:2px 8px;font-size:10px;line-height:1.2;border:1px solid}.status.ok{color:#008c51;background:#e5fff3;border-color:#60d9a2}.status.warn{color:#b37400;background:#fff4d8;border-color:#f6bc44}.status.bad{color:#d84632;background:#ffeae7;border-color:#ff8b7b}.status.muted{color:#777;background:#f3f3f3;border-color:#c9c9c9}.dots,.trash{border:0;background:transparent;color:#777}.pagination{height:46px;display:flex;justify-content:flex-end;align-items:center;gap:7px;padding-top:12px}.pagination span{margin-right:10px;color:#777}.pagination button{width:28px;height:28px;border:0;background:white}.pagination button.active{background:#dff2fc;color:#008fd2}.pagination select{border:0;background:white}.project-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;padding:12px 0}.project-grid article{border:1px solid var(--line);padding:14px;cursor:pointer}.project-grid article:hover{border-color:var(--blue);box-shadow:0 2px 7px #008fd21f}.project-grid article>div{display:flex;justify-content:space-between}.project-grid h3{margin:10px 0 5px}.project-grid p,.project-grid small{color:#888;font-size:11px}.project-grid section{display:flex;gap:18px;border-top:1px solid #eee;border-bottom:1px solid #eee;padding:10px 0;margin:12px 0}.project-grid section span{display:flex;flex-direction:column}.project-grid section b{font-size:16px}.two-column{display:grid;grid-template-columns:1fr 340px;gap:14px}.two-column.equal{grid-template-columns:1fr 1fr}.basic-layout>.panel>.panel-body{padding:17px}.stack{display:flex;flex-direction:column}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px}.form-grid>label{display:flex;flex-direction:column;gap:7px;font-size:11px;font-weight:bold}.form-grid>label>input,.form-grid>label>select,.form-grid>label>textarea{border:1px solid #cfd3d7;background:white;border-radius:2px;padding:8px;outline:none;font-weight:normal}.form-grid>label>input:focus,.form-grid>label>select:focus,.form-grid>label>textarea:focus{border-color:var(--blue)}.form-grid>label>input,.form-grid>label>select{height:34px}.form-grid>label>textarea{min-height:80px;resize:vertical;line-height:1.6}.field{display:flex;flex-direction:column;gap:7px}.field>span{font-size:11px;font-weight:bold}.field>span i{color:red;font-style:normal;margin-right:3px}.field input,.field select,.field textarea{border:1px solid #cfd3d7;background:white;border-radius:2px;padding:8px;outline:none}.field input,.field select{height:34px}.field textarea{min-height:130px;resize:vertical;line-height:1.6}.field textarea.small{min-height:80px}.field small{color:#999;font-size:10px}.form-grid .field:nth-child(n+3){grid-column:1/-1}.upload{height:90px;border:1px dashed #bfc7cd;background:#fafbfb;display:flex;flex-direction:column;justify-content:center;align-items:center;color:#777;gap:5px}.upload b{color:var(--blue);font-size:20px}.ai-box{text-align:center;padding:12px}.ai-box>span{width:42px;height:42px;border-radius:50%;background:linear-gradient(135deg,#05a9e8,#8056d9);color:#fff;display:grid;place-items:center;margin:auto;font-weight:bold}.ai-box h3{font-size:14px;margin:12px}.ai-box p{color:#777;line-height:1.7;text-align:left}.meter{height:7px;border-radius:5px;background:#edf0f2;overflow:hidden}.meter i{display:block;height:100%;background:linear-gradient(90deg,#00a4e7,#42c58a);transition:width .35s}.tips{padding-left:22px;line-height:2;color:#666}.kpi-row{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:14px}.kpi-row>div{height:75px;border:1px solid var(--line);background:white;padding:12px 14px;position:relative}.kpi-row span{display:block;color:#008dcf;font-size:11px;margin-bottom:6px}.kpi-row b{font-size:25px}.kpi-row small{margin-left:4px;color:#888}.kpi-row .status{margin-top:7px}.editable td{padding:7px}.editable input,.editable select{height:29px;border:1px solid #d3d7db;width:100%;padding:0 7px;background:#fff}.editable .number{width:65px}.tree{line-height:2.2}.tree .l1{padding-left:24px}.tree .l2{padding-left:52px;color:#777}.tree span{border-radius:10px;background:#eef2f4;padding:2px 7px;font-size:10px}.file-card{display:flex;align-items:center;gap:12px;border:1px solid #e0e3e5;background:#fafafa;padding:12px}.file-card .excel{width:36px;height:42px;background:#14834f;color:white;display:grid;place-items:center;font-weight:bold}.file-card>div{flex:1}.file-card p{margin:5px 0 0;color:#888;font-size:10px}.checks{display:flex;gap:20px;color:#15935c;padding:12px 2px 0;font-size:10px}.tabs{height:46px;border-bottom:1px solid var(--line);display:flex;gap:6px;margin:-12px -12px 0;padding-left:10px}.tabs button{padding:0 18px;border:0;border-bottom:2px solid transparent;background:white}.tabs button.active{color:#008ed2;border-bottom-color:#008ed2}.tabs i{font-style:normal;border-radius:10px;background:#e7f3f8;padding:2px 7px;margin-left:5px}.section-toolbar{display:flex;justify-content:space-between;align-items:center;padding:14px 4px}.section-toolbar p{margin:4px 0 0;color:#888;font-size:11px}.mu-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.mu-grid article{border:1px solid var(--line);display:grid;grid-template-columns:42% 58%;min-height:128px;cursor:pointer}.mu-grid article.selected{border:2px solid var(--blue);background:#fbfeff}.mu-thumb{margin:8px;background:#e9eef1;display:grid;place-items:center;position:relative;overflow:hidden}.mu-thumb:after{content:"";width:72%;height:40%;border:1px solid #8ec4dd;background:white;box-shadow:26px -23px 0 -8px #b9e1f3,-18px 25px 0 -7px #8ed1ec}.mu-thumb b{position:absolute;z-index:2;color:var(--blue);font-size:22px}.mu-grid article>div:last-child{padding:11px 10px 8px 0}.mu-grid header{display:flex;gap:7px;align-items:center}.mu-grid h3{font-size:13px;margin:0}.mu-grid p{color:#777;font-size:10px;line-height:1.5;min-height:32px}.mu-grid button{border:0;background:transparent;color:#008ed2;padding:0}.blue{color:#008ed2}.type-tag{background:#eff3f5;padding:4px 8px;border-radius:2px}.wide-cell{min-width:300px}.menu-builder{display:grid;grid-template-columns:230px 1fr 240px;gap:14px;background:#f6f7f8;padding:14px}.page-pool,.menu-tree,.menu-preview{background:white;border:1px solid var(--line);padding:12px}.page-pool p{color:#999;font-size:10px}.page-pool div{border:1px solid var(--line);padding:10px;margin-top:8px;background:#fafafa}.menu-tree section{border:1px solid var(--line);margin-bottom:8px;padding:10px}.menu-tree section>span{float:right}.menu-tree section>div{margin:9px 0 0 15px;padding:7px;border-top:1px solid #eee}.menu-tree i{font-style:normal;font-size:8px;background:#e1f0f8;color:#008ed2;padding:3px;margin-right:8px}.menu-tree i.cube{background:#eee8fb;color:#7045bc}.menu-tree>button{border:1px dashed #aaa;background:#fafafa;width:100%;height:34px}.menu-preview aside{background:#f4f5f6;min-height:250px;display:flex;flex-direction:column;padding-top:10px}.menu-preview strong{padding:12px}.menu-preview button{border:0;background:transparent;text-align:left;padding:10px}.menu-preview button.active{background:#d8effa;color:#0089ca}.generate-layout{display:grid;grid-template-columns:1.1fr .9fr;gap:14px}.confirm-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.confirm-grid div{border:1px solid #dfe2e5;padding:12px;display:flex;flex-direction:column;gap:5px;position:relative}.confirm-grid span,.confirm-grid small{color:#888;font-size:10px}.confirm-grid button{position:absolute;right:10px;top:10px;border:0;background:transparent;color:#008ed2;font-size:10px}.option-list{display:grid;grid-template-columns:1fr 1fr;gap:12px}.option-list label{display:flex;align-items:center;gap:8px}.task-panel{height:100%}.overall{background:#f6fbfd;border:1px solid #d3eaf5;padding:13px}.overall>div:first-child{display:flex;justify-content:space-between;margin-bottom:9px}.overall p{margin:8px 0 0;color:#777;font-size:10px}.task-list{padding:10px 0}.task-list>div{display:flex;align-items:center;border-bottom:1px solid #eee;padding:9px 3px}.task-list>div>i{font-style:normal;width:25px;height:25px;border-radius:50%;border:1px solid #d2d6da;display:grid;place-items:center;margin-right:9px}.task-list>div>i.done{background:#31b67c;border-color:#31b67c;color:white}.task-list>div>i.spin{color:#008fd2}.task-list>div>span{display:flex;flex:1;flex-direction:column;gap:3px}.task-list small{color:#999}.task-panel>.panel-body>.button{width:100%;margin:5px 0}.result-header{display:flex;align-items:center;justify-content:space-between;background:white;border:1px solid var(--line);padding:12px;margin-bottom:14px}.result-header>div{display:flex;align-items:center;gap:10px}.result-header h2{margin:0 0 4px}.result-header p{margin:0;color:#888;font-size:10px}.result-layout{display:grid;grid-template-columns:1fr 280px;gap:14px}.result-pages{display:grid;grid-template-columns:1fr;gap:10px}.result-pages article{border:1px solid var(--line)}.result-pages article>div:last-child{padding:10px}.result-pages article header{display:flex;justify-content:space-between}.result-pages h3{font-size:13px;margin:10px 0 4px}.result-pages p,.result-pages small{font-size:10px;color:#888}.result-pages footer{display:flex;margin-top:9px}.result-pages footer .button{margin:0 5px 0 0;min-width:68px;height:27px}.dashboard-thumb{height:115px;background:#f2f5f7;position:relative;padding:12px 10px 10px 35px}.dashboard-thumb>i{position:absolute;left:0;top:0;bottom:0;width:25px;background:#d7e2e8}.dashboard-thumb>header{display:block;height:10px;background:white;border:1px solid #d9e1e5}.dashboard-thumb>section{display:flex;gap:4px;margin-top:5px}.dashboard-thumb>section b{height:22px;flex:1;background:white;border:1px solid #cde0e9}.dashboard-thumb>footer{position:absolute;left:35px;right:46px;bottom:10px;height:50px;background:white;border:1px solid #d8e1e6;display:flex;align-items:flex-end;gap:5px;padding:8px}.dashboard-thumb>footer em{flex:1;background:#67c5ea;height:45%}.dashboard-thumb>footer em:nth-child(2){height:75%}.dashboard-thumb>footer em:nth-child(4){height:92%}.dashboard-thumb>span{position:absolute;right:9px;bottom:10px;width:31px;height:50px;background:conic-gradient(#17b876 0 72%,#e6edf0 72%)}.dashboard-thumb-live{position:relative;min-height:115px;background:#f2f5f7;border-bottom:1px solid #d8e1e6;overflow:hidden}.dashboard-thumb-live>div{position:relative}.thumb-empty{position:absolute;inset:0;display:grid;place-items:center;color:#999;font-size:11px}result-menu div{display:flex;justify-content:space-between;border-bottom:1px solid #eee;padding:10px 4px}.result-menu span{background:#edf1f3;border-radius:10px;padding:2px 7px}.text-button{border:0;background:transparent;color:#008ed2;margin-top:12px}.score{display:flex;align-items:center;justify-content:center;gap:10px;padding:10px}.score b{width:55px;height:55px;border-radius:50%;border:5px solid #32bb81;display:grid;place-items:center;font-size:18px}.score span{color:#16945d}.validation{padding-left:10px;list-style:none;line-height:2;color:#178f5a}.preview-shell{border:1px solid var(--line);background:white;margin:-2px}.preview-toolbar{height:45px;display:flex;align-items:center;justify-content:space-between;padding:0 10px;border-bottom:1px solid var(--line)}.preview-toolbar>div{display:flex;align-items:center;gap:8px}.preview-toolbar button{border:1px solid #d1d5d8;background:white;height:29px;padding:0 10px}.preview-main{display:grid;grid-template-columns:1fr 300px;height:655px}.cube-canvas{position:relative;background:#f2f4f5;border-right:1px solid var(--line);overflow:hidden}.cube-top{position:absolute;left:0;right:0;top:0;height:32px;background:white;border-top:3px solid var(--blue2);border-bottom:1px solid #cfd4d8;display:flex;align-items:center;padding:0 10px;gap:15px}.cube-top b{color:#087fc0}.cube-top i{margin-left:auto;font-style:normal;color:#777}.cube-side{position:absolute;left:0;top:32px;bottom:0;width:130px;background:#edf0f1;display:flex;flex-direction:column}.cube-side strong{padding:15px 10px}.cube-side button{height:37px;border:0;background:transparent;text-align:left;padding-left:12px}.cube-side button.active{background:#d6effb;color:#0088c9}.cube-content{position:absolute;left:130px;top:32px;right:0;bottom:0;padding:12px}.cube-content>header{display:flex;justify-content:space-between;align-items:center}.cube-content>header h2{font-size:16px;margin:5px 0 8px}.cube-content>header span,.cube-content>header small{font-size:9px;color:#888}.cube-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:7px}.cube-kpis>div{height:78px;background:white;border:1px solid #d6dade;padding:9px;position:relative;display:flex;flex-direction:column}.cube-kpis span{font-size:9px;color:#008dcd}.cube-kpis b{font-size:20px;margin:5px 0}.cube-kpis small{font-size:9px;color:#888}.spark{position:absolute;right:9px;bottom:14px;width:42px;height:18px;border-top:3px solid #13a7e5;transform:skewY(-12deg);border-radius:50%}.spark.s1{transform:skewY(8deg)}.spark.s2{border-color:#f0a000}.cube-grid{display:grid;grid-template-columns:1.7fr .8fr;gap:7px;margin-top:7px}.cube-grid>section{background:white;border:1px solid #d6dade;padding:10px}.cube-grid h3{font-size:11px;margin:0 0 8px}.cube-grid h3 span,.cube-grid h3 button{float:right;border:0;background:transparent;color:#777;font-size:9px}.line-chart{height:175px;position:relative}.line-chart>div{position:absolute;inset:0 0 20px;background:repeating-linear-gradient(to bottom,#fff 0,#fff 29px,#e8ecee 30px)}.line-chart svg{position:absolute;left:0;right:0;top:0;height:145px;width:100%}.line-chart footer{position:absolute;left:0;right:0;bottom:0;display:flex;justify-content:space-between;font-size:8px;color:#999}.donut{width:115px;height:115px;border-radius:50%;background:conic-gradient(#1abc78 0 80%,#f2aa24 80% 92%,#aeb7bd 92%);margin:13px auto;display:grid;place-items:center}.donut:before{content:"";position:absolute;width:72px;height:72px;background:white;border-radius:50%}.donut b{z-index:1;font-size:21px;display:flex;flex-direction:column;align-items:center}.donut small{font-size:8px;color:#888}.donut-panel footer{display:flex;justify-content:space-around;font-size:8px}.donut-panel footer i{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:3px}.green{background:#1abc78}.orange{background:#f2aa24}.gray{background:#aeb7bd}.alarm-panel{grid-column:1/-1}.alarm-panel table{border-collapse:collapse;width:100%;font-size:9px}.alarm-panel td{border-top:1px solid #e5e8ea;padding:8px}.inspector{background:white;overflow:auto}.inspector>nav{height:42px;display:flex;border-bottom:1px solid var(--line)}.inspector>nav button{flex:1;border:0;border-bottom:2px solid transparent;background:white;font-size:11px}.inspector>nav button.active{color:#008ed2;border-bottom-color:#008ed2}.inspector-body{padding:14px}.inspector-body h3{font-size:15px}.inspector-body p{font-size:11px;color:#777;line-height:1.7}.inspector-body dl{display:grid;grid-template-columns:85px 1fr;border-top:1px solid #eee;margin:15px 0}.inspector-body dt,.inspector-body dd{margin:0;padding:9px 0;border-bottom:1px solid #eee;font-size:10px}.inspector-body dt{color:#888}.component{padding:8px;border:1px solid #e2e5e7;margin-top:6px}.component span{float:right}.code-body header{text-align:right}.code-body header button{border:0;background:transparent;color:#008ed2}.code-body pre{font-size:10px;line-height:1.55;background:#202a33;color:#cce9f7;padding:12px;min-height:410px;overflow:auto}.inspector-body textarea{width:100%;height:120px;border:1px solid #ccd2d6;padding:9px;resize:none}.suggestion{border:1px solid #cfd5d9;background:#f8fafb;border-radius:12px;padding:5px 9px;margin:0 5px 7px 0;font-size:10px}.inspector-body>.button{width:100%;margin:12px 0}.sticky-actions{position:fixed;z-index:20;left:232px;right:0;bottom:0;height:54px;background:white;border-top:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 20px;box-shadow:0 -2px 6px #0000000c}.toast{position:fixed;z-index:100;right:24px;top:56px;background:#26323a;color:white;padding:12px 18px;box-shadow:0 5px 18px #0003}.toast b{color:#46d195;margin-right:8px}.modal-backdrop{position:fixed;z-index:90;inset:0;background:#0007;display:grid;place-items:center}.modal{width:610px;max-height:78vh;background:white;box-shadow:0 10px 30px #0004;display:flex;flex-direction:column}.modal>header{height:47px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 15px}.modal h2{font-size:15px}.modal>header button{border:0;background:transparent;font-size:22px;color:#888}.modal-body{padding:16px;overflow:auto;flex:1 1 auto;min-height:0}.modal>footer{border-top:1px solid var(--line);padding:10px 15px;text-align:right}.modal-note{background:#edf8fd;border-left:3px solid var(--blue);padding:12px;margin-bottom:14px;color:#26799f}.impact-title{font-weight:bold;margin-bottom:8px}.impact-list{list-style:none;padding:0;border:1px solid var(--line)}.impact-list li{display:flex;justify-content:space-between;padding:10px;border-bottom:1px solid #eee}.impact-list span{color:#008ed2}.download-list{display:flex;flex-direction:column}.download-list label{padding:11px;border-bottom:1px solid #eee;display:flex;gap:8px}.download-list>div{margin-top:15px;background:#f4f6f7;padding:12px;display:flex;justify-content:space-between}.history-list>div{display:flex;align-items:center;gap:10px;border-bottom:1px solid #eee;padding:11px}.history-list i{font-style:normal;width:34px;height:34px;border-radius:50%;background:#e4f4fb;color:#008ed2;display:grid;place-items:center}.history-list span{display:flex;flex:1;flex-direction:column;gap:4px}.history-list small{color:#888}.history-list button{border:0;background:transparent;color:#008ed2}.login-page{min-height:100vh;background:linear-gradient(145deg,#eef5f8,#fff 55%,#e5f4fb);border-top:4px solid var(--blue2);display:flex;align-items:center;justify-content:center}.login-top{position:absolute;left:18px;top:14px;display:flex;align-items:center;gap:10px}.login-top b{font-size:18px}.login-top span{border-left:1px solid #ccc;padding-left:10px;color:#777}.login-card{width:420px;background:white;border:1px solid #d6dce0;box-shadow:0 12px 35px #1e526618;padding:42px;text-align:center}.login-logo{font-size:48px}.login-card h1{font-size:23px}.login-card p{color:#777;line-height:1.6}.sso-button{width:100%;height:48px;margin:22px 0 12px;border:1px solid #ccd3d7;background:#fff;display:flex;align-items:center;gap:12px;padding:0 13px}.sso-button b{flex:1;text-align:left}.sso-button i{font-style:normal;font-size:18px;color:#008ed2}.login-card>small{color:#999}.login-page>footer{position:absolute;bottom:18px;color:#999;font-size:10px}
+.model-popover{position:absolute;left:0;right:0;bottom:calc(100% + 6px);z-index:61;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);box-shadow:0 8px 22px color-mix(in srgb,var(--ant-color-text-base) 13%,transparent);padding:8px;display:flex;flex-direction:column;gap:4px}
+.model-popover-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}
+.model-popover-head>span{font-weight:600;color:var(--ant-color-primary);font-size:var(--ant-font-size-sm)}
+.model-popover-head>small{color:var(--ant-color-text-disabled);font-size:var(--ant-font-size-sm)}
+.model-row{display:flex;align-items:center;gap:8px;padding:4px 8px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);background:var(--ant-color-bg-layout);font-size:var(--ant-font-size-sm);cursor:pointer}
+.model-row input[type=radio]{margin:0;accent-color:var(--ant-color-primary)}
+.model-row .model-name{font-weight:600;color:var(--ant-color-text-base);flex:1}
+.model-row .model-tag{color:var(--ant-color-success);font-size:var(--ant-font-size-sm);background:var(--ant-color-bg-layout);border:1px solid var(--ant-color-success-bg);padding:4px 4px;border-radius:var(--ant-border-radius);white-space:nowrap}
+.model-row .model-rate{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);min-width:34px;text-align:right}
+.model-row.disabled{opacity:.45;cursor:not-allowed;background:var(--ant-color-bg-layout)}
+.model-row.disabled .model-tag{color:var(--ant-color-text-secondary);background:var(--ant-color-bg-layout);border-color:var(--ant-color-border)}
+.model-row:not(.disabled):hover{border-color:var(--ant-color-primary-bg);background:var(--ant-color-bg-layout)}.workspace{min-height:calc(100vh - 56px);padding-bottom:64px}.crumb{height:34px;padding:12px 20px 0;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.crumb b{color:var(--ant-color-primary)}.page-heading{min-height:60px;padding:4px 20px 12px;display:flex;align-items:center;justify-content:space-between}.page-heading h1{font-size:var(--ant-font-size-heading-4);margin:0 0 4px}.page-heading p{margin:0;color:var(--ant-color-text-secondary)}.button{border:1px solid var(--ant-color-primary-active);background:var(--ant-color-primary-active);color:var(--ant-color-bg-container);border-radius:var(--ant-border-radius);min-width:92px;height:31px;padding:0 16px;margin-left:8px}.button:hover{background:var(--ant-color-primary-hover)}.button.secondary{background:var(--ant-color-bg-container);color:var(--ant-color-text-secondary);border-color:var(--ant-color-border)}.button.secondary:hover{border-color:var(--ant-color-primary);color:var(--ant-color-primary)}.button:disabled{opacity:.55;cursor:not-allowed}.steps{height:70px;background:var(--ant-color-bg-container);border-top:1px solid var(--ant-color-border);border-bottom:1px solid var(--ant-color-border);display:grid;grid-template-columns:repeat(4,1fr);padding:0 24px}.steps button{border:0;background:transparent;display:flex;align-items:center;position:relative;text-align:left;color:var(--ant-color-text-secondary);padding:0 20px}.steps button>i{font-style:normal;width:29px;height:29px;border-radius:50%;border:1px solid var(--ant-color-border);display:grid;place-items:center;margin-right:12px;background:var(--ant-color-bg-container)}.steps button span{display:flex;flex-direction:column;gap:4px}.steps button small{font-size:var(--ant-font-size-sm)}.steps button>b{position:absolute;right:0;font-size:var(--ant-font-size-heading-3);color:var(--ant-color-text-disabled)}.steps button.active{color:var(--ant-color-text-base)}.steps button.active>i{background:var(--ant-color-primary);border-color:var(--ant-color-primary);color:var(--ant-color-text-light-solid)}.steps button.done>i{background:var(--ant-color-success);border-color:var(--ant-color-success);color:var(--ant-color-bg-container)}.steps button.locked{cursor:default}.content{padding:16px 20px}.panel{background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);margin-bottom:16px}.panel-head{min-height:43px;border-bottom:1px solid var(--ant-color-border);padding:8px 12px;display:flex;justify-content:space-between;align-items:center}.panel-head>strong{font-size:var(--ant-font-size)}.panel-head .button{height:28px}.panel-body{padding:12px}.summary-strip{display:grid;grid-template-columns:repeat(5,1fr);height:64px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);margin-bottom:16px}.summary-strip div{display:flex;align-items:center;justify-content:center;gap:8px;border-right:1px solid var(--ant-color-border)}.summary-strip div:last-child{border:0}.summary-strip b{font-size:var(--ant-font-size-heading-3)}.summary-strip span{color:var(--ant-color-text-secondary)}.filterbar{height:45px;display:flex;align-items:center;gap:8px;padding-bottom:12px;border-bottom:1px solid var(--ant-color-border)}.filterbar>label{width:205px;height:30px;border:1px solid var(--ant-color-border);display:flex;align-items:center;padding:0 8px;color:var(--ant-color-text-secondary)}.filterbar input{border:0;outline:0;width:100%;padding-left:8px}.filterbar select,.preview-toolbar select{height:30px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);padding:0 24px 0 8px}.filterbar>span{flex:1}.view-switch{display:flex}.view-switch button,.icon-button{width:31px;height:29px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container)}.view-switch button.active{background:var(--ant-color-primary);color:var(--ant-color-bg-container);border-color:var(--ant-color-primary)}.icon-button{border:0;font-size:var(--ant-font-size)}.data-table{width:100%;border-collapse:collapse}.data-table th{height:35px;background:var(--ant-color-bg-layout);color:var(--ant-color-text-secondary);text-align:left;font-size:var(--ant-font-size-sm);font-weight:600;border-bottom:1px solid var(--ant-color-border);padding:0 12px}.data-table td{height:48px;border-bottom:1px solid var(--ant-color-border);padding:8px 12px;font-size:var(--ant-font-size-sm)}.data-table tr:hover td{background:var(--ant-color-bg-layout)}.link{border:0;background:transparent;color:var(--ant-color-primary);padding:0}.site-icon{display:inline-grid;place-items:center;width:30px;height:30px;margin-right:8px;color:var(--ant-color-primary);border:1px solid var(--ant-color-border);background:var(--ant-color-bg-layout);font-size:var(--ant-font-size);vertical-align:middle}.site-icon.big{width:40px;height:40px;font-size:var(--ant-font-size-heading-3)}.status{display:inline-flex;align-items:center;justify-content:center;border-radius:var(--ant-border-radius);padding:4px 8px;font-size:var(--ant-font-size-sm);line-height:1.2;border:1px solid}.status.ok{color:var(--ant-color-success);background:var(--ant-color-bg-layout);border-color:var(--ant-color-success)}.status.warn{color:var(--ant-color-error);background:var(--ant-color-primary-bg);border-color:var(--ant-color-error-bg)}.status.bad{color:var(--ant-color-error);background:var(--ant-color-bg-layout);border-color:var(--ant-color-error)}.status.muted{color:var(--ant-color-text-secondary);background:var(--ant-color-bg-layout);border-color:var(--ant-color-border)}.dots,.trash{border:0;background:transparent;color:var(--ant-color-text-secondary)}.pagination{height:46px;display:flex;justify-content:flex-end;align-items:center;gap:8px;padding-top:12px}.pagination span{margin-right:12px;color:var(--ant-color-text-secondary)}.pagination button{width:28px;height:28px;border:0;background:var(--ant-color-bg-container)}.pagination button.active{background:var(--ant-color-primary-bg);color:var(--ant-color-primary)}.pagination select{border:0;background:var(--ant-color-bg-container)}.project-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;padding:12px 0}.project-grid article{border:1px solid var(--ant-color-border);padding:16px;cursor:pointer}.project-grid article:hover{border-color:var(--ant-color-primary);box-shadow:none}.project-grid article>div{display:flex;justify-content:space-between}.project-grid h3{margin:12px 0 4px}.project-grid p,.project-grid small{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.project-grid section{display:flex;gap:20px;border-top:1px solid var(--ant-color-border);border-bottom:1px solid var(--ant-color-border);padding:12px 0;margin:12px 0}.project-grid section span{display:flex;flex-direction:column}.project-grid section b{font-size:var(--ant-font-size)}.two-column{display:grid;grid-template-columns:1fr 340px;gap:16px}.two-column.equal{grid-template-columns:1fr 1fr}.basic-layout>.panel>.panel-body{padding:16px}.stack{display:flex;flex-direction:column}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px}.form-grid>label{display:flex;flex-direction:column;gap:8px;font-size:var(--ant-font-size-sm);font-weight:600}.form-grid>label>input,.form-grid>label>select,.form-grid>label>textarea{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);border-radius:var(--ant-border-radius);padding:8px;outline:none;font-weight:400}.form-grid>label>input:focus,.form-grid>label>select:focus,.form-grid>label>textarea:focus{border-color:var(--ant-color-primary)}.form-grid>label>input,.form-grid>label>select{height:34px}.form-grid>label>textarea{min-height:80px;resize:vertical;line-height:1.6}.field{display:flex;flex-direction:column;gap:8px}.field>span{font-size:var(--ant-font-size-sm);font-weight:600}.field>span i{color:red;font-style:normal;margin-right:4px}.field input,.field select,.field textarea{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);border-radius:var(--ant-border-radius);padding:8px;outline:none}.field input,.field select{height:34px}.field textarea{min-height:130px;resize:vertical;line-height:1.6}.field textarea.small{min-height:80px}.field small{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.form-grid .field:nth-child(n+3){grid-column:1/-1}.upload{height:90px;border:1px dashed var(--ant-color-border);background:var(--ant-color-bg-layout);display:flex;flex-direction:column;justify-content:center;align-items:center;color:var(--ant-color-text-secondary);gap:4px}.upload b{color:var(--ant-color-primary);font-size:var(--ant-font-size-heading-4)}.ai-box{text-align:center;padding:12px}.ai-box>span{width:42px;height:42px;border-radius:50%;background:linear-gradient(135deg,var(--ant-color-primary),var(--ant-color-primary));color:var(--ant-color-text-light-solid);display:grid;place-items:center;margin:auto;font-weight:600}.ai-box h3{font-size:var(--ant-font-size);margin:12px}.ai-box p{color:var(--ant-color-text-secondary);line-height:1.7;text-align:left}.meter{height:7px;border-radius:var(--ant-border-radius);background:var(--ant-color-bg-layout);overflow:hidden}.meter i{display:block;height:100%;background:linear-gradient(90deg,var(--ant-color-primary),var(--ant-color-success));transition:width .35s}.tips{padding-left:24px;line-height:2;color:var(--ant-color-text-secondary)}.kpi-row{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px}.kpi-row>div{height:75px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);padding:12px 16px;position:relative}.kpi-row span{display:block;color:var(--ant-color-primary);font-size:var(--ant-font-size-sm);margin-bottom:8px}.kpi-row b{font-size:var(--ant-font-size-heading-3)}.kpi-row small{margin-left:4px;color:var(--ant-color-text-secondary)}.kpi-row .status{margin-top:8px}.editable td{padding:8px}.editable input,.editable select{height:29px;border:1px solid var(--ant-color-border);width:100%;padding:0 8px;background:var(--ant-color-bg-container)}.editable .number{width:65px}.tree{line-height:2.2}.tree .l1{padding-left:24px}.tree .l2{padding-left:52px;color:var(--ant-color-text-secondary)}.tree span{border-radius:var(--ant-border-radius);background:var(--ant-color-bg-layout);padding:4px 8px;font-size:var(--ant-font-size-sm)}.file-card{display:flex;align-items:center;gap:12px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-layout);padding:12px}.file-card .excel{width:36px;height:42px;background:var(--ant-color-success);color:var(--ant-color-bg-container);display:grid;place-items:center;font-weight:600}.file-card>div{flex:1}.file-card p{margin:4px 0 0;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.checks{display:flex;gap:20px;color:var(--ant-color-success);padding:12px 4px 0;font-size:var(--ant-font-size-sm)}.tabs{height:46px;border-bottom:1px solid var(--ant-color-border);display:flex;gap:8px;margin:-12px -12px 0;padding-left:12px}.tabs button{padding:0 20px;border:0;border-bottom:2px solid transparent;background:var(--ant-color-bg-container)}.tabs button.active{color:var(--ant-color-primary);border-bottom-color:var(--ant-color-primary)}.tabs i{font-style:normal;border-radius:var(--ant-border-radius);background:var(--ant-color-bg-layout);padding:4px 8px;margin-left:4px}.section-toolbar{display:flex;justify-content:space-between;align-items:center;padding:16px 4px}.section-toolbar p{margin:4px 0 0;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.mu-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.mu-grid article{border:1px solid var(--ant-color-border);display:grid;grid-template-columns:42% 58%;min-height:128px;cursor:pointer}.mu-grid article.selected{border:2px solid var(--ant-color-primary);background:var(--ant-color-bg-layout)}.mu-thumb{margin:8px;background:var(--ant-color-bg-layout);display:grid;place-items:center;position:relative;overflow:hidden}.mu-thumb:after{content:"";width:72%;height:40%;border:1px solid var(--ant-color-primary-bg);background:var(--ant-color-bg-container);box-shadow:none}.mu-thumb b{position:absolute;z-index:2;color:var(--ant-color-primary);font-size:var(--ant-font-size-heading-3)}.mu-grid article>div:last-child{padding:12px 12px 8px 0}.mu-grid header{display:flex;gap:8px;align-items:center}.mu-grid h3{font-size:var(--ant-font-size-sm);margin:0}.mu-grid p{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);line-height:1.5;min-height:32px}.mu-grid button{border:0;background:transparent;color:var(--ant-color-primary);padding:0}.blue{color:var(--ant-color-primary)}.type-tag{background:var(--ant-color-bg-layout);padding:4px 8px;border-radius:var(--ant-border-radius)}.wide-cell{min-width:300px}.menu-builder{display:grid;grid-template-columns:230px 1fr 240px;gap:16px;background:var(--ant-color-bg-layout);padding:16px}.page-pool,.menu-tree,.menu-preview{background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);padding:12px}.page-pool p{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.page-pool div{border:1px solid var(--ant-color-border);padding:12px;margin-top:8px;background:var(--ant-color-bg-layout)}.menu-tree section{border:1px solid var(--ant-color-border);margin-bottom:8px;padding:12px}.menu-tree section>span{float:right}.menu-tree section>div{margin:8px 0 0 16px;padding:8px;border-top:1px solid var(--ant-color-border)}.menu-tree i{font-style:normal;font-size:var(--ant-font-size-sm);background:var(--ant-color-bg-layout);color:var(--ant-color-primary);padding:4px;margin-right:8px}.menu-tree i.cube{background:var(--ant-color-bg-layout);color:var(--ant-color-primary)}.menu-tree>button{border:1px dashed var(--ant-color-border);background:var(--ant-color-bg-layout);width:100%;height:34px}.menu-preview aside{background:var(--ant-color-bg-layout);min-height:250px;display:flex;flex-direction:column;padding-top:12px}.menu-preview strong{padding:12px}.menu-preview button{border:0;background:transparent;text-align:left;padding:12px}.menu-preview button.active{background:var(--ant-color-primary-bg);color:var(--ant-color-primary)}.generate-layout{display:grid;grid-template-columns:1.1fr .9fr;gap:16px}.confirm-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.confirm-grid div{border:1px solid var(--ant-color-border);padding:12px;display:flex;flex-direction:column;gap:4px;position:relative}.confirm-grid span,.confirm-grid small{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.confirm-grid button{position:absolute;right:10px;top:10px;border:0;background:transparent;color:var(--ant-color-primary);font-size:var(--ant-font-size-sm)}.option-list{display:grid;grid-template-columns:1fr 1fr;gap:12px}.option-list label{display:flex;align-items:center;gap:8px}.task-panel{height:100%}.overall{background:var(--ant-color-bg-layout);border:1px solid var(--ant-color-primary);padding:12px}.overall>div:first-child{display:flex;justify-content:space-between;margin-bottom:8px}.overall p{margin:8px 0 0;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.task-list{padding:12px 0}.task-list>div{display:flex;align-items:center;border-bottom:1px solid var(--ant-color-border);padding:8px 4px}.task-list>div>i{font-style:normal;width:25px;height:25px;border-radius:50%;border:1px solid var(--ant-color-border);display:grid;place-items:center;margin-right:8px}.task-list>div>i.done{background:var(--ant-color-success);border-color:var(--ant-color-success);color:var(--ant-color-bg-container)}.task-list>div>i.spin{color:var(--ant-color-primary)}.task-list>div>span{display:flex;flex:1;flex-direction:column;gap:4px}.task-list small{color:var(--ant-color-text-secondary)}.task-panel>.panel-body>.button{width:100%;margin:4px 0}.result-header{display:flex;align-items:center;justify-content:space-between;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);padding:12px;margin-bottom:16px}.result-header>div{display:flex;align-items:center;gap:12px}.result-header h2{margin:0 0 4px}.result-header p{margin:0;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.result-layout{display:grid;grid-template-columns:1fr 280px;gap:16px}.result-pages{display:grid;grid-template-columns:1fr;gap:12px}.result-pages article{border:1px solid var(--ant-color-border)}.result-pages article>div:last-child{padding:12px}.result-pages article header{display:flex;justify-content:space-between}.result-pages h3{font-size:var(--ant-font-size-sm);margin:12px 0 4px}.result-pages p,.result-pages small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.result-pages footer{display:flex;margin-top:8px}.result-pages footer .button{margin:0 4px 0 0;min-width:68px;height:27px}.dashboard-thumb{height:115px;background:var(--ant-color-bg-layout);position:relative;padding:12px 12px 12px 36px}.dashboard-thumb>i{position:absolute;left:0;top:0;bottom:0;width:25px;background:var(--ant-color-fill-secondary)}.dashboard-thumb>header{display:block;height:10px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border)}.dashboard-thumb>section{display:flex;gap:4px;margin-top:4px}.dashboard-thumb>section b{height:22px;flex:1;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-primary)}.dashboard-thumb>footer{position:absolute;left:35px;right:46px;bottom:10px;height:50px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);display:flex;align-items:flex-end;gap:4px;padding:8px}.dashboard-thumb>footer em{flex:1;background:var(--ant-color-primary);height:45%}.dashboard-thumb>footer em:nth-child(2){height:75%}.dashboard-thumb>footer em:nth-child(4){height:92%}.dashboard-thumb>span{position:absolute;right:9px;bottom:10px;width:31px;height:50px;background:conic-gradient(var(--ant-color-success) 0 72%,var(--ant-color-fill-secondary) 72%)}.dashboard-thumb-live{position:relative;min-height:115px;background:var(--ant-color-bg-layout);border-bottom:1px solid var(--ant-color-border);overflow:hidden}.dashboard-thumb-live>div{position:relative}.thumb-empty{position:absolute;inset:0;display:grid;place-items:center;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}result-menu div{display:flex;justify-content:space-between;border-bottom:1px solid var(--ant-color-border);padding:12px 4px}.result-menu span{background:var(--ant-color-bg-layout);border-radius:var(--ant-border-radius);padding:4px 8px}.text-button{border:0;background:transparent;color:var(--ant-color-primary);margin-top:12px}.score{display:flex;align-items:center;justify-content:center;gap:12px;padding:12px}.score b{width:55px;height:55px;border-radius:50%;border:5px solid var(--ant-color-success);display:grid;place-items:center;font-size:var(--ant-font-size)}.score span{color:var(--ant-color-success)}.validation{padding-left:12px;list-style:none;line-height:2;color:var(--ant-color-success)}.preview-shell{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);margin:-4px}.preview-toolbar{height:45px;display:flex;align-items:center;justify-content:space-between;padding:0 12px;border-bottom:1px solid var(--ant-color-border)}.preview-toolbar>div{display:flex;align-items:center;gap:8px}.preview-toolbar button{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);height:29px;padding:0 12px}.preview-main{display:grid;grid-template-columns:1fr 300px;height:655px}.cube-canvas{position:relative;background:var(--ant-color-bg-layout);border-right:1px solid var(--ant-color-border);overflow:hidden}.cube-top{position:absolute;left:0;right:0;top:0;height:32px;background:var(--ant-color-bg-container);border-top:3px solid var(--ant-color-primary-active);border-bottom:1px solid var(--ant-color-border);display:flex;align-items:center;padding:0 12px;gap:16px}.cube-top b{color:var(--ant-color-primary)}.cube-top i{margin-left:auto;font-style:normal;color:var(--ant-color-text-secondary)}.cube-side{position:absolute;left:0;top:32px;bottom:0;width:130px;background:var(--ant-color-bg-layout);display:flex;flex-direction:column}.cube-side strong{padding:16px 12px}.cube-side button{height:37px;border:0;background:transparent;text-align:left;padding-left:12px}.cube-side button.active{background:var(--ant-color-primary-bg);color:var(--ant-color-primary)}.cube-content{position:absolute;left:130px;top:32px;right:0;bottom:0;padding:12px}.cube-content>header{display:flex;justify-content:space-between;align-items:center}.cube-content>header h2{font-size:var(--ant-font-size);margin:4px 0 8px}.cube-content>header span,.cube-content>header small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.cube-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.cube-kpis>div{height:78px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);padding:8px;position:relative;display:flex;flex-direction:column}.cube-kpis span{font-size:var(--ant-font-size-sm);color:var(--ant-color-primary)}.cube-kpis b{font-size:var(--ant-font-size-heading-4);margin:4px 0}.cube-kpis small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.spark{position:absolute;right:9px;bottom:14px;width:42px;height:18px;border-top:3px solid var(--ant-color-primary);transform:skewY(-12deg);border-radius:50%}.spark.s1{transform:skewY(8deg)}.spark.s2{border-color:var(--ant-color-error)}.cube-grid{display:grid;grid-template-columns:1.7fr .8fr;gap:8px;margin-top:8px}.cube-grid>section{background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);padding:12px}.cube-grid h3{font-size:var(--ant-font-size-sm);margin:0 0 8px}.cube-grid h3 span,.cube-grid h3 button{float:right;border:0;background:transparent;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.line-chart{height:175px;position:relative}.line-chart>div{position:absolute;inset:0 0 20px;background:repeating-linear-gradient(to bottom,var(--ant-color-bg-container) 0,var(--ant-color-bg-container) 29px,var(--ant-color-fill-secondary) 30px)}.line-chart svg{position:absolute;left:0;right:0;top:0;height:145px;width:100%}.line-chart footer{position:absolute;left:0;right:0;bottom:0;display:flex;justify-content:space-between;font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.donut{width:115px;height:115px;border-radius:50%;background:conic-gradient(var(--ant-color-success) 0 80%,var(--ant-color-error-bg) 80% 92%,var(--ant-color-text-disabled) 92%);margin:12px auto;display:grid;place-items:center}.donut:before{content:"";position:absolute;width:72px;height:72px;background:var(--ant-color-bg-container);border-radius:50%}.donut b{z-index:1;font-size:var(--ant-font-size-heading-4);display:flex;flex-direction:column;align-items:center}.donut small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.donut-panel footer{display:flex;justify-content:space-around;font-size:var(--ant-font-size-sm)}.donut-panel footer i{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:4px}.green{background:var(--ant-color-success)}.orange{background:var(--ant-color-error-bg)}.gray{background:var(--ant-color-text-disabled)}.alarm-panel{grid-column:1/-1}.alarm-panel table{border-collapse:collapse;width:100%;font-size:var(--ant-font-size-sm)}.alarm-panel td{border-top:1px solid var(--ant-color-border);padding:8px}.inspector{background:var(--ant-color-bg-container);overflow:auto}.inspector>nav{height:42px;display:flex;border-bottom:1px solid var(--ant-color-border)}.inspector>nav button{flex:1;border:0;border-bottom:2px solid transparent;background:var(--ant-color-bg-container);font-size:var(--ant-font-size-sm)}.inspector>nav button.active{color:var(--ant-color-primary);border-bottom-color:var(--ant-color-primary)}.inspector-body{padding:16px}.inspector-body h3{font-size:var(--ant-font-size)}.inspector-body p{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);line-height:1.7}.inspector-body dl{display:grid;grid-template-columns:85px 1fr;border-top:1px solid var(--ant-color-border);margin:16px 0}.inspector-body dt,.inspector-body dd{margin:0;padding:8px 0;border-bottom:1px solid var(--ant-color-border);font-size:var(--ant-font-size-sm)}.inspector-body dt{color:var(--ant-color-text-secondary)}.component{padding:8px;border:1px solid var(--ant-color-border);margin-top:8px}.component span{float:right}.code-body header{text-align:right}.code-body header button{border:0;background:transparent;color:var(--ant-color-primary)}.code-body pre{font-size:var(--ant-font-size-sm);line-height:1.55;background:var(--ant-color-text-base);color:var(--ant-color-primary-bg);padding:12px;min-height:410px;overflow:auto}.inspector-body textarea{width:100%;height:120px;border:1px solid var(--ant-color-border);padding:8px;resize:none}.suggestion{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-layout);border-radius:var(--ant-border-radius);padding:4px 8px;margin:0 4px 8px 0;font-size:var(--ant-font-size-sm)}.inspector-body>.button{width:100%;margin:12px 0}.sticky-actions{position:fixed;z-index:20;left:232px;right:0;bottom:0;height:54px;background:var(--ant-color-bg-container);border-top:1px solid var(--ant-color-border);display:flex;align-items:center;justify-content:space-between;padding:0 20px;box-shadow:none}.toast{position:fixed;z-index:100;right:24px;top:56px;background:var(--ant-color-text-base);color:var(--ant-color-bg-container);padding:12px 20px;box-shadow:none}.toast b{color:var(--ant-color-success);margin-right:8px}.modal-backdrop{position:fixed;z-index:90;inset:0;background:color-mix(in srgb,var(--ant-color-bg-mask) 47%,transparent);display:grid;place-items:center}.modal{width:610px;max-height:78vh;background:var(--ant-color-bg-container);box-shadow:0 10px 30px color-mix(in srgb,var(--ant-color-text-base) 27%,transparent);display:flex;flex-direction:column}.modal>header{height:47px;border-bottom:1px solid var(--ant-color-border);display:flex;align-items:center;justify-content:space-between;padding:0 16px}.modal h2{font-size:var(--ant-font-size)}.modal>header button{border:0;background:transparent;font-size:var(--ant-font-size-heading-3);color:var(--ant-color-text-secondary)}.modal-body{padding:16px;overflow:auto;flex:1 1 auto;min-height:0}.modal>footer{border-top:1px solid var(--ant-color-border);padding:12px 16px;text-align:right}.modal-note{background:var(--ant-color-bg-layout);border-left:3px solid var(--ant-color-primary);padding:12px;margin-bottom:16px;color:var(--ant-color-primary)}.impact-title{font-weight:600;margin-bottom:8px}.impact-list{list-style:none;padding:0;border:1px solid var(--ant-color-border)}.impact-list li{display:flex;justify-content:space-between;padding:12px;border-bottom:1px solid var(--ant-color-border)}.impact-list span{color:var(--ant-color-primary)}.download-list{display:flex;flex-direction:column}.download-list label{padding:12px;border-bottom:1px solid var(--ant-color-border);display:flex;gap:8px}.download-list>div{margin-top:16px;background:var(--ant-color-bg-layout);padding:12px;display:flex;justify-content:space-between}.history-list>div{display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--ant-color-border);padding:12px}.history-list i{font-style:normal;width:34px;height:34px;border-radius:50%;background:var(--ant-color-bg-layout);color:var(--ant-color-primary);display:grid;place-items:center}.history-list span{display:flex;flex:1;flex-direction:column;gap:4px}.history-list small{color:var(--ant-color-text-secondary)}.history-list button{border:0;background:transparent;color:var(--ant-color-primary)}.login-page{min-height:100vh;background:linear-gradient(145deg,var(--ant-color-bg-layout),var(--ant-color-bg-container) 55%,var(--ant-color-bg-layout));border-top:4px solid var(--ant-color-primary-active);display:flex;align-items:center;justify-content:center}.login-top{position:absolute;left:18px;top:14px;display:flex;align-items:center;gap:12px}.login-top b{font-size:var(--ant-font-size)}.login-top span{border-left:1px solid var(--ant-color-border);padding-left:12px;color:var(--ant-color-text-secondary)}.login-card{width:420px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);box-shadow:none;padding:44px;text-align:center}.login-logo{font-size:var(--ant-font-size-heading-2)}.login-card h1{font-size:var(--ant-font-size-heading-3)}.login-card p{color:var(--ant-color-text-secondary);line-height:1.6}.sso-button{width:100%;height:48px;margin:24px 0 12px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);display:flex;align-items:center;gap:12px;padding:0 12px}.sso-button b{flex:1;text-align:left}.sso-button i{font-style:normal;font-size:var(--ant-font-size);color:var(--ant-color-primary)}.login-card>small{color:var(--ant-color-text-secondary)}.login-page>footer{position:absolute;bottom:18px;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}
.avatar-button{border:0;padding:0}
-.design-link{margin-left:auto;color:var(--blue);font-size:12px;text-decoration:none;padding:0 10px;border-right:1px solid #d4d7da}
-.design-link:hover{text-decoration:underline}.avatar-button:hover{outline:1px solid var(--blue)}
+.design-link{margin-left:auto;color:var(--ant-color-primary);font-size:var(--ant-font-size-sm);text-decoration:none;padding:0 12px;border-right:1px solid var(--ant-color-border)}
+.design-link:hover{text-decoration:underline}.avatar-button:hover{outline:1px solid var(--ant-color-primary)}
.steps{grid-template-columns:repeat(5,1fr)}
.domain-steps{grid-template-columns:repeat(7,1fr)}
-.module-switch{padding:10px 9px 6px;border-bottom:1px solid var(--line);display:grid;gap:6px}.module-switch>button{height:47px;border:1px solid #d8dde1;background:#fff;display:flex;align-items:center;text-align:left;padding:0 9px;gap:9px;font-size:14px}.module-switch>button>b{width:23px;height:23px;display:grid;place-items:center;background:#eff3f5;color:#6f7d85}.module-switch>button>span{display:flex;flex-direction:column;font-weight:bold;gap:2px}.module-switch>button small{font-weight:normal;font-size:9px;color:#8d959a}.module-switch>button.active{border-color:transparent;background:rgba(0,140,214,.1);color:#008cd6}.module-switch>button:hover{background:rgba(0,0,0,.04)}.module-switch>button.active>b{background:#008cd6;color:white}.side-nav>.side-title{display:none}
-.domain-hero{min-height:160px;background:linear-gradient(120deg,#075d8b,#009bdc 58%,#55c8df);color:white;margin-bottom:14px;padding:26px 30px;display:flex;align-items:center;justify-content:space-between;position:relative;overflow:hidden}.domain-hero:after{content:"◆";position:absolute;right:34%;top:-55px;font-size:230px;color:#ffffff0b;transform:rotate(16deg)}.domain-hero>div:first-child{max-width:640px;z-index:1}.domain-hero .eyebrow{font-size:9px;letter-spacing:2px;opacity:.8}.domain-hero h2{font-size:24px;margin:9px 0}.domain-hero p{margin:0;line-height:1.7;opacity:.86}.domain-hero-stats{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:#ffffff38;z-index:1}.domain-hero-stats div{width:105px;height:58px;background:#ffffff14;display:flex;flex-direction:column;align-items:center;justify-content:center}.domain-hero-stats b{font-size:20px}.domain-hero-stats span{font-size:9px;opacity:.8}.domain-filter label{width:300px}.domain-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;padding:14px 0 4px}.domain-grid article{border:1px solid var(--line);border-top:3px solid #83949c;padding:15px;min-height:230px;background:white}.domain-grid article.water{border-top-color:#009fe3}.domain-grid article.gas{border-top-color:#e58c35}.domain-grid article.energy{border-top-color:#e7b61d}.domain-grid article.dc{border-top-color:#6b70bf}.domain-grid article>header{display:flex;justify-content:space-between;align-items:center}.domain-grid article>header>span{font-size:28px;color:#168ebf}.domain-grid article>header>div{display:flex;align-items:center;gap:7px}.domain-grid h3{font-size:16px;margin:13px 0 6px}.domain-grid p{color:#70787d;font-size:10px;line-height:1.65;min-height:34px}.domain-metrics{display:grid;grid-template-columns:1fr 1fr;border-top:1px solid #edf0f2;border-bottom:1px solid #edf0f2;padding:8px 0;gap:7px}.domain-metrics span{font-size:9px;color:#888}.domain-metrics b{font-size:13px;color:#333;margin-right:3px}.domain-grid footer{display:flex;justify-content:space-between;margin-top:12px}.domain-grid footer button{border:0;background:transparent;color:#008ed2;font-size:10px}.domain-grid footer button:last-child{border:1px solid #a9d8ed;padding:5px 9px}.solution-parts{display:grid;grid-template-columns:repeat(5,1fr);gap:10px}.solution-parts>div{border:1px solid #dfe3e6;background:#fafbfb;padding:12px;display:flex;gap:9px;min-height:80px}.solution-parts i{font-style:normal;font-size:20px;color:#008ed2}.solution-parts span{display:flex;flex-direction:column;gap:6px}.solution-parts small{font-size:9px;line-height:1.5;color:#777}
-.solution-banner{min-height:126px;border:1px solid var(--line);background:linear-gradient(100deg,#eef9fd,#fff);display:flex;align-items:center;padding:20px;margin-bottom:12px}.solution-logo{width:72px;height:72px;background:#009fe3;color:white;border-radius:4px;display:grid;place-items:center;font-size:40px;margin-right:18px}.solution-banner>div:nth-child(2){flex:1}.solution-banner h2{margin:5px 0;font-size:21px}.solution-banner p{margin:0 0 8px;color:#666}.solution-banner small{margin-left:10px;color:#888}.solution-banner aside{display:flex}.solution-summary{display:grid;grid-template-columns:repeat(6,1fr);background:#fff;border:1px solid var(--line);margin-bottom:12px}.solution-summary>div{height:65px;border-right:1px solid var(--line);display:flex;flex-direction:column;align-items:center;justify-content:center}.solution-summary b{font-size:21px}.solution-summary span{font-size:9px;color:#888;margin-top:5px}.detail-grid{display:grid;grid-template-columns:1fr 310px;gap:12px}.asset-template{display:grid;grid-template-columns:1fr 1.3fr;gap:15px}.asset-tree{background:#f5f8f9;padding:12px;display:flex;flex-direction:column;gap:10px}.asset-tree span{padding-left:15px}.asset-tree em{font-style:normal;color:#788187;font-size:10px}.template-specs{display:grid;gap:8px}.template-specs>div{border:1px solid #e0e4e6;padding:9px}.template-specs p{margin:4px 0 0;color:#777;font-size:10px;line-height:1.45}.domain-page-list{display:grid;grid-template-columns:1fr 1fr;gap:10px}.domain-page-list article{display:grid;grid-template-columns:115px 1fr;border:1px solid var(--line);min-height:145px}.domain-page-thumb{margin:8px;background:#e9eef1;position:relative;padding:28px 7px 7px;display:grid;grid-template-columns:repeat(3,1fr);gap:4px}.domain-page-thumb span{position:absolute;left:7px;top:6px;color:#008ed2}.domain-page-thumb i{background:#75c9e9;height:40px}.domain-page-thumb i:nth-child(2){height:62px}.domain-page-thumb i:nth-child(3){height:50px}.domain-page-list section{padding:10px 9px 8px 0}.domain-page-list section>div{display:flex;justify-content:space-between}.domain-page-list h3{font-size:12px;margin:7px 0 3px}.domain-page-list section>b{font-size:9px;color:#008ed2}.domain-page-list p{font-size:9px;color:#777;line-height:1.5}.domain-page-list footer button{border:0;background:transparent;color:#008ed2;padding:0;margin-right:12px;font-size:9px}.flow-list{display:grid;gap:8px}.flow-list>div{display:flex;align-items:center;border:1px solid #e0e4e6;padding:9px}.flow-list i{font-style:normal;width:29px;height:29px;background:#e8f5fb;color:#008ed2;display:grid;place-items:center;margin-right:9px}.flow-list span{display:flex;flex:1;flex-direction:column;gap:3px}.flow-list small{font-size:9px;color:#888}.flow-list em{font-style:normal;font-size:9px;color:#008ed2}.flow-list .future-item{border-style:dashed;opacity:.75}.solution-menu{display:flex;flex-direction:column}.solution-menu>b{padding:8px;background:#f2f4f5;margin-top:4px}.solution-menu>span{padding:7px 8px 7px 17px;border-bottom:1px solid #eee}.solution-menu i{font-style:normal;font-size:8px;color:#008ed2;margin-right:7px}.knowledge-list>div{display:flex;align-items:center;border-bottom:1px solid #eee;padding:9px 2px}.knowledge-list>div>i{font-style:normal;width:27px;color:#008ed2}.knowledge-list span{display:flex;flex:1;flex-direction:column;gap:3px}.knowledge-list small{font-size:9px;color:#888}.knowledge-list button{border:0;background:transparent;color:#008ed2}
-.ai-config-layout{display:grid;grid-template-columns:1fr 370px;gap:14px}.wordbuddy-card{background:#1e1f21;color:#e9eaec;border-radius:7px;overflow:hidden;box-shadow:0 8px 22px #0002}.wordbuddy-card>header{height:58px;border-bottom:1px solid #3a3c3f;display:flex;justify-content:space-between;align-items:center;padding:0 14px}.wordbuddy-card>header>div{display:flex;gap:9px;align-items:center}.wb-logo{width:31px;height:31px;border-radius:6px;background:linear-gradient(135deg,#6d5ee9,#0aa5e8);display:grid;place-items:center;font-weight:bold}.wordbuddy-card header section{display:flex;flex-direction:column}.wordbuddy-card header small{font-size:9px;color:#9ca1a6;margin-top:3px}.wordbuddy-card header label{display:flex;align-items:center;gap:8px}.model-list{padding:7px}.model-list>button{width:100%;min-height:56px;border:0;border-radius:5px;background:transparent;color:#e7e8ea;display:flex;align-items:center;padding:7px 9px;text-align:left;position:relative}.model-list>button:hover,.model-list>button.selected{background:#2d2f32}.model-list>button>i{font-style:normal;width:25px;color:#c7cbd0;font-weight:bold}.model-list>button>span{display:flex;flex:1;flex-direction:column;gap:2px}.model-list>button>span>b{font-size:13px}.model-list>button small{font-size:8px;color:#16a9ed}.model-list>button em{font-style:normal;font-size:8px;color:#8f959b}.model-list>button>strong{color:#aeb3b8;font-size:10px;margin-right:24px}.model-list>button>u{position:absolute;right:9px;color:#35d19c;text-decoration:none;font-size:15px}.wordbuddy-card>footer{border-top:1px solid #3a3c3f;padding:9px}.wordbuddy-card>footer button{border:0;background:transparent;color:#c7cbd0}.selected-model{display:flex;align-items:center;gap:12px;background:#f0f9fd;border:1px solid #cfeaf6;padding:13px}.selected-model>span{width:42px;height:42px;border-radius:50%;background:linear-gradient(135deg,#7060eb,#00a5e5);color:white;display:grid;place-items:center}.selected-model section{flex:1}.selected-model h3{margin:2px 0;font-size:18px}.selected-model p{margin:0;font-size:9px;color:#777}.model-facts{display:grid;grid-template-columns:110px 1fr;margin:12px 0 0}.model-facts dt,.model-facts dd{margin:0;padding:7px;border-bottom:1px solid #eee;font-size:10px}.model-facts dt{color:#888}.strategy-list{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px}.strategy-list label{border:1px solid #dce1e4;padding:9px;display:flex;gap:7px}.strategy-list label>span{display:flex;flex-direction:column;gap:3px}.strategy-list small,.setting-row small{font-size:9px;color:#888}.setting-row{display:flex;align-items:center;border-top:1px solid #eee;padding:9px 2px}.setting-row>span{display:flex;flex:1;flex-direction:column;gap:3px}.context-list{display:grid;gap:6px}.context-list>span{display:flex;align-items:center;background:#f7f8f8;padding:7px}.context-list i{font-style:normal;width:25px;color:#008ed2}.context-list b{flex:1;font-size:10px}.context-list em{font-style:normal;color:#14905b;font-size:9px}.token-estimate{display:flex;flex-direction:column;border-top:1px solid #ddd;margin-top:10px;padding-top:10px;gap:4px}.token-estimate span{font-size:9px;color:#888}
-@media(max-width:1100px){.app-shell{padding-left:42px}.side-nav{display:none}.workspace{min-width:900px}.sticky-actions{left:42px}.top-actions small{display:none}.result-layout{grid-template-columns:1fr}.preview-main{grid-template-columns:1fr 260px}.mu-grid{grid-template-columns:repeat(2,1fr)}}
+.module-switch{padding:12px 8px 8px;border-bottom:1px solid var(--ant-color-border);display:grid;gap:8px}.module-switch>button{height:47px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);display:flex;align-items:center;text-align:left;padding:0 8px;gap:8px;font-size:var(--ant-font-size)}.module-switch>button>b{width:23px;height:23px;display:grid;place-items:center;background:var(--ant-color-bg-layout);color:var(--ant-color-text-secondary)}.module-switch>button>span{display:flex;flex-direction:column;font-weight:600;gap:4px}.module-switch>button small{font-weight:400;font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.module-switch>button.active{border-color:transparent;background:var(--ant-color-primary-bg);color:var(--ant-color-primary)}.module-switch>button:hover{background:var(--ant-color-fill-quaternary)}.module-switch>button.active>b{background:var(--ant-color-primary);color:var(--ant-color-bg-container)}.side-nav>.side-title{display:none}
+.domain-hero{min-height:160px;background:linear-gradient(120deg,var(--ant-color-primary),var(--ant-color-primary) 58%,var(--ant-color-primary));color:var(--ant-color-bg-container);margin-bottom:16px;padding:28px 32px;display:flex;align-items:center;justify-content:space-between;position:relative;overflow:hidden}.domain-hero:after{content:"◆";position:absolute;right:34%;top:-55px;font-size:var(--ant-font-size-heading-2);color:color-mix(in srgb,var(--ant-color-text-light-solid) 4%,transparent);transform:rotate(16deg)}.domain-hero>div:first-child{max-width:640px;z-index:1}.domain-hero .eyebrow{font-size:var(--ant-font-size-sm);letter-spacing:2px;opacity:.8}.domain-hero h2{font-size:var(--ant-font-size-heading-3);margin:8px 0}.domain-hero p{margin:0;line-height:1.7;opacity:.86}.domain-hero-stats{display:grid;grid-template-columns:1fr 1fr;gap:4px;background:color-mix(in srgb,var(--ant-color-bg-container) 22%,transparent);z-index:1}.domain-hero-stats div{width:105px;height:58px;background:color-mix(in srgb,var(--ant-color-bg-container) 8%,transparent);display:flex;flex-direction:column;align-items:center;justify-content:center}.domain-hero-stats b{font-size:var(--ant-font-size-heading-4)}.domain-hero-stats span{font-size:var(--ant-font-size-sm);opacity:.8}.domain-filter label{width:300px}.domain-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;padding:16px 0 4px}.domain-grid article{border:1px solid var(--ant-color-border);border-top:3px solid var(--ant-color-border);padding:16px;min-height:230px;background:var(--ant-color-bg-container)}.domain-grid article.water{border-top-color:var(--ant-color-primary)}.domain-grid article.gas{border-top-color:var(--ant-color-error)}.domain-grid article.energy{border-top-color:var(--ant-color-error-bg)}.domain-grid article.dc{border-top-color:var(--ant-color-primary)}.domain-grid article>header{display:flex;justify-content:space-between;align-items:center}.domain-grid article>header>span{font-size:var(--ant-font-size-heading-2);color:var(--ant-color-primary)}.domain-grid article>header>div{display:flex;align-items:center;gap:8px}.domain-grid h3{font-size:var(--ant-font-size);margin:12px 0 8px}.domain-grid p{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);line-height:1.65;min-height:34px}.domain-metrics{display:grid;grid-template-columns:1fr 1fr;border-top:1px solid var(--ant-color-border);border-bottom:1px solid var(--ant-color-border);padding:8px 0;gap:8px}.domain-metrics span{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.domain-metrics b{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-base);margin-right:4px}.domain-grid footer{display:flex;justify-content:space-between;margin-top:12px}.domain-grid footer button{border:0;background:transparent;color:var(--ant-color-primary);font-size:var(--ant-font-size-sm)}.domain-grid footer button:last-child{border:1px solid var(--ant-color-primary-bg);padding:4px 8px}.solution-parts{display:grid;grid-template-columns:repeat(5,1fr);gap:12px}.solution-parts>div{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-layout);padding:12px;display:flex;gap:8px;min-height:80px}.solution-parts i{font-style:normal;font-size:var(--ant-font-size-heading-4);color:var(--ant-color-primary)}.solution-parts span{display:flex;flex-direction:column;gap:8px}.solution-parts small{font-size:var(--ant-font-size-sm);line-height:1.5;color:var(--ant-color-text-secondary)}
+.solution-banner{min-height:126px;border:1px solid var(--ant-color-border);background:linear-gradient(100deg,var(--ant-color-bg-layout),var(--ant-color-bg-container));display:flex;align-items:center;padding:20px;margin-bottom:12px}.solution-logo{width:72px;height:72px;background:var(--ant-color-primary);color:var(--ant-color-bg-container);border-radius:var(--ant-border-radius);display:grid;place-items:center;font-size:var(--ant-font-size-heading-2);margin-right:20px}.solution-banner>div:nth-child(2){flex:1}.solution-banner h2{margin:4px 0;font-size:var(--ant-font-size-heading-4)}.solution-banner p{margin:0 0 8px;color:var(--ant-color-text-secondary)}.solution-banner small{margin-left:12px;color:var(--ant-color-text-secondary)}.solution-banner aside{display:flex}.solution-summary{display:grid;grid-template-columns:repeat(6,1fr);background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);margin-bottom:12px}.solution-summary>div{height:65px;border-right:1px solid var(--ant-color-border);display:flex;flex-direction:column;align-items:center;justify-content:center}.solution-summary b{font-size:var(--ant-font-size-heading-4)}.solution-summary span{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);margin-top:4px}.detail-grid{display:grid;grid-template-columns:1fr 310px;gap:12px}.asset-template{display:grid;grid-template-columns:1fr 1.3fr;gap:16px}.asset-tree{background:var(--ant-color-bg-layout);padding:12px;display:flex;flex-direction:column;gap:12px}.asset-tree span{padding-left:16px}.asset-tree em{font-style:normal;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.template-specs{display:grid;gap:8px}.template-specs>div{border:1px solid var(--ant-color-border);padding:8px}.template-specs p{margin:4px 0 0;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);line-height:1.45}.domain-page-list{display:grid;grid-template-columns:1fr 1fr;gap:12px}.domain-page-list article{display:grid;grid-template-columns:115px 1fr;border:1px solid var(--ant-color-border);min-height:145px}.domain-page-thumb{margin:8px;background:var(--ant-color-bg-layout);position:relative;padding:28px 8px 8px;display:grid;grid-template-columns:repeat(3,1fr);gap:4px}.domain-page-thumb span{position:absolute;left:7px;top:6px;color:var(--ant-color-primary)}.domain-page-thumb i{background:var(--ant-color-primary);height:40px}.domain-page-thumb i:nth-child(2){height:62px}.domain-page-thumb i:nth-child(3){height:50px}.domain-page-list section{padding:12px 8px 8px 0}.domain-page-list section>div{display:flex;justify-content:space-between}.domain-page-list h3{font-size:var(--ant-font-size-sm);margin:8px 0 4px}.domain-page-list section>b{font-size:var(--ant-font-size-sm);color:var(--ant-color-primary)}.domain-page-list p{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);line-height:1.5}.domain-page-list footer button{border:0;background:transparent;color:var(--ant-color-primary);padding:0;margin-right:12px;font-size:var(--ant-font-size-sm)}.flow-list{display:grid;gap:8px}.flow-list>div{display:flex;align-items:center;border:1px solid var(--ant-color-border);padding:8px}.flow-list i{font-style:normal;width:29px;height:29px;background:var(--ant-color-bg-layout);color:var(--ant-color-primary);display:grid;place-items:center;margin-right:8px}.flow-list span{display:flex;flex:1;flex-direction:column;gap:4px}.flow-list small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.flow-list em{font-style:normal;font-size:var(--ant-font-size-sm);color:var(--ant-color-primary)}.flow-list .future-item{border-style:dashed;opacity:.75}.solution-menu{display:flex;flex-direction:column}.solution-menu>b{padding:8px;background:var(--ant-color-bg-layout);margin-top:4px}.solution-menu>span{padding:8px 8px 8px 16px;border-bottom:1px solid var(--ant-color-border)}.solution-menu i{font-style:normal;font-size:var(--ant-font-size-sm);color:var(--ant-color-primary);margin-right:8px}.knowledge-list>div{display:flex;align-items:center;border-bottom:1px solid var(--ant-color-border);padding:8px 4px}.knowledge-list>div>i{font-style:normal;width:27px;color:var(--ant-color-primary)}.knowledge-list span{display:flex;flex:1;flex-direction:column;gap:4px}.knowledge-list small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.knowledge-list button{border:0;background:transparent;color:var(--ant-color-primary)}
+.ai-config-layout{display:grid;grid-template-columns:1fr 370px;gap:16px}.wordbuddy-card{background:var(--ant-color-text-base);color:var(--ant-color-fill-secondary);border-radius:var(--ant-border-radius);overflow:hidden;box-shadow:none}.wordbuddy-card>header{height:58px;border-bottom:1px solid var(--ant-color-border);display:flex;justify-content:space-between;align-items:center;padding:0 16px}.wordbuddy-card>header>div{display:flex;gap:8px;align-items:center}.wb-logo{width:31px;height:31px;border-radius:var(--ant-border-radius);background:linear-gradient(135deg,var(--ant-color-primary),var(--ant-color-primary));display:grid;place-items:center;font-weight:600}.wordbuddy-card header section{display:flex;flex-direction:column}.wordbuddy-card header small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);margin-top:4px}.wordbuddy-card header label{display:flex;align-items:center;gap:8px}.model-list{padding:8px}.model-list>button{width:100%;min-height:56px;border:0;border-radius:var(--ant-border-radius);background:transparent;color:var(--ant-color-fill-secondary);display:flex;align-items:center;padding:8px 8px;text-align:left;position:relative}.model-list>button:hover,.model-list>button.selected{background:var(--ant-color-text-base)}.model-list>button>i{font-style:normal;width:25px;color:var(--ant-color-text-disabled);font-weight:600}.model-list>button>span{display:flex;flex:1;flex-direction:column;gap:4px}.model-list>button>span>b{font-size:var(--ant-font-size-sm)}.model-list>button small{font-size:var(--ant-font-size-sm);color:var(--ant-color-primary)}.model-list>button em{font-style:normal;font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.model-list>button>strong{color:var(--ant-color-text-disabled);font-size:var(--ant-font-size-sm);margin-right:24px}.model-list>button>u{position:absolute;right:9px;color:var(--ant-color-success);text-decoration:none;font-size:var(--ant-font-size)}.wordbuddy-card>footer{border-top:1px solid var(--ant-color-border);padding:8px}.wordbuddy-card>footer button{border:0;background:transparent;color:var(--ant-color-text-disabled)}.selected-model{display:flex;align-items:center;gap:12px;background:var(--ant-color-bg-layout);border:1px solid var(--ant-color-primary);padding:12px}.selected-model>span{width:42px;height:42px;border-radius:50%;background:linear-gradient(135deg,var(--ant-color-primary),var(--ant-color-primary));color:var(--ant-color-bg-container);display:grid;place-items:center}.selected-model section{flex:1}.selected-model h3{margin:4px 0;font-size:var(--ant-font-size)}.selected-model p{margin:0;font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.model-facts{display:grid;grid-template-columns:110px 1fr;margin:12px 0 0}.model-facts dt,.model-facts dd{margin:0;padding:8px;border-bottom:1px solid var(--ant-color-border);font-size:var(--ant-font-size-sm)}.model-facts dt{color:var(--ant-color-text-secondary)}.strategy-list{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px}.strategy-list label{border:1px solid var(--ant-color-border);padding:8px;display:flex;gap:8px}.strategy-list label>span{display:flex;flex-direction:column;gap:4px}.strategy-list small,.setting-row small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.setting-row{display:flex;align-items:center;border-top:1px solid var(--ant-color-border);padding:8px 4px}.setting-row>span{display:flex;flex:1;flex-direction:column;gap:4px}.context-list{display:grid;gap:8px}.context-list>span{display:flex;align-items:center;background:var(--ant-color-bg-layout);padding:8px}.context-list i{font-style:normal;width:25px;color:var(--ant-color-primary)}.context-list b{flex:1;font-size:var(--ant-font-size-sm)}.context-list em{font-style:normal;color:var(--ant-color-success);font-size:var(--ant-font-size-sm)}.token-estimate{display:flex;flex-direction:column;border-top:1px solid var(--ant-color-border);margin-top:12px;padding-top:12px;gap:4px}.token-estimate span{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}
+@media(max-width:1100px){.app-shell{padding-left:44px}.side-nav{display:none}.workspace{min-width:900px}.sticky-actions{left:42px}.top-actions small{display:none}.result-layout{grid-template-columns:1fr}.preview-main{grid-template-columns:1fr 260px}.mu-grid{grid-template-columns:repeat(2,1fr)}}
/* 右下角浮动 AI 会话 */
-.ai-fab{position:fixed;right:22px;bottom:22px;z-index:80;width:54px;height:54px;border-radius:50%;border:0;background:transparent;box-shadow:0 8px 22px #0a85c355;display:grid;place-items:center;cursor:pointer;touch-action:none;user-select:none}.ai-fab-icon{width:54px;height:54px;border-radius:50%;object-fit:cover;-webkit-user-drag:none;user-drag:none;user-select:none;pointer-events:none}
-.design-doc-link{display:block;margin-top:auto;margin-left:10px;margin-right:10px;margin-bottom:10px;padding:5px 8px;font-size:10px;line-height:1.4;font-weight:600;text-align:center;color:#fff;text-decoration:none;border-radius:6px;background:linear-gradient(135deg,#7e57c2 0%,#ab47bc 100%);box-shadow:0 1px 4px #7e57c255;opacity:.95;transition:opacity .15s}
-.design-doc-link:hover{opacity:1;color:#fff}
+.ai-fab{position:fixed;right:22px;bottom:22px;z-index:80;width:54px;height:54px;border-radius:50%;border:0;background:transparent;box-shadow:0 8px 22px color-mix(in srgb,var(--ant-color-primary) 33%,transparent);display:grid;place-items:center;cursor:pointer;touch-action:none;user-select:none}.ai-fab-icon{width:54px;height:54px;border-radius:50%;object-fit:cover;-webkit-user-drag:none;user-drag:none;user-select:none;pointer-events:none}
+.design-doc-link{display:block;margin-top:auto;margin-left:12px;margin-right:12px;margin-bottom:12px;padding:4px 8px;font-size:var(--ant-font-size-sm);line-height:1.4;font-weight:600;text-align:center;color:var(--ant-color-text-light-solid);text-decoration:none;border-radius:var(--ant-border-radius);background:linear-gradient(135deg,var(--ant-color-primary) 0%,var(--ant-color-primary-bg) 100%);box-shadow:none;opacity:.95;transition:opacity .15s}
+.design-doc-link:hover{opacity:1;color:var(--ant-color-text-light-solid)}
.ai-fab:active{cursor:grabbing}
-.ai-fab:hover:not(:active){transform:translateY(-1px);box-shadow:0 12px 26px #0a85c366}
-.ai-dialog{position:fixed;right:22px;bottom:88px;z-index:81;width:25vw;min-width:320px;height:70vh;min-height:420px;background:#fff;border:1px solid #d2d6d9;border-radius:10px;box-shadow:0 18px 48px #0003;display:flex;flex-direction:column;overflow:hidden}
-.ai-dialog header{display:flex;align-items:center;gap:9px;padding:10px 12px;border-bottom:1px solid #eceef0;background:linear-gradient(135deg,#6d5ee9,#0aa5e8);color:#fff}
-.ai-dialog .ai-dialog-logo{width:28px;height:28px;border-radius:7px;background:#ffffff33;display:grid;place-items:center;font-size:15px}
+.ai-fab:hover:not(:active){transform:translateY(-1px);box-shadow:0 12px 26px color-mix(in srgb,var(--ant-color-primary-hover) 40%,transparent)}
+.ai-dialog{position:fixed;right:22px;bottom:88px;z-index:81;width:25vw;min-width:320px;height:70vh;min-height:420px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);box-shadow:0 18px 48px color-mix(in srgb,var(--ant-color-text-base) 20%,transparent);display:flex;flex-direction:column;overflow:hidden}
+.ai-dialog header{display:flex;align-items:center;gap:8px;padding:12px 12px;border-bottom:1px solid var(--ant-color-border);background:linear-gradient(135deg,var(--ant-color-primary),var(--ant-color-primary));color:var(--ant-color-text-light-solid)}
+.ai-dialog .ai-dialog-logo{width:28px;height:28px;border-radius:var(--ant-border-radius);background:color-mix(in srgb,var(--ant-color-bg-container) 20%,transparent);display:grid;place-items:center;font-size:var(--ant-font-size)}
.ai-dialog .ai-dialog-title{flex:1;display:flex;flex-direction:column;line-height:1.25}
-.ai-dialog .ai-dialog-title b{font-size:13px}
-.ai-dialog .ai-dialog-title small{font-size:9px;opacity:.85}
-.ai-dialog .ai-close{border:0;background:transparent;color:#fff;font-size:20px;line-height:1;width:28px;height:28px;border-radius:6px}
-.ai-dialog .ai-close:hover{background:#ffffff33}
-.ai-messages{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:10px;background:#f7f8f9}
-.ai-welcome{width:100%;margin:auto 0;display:flex;flex-direction:column;gap:10px;color:#323233}.ai-welcome-intro{display:flex;align-items:center;gap:10px;padding-bottom:2px}.ai-welcome-intro>span{width:32px;height:32px;border-radius:4px;background:#e5f4fb;color:#008cd6;display:grid;place-items:center;flex:0 0 auto}.ai-welcome-intro>div{min-width:0}.ai-welcome-intro b{display:block;font-size:14px;line-height:1.4}.ai-welcome-intro p{margin:2px 0 0;color:#777;font-size:12px;line-height:1.5}.ai-capability-list{border:1px solid #dfe3e6;background:#fff}.ai-capability-list>div{min-height:52px;padding:8px 10px;display:flex;align-items:center;gap:10px;border-bottom:1px solid #edf0f2}.ai-capability-list>div:last-child{border-bottom:0}.ai-capability-list i{width:22px;height:22px;border-radius:50%;background:#e5f4fb;color:#008cd6;display:grid;place-items:center;flex:0 0 auto;font-size:12px;font-style:normal;font-weight:600}.ai-capability-list span{display:flex;flex-direction:column;gap:2px;min-width:0}.ai-capability-list b{font-size:13px;line-height:1.35;font-weight:500}.ai-capability-list small{color:#777;font-size:11px;line-height:1.35}.ai-welcome-tip{padding:8px 10px;background:#eef7fb;border-left:3px solid #008cd6;color:#666;font-size:11px;line-height:1.5}.ai-welcome-tip b{color:#323233}
+.ai-dialog .ai-dialog-title b{font-size:var(--ant-font-size-sm)}
+.ai-dialog .ai-dialog-title small{font-size:var(--ant-font-size-sm);opacity:.85}
+.ai-dialog .ai-close{border:0;background:transparent;color:var(--ant-color-text-light-solid);font-size:var(--ant-font-size-heading-4);line-height:1;width:28px;height:28px;border-radius:var(--ant-border-radius)}
+.ai-dialog .ai-close:hover{background:color-mix(in srgb,var(--ant-color-bg-container) 20%,transparent)}
+.ai-messages{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:12px;background:var(--ant-color-bg-layout)}
+.ai-welcome{width:100%;margin:auto 0;display:flex;flex-direction:column;gap:12px;color:var(--ant-color-text-base)}.ai-welcome-intro{display:flex;align-items:center;gap:12px;padding-bottom:4px}.ai-welcome-intro>span{width:32px;height:32px;border-radius:var(--ant-border-radius);background:var(--ant-color-bg-layout);color:var(--ant-color-primary);display:grid;place-items:center;flex:0 0 auto}.ai-welcome-intro>div{min-width:0}.ai-welcome-intro b{display:block;font-size:var(--ant-font-size);line-height:1.4}.ai-welcome-intro p{margin:4px 0 0;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);line-height:1.5}.ai-capability-list{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container)}.ai-capability-list>div{min-height:52px;padding:8px 12px;display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--ant-color-border)}.ai-capability-list>div:last-child{border-bottom:0}.ai-capability-list i{width:22px;height:22px;border-radius:50%;background:var(--ant-color-bg-layout);color:var(--ant-color-primary);display:grid;place-items:center;flex:0 0 auto;font-size:var(--ant-font-size-sm);font-style:normal;font-weight:600}.ai-capability-list span{display:flex;flex-direction:column;gap:4px;min-width:0}.ai-capability-list b{font-size:var(--ant-font-size-sm);line-height:1.35;font-weight:600}.ai-capability-list small{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);line-height:1.35}.ai-welcome-tip{padding:8px 12px;background:var(--ant-color-bg-layout);border-left:3px solid var(--ant-color-primary);color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);line-height:1.5}.ai-welcome-tip b{color:var(--ant-color-text-base)}
.ai-bubble{display:flex;gap:8px;align-items:flex-start;max-width:88%}
-.ai-bubble .ai-avatar{width:26px;height:26px;border-radius:50%;flex:0 0 auto;display:grid;place-items:center;font-size:11px;background:#e3edf3;color:#168abf}
+.ai-bubble .ai-avatar{width:26px;height:26px;border-radius:50%;flex:0 0 auto;display:grid;place-items:center;font-size:var(--ant-font-size-sm);background:var(--ant-color-fill-secondary);color:var(--ant-color-primary)}
.ai-bubble.me{flex-direction:row-reverse;align-self:flex-end}
-.ai-bubble.me .ai-avatar{background:linear-gradient(135deg,#7060eb,#00a5e5);color:#fff}
-.ai-bubble .ai-text{white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.6;padding:8px 11px;border-radius:9px;background:#fff;border:1px solid #e6e9eb}
-.ai-bubble.me .ai-text{background:#009fe3;color:#fff;border-color:#009fe3}
-.ai-dialog footer{display:flex;gap:8px;padding:9px;border-top:1px solid #eceef0;background:#fff}
-.ai-dialog .ai-input{flex:1;resize:none;height:42px;max-height:120px;border:1px solid #d4d8db;border-radius:7px;padding:9px 10px;font-size:12px;line-height:1.4;outline:none}
-.ai-dialog .ai-input:focus{border-color:#8ccce9}
-.ai-dialog .ai-send{border:0;background:#009fe3;color:#fff;border-radius:7px;padding:0 16px;font-size:12px;align-self:flex-end;height:42px}
+.ai-bubble.me .ai-avatar{background:linear-gradient(135deg,var(--ant-color-primary),var(--ant-color-primary));color:var(--ant-color-text-light-solid)}
+.ai-bubble .ai-text{white-space:pre-wrap;word-break:break-word;font-size:var(--ant-font-size-sm);line-height:1.6;padding:8px 12px;border-radius:var(--ant-border-radius);background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border)}
+.ai-bubble.me .ai-text{background:var(--ant-color-primary);color:var(--ant-color-text-light-solid);border-color:var(--ant-color-primary)}
+.ai-dialog footer{display:flex;gap:8px;padding:8px;border-top:1px solid var(--ant-color-border);background:var(--ant-color-bg-container)}
+.ai-dialog .ai-input{flex:1;resize:none;height:42px;max-height:120px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:8px 12px;font-size:var(--ant-font-size-sm);line-height:1.4;outline:none}
+.ai-dialog .ai-input:focus{border-color:var(--ant-color-primary-bg)}
+.ai-dialog .ai-send{border:0;background:var(--ant-color-primary);color:var(--ant-color-text-light-solid);border-radius:var(--ant-border-radius);padding:0 16px;font-size:var(--ant-font-size-sm);align-self:flex-end;height:42px}
.ai-dialog .ai-send:disabled{opacity:.5;cursor:not-allowed}
-.ai-dialog .ai-send:not(:disabled):hover{background:#008ac4}
+.ai-dialog .ai-send:not(:disabled):hover{background:var(--ant-color-primary-hover)}
/* ===== 新建站点向导:基本属性 ===== */
.domain-select{display:flex;align-items:center;gap:8px}
-.domain-select input{flex:1;border:1px solid var(--line);border-radius:3px;height:30px;padding:0 9px;background:#fff}
-.domain-select input:focus{border-color:var(--blue);outline:none}
-.domain-add{display:flex;align-items:center;gap:8px;margin-top:9px}
-.domain-add input{flex:1;border:1px solid var(--line);border-radius:3px;height:30px;padding:0 9px;background:#fff}
-.domain-add input:focus{border-color:var(--blue);outline:none}
-.file-list{list-style:none;margin:10px 0 0;padding:0;display:flex;flex-direction:column;gap:6px}
-.file-list li{display:flex;align-items:center;gap:6px;background:var(--soft);border:1px solid var(--line);border-radius:4px;padding:6px 9px;font-size:12px}
-.file-list a{color:var(--blue);text-decoration:none}
+.domain-select input{flex:1;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);height:30px;padding:0 8px;background:var(--ant-color-bg-container)}
+.domain-select input:focus{border-color:var(--ant-color-primary);outline:none}
+.domain-add{display:flex;align-items:center;gap:8px;margin-top:8px}
+.domain-add input{flex:1;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);height:30px;padding:0 8px;background:var(--ant-color-bg-container)}
+.domain-add input:focus{border-color:var(--ant-color-primary);outline:none}
+.file-list{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-direction:column;gap:8px}
+.file-list li{display:flex;align-items:center;gap:8px;background:var(--ant-color-bg-layout);border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:8px 8px;font-size:var(--ant-font-size-sm)}
+.file-list a{color:var(--ant-color-primary);text-decoration:none}
.file-list a:hover{text-decoration:underline}
-.file-list small{color:var(--muted);margin-left:auto}
-.invalid{border-color:var(--red)!important;background:#fff4f2!important}
-.hint{color:var(--muted);font-size:11px;margin:0 0 10px;line-height:1.6}
+.file-list small{color:var(--ant-color-text-secondary);margin-left:auto}
+.invalid{border-color:var(--ant-color-error)!important;background:var(--ant-color-bg-layout)!important}
+.hint{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);margin:0 0 12px;line-height:1.6}
/* ===== 可编辑表格内的输入控件 ===== */
-.data-table input,.data-table select,.data-table textarea{border:1px solid #d9dee2;border-radius:3px;height:28px;padding:0 8px;width:100%;background:#fff;font-size:12px}
-.data-table input:focus,.data-table select:focus,.data-table textarea:focus{border-color:var(--blue);outline:none}
+.data-table input,.data-table select,.data-table textarea{border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);height:28px;padding:0 8px;width:100%;background:var(--ant-color-bg-container);font-size:var(--ant-font-size-sm)}
+.data-table input:focus,.data-table select:focus,.data-table textarea:focus{border-color:var(--ant-color-primary);outline:none}
.data-table .number{width:100%;text-align:right}
-.data-table input::placeholder{color:#b9bfc3}
+.data-table input::placeholder{color:var(--ant-color-text-disabled)}
/* 站点列表:列宽可手动拖动调整(拖拽表头右缘的分隔条) */
.data-table.resizable-cols{table-layout:fixed;width:100%}
.data-table.resizable-cols th{position:relative}
.data-table .col-resizer{position:absolute;top:0;right:0;width:6px;height:100%;cursor:col-resize;z-index:2;user-select:none}
.data-table .col-resizer::after{content:"";position:absolute;top:0;right:2px;width:2px;height:100%;background:transparent}
-.data-table .col-resizer:hover::after,.data-table .col-resizer.active::after{background:var(--blue)}
+.data-table .col-resizer:hover::after,.data-table .col-resizer.active::after{background:var(--ant-color-primary)}
/* 可编辑表格使用固定列宽,避免输入框在自动布局下被压窄(设备类型框变窄的 BUG) */
.data-table.editable{table-layout:fixed}
.data-table.editable th:nth-child(1){width:40%}
.data-table.editable th:nth-child(4){width:auto}
.data-table.editable th:nth-child(5){width:48px}
.data-table.editable input,.data-table.editable select,.data-table.editable textarea{min-width:0;width:100%}
-.data-table.editable .trash{border:0;background:transparent;color:var(--red);font-size:15px;cursor:pointer;line-height:1}
-.data-table.editable .trash:hover{color:#d63723}
-.data-table.editable .asset-eye-btn{border:0;background:transparent;color:var(--blue);font-size:18px;cursor:pointer;line-height:1;padding:0;display:inline-flex;align-items:center}
-.data-table.editable .asset-eye-btn.disabled{color:#c4c9cc;cursor:default}
+.data-table.editable .trash{border:0;background:transparent;color:var(--ant-color-error);font-size:var(--ant-font-size);cursor:pointer;line-height:1}
+.data-table.editable .trash:hover{color:var(--ant-color-error)}
+.data-table.editable .asset-eye-btn{border:0;background:transparent;color:var(--ant-color-primary);font-size:var(--ant-font-size);cursor:pointer;line-height:1;padding:0;display:inline-flex;align-items:center}
+.data-table.editable .asset-eye-btn.disabled{color:var(--ant-color-text-disabled);cursor:default}
/* ===== 领域方案库:列表筛选栏 + 可排序列头 ===== */
-.filter-bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:10px}
-.filter-bar .filter-search{flex:1 1 220px;min-width:180px;height:30px;border:1px solid #d9dee2;border-radius:3px;padding:0 10px;font-size:12px;background:#fff}
-.filter-bar .filter-search:focus{border-color:var(--blue);outline:none}
-.filter-bar select{height:30px;border:1px solid #d9dee2;border-radius:3px;padding:0 8px;font-size:12px;background:#fff;color:#444}
-.filter-bar select:focus{border-color:var(--blue);outline:none}
-.filter-bar .filter-count{margin-left:auto;font-size:12px}
+.filter-bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:12px}
+.filter-bar .filter-search{flex:1 1 220px;min-width:180px;height:30px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:0 12px;font-size:var(--ant-font-size-sm);background:var(--ant-color-bg-container)}
+.filter-bar .filter-search:focus{border-color:var(--ant-color-primary);outline:none}
+.filter-bar select{height:30px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:0 8px;font-size:var(--ant-font-size-sm);background:var(--ant-color-bg-container);color:var(--ant-color-text-secondary)}
+.filter-bar select:focus{border-color:var(--ant-color-primary);outline:none}
+.filter-bar .filter-count{margin-left:auto;font-size:var(--ant-font-size-sm)}
.data-table th.sortable{cursor:pointer;user-select:none;white-space:nowrap}
-.data-table th.sortable:hover{color:var(--blue)}
-.data-table th.sortable .sort-ind{color:var(--blue);font-size:10px}
+.data-table th.sortable:hover{color:var(--ant-color-primary)}
+.data-table th.sortable .sort-ind{color:var(--ant-color-primary);font-size:var(--ant-font-size-sm)}
/* ===== Assets 资产配置 ===== */
-.empty-row td{background:#fafbfc}
-.empty-row input{color:#b7bcc0;background:#fafbfc}
-.empty-row input::placeholder{color:#c3c8cc}
-.asset-tree-preview{background:#f5f8f9;border:1px solid #e7ebed;border-radius:6px;padding:12px 14px;font-size:12px}
-.tree-root{font-weight:bold;color:#33414a;margin-bottom:8px;display:flex;align-items:center;gap:5px}
-.tree-branch{margin:4px 0 4px 14px;padding-left:12px;border-left:1px dashed #cdd5d9}
-.tree-node{display:flex;align-items:center;gap:6px;font-weight:bold;color:#3a4750}
-.site-structure-title{margin-top:18px}
-.site-structure{grid-column:2;margin-top:6px}
-.site-structure .tree-branch{margin:4px 0 4px 0;padding-left:12px;border-left:1px solid #ddd}
-.site-structure .tree-node{font-weight:normal;color:#555;font-size:14px}
-.site-structure .tree-node.current-site{color:var(--blue);background:rgba(0,140,214,.08);border-radius:3px;padding:1px 4px;margin-left:-4px}
-.tree-count{background:var(--pale);color:var(--blue2);border-radius:9px;padding:0 7px;font-size:10px;font-weight:normal}
-.tree-leaves{color:#788187;font-size:11px;line-height:1.7;margin:3px 0 0 20px;word-break:break-all}
+.empty-row td{background:var(--ant-color-bg-layout)}
+.empty-row input{color:var(--ant-color-text-disabled);background:var(--ant-color-bg-layout)}
+.empty-row input::placeholder{color:var(--ant-color-text-disabled)}
+.asset-tree-preview{background:var(--ant-color-bg-layout);border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:12px 16px;font-size:var(--ant-font-size-sm)}
+.tree-root{font-weight:600;color:var(--ant-color-text-secondary);margin-bottom:8px;display:flex;align-items:center;gap:4px}
+.tree-branch{margin:4px 0 4px 16px;padding-left:12px;border-left:1px dashed var(--ant-color-border)}
+.tree-node{display:flex;align-items:center;gap:8px;font-weight:600;color:var(--ant-color-text-secondary)}
+.site-structure-title{margin-top:20px}
+.site-structure{grid-column:2;margin-top:8px}
+.site-structure .tree-branch{margin:4px 0 4px 0;padding-left:12px;border-left:1px solid var(--ant-color-border)}
+.site-structure .tree-node{font-weight:400;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size)}
+.site-structure .tree-node.current-site{color:var(--ant-color-primary);background:var(--ant-color-primary-bg);border-radius:var(--ant-border-radius);padding:4px 4px;margin-left:-4px}
+.tree-count{background:var(--ant-color-primary-bg);color:var(--ant-color-primary-active);border-radius:var(--ant-border-radius);padding:0 8px;font-size:var(--ant-font-size-sm);font-weight:400}
+.tree-leaves{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);line-height:1.7;margin:4px 0 0 20px;word-break:break-all}
/* ===== Functions 功能与菜单 ===== */
-.mu-tree{display:flex;flex-direction:column;gap:14px}
-.mu-group>b{display:block;color:var(--blue2);font-size:13px;margin-bottom:6px}
-.mu-items{display:flex;flex-direction:column;gap:6px;padding:6px 0 4px 18px;border-left:1px dashed var(--line);margin-left:6px}
-.mu-items label{display:flex;align-items:flex-start;gap:7px;border:1px solid var(--line);border-radius:5px;padding:7px 9px;background:#fff;cursor:pointer;font-size:12px}
-.mu-items label:hover{border-color:#9ed3ec;background:#f4fafe}
-.mu-items label.disabled{opacity:.5;cursor:not-allowed;background:#f6f7f8}
-.mu-items label input{margin-top:2px;accent-color:var(--blue)}
+.mu-tree{display:flex;flex-direction:column;gap:16px}
+.mu-group>b{display:block;color:var(--ant-color-primary-active);font-size:var(--ant-font-size-sm);margin-bottom:8px}
+.mu-items{display:flex;flex-direction:column;gap:8px;padding:8px 0 4px 20px;border-left:1px dashed var(--ant-color-border);margin-left:8px}
+.mu-items label{display:flex;align-items:flex-start;gap:8px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:8px 8px;background:var(--ant-color-bg-container);cursor:pointer;font-size:var(--ant-font-size-sm)}
+.mu-items label:hover{border-color:var(--ant-color-primary-bg);background:var(--ant-color-bg-layout)}
+.mu-items label.disabled{opacity:.5;cursor:not-allowed;background:var(--ant-color-bg-layout)}
+.mu-items label input{margin-top:4px;accent-color:var(--ant-color-primary)}
.mu-items label span{display:flex;flex-direction:column;line-height:1.3}
-.mu-items label small{color:var(--muted);font-size:10px}
+.mu-items label small{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}
.two-column>div:first-child{min-width:0}
.cube-editor{overflow-x:auto}
.cube-editor table{width:100%}
.cube-editor .cube-editor-table th:nth-child(3){width:140px}
.cube-editor .cube-editor-table th:nth-child(4){width:200px}
.cube-editor .cube-editor-table td:nth-child(3){white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
-.cube-actions{display:flex;gap:5px;white-space:nowrap}
-.cube-actions button{border:1px solid var(--line);background:#fff;border-radius:4px;height:26px;padding:0 9px;font-size:11px;color:#444}
-.cube-actions button:hover{border-color:var(--blue);color:var(--blue)}
-.cube-actions .trash{border-color:#f0c4bd;color:var(--red)}
-.cube-actions .trash:hover{background:#fdeae6;border-color:var(--red)}
+.cube-actions{display:flex;gap:4px;white-space:nowrap}
+.cube-actions button{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);border-radius:var(--ant-border-radius);height:26px;padding:0 8px;font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}
+.cube-actions button:hover{border-color:var(--ant-color-primary);color:var(--ant-color-primary)}
+.cube-actions .trash{border-color:var(--ant-color-error-bg);color:var(--ant-color-error)}
+.cube-actions .trash:hover{background:var(--ant-color-bg-layout);border-color:var(--ant-color-error)}
/* 醒目 AI 操作按钮(渐变蓝紫) */
-.btn-ai{display:inline-flex;align-items:center;gap:4px;background:linear-gradient(135deg,#2563eb,#7c3aed);color:#fff;border:1px solid transparent;border-radius:5px;height:26px;padding:0 11px;font-size:11px;font-weight:600;cursor:pointer;box-shadow:0 1px 4px rgba(37,99,235,.35);white-space:nowrap}
+.btn-ai{display:inline-flex;align-items:center;gap:4px;background:linear-gradient(135deg,var(--ant-color-primary),var(--ant-color-primary));color:var(--ant-color-text-light-solid);border:1px solid transparent;border-radius:var(--ant-border-radius);height:26px;padding:0 12px;font-size:var(--ant-font-size-sm);font-weight:600;cursor:pointer;box-shadow:none;white-space:nowrap}
.btn-ai:hover{filter:brightness(1.08)}
.btn-ai:active{transform:translateY(1px)}
.btn-ai:disabled{opacity:.65;cursor:default;box-shadow:none}
.cube-actions .btn-ai{height:26px}
-.menu-editor{display:flex;flex-direction:column;gap:10px}
+.menu-editor{display:flex;flex-direction:column;gap:12px}
.menu-row{display:grid;grid-template-columns:1.4fr 1fr 1.4fr auto;gap:8px;align-items:center}
-.menu-row input,.menu-row select{border:1px solid var(--line);border-radius:3px;height:30px;padding:0 8px;background:#fff;width:100%}
-.menu-row .trash{border:1px solid #f0c4bd;background:#fff;color:var(--red);border-radius:4px;height:30px;width:34px;font-size:15px}
-.menu-row .trash:hover{background:#fdeae6;border-color:var(--red)}
-.menu-preview .menu-tree{border:1px solid var(--line);background:#fff}
-.menu-preview .menu-root{padding:10px 12px;font-weight:bold;border-bottom:1px solid var(--line);background:#fafbfc}
-.menu-preview .menu-section{padding:10px 12px;border-bottom:1px solid #eee}
+.menu-row input,.menu-row select{border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);height:30px;padding:0 8px;background:var(--ant-color-bg-container);width:100%}
+.menu-row .trash{border:1px solid var(--ant-color-error-bg);background:var(--ant-color-bg-container);color:var(--ant-color-error);border-radius:var(--ant-border-radius);height:30px;width:34px;font-size:var(--ant-font-size)}
+.menu-row .trash:hover{background:var(--ant-color-bg-layout);border-color:var(--ant-color-error)}
+.menu-preview .menu-tree{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container)}
+.menu-preview .menu-root{padding:12px 12px;font-weight:600;border-bottom:1px solid var(--ant-color-border);background:var(--ant-color-bg-layout)}
+.menu-preview .menu-section{padding:12px 12px;border-bottom:1px solid var(--ant-color-border)}
.menu-preview .menu-section:last-child{border-bottom:0}
-.menu-preview .menu-section-title{font-size:12px;color:var(--blue2);font-weight:bold;margin-bottom:7px}
-.menu-preview .menu-leaf{padding:5px 0 5px 16px;font-size:12px;color:#555;border-left:2px solid #e1e7ea;margin:3px 0 3px 4px}
-.menu-preview .menu-leaf.cube{border-left-color:#c9b8f0}
-.menu-preview .menu-leaf.muted{color:#aaa;border-left-color:#eee}
+.menu-preview .menu-section-title{font-size:var(--ant-font-size-sm);color:var(--ant-color-primary-active);font-weight:600;margin-bottom:8px}
+.menu-preview .menu-leaf{padding:4px 0 4px 16px;font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);border-left:2px solid var(--ant-color-border);margin:4px 0 4px 4px}
+.menu-preview .menu-leaf.cube{border-left-color:var(--ant-color-primary)}
+.menu-preview .menu-leaf.muted{color:var(--ant-color-text-disabled);border-left-color:var(--ant-color-border)}
/* ===== Generate 预览 line 面板 ===== */
-.line-panel{background:#fff;border:1px solid var(--line);border-radius:6px;padding:11px 13px;display:flex;flex-direction:column;gap:8px}
-.line-panel h3{margin:0;font-size:13px;display:flex;align-items:center;justify-content:space-between}
-.line-panel h3 span{color:var(--muted);font-size:10px;font-weight:normal}
+.line-panel{background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:12px 12px;display:flex;flex-direction:column;gap:8px}
+.line-panel h3{margin:0;font-size:var(--ant-font-size-sm);display:flex;align-items:center;justify-content:space-between}
+.line-panel h3 span{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);font-weight:400}
/* ===== 生成提示词查看 ===== */
.prompt-list{display:flex;flex-direction:column;gap:8px}
-.prompt-row{display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid var(--line);border-radius:5px;padding:8px 11px;background:#fff}
+.prompt-row{display:flex;align-items:center;justify-content:space-between;gap:12px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:8px 12px;background:var(--ant-color-bg-container)}
.prompt-row span{display:flex;flex-direction:column;line-height:1.35;min-width:0}
-.prompt-row span small{color:var(--muted);font-size:10px;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:420px}
-.prompt-row b{font-size:13px}
+.prompt-row span small{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);margin-top:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:420px}
+.prompt-row b{font-size:var(--ant-font-size-sm)}
.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]{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 input[type=checkbox]{display:block;width:16px;height:16px;padding:0;margin:0 auto;appearance:none;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);box-shadow:none;background:var(--ant-color-bg-container);cursor:pointer;vertical-align:middle}
+.data-table input[type=checkbox]:checked{border-color:var(--ant-color-primary);background-color:var(--ant-color-primary);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='var(--ant-color-bg-container)' 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 var(--ant-color-primary-bg);outline-offset:1px}
.data-table thead th:first-child{width:34px;text-align:center}
-.link.danger{color:var(--red)}
+.link.danger{color:var(--ant-color-error)}
.link.danger:hover{text-decoration:underline}
.project-grid article{position:relative}
-.card-del{position:absolute;top:8px;right:8px;border:1px solid #f0c4bd;background:#fff;color:var(--red);border-radius:4px;height:26px;padding:0 9px;font-size:11px;cursor:pointer}
-.card-del:hover{background:#fdeae6;border-color:var(--red)}
-.style-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;padding:14px 0 4px}
-.style-card{border:1px solid var(--line);background:white;display:flex;flex-direction:column}
-.style-card .style-thumb{position:relative;aspect-ratio:16/9;min-height:200px;background:#f2f5f7;display:grid;place-items:center;cursor:zoom-in;overflow:hidden}
+.card-del{position:absolute;top:8px;right:8px;border:1px solid var(--ant-color-error-bg);background:var(--ant-color-bg-container);color:var(--ant-color-error);border-radius:var(--ant-border-radius);height:26px;padding:0 8px;font-size:var(--ant-font-size-sm);cursor:pointer}
+.card-del:hover{background:var(--ant-color-bg-layout);border-color:var(--ant-color-error)}
+.style-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;padding:16px 0 4px}
+.style-card{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);display:flex;flex-direction:column}
+.style-card .style-thumb{position:relative;aspect-ratio:16/9;min-height:200px;background:var(--ant-color-bg-layout);display:grid;place-items:center;cursor:zoom-in;overflow:hidden}
.style-card .style-thumb img{width:100%;height:100%;object-fit:contain}
-.style-card .style-thumb .zoom{position:absolute;right:8px;bottom:8px;background:#0008;color:#fff;width:24px;height:24px;border-radius:3px;display:grid;place-items:center;font-size:13px}
-.style-info{padding:11px 12px 2px}
-.style-info h3{font-size:14px;margin:0}
-.style-info small{color:#999;font-size:10px}
-.style-card footer{display:flex;justify-content:flex-end;gap:14px;padding:8px 12px}
+.style-card .style-thumb .zoom{position:absolute;right:8px;bottom:8px;background:color-mix(in srgb,var(--ant-color-bg-mask) 53%,transparent);color:var(--ant-color-text-light-solid);width:24px;height:24px;border-radius:var(--ant-border-radius);display:grid;place-items:center;font-size:var(--ant-font-size-sm)}
+.style-info{padding:12px 12px 4px}
+.style-info h3{font-size:var(--ant-font-size);margin:0}
+.style-info small{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}
+.style-card footer{display:flex;justify-content:flex-end;gap:16px;padding:8px 12px}
.style-lightbox{max-width:92vw;max-height:88vh}
.style-upload-row{display:flex;gap:12px;align-items:stretch}
-.style-upload-prev{width:150px;height:120px;border:1px solid #d8dde0;display:grid;place-items:center;background:#f7f9fa;overflow:hidden}
+.style-upload-prev{width:150px;height:120px;border:1px solid var(--ant-color-border);display:grid;place-items:center;background:var(--ant-color-bg-layout);overflow:hidden}
.style-upload-prev img{width:100%;height:100%;object-fit:contain}
-.style-json-row{display:flex;gap:10px;align-items:flex-start}
-.style-json-row textarea{flex:1;min-height:150px;resize:vertical;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;line-height:1.5}
-.style-json-row .upload{width:120px;height:64px;flex:0 0 auto;margin-top:2px}
+.style-json-row{display:flex;gap:12px;align-items:flex-start}
+.style-json-row textarea{flex:1;min-height:150px;resize:vertical;font-family:var(--ant-font-family);font-size:var(--ant-font-size-sm);line-height:1.5}
+.style-json-row .upload{width:120px;height:64px;flex:0 0 auto;margin-top:4px}
.modal>footer .spacer{flex:1}
-.style-picker{margin-top:18px;border-top:1px dashed var(--line);padding-top:14px}
-.style-picker h4{margin:0 0 4px;font-size:14px}
-.style-picker h4 small{color:#999;font-weight:normal;font-size:10px;margin-left:6px}
-.style-pick-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:10px;margin-top:8px}
-.style-pick{border:1px solid var(--line);background:white;padding:7px;display:flex;flex-direction:column;gap:6px;cursor:pointer;text-align:center;color:#555}
-.style-pick:hover{border-color:var(--blue)}
-.style-pick.active{border:2px solid var(--blue);background:#f4fbff;color:#007cc3}
-.style-pick-thumb{height:78px;background:#f2f5f7;display:grid;place-items:center;overflow:hidden}
+.style-picker{margin-top:20px;border-top:1px dashed var(--ant-color-border);padding-top:16px}
+.style-picker h4{margin:0 0 4px;font-size:var(--ant-font-size)}
+.style-picker h4 small{color:var(--ant-color-text-secondary);font-weight:400;font-size:var(--ant-font-size-sm);margin-left:8px}
+.style-pick-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:12px;margin-top:8px}
+.style-pick{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);padding:8px;display:flex;flex-direction:column;gap:8px;cursor:pointer;text-align:center;color:var(--ant-color-text-secondary)}
+.style-pick:hover{border-color:var(--ant-color-primary)}
+.style-pick.active{border:2px solid var(--ant-color-primary);background:var(--ant-color-bg-layout);color:var(--ant-color-primary)}
+.style-pick-thumb{height:78px;background:var(--ant-color-bg-layout);display:grid;place-items:center;overflow:hidden}
.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}
+.style-pick-thumb.none{color:var(--ant-color-text-disabled);font-size:var(--ant-font-size-sm)}
+.style-pick span{font-size:var(--ant-font-size-sm);line-height:1.3}
/* ========================================================================== */
/* Company workspace standard — measured from the WISE-IoT service desk UI. */
/* 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}
+html,body{font-family:var(--ant-font-family);font-size:var(--ant-font-size);line-height:1.5;color:var(--ant-color-text-base);background:var(--ant-color-bg-container)}
+.app-shell{min-height:100vh;padding-top:56px;padding-left:200px;background:var(--ant-color-bg-container)}
+.topbar{height:56px;border-top:4px solid var(--ant-color-primary);border-bottom:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);background-image:none;box-shadow:none;padding:0 20px;gap:12px}
+.topbar>strong{font-size:var(--ant-font-size-heading-3);color:var(--ant-color-primary);letter-spacing:-.3px}.topbar .brand-mark{font-size:var(--ant-font-size-heading-4);color:var(--ant-color-primary)}
+.top-actions{gap:16px}.top-actions a{color:var(--ant-color-primary);text-decoration:none}.top-actions small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}
+.hamburger{font-size:var(--ant-font-size);color:var(--ant-color-text-secondary)}.divider{height:22px}
+.side-nav{top:56px;width:200px;background:var(--ant-color-bg-layout);border-right:1px solid var(--ant-color-border)}
+.module-switch{padding:0;border-bottom:1px solid var(--ant-color-border);display:flex;flex-direction:column;gap:0}
+.module-switch>button{height:50px;border:0;border-radius:0;background:transparent;padding:0 20px;gap:12px;font-size:var(--ant-font-size);font-weight:600}
+.module-switch>button>b{width:18px;height:auto;background:transparent;color:var(--ant-color-text-secondary)}
+.module-switch>button>span{display:block;font-weight:600}.module-switch>button small{display:none}
+.module-switch>button:hover{background:var(--ant-color-bg-layout)}.module-switch>button.active{background:var(--ant-color-primary-bg);color:var(--ant-color-primary)}
+.module-switch>button.active>b{background:transparent;color:var(--ant-color-primary)}
+.side-nav>small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);padding:20px 20px 8px;letter-spacing:0;text-transform:none}
+.side-nav>button{height:44px;padding:0 20px;border-left:0;font-size:var(--ant-font-size);gap:12px}
+.side-nav>button.active{background:var(--ant-color-primary-bg);color:var(--ant-color-primary);border-left:0;font-weight:600}
+.side-nav>button em{width:20px;height:20px;font-size:var(--ant-font-size-sm)}
+.site-switcher{height:32px;width:calc(100% - 32px);margin:8px 16px;border-color:var(--ant-color-border);border-radius:var(--ant-border-radius);font-size:var(--ant-font-size-sm)}
+.side-help{margin:auto 16px 16px;border:0;border-top:1px solid var(--ant-color-border);background:transparent;padding:16px 4px 0}
+.workspace{min-height:calc(100vh - 56px);padding-bottom:72px;background:var(--ant-color-bg-container)}.crumb{display:none}
+.page-heading{min-height:64px;padding:12px 24px;background:var(--ant-color-bg-layout);border-bottom:1px solid var(--ant-color-border)}
+.page-heading h1{font-size:var(--ant-font-size-heading-3);line-height:36px;font-weight:600;margin:0;color:var(--ant-color-text-base)}
+.page-heading p{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);margin:0}.page-heading>div:last-child{display:flex;gap:8px}
+.content{padding:0 24px 24px;background:var(--ant-color-bg-container)}
+.button.ant-btn{min-width:112px;height:32px;margin:0;border-radius:var(--ant-border-radius);padding:0 24px;font-size:var(--ant-font-size);font-weight:600;box-shadow:none}
+.button.ant-btn-primary{background:var(--ant-color-primary);border-color:var(--ant-color-primary)}.button.ant-btn-primary:hover{background:var(--ant-color-primary-hover)!important;border-color:var(--ant-color-primary-hover)!important}
+.button.ant-btn-default,.button.secondary{background:var(--ant-color-bg-container);color:var(--ant-color-text-secondary);border-color:var(--ant-color-border)}.button.ant-btn-default:hover,.button.secondary:hover{color:var(--ant-color-primary)!important;border-color:var(--ant-color-primary)!important;background:var(--ant-color-bg-container)!important}
+.steps{height:64px;border-top:0;border-bottom:1px solid var(--ant-color-border);padding:0 24px;background:var(--ant-color-bg-container)}.steps button{padding:0 20px;font-size:var(--ant-font-size)}.steps button>i{width:26px;height:26px}.steps button.active>i{background:var(--ant-color-primary);border-color:var(--ant-color-primary)}
+.panel{border:1px solid var(--ant-color-border);border-radius:0;background:var(--ant-color-bg-container);margin:16px 0;box-shadow:none}.panel-head{min-height:48px;padding:0 16px;background:var(--ant-color-bg-container)}.panel-head>strong{font-size:var(--ant-font-size)}.panel-body{padding:16px}
+.company-status-tabs{height:56px;display:flex;align-items:flex-end;border-bottom:1px solid var(--ant-color-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:var(--ant-color-bg-container);color:var(--ant-color-text-secondary);font-size:var(--ant-font-size)}
+.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 8px;border-radius:var(--ant-border-radius);background:var(--ant-color-bg-layout);color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}
+.company-status-tabs button.active{color:var(--ant-color-primary);border-bottom-color:var(--ant-color-primary);font-weight:600}.company-status-tabs button.active b{background:var(--ant-color-fill-secondary);color:var(--ant-color-success)}
.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}
+.company-filterbar{height:auto;min-height:56px;padding:12px 0;gap:8px}.company-filterbar .search-field{width:240px;height:32px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:0 12px;background:var(--ant-color-bg-container)}.company-filterbar .search-field:focus-within{border-color:var(--ant-color-primary)}
+.company-filterbar select{height:32px;min-width:160px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);background:var(--ant-color-bg-container);padding:0 28px 0 12px}
.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-table{table-layout:fixed}.company-table th{height:44px;background:var(--ant-color-bg-layout);font-size:var(--ant-font-size);font-weight:600;padding:0 12px}.company-table td{height:50px;padding:8px 12px;font-size:var(--ant-font-size);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.company-table tbody tr{cursor:pointer}.company-table tbody tr:hover td{background:var(--ant-color-bg-layout)}.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:var(--ant-font-size)}.company-record-link b{color:var(--ant-color-primary)}
+.status.ant-tag{height:24px;line-height:21px;border-radius:var(--ant-border-radius);padding:0 12px;margin:0;font-size:var(--ant-font-size-sm)}.status.ok{color:var(--ant-color-success);background:var(--ant-color-success-bg);border-color:var(--ant-color-success)}.status.warn{color:var(--ant-color-warning);background:var(--ant-color-warning-bg);border-color:var(--ant-color-warning)}.status.bad{color:var(--ant-color-error);background:var(--ant-color-error-bg);border-color:var(--ant-color-error)}.status.muted{color:var(--ant-color-text-secondary);background:var(--ant-color-bg-layout);border-color:var(--ant-color-border)}
+.pagination{height:56px;padding:12px 0 0;gap:0}.pagination span{border:1px solid var(--ant-color-border);height:32px;line-height:30px;padding:0 12px;margin-right:8px}.pagination button,.pagination select{height:32px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container)}.pagination button{width:34px}.pagination button.active{background:var(--ant-color-bg-container);color:var(--ant-color-text-base)}.pagination select{margin-left:8px;padding:0 12px}
+.company-detail .detail-status{margin-bottom:20px}.company-detail h3{font-size:var(--ant-font-size);margin:20px 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 var(--ant-color-border)}.company-detail dt{color:var(--ant-color-text-secondary)}
+.ant-drawer .ant-drawer-header{min-height:64px;border-bottom:1px solid var(--ant-color-border)}.ant-drawer .ant-drawer-title{font-size:var(--ant-font-size-heading-4);font-weight:600}.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>i.form-section-nav-dot{width:8px;height:8px;border-radius:50%;background:var(--company-primary);justify-self:center;align-self: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;align-items:flex-end;justify-content:flex-start;gap:12px;margin-top:4px}.field-assist .button{flex:none;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}.ai-assist-drawer .field>span{font-size:16px;font-weight:500}.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)}
+.form-section-nav{position:sticky;top:76px;display:grid;grid-template-columns:32px 1fr;padding:4px 0;color:var(--ant-color-text-base)}.form-section-nav>i.form-section-nav-dot{width:8px;height:8px;border-radius:50%;background:var(--ant-color-primary);justify-self:center;align-self:center}.form-section-nav>span{font-size:var(--ant-font-size);font-weight:600;line-height:24px;margin-bottom:12px}.form-section-nav>a{grid-column:2;color:var(--ant-color-text-secondary);text-decoration:none;padding:8px 0 8px 12px;border-left:1px solid var(--ant-color-border)}.form-section-nav>a:hover{color:var(--ant-color-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:var(--ant-font-size)}.single-column-form .field input,.single-column-form .field select{height:40px;border-radius:var(--ant-border-radius);padding:0 12px}.single-column-form .field textarea{min-height:112px;border-radius:var(--ant-border-radius);padding:12px 12px}.single-column-form .field textarea.small{min-height:88px}.single-column-form .field small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}
+.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;align-items:flex-end;justify-content:flex-start;gap:12px;margin-top:4px}.field-assist .button{flex:none;min-width:140px}.form-section-heading{max-width:680px;border-top:1px solid var(--ant-color-border);padding-top:20px;margin-top:4px}.form-section-heading h3{font-size:var(--ant-font-size);margin:0 0 4px}.form-section-heading p{margin:0;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.form-guidance{max-width:680px;padding:12px 16px;background:var(--ant-color-bg-layout);border-left:3px solid var(--ant-color-primary);display:flex;flex-direction:column;gap:4px}.form-guidance span{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.save-indicator{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}
+.upload{height:96px;border-color:var(--ant-color-border);background:var(--ant-color-bg-layout);border-radius:var(--ant-border-radius)}.upload b{font-size:var(--ant-font-size-heading-4)}.upload span{font-size:var(--ant-font-size)}.upload small{font-size:var(--ant-font-size-sm)}
+.ai-assist-intro{display:flex;gap:16px;padding:16px;background:var(--ant-color-bg-layout);border:1px solid var(--ant-color-border)}.ai-assist-intro>.anticon{font-size:var(--ant-font-size-heading-3);color:var(--ant-color-primary)}.ai-assist-intro h3{font-size:var(--ant-font-size);margin:0 0 8px}.ai-assist-intro p{margin:0;color:var(--ant-color-text-secondary);line-height:1.7}.ai-assist-drawer .field>span{font-size:var(--ant-font-size);font-weight:600}.drawer-actions{display:flex;gap:8px;margin:20px 0}.drawer-actions .button{min-width:120px}
+.result-header{min-height:72px;border:0;border-bottom:1px solid var(--ant-color-border);padding:12px 0;margin:0}.result-header h2{font-size:var(--ant-font-size-heading-4)}.result-header .site-icon{border-radius:var(--ant-border-radius);background:var(--ant-color-primary);color:var(--ant-color-text-light-solid);border:0;font-size:var(--ant-font-size)}.result-actions{display:flex;gap:8px}.result-actions .button{min-width:112px}
+.kpi-row{border-bottom:1px solid var(--ant-color-border);gap:0;margin:0 0 16px}.kpi-row>div{height:72px;border:0;border-right:1px solid var(--ant-color-border);padding:12px 16px}.kpi-row>div:last-child{border-right:0}.kpi-row span{color:var(--ant-color-text-secondary)}.kpi-row b{font-size:var(--ant-font-size-heading-3)}
+.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(--ant-color-border);padding:16px 0}.result-pages article>div:last-child{padding:0 20px}.result-pages h3{font-size:var(--ant-font-size);margin:16px 0 8px}.result-pages p{font-size:var(--ant-font-size-sm);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(--ant-color-border);background:var(--ant-color-bg-layout)}
+.text-button{color:var(--ant-color-primary);font-size:var(--ant-font-size)}.modal,.login-card{border-radius:var(--ant-border-radius)}.modal{box-shadow:0 6px 24px color-mix(in srgb,var(--ant-color-text-base) 13%,transparent)}.toast{top:68px;border-radius:var(--ant-border-radius);background:var(--ant-color-text-base)}
+.ai-fab{box-shadow:0 4px 12px color-mix(in srgb,var(--ant-color-text-base) 20%,transparent)}.ai-dialog{border-radius:var(--ant-border-radius)}.ai-dialog header{background:var(--ant-color-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}}
+@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:12px 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}
-.extension-menu-items{padding-bottom:2px}.extension-menu-item{height:50px;padding:0 20px 0 40px;display:flex;align-items:center;font-size:17px;line-height:50px;color:#323233;font-weight:500;cursor:default}.extension-submenu-title{justify-content:space-between;cursor:pointer}.extension-submenu-title>.anticon{font-size:11px;margin-right:1px}.extension-third-menu>div{height:50px;padding:0 20px 0 60px;display:flex;align-items:center;color:#323233;font-size:17px;line-height:50px;font-weight:500;cursor:default}.extension-menu-section .extension-menu-item:hover,.extension-menu-section .extension-third-menu>div:hover{background:transparent;color:inherit}
-.extension-third-menu>button{height:50px;padding:0 20px 0 60px;display:flex;align-items:center;color:#323233;font-size:17px;line-height:50px;font-weight:500;cursor:pointer;border:0;background:transparent;width:100%;text-align:left;font-family:inherit}
-.extension-menu-section .extension-third-menu>button.clickable:hover{background:#e8f4fb;color:var(--company-primary)}
-.extension-menu-section .extension-third-menu>button.clickable.active{background:#d7eef9;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}
+.company-side-nav{position:fixed;z-index:35;top:56px;bottom:0;left:0;width:200px;background:var(--ant-color-bg-layout);border-right:1px solid var(--ant-color-border);display:flex;flex-direction:column;color:var(--ant-color-text-base);font-family:var(--ant-font-family)}
+.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:var(--ant-font-family);font-weight:600}
+.menu-section-title{height:56px;padding:0 20px;display:flex;align-items:center;justify-content:space-between;font-size:var(--ant-font-size-heading-4);line-height:56px}
+.menu-section-title>span{display:block}.menu-section-title>.anticon{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-base)}
+.menu-section-title:hover{background:var(--ant-color-fill-secondary)}.menu-section.current>.menu-section-title{color:var(--ant-color-text-base);font-weight:600}
+.menu-section-items button{height:50px;padding:0 20px 0 40px;display:flex;align-items:center;font-size:var(--ant-font-size);line-height:50px;color:var(--ant-color-text-base)}
+.menu-section-items button:hover{background:var(--ant-color-fill-secondary);color:var(--ant-color-primary)}
+.menu-section-items button.active{background:var(--ant-color-primary-bg);color:var(--ant-color-primary);font-weight:600}
+.extension-menu-items{padding-bottom:4px}.extension-menu-item{height:50px;padding:0 20px 0 40px;display:flex;align-items:center;font-size:var(--ant-font-size);line-height:50px;color:var(--ant-color-text-base);font-weight:600;cursor:default}.extension-submenu-title{justify-content:space-between;cursor:pointer}.extension-submenu-title>.anticon{font-size:var(--ant-font-size-sm);margin-right:4px}.extension-third-menu>div{height:50px;padding:0 20px 0 60px;display:flex;align-items:center;color:var(--ant-color-text-base);font-size:var(--ant-font-size);line-height:50px;font-weight:600;cursor:default}.extension-menu-section .extension-menu-item:hover,.extension-menu-section .extension-third-menu>div:hover{background:transparent;color:inherit}
+.extension-third-menu>button{height:50px;padding:0 20px 0 60px;display:flex;align-items:center;color:var(--ant-color-text-base);font-size:var(--ant-font-size);line-height:50px;font-weight:600;cursor:pointer;border:0;background:transparent;width:100%;text-align:left;font-family:var(--ant-font-family)}
+.extension-menu-section .extension-third-menu>button.clickable:hover{background:var(--ant-color-bg-layout);color:var(--ant-color-primary)}
+.extension-menu-section .extension-third-menu>button.clickable.active{background:var(--ant-color-primary-bg);color:var(--ant-color-primary);font-weight:600}
+.company-side-footer{position:relative;margin:auto 16px 16px;padding:16px 4px 0;border-top:1px solid var(--ant-color-border);display:flex;flex-direction:column;gap:8px;font-size:var(--ant-font-size-sm)}
+.company-side-footer>b{color:var(--ant-color-primary)}.company-side-footer>button{height:26px;padding:0;border:0;background:transparent;display:flex;align-items:center;justify-content:space-between;color:var(--ant-color-text-secondary);text-align:left}
+.company-model-menu{position:absolute;left:0;right:0;bottom:30px;padding:8px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);box-shadow:none}
+.company-model-menu button{width:100%;height:32px;border:0;background:var(--ant-color-bg-container);display:flex;justify-content:space-between;align-items:center;padding:0 8px}.company-model-menu button:hover{background:var(--ant-color-bg-layout)}.company-model-menu span{color:var(--ant-color-primary);font-size:var(--ant-font-size-sm)}
.sticky-actions{left:200px}
@media(max-width:720px){.company-side-nav{display:none}.app-shell{padding-left:0}.sticky-actions{left:0}}
-.site-edit-switcher{display:flex;align-items:center;gap:10px;color:var(--company-muted);font-size:14px}.site-edit-switcher select{width:220px;height:34px;border:1px solid var(--company-border);background:#fff;border-radius:2px;padding:0 30px 0 10px;color:#323233}.site-edit-switcher select:focus{outline:1px solid var(--company-primary);border-color:var(--company-primary)}
-.site-edit-tabs{height:64px;background:#fff;border-top:1px solid var(--company-border);border-bottom:1px solid var(--company-border);display:flex;padding:0 24px;gap:0}.site-edit-tabs>button{height:64px;flex:1;min-width:0;border:0;border-bottom:3px solid transparent;background:#fff;color:#6f6f70;display:flex;align-items:center;gap:10px;padding:0 20px;font-size:14px;text-align:left}.site-edit-tabs>button i{font-style:normal;width:26px;height:26px;border:1px solid #c9cfd3;border-radius:50%;display:grid;place-items:center;background:#fff}.site-edit-tabs>button:hover{color:var(--company-primary);background:#fafcfd}.site-edit-tabs>button.active{color:var(--company-primary);border-bottom-color:var(--company-primary);font-weight:600}.site-edit-tabs>button.active i{background:var(--company-primary);border-color:var(--company-primary);color:#fff}.site-step-blank{min-height:calc(100vh - 286px);background:#fff}
-.asset-config-shell{min-width:0}.asset-config-tabs{height:66px;display:flex;align-items:flex-end;border:1px solid var(--company-border);border-bottom:0;background:#fff;padding:0 18px;gap:34px}.asset-config-tabs>button{height:65px;min-width:210px;padding:10px 4px 8px;border:0;border-bottom:3px solid transparent;background:transparent;text-align:left;display:flex;flex-direction:column;justify-content:center;gap:4px;color:#777}.asset-config-tabs>button span{font-size:16px;font-weight:600}.asset-config-tabs>button small{font-size:12px;color:#999}.asset-config-tabs>button.active{color:var(--company-primary);border-bottom-color:var(--company-primary)}.asset-config-tabs>button.active small{color:#666}.quick-asset-config{border-top:1px solid var(--company-border);padding-top:14px}.manual-asset-list{border-top:1px solid var(--company-border);padding-top:14px}.manual-asset-list>.panel{margin:0}.manual-asset-filter{padding:0 0 14px;margin-bottom:0}.asset-category{border-top:1px solid var(--company-border);margin:0 -12px}.asset-category>header{height:46px;display:flex;align-items:center;gap:9px;padding:0 14px;background:#f7f7f7}.asset-category>header strong{font-size:14px}.asset-category>header span{color:#888;font-size:12px}.asset-category .company-table{margin:0}.asset-category .company-table th:first-child,.asset-category .company-table td:first-child{padding-left:38px}.asset-group-dialog{width:540px}.dialog-form{display:flex;flex-direction:column;gap:18px;margin:20px 0}.dialog-form input,.dialog-form select{height:38px;border:1px solid var(--company-border);padding:0 10px;background:#fff}
-.manual-asset-editor{border-top:1px solid var(--company-border);padding-top:0;background:#fff}.asset-editor-title{height:64px;border:1px solid var(--company-border);border-top:0;display:flex;align-items:center;padding:0 16px;gap:18px}.asset-editor-title>.text-button{margin:0;padding-right:18px;border-right:1px solid #ddd}.asset-editor-title>div{flex:1;display:flex;align-items:baseline;gap:12px}.asset-editor-title h2{font-size:18px;margin:0}.asset-editor-title>div>span{color:#888}.asset-editor-title>.button{margin:0}.asset-editor-workbench{min-height:620px;border:1px solid var(--company-border);border-top:0;display:grid;grid-template-columns:250px 1fr}.asset-editor-side{border-right:1px solid var(--company-border);background:#fafafa}.asset-editor-side>.field{padding:16px;border-bottom:1px solid var(--company-border)}.asset-editor-side>.field input{background:#fff}.asset-batch-entry{width:100%;height:54px;border:0;border-bottom:1px solid var(--company-border);background:#fff;text-align:left;padding:0 16px;font-size:15px}.asset-batch-entry:hover{color:var(--company-primary);background:#f5fbfe}.asset-template-panel{padding:16px;display:flex;flex-direction:column;gap:10px}.asset-template-panel>strong{font-size:16px}.asset-template-panel>select{height:34px;border:1px solid var(--company-border);background:#fff;padding:0 8px}.asset-template-panel>button{min-height:58px;border:1px solid #dce2e6;background:#fff;display:grid;grid-template-columns:24px 1fr;grid-template-rows:1fr 1fr;text-align:left;padding:8px;column-gap:6px}.asset-template-panel>button>span{grid-row:1/3;color:var(--company-primary);align-self:center}.asset-template-panel>button>b{font-size:13px}.asset-template-panel>button>small{font-size:11px;color:#888}.asset-template-panel>button:hover{border-color:var(--company-primary)}.asset-tree-workspace{min-width:0;background:#f7f8f9}.asset-editor-toolbar{height:52px;border-bottom:1px solid var(--company-border);background:#fff;padding:0 12px;display:flex;align-items:center;justify-content:space-between}.asset-editor-toolbar>div{display:flex;align-items:center;gap:8px}.asset-editor-toolbar .button{margin:0;min-width:auto}.asset-tree-search{width:210px;height:30px;border:1px solid var(--company-border);background:#fff;display:flex;align-items:center;padding:0 8px;color:#999}.asset-tree-search input{border:0;outline:0;width:100%;padding-left:7px}.asset-tree-canvas{min-height:567px;padding:28px;overflow:auto}.asset-tree-root-card{width:220px;min-height:54px;margin:0 auto 24px;border:1px solid #b9c3c9;border-left:4px solid var(--company-primary);background:#fff;box-shadow:0 2px 6px #00000014;padding:9px 12px;display:flex;flex-direction:column;gap:3px;position:relative}.asset-tree-root-card:after{content:"";position:absolute;left:50%;bottom:-25px;height:24px;border-left:1px solid #9ecbe0}.asset-tree-root-card small{color:#888}.asset-node-tree{display:flex;justify-content:center;align-items:flex-start;gap:18px}.asset-tree-branch{position:relative;display:flex;flex-direction:column;align-items:center}.asset-node-card{width:190px;min-height:50px;border:1px solid #cbd3d8;background:#fff;box-shadow:0 2px 5px #00000014;display:grid;grid-template-columns:24px 1fr 22px;grid-template-rows:1fr 1fr;text-align:left;padding:7px 8px;column-gap:5px;position:relative}.asset-node-card>span{grid-row:1/3;color:var(--company-primary);align-self:center}.asset-node-card>b{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.asset-node-card>small{color:#888;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.asset-node-card>em{grid-column:3;grid-row:1/3;align-self:center;justify-self:end;font-style:normal;width:20px;height:20px;border-radius:50%;background:#eee;display:grid;place-items:center;color:#777}.asset-node-card:hover,.asset-node-card.selected{border-color:var(--company-primary);box-shadow:0 0 0 1px var(--company-primary)}.asset-tree-children{margin-top:28px;padding-top:0;display:flex;align-items:flex-start;justify-content:center;gap:14px;position:relative}.asset-tree-children:before{content:"";position:absolute;left:50%;top:-28px;height:28px;border-left:1px solid #9ecbe0}.asset-tree-children>.asset-tree-branch:before{content:"";position:absolute;left:50%;top:-14px;height:14px;border-left:1px solid #9ecbe0}.asset-tree-children>.asset-tree-branch:not(:only-child):after{content:"";position:absolute;top:-14px;left:-7px;right:-7px;border-top:1px solid #9ecbe0}.asset-tree-empty{width:420px;margin:60px auto;text-align:center;display:flex;flex-direction:column;gap:8px;color:#888}.asset-tree-empty b{color:#555}.node-drawer-tabs{height:45px;border-bottom:1px solid var(--company-border);display:flex;margin:-8px 0 20px}.node-drawer-tabs button{border:0;border-bottom:3px solid transparent;background:#fff;padding:0 18px;font-size:14px}.node-drawer-tabs button.active{color:var(--company-primary);border-bottom-color:var(--company-primary)}.node-drawer-form{display:flex;flex-direction:column;gap:18px}.node-drawer-form input,.node-drawer-form select,.node-drawer-form textarea{width:100%;border:1px solid var(--company-border);padding:8px;background:#fff}.node-drawer-form input,.node-drawer-form select{height:38px}.node-drawer-form textarea{min-height:110px}.node-config-list>header,.node-config-list>div{display:grid;grid-template-columns:1fr 1fr 90px;gap:8px;margin-bottom:8px}.node-config-list.binding>header,.node-config-list.binding>div{grid-template-columns:1fr 1fr}.node-config-list>header{color:#777;font-size:12px}.node-config-list input{height:36px;border:1px solid var(--company-border);padding:0 8px}.node-config-list>.button{margin:6px 0 0}.batch-create-dialog{width:min(760px,calc(100vw - 40px))}.batch-create-dialog .node-drawer-tabs{margin:10px 0 18px}.batch-table>header,.batch-table>div{display:grid;grid-template-columns:1.4fr 90px 1.4fr 32px;gap:8px;margin-bottom:8px}.batch-table>header{color:#777;font-size:12px}.batch-table input{height:36px;border:1px solid var(--company-border);padding:0 8px}.batch-table>.button{margin:6px 0 10px}.manual-excel-upload{height:180px;border:1px dashed #aebbc3;background:#fafcfd;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;cursor:pointer}.manual-excel-upload>b{font-size:28px;color:var(--company-primary)}.manual-excel-upload>span{font-size:15px}.manual-excel-upload>small{color:#888}
-@media(max-width:1100px){.asset-editor-workbench{grid-template-columns:220px 1fr}.asset-node-card{width:160px}.asset-tree-children{flex-direction:column}.asset-tree-children>.asset-tree-branch:after{display:none}.asset-config-tabs{gap:18px}.asset-config-tabs>button{min-width:180px}}
+.site-edit-switcher{display:flex;align-items:center;gap:12px;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size)}.site-edit-switcher select{width:220px;height:34px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);border-radius:var(--ant-border-radius);padding:0 32px 0 12px;color:var(--ant-color-text-base)}.site-edit-switcher select:focus{outline:1px solid var(--ant-color-primary);border-color:var(--ant-color-primary)}
+.site-edit-tabs{height:64px;background:var(--ant-color-bg-container);border-top:1px solid var(--ant-color-border);border-bottom:1px solid var(--ant-color-border);display:flex;padding:0 24px;gap:0}.site-edit-tabs>button{height:64px;flex:1;min-width:0;border:0;border-bottom:3px solid transparent;background:var(--ant-color-bg-container);color:var(--ant-color-text-secondary);display:flex;align-items:center;gap:12px;padding:0 20px;font-size:var(--ant-font-size);text-align:left}.site-edit-tabs>button i{font-style:normal;width:26px;height:26px;border:1px solid var(--ant-color-border);border-radius:50%;display:grid;place-items:center;background:var(--ant-color-bg-container)}.site-edit-tabs>button:hover{color:var(--ant-color-primary);background:var(--ant-color-bg-layout)}.site-edit-tabs>button.active{color:var(--ant-color-primary);border-bottom-color:var(--ant-color-primary);font-weight:600}.site-edit-tabs>button.active i{background:var(--ant-color-primary);border-color:var(--ant-color-primary);color:var(--ant-color-text-light-solid)}.site-step-blank{min-height:calc(100vh - 286px);background:var(--ant-color-bg-container)}
+.asset-config-shell{min-width:0}.asset-config-tabs{height:66px;display:flex;align-items:flex-end;border:1px solid var(--ant-color-border);border-bottom:0;background:var(--ant-color-bg-container);padding:0 20px;gap:36px}.asset-config-tabs>button{height:65px;min-width:210px;padding:12px 4px 8px;border:0;border-bottom:3px solid transparent;background:transparent;text-align:left;display:flex;flex-direction:column;justify-content:center;gap:4px;color:var(--ant-color-text-secondary)}.asset-config-tabs>button span{font-size:var(--ant-font-size);font-weight:600}.asset-config-tabs>button small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.asset-config-tabs>button.active{color:var(--ant-color-primary);border-bottom-color:var(--ant-color-primary)}.asset-config-tabs>button.active small{color:var(--ant-color-text-secondary)}.quick-asset-config{border-top:1px solid var(--ant-color-border);padding-top:16px}.manual-asset-list{border-top:1px solid var(--ant-color-border);padding-top:16px}.manual-asset-list>.panel{margin:0}.manual-asset-filter{padding:0 0 16px;margin-bottom:0}.asset-category{border-top:1px solid var(--ant-color-border);margin:0 -12px}.asset-category>header{height:46px;display:flex;align-items:center;gap:8px;padding:0 16px;background:var(--ant-color-bg-layout)}.asset-category>header strong{font-size:var(--ant-font-size)}.asset-category>header span{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.asset-category .company-table{margin:0}.asset-category .company-table th:first-child,.asset-category .company-table td:first-child{padding-left:40px}.asset-group-dialog{width:540px}.dialog-form{display:flex;flex-direction:column;gap:20px;margin:20px 0}.dialog-form input,.dialog-form select{height:38px;border:1px solid var(--ant-color-border);padding:0 12px;background:var(--ant-color-bg-container)}
+.manual-asset-editor{border-top:1px solid var(--ant-color-border);padding-top:0;background:var(--ant-color-bg-container)}.asset-editor-title{height:64px;border:1px solid var(--ant-color-border);border-top:0;display:flex;align-items:center;padding:0 16px;gap:20px}.asset-editor-title>.text-button{margin:0;padding-right:20px;border-right:1px solid var(--ant-color-border)}.asset-editor-title>div{flex:1;display:flex;align-items:baseline;gap:12px}.asset-editor-title h2{font-size:var(--ant-font-size);margin:0}.asset-editor-title>div>span{color:var(--ant-color-text-secondary)}.asset-editor-title>.button{margin:0}.asset-editor-workbench{min-height:620px;border:1px solid var(--ant-color-border);border-top:0;display:grid;grid-template-columns:250px 1fr}.asset-editor-side{border-right:1px solid var(--ant-color-border);background:var(--ant-color-bg-layout)}.asset-editor-side>.field{padding:16px;border-bottom:1px solid var(--ant-color-border)}.asset-editor-side>.field input{background:var(--ant-color-bg-container)}.asset-batch-entry{width:100%;height:54px;border:0;border-bottom:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);text-align:left;padding:0 16px;font-size:var(--ant-font-size)}.asset-batch-entry:hover{color:var(--ant-color-primary);background:var(--ant-color-bg-layout)}.asset-template-panel{padding:16px;display:flex;flex-direction:column;gap:12px}.asset-template-panel>strong{font-size:var(--ant-font-size)}.asset-template-panel>select{height:34px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);padding:0 8px}.asset-template-panel>button{min-height:58px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);display:grid;grid-template-columns:24px 1fr;grid-template-rows:1fr 1fr;text-align:left;padding:8px;column-gap:8px}.asset-template-panel>button>span{grid-row:1/3;color:var(--ant-color-primary);align-self:center}.asset-template-panel>button>b{font-size:var(--ant-font-size-sm)}.asset-template-panel>button>small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.asset-template-panel>button:hover{border-color:var(--ant-color-primary)}.asset-tree-workspace{min-width:0;background:var(--ant-color-bg-layout)}.asset-editor-toolbar{height:52px;border-bottom:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);padding:0 12px;display:flex;align-items:center;justify-content:space-between}.asset-editor-toolbar>div{display:flex;align-items:center;gap:8px}.asset-editor-toolbar .button{margin:0;min-width:auto}.asset-tree-search{width:210px;height:30px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);display:flex;align-items:center;padding:0 8px;color:var(--ant-color-text-secondary)}.asset-tree-search input{border:0;outline:0;width:100%;padding-left:8px}.asset-tree-canvas{min-height:567px;padding:28px;overflow:auto}.asset-tree-root-card{width:220px;min-height:54px;margin:0 auto 24px;border:1px solid var(--ant-color-border);border-left:4px solid var(--ant-color-primary);background:var(--ant-color-bg-container);box-shadow:none;padding:8px 12px;display:flex;flex-direction:column;gap:4px;position:relative}.asset-tree-root-card:after{content:"";position:absolute;left:50%;bottom:-25px;height:24px;border-left:1px solid var(--ant-color-primary-bg)}.asset-tree-root-card small{color:var(--ant-color-text-secondary)}.asset-node-tree{display:flex;justify-content:center;align-items:flex-start;gap:20px}.asset-tree-branch{position:relative;display:flex;flex-direction:column;align-items:center}.asset-node-card{width:190px;min-height:50px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);box-shadow:none;display:grid;grid-template-columns:24px 1fr 22px;grid-template-rows:1fr 1fr;text-align:left;padding:8px 8px;column-gap:4px;position:relative}.asset-node-card>span{grid-row:1/3;color:var(--ant-color-primary);align-self:center}.asset-node-card>b{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.asset-node-card>small{color:var(--ant-color-text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.asset-node-card>em{grid-column:3;grid-row:1/3;align-self:center;justify-self:end;font-style:normal;width:20px;height:20px;border-radius:50%;background:var(--ant-color-bg-layout);display:grid;place-items:center;color:var(--ant-color-text-secondary)}.asset-node-card:hover,.asset-node-card.selected{border-color:var(--ant-color-primary);box-shadow:none}.asset-tree-children{margin-top:28px;padding-top:0;display:flex;align-items:flex-start;justify-content:center;gap:16px;position:relative}.asset-tree-children:before{content:"";position:absolute;left:50%;top:-28px;height:28px;border-left:1px solid var(--ant-color-primary-bg)}.asset-tree-children>.asset-tree-branch:before{content:"";position:absolute;left:50%;top:-14px;height:14px;border-left:1px solid var(--ant-color-primary-bg)}.asset-tree-children>.asset-tree-branch:not(:only-child):after{content:"";position:absolute;top:-14px;left:-7px;right:-7px;border-top:1px solid var(--ant-color-primary-bg)}.asset-tree-empty{width:420px;margin:60px auto;text-align:center;display:flex;flex-direction:column;gap:8px;color:var(--ant-color-text-secondary)}.asset-tree-empty b{color:var(--ant-color-text-secondary)}.node-drawer-tabs{height:45px;border-bottom:1px solid var(--ant-color-border);display:flex;margin:-8px 0 20px}.node-drawer-tabs button{border:0;border-bottom:3px solid transparent;background:var(--ant-color-bg-container);padding:0 20px;font-size:var(--ant-font-size)}.node-drawer-tabs button.active{color:var(--ant-color-primary);border-bottom-color:var(--ant-color-primary)}.node-drawer-form{display:flex;flex-direction:column;gap:20px}.node-drawer-form input,.node-drawer-form select,.node-drawer-form textarea{width:100%;border:1px solid var(--ant-color-border);padding:8px;background:var(--ant-color-bg-container)}.node-drawer-form input,.node-drawer-form select{height:38px}.node-drawer-form textarea{min-height:110px}.node-config-list>header,.node-config-list>div{display:grid;grid-template-columns:1fr 1fr 90px;gap:8px;margin-bottom:8px}.node-config-list.binding>header,.node-config-list.binding>div{grid-template-columns:1fr 1fr}.node-config-list>header{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.node-config-list input{height:36px;border:1px solid var(--ant-color-border);padding:0 8px}.node-config-list>.button{margin:8px 0 0}.batch-create-dialog{width:min(760px,calc(100vw - 40px))}.batch-create-dialog .node-drawer-tabs{margin:12px 0 20px}.batch-table>header,.batch-table>div{display:grid;grid-template-columns:1.4fr 90px 1.4fr 32px;gap:8px;margin-bottom:8px}.batch-table>header{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.batch-table input{height:36px;border:1px solid var(--ant-color-border);padding:0 8px}.batch-table>.button{margin:8px 0 12px}.manual-excel-upload{height:180px;border:1px dashed var(--ant-color-border);background:var(--ant-color-bg-layout);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;cursor:pointer}.manual-excel-upload>b{font-size:var(--ant-font-size-heading-2);color:var(--ant-color-primary)}.manual-excel-upload>span{font-size:var(--ant-font-size)}.manual-excel-upload>small{color:var(--ant-color-text-secondary)}
+@media(max-width:1100px){.asset-editor-workbench{grid-template-columns:220px 1fr}.asset-node-card{width:160px}.asset-tree-children{flex-direction:column}.asset-tree-children>.asset-tree-branch:after{display:none}.asset-config-tabs{gap:20px}.asset-config-tabs>button{min-width:180px}}
/* Resource center list pages */
.resource-list-panel>.panel-body{padding:0 16px 12px;overflow-x:auto}
.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}
+.resource-detail>p{margin:8px 0;color:var(--ant-color-text-secondary);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(--ant-color-border);background:var(--ant-color-bg-layout);margin-bottom:20px}
.device-resource-table{min-width:820px}.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:90px;text-align:center}.device-resource-table th:nth-child(3),.device-resource-table td:nth-child(3){width:110px;text-align:left}.device-resource-table th:nth-child(4),.device-resource-table td.domain-cell{width:240px;min-width:180px;text-align:left;white-space:normal}.device-resource-table th:nth-child(5),.device-resource-table td:nth-child(5){width:64px;text-align:center}.device-resource-table .company-record-link{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}
-.domain-tag-list{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
-.ant-tag.tag-new{display:inline-flex;align-items:center;height:24px;line-height:21px;border-radius:12px;padding:0 10px;font-size:13px;white-space:nowrap;margin:0}
-.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)}
+.domain-tag-list{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
+.ant-tag.tag-new{display:inline-flex;align-items:center;height:24px;line-height:21px;border-radius:var(--ant-border-radius);padding:0 12px;font-size:var(--ant-font-size-sm);white-space:nowrap;margin:0}
+.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(--ant-color-border);background:var(--ant-color-bg-layout);object-fit:cover;border-radius:var(--ant-border-radius)}.resource-placeholder{display:grid;place-items:center;color:var(--ant-color-text-secondary)}.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 var(--ant-color-border)}.resource-point-list>div{height:40px;border-bottom:1px solid var(--ant-color-border);display:flex;align-items:center;justify-content:space-between}.resource-point-list span{color:var(--ant-color-text-secondary)}.resource-point-list>p{color:var(--ant-color-text-secondary)}
+.resource-back{height:44px;display:flex;align-items:center;border-bottom:1px solid var(--ant-color-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(--ant-color-border);background:var(--ant-color-bg-layout);border-radius:var(--ant-border-radius)}
+.style-drawer>img{display:block;width:100%;max-height:440px;object-fit:contain;background:var(--ant-color-bg-layout);border:1px solid var(--ant-color-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 var(--ant-color-border)}.style-drawer dt{color:var(--ant-color-text-secondary)}
/* 生成过程只读终端 */
-.gen-right{display:flex;flex-direction:column;gap:14px}
+.gen-right{display:flex;flex-direction:column;gap:16px}
.console-panel .panel-body{padding:0}
-.gen-console{height:300px;max-height:42vh;overflow-y:auto;background:#0f1722;color:#cfe3f0;font:12px/1.5 "SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace;padding:10px 12px}
+.gen-console{height:300px;max-height:42vh;overflow-y:auto;background:var(--ant-color-text-base);color:var(--ant-color-primary);font:12px/1.5 "SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace;padding:12px 12px}
.gen-console pre{margin:0;white-space:pre-wrap;word-break:break-word}
.gen-console pre:empty:before{content:""}
-.modal-console{margin-top:12px;border:1px solid #1c2a38;border-radius:4px;overflow:hidden}
-.modal-console-head{background:#0f1722;color:#9fd0e6;font-size:12px;padding:6px 10px;border-bottom:1px solid #1c2a38}
-.modal-console-body{background:#0f1722;color:#cfe3f0;font:12px/1.5 "SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace;margin:0;padding:10px 12px;max-height:240px;overflow:auto;white-space:pre-wrap;word-break:break-word}
+.modal-console{margin-top:12px;border:1px solid var(--ant-color-primary);border-radius:var(--ant-border-radius);overflow:hidden}
+.modal-console-head{background:var(--ant-color-text-base);color:var(--ant-color-primary-bg);font-size:var(--ant-font-size-sm);padding:8px 12px;border-bottom:1px solid var(--ant-color-primary)}
+.modal-console-body{background:var(--ant-color-text-base);color:var(--ant-color-primary);font:12px/1.5 "SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace;margin:0;padding:12px 12px;max-height:240px;overflow:auto;white-space:pre-wrap;word-break:break-word}
/* ===== 视图切换(列表/卡片) ===== */
-.view-toggle{display:inline-flex;border:1px solid var(--line);border-radius:3px;overflow:hidden}
-.view-toggle button{border:0;background:#fff;color:#666;height:28px;padding:0 12px;font-size:11px}
-.view-toggle button+button{border-left:1px solid var(--line)}
-.view-toggle button.active{background:var(--blue);color:#fff}
+.view-toggle{display:inline-flex;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);overflow:hidden}
+.view-toggle button{border:0;background:var(--ant-color-bg-container);color:var(--ant-color-text-secondary);height:28px;padding:0 12px;font-size:var(--ant-font-size-sm)}
+.view-toggle button+button{border-left:1px solid var(--ant-color-border)}
+.view-toggle button.active{background:var(--ant-color-primary);color:var(--ant-color-text-light-solid)}
/* ===== 领域方案卡片视图 ===== */
-.domain-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(245px,1fr));gap:12px;padding:6px 0}
-.domain-card{border:1px solid var(--line);border-top:3px solid var(--blue);background:#fff;padding:14px;display:flex;flex-direction:column;gap:9px;cursor:pointer;transition:.15s}
-.domain-card:hover{box-shadow:0 6px 18px #0001;border-top-color:var(--blue2)}
+.domain-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(245px,1fr));gap:12px;padding:8px 0}
+.domain-card{border:1px solid var(--ant-color-border);border-top:3px solid var(--ant-color-primary);background:var(--ant-color-bg-container);padding:16px;display:flex;flex-direction:column;gap:8px;cursor:pointer;transition:.15s}
+.domain-card:hover{box-shadow:none;border-top-color:var(--ant-color-primary-active)}
.domain-card>header{display:flex;align-items:center;justify-content:space-between;gap:8px}
-.domain-card h3{font-size:15px;margin:0}
-.domain-card .badge{display:inline-block;align-self:flex-start;background:var(--pale);color:var(--blue2);border:1px solid #cfeaf6;border-radius:10px;padding:1px 10px;font-size:10px}
-.domain-card .meta{display:flex;gap:14px;color:#888;font-size:11px}
-.domain-card .meta b{color:#333;font-size:13px;margin-right:3px}
+.domain-card h3{font-size:var(--ant-font-size);margin:0}
+.domain-card .badge{display:inline-block;align-self:flex-start;background:var(--ant-color-primary-bg);color:var(--ant-color-primary-active);border:1px solid var(--ant-color-primary);border-radius:var(--ant-border-radius);padding:4px 12px;font-size:var(--ant-font-size-sm)}
+.domain-card .meta{display:flex;gap:16px;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}
+.domain-card .meta b{color:var(--ant-color-text-base);font-size:var(--ant-font-size-sm);margin-right:4px}
/* 领域方案 7 步向导 */
-.placeholder-block{display:flex;flex-direction:column;gap:12px;padding:6px 2px}
-.badge.todo{display:inline-block;align-self:flex-start;background:#fff4d8;color:#b37400;border:1px solid #f6bc44;border-radius:10px;padding:2px 10px;font-size:10px}
+.placeholder-block{display:flex;flex-direction:column;gap:12px;padding:8px 4px}
+.badge.todo{display:inline-block;align-self:flex-start;background:var(--ant-color-primary-bg);color:var(--ant-color-error);border:1px solid var(--ant-color-error-bg);border-radius:var(--ant-border-radius);padding:4px 12px;font-size:var(--ant-font-size-sm)}
/* Cube 页面来源徽标 */
-.badge.src-upload{display:inline-flex;align-items:center;gap:2px;background:#e7f5fc;color:#007cc3;border:1px solid #b9e0f4;border-radius:10px;padding:2px 9px;font-size:10px}
-.badge.src-ai{display:inline-flex;align-items:center;gap:2px;background:#f3e8ff;color:#7a3cc0;border:1px solid #dcc4f3;border-radius:10px;padding:2px 9px;font-size:10px}
-.badge.src-manual{display:inline-flex;align-items:center;gap:2px;background:#f1f2f3;color:#6b7177;border:1px solid #dfe2e4;border-radius:10px;padding:2px 9px;font-size:10px}
+.badge.src-upload{display:inline-flex;align-items:center;gap:4px;background:var(--ant-color-bg-layout);color:var(--ant-color-primary);border:1px solid var(--ant-color-primary-bg);border-radius:var(--ant-border-radius);padding:4px 8px;font-size:var(--ant-font-size-sm)}
+.badge.src-ai{display:inline-flex;align-items:center;gap:4px;background:var(--ant-color-bg-layout);color:var(--ant-color-primary);border:1px solid var(--ant-color-primary);border-radius:var(--ant-border-radius);padding:4px 8px;font-size:var(--ant-font-size-sm)}
+.badge.src-manual{display:inline-flex;align-items:center;gap:4px;background:var(--ant-color-bg-layout);color:var(--ant-color-text-secondary);border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:4px 8px;font-size:var(--ant-font-size-sm)}
/* 扩展菜单可点击项 */
.extension-menu-section .extension-menu-item.clickable{cursor:pointer}
-.extension-menu-section .extension-menu-item.clickable:hover{background:#e8f4fb;color:var(--company-primary)}
-.extension-menu-section .extension-menu-item.clickable.active{background:#d7eef9;color:var(--company-primary);font-weight:500}
+.extension-menu-section .extension-menu-item.clickable:hover{background:var(--ant-color-bg-layout);color:var(--ant-color-primary)}
+.extension-menu-section .extension-menu-item.clickable.active{background:var(--ant-color-primary-bg);color:var(--ant-color-primary);font-weight:600}
/* 仪表盘页面 */
-.dashboard-page{min-height:calc(100vh - 56px);padding:20px;background:#f7f8f9;display:flex;align-items:flex-start;justify-content:center}
-.dashboard-page img{display:block;max-width:100%;height:auto;background:#fff;box-shadow:0 4px 12px #00000012;border:1px solid #e3e6e8;border-radius:4px}
-.domain-preview-canvas{background:#1e2630;height:64vh;border-radius:6px;overflow:auto}
+.dashboard-page{min-height:calc(100vh - 56px);padding:20px;background:var(--ant-color-bg-layout);display:flex;align-items:flex-start;justify-content:center}
+.dashboard-page img{display:block;max-width:100%;height:auto;background:var(--ant-color-bg-container);box-shadow:none;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius)}
+.domain-preview-canvas{background:var(--ant-color-text-base);height:64vh;border-radius:var(--ant-border-radius);overflow:auto}
.domain-preview-canvas .dashboard-thumb-live{min-height:0}
-.domain-preview-feedback{margin-top:14px}
-.domain-preview-feedback textarea{width:100%;border:1px solid #cfd3d7;border-radius:3px;padding:9px;resize:vertical;line-height:1.6;margin-top:6px}
+.domain-preview-feedback{margin-top:16px}
+.domain-preview-feedback textarea{width:100%;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:8px;resize:vertical;line-height:1.6;margin-top:8px}
.domain-review-pages{display:grid;grid-template-columns:1fr;gap:12px}
-.domain-review-page{border:1px solid #e0e3e5;background:#fff;overflow:hidden;transition:border-color .15s,box-shadow .15s}
-.domain-review-page:hover{border-color:var(--blue);box-shadow:0 2px 8px #008fd21f}
+.domain-review-page{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);overflow:hidden;transition:border-color .15s,box-shadow .15s}
+.domain-review-page:hover{border-color:var(--ant-color-primary);box-shadow:none}
.domain-review-page .dashboard-thumb-live{border-bottom:0;min-height:240px}
-.domain-review-page>div{padding:9px 11px;display:flex;align-items:center;justify-content:space-between;gap:8px}
-.domain-review-page h4{margin:0;font-size:13px}
-.domain-review-page .review-zoom{font-size:10px;color:#008ed2;white-space:nowrap}
+.domain-review-page>div{padding:8px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px}
+.domain-review-page h4{margin:0;font-size:var(--ant-font-size-sm)}
+.domain-review-page .review-zoom{font-size:var(--ant-font-size-sm);color:var(--ant-color-primary);white-space:nowrap}
-.domain-card .card-actions{display:flex;gap:14px;margin-top:2px;padding-top:8px;border-top:1px solid #f0f2f3}
+.domain-card .card-actions{display:flex;gap:16px;margin-top:4px;padding-top:8px;border-top:1px solid var(--ant-color-border)}
/* ===== 设备模版 ===== */
.device-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:12px;padding:4px 0}
-.device-card{border:1px solid var(--line);background:#fff;padding:14px}
-.device-card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
-.device-card-head b{font-size:14px}
-.cp-list{margin-top:10px}
-.cp-list>b{display:flex;align-items:center;gap:8px;font-size:12px}
-.cp-list .data-table{margin-top:6px}
+.device-card{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);padding:16px}
+.device-card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
+.device-card-head b{font-size:var(--ant-font-size)}
+.cp-list{margin-top:12px}
+.cp-list>b{display:flex;align-items:center;gap:8px;font-size:var(--ant-font-size-sm)}
+.cp-list .data-table{margin-top:8px}
.chk{display:flex;flex-wrap:wrap;gap:8px}
-.chk-item{display:inline-flex;align-items:center;gap:5px;font-size:12px;background:#f6f8f9;border:1px solid #e6e9eb;border-radius:3px;padding:4px 9px;cursor:pointer}
-.chk-item input{margin:0;accent-color:var(--blue)}
+.chk-item{display:inline-flex;align-items:center;gap:4px;font-size:var(--ant-font-size-sm);background:var(--ant-color-bg-layout);border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:4px 8px;cursor:pointer}
+.chk-item input{margin:0;accent-color:var(--ant-color-primary)}
/* ===== 方案详情:选用设备模版 ===== */
.device-picker{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:8px}
-.picker-item{display:flex;align-items:center;gap:9px;border:1px solid var(--line);background:#fff;border-radius:4px;padding:9px 11px;cursor:pointer}
-.picker-item.on{border-color:var(--blue);background:#f4fbff}
-.picker-item input{margin:0;accent-color:var(--blue)}
-.picker-name{font-weight:bold;font-size:12px}
-.picker-doms{margin-left:auto;color:#888;font-size:10px;max-width:42%;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+.picker-item{display:flex;align-items:center;gap:8px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);border-radius:var(--ant-border-radius);padding:8px 12px;cursor:pointer}
+.picker-item.on{border-color:var(--ant-color-primary);background:var(--ant-color-bg-layout)}
+.picker-item input{margin:0;accent-color:var(--ant-color-primary)}
+.picker-name{font-weight:600;font-size:var(--ant-font-size-sm)}
+.picker-doms{margin-left:auto;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);max-width:42%;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* ===== 设备模版:图片与采集点编辑 ===== */
-.device-list{display:grid;grid-template-columns:1fr;gap:14px;padding:4px 0}
-.device-img-field{margin-top:6px;border:1px dashed #c4ccd2;border-radius:6px;width:160px;height:160px;background:#f7f9fa;display:flex;align-items:center;justify-content:center;cursor:pointer;overflow:hidden;position:relative}
-.device-img-field:hover{border-color:var(--blue);background:#f0f8fc}
+.device-list{display:grid;grid-template-columns:1fr;gap:16px;padding:4px 0}
+.device-img-field{margin-top:8px;border:1px dashed var(--ant-color-border);border-radius:var(--ant-border-radius);width:160px;height:160px;background:var(--ant-color-bg-layout);display:flex;align-items:center;justify-content:center;cursor:pointer;overflow:hidden;position:relative}
+.device-img-field:hover{border-color:var(--ant-color-primary);background:var(--ant-color-bg-layout)}
.device-img-field img{width:100%;height:100%;object-fit:cover;display:block}
-.device-img-field .device-img-empty{color:#9aa1a5;font-size:12px;text-align:center;padding:0 8px}
-.cp-list-head{display:flex;align-items:center;justify-content:space-between;margin:10px 0 6px}
+.device-img-field .device-img-empty{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);text-align:center;padding:0 8px}
+.cp-list-head{display:flex;align-items:center;justify-content:space-between;margin:12px 0 8px}
.cp-list-head span{display:flex;gap:12px}
-.cp-editor td{padding:4px 6px;vertical-align:middle}
-.cp-editor input,.cp-editor select{width:100%;border:1px solid var(--line);border-radius:3px;height:30px;padding:0 7px;background:#fff;font-size:11px}
-.cp-editor input:focus,.cp-editor select:focus{outline:1px solid var(--blue)}
-.cp-range-custom{margin-top:5px}
+.cp-editor td{padding:4px 8px;vertical-align:middle}
+.cp-editor input,.cp-editor select{width:100%;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);height:30px;padding:0 8px;background:var(--ant-color-bg-container);font-size:var(--ant-font-size-sm)}
+.cp-editor input:focus,.cp-editor select:focus{outline:1px solid var(--ant-color-primary)}
+.cp-range-custom{margin-top:4px}
+/* ===== 设备模板:报警规则 ===== */
+.alarm-rule-block{margin-top:20px;border-top:1px solid var(--ant-color-border);padding-top:8px}.alarm-rule-head{min-height:44px;display:flex;align-items:center;justify-content:space-between;gap:16px}.alarm-rule-head>b{font-size:var(--ant-font-size)}.alarm-rule-head>div{display:flex;align-items:center;gap:12px}.alarm-rule-head .button{height:32px;min-width:74px;margin:0}.alarm-rule-search{width:210px;height:32px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);background:var(--ant-color-bg-container);color:var(--ant-color-text-secondary);display:flex;align-items:center;gap:8px;padding:0 8px}.alarm-rule-search:focus-within{border-color:var(--ant-color-primary)}.alarm-rule-search input{width:100%;border:0;outline:0;background:transparent}.alarm-rule-table{margin-top:0!important;border-top:1px solid var(--ant-color-border)}.alarm-rule-table th{height:40px;border-bottom:2px solid var(--ant-color-primary);font-size:var(--ant-font-size-sm)}.alarm-rule-table td{height:48px;font-size:var(--ant-font-size-sm)}.alarm-rule-table input[type=checkbox]{accent-color:var(--ant-color-primary)}.alarm-name-link{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-base)}.alarm-name-link:hover{color:var(--ant-color-primary)}.alarm-description-cell{max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.alarm-switch{width:25px;height:16px;padding:0;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);background:var(--ant-color-fill-secondary);position:relative;vertical-align:middle}.alarm-switch i{position:absolute;left:2px;top:2px;width:10px;height:10px;border-radius:50%;background:var(--ant-color-bg-container);transition:left .15s}.alarm-switch.on{border-color:var(--ant-color-primary);background:var(--ant-color-primary)}.alarm-switch.on i{left:11px}.alarm-row-actions{display:flex;align-items:center;gap:4px}.alarm-row-actions .ant-btn{width:30px;height:30px;padding:0;color:var(--ant-color-primary);font-size:var(--ant-font-size)}.alarm-row-actions .ant-btn-dangerous{color:var(--ant-color-error)}
+.alarm-rule-drawer .ant-drawer-header{min-height:56px;padding:0 24px;border-bottom:1px solid var(--ant-color-border)}.alarm-rule-drawer .ant-drawer-title{font-size:var(--ant-font-size);font-weight:600}.alarm-rule-drawer .ant-drawer-body{padding:28px 32px 32px;overflow:auto;background:var(--ant-color-bg-container)}.alarm-rule-drawer .ant-drawer-footer{height:56px;padding:12px 24px;border-top:1px solid var(--ant-color-border)}.alarm-rule-form{display:flex;flex-direction:column;gap:28px}.alarm-rule-form section>h3{margin:0 0 20px;padding-left:16px;border-left:4px solid var(--ant-color-primary);font-size:var(--ant-font-size);line-height:19px}.alarm-form-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px 40px;padding:0 20px}.alarm-form-grid label{display:grid;grid-template-columns:100px minmax(0,1fr);align-items:center;gap:0}.alarm-form-grid label.wide{grid-column:1/-1}.alarm-form-grid label>span{text-align:right;padding-right:16px;color:var(--ant-color-text-secondary)}.alarm-form-grid i,.alarm-sensitivity i{font-style:normal;color:var(--ant-color-error)}.alarm-form-grid input,.alarm-form-grid select{width:100%;height:34px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);background:var(--ant-color-bg-container);padding:0 12px;outline:0}.alarm-form-grid input:focus,.alarm-form-grid select:focus{border-color:var(--ant-color-primary)}.alarm-sensitivity{display:flex;align-items:center;gap:12px;padding:0 28px}.alarm-sensitivity>span{margin-right:8px}.alarm-sensitivity label{display:flex;align-items:center;gap:8px}.alarm-sensitivity input[type=radio]{accent-color:var(--ant-color-primary)}.alarm-sensitivity input[type=number]{width:55px;height:34px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:0 8px}.alarm-sensitivity input:disabled{background:var(--ant-color-bg-layout);color:var(--ant-color-text-disabled)}.alarm-sensitivity em{font-style:normal;color:var(--ant-color-text-secondary);margin-right:16px}.alarm-trigger-panel{border:1px solid var(--ant-color-border);margin:0 20px;min-height:250px}.alarm-trigger-panel>header{height:48px;display:flex;align-items:center;justify-content:space-between;padding:0 12px;border-bottom:1px solid var(--ant-color-border)}.alarm-trigger-panel>header b{font-size:var(--ant-font-size)}.alarm-trigger-panel>header>span{display:flex;align-items:center;gap:8px}.alarm-trigger-panel>header .button{height:32px;min-width:72px;margin:0}.alarm-trigger-table{table-layout:fixed}.alarm-trigger-table th{border-bottom:2px solid var(--ant-color-primary);font-size:var(--ant-font-size-sm)}.alarm-trigger-table th:nth-child(1){width:120px}.alarm-trigger-table th:nth-child(2){width:125px}.alarm-trigger-table th:nth-child(3){width:110px}.alarm-trigger-table th:nth-child(4){width:160px}.alarm-trigger-table th:nth-child(5){width:90px}.alarm-trigger-table th:nth-child(6){width:auto}.alarm-trigger-table th:nth-child(7){width:64px}.alarm-trigger-table td{height:48px;padding:8px 8px}.alarm-trigger-table input,.alarm-trigger-table select{width:100%;height:30px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);padding:0 8px;outline:0}.alarm-trigger-table input:focus,.alarm-trigger-table select:focus{border-color:var(--ant-color-primary)}.alarm-trigger-empty{height:130px;display:grid;place-items:center;color:var(--ant-color-text-secondary)}
+@media(max-width:900px){.alarm-rule-drawer .ant-drawer-content-wrapper{width:100vw!important}.alarm-form-grid{grid-template-columns:1fr}.alarm-form-grid label.wide{grid-column:auto}.alarm-sensitivity{flex-wrap:wrap}.alarm-rule-table{min-width:900px}.alarm-rule-block{overflow:auto}}
/* ===== 设备模版:列表视图 ===== */
-.list-toolbar{margin-bottom:10px}
-.list-toolbar .search{border:1px solid var(--line);border-radius:3px;height:32px;padding:0 10px;width:320px;background:#fff}
-.list-toolbar .search:focus{outline:1px solid var(--blue)}
+.list-toolbar{margin-bottom:12px}
+.list-toolbar .search{border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);height:32px;padding:0 12px;width:320px;background:var(--ant-color-bg-container)}
+.list-toolbar .search:focus{outline:1px solid var(--ant-color-primary)}
.device-tpl-list tbody tr{cursor:pointer}
-.device-tpl-list tbody tr:hover{background:#f5f9fb}
-.tpl-thumb{width:48px;height:34px;object-fit:cover;border:1px solid var(--line);border-radius:3px;display:block;background:#f2f5f7}
-.tpl-thumb-empty{color:#c2c8cc}
+.device-tpl-list tbody tr:hover{background:var(--ant-color-bg-layout)}
+.tpl-thumb{width:48px;height:34px;object-fit:cover;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);display:block;background:var(--ant-color-bg-layout)}
+.tpl-thumb-empty{color:var(--ant-color-text-disabled)}
/* ===== 自定义确认 / 输入弹窗(替代原生 confirm/prompt,居中显示) ===== */
-.dlg-backdrop{position:fixed;inset:0;z-index:90;background:#00000059;display:flex;align-items:center;justify-content:center;padding:20px}
-.dlg-box{background:#fff;border:1px solid #d2d6da;border-radius:8px;box-shadow:0 12px 36px #0003;width:min(420px,100%);max-width:100%;padding:20px 22px}
-.dlg-box p{margin:0 0 18px;font-size:13px;line-height:1.6;color:#333;white-space:pre-wrap;word-break:break-word}
-.dlg-box input{width:100%;border:1px solid var(--line);border-radius:4px;height:32px;padding:0 9px;font-size:13px}
-.dlg-box input:focus{outline:1px solid var(--blue)}
-.dlg-actions{display:flex;justify-content:flex-end;gap:10px}
+.dlg-backdrop{position:fixed;inset:0;z-index:90;background:color-mix(in srgb,var(--ant-color-bg-mask) 35%,transparent);display:flex;align-items:center;justify-content:center;padding:20px}
+.dlg-box{background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);box-shadow:0 12px 36px color-mix(in srgb,var(--ant-color-text-base) 20%,transparent);width:min(420px,100%);max-width:100%;padding:20px 24px}
+.dlg-box p{margin:0 0 20px;font-size:var(--ant-font-size-sm);line-height:1.6;color:var(--ant-color-text-base);white-space:pre-wrap;word-break:break-word}
+.dlg-box input{width:100%;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);height:32px;padding:0 8px;font-size:var(--ant-font-size-sm)}
+.dlg-box input:focus{outline:1px solid var(--ant-color-primary)}
+.dlg-actions{display:flex;justify-content:flex-end;gap:12px}
.dlg-actions .button{margin-left:0}
/* 领域方案库 · 管理领域 */
.domain-manage-list{display:flex;flex-direction:column;gap:8px}
-.domain-manage-row{display:flex;align-items:center;gap:12px;padding:9px 12px;border:1px solid var(--line);border-radius:8px;background:#fff}
-.domain-manage-name{font-weight:600;color:#333}
-.domain-manage-row .muted{font-size:11px}
-.text-button.danger{color:#d92d20}
+.domain-manage-row{display:flex;align-items:center;gap:12px;padding:8px 12px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);background:var(--ant-color-bg-container)}
+.domain-manage-name{font-weight:600;color:var(--ant-color-text-base)}
+.domain-manage-row .muted{font-size:var(--ant-font-size-sm)}
+.text-button.danger{color:var(--ant-color-error)}
.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-create-dialog h2{margin:0 0 8px;font-size:var(--ant-font-size-heading-4)}.domain-create-dialog p{margin:0 0 16px;color:var(--ant-color-text-secondary)}.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:var(--ant-font-size)}.settings-form input,.settings-form select{width:100%;height:40px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:0 12px;background:var(--ant-color-bg-container)}.settings-form input[readonly]{background:var(--ant-color-bg-layout);color:var(--ant-color-text-secondary)}.settings-note{padding:16px 16px;background:var(--ant-color-bg-layout);border-left:3px solid var(--ant-color-primary);display:flex;flex-direction:column;gap:4px}.settings-note span{color:var(--ant-color-text-secondary)}.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}
-.domain-nav-row>button:not(.domain-nav-del):not(.domain-nav-ok){flex:1;min-width:0;text-align:left;height:41px;border:0;background:transparent;padding:0 14px;display:flex;align-items:center;color:#333;font-size:13px;white-space:nowrap}
+.domain-nav-row>button:not(.domain-nav-del):not(.domain-nav-ok){flex:1;min-width:0;text-align:left;height:41px;border:0;background:transparent;padding:0 16px;display:flex;align-items:center;color:var(--ant-color-text-base);font-size:var(--ant-font-size-sm);white-space:nowrap}
.domain-nav-name{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
-.domain-nav-row>button b{width:18px;color:#8a9298;margin-right:6px;font-weight:normal;flex:0 0 auto}
-.domain-nav-row>button:hover{background:rgba(0,0,0,.04)}
-.domain-nav-row.active{background:rgba(0,140,214,.1)}
-.domain-nav-row>button.active:not(.domain-nav-del):not(.domain-nav-ok){background:rgba(0,140,214,.1);color:#008cd6;font-weight:bold}
-.domain-nav-row.active>button b,.domain-nav-row>button.active b{color:#009fe3}
-.domain-nav-del{border:0;background:transparent;width:30px;color:#b0b6ba;font-size:15px;line-height:1;flex:0 0 auto}
-.domain-nav-del:hover{color:var(--red);background:#fdecea}
-.domain-nav-ok{border:0;background:transparent;width:30px;color:#1f9d5f;font-size:14px;flex:0 0 auto}
-.domain-nav-ok:hover{background:#eafaef}
+.domain-nav-row>button b{width:18px;color:var(--ant-color-text-secondary);margin-right:8px;font-weight:400;flex:0 0 auto}
+.domain-nav-row>button:hover{background:var(--ant-color-fill-quaternary)}
+.domain-nav-row.active{background:var(--ant-color-primary-bg)}
+.domain-nav-row>button.active:not(.domain-nav-del):not(.domain-nav-ok){background:var(--ant-color-primary-bg);color:var(--ant-color-primary);font-weight:600}
+.domain-nav-row.active>button b,.domain-nav-row>button.active b{color:var(--ant-color-primary)}
+.domain-nav-del{border:0;background:transparent;width:30px;color:var(--ant-color-text-disabled);font-size:var(--ant-font-size);line-height:1;flex:0 0 auto}
+.domain-nav-del:hover{color:var(--ant-color-error);background:var(--ant-color-bg-layout)}
+.domain-nav-ok{border:0;background:transparent;width:30px;color:var(--ant-color-success);font-size:var(--ant-font-size);flex:0 0 auto}
+.domain-nav-ok:hover{background:var(--ant-color-bg-layout)}
.domain-nav-add-cta{display:block}
-.domain-nav-add{height:34px;border:0;background:transparent;text-align:left;padding:0 14px;display:flex;align-items:center;color:#009fe3;font-size:12px;width:100%}
-.domain-nav-add:hover{background:rgba(0,140,214,.1)}
-.domain-nav-add b{margin-right:6px}
-.domain-nav-row.add{align-items:center;padding:4px 6px}
-.domain-nav-input{flex:1;min-width:0;margin:0 4px;height:28px;border:1px solid #cfd4d9;border-radius:3px;padding:0 8px;font-size:12px;background:#fff}
-.domain-nav-input:focus{outline:1px solid var(--blue);border-color:var(--blue)}
+.domain-nav-add{height:34px;border:0;background:transparent;text-align:left;padding:0 16px;display:flex;align-items:center;color:var(--ant-color-primary);font-size:var(--ant-font-size-sm);width:100%}
+.domain-nav-add:hover{background:var(--ant-color-primary-bg)}
+.domain-nav-add b{margin-right:8px}
+.domain-nav-row.add{align-items:center;padding:4px 8px}
+.domain-nav-input{flex:1;min-width:0;margin:0 4px;height:28px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:0 8px;font-size:var(--ant-font-size-sm);background:var(--ant-color-bg-container)}
+.domain-nav-input:focus{outline:1px solid var(--ant-color-primary);border-color:var(--ant-color-primary)}
/* ===== 方案打包 / 方案概览(v0.32) ===== */
.gen-overview{display:block}
.gen-overview .panel{margin-bottom:0}
-.overview-info{display:grid;grid-template-columns:1fr 1fr;gap:10px 18px;padding:4px 2px 14px;border-bottom:1px solid var(--line);margin-bottom:12px}
-.overview-info>div{display:flex;flex-direction:column;gap:3px}
-.overview-info span{font-size:10px;color:#8b9094;font-weight:bold;letter-spacing:.3px}
-.overview-info b{font-size:14px;color:#2b2f33}
+.overview-info{display:grid;grid-template-columns:1fr 1fr;gap:12px 20px;padding:4px 4px 16px;border-bottom:1px solid var(--ant-color-border);margin-bottom:12px}
+.overview-info>div{display:flex;flex-direction:column;gap:4px}
+.overview-info span{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);font-weight:600;letter-spacing:.3px}
+.overview-info b{font-size:var(--ant-font-size);color:var(--ant-color-text-base)}
.overview-info .overview-desc{grid-column:1 / -1}
-.overview-info .overview-desc p{margin:0;font-size:12px;color:#555;line-height:1.6;background:#f8fafb;border:1px solid #eef1f3;border-radius:4px;padding:7px 9px}
+.overview-info .overview-desc p{margin:0;font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);line-height:1.6;background:var(--ant-color-bg-layout);border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:8px 8px}
.overview-tabs{margin:0 0 12px}
-.overview-tabs button{padding:0 14px;font-size:12px}
+.overview-tabs button{padding:0 16px;font-size:var(--ant-font-size-sm)}
.overview-body{min-height:120px}
-.overview-body .muted{padding:10px 2px}
+.overview-body .muted{padding:12px 4px}
.dev-list{display:flex;flex-direction:column;gap:8px}
-.dev-row{border:1px solid var(--line);border-radius:6px;overflow:hidden;background:#fff}
-.dev-row-head{width:100%;border:0;background:transparent;display:flex;align-items:center;gap:10px;padding:11px 13px;text-align:left;cursor:pointer}
-.dev-row-head:hover{background:#f6fafe}
-.dev-row-head b{font-size:13px;flex:1}
-.dev-count{font-size:11px;color:#008ed2;background:#e7f3f8;border-radius:10px;padding:1px 9px}
-.dev-caret{color:#9aa1a5;font-size:11px}
-.dev-detail{border-top:1px solid #eef1f3;padding:11px 13px;background:#fbfcfd}
-.dev-desc{margin:0 0 8px;font-size:12px;color:#555;line-height:1.7}
-.dev-cp b{font-size:11px;color:#333;display:block;margin-bottom:4px}
-.dev-cp ul{margin:0;padding-left:18px;color:#666;font-size:11px;line-height:1.8}
-.doc-list{display:flex;flex-direction:column;gap:7px}
-.doc-item{display:flex;align-items:baseline;gap:10px;padding:9px 11px;border:1px solid var(--line);border-radius:6px;background:#fff}
-.doc-item b{font-size:13px;flex:1}
-.doc-item .muted{font-size:11px;white-space:nowrap}
-.ops-placeholder{display:flex;flex-direction:column;gap:10px;padding:18px;border:1px dashed #cfd6da;border-radius:8px;background:#fafbfc}
+.dev-row{border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);overflow:hidden;background:var(--ant-color-bg-container)}
+.dev-row-head{width:100%;border:0;background:transparent;display:flex;align-items:center;gap:12px;padding:12px 12px;text-align:left;cursor:pointer}
+.dev-row-head:hover{background:var(--ant-color-bg-layout)}
+.dev-row-head b{font-size:var(--ant-font-size-sm);flex:1}
+.dev-count{font-size:var(--ant-font-size-sm);color:var(--ant-color-primary);background:var(--ant-color-bg-layout);border-radius:var(--ant-border-radius);padding:4px 8px}
+.dev-caret{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}
+.dev-detail{border-top:1px solid var(--ant-color-border);padding:12px 12px;background:var(--ant-color-bg-layout)}
+.dev-desc{margin:0 0 8px;font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);line-height:1.7}
+.dev-cp b{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-base);display:block;margin-bottom:4px}
+.dev-cp ul{margin:0;padding-left:20px;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);line-height:1.8}
+.doc-list{display:flex;flex-direction:column;gap:8px}
+.doc-item{display:flex;align-items:baseline;gap:12px;padding:8px 12px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);background:var(--ant-color-bg-container)}
+.doc-item b{font-size:var(--ant-font-size-sm);flex:1}
+.doc-item .muted{font-size:var(--ant-font-size-sm);white-space:nowrap}
+.ops-placeholder{display:flex;flex-direction:column;gap:12px;padding:20px;border:1px dashed var(--ant-color-border);border-radius:var(--ant-border-radius);background:var(--ant-color-bg-layout)}
.ops-placeholder .status{align-self:flex-start}
-.ops-placeholder p{margin:0;font-size:12px;color:#777;line-height:1.7}
+.ops-placeholder p{margin:0;font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);line-height:1.7}
/* ===== 方案打包示意图弹窗(v0.32) ===== */
.pkg-modal{width:560px}
-.pkg-body{padding:18px}
-.pkg-body .hint{margin:0 0 14px}
-.pkg-box{display:flex;justify-content:center;padding:6px 0 16px}
+.pkg-body{padding:20px}
+.pkg-body .hint{margin:0 0 16px}
+.pkg-box{display:flex;justify-content:center;padding:8px 0 16px}
.pkg-svg{width:240px;height:auto;filter:drop-shadow(0 8px 16px rgba(120,80,20,.18))}
-.pkg-items{display:grid;grid-template-columns:1fr 1fr;gap:10px}
-.pkg-item{display:flex;align-items:flex-start;gap:10px;padding:11px 12px;border:1px solid var(--line);border-radius:8px;background:#fff;box-shadow:0 1px 3px #0000000a}
-.pkg-item:hover{border-color:#9ed3ec;box-shadow:0 2px 8px #008fd21f}
-.pkg-icon{width:34px;height:34px;flex:0 0 auto;border-radius:7px;background:linear-gradient(135deg,#e8f4fb,#d6ecf7);color:#008ed2;display:grid;place-items:center;font-size:17px}
-.pkg-item-text{display:flex;flex-direction:column;gap:3px;min-width:0}
-.pkg-item-text b{font-size:13px;color:#2b2f33}
-.pkg-item-text small{font-size:10px;color:#888;line-height:1.5}
+.pkg-items{display:grid;grid-template-columns:1fr 1fr;gap:12px}
+.pkg-item{display:flex;align-items:flex-start;gap:12px;padding:12px 12px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);background:var(--ant-color-bg-container);box-shadow:none}
+.pkg-item:hover{border-color:var(--ant-color-primary-bg);box-shadow:none}
+.pkg-icon{width:34px;height:34px;flex:0 0 auto;border-radius:var(--ant-border-radius);background:linear-gradient(135deg,var(--ant-color-bg-layout),var(--ant-color-primary-bg));color:var(--ant-color-primary);display:grid;place-items:center;font-size:var(--ant-font-size)}
+.pkg-item-text{display:flex;flex-direction:column;gap:4px;min-width:0}
+.pkg-item-text b{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-base)}
+.pkg-item-text small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);line-height:1.5}
/* WISE-IoT Desk header — aligned to the reference application's native header. */
@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}}
/* 当前站点资产组编辑器:直接进入详情,不再经过资产组列表。 */
-.asset-editor-loading{min-height:680px;border:1px solid var(--company-border);border-top:0;background:#fff;display:flex;align-items:center;justify-content:center;gap:14px;color:#666}.asset-editor-loading.error{color:#c4382c}
-.manual-asset-editor{border-top:1px solid var(--company-border);background:#fff}.asset-editor-title{height:58px;border:1px solid var(--company-border);border-top:0;padding:0 20px}.asset-editor-title>div{display:flex;align-items:baseline;gap:18px}.asset-editor-title h2{font-size:20px;font-weight:600;color:#222}.asset-editor-title>div>span{font-size:13px;color:#777}.asset-editor-workbench{min-height:680px;height:calc(100vh - 306px);grid-template-columns:232px minmax(720px,1fr);border-color:var(--company-border)}
-.asset-editor-side{background:#fff;border-right:1px solid var(--company-border)}.asset-editor-side>.field{padding:14px 20px 15px}.asset-editor-side>.field>span{font-size:15px;font-weight:600;color:#222;margin-bottom:9px}.asset-editor-side>.field input{height:36px;border:1px solid #cfd5da;padding:0 9px}.asset-batch-entry{height:72px;padding:0 20px;font-size:15px;color:#222}.asset-batch-entry:hover{color:var(--company-primary);background:#f1f9fd}.asset-template-panel{padding:14px 20px;gap:12px}.asset-template-panel>strong{font-size:16px;font-weight:600;color:#222}.asset-template-panel>select{height:34px}.asset-template-panel>button{min-height:50px;border:0;background:transparent;grid-template-columns:24px 1fr;padding:7px 6px}.asset-template-panel>button:hover{background:#eef8fd;border:0}.asset-template-panel>button>span{font-size:18px}.asset-template-panel>button>small{color:#8a8a8a}
-.asset-tree-workspace{background:#fff;display:flex;flex-direction:column;min-height:0}.asset-editor-toolbar{height:49px;min-height:49px;padding:0 14px;position:relative}.asset-editor-toolbar>div{gap:2px}.asset-tool-group,.asset-canvas-tools{display:flex;align-items:center}.asset-tool-button.ant-btn{width:34px;min-width:34px;height:32px;padding:0;border-radius:2px;color:#7f8a91;font-size:17px}.asset-tool-button.ant-btn:not(:disabled){color:var(--company-primary)}.asset-tool-button.ant-btn:hover:not(:disabled){color:#0078b9;background:#eef8fd}.asset-tool-button.primary.ant-btn{color:var(--company-primary)}.asset-tool-divider{height:22px;border-left:1px solid #d9dde0;margin:0 5px}.asset-view-switch{position:absolute;left:50%;transform:translateX(-50%);height:32px;border:1px solid #cfd5da;display:flex!important;gap:0!important}.asset-view-switch button{width:38px;border:0;border-right:1px solid #cfd5da;background:#fff;color:#7b8790;font-size:16px}.asset-view-switch button:last-child{border-right:0}.asset-view-switch button.active{color:var(--company-primary);background:#edf8fd}.asset-tree-search{width:172px;height:30px;margin-left:8px;color:#8b969d}.asset-tree-search input{font-size:12px}
-.asset-tree-canvas{flex:1;min-height:625px;padding:44px;overflow:auto;background:#fbfbfb;cursor:grab;touch-action:none;user-select:none}.asset-tree-canvas.dragging{cursor:grabbing}.asset-tree-stage{width:max-content;min-width:100%;min-height:100%;display:flex;align-items:center;justify-content:center;transform-origin:center center;transition:transform .12s ease}.dragging .asset-tree-stage{transition:none}.asset-node-tree{display:flex;flex-direction:column;justify-content:center;align-items:flex-start;gap:26px;min-width:max-content}.asset-tree-branch{display:flex;flex-direction:row;align-items:center;position:relative}.asset-node-card{width:174px;min-height:42px;grid-template-columns:24px 1fr 22px;grid-template-rows:1fr 1fr;padding:6px 7px;border:0;border-radius:2px;box-shadow:0 2px 7px #00000025;background:#fff;cursor:pointer}.asset-node-card>span{font-size:18px}.asset-node-card>b{font-size:13px;font-weight:500;color:#5d6469}.asset-node-card>small{font-size:11px;color:#92999d}.asset-node-card>button{grid-column:3;grid-row:1/3;align-self:center;justify-self:end;width:19px;height:19px;border:0;border-radius:50%;padding:0;background:#c4c8ca;color:#fff;display:grid;place-items:center;font-size:10px}.asset-node-card>button:hover{background:var(--company-primary)}.asset-node-card:hover,.asset-node-card.selected{border:0;box-shadow:0 0 0 1px var(--company-primary),0 2px 7px #00000025}.asset-node-card.matched{box-shadow:0 0 0 2px #f5a623,0 2px 7px #00000025}.asset-tree-children{margin:0 0 0 34px;padding:0 0 0 34px;display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:17px;position:relative}.asset-tree-children:before{left:0;top:21px;bottom:21px;height:auto;border-left:1px solid #159bd3}.asset-tree-children>.asset-tree-branch:before{content:"";position:absolute;left:-34px;top:50%;width:34px;height:0;border-top:1px solid #159bd3}.asset-tree-children>.asset-tree-branch:not(:only-child):after{display:none}.asset-tree-branch>.asset-node-card+.asset-tree-children:after{content:"";position:absolute;left:-34px;top:50%;width:34px;border-top:1px solid #159bd3}.asset-tree-empty{margin:80px auto;color:#8a8a8a}.asset-tree-empty b{font-weight:600}
-.asset-node-list{flex:1;min-height:625px;overflow:auto;background:#fff}.asset-node-list-head,.asset-node-list>button{height:43px;display:grid;grid-template-columns:minmax(260px,1.5fr) 120px minmax(180px,1fr) 90px;align-items:center;text-align:left;border:0;border-bottom:1px solid #e4e7e9;padding:0 16px;background:#fff;color:#555}.asset-node-list-head{height:40px;background:#f4f6f7;font-size:12px;font-weight:600;color:#616b71}.asset-node-list>button:hover,.asset-node-list>button.selected{background:#eef8fd}.asset-node-list>button.selected{box-shadow:inset 3px 0 var(--company-primary)}.asset-node-list>button>span:first-child{color:#333}
+.asset-editor-loading{min-height:680px;border:1px solid var(--ant-color-border);border-top:0;background:var(--ant-color-bg-container);display:flex;align-items:center;justify-content:center;gap:16px;color:var(--ant-color-text-secondary)}.asset-editor-loading.error{color:var(--ant-color-error)}
+.manual-asset-editor{border-top:1px solid var(--ant-color-border);background:var(--ant-color-bg-container)}.asset-editor-title{height:58px;border:1px solid var(--ant-color-border);border-top:0;padding:0 20px}.asset-editor-title>div{display:flex;align-items:baseline;gap:20px}.asset-editor-title h2{font-size:var(--ant-font-size-heading-4);font-weight:600;color:var(--ant-color-text-base)}.asset-editor-title>div>span{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.asset-editor-workbench{min-height:680px;height:calc(100vh - 306px);grid-template-columns:232px minmax(720px,1fr);border-color:var(--ant-color-border)}
+.asset-editor-side{background:var(--ant-color-bg-container);border-right:1px solid var(--ant-color-border)}.asset-editor-side>.field{padding:16px 20px 16px}.asset-editor-side>.field>span{font-size:var(--ant-font-size);font-weight:600;color:var(--ant-color-text-base);margin-bottom:8px}.asset-editor-side>.field input{height:36px;border:1px solid var(--ant-color-border);padding:0 8px}.asset-batch-entry{height:72px;padding:0 20px;font-size:var(--ant-font-size);color:var(--ant-color-text-base)}.asset-batch-entry:hover{color:var(--ant-color-primary);background:var(--ant-color-bg-layout)}.asset-template-panel{padding:16px 20px;gap:12px}.asset-template-panel>strong{font-size:var(--ant-font-size);font-weight:600;color:var(--ant-color-text-base)}.asset-template-panel>select{height:34px}.asset-template-panel>button{min-height:50px;border:0;background:transparent;grid-template-columns:24px 1fr;padding:8px 8px}.asset-template-panel>button:hover{background:var(--ant-color-bg-layout);border:0}.asset-template-panel>button>span{font-size:var(--ant-font-size)}.asset-template-panel>button>small{color:var(--ant-color-text-secondary)}
+.asset-tree-workspace{background:var(--ant-color-bg-container);display:flex;flex-direction:column;min-height:0}.asset-editor-toolbar{height:49px;min-height:49px;padding:0 16px;position:relative}.asset-editor-toolbar>div{gap:4px}.asset-tool-group,.asset-canvas-tools{display:flex;align-items:center}.asset-tool-button.ant-btn{width:34px;min-width:34px;height:32px;padding:0;border-radius:var(--ant-border-radius);color:var(--ant-color-text-secondary);font-size:var(--ant-font-size)}.asset-tool-button.ant-btn:not(:disabled){color:var(--ant-color-primary)}.asset-tool-button.ant-btn:hover:not(:disabled){color:var(--ant-color-primary-hover);background:var(--ant-color-bg-layout)}.asset-tool-button.primary.ant-btn{color:var(--ant-color-primary)}.asset-tool-divider{height:22px;border-left:1px solid var(--ant-color-border);margin:0 4px}.asset-view-switch{position:absolute;left:50%;transform:translateX(-50%);height:32px;border:1px solid var(--ant-color-border);display:flex!important;gap:0!important}.asset-view-switch button{width:38px;border:0;border-right:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);color:var(--ant-color-text-secondary);font-size:var(--ant-font-size)}.asset-view-switch button:last-child{border-right:0}.asset-view-switch button.active{color:var(--ant-color-primary);background:var(--ant-color-bg-layout)}.asset-tree-search{width:172px;height:30px;margin-left:8px;color:var(--ant-color-text-secondary)}.asset-tree-search input{font-size:var(--ant-font-size-sm)}
+.asset-tree-canvas{flex:1;min-height:625px;padding:44px;overflow:auto;background:var(--ant-color-bg-layout);cursor:grab;touch-action:none;user-select:none}.asset-tree-canvas.dragging{cursor:grabbing}.asset-tree-stage{width:max-content;min-width:100%;min-height:100%;display:flex;align-items:center;justify-content:center;transform-origin:center center;transition:transform .12s ease}.dragging .asset-tree-stage{transition:none}.asset-node-tree{display:flex;flex-direction:column;justify-content:center;align-items:flex-start;gap:28px;min-width:max-content}.asset-tree-branch{display:flex;flex-direction:row;align-items:center;position:relative}.asset-node-card{width:174px;min-height:42px;grid-template-columns:24px 1fr 22px;grid-template-rows:1fr 1fr;padding:8px 8px;border:0;border-radius:var(--ant-border-radius);box-shadow:none;background:var(--ant-color-bg-container);cursor:pointer}.asset-node-card>span{font-size:var(--ant-font-size)}.asset-node-card>b{font-size:var(--ant-font-size-sm);font-weight:600;color:var(--ant-color-text-secondary)}.asset-node-card>small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.asset-node-card>button{grid-column:3;grid-row:1/3;align-self:center;justify-self:end;width:19px;height:19px;border:0;border-radius:50%;padding:0;background:var(--ant-color-text-disabled);color:var(--ant-color-text-light-solid);display:grid;place-items:center;font-size:var(--ant-font-size-sm)}.asset-node-card>button:hover{background:var(--ant-color-primary)}.asset-node-card:hover,.asset-node-card.selected{border:0;box-shadow:none}.asset-node-card.matched{box-shadow:none}.asset-tree-children{margin:0 0 0 36px;padding:0 0 0 36px;display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:16px;position:relative}.asset-tree-children:before{left:0;top:21px;bottom:21px;height:auto;border-left:1px solid var(--ant-color-primary)}.asset-tree-children>.asset-tree-branch:before{content:"";position:absolute;left:-34px;top:50%;width:34px;height:0;border-top:1px solid var(--ant-color-primary)}.asset-tree-children>.asset-tree-branch:not(:only-child):after{display:none}.asset-tree-branch>.asset-node-card+.asset-tree-children:after{content:"";position:absolute;left:-34px;top:50%;width:34px;border-top:1px solid var(--ant-color-primary)}.asset-tree-empty{margin:80px auto;color:var(--ant-color-text-secondary)}.asset-tree-empty b{font-weight:600}
+.asset-node-list{flex:1;min-height:625px;overflow:auto;background:var(--ant-color-bg-container)}.asset-node-list-head,.asset-node-list>button{height:43px;display:grid;grid-template-columns:minmax(260px,1.5fr) 120px minmax(180px,1fr) 90px;align-items:center;text-align:left;border:0;border-bottom:1px solid var(--ant-color-border);padding:0 16px;background:var(--ant-color-bg-container);color:var(--ant-color-text-secondary)}.asset-node-list-head{height:40px;background:var(--ant-color-bg-layout);font-size:var(--ant-font-size-sm);font-weight:600;color:var(--ant-color-text-secondary)}.asset-node-list>button:hover,.asset-node-list>button.selected{background:var(--ant-color-bg-layout)}.asset-node-list>button.selected{box-shadow:none}.asset-node-list>button>span:first-child{color:var(--ant-color-text-base)}
@media(max-width:1100px){.asset-editor-workbench{grid-template-columns:210px minmax(660px,1fr)}.asset-editor-title span{display:none}.asset-tree-search{width:130px}.asset-zoom-value{display:none}}
/* Asset 节点配置抽屉:对齐原资产设定的三 Tab 信息架构。 */
-.asset-node-drawer .ant-drawer-header{min-height:56px;padding:0 24px;border-bottom:1px solid #d7dade}.asset-node-drawer .ant-drawer-title{font-size:16px;font-weight:500;color:#222}.asset-node-drawer .ant-drawer-body{padding:0;overflow:auto}.asset-node-drawer .ant-drawer-footer{height:56px;padding:11px 24px;border-top:1px solid #d7dade;background:#fff}.node-drawer-footer{display:flex;justify-content:flex-end;align-items:center;gap:8px}.node-drawer-footer .button{margin:0}
-.asset-node-drawer .node-drawer-tabs{height:49px;margin:0;padding-left:20px;gap:0;border-bottom:1px solid #d7dade}.asset-node-drawer .node-drawer-tabs button{height:49px;padding:0 20px;border-right:1px solid #d7dade;border-bottom:3px solid transparent;color:#777}.asset-node-drawer .node-drawer-tabs button:first-child{padding-left:0;margin-right:20px}.asset-node-drawer .node-drawer-tabs button.active{color:#555;border-bottom-color:var(--company-primary)}
-.node-config-page{padding:24px 24px 48px;display:flex;flex-direction:column;gap:38px}.node-config-section>h3{height:22px;line-height:22px;margin:0 0 24px;padding-left:17px;border-left:4px solid var(--company-primary);font-size:16px;font-weight:600;color:#333}.node-config-section>h4,.binding-parameter-head h4{margin:0 0 12px;font-size:16px;color:#3f4549}.node-id-row{display:grid;grid-template-columns:105px 1fr;align-items:center;max-width:420px;margin:0 0 20px 47px;color:#777}.node-id-row b{font-weight:400;color:#56606a}.node-basic-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px 36px}.node-basic-grid .field,.node-property-grid .field{display:grid;grid-template-columns:105px minmax(0,1fr);align-items:center;gap:12px}.node-basic-grid .field>span,.node-property-grid .field>span{font-size:14px;font-weight:400;color:#666;text-align:right}.node-basic-grid .field input,.node-basic-grid .field select,.node-property-grid .field input{width:100%;height:34px;padding:0 10px;border:1px solid #ced5da}.node-basic-grid .field:nth-child(5){grid-column:1/-1}.node-image-row{display:flex;align-items:flex-start;gap:12px;margin-left:20px}.node-image-preview{width:108px;height:108px;border:1px solid #cbd2d7;background:#fafafa;display:grid;place-items:center;overflow:hidden;color:#9da8ae;font-size:36px;cursor:pointer}.node-image-preview img{width:100%;height:100%;object-fit:contain}.node-image-row>div{display:flex;flex-direction:column;align-items:flex-start;gap:5px}.node-upload-link{color:var(--company-primary);font-size:14px;cursor:pointer}.node-image-row small{font-size:12px;color:#aaa}.node-image-row .text-button{margin:3px 0 0}.node-config-section .ant-qrcode{margin-left:20px;padding:0;background:#fff}
-.node-property-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px 36px}.node-property-grid .field{min-width:0}.node-property-grid .field:nth-last-child(2){grid-column:auto}.node-property-grid input[type="date"]{color:#666}
-.binding-page{gap:28px}.edge-component-table{border:1px solid #d5dade;max-height:200px;overflow:auto}.edge-component-table>header,.edge-component-table>div{min-height:45px;display:grid;grid-template-columns:1.2fr 1fr 1fr 1.2fr 88px;align-items:center;padding:0 8px;border-bottom:1px solid #dfe3e6}.edge-component-table>header{min-height:38px;background:#f4f5f6;border-bottom:2px solid var(--company-primary);font-size:12px;font-weight:600;color:#444}.edge-component-table>div:last-child{border-bottom:0}.edge-component-table>div.active{background:#f1f9fd}.edge-component-table b{font-weight:400}.edge-component-table>div>span:last-child{display:flex;justify-content:flex-end;gap:7px}.edge-component-table button{width:28px;height:28px;border:0;background:transparent;color:var(--company-primary);font-size:17px}.edge-component-table button:hover{background:#e8f6fc}.binding-component-tabs{height:42px;display:flex;align-items:flex-end;justify-content:center;margin-bottom:12px}.binding-component-tabs button{height:34px;padding:0 20px;border:0;border-right:1px solid #d7dade;background:#fff;color:#a0a6aa}.binding-component-tabs button:last-child{border-right:0}.binding-component-tabs button.active{color:var(--company-primary);font-weight:600}.binding-parameter-head{display:flex;align-items:center;justify-content:space-between;border:1px solid #d5dade;border-bottom:0;padding:8px 12px}.binding-parameter-head h4{margin:0}.binding-parameter-head .button{height:30px;margin:0}.binding-parameter-table{border:1px solid #d5dade;overflow:auto}.binding-parameter-table>header,.binding-parameter-table>div:not(.binding-empty){min-width:760px;display:grid;grid-template-columns:1fr 1.2fr 70px 64px 1fr 1.2fr 42px;align-items:center;gap:6px;padding:0 8px}.binding-parameter-table>header{height:38px;background:#f4f5f6;border-bottom:2px solid var(--company-primary);font-size:12px;font-weight:600}.binding-parameter-table>div:not(.binding-empty){min-height:44px;border-bottom:1px solid #e1e4e6}.binding-parameter-table input{width:100%;height:30px;border:1px solid #d3d8dc;padding:0 6px}.binding-parameter-table .trash{color:#d84b3f}.binding-empty{height:120px;display:grid;place-items:center;color:#999}.binding-import-backdrop{z-index:1300}.binding-import-dialog{width:min(620px,calc(100vw - 48px))}
+.asset-node-drawer .ant-drawer-header{min-height:56px;padding:0 24px;border-bottom:1px solid var(--ant-color-border)}.asset-node-drawer .ant-drawer-title{font-size:var(--ant-font-size);font-weight:600;color:var(--ant-color-text-base)}.asset-node-drawer .ant-drawer-body{padding:0;overflow:auto}.asset-node-drawer .ant-drawer-footer{height:56px;padding:12px 24px;border-top:1px solid var(--ant-color-border);background:var(--ant-color-bg-container)}.node-drawer-footer{display:flex;justify-content:flex-end;align-items:center;gap:8px}.node-drawer-footer .button{margin:0}
+.asset-node-drawer .node-drawer-tabs{height:49px;margin:0;padding-left:20px;gap:0;border-bottom:1px solid var(--ant-color-border)}.asset-node-drawer .node-drawer-tabs button{height:49px;padding:0 20px;border-right:1px solid var(--ant-color-border);border-bottom:3px solid transparent;color:var(--ant-color-text-secondary)}.asset-node-drawer .node-drawer-tabs button:first-child{padding-left:0;margin-right:20px}.asset-node-drawer .node-drawer-tabs button.active{color:var(--ant-color-text-secondary);border-bottom-color:var(--ant-color-primary)}
+.node-config-page{padding:24px 24px 48px;display:flex;flex-direction:column;gap:40px}.node-config-section>h3{height:22px;line-height:22px;margin:0 0 24px;padding-left:16px;border-left:4px solid var(--ant-color-primary);font-size:var(--ant-font-size);font-weight:600;color:var(--ant-color-text-base)}.node-config-section>h4,.binding-parameter-head h4{margin:0 0 12px;font-size:var(--ant-font-size);color:var(--ant-color-text-secondary)}.node-id-row{display:grid;grid-template-columns:105px 1fr;align-items:center;max-width:420px;margin:0 0 20px 48px;color:var(--ant-color-text-secondary)}.node-id-row b{font-weight:400;color:var(--ant-color-text-secondary)}.node-basic-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px 36px}.node-basic-grid .field,.node-property-grid .field{display:grid;grid-template-columns:105px minmax(0,1fr);align-items:center;gap:12px}.node-basic-grid .field>span,.node-property-grid .field>span{font-size:var(--ant-font-size);font-weight:400;color:var(--ant-color-text-secondary);text-align:right}.node-basic-grid .field input,.node-basic-grid .field select,.node-property-grid .field input{width:100%;height:34px;padding:0 12px;border:1px solid var(--ant-color-border)}.node-basic-grid .field:nth-child(5){grid-column:1/-1}.node-image-row{display:flex;align-items:flex-start;gap:12px;margin-left:20px}.node-image-preview{width:108px;height:108px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-layout);display:grid;place-items:center;overflow:hidden;color:var(--ant-color-text-disabled);font-size:var(--ant-font-size-heading-2);cursor:pointer}.node-image-preview img{width:100%;height:100%;object-fit:contain}.node-image-row>div{display:flex;flex-direction:column;align-items:flex-start;gap:4px}.node-upload-link{color:var(--ant-color-primary);font-size:var(--ant-font-size);cursor:pointer}.node-image-row small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-disabled)}.node-image-row .text-button{margin:4px 0 0}.node-config-section .ant-qrcode{margin-left:20px;padding:0;background:var(--ant-color-bg-container)}
+.node-property-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px 36px}.node-property-grid .field{min-width:0}.node-property-grid .field:nth-last-child(2){grid-column:auto}.node-property-grid input[type="date"]{color:var(--ant-color-text-secondary)}
+.binding-page{gap:28px}.edge-component-table{border:1px solid var(--ant-color-border);max-height:200px;overflow:auto}.edge-component-table>header,.edge-component-table>div{min-height:45px;display:grid;grid-template-columns:1.2fr 1fr 1fr 1.2fr 88px;align-items:center;padding:0 8px;border-bottom:1px solid var(--ant-color-border)}.edge-component-table>header{min-height:38px;background:var(--ant-color-bg-layout);border-bottom:2px solid var(--ant-color-primary);font-size:var(--ant-font-size-sm);font-weight:600;color:var(--ant-color-text-secondary)}.edge-component-table>div:last-child{border-bottom:0}.edge-component-table>div.active{background:var(--ant-color-bg-layout)}.edge-component-table b{font-weight:400}.edge-component-table>div>span:last-child{display:flex;justify-content:flex-end;gap:8px}.edge-component-table button{width:28px;height:28px;border:0;background:transparent;color:var(--ant-color-primary);font-size:var(--ant-font-size)}.edge-component-table button:hover{background:var(--ant-color-bg-layout)}.binding-component-tabs{height:42px;display:flex;align-items:flex-end;justify-content:center;margin-bottom:12px}.binding-component-tabs button{height:34px;padding:0 20px;border:0;border-right:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);color:var(--ant-color-text-disabled)}.binding-component-tabs button:last-child{border-right:0}.binding-component-tabs button.active{color:var(--ant-color-primary);font-weight:600}.binding-parameter-head{display:flex;align-items:center;justify-content:space-between;border:1px solid var(--ant-color-border);border-bottom:0;padding:8px 12px}.binding-parameter-head h4{margin:0}.binding-parameter-head .button{height:30px;margin:0}.binding-parameter-table{border:1px solid var(--ant-color-border);overflow:auto}.binding-parameter-table>header,.binding-parameter-table>div:not(.binding-empty){min-width:760px;display:grid;grid-template-columns:1fr 1.2fr 70px 64px 1fr 1.2fr 42px;align-items:center;gap:8px;padding:0 8px}.binding-parameter-table>header{height:38px;background:var(--ant-color-bg-layout);border-bottom:2px solid var(--ant-color-primary);font-size:var(--ant-font-size-sm);font-weight:600}.binding-parameter-table>div:not(.binding-empty){min-height:44px;border-bottom:1px solid var(--ant-color-border)}.binding-parameter-table input{width:100%;height:30px;border:1px solid var(--ant-color-border);padding:0 8px}.binding-parameter-table .trash{color:var(--ant-color-error)}.binding-empty{height:120px;display:grid;place-items:center;color:var(--ant-color-text-secondary)}.binding-import-backdrop{z-index:1300}.binding-import-dialog{width:min(620px,calc(100vw - 48px))}
@media(max-width:1200px){.asset-node-drawer .ant-drawer-content-wrapper{width:78vw!important}.node-basic-grid,.node-property-grid{grid-template-columns:1fr}.node-basic-grid .field:nth-child(5){grid-column:auto}}@media(max-width:760px){.asset-node-drawer .ant-drawer-content-wrapper{width:100vw!important}.node-basic-grid .field,.node-property-grid .field{grid-template-columns:90px minmax(0,1fr)}.node-config-page{padding:20px 16px}.edge-component-table>header,.edge-component-table>div{grid-template-columns:1.2fr 1fr 1fr 72px}.edge-component-table>header>span:nth-child(4),.edge-component-table>div>span:nth-child(4){display:none}}
+
+/* 领域方案:从分步编辑调整为领域能力展示。 */
+.domain-showcase-page{display:flex;flex-direction:column;gap:16px}.domain-showcase-intro{min-height:92px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);border-top:3px solid var(--ant-color-primary);display:grid;grid-template-columns:150px 1fr auto;align-items:center;padding:16px 20px;gap:24px}.domain-showcase-intro>div:first-child{display:flex;align-items:baseline;gap:8px;color:var(--ant-color-text-secondary)}.domain-showcase-intro>div:first-child b{font-size:var(--ant-font-size-heading-2);color:var(--ant-color-text-base)}.domain-showcase-intro>p{margin:0;padding-left:24px;border-left:1px solid var(--ant-color-border);color:var(--ant-color-text-secondary);line-height:1.8}.domain-showcase-total{display:flex;gap:20px}.domain-showcase-total span{display:flex;flex-direction:column;gap:4px;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.domain-showcase-total b{color:var(--ant-color-text-base);font-size:var(--ant-font-size)}.domain-showcase-filter{height:52px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);display:flex;align-items:center;gap:12px;padding:12px 12px}.domain-showcase-filter label{width:300px;height:32px;border:1px solid var(--ant-color-border);display:flex;align-items:center;padding:0 12px;color:var(--ant-color-text-secondary)}.domain-showcase-filter label:focus-within{border-color:var(--ant-color-primary)}.domain-showcase-filter input{width:100%;height:30px;border:0;outline:0;padding-left:8px}.domain-showcase-filter select{height:32px;min-width:180px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);padding:0 32px 0 12px}.domain-showcase-filter>span{margin-left:auto;color:var(--ant-color-text-secondary)}.domain-showcase-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.domain-showcase-card{min-height:340px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);border-top:3px solid var(--ant-color-primary);padding:20px;display:flex;flex-direction:column;cursor:pointer;transition:box-shadow .15s,border-color .15s}.domain-showcase-card:hover{border-color:var(--ant-color-primary-bg);box-shadow:none}.domain-showcase-card.violet{border-top-color:var(--ant-color-primary)}.domain-showcase-card.green{border-top-color:var(--ant-color-success)}.domain-showcase-card.gray{border-top-color:var(--ant-color-border)}.domain-showcase-card>header{height:42px;display:flex;align-items:flex-start;justify-content:space-between}.domain-showcase-card>header>div:last-child{display:flex;align-items:center;gap:8px}.domain-showcase-mark{font-size:var(--ant-font-size-sm);font-weight:600;letter-spacing:1px;color:var(--ant-color-primary);background:var(--ant-color-bg-layout);padding:8px 8px}.violet .domain-showcase-mark{color:var(--ant-color-primary);background:var(--ant-color-bg-layout)}.green .domain-showcase-mark{color:var(--ant-color-success);background:var(--ant-color-bg-layout)}.gray .domain-showcase-mark{color:var(--ant-color-text-secondary);background:var(--ant-color-bg-layout)}.domain-showcase-card h2{font-size:var(--ant-font-size);margin:16px 0 8px;color:var(--ant-color-text-base)}.domain-showcase-card>p{height:44px;margin:0;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);line-height:1.75;overflow:hidden}.domain-showcase-capabilities{display:grid;grid-template-columns:1fr 1fr;margin-top:16px;border:1px solid var(--ant-color-border)}.domain-showcase-capabilities>span{min-height:62px;padding:12px;display:grid;grid-template-columns:22px 1fr;grid-template-rows:auto auto;align-items:center;border-right:1px solid var(--ant-color-border);border-bottom:1px solid var(--ant-color-border)}.domain-showcase-capabilities>span:nth-child(2n){border-right:0}.domain-showcase-capabilities>span:nth-last-child(-n+2){border-bottom:0}.domain-showcase-capabilities svg{grid-row:1/3;color:var(--ant-color-primary);font-size:var(--ant-font-size)}.domain-showcase-capabilities b{font-size:var(--ant-font-size-sm)}.domain-showcase-capabilities small{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.domain-showcase-card>footer{margin-top:auto;padding-top:16px;display:flex;align-items:center;justify-content:space-between;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.domain-showcase-card>footer button{height:30px;border:0;background:transparent;color:var(--ant-color-primary);padding:0;font-weight:600}.domain-showcase-card>footer button:hover{text-decoration:underline}
+
+.domain-capability-detail{display:flex;flex-direction:column;gap:12px}.domain-capability-back{align-self:flex-start;border:0;background:transparent;color:var(--ant-color-primary);padding:0;height:28px}.domain-capability-head{min-height:156px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);border-top:3px solid var(--ant-color-primary);padding:24px;display:grid;grid-template-columns:76px 1fr auto;gap:20px;align-items:center}.domain-capability-head.violet{border-top-color:var(--ant-color-primary)}.domain-capability-head.green{border-top-color:var(--ant-color-success)}.domain-capability-head.gray{border-top-color:var(--ant-color-border)}.domain-capability-code{width:76px;height:76px;display:grid;place-items:center;background:var(--ant-color-bg-layout);color:var(--ant-color-primary);font-weight:600;font-size:var(--ant-font-size-sm);letter-spacing:.8px}.domain-capability-head.violet .domain-capability-code{color:var(--ant-color-primary);background:var(--ant-color-bg-layout)}.domain-capability-head.green .domain-capability-code{color:var(--ant-color-success);background:var(--ant-color-bg-layout)}.domain-capability-head.gray .domain-capability-code{color:var(--ant-color-text-secondary);background:var(--ant-color-bg-layout)}.domain-capability-copy>div{display:flex;align-items:center;gap:8px}.domain-capability-copy>div>span:last-child{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.domain-capability-copy h2{margin:8px 0 4px;font-size:var(--ant-font-size-heading-3)}.domain-capability-copy p{margin:0;color:var(--ant-color-text-secondary);line-height:1.6}.domain-capability-copy small{display:block;margin-top:8px;color:var(--ant-color-text-secondary)}.domain-capability-actions{display:flex;align-items:center;gap:4px}.domain-capability-summary{height:66px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);display:grid;grid-template-columns:repeat(4,1fr)}.domain-capability-summary span{display:flex;align-items:baseline;justify-content:center;gap:8px;border-right:1px solid var(--ant-color-border);color:var(--ant-color-text-secondary)}.domain-capability-summary span:last-child{border-right:0}.domain-capability-summary b{font-size:var(--ant-font-size-heading-3);color:var(--ant-color-text-base)}.domain-capability-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.domain-capability-card{min-height:286px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);display:flex;flex-direction:column}.domain-capability-card>header{height:72px;padding:12px 16px;border-bottom:1px solid var(--ant-color-border);display:grid;grid-template-columns:38px 1fr auto;align-items:center;gap:12px}.domain-capability-icon{width:36px;height:36px;background:var(--ant-color-bg-layout);color:var(--ant-color-primary);display:grid;place-items:center;font-size:var(--ant-font-size-heading-4)}.domain-capability-card>header h3{margin:0 0 4px;font-size:var(--ant-font-size)}.domain-capability-card>header p{margin:0;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.domain-capability-card>header>strong{font-size:var(--ant-font-size-sm);font-weight:600;color:var(--ant-color-text-secondary);background:var(--ant-color-bg-layout);padding:4px 8px}.domain-capability-body{min-height:158px;padding:16px;background:var(--ant-color-bg-layout)}.domain-capability-card>footer{height:50px;margin-top:auto;border-top:1px solid var(--ant-color-border);display:flex;align-items:center;justify-content:flex-end;padding:0 12px;gap:8px}.domain-capability-card>footer button{height:29px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);color:var(--ant-color-text-secondary);padding:0 12px}.domain-capability-card>footer button:first-child{color:var(--ant-color-primary);border-color:var(--ant-color-primary-bg)}.domain-capability-card>footer button.capability-edit-button{margin-right:auto}.domain-capability-card>footer button:hover{border-color:var(--ant-color-primary);color:var(--ant-color-primary)}.capability-device-tags{display:flex;flex-wrap:wrap;gap:8px}.capability-device-tags span{height:28px;display:flex;align-items:center;gap:8px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);padding:0 8px;color:var(--ant-color-text-secondary)}.capability-device-tags b{min-width:18px;height:18px;line-height:18px;text-align:center;border-radius:var(--ant-border-radius);background:var(--ant-color-bg-layout);color:var(--ant-color-primary);font-size:var(--ant-font-size-sm)}.capability-mini-tree{margin-top:16px;height:62px;display:flex;align-items:center;position:relative;padding-left:4px}.capability-mini-tree>i{width:10px;height:10px;border:2px solid var(--ant-color-primary);border-radius:50%;margin-right:8px}.capability-mini-tree>b{background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);padding:8px 12px;font-size:var(--ant-font-size-sm)}.capability-mini-tree>span{position:relative;margin-left:28px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);padding:8px 8px;font-size:var(--ant-font-size-sm)}.capability-mini-tree>span:before{content:"";position:absolute;left:-29px;top:50%;width:28px;border-top:1px solid var(--ant-color-primary)}.capability-page-pair{display:grid;grid-template-columns:1fr 1fr;gap:16px;height:128px}.mini-dashboard,.mini-twin{position:relative;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-layout);padding:20px 8px 8px;display:grid;grid-template-columns:1.2fr .8fr;gap:4px}.mini-dashboard:before{content:"仪表板";position:absolute;left:8px;top:5px;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.mini-dashboard i{background:var(--ant-color-primary)}.mini-dashboard i:nth-child(2),.mini-dashboard i:nth-child(3){background:var(--ant-color-primary-bg)}.mini-twin{overflow:hidden;background:linear-gradient(160deg,var(--ant-color-bg-layout),var(--ant-color-bg-layout))}.mini-twin:before{content:"SC 数字孪生";position:absolute;left:8px;top:5px;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.mini-twin i{width:38px;height:27px;background:var(--ant-color-primary);transform:skewY(-18deg);align-self:end;box-shadow:none}.mini-twin span{position:absolute;right:7px;bottom:5px;color:var(--ant-color-primary);font-size:var(--ant-font-size-sm)}.capability-file-list{display:flex;flex-direction:column;gap:8px}.capability-file-list span{height:36px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);display:flex;align-items:center;padding:0 8px;color:var(--ant-color-text-secondary)}.capability-file-list b{width:24px;height:24px;background:var(--ant-color-bg-layout);color:var(--ant-color-primary);display:grid;place-items:center;margin-right:8px;font-size:var(--ant-font-size-sm)}.capability-menu-preview{width:180px;min-height:126px;background:var(--ant-color-bg-layout);border:1px solid var(--ant-color-border);display:flex;flex-direction:column;padding:8px;margin:auto}.capability-menu-preview>b{height:27px;background:var(--ant-color-bg-container);padding:8px 8px;font-size:var(--ant-font-size-sm)}.capability-menu-preview>span{height:22px;padding:4px 12px;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.capability-menu-preview>span:first-of-type{background:var(--ant-color-primary-bg);color:var(--ant-color-primary)}
+
+.domain-capability-grid{grid-template-columns:repeat(4,minmax(0,1fr))}.domain-capability-card{height:calc(100vh - 450px);min-height:500px;max-height:680px}.domain-capability-card>header{height:auto;min-height:82px;padding:12px 16px;grid-template-columns:38px minmax(0,1fr)}.domain-capability-card>header>strong{grid-column:1/-1;justify-self:end}.domain-capability-body.capability-empty-slot{flex:1;min-height:0;padding:0;background:var(--ant-color-bg-container)}.domain-capability-body.capability-asset-content,.domain-capability-body.capability-page-content,.domain-capability-body.capability-twin-content,.domain-capability-body.capability-knowledge-content{flex:1;min-height:0;padding:16px;background:var(--ant-color-bg-container);overflow-y:auto;scrollbar-gutter:stable}.capability-section-title{height:30px;display:flex;align-items:flex-start;justify-content:space-between;border-bottom:1px solid var(--ant-color-border);margin-bottom:12px}.capability-section-title b{font-size:var(--ant-font-size);font-weight:600;color:var(--ant-color-text-base)}.capability-section-title span{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.device-template-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(86px,1fr));gap:8px}.device-template-tile{aspect-ratio:1/1;min-height:88px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:8px;text-align:center;transition:border-color .15s,box-shadow .15s}.device-template-tile:hover{border-color:var(--ant-color-primary-bg);box-shadow:none}.device-template-tile>span{width:38px;height:38px;display:grid;place-items:center;background:var(--ant-color-bg-layout);color:var(--ant-color-primary);font-size:var(--ant-font-size-heading-4)}.device-template-tile>b{width:100%;font-size:var(--ant-font-size-sm);font-weight:600;color:var(--ant-color-text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.extension-page-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.extension-page-tile{min-width:0;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);overflow:hidden}.extension-page-tile .dashboard-thumb-live{height:92px;min-height:92px;border:0;border-bottom:1px solid var(--ant-color-border);background:var(--ant-color-bg-layout)}.extension-page-tile>b{display:block;height:34px;line-height:34px;padding:0 8px;font-size:var(--ant-font-size-sm);font-weight:600;color:var(--ant-color-text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.extension-page-tile:hover{border-color:var(--ant-color-primary-bg);box-shadow:none}.twin-scene-card-list{display:flex;flex-direction:column;gap:12px}.twin-scene-card{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);overflow:hidden}.twin-scene-card .sc-scene{height:145px}.twin-scene-card .sc-scene:after{height:72px;bottom:7px}.twin-scene-card .sc-scene i{transform:scale(.7);transform-origin:left bottom}.twin-scene-card>div:last-child{height:42px;display:flex;align-items:center;justify-content:space-between;padding:0 12px}.twin-scene-card b{font-size:var(--ant-font-size-sm);font-weight:600}.twin-scene-card small{font-size:var(--ant-font-size-sm);color:var(--ant-color-success);background:var(--ant-color-bg-layout);padding:4px 8px}.knowledge-group+.knowledge-group{margin-top:20px}.knowledge-item-list{display:flex;flex-direction:column;gap:8px}.knowledge-item{min-height:55px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);display:grid;grid-template-columns:38px minmax(0,1fr);align-items:center;gap:12px;padding:8px}.knowledge-type{width:34px;height:34px;display:grid;place-items:center;background:var(--ant-color-bg-layout);color:var(--ant-color-primary);font-size:var(--ant-font-size)}.knowledge-type.link{background:var(--ant-color-bg-layout);color:var(--ant-color-primary)}.knowledge-item>div{min-width:0;display:flex;flex-direction:column;gap:4px}.knowledge-item b{font-size:var(--ant-font-size-sm);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.knowledge-item small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.knowledge-item:hover{border-color:var(--ant-color-primary-bg)}.device-template-empty{min-height:150px;display:grid;place-items:center;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}
+
+.capability-preview-modal{width:min(1040px,94vw);max-width:94vw}.capability-preview-modal>.modal-body{min-height:520px;padding:20px}.capability-assets-preview,.capability-knowledge-preview{display:grid;grid-template-columns:1.2fr .8fr;gap:16px}.capability-assets-preview>section,.capability-knowledge-preview>section{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);padding:16px}.capability-assets-preview h3,.capability-knowledge-preview h3{margin:0 0 12px;font-size:var(--ant-font-size)}.capability-full-tree{min-height:380px;background:var(--ant-color-bg-layout);padding:24px}.capability-full-tree>b{display:block;width:180px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);padding:12px 12px;margin-bottom:20px}.capability-full-tree>div{margin:0 0 12px 36px;border-left:1px solid var(--ant-color-primary);padding-left:24px;position:relative}.capability-full-tree>div:before{content:"";position:absolute;left:0;top:13px;width:20px;border-top:1px solid var(--ant-color-primary)}.capability-full-tree span{display:block;width:170px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);padding:8px 8px}.capability-full-tree small{display:block;margin:4px 0 0 12px;color:var(--ant-color-text-secondary)}.capability-preview-tabs{height:44px;display:flex;border-bottom:1px solid var(--ant-color-border);margin-bottom:16px}.capability-preview-tabs button{height:44px;border:0;border-bottom:3px solid transparent;background:var(--ant-color-bg-container);padding:0 20px;color:var(--ant-color-text-secondary)}.capability-preview-tabs button.active{color:var(--ant-color-primary);border-bottom-color:var(--ant-color-primary)}.capability-preview-tabs b{font-size:var(--ant-font-size-sm);background:var(--ant-color-bg-layout);padding:4px 8px;border-radius:var(--ant-border-radius);margin-left:4px}.capability-dashboard-grid,.capability-twin-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.capability-dashboard-grid article,.capability-twin-grid article{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container)}.capability-dashboard-grid .dashboard-thumb-live{height:230px!important;min-height:230px;background:var(--ant-color-bg-layout)}.capability-dashboard-grid article>div:last-child,.capability-twin-grid article>div:last-child{padding:12px 12px;display:flex;flex-direction:column;gap:4px}.capability-dashboard-grid article span,.capability-twin-grid article span{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.sc-scene{height:230px;position:relative;overflow:hidden;background:linear-gradient(#edf5f7,#d9e5e8);perspective:500px}.sc-scene:after{content:"";position:absolute;left:8%;right:8%;bottom:18px;height:105px;background:linear-gradient(160deg,#b7cbd1,#eef4f5);transform:rotateX(58deg);border:1px solid #9fb7bf}.sc-scene i{position:absolute;z-index:2;width:75px;height:62px;background:#779aa5;border:1px solid #63828c;box-shadow:9px -9px #aec4ca}.sc-scene i:nth-child(1){left:20%;bottom:68px}.sc-scene i:nth-child(2){left:43%;bottom:86px;background:#7daeb9}.sc-scene i:nth-child(3){left:62%;bottom:57px;width:56px;height:76px}.sc-scene i:nth-child(4){left:76%;bottom:96px;width:40px;height:45px;background:#51a7c6}.sc-scene>span{position:absolute;z-index:3;right:12px;top:10px;background:#ffffffdd;color:#477783;padding:4px 8px;font-size:10px}.capability-knowledge-preview>section>div{height:58px;border-bottom:1px solid var(--ant-color-border);display:flex;align-items:center;gap:12px}.capability-knowledge-preview>section>div>svg{font-size:var(--ant-font-size-heading-4);color:var(--ant-color-primary)}.capability-knowledge-preview>section>div>span:last-child{display:flex;flex-direction:column;gap:4px}.capability-knowledge-preview small{color:var(--ant-color-text-secondary)}.file-type{width:38px;height:27px;display:grid!important;place-items:center;background:var(--ant-color-bg-layout);color:var(--ant-color-primary);font-size:var(--ant-font-size-sm)}.capability-menu-full{display:grid;grid-template-columns:230px 1fr;min-height:500px;border:1px solid var(--ant-color-border)}.capability-menu-full aside{background:var(--ant-color-bg-layout);border-right:1px solid var(--ant-color-border);padding:12px}.capability-menu-full aside>b{display:block;padding:12px 8px;border-bottom:1px solid var(--ant-color-border)}.capability-menu-full aside>div{display:flex;flex-direction:column}.capability-menu-full aside strong{padding:12px 8px;font-weight:600}.capability-menu-full aside span{padding:8px 24px;color:var(--ant-color-text-secondary)}.capability-menu-full aside>div:first-of-type strong{background:var(--ant-color-primary-bg);color:var(--ant-color-primary)}.capability-menu-full>section{background:var(--ant-color-bg-layout)}.menu-screen-title{height:48px;background:var(--ant-color-bg-container);border-bottom:1px solid var(--ant-color-border);padding:16px 20px;font-weight:600}.menu-screen-canvas{display:grid;grid-template-columns:1fr 1fr;gap:16px;padding:24px}.menu-screen-canvas i{height:170px;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);box-shadow:none}.menu-screen-canvas i:nth-child(2){box-shadow:none}.menu-screen-canvas i:nth-child(3){box-shadow:none}.menu-screen-canvas i:nth-child(4){box-shadow:none}
+
+.domain-import-page{max-width:980px}.domain-import-layout{display:grid;grid-template-columns:1fr 1fr;gap:24px;padding:8px}.domain-import-drop{min-height:310px;border:1px dashed var(--ant-color-primary-bg);background:var(--ant-color-bg-layout);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;text-align:center;padding:28px;cursor:pointer}.domain-import-drop:hover,.domain-import-drop.selected{border-color:var(--ant-color-primary);background:var(--ant-color-bg-layout)}.domain-import-drop>svg{font-size:var(--ant-font-size-heading-2);color:var(--ant-color-primary)}.domain-import-drop>b{font-size:var(--ant-font-size)}.domain-import-drop>span{max-width:320px;color:var(--ant-color-text-secondary);line-height:1.7}.domain-import-form{display:flex;flex-direction:column;gap:16px}.domain-import-form .field input,.domain-import-form .field select,.domain-import-form .field textarea{width:100%}.domain-package-check{margin:20px 8px 0;padding:16px;background:var(--ant-color-bg-layout);display:flex;align-items:center;gap:8px}.domain-package-check>b{margin-right:12px}.domain-package-check>span{background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);padding:8px 8px;color:var(--ant-color-text-secondary)}.domain-import-actions{display:flex;justify-content:flex-end;margin:16px 0 4px}.domain-maintain-modal{width:min(720px,92vw)}.domain-maintain-note{margin-top:20px;padding:12px 16px;background:var(--ant-color-bg-layout);border-left:3px solid var(--ant-color-primary);color:var(--ant-color-text-secondary)}.capability-package-modal{width:min(660px,92vw)}.capability-package-modal>.modal-body>div{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:16px}.capability-package-modal>.modal-body>div>span{min-height:70px;border:1px solid var(--ant-color-border);padding:12px;display:grid;grid-template-columns:28px 1fr;grid-template-rows:auto auto;align-items:center}.capability-package-modal svg{grid-row:1/3;color:var(--ant-color-primary);font-size:var(--ant-font-size-heading-4)}.capability-package-modal small{color:var(--ant-color-text-secondary)}
+@media(max-width:1320px){.domain-showcase-grid{grid-template-columns:1fr 1fr}.domain-showcase-intro{grid-template-columns:130px 1fr}.domain-showcase-total{grid-column:1/-1;border-top:1px solid var(--ant-color-border);padding-top:12px}.domain-capability-grid{grid-template-columns:1fr 1fr}.domain-capability-card{height:390px;min-height:0}.domain-capability-body.capability-empty-slot{min-height:0}.domain-capability-mini-tree>span:nth-last-child(n+4){display:none}}
+@media(max-width:900px){.domain-showcase-grid,.domain-capability-grid,.capability-assets-preview,.capability-knowledge-preview,.domain-import-layout{grid-template-columns:1fr}.domain-showcase-intro{grid-template-columns:1fr}.domain-showcase-intro>p{padding:0;border:0}.domain-capability-head{grid-template-columns:62px 1fr}.domain-capability-code{width:62px;height:62px}.domain-capability-actions{grid-column:1/-1;justify-content:flex-end}.domain-capability-summary{grid-template-columns:1fr 1fr;height:auto}.domain-capability-summary span{min-height:55px;border-bottom:1px solid var(--ant-color-border)}.domain-capability-summary span:nth-child(2n){border-right:0}.capability-dashboard-grid,.capability-twin-grid{grid-template-columns:1fr}}
+.capability-dashboard-grid article span,.capability-twin-grid article span{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;line-height:1.55}
+@media(max-width:600px){.domain-showcase-filter{height:auto;flex-wrap:wrap}.domain-showcase-filter label,.domain-showcase-filter select{width:100%;min-width:0}.domain-showcase-filter>span{width:100%;text-align:right}.domain-capability-head{grid-template-columns:1fr;padding:16px}.domain-capability-code{width:auto;height:38px;justify-self:start;padding:0 12px}.domain-capability-actions{width:100%;flex-direction:column}.domain-capability-actions .button{width:100%;margin:0}.domain-capability-card>header{grid-template-columns:38px 1fr}.domain-capability-card>header>strong{grid-column:2}.capability-mini-tree>span:nth-of-type(n+2){display:none}.capability-menu-full{grid-template-columns:1fr}.capability-menu-full>section{display:none}.capability-package-modal>.modal-body>div{grid-template-columns:1fr}.domain-package-check{align-items:flex-start;flex-wrap:wrap}.capability-preview-modal>.modal-body{padding:12px}}
+
+/* 领域扩展仪表板管理:用领域下拉框替代分类树。 */
+.domain-dashboard-manager{display:flex;flex-direction:column;gap:12px}.dashboard-manager-panel{min-height:620px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container)}.dashboard-manager-toolbar{height:54px;border-bottom:1px solid var(--ant-color-border);display:grid;grid-template-columns:300px 1fr auto;align-items:center}.dashboard-manager-toolbar>label{height:100%;display:flex;align-items:center;gap:12px;padding:0 12px;border-right:1px solid var(--ant-color-border)}.dashboard-manager-toolbar>label span{font-weight:600;white-space:nowrap}.dashboard-manager-toolbar select{height:32px;min-width:190px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);padding:0 28px 0 8px}.dashboard-manager-toolbar>strong{padding-left:20px;font-size:var(--ant-font-size)}.dashboard-manager-actions{display:flex;align-items:center;gap:8px;padding-right:12px}.dashboard-manager-actions .button{margin:0;min-width:72px}.dashboard-manager-search{width:220px;height:32px;border:1px solid var(--ant-color-border);display:flex!important;align-items:center;gap:8px;padding:0 8px!important}.dashboard-manager-search input{width:100%;height:28px;border:0;outline:0}.dashboard-manager-grid{display:grid;grid-template-columns:repeat(2,minmax(280px,354px));gap:24px;padding:20px 24px;align-content:start}.dashboard-manager-grid article{height:124px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);background:var(--ant-color-bg-container);padding:16px 44px 12px 16px;position:relative}.dashboard-manager-grid h3{margin:0 0 12px;font-size:var(--ant-font-size);font-weight:600}.dashboard-manager-grid p{margin:0 0 8px;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.dashboard-card-more{position:absolute;right:8px;top:10px;width:30px;height:30px;border:0;background:transparent;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-heading-4)}.dashboard-card-more:hover{background:var(--ant-color-bg-layout);color:var(--ant-color-primary)}
+@media(max-width:1100px){.dashboard-manager-toolbar{height:auto;grid-template-columns:1fr}.dashboard-manager-toolbar>label{height:50px;border-right:0;border-bottom:1px solid var(--ant-color-border)}.dashboard-manager-toolbar>strong{height:44px;display:flex;align-items:center;padding-left:12px}.dashboard-manager-actions{padding:0 12px 12px}.dashboard-manager-grid{grid-template-columns:1fr 1fr}}@media(max-width:720px){.dashboard-manager-actions{flex-wrap:wrap}.dashboard-manager-search{width:100%}.dashboard-manager-grid{grid-template-columns:1fr;padding:12px}}
+
+/* 仪表板资源页视觉密度:宽屏四列,等宽卡片,不保留分类树的空白。 */
+.dashboard-manager-panel{min-height:calc(100vh - 154px);background:var(--ant-color-bg-layout);border-color:var(--ant-color-border)}.dashboard-manager-toolbar{height:56px;grid-template-columns:286px 1fr;background:var(--ant-color-bg-container)}.dashboard-manager-toolbar>label{padding:0 16px;gap:12px}.dashboard-manager-toolbar>label span{font-size:var(--ant-font-size);font-weight:600;color:var(--ant-color-text-base)}.dashboard-manager-toolbar select{height:32px;min-width:176px;border-color:var(--ant-color-border);border-radius:var(--ant-border-radius);color:var(--ant-color-text-secondary)}.dashboard-manager-actions{justify-self:end;gap:8px;padding-right:12px}.dashboard-manager-actions .button{height:32px;min-width:72px;padding:0 12px;border-radius:var(--ant-border-radius);font-weight:600}.dashboard-manager-search{width:210px;height:32px;background:var(--ant-color-bg-container);border-color:var(--ant-color-border);border-radius:var(--ant-border-radius);color:var(--ant-color-text-secondary)}.dashboard-manager-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:20px 20px;padding:20px 20px;align-items:start}.dashboard-manager-grid article{width:auto;height:124px;border-color:var(--ant-color-border);border-radius:var(--ant-border-radius);padding:16px 44px 12px 16px;box-shadow:none;transition:border-color .15s,box-shadow .15s}.dashboard-manager-grid article:hover{border-color:var(--ant-color-primary-hover);box-shadow:none}.dashboard-manager-grid h3{margin:0 0 12px;font-size:var(--ant-font-size);font-weight:600;color:var(--ant-color-text-base);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dashboard-manager-grid p{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.dashboard-card-more{right:8px;top:9px;transform:rotate(90deg)}.dashboard-card-more:hover{background:transparent;color:var(--ant-color-primary)}
+@media(max-width:1550px){.dashboard-manager-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}
+@media(max-width:1120px){.dashboard-manager-toolbar{height:auto;grid-template-columns:1fr}.dashboard-manager-toolbar>label{height:50px;border-right:0;border-bottom:1px solid var(--ant-color-border)}.dashboard-manager-actions{justify-self:stretch;padding:0 12px 12px}.dashboard-manager-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
+@media(max-width:720px){.dashboard-manager-actions{flex-wrap:wrap}.dashboard-manager-search{width:100%}.dashboard-manager-grid{grid-template-columns:1fr;padding:12px}}
import type { Metadata } from "next";
import "antd/dist/reset.css";
import "./globals.css";
+import "./site-workspace.css";
+import "./ui-system.css";
+import "./ui-layout-compat.css";
+import "../src/antd.less";
+import "../src/theme/overrides.css";
+import "../src/styles.less";
+import "./brand-layout.css";
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"}};
"use client";
+import { UiButton, UiInput, UiSelect, UiTable, UiTabbedRegion, UiActions, UiOverlay } from "./ui-system";
+import BrandSideMenu from "../src/components/menu";
+import BrandSearchInput from "../src/components/search-input";
+import BrandPagination from "../src/components/pagination";
+import { Input as UiAntInput, Card, Form, App as AntApp } from "antd";
+const UiTextArea = UiAntInput.TextArea;
+
import { useEffect, useMemo, useRef, useState, type PointerEvent as ReactPointerEvent } from "react";
import { Button as AntButton, Drawer, Dropdown, QRCode, Tag } from "antd";
+import { StationHierarchyWorkspace, SiteConfigurationContext, SitePageSelection, SiteMenuConfiguration } from "./site-workspace";
import { AimOutlined, ApartmentOutlined, BarsOutlined, BlockOutlined, CaretDownOutlined, CaretRightOutlined, ControlOutlined, CopyOutlined, DeleteOutlined, DownloadOutlined, EditOutlined, EllipsisOutlined, EyeOutlined, LinkOutlined, PlusOutlined, PlusSquareOutlined, RobotOutlined, SearchOutlined, SnippetsOutlined, UploadOutlined, ZoomInOutlined, ZoomOutOutlined } from "@ant-design/icons";
-type PageKey = "login" | "projects" | "siteEdit" | "basic" | "assets" | "pageConfig" | "functions" | "generate" | "result" | "preview" | "domains" | "domainDetail" | "styleLibrary" | "deviceTemplates" | "toolSettings" | "domainSettings" | "dashboard" | "report-dashboard" | "report-doc" | "basic-components" | "func-components" | "filters" | "datasource" | "monitor-dashboard" | "twin-model" | "ops-template" | "knowledge-template";
+type PageKey = "login" | "projects" | "siteEdit" | "basic" | "assets" | "opsflow" | "pageConfig" | "functions" | "generate" | "result" | "preview" | "domains" | "domainDetail" | "styleLibrary" | "deviceTemplates" | "toolSettings" | "domainSettings" | "dashboard" | "report-dashboard" | "report-doc" | "basic-components" | "func-components" | "filters" | "datasource" | "monitor-dashboard" | "twin-model" | "ops-template" | "knowledge-template";
type ModalKey = "" | "feedback" | "download" | "history" | "prompt" | "completePrompt";
const meta: Record<PageKey, { label: string; icon: string; subtitle: string }> = {
login:{label:"内部登录",icon:"◇",subtitle:"使用公司账号登录"},
- projects:{label:"站点列表",icon:"▦",subtitle:"创建、查找和管理站点生成项目"},
+ projects:{label:"站点管理",icon:"▦",subtitle:"先建立上下级站点,再按模板配置各站点"},
siteEdit:{label:"站点编辑",icon:"",subtitle:"维护已建站点的基本属性与参考资料"},
- basic:{label:"站点基本属性",icon:"▤",subtitle:"描述站点业务背景,信息越完整,生成结果越符合预期"},
- assets:{label:"站点资产设置",icon:"▧",subtitle:"通过快速配置或手动配置建立场站资产结构"},
- pageConfig:{label:"页面配置",icon:"",subtitle:""},
- functions:{label:"菜单设置",icon:"☷",subtitle:"选择 MU 标准页面,定义 Cube 扩展页面及菜单结构"},
+ basic:{label:"站点信息",icon:"▤",subtitle:"维护站点基本信息与参考资料"},
+ assets:{label:"资产设置",icon:"▧",subtitle:"在一个工作区完成资产结构、设备节点、属性与数据绑定"},
+ opsflow:{label:"运维流程配置",icon:"",subtitle:""},
+ pageConfig:{label:"页面配置",icon:"",subtitle:"选择站点所需的标准功能页面、领域仪表板及数字孪生场景"},
+ functions:{label:"菜单配置",icon:"☷",subtitle:"设置应用基本信息,配置第四步已选择页面的菜单"},
generate:{label:"生成确认",icon:"↻",subtitle:"确认生成范围,调用 Cube Skill 生成页面和模拟数据"},
result:{label:"站点结果",icon:"◫",subtitle:"集中查看、预览、校验并下载站点交付物"},
preview:{label:"Cube 页面预览",icon:"▣",subtitle:"检查单个 Cube 页面效果,并通过反馈快速重新生成"},
- domains:{label:"领域方案",icon:"◆",subtitle:"管理可复用的领域标准方案及其版本与配置内容"},
- domainDetail:{label:"方案详情",icon:"◇",subtitle:"查看领域资产、运维流程、Cube 页面、菜单及知识内容"},
+ domains:{label:"领域方案",icon:"◆",subtitle:"查看已沉淀的领域能力,并按需预览、下载或导入方案"},
+ domainDetail:{label:"方案详情",icon:"◇",subtitle:"集中查看领域方案包含的资产、页面、知识与菜单能力"},
deviceTemplates:{label:"设备模板",icon:"⬡",subtitle:"管理设备模板及采集点配置,供领域方案和站点复用"},
styleLibrary:{label:"页面样式",icon:"❖",subtitle:"管理可复用的 Cube 页面视觉样式与预览资源"},
toolSettings:{label:"内部工具配置",icon:"",subtitle:"配置 StationBuilder 使用的内部能力、AI 服务和默认模型"},
];
// ---- types ----
-type SiteSummary = { id:number; name:string; domain:string; status:string; device_count:number; mu_count:number; cube_count:number; gen_count:number; updated_at:string; created_at:string; parent_id?:number|null; };
+type SiteSummary = { id:number; name:string; domain:string; status:string; device_count:number; mu_count:number; cube_count:number; gen_count:number; updated_at:string; created_at:string; parent_id?:number|null; template_id?:number|null; template_name?:string; template_applied_at?:string|null; };
type Asset = { id?:number; type:string; count:number; rule:string; sort_order:number; };
type Cube = { id?:number; name:string; content:string; status?:string; gen_file_id?:number|null; gen_filename?:string; generated_at?:string; prompt?:string; sort_order:number; };
type MenuItem = { id?:number; parent_id:number|null; label:string; page_type:"mu"|"cube"|""; ref_id?:number|string; sort_order:number; };
type ReferenceFile = { id:number; filename:string; mime:string; size:number; created_at:string; };
type SiteDetail = {
id:number; name:string; domain:string; description:string; supplement:string; status:string;
+ template_id?:number|null; template_name?:string; template_applied_at?:string|null;
+ page_config?:{dashboard_ids:number[];twin_ids:string[];standard_pages_initialized?:boolean}|null;
style_id?: number | null;
parent_id?: number | null;
- app_name?: string; app_icon?: string; app_icon_filename?: string;
+ app_name?: string; app_icon?: string; app_icon_filename?: string; app_color_scheme?: string;
created_at:string; updated_at:string;
assets: Asset[]; cubes: Cube[]; mu: string[]; menu: MenuItem[]; files: ReferenceFile[];
};
type DomainAsset = { id?:number; device_template_id:number; device_name:string; count:number; rule:string; sort_order:number };
type DomainPage = { id:number; name:string; description:string; json_text:string; status:string; gen_filename?:string|null; prompt?:string|null; source?:string|null; sort_order:number };
type DomainStepKey = "basic" | "assets" | "functions" | "opsflow" | "knowledge" | "generate" | "expert";
-type SiteEditSection = "basic" | "assets" | "pageConfig" | "functions" | "generate";
-const SITE_EDIT_STEPS:SiteEditSection[] = ["basic","assets","pageConfig","functions","generate"];
+type SiteEditSection = "basic" | "assets" | "opsflow" | "pageConfig" | "functions";
+const SITE_EDIT_STEPS:SiteEditSection[] = ["basic","assets","opsflow","pageConfig","functions"];
type ManualAssetBinding = {source:string;name?:string;point:string;collect_type?:string;group?:string;device_name?:string;unit?:string;precision?:string;mapping?:string;description?:string};
type ManualAssetNode = { id:string; name:string; alias?:string; asset_type?:string; image_data?:string; node_type:"group"|"device"; template_name:string; description:string; properties:Array<{name:string;value:string;unit:string}>; bindings:ManualAssetBinding[]; children:ManualAssetNode[] };
type ManualAssetGroup = { id:number; site_id:number; name:string; category:string; template_name:string; structure:ManualAssetNode[]; created_at:string; updated_at:string };
// ---- helpers ----
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 <Tag className={"status "+tone}>{children}</Tag>}
function Button({children,secondary,onClick,disabled,title}:{children:React.ReactNode;secondary?:boolean;onClick?:()=>void;disabled?:boolean;title?:string}){return <AntButton title={title} disabled={disabled} onClick={onClick} type={secondary?"default":"primary"} className={"button "+(secondary?"secondary":"")}>{children}</AntButton>}
-function Panel({title,tools,children,className=""}:{title?:string;tools?:React.ReactNode;children:React.ReactNode;className?:string}){return <section className={"panel "+className}>{title&&<div className="panel-head"><strong>{title}</strong><div>{tools}</div></div>}<div className="panel-body">{children}</div></section>}
-function Field({label,required,hint,children}:{label:string;required?:boolean;hint?:string;children:React.ReactNode}){return <label className="field"><span>{required&&<i>*</i>}{label}</span>{children}{hint&&<small>{hint}</small>}</label>}
+Button.uiPrimaryAction = true;
+function Panel({title,tools,children,className=""}:{title?:string;tools?:React.ReactNode;children:React.ReactNode;className?:string}){return <Card className={"panel "+className} title={title?<strong>{title}</strong>:undefined} extra={title&&tools?<UiActions className="station-inline-actions">{tools}</UiActions>:undefined} classNames={{header:"panel-head",body:"panel-body"}}>{children}</Card>}
+function Field({label,required,hint,children}:{label:string;required?:boolean;hint?:string;children:React.ReactNode}){return <Form.Item className="field" label={label} required={required} extra={hint} layout="vertical">{children}</Form.Item>}
function domainTagColor(name:string){
- const map:Record<string,string>={"数据中心":"#009fe3","智慧水务":"#00b96b","燃气":"#ff990b","水务 · 自来水厂":"#42d067","新能源":"#7c3aed","其他":"#888888"};
+ const map:Record<string,string>={"数据中心":"blue","智慧水务":"cyan","燃气":"orange","水务 · 自来水厂":"green","新能源":"purple","其他":"default"};
if(map[name]) return map[name];
- const palette=["#009fe3","#00b96b","#ff990b","#42d067","#7c3aed","#f05b49","#008cd6","#e58c35","#6d5ee9","#25a85b"];
+ const palette=["blue","cyan","orange","green","purple","red"];
let hash=0; for(let i=0;i<name.length;i++){ hash=((hash<<5)-hash)+name.charCodeAt(i); hash|=0; }
return palette[Math.abs(hash)%palette.length];
}
-function DomainTag({name}:{name:string}){const color=domainTagColor(name); return <span className="ant-tag tag-new" style={{backgroundColor:`${color}32`,color,border:`1px solid ${color}`}}>{name}</span>;}
+function DomainTag({name}:{name:string}){return <Tag className="tag-new" color={domainTagColor(name)}>{name}</Tag>;}
async function fetchDb(path:string, opts?:RequestInit){
const res = await fetch(`/api/db${path}`, opts);
function countChars(v:string){ return Array.from(v||"").length; }
// ---- main ----
-export default function Home(){
+export default function Home(){ const { message } = AntApp.useApp();
const [page,setPage]=useState<PageKey>("projects");
const [projectNavOpen,setProjectNavOpen]=useState(true);
const [extensionNavOpen,setExtensionNavOpen]=useState(true);
const [resourceNavOpen,setResourceNavOpen]=useState(true);
+ const [navCollapsed,setNavCollapsed]=useState(false);
const [systemNavOpen,setSystemNavOpen]=useState(true);
- const [toast,setToast]=useState("");
+
const [modal,setModal]=useState<ModalKey>("");
const [promptData,setPromptData]=useState<{name:string;text:string}|null>(null);
const [model,setModel]=useState("Hy3");
const [pageStyles,setPageStyles]=useState<PageStyle[]>([]);
const [openDomainTemplateId,setOpenDomainTemplateId]=useState<number|"create"|null>(null);
const [domainFilter,setDomainFilter]=useState<number|null>(null);
+ const [deviceTemplateDomain,setDeviceTemplateDomain]=useState(()=>{ try{return new URLSearchParams(window.location.search).get("domain")||"";}catch{return "";} });
const [domainOptions,setDomainOptions]=useState<{id:number;name:string}[]>([]);
const [addingDomain,setAddingDomain]=useState(false);
const [newDomainName,setNewDomainName]=useState("");
const [view,setView]=useState<"table"|"card">("table");
const [siteEditSection,setSiteEditSection]=useState<SiteEditSection>("basic");
+ const assetSaveRef=useRef<null|(()=>Promise<void>)>(null);
+ const pageSaveRef=useRef<null|(()=>Promise<void>)>(null);
+ const menuSaveRef=useRef<null|(()=>Promise<void>)>(null);
+ const [configurationSaving,setConfigurationSaving]=useState(false);
const [assetConfigMode,setAssetConfigMode]=useState<"quick"|"manual">("quick");
const [pageConfigTab,setPageConfigTab]=useState<"ops"|"dashboard"|"twin">("ops");
const PAGE_CONFIG_LABEL:Record<string,string>={ops:"设备运维",dashboard:"仪表板",twin:"数字孪生场景"};
const [refining,setRefining]=useState(false);
const [refineLog,setRefineLog]=useState("");
- const notify=(m:string)=>{setToast(m);window.setTimeout(()=>setToast(""),2200);};
+ const notify=(m:string)=>{void message.info(m,2.2);};
const viewPrompt=async(cubeId:number|undefined,name:string,content?:string)=>{
const sid = site?.id ?? siteId;
};
const go=async(target:PageKey, sid?:number|"new")=>{
try{
+ if(page==="siteEdit"&&(target!==page||(typeof sid==="number"&&sid!==siteId))){if(siteEditSection==="assets"&&assetSaveRef.current)await assetSaveRef.current();if(siteEditSection==="functions"&&menuSaveRef.current)await menuSaveRef.current();}
if(target==="siteEdit")setSiteEditSection("basic");
// 显式要求新建站点:始终创建空白站点,避免复用/误建
if(sid==="new"){ await createSite(); setPage(target); window.history.replaceState({}, "", "?page="+target); return; }
curId = first.id;
}
// 详情类页面必须依附于已选站点:无站点时不自动建站,回站点列表并提示
- const needsSite = ["siteEdit","basic","assets","pageConfig","functions","generate","result","preview"].includes(target);
+ const needsSite = ["siteEdit","basic","assets","opsflow","pageConfig","functions","generate","result","preview"].includes(target);
if(needsSite && !curId){
setPage("projects");
window.history.replaceState({}, "", "?page=projects");
notify("操作失败: "+(e?.message||e));
}
};
+ const createSiteFromDomainTemplate=async(templateId:number)=>{
+ if(!(await confirmUnsaved())) return;
+ const created=await fetchDb(`/domain-templates/${templateId}/create-site`,{method:"POST",headers:{"Content-Type":"application/json"}}) as SiteDetail;
+ if(!created?.id) throw new Error("未返回有效的站点编号");
+ setSites(prev=>[created as any,...prev]);
+ setSite(created);setSiteId(created.id);setDirty(false);
+ setSiteEditSection("basic");setPage("siteEdit");
+ window.history.replaceState({},"",`?page=siteEdit&id=${created.id}`);
+ notify("已根据领域模板创建草稿站点,可继续调整配置");
+ fetchDb("/sites").then(data=>setSites(data as SiteSummary[])).catch(()=>{});
+ };
+ const saveHierarchySite=async(values:any,id?:number)=>{
+ const saved=await fetchDb(id?`/sites/${id}`:"/sites",{method:id?"PUT":"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(values)});
+ setSites(await fetchDb("/sites"));if(site?.id===id)setSite(saved);return saved;
+ };
+ const saveCurrentConfiguration=async()=>{
+ if(saving)throw new Error("基本信息正在保存,请稍后重试");
+ const section=page==="siteEdit"?siteEditSection:page;
+ if(section==="assets"&&assetSaveRef.current)await assetSaveRef.current();
+ if(section==="pageConfig"&&pageSaveRef.current)await pageSaveRef.current();
+ if(section==="functions"&&menuSaveRef.current)await menuSaveRef.current();
+ if(dirty&&site)await fetchDb(`/sites/${site.id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(site)});
+ setDirty(false);setSites(await fetchDb("/sites"));
+ };
+ const saveConfigurationDraft=async()=>{if(configurationSaving)return;setConfigurationSaving(true);try{await saveCurrentConfiguration();notify("草稿已保存");}catch(error:any){notify("保存失败:"+error.message);}finally{setConfigurationSaving(false);}};
+ const changeConfigurationStep=async(section:SiteEditSection)=>{
+ if(configurationSaving)return;setConfigurationSaving(true);try{await saveCurrentConfiguration();if(page==="siteEdit")setSiteEditSection(section);else await go(section,site?.id);}catch(error:any){notify("请先完成保存:"+error.message);}finally{setConfigurationSaving(false);}
+ };
+ const backToHierarchy=async()=>{if(configurationSaving)return;setConfigurationSaving(true);try{await saveCurrentConfiguration();await go("projects");}catch(error:any){notify("请先完成保存:"+error.message);}finally{setConfigurationSaving(false);}};
+ const nextConfigurationStep=async()=>{const section=page==="siteEdit"?siteEditSection:page as SiteEditSection;const index=SITE_EDIT_STEPS.indexOf(section);if(index<4)return changeConfigurationStep(SITE_EDIT_STEPS[index+1]);if(configurationSaving)return;setConfigurationSaving(true);try{await saveCurrentConfiguration();await go("generate",site?.id);}catch(error:any){notify("请先完成保存:"+error.message);}finally{setConfigurationSaving(false);}};
+ const applyTemplateToSite=async(templateId:number,replace:boolean)=>{
+ if(!site)return;const applied=await fetchDb(`/sites/${site.id}/apply-template`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_id:templateId,replace})});
+ setSite(applied);setDirty(false);setSites(await fetchDb("/sites"));if(page==="siteEdit")setSiteEditSection("assets");else await go("assets",site.id);notify("模板已应用,可在当前工作区调整资产");
+ };
+ const savePageConfiguration=async(config:any)=>{
+ if(!site)return;const saved=await fetchDb(`/sites/${site.id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({page_config:config})});setSite(saved);
+ };
+ const openDomainDeviceTemplates=(domainName:string)=>{
+ setDeviceTemplateDomain(domainName);
+ setPage("deviceTemplates");
+ window.history.replaceState({},"",`?page=deviceTemplates&domain=${encodeURIComponent(domainName)}`);
+ };
useEffect(()=>{
if(loading||routeAppliedRef.current)return;
const saveMu=async(selected:string[])=>{
if(!site) return;
- setSite({...site, mu:selected});
try{
- await fetchDb(`/sites/${site.id}/mu`, {method:"PUT", headers:{"Content-Type":"application/json"}, body:JSON.stringify({selected})});
- }catch(e:any){ notify("MU 选择保存失败: "+e.message); }
+ const saved=await fetchDb(`/sites/${site.id}/mu`, {method:"PUT", headers:{"Content-Type":"application/json"}, body:JSON.stringify({selected})}) as SiteDetail;
+ setSite(saved);
+ }catch(e:any){ notify("MU 选择保存失败: "+e.message); throw e; }
};
const addDomain=async(name:string)=>{
const children=(sites.filter(s=>s.parent_id===siteId));
return {ancestors,children,current};
},[sites,siteId]);
- const manualAssetActive = assetConfigMode==="manual" && (page==="assets" || (page==="siteEdit"&&siteEditSection==="assets"));
+ const manualAssetActive = (page==="assets" || (page==="siteEdit"&&siteEditSection==="assets"));
- return <div className="app-shell">
+ return <div className="app-shell page sb-brand-shell">
<header className="topbar wise-header">
- <button className="wise-menu-button" type="button" aria-label="展开或收起菜单"><img src="/t/stationbuilder/wise-header/menu.svg" alt=""/></button>
+ <UiButton className="wise-menu-button" type="button" aria-label="展开或收起菜单" aria-expanded={!navCollapsed} onClick={()=>setNavCollapsed(value=>!value)}><img src="/t/stationbuilder/wise-header/menu.svg" alt=""/></UiButton>
<a className="wise-logo-link" href="#" aria-label="WISE-IoT 首页" onClick={event=>event.preventDefault()}><img src="/t/stationbuilder/wise-header/wise-iot-logo.svg" alt="WISE-IoT"/></a>
- <button className="wise-product-switch" type="button" aria-label="切换产品">
+ <UiButton className="wise-product-switch" type="button" aria-label="切换产品">
<img src="/t/stationbuilder/wise-header/folder.svg" alt=""/>
<span>StationBuilder</span><strong>| 建站工具 v0.1</strong>
<img src="/t/stationbuilder/wise-header/caret-down.svg" alt=""/>
- </button>
+ </UiButton>
<nav className="wise-header-actions" aria-label="顶部工具栏">
- <button type="button" aria-label="搜索" title="搜索"><img src="/t/stationbuilder/wise-header/search.svg" alt=""/></button>
- <button type="button" aria-label="通知" title="通知"><img src="/t/stationbuilder/wise-header/bell.svg" alt=""/></button>
- <button type="button" className="wise-header-secondary" aria-label="主题设置" title="主题设置"><img src="/t/stationbuilder/wise-header/theme.svg" alt=""/></button>
- <button type="button" className="wise-header-secondary" aria-label="收藏" title="收藏"><img src="/t/stationbuilder/wise-header/favorite.svg" alt=""/></button>
- <button type="button" aria-label="系统设置" title="系统设置"><img src="/t/stationbuilder/wise-header/settings.svg" alt=""/></button>
- <button type="button" className="wise-avatar" aria-label="管理员账户" title="管理员账户" onClick={()=>go("login")}>管</button>
+ <UiButton type="button" aria-label="搜索" title="搜索"><img src="/t/stationbuilder/wise-header/search.svg" alt=""/></UiButton>
+ <UiButton type="button" aria-label="通知" title="通知"><img src="/t/stationbuilder/wise-header/bell.svg" alt=""/></UiButton>
+ <UiButton type="button" className="wise-header-secondary" aria-label="主题设置" title="主题设置"><img src="/t/stationbuilder/wise-header/theme.svg" alt=""/></UiButton>
+ <UiButton type="button" className="wise-header-secondary" aria-label="收藏" title="收藏"><img src="/t/stationbuilder/wise-header/favorite.svg" alt=""/></UiButton>
+ <UiButton type="button" aria-label="系统设置" title="系统设置"><img src="/t/stationbuilder/wise-header/settings.svg" alt=""/></UiButton>
+ <UiButton type="button" className="wise-avatar" aria-label="管理员账户" title="管理员账户" onClick={()=>go("login")}>管</UiButton>
<span className="wise-powered-divider" aria-hidden="true"/>
<img className="wise-powered-logo" src="/t/stationbuilder/wise-header/powered-by-wise-iot.svg" alt="Powered by WISE-IoT"/>
</nav>
</header>
- <aside className="company-side-nav">
- <nav className="side-menu" aria-label="StationBuilder 主菜单">
- <section className={domainArea?"menu-section current":"menu-section"}>
- <button className="menu-section-title" onClick={()=>{setDomainFilter(null);go("domains");}}><span>领域方案</span></button>
- </section>
- <section className={projectArea?"menu-section current":"menu-section"}>
- <button className="menu-section-title" onClick={()=>go("projects")}><span>站点管理</span></button>
- </section>
- <section className={resourceArea?"menu-section extension-menu-section current":"menu-section extension-menu-section"}>
- <button className="menu-section-title" aria-expanded={resourceNavOpen} onClick={()=>setResourceNavOpen(v=>!v)}><span>建站资源</span>{resourceNavOpen?<CaretDownOutlined/>:<CaretRightOutlined/>}</button>
- {resourceNavOpen&&<div className="menu-section-items">
- <button className={page==="deviceTemplates"?"active":""} onClick={()=>go("deviceTemplates")}><span>设备模板</span></button>
- <button className={page==="monitor-dashboard"?"active":""} onClick={()=>go("monitor-dashboard")}><span>监控仪表板</span></button>
- <button className={page==="twin-model"?"active":""} onClick={()=>go("twin-model")}><span>数字孪生模型</span></button>
- <button className={page==="ops-template"?"active":""} onClick={()=>go("ops-template")}><span>运维模板</span></button>
- <button className={page==="knowledge-template"?"active":""} onClick={()=>go("knowledge-template")}><span>知识模板</span></button>
- </div>}
- </section>
- <section className={systemArea?"menu-section current":"menu-section"}>
- <button className="menu-section-title" aria-expanded={systemNavOpen} onClick={()=>setSystemNavOpen(v=>!v)}><span>系统设定</span>{systemNavOpen?<CaretDownOutlined/>:<CaretRightOutlined/>}</button>
- {systemNavOpen&&<div className="menu-section-items">
- <button className={page==="toolSettings"?"active":""} onClick={()=>go("toolSettings")}><span>内部工具配置</span></button>
- <button className={page==="domainSettings"?"active":""} onClick={()=>go("domainSettings")}><span>领域配置</span></button>
- </div>}
- </section>
+ <div className="body">
+ <aside className="left-menu sb-brand-nav">
+ <nav aria-label="StationBuilder 主菜单">
+ <BrandSideMenu
+ collapsed={navCollapsed}
+ selectedKeys={[domainArea ? "domains" : projectArea ? "projects" : page]}
+ openKeys={navCollapsed ? undefined : [...(resourceNavOpen ? ["resources"] : []), ...(systemNavOpen ? ["system"] : [])]}
+ onOpenChange={keys=>{setResourceNavOpen(keys.includes("resources"));setSystemNavOpen(keys.includes("system"));}}
+ onClick={({key})=>{if(key==="domains")setDomainFilter(null);go(key as PageKey);}}
+ items={[
+ {key:"domains",label:"领域方案"},
+ {key:"projects",label:"站点管理"},
+ {key:"resources",label:"建站资源",children:[
+ {key:"deviceTemplates",label:"设备模板"},
+ {key:"monitor-dashboard",label:"监控仪表板"},
+ {key:"twin-model",label:"数字孪生模型"},
+ {key:"ops-template",label:"运维模板"},
+ {key:"knowledge-template",label:"知识模板"},
+ ]},
+ {key:"system",label:"系统设定",children:[
+ {key:"toolSettings",label:"内部工具配置"},
+ {key:"domainSettings",label:"领域配置"},
+ ]},
+ ]}
+ />
</nav>
- <a className="design-doc-link" href="https://aidemo.istation.cloud/t/stationbuilder/design-doc.html" target="_blank" rel="noopener">设计方案</a>
+ {!navCollapsed&&<a className="design-doc-link" href="https://aidemo.istation.cloud/t/stationbuilder/design-doc.html" target="_blank" rel="noopener">设计方案</a>}
</aside>
- <aside className="side-nav"><div className="module-switch"><button className={(["projects","basic","assets","pageConfig","functions","generate","result","preview"] as PageKey[]).includes(page)?"active":""} onClick={()=>go("projects")}><b>▦</b><span>StationBuilder<small>站点生成工具</small></span></button><button className={(["domains","domainDetail","deviceTemplates"] as PageKey[]).includes(page)?"active":""} onClick={()=>{setDomainFilter(null); go("domains");}}><b>◆</b><span>领域方案库<small>行业标准方案</small></span></button><button className={page==="styleLibrary"?"active":""} onClick={()=>go("styleLibrary")}><b>❖</b><span>页面样式库<small>页面视觉样式</small></span></button></div>{page==="styleLibrary"?<><small>样式库导航</small><button className={page==="styleLibrary"?"active":""} onClick={()=>go("styleLibrary")}><b>❖</b>全部页面样式</button></>:(["domains","domainDetail","deviceTemplates"] as PageKey[]).includes(page)?<><small>方案库导航</small><button className={page==="domains"?"active":""} onClick={()=>{setDomainFilter(null); go("domains");}}><b>▦</b>领域方案</button><button className={page==="deviceTemplates"?"active":""} onClick={()=>go("deviceTemplates")}><b>⬡</b>设备模版</button><small>领域分类</small>
+ <aside className="side-nav"><div className="module-switch"><UiButton className={(["projects","basic","assets","pageConfig","functions","generate","result","preview"] as PageKey[]).includes(page)?"active":""} onClick={()=>go("projects")}><b>▦</b><span>StationBuilder<small>站点生成工具</small></span></UiButton><UiButton className={(["domains","domainDetail","deviceTemplates"] as PageKey[]).includes(page)?"active":""} onClick={()=>{setDomainFilter(null); go("domains");}}><b>◆</b><span>领域方案库<small>行业标准方案</small></span></UiButton><UiButton className={page==="styleLibrary"?"active":""} onClick={()=>go("styleLibrary")}><b>❖</b><span>页面样式库<small>页面视觉样式</small></span></UiButton></div>{page==="styleLibrary"?<><small>样式库导航</small><UiButton className={page==="styleLibrary"?"active":""} onClick={()=>go("styleLibrary")}><b>❖</b>全部页面样式</UiButton></>:(["domains","domainDetail","deviceTemplates"] as PageKey[]).includes(page)?<><small>方案库导航</small><UiButton className={page==="domains"?"active":""} onClick={()=>{setDomainFilter(null); go("domains");}}><b>▦</b>领域方案</UiButton><UiButton className={page==="deviceTemplates"?"active":""} onClick={()=>go("deviceTemplates")}><b>⬡</b>设备模版</UiButton><small>领域分类</small>
{domainOptions.map((d:any)=>(
<div key={d.id} className={"domain-nav-row"+(page==="domains"&&domainFilter===d.id?" active":"")}>
- <button className={(page==="domains"&&domainFilter===d.id)?"active":""} onClick={()=>{setDomainFilter(d.id); go("domains");}}><b>◆</b><span className="domain-nav-name">{d.name}</span></button>
- {d.name!=="其他" && <button className="domain-nav-del" title="删除领域" onClick={async()=>{ if(!window.confirm(`确定删除领域「${d.name}」?其下的方案将移入「其他」领域。`)) return; try{ await fetchDb(`/domains/${d.id}`,{method:"DELETE"}); notify("已删除领域,相关方案已归入「其他」"); await handleDomainDeleted(d.id); }catch(e:any){ notify("删除失败:"+e.message); } }}>×</button>}
+ <UiButton className={(page==="domains"&&domainFilter===d.id)?"active":""} onClick={()=>{setDomainFilter(d.id); go("domains");}}><b>◆</b><span className="domain-nav-name">{d.name}</span></UiButton>
+ {d.name!=="其他" && <UiButton className="domain-nav-del" title="删除领域" onClick={async()=>{ if(!window.confirm(`确定删除领域「${d.name}」?其下的方案将移入「其他」领域。`)) return; try{ await fetchDb(`/domains/${d.id}`,{method:"DELETE"}); notify("已删除领域,相关方案已归入「其他」"); await handleDomainDeleted(d.id); }catch(e:any){ notify("删除失败:"+e.message); } }}>×</UiButton>}
</div>
))}
{addingDomain ? (
<div className="domain-nav-row add">
- <input className="domain-nav-input" autoFocus value={newDomainName} placeholder="领域名称" onChange={e=>setNewDomainName(e.target.value)} onKeyDown={async e=>{ if(e.key==="Enter"){ await doAddDomain(); } else if(e.key==="Escape"){ setAddingDomain(false); setNewDomainName(""); } }}/>
- <button className="domain-nav-ok" title="确认" onClick={doAddDomain}>✓</button>
- <button className="domain-nav-del" title="取消" onClick={()=>{setAddingDomain(false);setNewDomainName("");}}>×</button>
+ <UiInput className="domain-nav-input" autoFocus value={newDomainName} placeholder="领域名称" onChange={e=>setNewDomainName(e.target.value)} onKeyDown={async e=>{ if(e.key==="Enter"){ await doAddDomain(); } else if(e.key==="Escape"){ setAddingDomain(false); setNewDomainName(""); } }}/>
+ <UiButton className="domain-nav-ok" title="确认" onClick={doAddDomain}>✓</UiButton>
+ <UiButton className="domain-nav-del" title="取消" onClick={()=>{setAddingDomain(false);setNewDomainName("");}}>×</UiButton>
</div>
) : (
- <div className="domain-nav-add-cta"><button className="domain-nav-add" onClick={()=>{setAddingDomain(true);setNewDomainName("");}}><b>+</b>新增领域</button></div>
-)}</>:<><small>站点列表</small><button className={page==="projects"?"active":""} onClick={()=>go("projects")}><b>▦</b>站点列表</button><small>站点详情</small><select className="site-switcher" value={siteId ?? ""} onChange={e=>{const v=e.target.value; if(v){ const t=(page==="projects"||["domains","domainDetail","styleLibrary","deviceTemplates"].includes(page))?"basic":page; go(t, Number(v)); }}}><option value="">选择站点…</option>{sites.map(s=><option key={s.id} value={s.id}>{s.name||"未命名站点"}</option>)}</select><small>生成向导</small>{SITE_EDIT_STEPS.map((item,index)=><button key={item} className={page===item?"active":""} onClick={()=>go(item)}><em>{index+1}</em>{meta[item].label}</button>)}<small>方案交付</small><button className={page==="result"?"active":""} onClick={()=>go("result")}><b>◫</b>站点结果</button><button className={page==="preview"?"active":""} onClick={()=>go("preview")}><b>▣</b>页面预览</button></>}<div className="side-help">
+ <div className="domain-nav-add-cta"><UiButton className="domain-nav-add" onClick={()=>{setAddingDomain(true);setNewDomainName("");}}><b>+</b>新增领域</UiButton></div>
+)}</>:<><small>站点列表</small><UiButton className={page==="projects"?"active":""} onClick={()=>go("projects")}><b>▦</b>站点列表</UiButton><small>站点详情</small><UiSelect className="site-switcher" value={siteId ?? ""} onChange={e=>{const v=e.target.value; if(v){ const t=(page==="projects"||["domains","domainDetail","styleLibrary","deviceTemplates"].includes(page))?"basic":page; go(t, Number(v)); }}}><option value="">选择站点…</option>{sites.map(s=><option key={s.id} value={s.id}>{s.name||"未命名站点"}</option>)}</UiSelect><small>生成向导</small>{SITE_EDIT_STEPS.map((item,index)=><UiButton key={item} className={page===item?"active":""} onClick={()=>go(item)}><em>{index+1}</em>{meta[item].label}</UiButton>)}<small>方案交付</small><UiButton className={page==="result"?"active":""} onClick={()=>go("result")}><b>◫</b>站点结果</UiButton><UiButton className={page==="preview"?"active":""} onClick={()=>go("preview")}><b>▣</b>页面预览</UiButton></>}<div className="side-help">
<b>内部工具 · v0.3</b>
<span className="model-trigger" onClick={() => setModelOpen(o => !o)}>
Wordbuddy AI · {model}<span className="caret">▾</span>
<div className="model-popover-head"><span>Wordbuddy AI 模型</span><small>仅免费模型可选</small></div>
{MODELS.map((m) => { const free = m.rate === "0.00x"; return (
<label key={m.name} className={"model-row" + (free ? "" : " disabled")}>
- <input type="radio" name="sideModel" disabled={!free} checked={model === m.name} onChange={() => { setModel(m.name); setModelOpen(false); }} />
+ <UiInput type="radio" name="sideModel" disabled={!free} checked={model === m.name} onChange={() => { setModel(m.name); setModelOpen(false); }} />
<span className="model-name">{m.name}</span>
<span className="model-tag">{m.tag || (free ? "免费" : "")}</span>
<span className="model-rate">{m.rate}</span>
</>
)}
</div></aside>
- <main className="workspace"><div className="crumb">iStation StationBuilder / <b>{["domains","domainDetail","styleLibrary","deviceTemplates","toolSettings","domainSettings","monitor-dashboard","twin-model","ops-template","knowledge-template"].includes(page)?meta[page].label:page==="projects"?"站点列表":page==="siteEdit"?`站点管理 / 站点编辑 / ${siteEditMeta.label}`:`站点详情 / ${meta[page].label}`}</b></div><div className="page-heading"><div><h1>{page==="siteEdit"?siteEditMeta.label:meta[page].label}</h1><p>{page==="siteEdit"?siteEditMeta.subtitle:meta[page].subtitle}</p></div><div>{page==="projects"&&<Button onClick={()=>go("basic","new")}>+ 新建站点</Button>}{page==="siteEdit"&&<label className="site-edit-switcher"><span>当前站点</span><select value={siteId??""} onChange={async e=>{const id=Number(e.target.value);if(id!==siteId&&!(await confirmUnsaved())){e.target.value=String(siteId??'');return;}await loadSite(id);window.history.replaceState({},"",`?page=siteEdit&id=${id}`);}}>{sites.map(item=><option key={item.id} value={item.id}>{item.name||"未命名站点"}</option>)}</select></label>}{page==="domains"&&openDomainTemplateId==null&&<Button onClick={()=>setOpenDomainTemplateId("create")}>+ 新增领域方案</Button>}{page==="domains"&&openDomainTemplateId!=null&&<Button secondary onClick={()=>setOpenDomainTemplateId(null)}>← 返回方案列表</Button>}{domainFilter!=null&&page==="domains"&&openDomainTemplateId==null&&<Button secondary onClick={()=>{setDomainFilter(null);}}>清除领域筛选:{domainOptions.find(d=>d.id===domainFilter)?.name||""}</Button>}{page==="domainSettings"&&<Button onClick={()=>{setNewDomainName("");setAddingDomain(true);}}>+ 新增领域</Button>}</div></div>{page==="siteEdit"&&<SiteEditTabs section={siteEditSection} onChange={setSiteEditSection}/>} {SITE_EDIT_STEPS.includes(page as SiteEditSection)&&<Steps page={page} go={go}/>}<div className="content">
- {page==="projects"&&<Projects view={view} setView={setView} sites={sites} loading={loading} go={go} loadSite={loadSite} notify={notify} onDelete={deleteSites} onCreateSub={createSubSite}/>}
+ <main className="workspace main"><div className="crumb">iStation StationBuilder / <b>{["domains","domainDetail","styleLibrary","deviceTemplates","toolSettings","domainSettings","monitor-dashboard","twin-model","ops-template","knowledge-template"].includes(page)?meta[page].label:page==="projects"?"站点列表":page==="siteEdit"?`站点管理 / 站点编辑 / ${siteEditMeta.label}`:`站点详情 / ${meta[page].label}`}</b></div><div className="page-heading"><div><h1>{page==="siteEdit"?siteEditMeta.label:meta[page].label}</h1><p>{page==="siteEdit"?siteEditMeta.subtitle:meta[page].subtitle}</p></div><div>{page==="siteEdit"&&<label className="site-edit-switcher"><span>当前站点</span><UiSelect value={siteId??""} onChange={async e=>{const id=Number(e.target.value);if(id===siteId)return;try{await saveCurrentConfiguration();await loadSite(id);setSiteEditSection("basic");}catch(error:any){notify("切换失败:"+error.message);return;}window.history.replaceState({},"",`?page=siteEdit&id=${id}`);}}>{sites.map(item=><option key={item.id} value={item.id}>{item.name||"未命名站点"}</option>)}</UiSelect></label>}{page==="domains"&&openDomainTemplateId==null&&<Button onClick={()=>setOpenDomainTemplateId("create")}><UploadOutlined/> 导入领域方案</Button>}{page==="domains"&&openDomainTemplateId!=null&&<Button secondary onClick={()=>setOpenDomainTemplateId(null)}>← 返回方案列表</Button>}{domainFilter!=null&&page==="domains"&&openDomainTemplateId==null&&<Button secondary onClick={()=>{setDomainFilter(null);}}>清除领域筛选:{domainOptions.find(d=>d.id===domainFilter)?.name||""}</Button>}{page==="domainSettings"&&<Button onClick={()=>{setNewDomainName("");setAddingDomain(true);}}>+ 新增领域</Button>}</div></div>{page==="siteEdit"&&<SiteEditTabs section={siteEditSection} onChange={section=>void changeConfigurationStep(section)}/>} {SITE_EDIT_STEPS.includes(page as SiteEditSection)&&<Steps page={page} go={go}/>}<div className="content">
+ {page==="projects"&&<StationHierarchyWorkspace sites={sites} domains={domainOptions} loading={loading} onSave={saveHierarchySite} onConfigure={id=>go("siteEdit",id)} onDelete={deleteSites} notify={notify}/>}
{page==="dashboard"&&<Dashboard/>}
{page==="report-dashboard"&&<ImagePage src="/t/stationbuilder/ext-pages/report-dashboard.jpg"/>}
{page==="report-doc"&&<ImagePage src="/t/stationbuilder/ext-pages/report-doc.jpg"/>}
{page==="filters"&&<ImagePage src="/t/stationbuilder/ext-pages/filters.jpg"/>}
{page==="datasource"&&<ImagePage src="/t/stationbuilder/ext-pages/datasource.jpg"/>}
{(page==="domains"||page==="domainDetail")&&(openDomainTemplateId!=null
- ? <DomainDetail id={openDomainTemplateId==="create"?null:openDomainTemplateId} go={go} notify={notify} onBack={()=>setOpenDomainTemplateId(null)} onCreated={setOpenDomainTemplateId} viewCompletePrompt={viewDtCompletePrompt} viewCompleteAssetsPrompt={viewDtCompleteAssetsPrompt} viewCompleteCubesPrompt={viewDtCompleteCubesPrompt} pageStyles={pageStyles}/>
+ ? <DomainDetail id={openDomainTemplateId==="create"?null:openDomainTemplateId} go={go} notify={notify} onBack={()=>setOpenDomainTemplateId(null)} onCreated={setOpenDomainTemplateId} viewCompletePrompt={viewDtCompletePrompt} viewCompleteAssetsPrompt={viewDtCompleteAssetsPrompt} viewCompleteCubesPrompt={viewDtCompleteCubesPrompt} pageStyles={pageStyles} onEditAssets={openDomainDeviceTemplates} onCreateSite={createSiteFromDomainTemplate}/>
: <CompanyDomainLibrary go={go} notify={notify} onOpen={(id:number)=>setOpenDomainTemplateId(id)} domainFilter={domainFilter} domainOptions={domainOptions}/>)}
{page==="styleLibrary"&&<CompanyPageStyleLibrary pageStyles={pageStyles} loadPageStyles={loadPageStyles} notify={notify}/>}
- {page==="deviceTemplates"&&<CompanyDeviceTemplates notify={notify} onViewCpPrompt={viewDeviceCpCompletePrompt} go={go} siteId={siteId}/>}
+ {page==="deviceTemplates"&&<CompanyDeviceTemplates notify={notify} onViewCpPrompt={viewDeviceCpCompletePrompt} go={go} siteId={siteId} domainOptions={domainOptions} initialDomain={deviceTemplateDomain}/>}
{page==="monitor-dashboard"&&<ResourcePlaceholder title="监控仪表板"/>}
{page==="twin-model"&&<ResourcePlaceholder title="数字孪生模型"/>}
{page==="ops-template"&&<ResourcePlaceholder title="运维模板"/>}
{page==="knowledge-template"&&<ResourcePlaceholder title="知识模板"/>}
{page==="toolSettings"&&<InternalToolSettings model={model} onModelChange={setModel} notify={notify}/>}
{page==="domainSettings"&&<DomainSettings domains={domainOptions} notify={notify} onDeleted={handleDomainDeleted}/>}
- {(page==="basic"||(page==="siteEdit"&&siteEditSection==="basic"))&&<Basic site={site} domains={domains} onChange={saveSiteBasic} onAddDomain={addDomain} onUpload={uploadFile} saving={saving} notify={notify} onViewCompletePrompt={viewCompletePrompt} go={go} siteId={siteId} siteHierarchy={siteHierarchy}/>}
- {page==="assets"&&<Assets site={site} onChange={saveAssets} saving={saving} notify={notify} onViewCompletePrompt={viewCompleteAssetsPrompt} mode={assetConfigMode} onModeChange={setAssetConfigMode} go={go}/>}
- {page==="siteEdit"&&siteEditSection==="assets"&&<Assets site={site} onChange={saveAssets} saving={saving} notify={notify} onViewCompletePrompt={viewCompleteAssetsPrompt} mode={assetConfigMode} onModeChange={setAssetConfigMode} go={go}/>}
- {(page==="pageConfig"||(page==="siteEdit"&&siteEditSection==="pageConfig"))&&<div className="page-config-shell">
- <div className="asset-config-tabs" role="tablist" aria-label="页面配置">
- <button role="tab" aria-selected={pageConfigTab==="ops"} className={pageConfigTab==="ops"?"active":""} onClick={()=>setPageConfigTab("ops")}><span>设备运维</span><small>设备运行维护相关配置</small></button>
- <button role="tab" aria-selected={pageConfigTab==="dashboard"} className={pageConfigTab==="dashboard"?"active":""} onClick={()=>setPageConfigTab("dashboard")}><span>仪表板</span><small>可视化仪表板配置</small></button>
- <button role="tab" aria-selected={pageConfigTab==="twin"} className={pageConfigTab==="twin"?"active":""} onClick={()=>setPageConfigTab("twin")}><span>数字孪生场景</span><small>数字孪生场景配置</small></button>
- </div>
- {pageConfigTab==="ops"
- ? <Panel title={PAGE_CONFIG_LABEL.ops}>{/* MU 标准页面表格,移自功能与菜单,默认全选中 */}
- {(()=>{
- const opLeaves = buildMuLeaves(muTree);
- const opGrouped = (()=>{ const map=new Map<string,{parentMenu:string,items:typeof opLeaves}>(); for(const l of opLeaves){ if(!map.has(l.parentMenu)) map.set(l.parentMenu,{parentMenu:l.parentMenu,items:[]}); map.get(l.parentMenu)!.items.push(l); } return Array.from(map.values()); })();
- const opAllKeys = opLeaves.map(l=>l.key);
- const opSelected = site?.mu || [];
- const opEff = opSelected.length ? opSelected : opAllKeys;
- const opToggle=(key:string)=>{ const base=opSelected.length?opSelected:opAllKeys; saveMu(base.includes(key)?base.filter(k=>k!==key):[...base,key]); };
- return <div className="mu-tree">{opGrouped.map(g=><div key={g.parentMenu} className="mu-group"><b>{g.parentMenu}</b><div className="mu-items">{g.items.map(item=><label key={item.key} className={item.disabled?"disabled":""}><input type="checkbox" disabled={item.disabled} checked={opEff.includes(item.key)} onChange={()=>opToggle(item.key)}/><span>{item.label}</span></label>)}</div></div>)}</div>;
- })()}
- </Panel>
- : pageConfigTab==="dashboard"
- ? <Panel title={PAGE_CONFIG_LABEL.dashboard}><img src="/t/stationbuilder/dashboard-panel.png" alt="仪表板配置" style={{maxWidth:"100%",display:"block"}}/></Panel>
- : <Panel title={PAGE_CONFIG_LABEL.twin}><img src="/t/stationbuilder/twin-panel.png" alt="数字孪生场景配置" style={{maxWidth:"100%",display:"block"}}/></Panel>}
- </div>}
- {page==="functions"&&<Functions site={site} muTree={muTree} onMuChange={saveMu} onSiteChange={setSite} pageStyles={pageStyles} notify={notify} onViewCompletePrompt={viewCompleteCubesPrompt}/>}
- {page==="siteEdit"&&siteEditSection==="functions"&&<Functions site={site} muTree={muTree} onMuChange={saveMu} onSiteChange={setSite} pageStyles={pageStyles} notify={notify} onViewCompletePrompt={viewCompleteCubesPrompt}/>}
+ {page==="siteEdit"&&site&&<SiteConfigurationContext site={site} onBack={()=>void backToHierarchy()}/>}
+ {(page==="basic"||(page==="siteEdit"&&siteEditSection==="basic"))&&<div className="station-overview-layout"><Basic site={site} domains={domains} onChange={saveSiteBasic} onAddDomain={addDomain} onUpload={uploadFile} saving={saving} notify={notify} onViewCompletePrompt={viewCompletePrompt} go={go} siteId={siteId} siteHierarchy={siteHierarchy}/></div>}
+ {(page==="assets"||(page==="siteEdit"&&siteEditSection==="assets"))&&site&&<div className="station-unified-assets"><ManualAssetConfiguration key={site.id} site={site} notify={notify} onSiteChange={setSite} registerSave={save=>{assetSaveRef.current=save;}}/></div>}
+ {(page==="opsflow"||(page==="siteEdit"&&siteEditSection==="opsflow"))&&<div className="station-opsflow-blank" aria-label="运维流程配置预留区域"/>}
+ {(page==="pageConfig"||(page==="siteEdit"&&siteEditSection==="pageConfig"))&&site&&<SitePageSelection key={site.id} site={site} leaves={buildMuLeaves(muTree)} onMuChange={saveMu} onPageConfigChange={savePageConfiguration} onSiteChange={setSite} renderDomainDashboard={(domainPage,large)=><DomainPageThumb jsonText={domainPage.json_text} fit fixedHeight={large?Math.min(680,window.innerHeight-180):145}/>} renderDashboard={(cube,large)=><DashboardThumb siteId={site.id} cube={cube} fixedHeight={large?Math.min(680,window.innerHeight-180):145}/>} notify={notify} registerSave={save=>{pageSaveRef.current=save;}}/>}
+ {(page==="functions"||(page==="siteEdit"&&siteEditSection==="functions"))&&site&&<SiteMenuConfiguration key={site.id} site={site} leaves={buildMuLeaves(muTree)} onSiteChange={setSite} notify={notify} registerSave={save=>{menuSaveRef.current=save;}}/>}
{page==="generate"&&<Generate site={site} onSiteChange={setSite} notify={notify} viewPrompt={viewPrompt} viewSitePrompt={viewSitePrompt}/>}
- {page==="siteEdit"&&siteEditSection==="generate"&&<Generate site={site} onSiteChange={setSite} notify={notify} viewPrompt={viewPrompt} viewSitePrompt={viewSitePrompt}/>}
{page==="result"&&<Result site={site} go={go} setModal={setModal} viewPrompt={viewPrompt} notify={notify} openFeedback={openFeedback} viewSitePrompt={viewSitePrompt}/>}
{page==="preview"&&<Preview site={site} tab={previewTab} setTab={setPreviewTab} setModal={setModal} notify={notify}/>}
- </div>{SITE_EDIT_STEPS.slice(0,-1).includes(page as SiteEditSection)&&<div className="sticky-actions"><Button secondary onClick={()=>{const i=SITE_EDIT_STEPS.indexOf(page as SiteEditSection);go(i>0?SITE_EDIT_STEPS[i-1]:"projects")}}>上一步</Button><div style={{display:"flex",gap:"12px"}}><Button secondary onClick={()=>notify("草稿已保存")}>保存草稿</Button><Button onClick={()=>{const i=SITE_EDIT_STEPS.indexOf(page as SiteEditSection);go(SITE_EDIT_STEPS[i+1])}}>下一步</Button></div></div>}{page==="siteEdit"&&<div className="sticky-actions"><Button secondary onClick={()=>{const i=SITE_EDIT_STEPS.indexOf(siteEditSection);if(i>0)setSiteEditSection(SITE_EDIT_STEPS[i-1]);else go("projects")}}>上一步</Button><div style={{display:"flex",gap:"12px"}}><Button secondary onClick={()=>notify("草稿已保存")}>保存草稿</Button>{siteEditSection!=="generate"&&<Button onClick={()=>{const i=SITE_EDIT_STEPS.indexOf(siteEditSection);setSiteEditSection(SITE_EDIT_STEPS[i+1])}}>下一步</Button>}</div></div>}</main>
- {addingDomain&&page==="domainSettings"&&<div className="dlg-backdrop" onMouseDown={()=>{setAddingDomain(false);setNewDomainName("");}}><div className="dlg-box domain-create-dialog" onMouseDown={e=>e.stopPropagation()}><h2>新增领域</h2><p>创建后可在领域方案和站点基本信息中直接选择。</p><input autoFocus value={newDomainName} maxLength={6} placeholder="请输入领域名称(最多 6 个字)" onChange={e=>setNewDomainName(e.target.value)} onKeyDown={async e=>{if(e.key==="Enter")await doAddDomain();if(e.key==="Escape"){setAddingDomain(false);setNewDomainName("");}}}/><div className="dlg-actions"><Button secondary onClick={()=>{setAddingDomain(false);setNewDomainName("");}}>取消</Button><Button disabled={!newDomainName.trim()} onClick={doAddDomain}>创建</Button></div></div></div>}
- {toast&&<div className="toast"><b>✓</b>{toast}</div>}{modal&&<Modal type={modal} close={()=>setModal("")} notify={notify} promptData={promptData} feedbackCube={feedbackCube} refineCube={refineCube} refining={refining} refineLog={refineLog}/>}
- {!aiOpen&&!manualAssetActive&&<button ref={fabRef} className="ai-fab" title="AI 助手 · 拖动可移动位置,双击复位" aria-label="AI 助手"
- style={fabPos?{left:fabPos.x,top:fabPos.y,right:"auto",bottom:"auto"}:undefined}
+ </div>{(page==="siteEdit"||SITE_EDIT_STEPS.includes(page as SiteEditSection))&&<div className="sticky-actions station-config-footer"><Button secondary disabled={configurationSaving||saving} onClick={()=>{const section=page==="siteEdit"?siteEditSection:page as SiteEditSection;const i=SITE_EDIT_STEPS.indexOf(section);if(i>0)void changeConfigurationStep(SITE_EDIT_STEPS[i-1]);else void backToHierarchy();}}>上一步</Button><div style={{display:"flex",gap:12}}><Button secondary disabled={configurationSaving||saving} onClick={()=>void saveConfigurationDraft()}>保存草稿</Button><Button disabled={configurationSaving||saving} onClick={()=>void nextConfigurationStep()}>{(page==="siteEdit"?siteEditSection:page)==="functions"?"保存并进入生成确认":"下一步"}</Button></div></div>}{page==="generate"&&<div className="sticky-actions"><Button secondary onClick={()=>go("siteEdit",site?.id)}>返回站点配置</Button></div>}</main></div>
+ {addingDomain&&page==="domainSettings"&&<UiOverlay className="dlg-backdrop" onMouseDown={()=>{setAddingDomain(false);setNewDomainName("");}}><div className="dlg-box domain-create-dialog" onMouseDown={e=>e.stopPropagation()}><h2>新增领域</h2><p>创建后可在领域方案和站点基本信息中直接选择。</p><UiInput autoFocus value={newDomainName} maxLength={6} placeholder="请输入领域名称(最多 6 个字)" onChange={e=>setNewDomainName(e.target.value)} onKeyDown={async e=>{if(e.key==="Enter")await doAddDomain();if(e.key==="Escape"){setAddingDomain(false);setNewDomainName("");}}}/><UiActions className="dlg-actions"><Button secondary onClick={()=>{setAddingDomain(false);setNewDomainName("");}}>取消</Button><Button disabled={!newDomainName.trim()} onClick={doAddDomain}>创建</Button></UiActions></div></UiOverlay>}
+ {modal&&<Modal type={modal} close={()=>setModal("")} notify={notify} promptData={promptData} feedbackCube={feedbackCube} refineCube={refineCube} refining={refining} refineLog={refineLog}/>}
+ {!aiOpen&&!manualAssetActive&&!domainArea&&<UiButton ref={fabRef} className="ai-fab" title="AI 助手 · 拖动可移动位置,双击复位" aria-label="AI 助手"
+ style={fabPos?{left:fabPos.x,top:page==="siteEdit"?Math.min(fabPos.y,window.innerHeight-132):fabPos.y,right:"auto",bottom:"auto"}:page==="siteEdit"?{bottom:76}:undefined}
onPointerDown={onFabPointerDown} onPointerMove={onFabPointerMove} onPointerUp={onFabPointerUp} onDoubleClick={resetFab}>
<img src="/t/stationbuilder/ai-icon.png" alt="AI 助手" className="ai-fab-icon" draggable={false} />
- </button>}
- {aiOpen&&!manualAssetActive&&<div className="ai-dialog">
- <header><span className="ai-dialog-logo">✦</span><div className="ai-dialog-title"><b>StationBuilder AI</b><small>模型 · {model}</small></div><button className="ai-close" onClick={()=>setAiOpen(false)}>×</button></header>
+ </UiButton>}
+ {aiOpen&&!manualAssetActive&&!domainArea&&<div className="ai-dialog">
+ <header><span className="ai-dialog-logo">✦</span><div className="ai-dialog-title"><b>StationBuilder AI</b><small>模型 · {model}</small></div><UiButton className="ai-close" onClick={()=>setAiOpen(false)}>×</UiButton></header>
<div className="ai-messages">
{aiMessages.length===0&&<div className="ai-welcome">
<div className="ai-welcome-intro"><span>✦</span><div><b>我可以帮你建设场站</b><p>进入对应页面使用 AI 功能,或直接在下方提问。</p></div></div>
<div ref={aiEndRef}/>
</div>
<footer>
- <textarea className="ai-input" placeholder="输入消息,Enter 发送,Shift+Enter 换行" value={aiDraft} onChange={e=>setAiDraft(e.target.value)} onKeyDown={e=>{ if(e.key==="Enter"&&!e.shiftKey){e.preventDefault();sendAi()} }}/>
- <button className="ai-send" disabled={aiBusy||!aiDraft.trim()} onClick={sendAi}>{aiBusy?"生成中":"发送"}</button>
+ <UiTextArea className="ai-input" placeholder="输入消息,Enter 发送,Shift+Enter 换行" value={aiDraft} onChange={e=>setAiDraft(e.target.value)} onKeyDown={e=>{ if(e.key==="Enter"&&!e.shiftKey){e.preventDefault();sendAi()} }}/>
+ <UiButton className="ai-send" disabled={aiBusy||!aiDraft.trim()} onClick={sendAi}>{aiBusy?"生成中":"发送"}</UiButton>
</footer>
</div>}
</div>
}
-function Login({onLogin}:{onLogin:()=>void}){return <div className="login-page"><div className="login-top"><div className="brand-mark">⬡</div><b>iStation</b><span>StationBuilder</span></div><div className="login-card"><div className="login-logo">⬡</div><h1>iStation StationBuilder</h1><p>面向 iStation 团队的 AI 站点与领域方案生成工具</p><button className="sso-button" onClick={onLogin}><span className="avatar">W</span><b>使用公司账号登录</b><i>→</i></button><small>仅限 iStation 团队内部成员访问</small></div><footer>© 2026 Advantech · iStation Team · v0.3</footer></div>}
-function Steps({page,go}:{page:PageKey;go:(p:PageKey)=>void}){const current=SITE_EDIT_STEPS.indexOf(page as SiteEditSection);return <div className="steps">{SITE_EDIT_STEPS.map((item,index)=><button key={item} onClick={()=>index<=current&&go(item)} className={index===current?"active":index<current?"done":""}><i>{index<current?"✓":index+1}</i><span><small>STEP {index+1}</small>{meta[item].label}</span>{index<SITE_EDIT_STEPS.length-1&&<b>›</b>}</button>)}</div>}
+function Login({onLogin}:{onLogin:()=>void}){return <div className="login-page"><div className="login-top"><div className="brand-mark">⬡</div><b>iStation</b><span>StationBuilder</span></div><div className="login-card"><div className="login-logo">⬡</div><h1>iStation StationBuilder</h1><p>面向 iStation 团队的 AI 站点与领域方案生成工具</p><UiButton className="sso-button" onClick={onLogin}><span className="avatar">W</span><b>使用公司账号登录</b><i>→</i></UiButton><small>仅限 iStation 团队内部成员访问</small></div><footer>© 2026 Advantech · iStation Team · v0.3</footer></div>}
+function Steps({page,go}:{page:PageKey;go:(p:PageKey)=>void}){const current=SITE_EDIT_STEPS.indexOf(page as SiteEditSection);return <div className="steps">{SITE_EDIT_STEPS.map((item,index)=><UiButton key={item} onClick={()=>index<=current&&go(item)} className={index===current?"active":index<current?"done":""}><i>{index<current?"✓":index+1}</i><span><small>STEP {index+1}</small>{meta[item].label}</span>{index<SITE_EDIT_STEPS.length-1&&<b>›</b>}</UiButton>)}</div>}
function SiteEditTabs({section,onChange}:{section:SiteEditSection;onChange:(section:SiteEditSection)=>void}){
- return <nav className="site-edit-tabs" aria-label="站点编辑功能">{SITE_EDIT_STEPS.map((item,index)=><button key={item} className={section===item?"active":""} aria-current={section===item?"page":undefined} onClick={()=>onChange(item)}><i>{index+1}</i><span>{meta[item].label}</span></button>)}</nav>;
+ return <nav className="site-edit-tabs" aria-label="站点编辑功能">{SITE_EDIT_STEPS.map((item,index)=><UiButton key={item} className={section===item?"active":""} aria-current={section===item?"page":undefined} onClick={()=>onChange(item)}><i>{index+1}</i><span>{meta[item].label}</span></UiButton>)}</nav>;
}
function Projects({view,setView,sites,loading,go,loadSite,notify,onDelete,onCreateSub}:{view:"table"|"card";setView:(v:"table"|"card")=>void;sites:SiteSummary[];loading:boolean;go:(p:PageKey, sid?:number|"new")=>void;loadSite:(id:number)=>Promise<void>;notify:(m:string)=>void;onDelete:(ids:number[])=>Promise<void>;onCreateSub:(parentId:number)=>Promise<void>}){
const isCollapsed=collapsed.has(row.id);
out.push(
<tr key={row.id} onClick={()=>openProject(row)}>
- <td onClick={e=>e.stopPropagation()}><input type="checkbox" checked={selected.has(row.id)} onChange={()=>toggle(row.id)}/></td>
+ <td onClick={e=>e.stopPropagation()}><UiInput type="checkbox" checked={selected.has(row.id)} onChange={()=>toggle(row.id)}/></td>
<td>
{hasKids&&<AntButton type="text" onClick={e=>{e.stopPropagation();toggleCollapse(row.id);}} aria-label={isCollapsed?"展开":"收起"} icon={isCollapsed?<CaretRightOutlined/>:<CaretDownOutlined/>}/>}
- <button className="link company-record-link" style={{paddingLeft: depth*22}} onClick={e=>{e.stopPropagation();openProject(row);}}><b>{row.name||"未命名站点"}</b></button>
+ <UiButton className="link company-record-link" style={{paddingLeft: depth*22}} onClick={e=>{e.stopPropagation();openProject(row);}}><b>{row.name||"未命名站点"}</b></UiButton>
</td>
<td>{row.domain||"—"}</td>
<td>{row.device_count}</td>
};
if(loading) return <Panel><div className="empty-state">加载中…</div></Panel>;
return <>{confirmEl}
- <div className="company-status-tabs" role="tablist" aria-label="站点状态筛选">
+ <UiTabbedRegion navigation={<div className="company-status-tabs" role="tablist" aria-label="站点状态筛选">
{[
{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=><button role="tab" aria-selected={statusFilter===item.key} className={statusFilter===item.key?"active":""} key={item.key||"all"} onClick={()=>setStatusFilter(item.key)}><span>{item.label}</span><b>{item.count}</b></button>)}
- </div>
+ ].map(item=><UiButton role="tab" aria-selected={statusFilter===item.key} className={statusFilter===item.key?"active":""} key={item.key||"all"} onClick={()=>setStatusFilter(item.key)}><span>{item.label}</span><b>{item.count}</b></UiButton>)}
+ </div>} content={<>
<Panel className="company-list-panel">
<div className="filterbar company-filterbar">
- <label className="search-field"><span aria-hidden>⌕</span><input value={query} onChange={e=>setQuery(e.target.value)} placeholder="搜索站点名称"/></label>
- <select value={domain} onChange={e=>setDomain(e.target.value)}><option value="">全部领域</option>{domains.map(d=><option key={d} value={d}>{d}</option>)}</select>
- <div className="filter-actions"><Button onClick={()=>notify(`已显示 ${filtered.length} 个站点`)}>查询</Button><Button secondary onClick={()=>{setQuery("");setDomain("");setStatusFilter("");}}>重置</Button>{selected.size>0&&<Button secondary onClick={batchDelete}>删除所选 ({selected.size})</Button>}</div>
+ <BrandSearchInput value={query} onChange={setQuery} placeholder="搜索站点名称"/>
+ <UiSelect value={domain} onChange={e=>setDomain(e.target.value)}><option value="">全部领域</option>{domains.map(d=><option key={d} value={d}>{d}</option>)}</UiSelect>
+ <UiActions className="filter-actions"><Button onClick={()=>notify(`已显示 ${filtered.length} 个站点`)}>查询</Button><Button secondary onClick={()=>{setQuery("");setDomain("");setStatusFilter("");}}>重置</Button>{selected.size>0&&<Button secondary onClick={batchDelete}>删除所选 ({selected.size})</Button>}</UiActions>
</div>
- <table className="data-table company-table resizable-cols"><colgroup>{colWidths.map((w,i)=><col key={i} style={w!=null?{width:w}:undefined}/>)}</colgroup><thead><tr><th><input type="checkbox" checked={allSelected} onChange={toggleAll}/></th>{["站点名称","所属领域","设备数量","MU 页面","Cube 页面","状态","最近修改"].map((label,i)=><th key={label} className="col-h">{label}<span className="col-resizer" onMouseDown={startResize(i+1)}/></th>)}<th>操作</th></tr></thead><tbody>{buildRows(0,0)}</tbody></table>
+ <UiTable className="data-table company-table resizable-cols"><colgroup>{colWidths.map((w,i)=><col key={i} style={w!=null?{width:w}:undefined}/>)}</colgroup><thead><tr><th><UiInput type="checkbox" checked={allSelected} onChange={toggleAll}/></th>{["站点名称","所属领域","设备数量","MU 页面","Cube 页面","状态","最近修改"].map((label,i)=><th key={label} className="col-h">{label}<span className="col-resizer" onMouseDown={startResize(i+1)}/></th>)}<th>操作</th></tr></thead><tbody>{buildRows(0,0)}</tbody></UiTable>
{visibleIds.size===0&&<div className="empty-state">没有符合条件的站点</div>}
- <div className="pagination"><span>{visibleIds.size} / 总数</span><button disabled>‹</button><button className="active">1</button><button disabled>›</button><select><option>10 / 页</option></select></div>
- </Panel>
+ <div className="pagination"><span>{visibleIds.size} / 总数</span><UiButton disabled>‹</UiButton><UiButton className="active">1</UiButton><UiButton disabled>›</UiButton><UiSelect><option>10 / 页</option></UiSelect></div>
+ </Panel></>}/>
</>
}
if(!site) return <Panel><div className="empty-state">加载中…</div></Panel>;
const nameTooLong = countChars(site.name)>20;
const domainTooLong = newDomain.length>6;
- const renderSiteBranch=(idx:number):JSX.Element|null=>{
+ const renderSiteBranch=(idx:number):React.ReactElement|null=>{
if(!siteHierarchy||idx>=siteHierarchy.ancestors.length) return null;
const a=siteHierarchy.ancestors[idx];
const isLast=idx===siteHierarchy.ancestors.length-1;
);
};
return <><div className="company-form-page"><aside className="form-section-nav"><i className="form-section-nav-dot"/><span>站点信息</span><a href="#site-basic">基本信息</a><a href="#site-description">情况描述</a>{siteHierarchy&&<><i className="form-section-nav-dot"/><span className="site-structure-title">站点结构</span><div className="site-structure">{renderSiteBranch(0)}</div></>}</aside><div className="single-column-form"><Panel title="站点基本信息"><div className="form-grid" id="site-basic">
- <Field label="站点名称" required hint="最多 20 个汉字"><input value={site.name} maxLength={20} onChange={e=>onChange({name:e.target.value})} className={nameTooLong?"invalid":""}/></Field>
+ <Field label="站点名称" required hint="最多 20 个汉字"><UiInput value={site.name} maxLength={20} onChange={e=>onChange({name:e.target.value})} className={nameTooLong?"invalid":""}/></Field>
<Field label="所属领域" required>
<div className="domain-select">
- <input list="domain-list" value={domainInput} onChange={e=>{setDomainInput(e.target.value); onChange({domain:e.target.value});}} placeholder="选择或输入新领域"/>
+ <UiInput list="domain-list" value={domainInput} onChange={e=>{setDomainInput(e.target.value); onChange({domain:e.target.value});}} placeholder="选择或输入新领域"/>
<datalist id="domain-list">{domains.map(d=><option key={d} value={d}/>)}</datalist>
- <button className="text-button" onClick={()=>setShowAdd(!showAdd)}>{showAdd?"收起新增":"新增领域"}</button>
+ <UiButton className="text-button" onClick={()=>setShowAdd(!showAdd)}>{showAdd?"收起新增":"新增领域"}</UiButton>
</div>
{showAdd&&<div className="domain-add">
- <input value={newDomain} maxLength={6} placeholder="新领域名称(最多6字)" onChange={e=>setNewDomain(e.target.value)}/>
+ <UiInput value={newDomain} maxLength={6} placeholder="新领域名称(最多6字)" onChange={e=>setNewDomain(e.target.value)}/>
<Button secondary onClick={()=>{onAddDomain(newDomain); setNewDomain(""); setShowAdd(false);}} disabled={!newDomain || domainTooLong}>保存</Button>
</div>}
</Field>
<div className="form-section-heading" id="site-description"><h3>情况描述</h3><p>说明站点用途、重点设备、管理目标及关注指标。</p></div>
- <Field label="站点基本情况描述"><textarea value={site.description} onChange={e=>onChange({description:e.target.value})}/></Field>
+ <Field label="站点基本情况描述"><UiTextArea value={site.description} onChange={e=>onChange({description:e.target.value})}/></Field>
<div className="field-assist"><Button secondary onClick={()=>setAiOpen(true)}><RobotOutlined/> AI 智能补全</Button><span className="muted">补充要求、填写建议与参考资料均在右侧抽屉中维护</span></div>
{saving&&<div className="save-indicator">正在保存…</div>}
</div></Panel></div></div>
- <Drawer title="AI 智能补全" width={440} open={aiOpen} onClose={()=>setAiOpen(false)}>
+ <Drawer title="AI 智能补全" size={440} open={aiOpen} onClose={()=>setAiOpen(false)}>
<div className="ai-assist-drawer">
<div className="ai-assist-intro"><RobotOutlined/><div><h3>补充站点需求</h3><p>AI 将根据站点名称、所属领域和基本情况描述生成补充要求。生成结果会写入下方,仍可继续编辑。</p></div></div>
- <Field label="补充要求"><textarea className="small" value={site.supplement} onChange={e=>onChange({supplement:e.target.value})}/></Field>
+ <Field label="补充要求"><UiTextArea className="small" value={site.supplement} onChange={e=>onChange({supplement:e.target.value})}/></Field>
<div className="form-guidance"><strong>填写建议</strong><span>站点承担什么业务?哪些设备最重要?管理人员关注哪些指标?首页首先需要看到什么?</span></div>
<div className="form-section-heading" id="site-files"><h3>参考资料</h3><p>上传需求文档、设备清单或参考图片,帮助生成结果贴近真实需求。</p></div>
<Field label="文件上传">
</label>
{site.files.length>0&&<ul className="file-list">{site.files.map(f=><li key={f.id}><a href={`/api/db/sites/${site.id}/files/${f.id}`} download>{f.filename}</a> <small>({(f.size/1024).toFixed(1)} KB)</small></li>)}</ul>}
</Field>
- <div className="drawer-actions"><Button disabled={completing} onClick={runComplete}>{completing?"补全中…":"开始补全"}</Button><Button secondary onClick={onViewCompletePrompt}>查看提示词</Button></div>
+ <UiActions className="drawer-actions"><Button disabled={completing} onClick={runComplete}>{completing?"补全中…":"开始补全"}</Button><Button secondary onClick={onViewCompletePrompt}>查看提示词</Button></UiActions>
{(completing||completeLog)&&<div className="modal-console"><div className="modal-console-head">生成进度</div><pre className="modal-console-body" ref={completeConsoleRef}>{completeLog||"(等待 AI 分析…)"}</pre></div>}
</div>
</Drawer></>;
const add=()=>onChange([...rows,{type:"",count:0,rule:"",sort_order:rows.length}]);
const remove=(i:number)=>onChange(rows.filter((_,idx)=>idx!==i));
return <div className="asset-config-shell">
- <div className="asset-config-tabs" role="tablist" aria-label="资产配置方式">
- <button role="tab" aria-selected={mode==="quick"} className={mode==="quick"?"active":""} onClick={()=>onModeChange("quick")}><span>快速资产配置</span></button>
- <button role="tab" aria-selected={mode==="manual"} className={mode==="manual"?"active":""} onClick={()=>onModeChange("manual")}><span>进阶配置</span></button>
- </div>
+ <UiTabbedRegion navigation={<div className="asset-config-tabs" role="tablist" aria-label="资产配置方式">
+ <UiButton role="tab" aria-selected={mode==="quick"} className={mode==="quick"?"active":""} onClick={()=>onModeChange("quick")}><span>快速资产配置</span></UiButton>
+ <UiButton role="tab" aria-selected={mode==="manual"} className={mode==="manual"?"active":""} onClick={()=>onModeChange("manual")}><span>进阶配置</span></UiButton>
+ </div>} content={<>
{mode==="manual"?<ManualAssetConfiguration site={site} notify={notify}/>:<div className="two-column quick-asset-config">
<div>
<div className="kpi-row">{[[rows.length,"设备类型","类"],[total,"设备实例","个"],[Math.max(2, rows.filter(r=>r.type).length+1),"资产层级","层"]].map(x=><div key={x[1]}><span>{x[1]}</span><b>{x[0]}</b><small>{x[2]}</small></div>)}{saving&&<div><span>保存</span><Status>保存中…</Status></div>}</div>
<Panel title="设备资产配置" tools={<Button onClick={add}>+ 添加设备类型</Button>}>
- <table className="data-table editable">
+ <UiTable className="data-table editable">
<thead><tr><th>设备类型 *</th><th></th><th>数量 *</th><th>命名规则</th><th>操作</th></tr></thead>
<tbody>{rows.map((row,i)=>{ const matchedTemplate = deviceTemplates.find(t=>t.name===row.type); return <tr key={i} className={!row.type?"empty-row":""}>
- <td>{deviceTemplates.length > 0 ? <select value={row.type} onChange={e=>update(i,{type:e.target.value})}><option value="">请选择设备模板</option>{deviceTemplates.map(t=><option key={t.id} value={t.name}>{t.name}</option>)}</select> : <input value={row.type} placeholder="例如:冷水机组" onChange={e=>update(i,{type:e.target.value})}/>}</td>
- <td><button className={"asset-eye-btn"+(matchedTemplate?"":" disabled")} title={matchedTemplate?`查看模板:${matchedTemplate.name}`:"请先选择设备模板"} onClick={()=>{ if(matchedTemplate){ go("deviceTemplates"); window.history.replaceState({}, "", "?page=deviceTemplates&openTemplate="+matchedTemplate.id); } }}><EyeOutlined/></button></td>
- <td><input className="number" type="number" min={0} value={row.count||""} placeholder="0" onChange={e=>update(i,{count:Number(e.target.value)})}/></td>
- <td><input value={row.rule} placeholder="例如:CH-##" onChange={e=>update(i,{rule:e.target.value})}/></td>
- <td><button className="trash" onClick={()=>remove(i)}>⌫</button></td>
+ <td>{deviceTemplates.length > 0 ? <UiSelect value={row.type} onChange={e=>update(i,{type:e.target.value})}><option value="">请选择设备模板</option>{deviceTemplates.map(t=><option key={t.id} value={t.name}>{t.name}</option>)}</UiSelect> : <UiInput value={row.type} placeholder="例如:冷水机组" onChange={e=>update(i,{type:e.target.value})}/>}</td>
+ <td><UiButton className={"asset-eye-btn"+(matchedTemplate?"":" disabled")} title={matchedTemplate?`查看模板:${matchedTemplate.name}`:"请先选择设备模板"} onClick={()=>{ if(matchedTemplate){ go("deviceTemplates"); window.history.replaceState({}, "", "?page=deviceTemplates&openTemplate="+matchedTemplate.id); } }}><EyeOutlined/></UiButton></td>
+ <td><UiInput className="number" type="number" min={0} value={row.count||""} placeholder="0" onChange={e=>update(i,{count:Number(e.target.value)})}/></td>
+ <td><UiInput value={row.rule} placeholder="例如:CH-##" onChange={e=>update(i,{rule:e.target.value})}/></td>
+ <td><UiButton className="trash" onClick={()=>remove(i)}>⌫</UiButton></td>
</tr>})}</tbody>
- </table>
+ </UiTable>
</Panel>
</div>
- </div>}
+ </div>}</>}/>
</div>;
}
return null;
}
-function ManualAssetConfiguration({site,notify}:{site:SiteDetail;notify:(message:string)=>void}){
+function ManualAssetConfiguration({site,notify,onSiteChange,registerSave}:{site:SiteDetail;notify:(message:string)=>void;onSiteChange?:(site:SiteDetail)=>void;registerSave?:(save:(()=>Promise<void>)|null)=>void}){
const [group,setGroup]=useState<ManualAssetGroup|null>(null);
+ const [deviceTemplates,setDeviceTemplates]=useState<string[]>([]);
+ const [templateNames,setTemplateNames]=useState<Record<string,string>>({});
+ useEffect(()=>{fetchDb("/device-templates").then((rows:any[])=>setDeviceTemplates(rows.filter(row=>{try{return (Array.isArray(row.domain_names)?row.domain_names:JSON.parse(row.domain_names||"[]")).includes(site.domain);}catch{return false;}}).map(row=>row.name))).catch(()=>{});},[site.domain]);
const [loading,setLoading]=useState(true);
const [loadError,setLoadError]=useState("");
setLoading(true);
setLoadError("");
try{
- let rows=await fetchDb(`/sites/${site.id}/asset-groups`) as ManualAssetGroup[];
+ const [loadedGroups,allDevices]=await Promise.all([fetchDb(`/sites/${site.id}/asset-groups`),fetchDb("/device-templates")]);
+ setTemplateNames(Object.fromEntries(allDevices.map((device:any)=>[String(device.id),device.name])));
+ let rows=loadedGroups as ManualAssetGroup[];
if(rows.length===0){
const created=await fetchDb(`/sites/${site.id}/asset-groups`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:`${site.name||"未命名站点"}资产组`,category:site.domain||"站点资产",template_name:"通用资产模板",structure:seedStructure()})}) as ManualAssetGroup;
rows=[created];
const saveGroup=async(group:ManualAssetGroup)=>{
const saved=await fetchDb(`/sites/${site.id}/asset-groups/${group.id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(group)}) as ManualAssetGroup;
- setGroup(saved); notify("资产组已保存"); return saved;
+ setGroup(saved);if(onSiteChange)onSiteChange(await fetchDb(`/sites/${site.id}`)); notify("资产组已保存"); return saved;
};
if(loading)return <div className="asset-editor-loading"><span>正在打开当前站点资产组…</span></div>;
if(loadError||!group)return <div className="asset-editor-loading error"><span>{loadError||"资产组不可用"}</span><Button secondary onClick={loadGroup}>重新加载</Button></div>;
- return <ManualAssetEditor key={group.id} group={group} siteName={site.name} notify={notify} onSave={saveGroup}/>;
+ return <ManualAssetEditor key={group.id} group={group} siteName={site.name} notify={notify} onSave={saveGroup} deviceTemplates={deviceTemplates} templateNames={templateNames} registerSave={registerSave}/>;
}
function flattenAssetNodes(nodes:ManualAssetNode[],depth=0):Array<{node:ManualAssetNode;depth:number}>{
return {...node,properties:exists?node.properties.map(item=>item.name===name?{...item,value}:item):[...node.properties,{name,value,unit:""}]};
}
-function ManualAssetEditor({group,siteName,onSave,notify}:{group:ManualAssetGroup;siteName:string;onSave:(group:ManualAssetGroup)=>Promise<ManualAssetGroup>;notify:(message:string)=>void}){
- const [draft,setDraft]=useState(group);
+function ManualAssetEditor({group,siteName,onSave,notify,deviceTemplates=[],templateNames={},registerSave}:{group:ManualAssetGroup;siteName:string;onSave:(group:ManualAssetGroup)=>Promise<ManualAssetGroup>;notify:(message:string)=>void;deviceTemplates?:string[];templateNames?:Record<string,string>;registerSave?:(save:(()=>Promise<void>)|null)=>void}){
+ const normalizeLegacy=(nodes:ManualAssetNode[]):ManualAssetNode[]=>nodes.map(node=>({...node,name:templateNames[node.name]||node.name,template_name:templateNames[node.template_name]||node.template_name,children:normalizeLegacy(node.children)}));
+ const [draft,setDraft]=useState({...group,structure:normalizeLegacy(group.structure)});
const [selectedId,setSelectedId]=useState<string|null>(group.structure[0]?.id||null);
const [nodeOpen,setNodeOpen]=useState(false);
const [nodeTab,setNodeTab]=useState<"basic"|"properties"|"binding">("basic");
if(!rows.length)throw new Error("未找到“设备类型/资产模板、数量、命名规则”列"); setBatchRows(rows); setBatchTab("online"); notify(`已读取 ${rows.length} 行配置`);
}catch(e:any){notify("读取文件失败:"+(e?.message||e));}
};
- const saveAll=async()=>{setSaving(true);try{const saved=await onSave(draft);setDraft(saved);}catch(e:any){notify("保存失败:"+(e?.message||e));}finally{setSaving(false);}};
+ const saveAll=async()=>{if(nodeOpen||batchOpen)throw new Error("请先确认或取消正在编辑的节点/批量配置");if(saving)throw new Error("资产正在保存");if(JSON.stringify(draft)===JSON.stringify(group))return;setSaving(true);try{const saved=await onSave(draft);setDraft(saved);}finally{setSaving(false);}};
+ useEffect(()=>{registerSave?.(saveAll);return()=>registerSave?.(null);},[draft,group,saving,nodeOpen,batchOpen]);
+ const assetTemplates=Array.from(new Set([...deviceTemplates,...flattenAssetNodes(draft.structure).map(({node})=>node.template_name).filter(Boolean),"不绑定设备的模板"]));
const copySelected=(deep:boolean)=>{if(!selected){notify("请先选择节点");return;}setClipboard(deep?selected:{...selected,children:[]});notify(deep?"已深度复制节点及其下级":"已复制节点");};
const editSelected=(tab:"basic"|"properties"|"binding"="basic")=>{if(!selected){notify("请先选择节点");return;}openNode(selected);setNodeTab(tab);};
const bindTemplate=(templateName:string)=>{if(!selected){notify("请先选择节点");return;}const next={...selected,template_name:templateName};setDraft(prev=>({...prev,structure:updateAssetNode(prev.structure,selected.id,next)}));notify(`已应用${templateName}`);};
const visibleRows=flattenAssetNodes(draft.structure).filter(({node})=>!search||node.name.toLowerCase().includes(search.toLowerCase())||node.template_name.toLowerCase().includes(search.toLowerCase()));
const fitCanvas=()=>{setZoom(1);setPan({x:0,y:0});};
return <div className="manual-asset-editor">
- <div className="asset-editor-title"><div><h2>资产组详情</h2><span>当前站点:{siteName||"未命名站点"}</span></div><Button disabled={saving} onClick={saveAll}>{saving?"保存中…":"保存"}</Button></div>
+ <div className="asset-editor-title"><div><h2>资产组详情</h2><span>当前站点:{siteName||"未命名站点"}</span></div><Button disabled={saving} onClick={()=>void saveAll().then(()=>notify("资产组已保存")).catch(error=>notify("保存失败:"+error.message))}>{saving?"保存中…":"保存"}</Button></div>
<div className="asset-editor-workbench">
- <aside className="asset-editor-side"><Field label="站点资产组"><input value={draft.name} onChange={e=>setDraft(prev=>({...prev,name:e.target.value}))}/></Field><button className="asset-batch-entry" onClick={()=>setBatchOpen(true)}>批量创建</button><div className="asset-template-panel"><strong>资产模板</strong><select aria-label="资产模板分类" value={templateCategory} onChange={e=>setTemplateCategory(e.target.value)}><option value="site">场站设备类别</option><option value="unbound">不绑定设备的类别</option></select>{(templateCategory==="site"?["通用资产模板","污水处理设备模板","泵站设备模板"]:["不绑定设备的模板"]).map(template=><button key={template} onClick={()=>addNode(selected,template)}><span>{templateCategory==="site"?"◆":"◇"}</span><b>{template}</b><small>点击添加到当前选中节点</small></button>)}</div></aside>
- <section className="asset-tree-workspace"><div className="asset-editor-toolbar"><div className="asset-tool-group"><AntButton type="text" className="asset-tool-button" title="复制节点" aria-label="复制节点" disabled={!selected} icon={<CopyOutlined/>} onClick={()=>copySelected(false)}/><AntButton type="text" className="asset-tool-button" title="深度复制节点及其下级" aria-label="深度复制节点" disabled={!selected} icon={<SnippetsOutlined/>} onClick={()=>copySelected(true)}/><AntButton type="text" className="asset-tool-button" title="节点属性设置" aria-label="节点属性设置" disabled={!selected} icon={<ControlOutlined/>} onClick={()=>editSelected("properties")}/><AntButton type="text" className="asset-tool-button" title="删除节点" aria-label="删除节点" danger disabled={!selected} icon={<DeleteOutlined/>} onClick={deleteNode}/><span className="asset-tool-divider"/><AntButton type="text" className="asset-tool-button primary" title="添加同级节点" aria-label="添加同级节点" disabled={!selected} icon={<PlusSquareOutlined/>} onClick={addSibling}/><AntButton type="text" className="asset-tool-button primary" title="添加子节点" aria-label="添加子节点" icon={<PlusOutlined/>} onClick={()=>addNode(selected)}/><Dropdown trigger={["click"]} menu={{items:[{key:"通用资产模板",label:"应用通用资产模板"},{key:"污水处理设备模板",label:"应用污水处理设备模板"},{key:"泵站设备模板",label:"应用泵站设备模板"}],onClick:({key})=>bindTemplate(key)}}><AntButton type="text" className="asset-tool-button primary" title="应用资产模板" aria-label="应用资产模板" disabled={!selected} icon={<BlockOutlined/>}/></Dropdown></div><div className="asset-view-switch" role="group" aria-label="视图切换"><button className={viewMode==="tree"?"active":""} title="资产树视图" onClick={()=>setViewMode("tree")}><ApartmentOutlined/></button><button className={viewMode==="list"?"active":""} title="列表视图" onClick={()=>setViewMode("list")}><BarsOutlined/></button></div><div className="asset-canvas-tools"><AntButton type="text" className="asset-tool-button" title="缩小" aria-label="缩小" icon={<ZoomOutOutlined/>} onClick={()=>setZoom(value=>Math.max(.55,Number((value-.1).toFixed(2))))}/><AntButton type="text" className="asset-tool-button" title="放大" aria-label="放大" icon={<ZoomInOutlined/>} onClick={()=>setZoom(value=>Math.min(1.6,Number((value+.1).toFixed(2))))}/><AntButton type="text" className="asset-tool-button" title="适合窗口" aria-label="适合窗口" icon={<AimOutlined/>} onClick={fitCanvas}/><label className="asset-tree-search"><SearchOutlined/><input value={search} onChange={e=>setSearch(e.target.value)} placeholder="搜索资产节点"/></label></div></div>{viewMode==="tree"?<div className={dragging?"asset-tree-canvas dragging":"asset-tree-canvas"} onPointerDown={(e:ReactPointerEvent<HTMLDivElement>)=>{if((e.target as HTMLElement).closest("button,input,[role='button']"))return;setDragging(true);e.currentTarget.setPointerCapture(e.pointerId);}} onPointerMove={(e:ReactPointerEvent<HTMLDivElement>)=>{if(dragging)setPan(value=>({x:value.x+e.movementX,y:value.y+e.movementY}));}} onPointerUp={(e:ReactPointerEvent<HTMLDivElement>)=>{setDragging(false);e.currentTarget.releasePointerCapture(e.pointerId);}}><div className="asset-tree-stage" style={{transform:`translate(${pan.x}px, ${pan.y}px) scale(${zoom})`}}><div className="asset-node-tree">{draft.structure.map(node=><AssetTreeBranch key={node.id} node={node} selectedId={selectedId} search={search} onSelect={setSelectedId} onOpen={openNode} onAddChild={addNode}/>)}</div></div></div>:<div className="asset-node-list"><div className="asset-node-list-head"><span>资产名称</span><span>节点类型</span><span>资产模板</span><span>下级节点</span></div>{visibleRows.length?visibleRows.map(({node,depth})=><button key={node.id} className={selectedId===node.id?"selected":""} onClick={()=>openNode(node)}><span style={{paddingLeft:depth*22}}>{node.node_type==="group"?"◇":"◆"} {node.name}</span><span>{node.node_type==="group"?"资产分组":"设备资产"}</span><span>{node.template_name||"—"}</span><span>{node.children.length}</span></button>):<div className="asset-tree-empty"><b>没有匹配的资产节点</b><span>请更换搜索关键词。</span></div>}</div>}</section>
+ <aside className="asset-editor-side"><Field label="站点资产组"><UiInput value={draft.name} onChange={e=>setDraft(prev=>({...prev,name:e.target.value}))}/></Field><UiButton className="asset-batch-entry" onClick={()=>setBatchOpen(true)}>批量创建</UiButton><div className="asset-template-panel"><strong>资产模板</strong><UiSelect aria-label="资产模板分类" value={templateCategory} onChange={e=>setTemplateCategory(e.target.value)}><option value="site">场站设备类别</option><option value="unbound">不绑定设备的类别</option></UiSelect>{(templateCategory==="site"?assetTemplates:["不绑定设备的模板"]).map(template=><UiButton key={template} onClick={()=>addNode(selected,template)}><span>{templateCategory==="site"?"◆":"◇"}</span><b>{template}</b><small>点击添加到当前选中节点</small></UiButton>)}</div></aside>
+ <section className="asset-tree-workspace"><div className="asset-editor-toolbar"><div className="asset-tool-group"><AntButton type="text" className="asset-tool-button" title="复制节点" aria-label="复制节点" disabled={!selected} icon={<CopyOutlined/>} onClick={()=>copySelected(false)}/><AntButton type="text" className="asset-tool-button" title="深度复制节点及其下级" aria-label="深度复制节点" disabled={!selected} icon={<SnippetsOutlined/>} onClick={()=>copySelected(true)}/><AntButton type="text" className="asset-tool-button" title="节点属性设置" aria-label="节点属性设置" disabled={!selected} icon={<ControlOutlined/>} onClick={()=>editSelected("properties")}/><AntButton type="text" className="asset-tool-button" title="删除节点" aria-label="删除节点" danger disabled={!selected} icon={<DeleteOutlined/>} onClick={deleteNode}/><span className="asset-tool-divider"/><AntButton type="text" className="asset-tool-button primary" title="添加同级节点" aria-label="添加同级节点" disabled={!selected} icon={<PlusSquareOutlined/>} onClick={addSibling}/><AntButton type="text" className="asset-tool-button primary" title="添加子节点" aria-label="添加子节点" icon={<PlusOutlined/>} onClick={()=>addNode(selected)}/><Dropdown trigger={["click"]} menu={{items:assetTemplates.map(name=>({key:name,label:`应用${name}`})),onClick:({key})=>bindTemplate(key)}}><AntButton type="text" className="asset-tool-button primary" title="应用资产模板" aria-label="应用资产模板" disabled={!selected} icon={<BlockOutlined/>}/></Dropdown></div><div className="asset-view-switch" role="group" aria-label="视图切换"><UiButton className={viewMode==="tree"?"active":""} title="资产树视图" onClick={()=>setViewMode("tree")}><ApartmentOutlined/></UiButton><UiButton className={viewMode==="list"?"active":""} title="列表视图" onClick={()=>setViewMode("list")}><BarsOutlined/></UiButton></div><div className="asset-canvas-tools"><AntButton type="text" className="asset-tool-button" title="缩小" aria-label="缩小" icon={<ZoomOutOutlined/>} onClick={()=>setZoom(value=>Math.max(.55,Number((value-.1).toFixed(2))))}/><AntButton type="text" className="asset-tool-button" title="放大" aria-label="放大" icon={<ZoomInOutlined/>} onClick={()=>setZoom(value=>Math.min(1.6,Number((value+.1).toFixed(2))))}/><AntButton type="text" className="asset-tool-button" title="适合窗口" aria-label="适合窗口" icon={<AimOutlined/>} onClick={fitCanvas}/><label className="asset-tree-search"><SearchOutlined/><UiInput value={search} onChange={e=>setSearch(e.target.value)} placeholder="搜索资产节点"/></label></div></div>{viewMode==="tree"?<div className={dragging?"asset-tree-canvas dragging":"asset-tree-canvas"} onPointerDown={(e:ReactPointerEvent<HTMLDivElement>)=>{if((e.target as HTMLElement).closest("button,input,[role='button']"))return;setDragging(true);e.currentTarget.setPointerCapture(e.pointerId);}} onPointerMove={(e:ReactPointerEvent<HTMLDivElement>)=>{if(dragging)setPan(value=>({x:value.x+e.movementX,y:value.y+e.movementY}));}} onPointerUp={(e:ReactPointerEvent<HTMLDivElement>)=>{setDragging(false);e.currentTarget.releasePointerCapture(e.pointerId);}}><div className="asset-tree-stage" style={{transform:`translate(${pan.x}px, ${pan.y}px) scale(${zoom})`}}><div className="asset-node-tree">{draft.structure.map(node=><AssetTreeBranch key={node.id} node={node} selectedId={selectedId} search={search} onSelect={setSelectedId} onOpen={openNode} onAddChild={addNode}/>)}</div></div></div>:<div className="asset-node-list"><div className="asset-node-list-head"><span>资产名称</span><span>节点类型</span><span>资产模板</span><span>下级节点</span></div>{visibleRows.length?visibleRows.map(({node,depth})=><UiButton key={node.id} className={selectedId===node.id?"selected":""} onClick={()=>openNode(node)}><span style={{paddingLeft:depth*22}}>{node.node_type==="group"?"◇":"◆"} {node.name}</span><span>{node.node_type==="group"?"资产分组":"设备资产"}</span><span>{node.template_name||"—"}</span><span>{node.children.length}</span></UiButton>):<div className="asset-tree-empty"><b>没有匹配的资产节点</b><span>请更换搜索关键词。</span></div>}</div>}</section>
</div>
- <Drawer className="asset-node-drawer" title="节点配置" width="58vw" open={nodeOpen} onClose={cancelNode} footer={<div className="node-drawer-footer"><Button secondary onClick={cancelNode}>取消</Button><Button disabled={!nodeDraft?.name.trim()||!nodeDraft?.alias?.trim()||!nodeDraft?.template_name.trim()||!nodeDraft?.asset_type?.trim()} onClick={saveNode}>确定</Button></div>}>
- <div className="node-drawer-tabs"><button className={nodeTab==="basic"?"active":""} onClick={()=>setNodeTab("basic")}>基础信息设定</button><button className={nodeTab==="properties"?"active":""} onClick={()=>setNodeTab("properties")}>属性设定</button><button className={nodeTab==="binding"?"active":""} onClick={()=>setNodeTab("binding")}>数据绑定</button></div>
- {nodeDraft&&nodeTab==="basic"&&<div className="node-config-page"><section className="node-config-section"><h3>基础设定</h3><div className="node-id-row"><span>节点ID</span><b>{nodeDraft.id.replace("node-","").slice(0,10)}</b></div><div className="node-basic-grid"><Field label="名称" required><input value={nodeDraft.name} onChange={e=>setNodeDraft({...nodeDraft,name:e.target.value})}/></Field><Field label="编号/别名" required><input value={nodeDraft.alias||""} onChange={e=>setNodeDraft({...nodeDraft,alias:e.target.value})}/></Field><Field label="模板" required><select value={nodeDraft.template_name} onChange={e=>setNodeDraft({...nodeDraft,template_name:e.target.value})}><option>通用资产模板</option><option>污水处理设备模板</option><option>泵站设备模板</option><option>不绑定设备的模板</option></select></Field><Field label="类型" required><input value={nodeDraft.asset_type||""} onChange={e=>setNodeDraft({...nodeDraft,asset_type:e.target.value})}/></Field><Field label="节点描述"><input value={nodeDraft.description} placeholder="请输入" onChange={e=>setNodeDraft({...nodeDraft,description:e.target.value})}/></Field></div></section><section className="node-config-section"><h3>图片</h3><div className="node-image-row"><label className="node-image-preview"><input type="file" accept="image/jpeg,image/png,image/bmp,image/svg+xml" hidden onChange={e=>{const file=e.target.files?.[0];if(file)readNodeImage(file);}}/>{nodeDraft.image_data?<img src={nodeDraft.image_data} alt="节点图片"/>:<BlockOutlined/>}</label><div><label className="node-upload-link"><input type="file" accept="image/jpeg,image/png,image/bmp,image/svg+xml" hidden onChange={e=>{const file=e.target.files?.[0];if(file)readNodeImage(file);}}/><UploadOutlined/> 图片上传</label><small>支持格式 jpeg/jpg/png/bmp/svg,文件不超过 2 MB</small>{nodeDraft.image_data&&<button className="text-button" onClick={()=>setNodeDraft({...nodeDraft,image_data:""})}>移除图片</button>}</div></div></section><section className="node-config-section"><h3>节点二维码</h3><QRCode size={112} value={`stationbuilder://site/${group.site_id}/asset/${nodeDraft.id}`} bordered={false}/></section></div>}
- {nodeDraft&&nodeTab==="properties"&&<div className="node-config-page"><section className="node-config-section"><h3>基础属性</h3><div className="node-property-grid">{assetPropertyFields.map(name=><Field key={name} label={name}><input type={name.includes("日期")?"date":"text"} value={assetPropertyValue(nodeDraft,name)||(name==="设备运行状态计算阈值"?"5":"")} placeholder={name==="设备运行状态计算阈值"?"5":"请输入"} onChange={e=>setNodeDraft(setAssetProperty(nodeDraft,name,e.target.value))}/></Field>)}</div></section></div>}
- {nodeDraft&&nodeTab==="binding"&&<div className="node-config-page binding-page"><section className="node-config-section"><h3>边缘端绑定</h3><h4>组件列表</h4><div className="edge-component-table"><header><span>边缘组件</span><span>采集类型</span><span>群组</span><span>设备名称</span><span>操作</span></header>{bindingComponents.map(component=>{const item=nodeDraft.bindings.find(binding=>binding.source===component);return <div key={component} className={bindingComponent===component?"active":""}><b>{component}</b><span>{item?.collect_type||"—"}</span><span>{item?.group||"—"}</span><span>{item?.device_name||"—"}</span><span><button title="编辑点位" onClick={()=>setBindingComponent(component)}><EditOutlined/></button><button title="绑定边缘设备" onClick={()=>bindEdgeComponent(component)}><LinkOutlined/></button></span></div>})}</div></section><section className="node-config-section"><h3>点位绑定</h3><div className="binding-component-tabs">{bindingComponents.map(component=><button key={component} className={bindingComponent===component?"active":""} onClick={()=>setBindingComponent(component)}>{component}</button>)}</div><div className="binding-parameter-head"><h4>参数列表</h4><Button onClick={()=>setBindingImportOpen(true)}>+ 批量导入</Button></div><div className="binding-parameter-table"><header><span>名称</span><span>采集点</span><span>单位</span><span>精度</span><span>映射</span><span>描述</span><span>操作</span></header>{nodeDraft.bindings.filter(item=>item.source===bindingComponent).map((item,index)=><div key={`${bindingComponent}-${index}`}><input value={item.name||""} placeholder="参数名称" onChange={e=>updateBinding(index,{name:e.target.value})}/><input value={item.point} placeholder="采集点" onChange={e=>updateBinding(index,{point:e.target.value})}/><input value={item.unit||""} onChange={e=>updateBinding(index,{unit:e.target.value})}/><input value={item.precision||""} onChange={e=>updateBinding(index,{precision:e.target.value})}/><input value={item.mapping||""} onChange={e=>updateBinding(index,{mapping:e.target.value})}/><input value={item.description||""} onChange={e=>updateBinding(index,{description:e.target.value})}/><button className="trash" title="删除参数" onClick={()=>removeBinding(index)}><DeleteOutlined/></button></div>)}{!nodeDraft.bindings.some(item=>item.source===bindingComponent)&&<div className="binding-empty">暂无数据,请先在组件列表中绑定设备或批量导入点位。</div>}</div></section></div>}
+ <Drawer className="asset-node-drawer" title="节点配置" size="58vw" open={nodeOpen} onClose={cancelNode} footer={<div className="node-drawer-footer"><Button secondary onClick={cancelNode}>取消</Button><Button disabled={!nodeDraft?.name.trim()||!nodeDraft?.alias?.trim()||!nodeDraft?.template_name.trim()||!nodeDraft?.asset_type?.trim()} onClick={saveNode}>确定</Button></div>}>
+ <UiTabbedRegion navigation={<div className="node-drawer-tabs"><UiButton className={nodeTab==="basic"?"active":""} onClick={()=>setNodeTab("basic")}>基础信息设定</UiButton><UiButton className={nodeTab==="properties"?"active":""} onClick={()=>setNodeTab("properties")}>属性设定</UiButton><UiButton className={nodeTab==="binding"?"active":""} onClick={()=>setNodeTab("binding")}>数据绑定</UiButton></div>} content={<>
+ {nodeDraft&&nodeTab==="basic"&&<div className="node-config-page"><section className="node-config-section"><h3>基础设定</h3><div className="node-id-row"><span>节点ID</span><b>{nodeDraft.id.replace("node-","").slice(0,10)}</b></div><div className="node-basic-grid"><Field label="名称" required><UiInput value={nodeDraft.name} onChange={e=>setNodeDraft({...nodeDraft,name:e.target.value})}/></Field><Field label="编号/别名" required><UiInput value={nodeDraft.alias||""} onChange={e=>setNodeDraft({...nodeDraft,alias:e.target.value})}/></Field><Field label="模板" required><UiSelect value={nodeDraft.template_name} onChange={e=>setNodeDraft({...nodeDraft,template_name:e.target.value})}>{Array.from(new Set([...assetTemplates,nodeDraft.template_name])).map(name=><option key={name}>{name}</option>)}</UiSelect></Field><Field label="类型" required><UiInput value={nodeDraft.asset_type||""} onChange={e=>setNodeDraft({...nodeDraft,asset_type:e.target.value})}/></Field><Field label="节点描述"><UiInput value={nodeDraft.description} placeholder="请输入" onChange={e=>setNodeDraft({...nodeDraft,description:e.target.value})}/></Field></div></section><section className="node-config-section"><h3>图片</h3><div className="node-image-row"><label className="node-image-preview"><input type="file" accept="image/jpeg,image/png,image/bmp,image/svg+xml" hidden onChange={e=>{const file=e.target.files?.[0];if(file)readNodeImage(file);}}/>{nodeDraft.image_data?<img src={nodeDraft.image_data} alt="节点图片"/>:<BlockOutlined/>}</label><div><label className="node-upload-link"><input type="file" accept="image/jpeg,image/png,image/bmp,image/svg+xml" hidden onChange={e=>{const file=e.target.files?.[0];if(file)readNodeImage(file);}}/><UploadOutlined/> 图片上传</label><small>支持格式 jpeg/jpg/png/bmp/svg,文件不超过 2 MB</small>{nodeDraft.image_data&&<UiButton className="text-button" onClick={()=>setNodeDraft({...nodeDraft,image_data:""})}>移除图片</UiButton>}</div></div></section><section className="node-config-section"><h3>节点二维码</h3><QRCode size={112} value={`stationbuilder://site/${group.site_id}/asset/${nodeDraft.id}`} bordered={false}/></section></div>}
+ {nodeDraft&&nodeTab==="properties"&&<div className="node-config-page"><section className="node-config-section"><h3>基础属性</h3><div className="node-property-grid">{assetPropertyFields.map(name=><Field key={name} label={name}><UiInput type={name.includes("日期")?"date":"text"} value={assetPropertyValue(nodeDraft,name)||(name==="设备运行状态计算阈值"?"5":"")} placeholder={name==="设备运行状态计算阈值"?"5":"请输入"} onChange={e=>setNodeDraft(setAssetProperty(nodeDraft,name,e.target.value))}/></Field>)}</div></section></div>}
+ {nodeDraft&&nodeTab==="binding"&&<div className="node-config-page binding-page"><section className="node-config-section"><h3>边缘端绑定</h3><h4>组件列表</h4><div className="edge-component-table"><header><span>边缘组件</span><span>采集类型</span><span>群组</span><span>设备名称</span><span>操作</span></header>{bindingComponents.map(component=>{const item=nodeDraft.bindings.find(binding=>binding.source===component);return <div key={component} className={bindingComponent===component?"active":""}><b>{component}</b><span>{item?.collect_type||"—"}</span><span>{item?.group||"—"}</span><span>{item?.device_name||"—"}</span><span><UiButton title="编辑点位" onClick={()=>setBindingComponent(component)}><EditOutlined/></UiButton><UiButton title="绑定边缘设备" onClick={()=>bindEdgeComponent(component)}><LinkOutlined/></UiButton></span></div>})}</div></section><section className="node-config-section"><h3>点位绑定</h3><UiTabbedRegion navigation={<div className="binding-component-tabs">{bindingComponents.map(component=><UiButton key={component} className={bindingComponent===component?"active":""} onClick={()=>setBindingComponent(component)}>{component}</UiButton>)}</div>} content={<><div className="binding-parameter-head"><h4>参数列表</h4><Button onClick={()=>setBindingImportOpen(true)}>+ 批量导入</Button></div><div className="binding-parameter-table"><header><span>名称</span><span>采集点</span><span>单位</span><span>精度</span><span>映射</span><span>描述</span><span>操作</span></header>{nodeDraft.bindings.filter(item=>item.source===bindingComponent).map((item,index)=><div key={`${bindingComponent}-${index}`}><UiInput value={item.name||""} placeholder="参数名称" onChange={e=>updateBinding(index,{name:e.target.value})}/><UiInput value={item.point} placeholder="采集点" onChange={e=>updateBinding(index,{point:e.target.value})}/><UiInput value={item.unit||""} onChange={e=>updateBinding(index,{unit:e.target.value})}/><UiInput value={item.precision||""} onChange={e=>updateBinding(index,{precision:e.target.value})}/><UiInput value={item.mapping||""} onChange={e=>updateBinding(index,{mapping:e.target.value})}/><UiInput value={item.description||""} onChange={e=>updateBinding(index,{description:e.target.value})}/><UiButton className="trash" title="删除参数" onClick={()=>removeBinding(index)}><DeleteOutlined/></UiButton></div>)}{!nodeDraft.bindings.some(item=>item.source===bindingComponent)&&<div className="binding-empty">暂无数据,请先在组件列表中绑定设备或批量导入点位。</div>}</div></>}/></section></div>}</>}/>
</Drawer>
- {bindingImportOpen&&<div className="dlg-backdrop binding-import-backdrop" onMouseDown={()=>setBindingImportOpen(false)}><div className="dlg-box binding-import-dialog" onMouseDown={e=>e.stopPropagation()}><h2>批量导入点位参数</h2><p className="hint">导入到组件:{bindingComponent}</p><label className="manual-excel-upload"><input type="file" accept=".xlsx,.xls,.csv" hidden onChange={e=>{const file=e.target.files?.[0];if(file)importBindings(file);}}/><b>⇧</b><span>点击选择点位文件</span><small>表头支持:名称、采集点、单位、精度、映射、描述</small></label><div className="dlg-actions"><Button secondary onClick={()=>setBindingImportOpen(false)}>取消</Button></div></div></div>}
- {batchOpen&&<div className="dlg-backdrop" onMouseDown={()=>setBatchOpen(false)}><div className="dlg-box batch-create-dialog" onMouseDown={e=>e.stopPropagation()}><h2>批量创建资产节点</h2><div className="node-drawer-tabs"><button className={batchTab==="online"?"active":""} onClick={()=>setBatchTab("online")}>在线配置</button><button className={batchTab==="excel"?"active":""} onClick={()=>setBatchTab("excel")}>Excel 导入</button></div>{batchTab==="online"?<div className="batch-table"><header><span>资产模板</span><span>数量</span><span>命名规则</span><span></span></header>{batchRows.map((row,index)=><div key={index}><input value={row.template_name} onChange={e=>setBatchRows(prev=>prev.map((item,i)=>i===index?{...item,template_name:e.target.value}:item))}/><input type="number" min={1} value={row.count} onChange={e=>setBatchRows(prev=>prev.map((item,i)=>i===index?{...item,count:Number(e.target.value)}:item))}/><input value={row.rule} onChange={e=>setBatchRows(prev=>prev.map((item,i)=>i===index?{...item,rule:e.target.value}:item))}/><button className="trash" onClick={()=>setBatchRows(prev=>prev.filter((_,i)=>i!==index))}>×</button></div>)}<Button secondary onClick={addBatchRow}>+ 添加一行</Button></div>:<label className="manual-excel-upload"><input type="file" accept=".xlsx,.xls,.csv" hidden onChange={e=>{const file=e.target.files?.[0];if(file)importBatchFile(file);}}/><b>⇧</b><span>点击选择资产配置文件</span><small>表头支持:设备类型或资产模板、数量、命名规则</small></label>}<div className="dlg-actions"><Button secondary onClick={()=>setBatchOpen(false)}>取消</Button><Button disabled={batchTab!=="online"||!batchRows.some(item=>item.template_name&&item.count>0)} onClick={createBatch}>创建节点</Button></div></div></div>}
+ {bindingImportOpen&&<div className="dlg-backdrop binding-import-backdrop" onMouseDown={()=>setBindingImportOpen(false)}><div className="dlg-box binding-import-dialog" onMouseDown={e=>e.stopPropagation()}><h2>批量导入点位参数</h2><p className="hint">导入到组件:{bindingComponent}</p><label className="manual-excel-upload"><input type="file" accept=".xlsx,.xls,.csv" hidden onChange={e=>{const file=e.target.files?.[0];if(file)importBindings(file);}}/><b>⇧</b><span>点击选择点位文件</span><small>表头支持:名称、采集点、单位、精度、映射、描述</small></label><UiActions className="dlg-actions"><Button secondary onClick={()=>setBindingImportOpen(false)}>取消</Button></UiActions></div></div>}
+ {batchOpen&&<UiOverlay className="dlg-backdrop" onMouseDown={()=>setBatchOpen(false)}><div className="dlg-box batch-create-dialog" onMouseDown={e=>e.stopPropagation()}><h2>批量创建资产节点</h2><UiTabbedRegion navigation={<div className="node-drawer-tabs"><UiButton className={batchTab==="online"?"active":""} onClick={()=>setBatchTab("online")}>在线配置</UiButton><UiButton className={batchTab==="excel"?"active":""} onClick={()=>setBatchTab("excel")}>Excel 导入</UiButton></div>} content={<>{batchTab==="online"?<div className="batch-table"><header><span>资产模板</span><span>数量</span><span>命名规则</span><span></span></header>{batchRows.map((row,index)=><div key={index}><UiInput value={row.template_name} onChange={e=>setBatchRows(prev=>prev.map((item,i)=>i===index?{...item,template_name:e.target.value}:item))}/><UiInput type="number" min={1} value={row.count} onChange={e=>setBatchRows(prev=>prev.map((item,i)=>i===index?{...item,count:Number(e.target.value)}:item))}/><UiInput value={row.rule} onChange={e=>setBatchRows(prev=>prev.map((item,i)=>i===index?{...item,rule:e.target.value}:item))}/><UiButton className="trash" onClick={()=>setBatchRows(prev=>prev.filter((_,i)=>i!==index))}>×</UiButton></div>)}<Button secondary onClick={addBatchRow}>+ 添加一行</Button></div>:<label className="manual-excel-upload"><input type="file" accept=".xlsx,.xls,.csv" hidden onChange={e=>{const file=e.target.files?.[0];if(file)importBatchFile(file);}}/><b>⇧</b><span>点击选择资产配置文件</span><small>表头支持:设备类型或资产模板、数量、命名规则</small></label>}</>}/><UiActions className="dlg-actions"><Button secondary onClick={()=>setBatchOpen(false)}>取消</Button><Button disabled={batchTab!=="online"||!batchRows.some(item=>item.template_name&&item.count>0)} onClick={createBatch}>创建节点</Button></UiActions></div></UiOverlay>}
</div>;
}
function AssetTreeBranch({node,selectedId,search,onSelect,onOpen,onAddChild}:{node:ManualAssetNode;selectedId:string|null;search:string;onSelect:(id:string)=>void;onOpen:(node:ManualAssetNode)=>void;onAddChild:(node:ManualAssetNode)=>void}){
const [expanded,setExpanded]=useState(true);
const matched=!!search&&(node.name.toLowerCase().includes(search.toLowerCase())||node.template_name.toLowerCase().includes(search.toLowerCase()));
- return <div className="asset-tree-branch"><div role="button" tabIndex={0} className={["asset-node-card",selectedId===node.id?"selected":"",matched?"matched":""].filter(Boolean).join(" ")} onClick={()=>onOpen(node)} onKeyDown={e=>{if(e.key==="Enter")onOpen(node);}}><span>{node.node_type==="group"?"◇":"◆"}</span><b>{node.name}</b><small>{node.template_name}</small>{node.children.length?<button title={expanded?"收起下级节点":"展开下级节点"} aria-label={expanded?`收起${node.name}`:`展开${node.name}`} onClick={e=>{e.stopPropagation();onSelect(node.id);setExpanded(value=>!value);}}>{expanded?"−":"+"}</button>:<button title="添加子节点" aria-label={`为${node.name}添加子节点`} onClick={e=>{e.stopPropagation();onSelect(node.id);onAddChild(node);}}><PlusOutlined/></button>}</div>{expanded&&node.children.length>0&&<div className="asset-tree-children">{node.children.map(child=><AssetTreeBranch key={child.id} node={child} selectedId={selectedId} search={search} onSelect={onSelect} onOpen={onOpen} onAddChild={onAddChild}/>)}</div>}</div>;
+ return <div className="asset-tree-branch"><div role="button" tabIndex={0} className={["asset-node-card",selectedId===node.id?"selected":"",matched?"matched":""].filter(Boolean).join(" ")} onClick={()=>onOpen(node)} onKeyDown={e=>{if(e.key==="Enter")onOpen(node);}}><span>{node.node_type==="group"?"◇":"◆"}</span><b>{node.name}</b><small>{node.template_name}</small>{node.children.length?<UiButton title={expanded?"收起下级节点":"展开下级节点"} aria-label={expanded?`收起${node.name}`:`展开${node.name}`} onClick={e=>{e.stopPropagation();onSelect(node.id);setExpanded(value=>!value);}}>{expanded?"−":"+"}</UiButton>:<UiButton title="添加子节点" aria-label={`为${node.name}添加子节点`} onClick={e=>{e.stopPropagation();onSelect(node.id);onAddChild(node);}}><PlusOutlined/></UiButton>}</div>{expanded&&node.children.length>0&&<div className="asset-tree-children">{node.children.map(child=><AssetTreeBranch key={child.id} node={child} selectedId={selectedId} search={search} onSelect={onSelect} onOpen={onOpen} onAddChild={onAddChild}/>)}</div>}</div>;
}
function Functions({site,muTree,onMuChange,onSiteChange,pageStyles,notify,onViewCompletePrompt}:{site:SiteDetail|null;muTree:MuNode[];onMuChange:(s:string[])=>void;onSiteChange:(s:SiteDetail)=>void;pageStyles:PageStyle[];notify:(m:string)=>void;onViewCompletePrompt:()=>void}){
return <Panel>
{/* 应用信息 */}
<div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:"16px",marginBottom:"16px"}}>
- <Field label="应用名称"><input value={appName} placeholder="例如:智慧水务平台" onChange={e=>setAppName(e.target.value)} onBlur={saveAppInfo}/></Field>
+ <Field label="应用名称"><UiInput value={appName} placeholder="例如:智慧水务平台" onChange={e=>setAppName(e.target.value)} onBlur={saveAppInfo}/></Field>
<Field label="应用Icon" hint="支持 png、svg,小于 100KB">
<div style={{display:"flex",alignItems:"center",gap:"10px"}}>
- {appIcon?<img src={appIcon} alt="应用图标" style={{width:40,height:40,borderRadius:6,objectFit:"cover",border:"1px solid #ddd"}}/>:<div style={{width:40,height:40,borderRadius:6,border:"1px dashed #ddd",display:"flex",alignItems:"center",justifyContent:"center",color:"#bbb",fontSize:"12px"}}>无</div>}
+ {appIcon?<img src={appIcon} alt="应用图标" style={{width:40,height:40,borderRadius:"var(--ant-border-radius)",objectFit:"cover",border:"1px solid var(--ant-color-border)"}}/>:<div style={{width:40,height:40,borderRadius:"var(--ant-border-radius)",border:"1px dashed var(--ant-color-border)",display:"flex",alignItems:"center",justifyContent:"center",color:"var(--ant-color-text-disabled)",fontSize:"var(--ant-font-size-sm)"}}>无</div>}
<label className="text-button" style={{cursor:"pointer"}}>选择文件<input type="file" accept=".png,.svg,.jpeg,image/png,image/svg+xml" hidden onChange={pickAppIcon}/></label>
{appIconFile&&<span className="muted">{appIconFile}</span>}
</div>
{/* 设备运维页面 */}
<div style={{marginTop:"20px"}}>
- <h3 style={{fontSize:"15px",fontWeight:600,marginBottom:"10px"}}>设备运维页面</h3>
- <div className="mu-tree">{opGrouped.map(g=><div key={g.parentMenu} className="mu-group"><b>{g.parentMenu}</b><div className="mu-items">{g.items.map(item=><label key={item.key} className={item.disabled?"disabled":""}><input type="checkbox" disabled={item.disabled} checked={effSelected.includes(item.key)} onChange={()=>toggleMu(item.key)}/><span>{item.label}</span></label>)}</div></div>)}</div>
+ <h3 style={{fontSize:"var(--ant-font-size)",fontWeight:600,marginBottom:"var(--ant-padding-sm)"}}>设备运维页面</h3>
+ <div className="mu-tree">{opGrouped.map(g=><div key={g.parentMenu} className="mu-group"><b>{g.parentMenu}</b><div className="mu-items">{g.items.map(item=><label key={item.key} className={item.disabled?"disabled":""}><UiInput type="checkbox" disabled={item.disabled} checked={effSelected.includes(item.key)} onChange={()=>toggleMu(item.key)}/><span>{item.label}</span></label>)}</div></div>)}</div>
</div>
{/* 仪表盘页面 */}
<div style={{marginTop:"20px"}}>
- <h3 style={{fontSize:"15px",fontWeight:600,marginBottom:"10px"}}>仪表盘页面</h3>
+ <h3 style={{fontSize:"var(--ant-font-size)",fontWeight:600,marginBottom:"var(--ant-padding-sm)"}}>仪表盘页面</h3>
<p className="hint">以下为预设仪表盘模板示例(Demo),实际内容待配置。</p>
- <div style={{display:"flex",flexWrap:"wrap",gap:"8px"}}>{dashboardExamples.map((name,i)=><div key={i} style={{padding:"8px 14px",border:"1px solid #d9dee2",borderRadius:"4px",fontSize:"13px",color:"#555"}}>{name}</div>)}</div>
+ <div style={{display:"flex",flexWrap:"wrap",gap:"8px"}}>{dashboardExamples.map((name,i)=><div key={i} style={{padding:"var(--ant-padding-xs) var(--ant-padding)",border:"1px solid var(--ant-color-border)",borderRadius:"var(--ant-border-radius)",fontSize:"var(--ant-font-size-sm)",color:"var(--ant-color-text-secondary)"}}>{name}</div>)}</div>
</div>
{/* 数字孪生场景 */}
<div style={{marginTop:"20px"}}>
- <h3 style={{fontSize:"15px",fontWeight:600,marginBlur:"10px"}}>数字孪生场景</h3>
+ <h3 style={{fontSize:"var(--ant-font-size)",fontWeight:600,marginBottom:"var(--ant-padding-sm)"}}>数字孪生场景</h3>
<p className="hint">以下为预设数字孪生场景示例(Demo),实际内容待配置。</p>
- <div style={{display:"flex",flexWrap:"wrap",gap:"8px"}}>{twinExamples.map((name,i)=><div key={i} style={{padding:"8px 14px",border:"1px solid #d9dee2",borderRadius:"4px",fontSize:"13px",color:"#555"}}>{name}</div>)}</div>
+ <div style={{display:"flex",flexWrap:"wrap",gap:"8px"}}>{twinExamples.map((name,i)=><div key={i} style={{padding:"var(--ant-padding-xs) var(--ant-padding)",border:"1px solid var(--ant-color-border)",borderRadius:"var(--ant-border-radius)",fontSize:"var(--ant-font-size-sm)",color:"var(--ant-color-text-secondary)"}}>{name}</div>)}</div>
</div>
</Panel>;
}
const resetLog=()=>{ if(logTimer.current!=null){ clearTimeout(logTimer.current); logTimer.current=null; } logBuf.current=""; setLog(""); };
useEffect(()=>{ if(consoleRef.current) consoleRef.current.scrollTop=consoleRef.current.scrollHeight; },[log]);
if(!site) return <Panel><div className="empty-state">加载中…</div></Panel>;
+ const selectedCubes=site.cubes.filter(cube=>!site.page_config?.dashboard_ids||site.page_config.dashboard_ids.includes(cube.id!));
const scope=[
["站点",site.name||"未命名站点",site.domain],
- ["Asset 资产",`${site.assets.filter(a=>a.type).length} 类 / ${site.assets.reduce((n,a)=>n+Number(a.count||0),0)} 个设备`,`3 层资产结构(未开发)`],
- ["SC 页面",`${site.mu.length} 个标准页面`,`直接复用并配置菜单(未开发)`],
- ["Cube 页面",`${site.cubes.length} 个扩展页面`,`生成 ${site.cubes.length} 个独立 JSON`],
+ ["Asset 资产",`${site.assets.filter(a=>a.type).length} 类 / ${site.assets.reduce((n,a)=>n+Number(a.count||0),0)} 个设备`,`使用当前站点已保存的资产组结构`],
+ ["标准页面",`${site.mu.length} 个标准页面`,`使用第四步页面选择与第五步菜单配置`],
+ ["Cube 页面",`${selectedCubes.length} 个扩展页面`,`生成 ${selectedCubes.length} 个独立 JSON`],
];
const start=async()=>{
setRunning(true); setZipUrl(""); resetLog();
- const initial = [{name:"模型准备",status:"pending" as const}, ...site.cubes.map(c=>({name:c.name,status:"pending" as const})), {name:"打包与检查校验",status:"pending" as const}];
+ const initial = [{name:"模型准备",status:"pending" as const}, ...selectedCubes.map(c=>({name:c.name,status:"pending" as const})), {name:"打包与检查校验",status:"pending" as const}];
totalRef.current = initial.length;
setTasks(initial); setProgress(0);
try{
- const res = await fetch(`/api/db/sites/${site.id}/generate-all`, {method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({ cubes: site.cubes.map(c=>({id:c.id, name:c.name, content:c.content})) }) });
+ const res = await fetch(`/api/db/sites/${site.id}/generate-all`, {method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({ cubes: selectedCubes.map(c=>({id:c.id, name:c.name, content:c.content})) }) });
if(!res.ok||!res.body) throw new Error("HTTP "+res.status);
const reader = res.body.getReader(); const dec = new TextDecoder(); let buf="";
for(;;){
}catch(e:any){ notify("生成失败: "+e.message); }
finally{ setRunning(false); }
};
- return <div className="generate-layout"><Panel title="生成范围确认"><div className="confirm-grid">{scope.map(x=><div key={x[0]}><span>{x[0]}</span><b>{x[1]}</b><small>{x[2]}</small><div style={{display:"flex",gap:"6px"}}><button>{"返回修改"}</button><button className="text-button"><DownloadOutlined/> 下载</button></div></div>)}</div>
+ return <div className="generate-layout"><Panel title="生成范围确认"><div className="confirm-grid">{scope.map(x=><div key={x[0]}><span>{x[0]}</span><b>{x[1]}</b><small>{x[2]}</small><div style={{display:"flex",gap:"6px"}}><UiButton>{"返回修改"}</UiButton><UiButton className="text-button"><DownloadOutlined/> 下载</UiButton></div></div>)}</div>
<div style={{marginTop:"16px",textAlign:"right"}}><Button><DownloadOutlined/> 下载全部</Button></div></Panel></div>;
}
function loadRenderer(){
if(typeof window!=="undefined" && (window as any).renderDashboard) return Promise.resolve((window as any).renderDashboard);
if(rendererPromise) return rendererPromise;
- rendererPromise=loadScript("/echarts.min.js").then(()=>loadScript("/dashboard-renderer.js")).then(()=>(window as any).renderDashboard);
+ rendererPromise=loadScript("/t/stationbuilder/echarts.min.js").then(()=>loadScript("/t/stationbuilder/dashboard-renderer.js")).then(()=>(window as any).renderDashboard).catch(error=>{rendererPromise=null;throw error;});
return rendererPromise;
}
-function DashboardThumb({siteId, cube}:{siteId:number; cube:Cube}){
+function DashboardThumb({siteId, cube, fixedHeight}:{siteId:number; cube:Cube;fixedHeight?:number}){
const outer=useRef<HTMLDivElement>(null);
const inner=useRef<HTMLDivElement>(null);
useEffect(()=>{
const pw=json.grid?.pageWidth||1920;
const rootEl=inner.current.firstElementChild as HTMLElement|null;
const rh=rootEl? rootEl.offsetHeight : (json.grid?.pageHeight||1080);
- const s=Math.max(0.02, w/pw);
+ const s=Math.max(0.02, fixedHeight?Math.min(w/pw,fixedHeight/rh):w/pw);
inner.current.style.height=`${rh}px`;
inner.current.style.transform=`scale(${s})`;
inner.current.style.transformOrigin="top left";
- if(outer.current) outer.current.style.height=`${rh*s}px`;
+ if(outer.current) outer.current.style.height=`${fixedHeight||rh*s}px`;
}catch(e){
if(inner.current) inner.current.innerHTML='<div style="padding:8px;color:#999">预览不可用</div>';
}
})();
return ()=>{cancelled=true;};
- },[siteId, cube.id, cube.generated_at]);
+ },[siteId, cube.id, cube.generated_at, fixedHeight]);
if(!cube.id) return <div className="dashboard-thumb-live"><div className="thumb-empty">未生成</div></div>;
return <div ref={outer} className="dashboard-thumb-live" style={{overflow:"hidden"}}><div ref={inner} style={{width:1920, height:1080, position:"relative"}}/></div>;
}
const json=await fetchDb(`/sites/${site.id}/cubes/${cube.id}/file`) as any;
const safe=JSON.stringify(json).replace(/</g,"\\u003c");
const origin=window.location.origin;
- const html=`<!doctype html><html><head><meta charset="utf-8"><title>${cube.name||"看板预览"}</title><style>html,body{margin:0;background:#fff;font-family:Arial,"Microsoft YaHei",sans-serif}</style></head><body><div id="root"></div><script src="${origin}/echarts.min.js"></script><script src="${origin}/dashboard-renderer.js"></script><script>renderDashboard(${safe}, document.getElementById('root'), {fit:true});<\/script></body></html>`;
+ const html=`<!doctype html><html><head><meta charset="utf-8"><title>${cube.name||"看板预览"}</title><style>html,body{margin:0;background:#fff;font-family:Arial,"Microsoft YaHei",sans-serif}</style></head><body><div id="root"></div><script src="${origin}/t/stationbuilder/echarts.min.js"></script><script src="${origin}/t/stationbuilder/dashboard-renderer.js"></script><script>renderDashboard(${safe}, document.getElementById('root'), {fit:true});<\/script></body></html>`;
const blob=new Blob([html],{type:"text/html"});
const url=URL.createObjectURL(blob);
window.open(url,"_blank");
setTimeout(()=>URL.revokeObjectURL(url),60000);
}catch(e:any){ notify("预览打开失败:"+(e?.message||e)); }
};
- return <><div className="result-header"><div><span className="site-icon big">S</span><section><h2>{site.name || "未命名站点"}</h2><p>{site.status==="generated"?"生成完成":"草稿"} · {site.updated_at}</p></section><Status>{site.status==="generated"?"生成完成":"草稿"}</Status></div><div className="result-actions"><Button secondary onClick={()=>go("assets")}><EditOutlined/> 修改配置</Button><Button onClick={()=>window.open(`/api/db/sites/${site.id}/package`,`_blank`)}><DownloadOutlined/> 下载全部</Button><Dropdown trigger={["click"]} menu={{items:[{key:"history",label:"版本历史"},{key:"prompt",label:"查看站点提示词"}],onClick:({key})=>key==="history"?setModal("history"):viewSitePrompt()}}><Button secondary><EllipsisOutlined/> 更多</Button></Dropdown></div></div>
+ return <><div className="result-header"><div><span className="site-icon big">S</span><section><h2>{site.name || "未命名站点"}</h2><p>{site.status==="generated"?"生成完成":"草稿"} · {site.updated_at}</p></section><Status>{site.status==="generated"?"生成完成":"草稿"}</Status></div><UiActions className="result-actions"><Button secondary onClick={()=>go("assets")}><EditOutlined/> 修改配置</Button><Button onClick={()=>window.open(`/api/db/sites/${site.id}/package`,`_blank`)}><DownloadOutlined/> 下载全部</Button><Dropdown trigger={["click"]} menu={{items:[{key:"history",label:"版本历史"},{key:"prompt",label:"查看站点提示词"}],onClick:({key})=>key==="history"?setModal("history"):viewSitePrompt()}}><Button secondary><EllipsisOutlined/> 更多</Button></Dropdown></UiActions></div>
<div className="kpi-row">{[[site.assets.filter(a=>a.type).length,"Asset 资产","类设备"],[site.mu.length,"MU 页面","已配置"],[generated.length,"Cube 页面",generated.length===site.cubes.length?"校验全部通过":"部分未生成"],[generated.length+1,"交付文件","JSON+ZIP"]].map(x=><div key={x[1]}><span>{x[1]}</span><b>{x[0]}</b><small>{x[2]}</small></div>)}</div>
<div className="result-layout"><div><Panel title="页面结果"><div className="result-pages">{site.cubes.map((cube,i)=><article key={i}><DashboardThumb siteId={site.id} cube={cube} /><div><header><Status>{cube.status==="generated"?"校验通过":"未生成"}</Status><small>Cube 页面</small></header><h3>{cube.name}</h3><p>{cube.content || "无描述"}</p><footer><Button secondary onClick={()=>openPreview(cube)}><EyeOutlined/> 预览</Button>{cube.id&&<Dropdown trigger={["click"]} menu={{items:[{key:"json",label:"下载 JSON",icon:<DownloadOutlined/>},{key:"prompt",label:"查看提示词"},...(cube.gen_file_id?[{key:"feedback",label:"提交修改意见",icon:<EditOutlined/>}]:[])],onClick:({key})=>{if(key==="json")fetch(`/api/db/sites/${site.id}/cubes/${cube.id}/file`).then(r=>r.blob()).then(b=>downloadBlob(b,cube.gen_filename||`${cube.name}.json`));else if(key==="prompt")viewPrompt(cube.id,cube.name,cube.content);else if(key==="feedback")openFeedback(cube);}}}><Button secondary><EllipsisOutlined/> 更多</Button></Dropdown>}</footer></div></article>)}</div></Panel></div></div></>;
}
function Preview({site,tab,setTab,setModal,notify}:{site:SiteDetail|null;tab:"info"|"json"|"feedback";setTab:(t:"info"|"json"|"feedback")=>void;setModal:(m:ModalKey)=>void;notify:(m:string)=>void}){
const [cubeIdx,setCubeIdx]=useState(0);
const cube = site?.cubes?.[cubeIdx];
- return <div className="preview-shell"><div className="preview-toolbar"><div><select value={cubeIdx} onChange={e=>setCubeIdx(Number(e.target.value))}>{(site?.cubes||[]).map((c,i)=><option key={i} value={i}>{c.name}</option>)}</select><Status>{cube?.status==="generated"?"校验通过":"未生成"}</Status></div><div><button>1440 × 900⌄</button><button>☼ 浅色</button><button onClick={()=>notify("模拟数据已刷新")}>↻ 刷新数据</button><button>⛶ 全屏</button></div></div><div className="preview-main"><div className="cube-canvas"><div className="cube-top"><b>⬡ iStation</b><span>{site?.name || "未命名站点"}</span><i>⌕ ♧ ? ▦ W</i></div><aside className="cube-side"><strong>站点监控</strong><button className="active">▦ {cube?.name || "运行总览"}</button><button>▧ 设备管理</button><button>▥ 能源管理</button><button>△ 告警中心</button><button>⚙ 系统设置</button></aside><div className="cube-content"><header><div><small>站点监控 / {cube?.name}</small><h2>{site?.name || "未命名站点"}</h2></div><span>最后更新:刚刚 ↻</span></header><div className="cube-kpis">{[["设备在线率","92%","23 / 25"],["今日总能耗","12,480","kWh"],["未处理告警","7","高危 2"],["平均负载率","68%","较昨日 +3%"]].map((x,i)=><div key={x[0]}><span>{x[0]}</span><b>{x[1]}</b><small>{x[2]}</small><i className={"spark s"+i}/></div>)}</div><div className="cube-grid"><section className="line-panel"><h3>近 7 日能源消耗 <span>本周⌄</span></h3><div className="line-chart"><div/><svg viewBox="0 0 500 150" preserveAspectRatio="none"><polyline points="0,112 65,92 130,103 200,56 270,75 335,45 400,62 500,24" fill="none" stroke="#009fe3" strokeWidth="3"/><polyline points="0,132 65,118 130,121 200,98 270,102 335,82 400,91 500,68" fill="none" stroke="#8ecfee" strokeWidth="2"/></svg><footer>{["08/08","08/09","08/10","08/11","08/12","08/13","今天"].map(x=><span key={x}>{x}</span>)}</footer></div></section><section className="donut-panel"><h3>设备状态分布</h3><div className="donut"><svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="none" stroke="#E8E8E8" strokeWidth="14"/><circle cx="50" cy="50" r="40" fill="none" stroke="#009fe3" strokeWidth="14" strokeDasharray="180 251"/></svg><b>72%</b><span>在线</span></div></section></div></div></div></div></div>
+ return <div className="preview-shell"><div className="preview-toolbar"><div><UiSelect value={cubeIdx} onChange={e=>setCubeIdx(Number(e.target.value))}>{(site?.cubes||[]).map((c,i)=><option key={i} value={i}>{c.name}</option>)}</UiSelect><Status>{cube?.status==="generated"?"校验通过":"未生成"}</Status></div><div><UiButton>1440 × 900⌄</UiButton><UiButton>☼ 浅色</UiButton><UiButton onClick={()=>notify("模拟数据已刷新")}>↻ 刷新数据</UiButton><UiButton>⛶ 全屏</UiButton></div></div><div className="preview-main"><div className="cube-canvas"><div className="cube-top"><b>⬡ iStation</b><span>{site?.name || "未命名站点"}</span><i>⌕ ♧ ? ▦ W</i></div><aside className="cube-side"><strong>站点监控</strong><UiButton className="active">▦ {cube?.name || "运行总览"}</UiButton><UiButton>▧ 设备管理</UiButton><UiButton>▥ 能源管理</UiButton><UiButton>△ 告警中心</UiButton><UiButton>⚙ 系统设置</UiButton></aside><div className="cube-content"><header><div><small>站点监控 / {cube?.name}</small><h2>{site?.name || "未命名站点"}</h2></div><span>最后更新:刚刚 ↻</span></header><div className="cube-kpis">{[["设备在线率","92%","23 / 25"],["今日总能耗","12,480","kWh"],["未处理告警","7","高危 2"],["平均负载率","68%","较昨日 +3%"]].map((x,i)=><div key={x[0]}><span>{x[0]}</span><b>{x[1]}</b><small>{x[2]}</small><i className={"spark s"+i}/></div>)}</div><div className="cube-grid"><section className="line-panel"><h3>近 7 日能源消耗 <span>本周⌄</span></h3><div className="line-chart"><div/><svg viewBox="0 0 500 150" preserveAspectRatio="none"><polyline points="0,112 65,92 130,103 200,56 270,75 335,45 400,62 500,24" fill="none" stroke="#009fe3" strokeWidth="3"/><polyline points="0,132 65,118 130,121 200,98 270,102 335,82 400,91 500,68" fill="none" stroke="#8ecfee" strokeWidth="2"/></svg><footer>{["08/08","08/09","08/10","08/11","08/12","08/13","今天"].map(x=><span key={x}>{x}</span>)}</footer></div></section><section className="donut-panel"><h3>设备状态分布</h3><div className="donut"><svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="none" stroke="#E8E8E8" strokeWidth="14"/><circle cx="50" cy="50" r="40" fill="none" stroke="#009fe3" strokeWidth="14" strokeDasharray="180 251"/></svg><b>72%</b><span>在线</span></div></section></div></div></div></div></div>
}
function fileToBase64(file:File){return new Promise<{filename:string;mime:string;data:string}>((resolve,reject)=>{const r=new FileReader();r.onload=()=>{const buf=new Uint8Array(r.result as ArrayBuffer);let bin="";for(const b of buf)bin+=String.fromCharCode(b);resolve({filename:file.name,mime:file.type||"application/octet-stream",data:btoa(bin)});};r.onerror=reject;r.readAsArrayBuffer(file);});}
const confirm=(message:string)=>new Promise<boolean>(resolve=>setState({open:true,message,resolve}));
const close=(v:boolean)=>{ state.resolve?.(v); setState(s=>({...s,open:false})); };
const el = state.open ? (
- <div className="dlg-backdrop" onClick={()=>close(false)}>
+ <UiOverlay className="dlg-backdrop" onClick={()=>close(false)}>
<div className="dlg-box" onClick={e=>e.stopPropagation()}>
<p>{state.message}</p>
- <div className="dlg-actions"><button className="button secondary" onClick={()=>close(false)}>取消</button><button className="button" onClick={()=>close(true)}>确定</button></div>
+ <UiActions className="dlg-actions"><UiButton className="button secondary" onClick={()=>close(false)}>取消</UiButton><UiButton className="button" onClick={()=>close(true)}>确定</UiButton></UiActions>
</div>
- </div>
+ </UiOverlay>
) : null;
return { confirm, el };
}
const prompt=(message:string, defaultValue="")=>new Promise<string|null>(resolve=>setState({open:true,message,value:defaultValue,resolve}));
const close=(v:string|null)=>{ state.resolve?.(v); setState(s=>({...s,open:false})); };
const el = state.open ? (
- <div className="dlg-backdrop" onClick={()=>close(null)}>
+ <UiOverlay className="dlg-backdrop" onClick={()=>close(null)}>
<div className="dlg-box" onClick={e=>e.stopPropagation()}>
<p>{state.message}</p>
- <input autoFocus value={state.value} onChange={e=>setState(s=>({...s,value:e.target.value}))} onKeyDown={e=>{ if(e.key==="Enter") close(state.value); if(e.key==="Escape") close(null); }}/>
- <div className="dlg-actions" style={{marginTop:14}}><button className="button secondary" onClick={()=>close(null)}>取消</button><button className="button" onClick={()=>close(state.value)}>确定</button></div>
+ <UiInput autoFocus value={state.value} onChange={e=>setState(s=>({...s,value:e.target.value}))} onKeyDown={e=>{ if(e.key==="Enter") close(state.value); if(e.key==="Escape") close(null); }}/>
+ <UiActions className="dlg-actions" style={{marginTop:14}}><UiButton className="button secondary" onClick={()=>close(null)}>取消</UiButton><UiButton className="button" onClick={()=>close(state.value)}>确定</UiButton></UiActions>
</div>
- </div>
+ </UiOverlay>
) : null;
return { prompt, el };
}
useEffect(()=>setDraftModel(model),[model]);
const freeModels=MODELS.filter(item=>item.rate==="0.00x");
return <div className="system-settings-page"><Panel title="内部工具配置"><div className="settings-form">
- <Field label="工具名称"><input value="StationBuilder" readOnly/></Field>
- <Field label="内部 AI 服务"><input value="Wordbuddy AI" readOnly/></Field>
- <Field label="默认模型" hint="当前仅开放免费模型"><select value={draftModel} onChange={e=>setDraftModel(e.target.value)}>{freeModels.map(item=><option key={item.name} value={item.name}>{item.name} · {item.tag||"免费"}</option>)}</select></Field>
- <Field label="工具版本"><input value="v0.4" readOnly/></Field>
+ <Field label="工具名称"><UiInput value="StationBuilder" readOnly/></Field>
+ <Field label="内部 AI 服务"><UiInput value="Wordbuddy AI" readOnly/></Field>
+ <Field label="默认模型" hint="当前仅开放免费模型"><UiSelect value={draftModel} onChange={e=>setDraftModel(e.target.value)}>{freeModels.map(item=><option key={item.name} value={item.name}>{item.name} · {item.tag||"免费"}</option>)}</UiSelect></Field>
+ <Field label="工具版本"><UiInput value="v0.4" readOnly/></Field>
<div className="settings-note"><strong>配置说明</strong><span>此处集中维护原侧栏底部的内部工具配置。保存后,新建的 AI 会话将使用所选默认模型。</span></div>
- <div className="settings-actions"><Button onClick={()=>{onModelChange(draftModel);notify("内部工具配置已保存");}}>保存配置</Button></div>
+ <UiActions className="settings-actions"><Button onClick={()=>{onModelChange(draftModel);notify("内部工具配置已保存");}}>保存配置</Button></UiActions>
</div></Panel></div>;
}
}catch(e:any){ notify("删除失败:"+(e?.message||e)); }
};
return <>{el}<div className="system-settings-page"><Panel title="领域配置"><div className="settings-table-wrap">
- <table className="data-table company-table settings-domain-table"><thead><tr><th>领域名称</th><th>使用范围</th><th>状态</th><th>操作</th></tr></thead><tbody>{domains.map(row=><tr key={row.id}><td><b>{row.name}</b></td><td>领域方案、站点项目</td><td><Status>{row.name==="其他"?"系统预置":"可用"}</Status></td><td>{row.name==="其他"?<span className="muted">不可删除</span>:<button className="text-button danger" onClick={()=>remove(row)}>删除</button>}</td></tr>)}</tbody></table>
+ <UiTable className="data-table company-table settings-domain-table"><thead><tr><th>领域名称</th><th>使用范围</th><th>状态</th><th>操作</th></tr></thead><tbody>{domains.map(row=><tr key={row.id}><td><b>{row.name}</b></td><td>领域方案、站点项目</td><td><Status>{row.name==="其他"?"系统预置":"可用"}</Status></td><td>{row.name==="其他"?<span className="muted">不可删除</span>:<UiButton className="text-button danger" onClick={()=>remove(row)}>删除</UiButton>}</td></tr>)}</tbody></UiTable>
{domains.length===0&&<div className="empty-state">暂无领域,请点击右上角“新增领域”创建</div>}
</div></Panel></div></>;
}
<div className="domain-hero-stats">{domainOptions.map(d=><div key={d.id}><b>{list.filter(t=>t.domain_id===d.id).length}</b><span>{d.name}</span></div>)}</div>
</div>
<Panel title="领域方案列表" tools={<div className="view-toggle">
- <button className={view==="list"?"active":""} onClick={()=>setView("list")}>▤ 列表</button>
- <button className={view==="card"?"active":""} onClick={()=>setView("card")}>▦ 卡片</button>
+ <UiButton className={view==="list"?"active":""} onClick={()=>setView("list")}>▤ 列表</UiButton>
+ <UiButton className={view==="card"?"active":""} onClick={()=>setView("card")}>▦ 卡片</UiButton>
</div>}>
<div className="filter-bar">
- <input className="filter-search" value={q} onChange={e=>setQ(e.target.value)} placeholder="查找 方案名称 / 创建人" />
- <select value={domainSel} onChange={e=>setDomainSel(e.target.value)}>
+ <UiInput className="filter-search" value={q} onChange={e=>setQ(e.target.value)} placeholder="查找 方案名称 / 创建人" />
+ <UiSelect value={domainSel} onChange={e=>setDomainSel(e.target.value)}>
<option value="">全部所属领域</option>
{domainOptions.map(d=><option key={d.id} value={String(d.id)}>{d.name}</option>)}
- </select>
- <select value={statusSel} onChange={e=>setStatusSel(e.target.value)}>
+ </UiSelect>
+ <UiSelect value={statusSel} onChange={e=>setStatusSel(e.target.value)}>
<option value="">全部状态</option>
<option value="available">可用</option>
<option value="unavailable">不可用</option>
- </select>
+ </UiSelect>
<span className="filter-count muted">共 {filtered.length} 条</span>
</div>
{loading? <p>加载中…</p> :
filtered.length===0? <p className="muted">暂无方案,点击右上角「+ 新增领域方案」创建</p> :
view==="list"? (
- <table className="data-table domain-list"><thead><tr>
+ <UiTable className="data-table domain-list"><thead><tr>
<th className="sortable" style={{width:160}} onClick={()=>toggleSort("domain")}>所属领域{sortInd("domain")}</th>
<th className="sortable" style={{width:"40%"}} onClick={()=>toggleSort("name")}>方案名称{sortInd("name")}</th>
<th style={{width:64}}>版本</th>
<td><Status>{t.status==="available"?"可用":"不可用"}</Status></td>
<td>{t.creator||"—"}</td>
<td>{t.device_count}/{t.page_count}/{t.doc_count}</td>
- <td><button className="text-button" onClick={()=>onOpen(t.id)}>编辑</button> <button className="text-button" onClick={()=>del(t.id)}>删除</button></td>
+ <td><UiButton className="text-button" onClick={()=>onOpen(t.id)}>编辑</UiButton> <UiButton className="text-button" onClick={()=>del(t.id)}>删除</UiButton></td>
</tr>))}
- </tbody></table>
+ </tbody></UiTable>
) : (
<div className="domain-card-grid">{filtered.map((t:any)=>(
<article key={t.id} className="domain-card" onClick={()=>onOpen(t.id)}>
<span className="badge">{t.domain_name}</span>
<div className="meta"><span>版本 <b>{t.version}</b></span><span>创建人 <b>{t.creator||"—"}</b></span></div>
<div className="meta"><span>设备 <b>{t.device_count}</b></span><span>页面 <b>{t.page_count}</b></span><span>文档 <b>{t.doc_count}</b></span></div>
- <footer className="card-actions"><button className="text-button" onClick={e=>{e.stopPropagation();onOpen(t.id);}}>编辑</button><button className="text-button" onClick={e=>{e.stopPropagation();del(t.id);}}>删除</button></footer>
+ <footer className="card-actions"><UiButton className="text-button" onClick={e=>{e.stopPropagation();onOpen(t.id);}}>编辑</UiButton><UiButton className="text-button" onClick={e=>{e.stopPropagation();del(t.id);}}>删除</UiButton></footer>
</article>
))}</div>
)}
useEffect(()=>{ if(doms.length && !domain_id) setDomainId(doms[0].id); },[doms]);
const save=async()=>{ if(!domain_id){ notify("请选择所属领域"); return; } try{ const r:any=await fetchDb("/domain-templates",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({domain_id,name,description,status,version,creator})}); notify("已创建"); onCreated(r.id); }catch(e:any){ notify("创建失败:"+e.message); } };
return <Panel title="新增领域方案"><div className="form-grid">
- <label>所属领域<select value={domain_id} onChange={e=>setDomainId(Number(e.target.value))}>{doms.map(d=><option key={d.id} value={d.id}>{d.name}</option>)}</select></label>
- <label>方案名称<input value={name} onChange={e=>setName(e.target.value)} placeholder="如:自来水厂标准方案"/></label>
- <label>模板版本<input value={version} onChange={e=>setVersion(e.target.value)}/></label>
- <label>状态<select value={status} onChange={e=>setStatus(e.target.value)}><option value="available">可用</option><option value="unavailable">不可用</option></select></label>
- <label>创建人<input value={creator} onChange={e=>setCreator(e.target.value)}/></label>
- <label style={{gridColumn:"1 / -1"}}>领域描述<textarea value={description} onChange={e=>setDesc(e.target.value)} rows={3}/></label>
+ <label>所属领域<UiSelect value={domain_id} onChange={e=>setDomainId(Number(e.target.value))}>{doms.map(d=><option key={d.id} value={d.id}>{d.name}</option>)}</UiSelect></label>
+ <label>方案名称<UiInput value={name} onChange={e=>setName(e.target.value)} placeholder="如:自来水厂标准方案"/></label>
+ <label>模板版本<UiInput value={version} onChange={e=>setVersion(e.target.value)}/></label>
+ <label>状态<UiSelect value={status} onChange={e=>setStatus(e.target.value)}><option value="available">可用</option><option value="unavailable">不可用</option></UiSelect></label>
+ <label>创建人<UiInput value={creator} onChange={e=>setCreator(e.target.value)}/></label>
+ <label style={{gridColumn:"1 / -1"}}>领域描述<UiTextArea value={description} onChange={e=>setDesc(e.target.value)} rows={3}/></label>
<div style={{gridColumn:"1 / -1"}}><Button onClick={save}>创建</Button></div>
</div></Panel>;
}
const toggleAllAttr=()=>setAttrs(attrs.map(a=>({...a,checked:!allAttrChecked})));
const delAttrRows=async()=>{ const sel=attrs.filter(a=>a.checked); if(sel.length===0){ notify("请先勾选要删除的属性"); return; } if(!(await confirm(`确定删除选中的 ${sel.length} 个属性?`))) return; for(const a of sel){ if(a.id) await fetchDb(`/attributes/${a.id}`,{method:"DELETE"}); } setAttrs(attrs.filter(a=>!a.checked)); onChange(); };
const saveAttrRows=async()=>{ try{ for(const a of attrs){ const payload={keyword:a.keyword,name:a.name,value:a.value,unit:a.unit}; if(a.id){ await fetchDb(`/attributes/${a.id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)}); } else { await fetchDb(`/device-templates/${dev.id}/attributes`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)}); } } notify("属性已保存"); onChange(); }catch(e:any){ notify("保存失败:"+e.message); } };
+ const [alarmRules,setAlarmRules]=useState<any[]>(()=>Array.isArray(dev.alarm_rules)&&dev.alarm_rules.length?dev.alarm_rules:[
+ {id:1,name:"设备离线",level:"高",category:"故障",method:"自动",description:"设备连续 5 分钟无数据上报",enabled:true,sensitivityType:"duration",sensitivityValue:"5",typeName:"离线报警",triggers:[{id:11,name:"离线判断",source:"当前设备",compareType:"状态",point:"在线状态",operator:"=",threshold:"0"}]},
+ {id:2,name:"电压过高",level:"高",category:"报警",method:"自动",description:"供电电压超过额定上限",enabled:true,sensitivityType:"consecutive",sensitivityValue:"1",typeName:"阈值报警",triggers:[{id:21,name:"电压上限",source:"当前设备",compareType:"数值",point:"供电电压",operator:">",threshold:"240"}]}
+ ]);
+ const [alarmQuery,setAlarmQuery]=useState("");
+ const [selectedAlarmIds,setSelectedAlarmIds]=useState<Set<number>>(new Set());
+ const [alarmDraft,setAlarmDraft]=useState<any|null>(null);
+ const alarmFiltered=useMemo(()=>alarmRules.filter(rule=>!alarmQuery.trim()||`${rule.name} ${rule.description||""}`.toLowerCase().includes(alarmQuery.trim().toLowerCase())),[alarmRules,alarmQuery]);
+ const newAlarmDraft=()=>({id:null,name:"",level:"高",category:"报警",method:"自动",description:"",enabled:true,typeName:"阈值报警",sensitivityType:"consecutive",sensitivityValue:"1",triggers:[{id:-Date.now(),name:"规则 1",source:"当前设备",compareType:"数值",point:cps[0]?.name||"",operator:">",threshold:""}]});
+ const editAlarm=(rule:any)=>setAlarmDraft({...rule,triggers:(rule.triggers||[]).map((item:any)=>({...item}))});
+ const saveAlarm=()=>{ if(!alarmDraft?.name?.trim()){ notify("请填写报警名称"); return; } if(!alarmDraft.triggers?.length){ notify("请至少添加一条触发规则"); return; } const isCreate=alarmDraft.id==null; const saved={...alarmDraft,id:alarmDraft.id??Date.now()}; setAlarmRules(current=>isCreate?[...current,saved]:current.map(rule=>rule.id===saved.id?saved:rule)); setAlarmDraft(null); notify(isCreate?"报警规则已新增":"报警规则已更新"); };
+ const copyAlarm=(rule:any)=>{ const copied={...rule,id:Date.now(),name:`${rule.name} - 副本`,triggers:(rule.triggers||[]).map((item:any,index:number)=>({...item,id:-(Date.now()+index)}))}; setAlarmRules(current=>[...current,copied]); notify("已复制报警规则"); };
+ const removeAlarm=async(rule:any)=>{ if(!(await confirm(`确定删除报警规则「${rule.name}」?`))) return; setAlarmRules(current=>current.filter(item=>item.id!==rule.id)); setSelectedAlarmIds(current=>{const next=new Set(current);next.delete(rule.id);return next;}); notify("报警规则已删除"); };
+ const toggleAlarm=(id:number)=>setAlarmRules(current=>current.map(rule=>rule.id===id?{...rule,enabled:!rule.enabled}:rule));
+ const toggleAlarmSelect=(id:number)=>setSelectedAlarmIds(current=>{const next=new Set(current);next.has(id)?next.delete(id):next.add(id);return next;});
+ const allAlarmsChecked=alarmFiltered.length>0&&alarmFiltered.every(rule=>selectedAlarmIds.has(rule.id));
+ const toggleAllAlarms=()=>setSelectedAlarmIds(current=>{const next=new Set(current);if(allAlarmsChecked)alarmFiltered.forEach(rule=>next.delete(rule.id));else alarmFiltered.forEach(rule=>next.add(rule.id));return next;});
+ const updateAlarmTrigger=(id:number,field:string,value:string)=>setAlarmDraft((draft:any)=>({...draft,triggers:draft.triggers.map((item:any)=>item.id===id?{...item,[field]:value}:item)}));
+ const addAlarmTrigger=()=>setAlarmDraft((draft:any)=>({...draft,triggers:[...draft.triggers,{id:-Date.now(),name:`规则 ${draft.triggers.length+1}`,source:"当前设备",compareType:"数值",point:cps[0]?.name||"",operator:">",threshold:""}]}));
+ const removeAlarmTrigger=(id:number)=>setAlarmDraft((draft:any)=>({...draft,triggers:draft.triggers.filter((item:any)=>item.id!==id)}));
const cpAi=useStreamComplete();
const runCpComplete=async()=>{
if(cpAi.completing) return;
};
const viewCpCompletePrompt=()=>onViewCpPrompt(dev.id);
return <div className="device-card">
- <div className="device-card-head"><b>{name||"(未命名)"}</b><button className="text-button" onClick={delDev}>删除设备</button></div>
+ <div className="device-card-head"><b>{name||"(未命名)"}</b><UiButton className="text-button" onClick={delDev}>删除设备</UiButton></div>
<div className="form-grid">
- <label>设备名称<input value={name} onChange={e=>setName(e.target.value)}/></label>
+ <label>设备名称<UiInput value={name} onChange={e=>setName(e.target.value)}/></label>
<label style={{gridColumn:"1 / -1"}}>设备图片<DeviceImageField imageUrl={imgPreview} onPick={onPick}/></label>
- <label style={{gridColumn:"1 / -1"}}>说明<textarea value={description} onChange={e=>setDesc(e.target.value)} rows={2} placeholder="设备说明,可描述其用途,特性,常见使用方式等。"/></label>
+ <label style={{gridColumn:"1 / -1"}}>说明<UiTextArea value={description} onChange={e=>setDesc(e.target.value)} rows={2} placeholder="设备说明,可描述其用途,特性,常见使用方式等。"/></label>
</div>
<div className="cp-list">
- <div className="cp-list-head"><b>采集点</b><span><button className="text-button" onClick={addCpRow}>+ 新增一行</button><button className="text-button" onClick={delCpRows}>删除选中</button><button className="text-button" onClick={saveCpRows}>保存采集点</button></span></div>
- <table className="data-table cp-editor"><thead><tr><th style={{width:34}}><input type="checkbox" checked={allChecked} onChange={toggleAll}/></th><th>名称</th><th>说明</th><th>单位</th><th>数值范围</th><th>描述</th></tr></thead>
+ <div className="cp-list-head"><b>采集点</b><span><UiButton className="text-button" onClick={addCpRow}>+ 新增一行</UiButton><UiButton className="text-button" onClick={delCpRows}>删除选中</UiButton><UiButton className="text-button" onClick={saveCpRows}>保存采集点</UiButton></span></div>
+ <UiTable className="data-table cp-editor"><thead><tr><th style={{width:34}}><UiInput type="checkbox" checked={allChecked} onChange={toggleAll}/></th><th>名称</th><th>说明</th><th>单位</th><th>数值范围</th><th>描述</th></tr></thead>
<tbody>
{cps.length===0 && <tr><td colSpan={6} className="muted">暂无采集点,点「+ 新增一行」添加</td></tr>}
{cps.map((c:any)=>(
<tr key={c.key}>
- <td><input type="checkbox" checked={!!c.checked} onChange={()=>toggleCp(c.key)}/></td>
- <td><input value={c.name} onChange={e=>updCp(c.key,"name",e.target.value)} placeholder="名称"/></td>
- <td><input value={c.collect_note||""} onChange={e=>updCp(c.key,"collect_note",e.target.value)} placeholder="说明"/></td>
- <td><input value={c.unit||""} onChange={e=>updCp(c.key,"unit",e.target.value)} placeholder="单位"/></td>
+ <td><UiInput type="checkbox" checked={!!c.checked} onChange={()=>toggleCp(c.key)}/></td>
+ <td><UiInput value={c.name} onChange={e=>updCp(c.key,"name",e.target.value)} placeholder="名称"/></td>
+ <td><UiInput value={c.collect_note||""} onChange={e=>updCp(c.key,"collect_note",e.target.value)} placeholder="说明"/></td>
+ <td><UiInput value={c.unit||""} onChange={e=>updCp(c.key,"unit",e.target.value)} placeholder="单位"/></td>
<td>
- <select value={CP_RANGE_PRESETS.includes(c.value_range)?c.value_range:"__custom__"} onChange={e=>updCp(c.key,"value_range", e.target.value==="__custom__"?"":e.target.value)}>
+ <UiSelect value={CP_RANGE_PRESETS.includes(c.value_range)?c.value_range:"__custom__"} onChange={e=>updCp(c.key,"value_range", e.target.value==="__custom__"?"":e.target.value)}>
{CP_RANGE_PRESETS.map(p=><option key={p} value={p}>{p}</option>)}
<option value="__custom__">自定义…</option>
- </select>
- {!CP_RANGE_PRESETS.includes(c.value_range) && <input className="cp-range-custom" value={c.value_range} onChange={e=>updCp(c.key,"value_range",e.target.value)} placeholder="如 0~100"/>}
+ </UiSelect>
+ {!CP_RANGE_PRESETS.includes(c.value_range) && <UiInput className="cp-range-custom" value={c.value_range} onChange={e=>updCp(c.key,"value_range",e.target.value)} placeholder="如 0~100"/>}
</td>
- <td><input value={c.description||""} onChange={e=>updCp(c.key,"description",e.target.value)} placeholder="描述"/></td>
+ <td><UiInput value={c.description||""} onChange={e=>updCp(c.key,"description",e.target.value)} placeholder="描述"/></td>
</tr>
))}
- </tbody></table>
+ </tbody></UiTable>
</div>
<div className="cp-list">
- <div className="cp-list-head"><b>属性列表</b><span><button className="text-button" onClick={addAttrRow}>+ 新增一行</button><button className="text-button" onClick={delAttrRows}>删除选中</button><button className="text-button" onClick={saveAttrRows}>保存属性</button></span></div>
- <table className="data-table cp-editor"><thead><tr><th style={{width:34}}><input type="checkbox" checked={allAttrChecked} onChange={toggleAllAttr}/></th><th>关键字</th><th>属性名称</th><th>属性值</th><th>单位</th></tr></thead>
+ <div className="cp-list-head"><b>属性列表</b><span><UiButton className="text-button" onClick={addAttrRow}>+ 新增一行</UiButton><UiButton className="text-button" onClick={delAttrRows}>删除选中</UiButton><UiButton className="text-button" onClick={saveAttrRows}>保存属性</UiButton></span></div>
+ <UiTable className="data-table cp-editor"><thead><tr><th style={{width:34}}><UiInput type="checkbox" checked={allAttrChecked} onChange={toggleAllAttr}/></th><th>关键字</th><th>属性名称</th><th>属性值</th><th>单位</th></tr></thead>
<tbody>
{attrs.length===0 && <tr><td colSpan={5} className="muted">暂无属性,点「+ 新增一行」添加</td></tr>}
{attrs.map((a:any)=>(
<tr key={a.key}>
- <td><input type="checkbox" checked={!!a.checked} onChange={()=>toggleAttr(a.key)}/></td>
- <td><input value={a.keyword||""} onChange={e=>updAttr(a.key,"keyword",e.target.value)} placeholder="关键字"/></td>
- <td><input value={a.name||""} onChange={e=>updAttr(a.key,"name",e.target.value)} placeholder="属性名称"/></td>
- <td><input value={a.value||""} onChange={e=>updAttr(a.key,"value",e.target.value)} placeholder="属性值"/></td>
- <td><input value={a.unit||""} onChange={e=>updAttr(a.key,"unit",e.target.value)} placeholder="单位"/></td>
+ <td><UiInput type="checkbox" checked={!!a.checked} onChange={()=>toggleAttr(a.key)}/></td>
+ <td><UiInput value={a.keyword||""} onChange={e=>updAttr(a.key,"keyword",e.target.value)} placeholder="关键字"/></td>
+ <td><UiInput value={a.name||""} onChange={e=>updAttr(a.key,"name",e.target.value)} placeholder="属性名称"/></td>
+ <td><UiInput value={a.value||""} onChange={e=>updAttr(a.key,"value",e.target.value)} placeholder="属性值"/></td>
+ <td><UiInput value={a.unit||""} onChange={e=>updAttr(a.key,"unit",e.target.value)} placeholder="单位"/></td>
</tr>
))}
- </tbody></table>
+ </tbody></UiTable>
+ </div>
+ <div className="cp-list alarm-rule-block">
+ <div className="alarm-rule-head"><b>报警规则</b><div><label className="alarm-rule-search"><SearchOutlined/><UiInput value={alarmQuery} onChange={e=>setAlarmQuery(e.target.value)} placeholder="报警名称"/></label><Button onClick={()=>setAlarmDraft(newAlarmDraft())}><PlusOutlined/> 新增</Button></div></div>
+ <UiTable className="data-table alarm-rule-table"><thead><tr><th style={{width:42}}><UiInput type="checkbox" checked={allAlarmsChecked} onChange={toggleAllAlarms}/></th><th>报警名称</th><th>报警等级</th><th>报警类别</th><th>处理方式</th><th>报警描述</th><th style={{width:76}}>开关</th><th style={{width:128}}>操作</th></tr></thead><tbody>
+ {alarmFiltered.length===0&&<tr><td colSpan={8} className="muted">暂无报警规则,点击“新增”进行配置</td></tr>}
+ {alarmFiltered.map(rule=><tr key={rule.id}><td><UiInput type="checkbox" checked={selectedAlarmIds.has(rule.id)} onChange={()=>toggleAlarmSelect(rule.id)}/></td><td><UiButton className="link alarm-name-link" onClick={()=>editAlarm(rule)}>{rule.name}</UiButton></td><td>{rule.level}</td><td>{rule.category}</td><td>{rule.method}</td><td className="alarm-description-cell" title={rule.description}>{rule.description||"—"}</td><td><UiButton type="button" role="switch" aria-checked={rule.enabled} className={rule.enabled?"alarm-switch on":"alarm-switch"} onClick={()=>toggleAlarm(rule.id)}><i/></UiButton></td><td><div className="alarm-row-actions"><AntButton type="text" title="编辑" aria-label="编辑报警规则" icon={<EditOutlined/>} onClick={()=>editAlarm(rule)}/><AntButton type="text" title="复制" aria-label="复制报警规则" icon={<CopyOutlined/>} onClick={()=>copyAlarm(rule)}/><AntButton type="text" danger title="删除" aria-label="删除报警规则" icon={<DeleteOutlined/>} onClick={()=>removeAlarm(rule)}/></div></td></tr>)}
+ </tbody></UiTable>
</div>
<div className="field-assist" style={{marginTop:16}}>
<Button secondary onClick={()=>setAiOpen(true)}><RobotOutlined/> AI 智能补全</Button>
<span className="muted">基于设备名称 / 领域 / 说明,AI 推荐合适的采集点并追加</span>
</div>
- <Drawer title="AI 智能补全" width={440} open={aiOpen} onClose={()=>setAiOpen(false)}>
+ <Drawer className="alarm-rule-drawer" title={alarmDraft?.id==null?"新增报警规则":"编辑报警规则"} size={1000} open={!!alarmDraft} onClose={()=>setAlarmDraft(null)} footer={<div className="node-drawer-footer"><Button secondary onClick={()=>setAlarmDraft(null)}>取消</Button><Button onClick={saveAlarm}>确定</Button></div>}>
+ {alarmDraft&&<div className="alarm-rule-form">
+ <section><h3>基本信息</h3><div className="alarm-form-grid"><label><span><i>*</i> 报警名称</span><UiInput value={alarmDraft.name} onChange={e=>setAlarmDraft({...alarmDraft,name:e.target.value})} placeholder="请输入报警名称"/></label><label><span><i>*</i> 报警等级</span><UiSelect value={alarmDraft.level} onChange={e=>setAlarmDraft({...alarmDraft,level:e.target.value})}><option>紧急</option><option>高</option><option>中</option><option>低</option></UiSelect></label><label className="wide"><span>报警描述</span><UiInput value={alarmDraft.description} onChange={e=>setAlarmDraft({...alarmDraft,description:e.target.value})} placeholder="请输入报警描述"/></label></div></section>
+ <section><h3>类别设定</h3><div className="alarm-form-grid"><label><span><i>*</i> 类别名称</span><UiSelect value={alarmDraft.category} onChange={e=>setAlarmDraft({...alarmDraft,category:e.target.value})}><option>报警</option><option>故障</option><option>事件</option><option>通知</option></UiSelect></label><label><span><i>*</i> 类型名称</span><UiSelect value={alarmDraft.typeName} onChange={e=>setAlarmDraft({...alarmDraft,typeName:e.target.value})}><option>阈值报警</option><option>离线报警</option><option>状态报警</option><option>组合报警</option></UiSelect></label></div></section>
+ <section><h3>灵敏度设定</h3><div className="alarm-sensitivity"><span><i>*</i> 灵敏度类型</span><label><UiInput type="radio" name="alarm-sensitivity" checked={alarmDraft.sensitivityType==="duration"} onChange={()=>setAlarmDraft({...alarmDraft,sensitivityType:"duration"})}/> 持续时间</label><UiInput type="number" min="1" value={alarmDraft.sensitivityType==="duration"?alarmDraft.sensitivityValue:""} disabled={alarmDraft.sensitivityType!=="duration"} onChange={e=>setAlarmDraft({...alarmDraft,sensitivityValue:e.target.value})}/><em>秒,报警一次</em><label><UiInput type="radio" name="alarm-sensitivity" checked={alarmDraft.sensitivityType==="consecutive"} onChange={()=>setAlarmDraft({...alarmDraft,sensitivityType:"consecutive"})}/> 连续触发</label><UiInput type="number" min="1" value={alarmDraft.sensitivityType==="consecutive"?alarmDraft.sensitivityValue:""} disabled={alarmDraft.sensitivityType!=="consecutive"} onChange={e=>setAlarmDraft({...alarmDraft,sensitivityValue:e.target.value})}/><em>次,报警一次</em></div></section>
+ <section><h3>触发规则设定</h3><div className="alarm-trigger-panel"><header><b>触发规则列表</b><span><Button onClick={()=>notify("规则策略设定将在后续接入")}><ControlOutlined/> 规则策略设定</Button><Button onClick={addAlarmTrigger}><PlusOutlined/> 新增</Button></span></header><UiTable className="data-table alarm-trigger-table"><thead><tr><th>名称</th><th>报警来源</th><th>比较类型</th><th>监测点</th><th>运算符号</th><th>阈值</th><th>操作</th></tr></thead><tbody>{alarmDraft.triggers.map((trigger:any)=><tr key={trigger.id}><td><UiInput value={trigger.name} onChange={e=>updateAlarmTrigger(trigger.id,"name",e.target.value)}/></td><td><UiSelect value={trigger.source} onChange={e=>updateAlarmTrigger(trigger.id,"source",e.target.value)}><option>当前设备</option><option>关联设备</option></UiSelect></td><td><UiSelect value={trigger.compareType} onChange={e=>updateAlarmTrigger(trigger.id,"compareType",e.target.value)}><option>数值</option><option>状态</option><option>字符串</option></UiSelect></td><td><UiSelect value={trigger.point} onChange={e=>updateAlarmTrigger(trigger.id,"point",e.target.value)}><option value="">请选择</option>{cps.map((point:any)=><option key={point.key} value={point.name}>{point.name||"未命名采集点"}</option>)}</UiSelect></td><td><UiSelect value={trigger.operator} onChange={e=>updateAlarmTrigger(trigger.id,"operator",e.target.value)}><option>></option><option>>=</option><option><</option><option><=</option><option>=</option><option>!=</option></UiSelect></td><td><UiInput value={trigger.threshold} onChange={e=>updateAlarmTrigger(trigger.id,"threshold",e.target.value)} placeholder="请输入"/></td><td><AntButton type="text" danger title="删除触发规则" icon={<DeleteOutlined/>} onClick={()=>removeAlarmTrigger(trigger.id)}/></td></tr>)}</tbody></UiTable>{alarmDraft.triggers.length===0&&<div className="alarm-trigger-empty">暂无数据</div>}</div></section>
+ </div>}
+ </Drawer>
+ <Drawer title="AI 智能补全" size={440} open={aiOpen} onClose={()=>setAiOpen(false)}>
<div className="ai-assist-drawer">
<div className="ai-assist-intro"><RobotOutlined/><div><h3>补充采集点</h3><p>AI 将根据设备名称、所属领域和说明生成合适的采集点,并以追加模式写入该设备模板。</p></div></div>
<p className="hint">为当前设备模板补充一套合适的采集点(追加模式,不覆盖已手动录入的采集点)。</p>
- <div className="drawer-actions">
+ <UiActions className="drawer-actions">
<Button disabled={cpAi.completing} onClick={runCpComplete}>{cpAi.completing?"采集中…":"开始补全"}</Button>
{cpAi.completing&&<Button secondary onClick={stopCpComplete}>停止</Button>}
<Button secondary onClick={viewCpCompletePrompt}>查看提示词</Button>
- </div>
+ </UiActions>
{(cpAi.completing||cpAi.log)&&<div className="modal-console"><div className="modal-console-head">AI 智能补全</div><pre className="modal-console-body" ref={cpAi.ref}>{cpAi.log||"(等待 AI 分析…)"}</pre></div>}
</div>
</Drawer>
const [imgB64,setImgB64]=useState<{filename:string;mime:string;data:string}|null>(null);
const onPick=async(f:File)=>{ setImgPreview(URL.createObjectURL(f)); const b=await fileToBase64(f); setImgB64({filename:b.filename,mime:b.mime,data:b.data}); };
const add=async()=>{ if(!name.trim()){ notify("请填写设备名称"); return; } try{ const body:any={name,description}; if(imgB64) body.image=imgB64; const r:any=await fetchDb(`/device-templates`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)}); notify("已创建设备模版"); onDone(r.id); }catch(e:any){ notify("创建失败:"+e.message); } };
- return <Panel title="新增设备模版" tools={<button className="text-button" onClick={onCancel}>取消</button>}><div className="form-grid">
- <label>设备名称<input value={name} onChange={e=>setName(e.target.value)} placeholder="如:PLC 控制器"/></label>
+ return <Panel title="新增设备模版" tools={<UiButton className="text-button" onClick={onCancel}>取消</UiButton>}><div className="form-grid">
+ <label>设备名称<UiInput value={name} onChange={e=>setName(e.target.value)} placeholder="如:PLC 控制器"/></label>
<label style={{gridColumn:"1 / -1"}}>设备图片<DeviceImageField imageUrl={imgPreview} onPick={onPick}/></label>
- <label style={{gridColumn:"1 / -1"}}>说明<textarea value={description} onChange={e=>setDesc(e.target.value)} rows={2}/></label>
+ <label style={{gridColumn:"1 / -1"}}>说明<UiTextArea value={description} onChange={e=>setDesc(e.target.value)} rows={2}/></label>
<div style={{gridColumn:"1 / -1"}}><Button onClick={add}>创建</Button></div>
</div></Panel>;
}
{confirmEl}
<div className="domain-hero"><div><span className="eyebrow">DEVICE TEMPLATE LIBRARY</span><h2>设备模版</h2><p>独立维护设备模版及其采集点,编辑后可在领域方案中按需选用。</p></div></div>
<Panel title="设备模版" tools={<Button onClick={()=>setOpenId("create")}>+ 新增设备模版</Button>}>
- <div className="list-toolbar"><input className="search" placeholder="搜索设备名称 / 说明" value={q} onChange={e=>setQ(e.target.value)}/></div>
+ <div className="list-toolbar"><UiInput className="search" placeholder="搜索设备名称 / 说明" value={q} onChange={e=>setQ(e.target.value)}/></div>
{loading? <p>加载中…</p> :
filtered.length===0? <p className="muted">{list.length===0?"暂无设备模版,点击「+ 新增设备模版」创建。":"无匹配结果"}</p> :
- <table className="data-table device-tpl-list"><thead><tr><th>图片</th><th>名称</th><th>说明</th><th>采集点</th><th>操作</th></tr></thead>
+ <UiTable className="data-table device-tpl-list"><thead><tr><th>图片</th><th>名称</th><th>说明</th><th>采集点</th><th>操作</th></tr></thead>
<tbody>{filtered.map((d:any)=>(
<tr key={d.id} onClick={()=>setOpenId(d.id)}>
<td>{d.image_path? <img className="tpl-thumb" src={`/api/db/device-templates/${d.id}/image`} alt=""/> : <span className="tpl-thumb-empty">—</span>}</td>
<td>{d.name}</td>
<td className="muted">{d.description||""}</td>
<td>{(d.collection_points||[]).length}</td>
- <td onClick={e=>e.stopPropagation()}><button className="text-button" onClick={()=>setOpenId(d.id)}>编辑</button> <button className="text-button" onClick={()=>delDev(d.id)}>删除</button></td>
+ <td onClick={e=>e.stopPropagation()}><UiButton className="text-button" onClick={()=>setOpenId(d.id)}>编辑</UiButton> <UiButton className="text-button" onClick={()=>delDev(d.id)}>删除</UiButton></td>
</tr>
- ))}</tbody></table>}
+ ))}</tbody></UiTable>}
</Panel>
</>;
if(openId==="create") return <>
- <div className="domain-detail-head"><button className="text-button" onClick={()=>setOpenId(null)}>← 返回列表</button></div>
+ <div className="domain-detail-head"><UiButton className="text-button" onClick={()=>setOpenId(null)}>← 返回列表</UiButton></div>
<DeviceTemplateCreate notify={notify} onDone={async(id:number)=>{ await load(); setOpenId(id); }} onCancel={()=>setOpenId(null)}/>
</>;
const dev=list.find(d=>d.id===openId);
if(!dev) return <p>加载中…</p>;
return <>
- <div className="domain-detail-head"><button className="text-button" onClick={()=>setOpenId(null)}>← 返回列表</button></div>
+ <div className="domain-detail-head"><UiButton className="text-button" onClick={()=>setOpenId(null)}>← 返回列表</UiButton></div>
<DeviceTemplateCard dev={dev} notify={notify} onChange={load} onBack={()=>setOpenId(null)} onViewCpPrompt={onViewCpPrompt}/>
</>;
}
{all.length===0? <p className="muted">暂无设备模版,点击下方「+ 新增设备模版」创建后选用。</p> :
all.map((d:any)=>{ let doms:any[]=[]; try{ doms=JSON.parse(d.domain_names||"[]"); }catch{} return (
<label key={d.id} className={"picker-item"+(sel.has(d.id)?" on":"")}>
- <input type="checkbox" checked={sel.has(d.id)} onChange={e=>toggle(d.id,e.target.checked)}/>
+ <UiInput type="checkbox" checked={sel.has(d.id)} onChange={e=>toggle(d.id,e.target.checked)}/>
<span className="picker-name">{d.name}</span>
<span className="picker-doms">{(doms.length?doms.join("、"):"未归类")}</span>
</label>
const {confirm, el:confirmEl}=useConfirm();
const save=async()=>{ try{ JSON.parse(json_text); }catch{ notify("Cube JSON 不是合法 JSON"); return; } await fetchDb(`/domain-pages/${p.id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({name,description,json_text})}); notify("页面已保存"); setEditing(false); onChange(); };
const del=async()=>{ if(!(await confirm("删除该页面?"))) return; await fetchDb(`/domain-pages/${p.id}`,{method:"DELETE"}); onChange(); };
- return <div className="page-row">{confirmEl}<div className="page-row-head"><b>{name}</b><span>{editing? <><Button onClick={save}>保存</Button> <button className="text-button" onClick={()=>setEditing(false)}>取消</button></> : <><button className="text-button" onClick={()=>setEditing(true)}>编辑</button> <button className="text-button" onClick={del}>删除</button></>}</span></div>
+ return <div className="page-row">{confirmEl}<div className="page-row-head"><b>{name}</b><span>{editing? <><Button onClick={save}>保存</Button> <UiButton className="text-button" onClick={()=>setEditing(false)}>取消</UiButton></> : <><UiButton className="text-button" onClick={()=>setEditing(true)}>编辑</UiButton> <UiButton className="text-button" onClick={del}>删除</UiButton></>}</span></div>
{editing && <div className="form-grid">
- <label>页面名称<input value={name} onChange={e=>setName(e.target.value)}/></label>
- <label>说明<input value={description} onChange={e=>setDesc(e.target.value)}/></label>
- <label style={{gridColumn:"1 / -1"}}>Cube JSON<textarea value={json_text} onChange={e=>setJson(e.target.value)} rows={10} style={{fontFamily:"monospace"}}/></label>
+ <label>页面名称<UiInput value={name} onChange={e=>setName(e.target.value)}/></label>
+ <label>说明<UiInput value={description} onChange={e=>setDesc(e.target.value)}/></label>
+ <label style={{gridColumn:"1 / -1"}}>Cube JSON<UiTextArea value={json_text} onChange={e=>setJson(e.target.value)} rows={10} style={{fontFamily:"monospace"}}/></label>
</div>}
</div>;
}
-function OpsFlowsViewer(){ const [flows,setFlows]=useState<any[]>([]); useEffect(()=>{ fetchDb("/ops-flows").then((d:any)=>setFlows(d||[])).catch(()=>{}); },[]); return <Panel title="运维流程(本期为占位,后续实现)"><table className="data-table"><thead><tr><th>流程名称</th><th>内容</th></tr></thead><tbody>{flows.length===0? <tr><td colSpan={2} className="muted">暂无</td></tr> : flows.map((f:any)=><tr key={f.id}><td>{f.name}</td><td style={{maxWidth:480,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}>{f.content}</td></tr>)}</tbody></table><p className="muted">运维流程功能尚未实现,当前仅保留占位记录。</p></Panel>; }
+function OpsFlowsViewer(){ const [flows,setFlows]=useState<any[]>([]); useEffect(()=>{ fetchDb("/ops-flows").then((d:any)=>setFlows(d||[])).catch(()=>{}); },[]); return <Panel title="运维流程(本期为占位,后续实现)"><UiTable className="data-table"><thead><tr><th>流程名称</th><th>内容</th></tr></thead><tbody>{flows.length===0? <tr><td colSpan={2} className="muted">暂无</td></tr> : flows.map((f:any)=><tr key={f.id}><td>{f.name}</td><td style={{maxWidth:480,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}>{f.content}</td></tr>)}</tbody></UiTable><p className="muted">运维流程功能尚未实现,当前仅保留占位记录。</p></Panel>; }
-function DomainDetail({id,go,notify,onBack,onCreated,viewCompletePrompt,viewCompleteAssetsPrompt,viewCompleteCubesPrompt,pageStyles}:{id:number|null;go:(p:PageKey)=>void;notify:(m:string)=>void;onBack:()=>void;onCreated:(nid:number)=>void;viewCompletePrompt:(id:number,name:string,domain:string,description:string)=>void;viewCompleteAssetsPrompt:(id:number,name:string,domain:string,description:string,supplement:string)=>void;viewCompleteCubesPrompt:(id:number,name:string,domain:string,description:string,supplement:string,assets:any[])=>void;pageStyles:PageStyle[]}){
+function DomainDetail({id,go,notify,onBack,onCreated,viewCompletePrompt,viewCompleteAssetsPrompt,viewCompleteCubesPrompt,pageStyles,onEditAssets,onCreateSite}:{id:number|null;go:(p:PageKey)=>void;notify:(m:string)=>void;onBack:()=>void;onCreated:(nid:number)=>void;viewCompletePrompt:(id:number,name:string,domain:string,description:string)=>void;viewCompleteAssetsPrompt:(id:number,name:string,domain:string,description:string,supplement:string)=>void;viewCompleteCubesPrompt:(id:number,name:string,domain:string,description:string,supplement:string,assets:any[])=>void;pageStyles:PageStyle[];onEditAssets:(domainName:string)=>void;onCreateSite:(templateId:number)=>Promise<void>}){
const [doms,setDoms]=useState<any[]>([]);
- const [loading,setLoading]=useState(false);
const loadDoms=async()=>{ try{ setDoms(await fetchDb("/domains") as any); }catch{} };
useEffect(()=>{ loadDoms(); },[]);
- if(id==null) return <CreateTemplate doms={doms} notify={notify} onCreated={onCreated}/>;
- return <DomainWizard id={id} doms={doms} go={go} notify={notify} onBack={onBack} viewCompletePrompt={viewCompletePrompt} viewCompleteAssetsPrompt={viewCompleteAssetsPrompt} viewCompleteCubesPrompt={viewCompleteCubesPrompt} pageStyles={pageStyles}/>;
+ if(id==null) return <ImportDomainPackage doms={doms} notify={notify} onImported={onCreated}/>;
+ return <DomainCapabilityDetail id={id} doms={doms} notify={notify} onBack={onBack} onEditAssets={onEditAssets} onCreateSite={onCreateSite}/>;
}
const DOMAIN_STEPS:{key:DomainStepKey;label:string}[]=[
const isDone=completed?configured[item.key]:index<current;
const clickable=completed||index<=current;
const cls=index===current?"active":isDone?"done":"";
- return <button key={item.key} onClick={()=>clickable&&go(item.key)} className={cls+(clickable?"":" locked")}><i>{isDone?"✓":index+1}</i><span><small>STEP {index+1}</small>{item.label}</span>{index<6&&<b>›</b>}</button>;
+ return <UiButton key={item.key} onClick={()=>clickable&&go(item.key)} className={cls+(clickable?"":" locked")}><i>{isDone?"✓":index+1}</i><span><small>STEP {index+1}</small>{item.label}</span>{index<6&&<b>›</b>}</UiButton>;
})}</div>;
}
for(;;){ const {done,value}=await reader.read(); if(done) break; buf+=dec.decode(value,{stream:true}); const lines=buf.split("\n"); buf=lines.pop()||""; for(const line of lines){ if(!line.trim()) continue; try{ const ev=JSON.parse(line); if(ev.log) setLog(prev=>prev+ev.log); onEv(ev); }catch{} } }
}
-function DomainPageThumb({jsonText,emptyLabel="未生成",fit=false}:{jsonText:string; emptyLabel?:string; fit?:boolean}){
+function DomainPageThumb({jsonText,emptyLabel="未生成",fit=false,fixedHeight}:{jsonText:string; emptyLabel?:string; fit?:boolean; fixedHeight?:number}){
const outer=useRef<HTMLDivElement>(null);
const inner=useRef<HTMLDivElement>(null);
useEffect(()=>{
let cancelled=false;
- if(!jsonText || jsonText.trim()==="{}" || jsonText.trim()===""){ if(inner.current) inner.current.innerHTML='<div class="thumb-empty">'+emptyLabel+'</div>'; if(outer.current) outer.current.style.height="auto"; return; }
+ if(!jsonText || jsonText.trim()==="{}" || jsonText.trim()===""){ if(inner.current) inner.current.innerHTML='<div class="thumb-empty">'+emptyLabel+'</div>'; if(outer.current) outer.current.style.height=fixedHeight?`${fixedHeight}px`:"auto"; return; }
(async()=>{
try{
const json=JSON.parse(jsonText);
const rootEl=inner.current.firstElementChild as HTMLElement|null;
const rh=rootEl? rootEl.offsetHeight : (json.grid?.pageHeight||1080);
// fit 模式:同时按宽、高缩放,整页一次可见(修复“只能预览上半部分”)
- const availH = fit ? (outer.current.parentElement?.clientHeight || rh) : Infinity;
+ const availH = fixedHeight || (fit ? (outer.current.parentElement?.clientHeight || rh) : Infinity);
const s = fit ? Math.max(0.02, Math.min(w/pw, availH/rh)) : Math.max(0.02, w/pw);
inner.current.style.height=`${rh}px`;
inner.current.style.transform=`scale(${s})`;
inner.current.style.transformOrigin="top left";
- outer.current.style.height=`${rh*s}px`;
+ outer.current.style.height=fixedHeight?`${fixedHeight}px`:`${rh*s}px`;
}catch(e){ if(inner.current) inner.current.innerHTML='<div class="thumb-empty">预览不可用</div>'; }
})();
return ()=>{cancelled=true;};
- },[jsonText, emptyLabel, fit]);
+ },[jsonText, emptyLabel, fit, fixedHeight]);
return <div ref={outer} className="dashboard-thumb-live" style={{overflow:"hidden"}}><div ref={inner} style={{width:1920, height:1080, position:"relative"}}/></div>;
}
}catch(e:any){ notify("重新生成失败:"+(e?.message||e)); }
finally{ clearTimeout(watchdog); setRefining(false); }
};
- return <div className="modal-backdrop" onMouseDown={()=>onClose(false)}><div className="modal" style={{width:"min(1100px,94vw)",maxWidth:"94vw"}} onMouseDown={e=>e.stopPropagation()}>
- <header><h2>{page.name||"页面预览"}</h2><button onClick={()=>onClose(false)}>×</button></header>
+ return <UiOverlay className="modal-backdrop" onMouseDown={()=>onClose(false)}><div className="modal" style={{width:"min(1100px,94vw)",maxWidth:"94vw"}} onMouseDown={e=>e.stopPropagation()}>
+ <header><h2>{page.name||"页面预览"}</h2><UiButton onClick={()=>onClose(false)}>×</UiButton></header>
<div className="modal-body">
<div className="domain-preview-canvas">{hasJson? <DomainPageThumb jsonText={page.json_text} fit/> : <div className="thumb-empty">尚未生成,请点击「生成」后再预览</div>}</div>
<div className="domain-preview-feedback">
<h4>修改意见</h4>
<p className="hint">输入对当前页面的修改意见,AI 将据此重新生成该页面。</p>
- <textarea value={feedback} onChange={e=>setFeedback(e.target.value)} placeholder="例如:把能耗图表改为按周维度对比,并增加告警列表" rows={4}/>
+ <UiTextArea value={feedback} onChange={e=>setFeedback(e.target.value)} placeholder="例如:把能耗图表改为按周维度对比,并增加告警列表" rows={4}/>
{refining&&<pre className="modal-console-body" ref={consoleRef}>{refineLog||"(重新生成中…)"}</pre>}
<Button disabled={refining} onClick={regenerate}>{refining?"重新生成中…":"根据意见重新生成"}</Button>
</div>
</div>
- </div></div>;
+ </div></UiOverlay>;
}
function DomainWizard({id,doms,go,notify,onBack,viewCompletePrompt,viewCompleteAssetsPrompt,viewCompleteCubesPrompt,pageStyles}:{id:number;doms:any[];go:(p:PageKey)=>void;notify:(m:string)=>void;onBack:()=>void;viewCompletePrompt:(id:number,name:string,domain:string,description:string)=>void;viewCompleteAssetsPrompt:(id:number,name:string,domain:string,description:string,supplement:string)=>void;viewCompleteCubesPrompt:(id:number,name:string,domain:string,description:string,supplement:string,assets:any[])=>void;pageStyles:PageStyle[]}){
return <>
{confirmEl}
- {devCreateOpen && <div className="modal-backdrop" onMouseDown={()=>{setDevCreateOpen(false);setPendingDevRow(null);}}><div className="modal" style={{width:"640px"}} onMouseDown={e=>e.stopPropagation()}><header><h2>新增设备模版</h2><button onClick={()=>{setDevCreateOpen(false);setPendingDevRow(null);}}>×</button></header><div className="modal-body"><DeviceTemplateCreate notify={notify} onDone={onDevCreated} onCancel={()=>{setDevCreateOpen(false);setPendingDevRow(null);}}/></div></div></div>}
+ {devCreateOpen && <UiOverlay className="modal-backdrop" onMouseDown={()=>{setDevCreateOpen(false);setPendingDevRow(null);}}><div className="modal" style={{width:"640px"}} onMouseDown={e=>e.stopPropagation()}><header><h2>新增设备模版</h2><UiButton onClick={()=>{setDevCreateOpen(false);setPendingDevRow(null);}}>×</UiButton></header><div className="modal-body"><DeviceTemplateCreate notify={notify} onDone={onDevCreated} onCancel={()=>{setDevCreateOpen(false);setPendingDevRow(null);}}/></div></div></UiOverlay>}
{previewPage && <DomainPagePreviewModal page={previewPage} tmplId={id} notify={notify} onClose={(reload:boolean)=>{ setPreviewPage(null); if(reload) load(true); }}/>}
- <div className="domain-detail-head"><button className="text-button" onClick={onBack}>← 返回列表</button></div>
+ <div className="domain-detail-head"><UiButton className="text-button" onClick={onBack}>← 返回列表</UiButton></div>
<DomainSteps step={step} go={goStep} completed={completed} configured={configured}/>
<div className="content">
{step==="basic" && <div className="two-column basic-layout"><Panel title="方案信息"><div className="form-grid">
- <Field label="所属领域" required><select value={m.domain_id} onChange={e=>setM({...m,domain_id:Number(e.target.value)})}>{doms.map((d:any)=><option key={d.id} value={d.id}>{d.name}</option>)}</select></Field>
- <Field label="方案名称" required><input value={m.name} maxLength={20} onChange={e=>setM({...m,name:e.target.value})}/></Field>
- <Field label="版本"><input value={m.version} onChange={e=>setM({...m,version:e.target.value})}/></Field>
- <Field label="状态"><select value={m.status} onChange={e=>setM({...m,status:e.target.value})}><option value="available">可用</option><option value="unavailable">不可用</option></select></Field>
- <Field label="创建人"><input value={m.creator} onChange={e=>setM({...m,creator:e.target.value})}/></Field>
- <Field label="方案描述" required hint="建议包含方案用途、重点设备、管理目标及关注指标"><textarea value={m.description} onChange={e=>setM({...m,description:e.target.value})} rows={3}/></Field>
- <Field label="补充描述" hint="可由右侧 AI 智能补全自动生成"><textarea className="small" value={m.supplement||""} onChange={e=>setM({...m,supplement:e.target.value})}/></Field>
+ <Field label="所属领域" required><UiSelect value={m.domain_id} onChange={e=>setM({...m,domain_id:Number(e.target.value)})}>{doms.map((d:any)=><option key={d.id} value={d.id}>{d.name}</option>)}</UiSelect></Field>
+ <Field label="方案名称" required><UiInput value={m.name} maxLength={20} onChange={e=>setM({...m,name:e.target.value})}/></Field>
+ <Field label="版本"><UiInput value={m.version} onChange={e=>setM({...m,version:e.target.value})}/></Field>
+ <Field label="状态"><UiSelect value={m.status} onChange={e=>setM({...m,status:e.target.value})}><option value="available">可用</option><option value="unavailable">不可用</option></UiSelect></Field>
+ <Field label="创建人"><UiInput value={m.creator} onChange={e=>setM({...m,creator:e.target.value})}/></Field>
+ <Field label="方案描述" required hint="建议包含方案用途、重点设备、管理目标及关注指标"><UiTextArea value={m.description} onChange={e=>setM({...m,description:e.target.value})} rows={3}/></Field>
+ <Field label="补充描述" hint="可由右侧 AI 智能补全自动生成"><UiTextArea className="small" value={m.supplement||""} onChange={e=>setM({...m,supplement:e.target.value})}/></Field>
<div style={{gridColumn:"1 / -1"}}><Button onClick={saveMeta}>保存方案信息</Button></div>
</div></Panel>
<div className="stack">
<Panel title="AI 智能补全">
<p className="hint">根据「方案名称 / 所属领域 / 方案描述」,由 AI 搜索同类型站点的物联网建设经验,自动生成补充描述到左侧「补充描述」。</p>
<div style={{display:"flex",gap:"8px",alignItems:"center"}}>
- <button className="button" disabled={basicAi.completing} onClick={runBasicComplete}>{basicAi.completing?"补全中…":"开始补全"}</button>
- <button className="text-button" onClick={()=>viewCompletePrompt(id,m.name,domainName,m.description)}>查看提示词</button>
+ <UiButton className="button" disabled={basicAi.completing} onClick={runBasicComplete}>{basicAi.completing?"补全中…":"开始补全"}</UiButton>
+ <UiButton className="text-button" onClick={()=>viewCompletePrompt(id,m.name,domainName,m.description)}>查看提示词</UiButton>
</div>
{(basicAi.completing||basicAi.log)&&<div className="modal-console"><div className="modal-console-head">AI 智能补全</div><pre className="modal-console-body" ref={basicAi.ref}>{basicAi.log||"(等待 AI 分析…)"}</pre></div>}
</Panel>
<div>
<div className="kpi-row">{[[assets.filter(a=>a.device_template_id).length,"设备类型","类"],[assets.reduce((n,a)=>n+Number(a.count||0),0),"设备实例","个"],[Math.max(2, assets.filter(a=>a.device_template_id).length+1),"资产层级","层"]].map(x=><div key={x[1]}><span>{x[1]}</span><b>{x[0]}</b><small>{x[2]}</small></div>)}{saving&&<div><span>保存</span><Status>保存中…</Status></div>}</div>
<Panel title="设备资产配置" tools={<Button onClick={addAssetRow}>+ 添加设备类型</Button>}>
- <table className="data-table editable">
+ <UiTable className="data-table editable">
<thead><tr><th>设备类型 *</th><th>数量 *</th><th>命名规则</th><th>操作</th></tr></thead>
<tbody>{assetRows.map((row,i)=><tr key={i} className={!row.device_template_id?"empty-row":""}>
- <td><select value={row.device_template_id||""} onChange={e=>onAssetDeviceChange(i,e.target.value)}><option value="">选择设备模板…</option>{allDevices.map((d:any)=><option key={d.id} value={d.id}>{d.name}</option>)}<option value="__new__">+ 新增设备模板…</option></select></td>
- <td><input className="number" type="number" min={0} value={row.count||""} placeholder="0" onChange={e=>updateAsset(i,{count:Number(e.target.value)})}/></td>
- <td><input value={row.rule} placeholder="例如:CH-##" onChange={e=>updateAsset(i,{rule:e.target.value})}/></td>
- <td><button className="trash" onClick={()=>removeAssetRow(i)}>⌫</button></td>
+ <td><UiSelect value={row.device_template_id||""} onChange={e=>onAssetDeviceChange(i,e.target.value)}><option value="">选择设备模板…</option>{allDevices.map((d:any)=><option key={d.id} value={d.id}>{d.name}</option>)}<option value="__new__">+ 新增设备模板…</option></UiSelect></td>
+ <td><UiInput className="number" type="number" min={0} value={row.count||""} placeholder="0" onChange={e=>updateAsset(i,{count:Number(e.target.value)})}/></td>
+ <td><UiInput value={row.rule} placeholder="例如:CH-##" onChange={e=>updateAsset(i,{rule:e.target.value})}/></td>
+ <td><UiButton className="trash" onClick={()=>removeAssetRow(i)}>⌫</UiButton></td>
</tr>)}</tbody>
- </table>
+ </UiTable>
<div style={{marginTop:8}}><Button secondary onClick={()=>go("deviceTemplates")}>查看全部设备模版</Button></div>
</Panel>
<Panel title="资产层级预览"><div className="asset-tree-preview">
<Panel title="AI 智能补全">
<p className="hint">基于方案名称 / 领域 / 描述 / 补充描述,由 AI 分析并补充设备类型、数量与命名规则(位于您已填项下方)。</p>
<div style={{display:"flex",gap:"8px",alignItems:"center"}}>
- <button className="button" disabled={assetsAi.completing} onClick={runAssetsComplete}>{assetsAi.completing?"补全中…":"开始补全"}</button>
- <button className="text-button" onClick={()=>viewCompleteAssetsPrompt(id,m.name,domainName,m.description,m.supplement||"")}>查看提示词</button>
+ <UiButton className="button" disabled={assetsAi.completing} onClick={runAssetsComplete}>{assetsAi.completing?"补全中…":"开始补全"}</UiButton>
+ <UiButton className="text-button" onClick={()=>viewCompleteAssetsPrompt(id,m.name,domainName,m.description,m.supplement||"")}>查看提示词</UiButton>
</div>
{(assetsAi.completing||assetsAi.log)&&<div className="modal-console"><div className="modal-console-head">AI 智能补全</div><pre className="modal-console-body" ref={assetsAi.ref}>{assetsAi.log||"(等待 AI 分析…)"}</pre></div>}
</Panel>
const [tab,setTab]=useState<"mu"|"cube"|"menu">("cube");
const genLogRef=useRef<HTMLPreElement>(null);
useEffect(()=>{ if(genLogRef.current) genLogRef.current.scrollTop=genLogRef.current.scrollHeight; },[genLog]);
- return <Panel><div className="tabs"><button className={tab==="cube"?"active":""} onClick={()=>setTab("cube")}>领域扩展页面 <i>{pages.length}</i></button><button className={tab==="mu"?"active":""} onClick={()=>setTab("mu")}>MU 标准页面 <i>{effSelected.length}</i></button><button className={tab==="menu"?"active":""} onClick={()=>setTab("menu")}>菜单结构</button></div>
- {tab==="mu" && <div className="mu-tree">{grouped.map(g=><div key={g.parentMenu} className="mu-group"><b>{g.parentMenu}</b><div className="mu-items">{g.items.map((item:any)=><label key={item.key} className={item.disabled?"disabled":""}><input type="checkbox" disabled={item.disabled} checked={effSelected.includes(item.key)} onChange={()=>onToggleMu(item.key)}/><span>{item.label}</span></label>)}</div></div>)}</div>}
+ return <Panel><UiTabbedRegion navigation={<div className="tabs"><UiButton className={tab==="cube"?"active":""} onClick={()=>setTab("cube")}>领域扩展页面 <i>{pages.length}</i></UiButton><UiButton className={tab==="mu"?"active":""} onClick={()=>setTab("mu")}>MU 标准页面 <i>{effSelected.length}</i></UiButton><UiButton className={tab==="menu"?"active":""} onClick={()=>setTab("menu")}>菜单结构</UiButton></div>} content={<>
+ {tab==="mu" && <div className="mu-tree">{grouped.map(g=><div key={g.parentMenu} className="mu-group"><b>{g.parentMenu}</b><div className="mu-items">{g.items.map((item:any)=><label key={item.key} className={item.disabled?"disabled":""}><UiInput type="checkbox" disabled={item.disabled} checked={effSelected.includes(item.key)} onChange={()=>onToggleMu(item.key)}/><span>{item.label}</span></label>)}</div></div>)}</div>}
{tab==="cube" && <div className="two-column">
<div>
<div className="style-picker">
<h4>领域方案页面样式 <small>方案级 · 本方案全部 Cube 页面共用</small></h4>
<p className="hint">选择一个样式库方案作为本方案所有 Cube 页面的视觉风格(生成时参考其 JSON 主题参数)。</p>
<div className="style-pick-list">
- <button className={"style-pick"+(!styleId?" active":"")} onClick={()=>onSelectStyle(null)}>
+ <UiButton className={"style-pick"+(!styleId?" active":"")} onClick={()=>onSelectStyle(null)}>
<div className="style-pick-thumb none">无</div><span>不使用样式</span>
- </button>
- {pageStyles.map(s=><button key={s.id} className={"style-pick"+(styleId===s.id?" active":"")} onClick={()=>onSelectStyle(s.id)}>
+ </UiButton>
+ {pageStyles.map(s=><UiButton key={s.id} className={"style-pick"+(styleId===s.id?" active":"")} onClick={()=>onSelectStyle(s.id)}>
<div className="style-pick-thumb"><img src={`/api/db/page-styles/${s.id}/image?w=360`} alt={s.name} loading="lazy"/></div>
<span>{s.name}</span>
- </button>)}
+ </UiButton>)}
</div>
</div>
- <div className="cube-editor"><table className="data-table editable cube-editor-table"><thead><tr><th>页面名称 <small>最多6字</small></th><th>页面描述</th><th>来源</th><th>Json文件</th><th>操作</th></tr></thead><tbody>{pages.map((p,i)=><tr key={p.id}>
- <td><input value={p.name} maxLength={6} placeholder="例如:运行总览" onChange={e=>updatePageField(i,{name:e.target.value})}/></td>
- <td><input value={p.description} placeholder="描述该页面要展示的内容与指标" onChange={e=>updatePageField(i,{description:e.target.value})}/></td>
+ <div className="cube-editor"><UiTable className="data-table editable cube-editor-table"><thead><tr><th>页面名称 <small>最多6字</small></th><th>页面描述</th><th>来源</th><th>Json文件</th><th>操作</th></tr></thead><tbody>{pages.map((p,i)=><tr key={p.id}>
+ <td><UiInput value={p.name} maxLength={6} placeholder="例如:运行总览" onChange={e=>updatePageField(i,{name:e.target.value})}/></td>
+ <td><UiInput value={p.description} placeholder="描述该页面要展示的内容与指标" onChange={e=>updatePageField(i,{description:e.target.value})}/></td>
<td>{sourceBadge(p.source)}</td>
- <td>{p.gen_filename? <button className="link" onClick={()=>onDownloadPage(p)}>下载 {p.gen_filename}</button> : <span className="muted">未生成</span>}</td>
- <td className="cube-actions"><button onClick={()=>onPreviewPage(p)}>预览</button><button className="btn-ai" disabled={genIds.has(p.id)} onClick={()=>onGeneratePage(p)}>{genIds.has(p.id)?"AI生成中":"AI生成"}</button><button onClick={()=>onSavePageMeta(p)}>保存</button><button className="trash" onClick={()=>onDeletePage(p)}>删除</button></td>
- </tr>)}</tbody></table></div>
+ <td>{p.gen_filename? <UiButton className="link" onClick={()=>onDownloadPage(p)}>下载 {p.gen_filename}</UiButton> : <span className="muted">未生成</span>}</td>
+ <td className="cube-actions"><UiActions className="station-inline-actions"><UiButton onClick={()=>onPreviewPage(p)}>预览</UiButton><UiButton className="btn-ai" disabled={genIds.has(p.id)} onClick={()=>onGeneratePage(p)}>{genIds.has(p.id)?"AI生成中":"AI生成"}</UiButton><UiButton onClick={()=>onSavePageMeta(p)}>保存</UiButton><UiButton className="trash" onClick={()=>onDeletePage(p)}>删除</UiButton></UiActions></td>
+ </tr>)}</tbody></UiTable></div>
<div style={{display:"flex",gap:8,marginTop:8,alignItems:"center",flexWrap:"wrap"}}>
<Button onClick={onAddPage}>+ 添加 Cube 页面</Button>
<label className="button secondary" style={{cursor:"pointer"}}>↑ 上传页面 Json<input type="file" accept=".json,application/json" hidden onChange={e=>{const f=e.target.files?.[0]; if(f) onUploadPageJson(f); e.target.value="";}}/></label>
- <button className="btn-ai" style={{height:30}} onClick={onGenerateAll} title="对所有未生成的页面排队生成">⚡ AI 统一生成全部({pages.filter(p=>p.status!=="generated").length})</button>
+ <UiButton className="btn-ai" style={{height:30}} onClick={onGenerateAll} title="对所有未生成的页面排队生成">⚡ AI 统一生成全部({pages.filter(p=>p.status!=="generated").length})</UiButton>
</div>
{(genIds.size>0||genLog)&&<div className="modal-console"><div className="modal-console-head">AI 生成页面</div><pre className="modal-console-body" ref={genLogRef}>{genLog||"(等待生成…)"}</pre></div>}
</div>
<Panel title="AI 智能补全">
<p className="hint">基于方案名称 / 领域 / 描述 / 补充描述及资产,由 AI 推断并补充 Cube 扩展页面(追加在已有页面下方)。</p>
<div style={{display:"flex",gap:"8px",alignItems:"center"}}>
- <button className="button" disabled={cubesAi.completing} onClick={onRunCubesComplete}>{cubesAi.completing?"补全中…":"开始补全"}</button>
- <button className="text-button" onClick={onViewCompleteCubesPrompt}>查看提示词</button>
+ <UiButton className="button" disabled={cubesAi.completing} onClick={onRunCubesComplete}>{cubesAi.completing?"补全中…":"开始补全"}</UiButton>
+ <UiButton className="text-button" onClick={onViewCompleteCubesPrompt}>查看提示词</UiButton>
</div>
{(cubesAi.completing||cubesAi.log)&&<div className="modal-console"><div className="modal-console-head">AI 智能补全</div><pre className="modal-console-body" ref={cubesAi.ref}>{cubesAi.log||"(等待 AI 分析…)"}</pre></div>}
</Panel>
{saving&&<Panel><div className="empty-state">正在保存…</div></Panel>}
</div>
- </div>}
+ </div>}</>}/>
{tab==="menu" && <div className="menu-preview">
<p className="hint">默认菜单结构(展示用,无需编辑):MU 标准页面在上,所有 Cube 扩展页面归入「综合监控」一级菜单下。</p>
<div className="menu-tree">
<div className="overview-desc"><span>方案描述</span><p>{m.description||"—"}</p></div>
<div className="overview-desc"><span>补充描述</span><p>{m.supplement||"—"}</p></div>
</div>
- <div className="tabs overview-tabs">
- {tabs.map(t=><button key={t.key} className={tab===t.key?"active":""} onClick={()=>setTab(t.key)}>{t.label}</button>)}
- </div>
+ <UiTabbedRegion navigation={<div className="tabs overview-tabs">
+ {tabs.map(t=><UiButton key={t.key} className={tab===t.key?"active":""} onClick={()=>setTab(t.key)}>{t.label}</UiButton>)}
+ </div>} content={<>
<div className="overview-body">
{tab==="dev" && <div className="dev-list">{devList.length===0? <p className="muted">本方案未选用设备模板。</p> :
devList.map((a:any)=>(
<div key={a.device_template_id} className="dev-row">
- <button className="dev-row-head" onClick={()=>openDev(a.device_template_id as number)}>
+ <UiButton className="dev-row-head" onClick={()=>openDev(a.device_template_id as number)}>
<b>{a.device_name}</b><span className="dev-count">{a.count} 台</span><span className="dev-caret">{devOpen===a.device_template_id?"▾":"▸"}</span>
- </button>
+ </UiButton>
{devOpen===a.device_template_id && <div className="dev-detail">{devLoading? <p className="muted">加载中…</p> :
devDetail? <><p className="dev-desc">{devDetail.description||"(无说明)"}</p>
<div className="dev-cp"><b>采集点({(devDetail.collection_points||[]).length})</b>{(devDetail.collection_points||[]).length===0? <span className="muted"> 无</span> :
))}</div>}
{tab==="asset" && <div className="asset-tree-preview"><div className="tree-root">⌄ <b>{m.name||"未命名方案"}</b></div>{assetGroups.map((row,i)=><div key={i} className="tree-branch"><div className="tree-node">⌄ {row.device_name}<span className="tree-count">{row.count}</span></div>{row.count>0&&<div className="tree-leaves">{formatInstances(row.rule,row.count)}</div>}</div>)}</div>}
{tab==="mu" && <div className="mu-tree">{grouped.length===0? <p className="muted">(未加载 MU 树)</p> :
- grouped.map(g=><div key={g.parentMenu} className="mu-group"><b>{g.parentMenu}</b><div className="mu-items">{g.items.map((item:any)=><label key={item.key} className="disabled"><input type="checkbox" disabled checked={effSelected.includes(item.key)} onChange={()=>{}}/><span>{item.label}</span></label>)}</div></div>)}</div>}
+ grouped.map(g=><div key={g.parentMenu} className="mu-group"><b>{g.parentMenu}</b><div className="mu-items">{g.items.map((item:any)=><label key={item.key} className="disabled"><UiInput type="checkbox" disabled checked={effSelected.includes(item.key)} onChange={()=>{}}/><span>{item.label}</span></label>)}</div></div>)}</div>}
{tab==="cube" && <div className="domain-review-pages">{pages.length===0? <div className="muted">暂无扩展页面,返回「功能与菜单 → 领域扩展页面」生成。</div> :
pages.map(p=><article key={p.id} className="domain-review-page" style={{cursor:"pointer"}} onClick={()=>onPreviewPage&&onPreviewPage(p)}><DomainPageThumb jsonText={p.json_text} emptyLabel="未生成"/><div><Status>{p.status==="generated"?"AI已生成":p.status==="uploaded"?"已上传":"未生成"}{p.source?` · ${p.source==="ai"?"AI":p.source==="upload"?"上传":"手动"}`:""}</Status><h4>{p.name}</h4><span className="review-zoom">⛶ 点击放大</span></div></article>)}</div>}
{tab==="knowledge" && <div className="doc-list">{docs.length===0? <p className="muted">未上传领域知识,返回「领域知识」步骤上传。</p> :
docs.map((d:any,i:number)=><div key={i} className="doc-item"><b>{d.name}</b><span className="muted">{d.type==="link"?"参考站点":(d.mime||"文档")}{(d.size||d.file_size)?` · ${(Number(d.size||d.file_size)/1024).toFixed(1)}KB`:""}</span></div>)}</div>}
{tab==="ops" && <div className="ops-placeholder"><span className="status muted">待开发</span><p>运维流程编辑功能待开发,后续版本将支持在方案中配置标准运维 SOP 与告警处理流程。</p></div>}
- </div>
+ </div></>}/>
</Panel>
</div>;
}
{icon:"☰",label:"默认菜单",desc:"MU + 综合监控 菜单结构"},
{icon:"📄",label:"领域知识",desc:"上传的领域知识文档"},
];
- return <div className="modal-backdrop" onMouseDown={onClose}><div className="modal pkg-modal" onMouseDown={e=>e.stopPropagation()}>
- <header><h2>方案打包</h2><button onClick={onClose}>×</button></header>
+ return <UiOverlay className="modal-backdrop" onMouseDown={onClose}><div className="modal pkg-modal" onMouseDown={e=>e.stopPropagation()}>
+ <header><h2>方案打包</h2><UiButton onClick={onClose}>×</UiButton></header>
<div className="modal-body pkg-body">
<p className="hint">即将把方案「{name||"未命名方案"}」打包为可交付成果,包含以下组成部分:</p>
<div className="pkg-box">
</div>
</div>
<footer><Button secondary onClick={onClose}>取消</Button><Button onClick={()=>{ notify("方案已打包(示意),可交付给实施团队"); onClose(); }}>确认打包下载</Button></footer>
- </div></div>;
+ </div></UiOverlay>;
}
function KnowledgeStep({id,docs,notify,reload,confirm}:{id:number;docs:any[];notify:(m:string)=>void;reload:()=>void;confirm:(msg:string)=>Promise<boolean>}){
<Panel title="配置参考 URL 站点">
<p className="hint">填写一个领域相关的参考网站地址。AI 补全时会由服务端抓取该站点正文并注入提示词(抓取失败则仅保留链接作为参考)。</p>
<div style={{display:"flex",flexDirection:"column",gap:8}}>
- <input placeholder="站点名称,如:XX 行业标准" value={urlName} onChange={e=>setUrlName(e.target.value)}/>
- <input placeholder="https://..." value={urlVal} onChange={e=>setUrlVal(e.target.value)}/>
+ <UiInput placeholder="站点名称,如:XX 行业标准" value={urlName} onChange={e=>setUrlName(e.target.value)}/>
+ <UiInput placeholder="https://..." value={urlVal} onChange={e=>setUrlVal(e.target.value)}/>
<div><Button onClick={addUrl} disabled={busy}>添加站点</Button></div>
</div>
</Panel>
<Panel title={`已沉淀领域知识(${docs.length})`} tools={<Button secondary onClick={reload}>刷新</Button>}>
{docs.length===0? <div className="empty-state">暂无领域知识,上传文档或配置站点后此处展示。</div> :
- <table className="data-table"><thead><tr><th>名称</th><th>类型</th><th>大小</th><th>创建时间</th><th>操作</th></tr></thead>
+ <UiTable className="data-table"><thead><tr><th>名称</th><th>类型</th><th>大小</th><th>创建时间</th><th>操作</th></tr></thead>
<tbody>{docs.map((d:any)=><tr key={d.id}>
<td>{d.name}</td>
- <td><span style={{padding:"2px 8px",borderRadius:4,fontSize:12,background:"#2a3a55",color:"#cfe8ff"}}>{d.doc_type==="link"?"URL 站点":"文件"}</span></td>
+ <td><span style={{padding:"var(--ant-padding-xxs) var(--ant-padding-xs)",borderRadius:"var(--ant-border-radius)",fontSize:"var(--ant-font-size-sm)",background:"var(--ant-color-primary-bg)",color:"var(--ant-color-primary)"}}>{d.doc_type==="link"?"URL 站点":"文件"}</span></td>
<td>{d.size!=null?`${Math.round(Number(d.size)/1024*10)/10} KB`:"—"}</td>
<td>{d.created_at||""}</td>
- <td style={{display:"flex",gap:8}}>{d.doc_type==="file"&&<a className="text-button" href={`/api/db/knowledge-docs/${d.id}/file`} target="_blank" rel="noreferrer">下载</a>}<button className="text-button" onClick={()=>removeDoc(d.id)}>删除</button></td>
- </tr>)}</tbody></table>}
+ <td style={{display:"flex",gap:8}}>{d.doc_type==="file"&&<a className="text-button" href={`/api/db/knowledge-docs/${d.id}/file`} target="_blank" rel="noreferrer">下载</a>}<UiButton className="text-button" onClick={()=>removeDoc(d.id)}>删除</UiButton></td>
+ </tr>)}</tbody></UiTable>}
</Panel>
</div>;
}
</div>
<div className="style-info"><h3>{s.name}</h3><small>{s.created_at||""}</small></div>
<footer>
- <button className="text-button" onClick={()=>openEdit(s)}>✎ 编辑</button>
- <button className="text-button" onClick={()=>remove(s)}>🗑 删除</button>
+ <UiButton className="text-button" onClick={()=>openEdit(s)}>✎ 编辑</UiButton>
+ <UiButton className="text-button" onClick={()=>remove(s)}>🗑 删除</UiButton>
</footer>
</article>)}</div>}
</Panel>
}
function StylePreview({style,onClose}:{style:PageStyle;onClose:()=>void}){
- return <div className="modal-backdrop" onMouseDown={onClose}><div className="modal" style={{width:"auto",maxWidth:"92vw"}} onMouseDown={e=>e.stopPropagation()}>
- <header><h2>{style.name}</h2><button onClick={onClose}>×</button></header>
+ return <UiOverlay className="modal-backdrop" onMouseDown={onClose}><div className="modal" style={{width:"auto",maxWidth:"92vw"}} onMouseDown={e=>e.stopPropagation()}>
+ <header><h2>{style.name}</h2><UiButton onClick={onClose}>×</UiButton></header>
<div className="modal-body"><img className="style-lightbox" src={`/api/db/page-styles/${style.id}/image`} alt={style.name}/></div>
- </div></div>;
+ </div></UiOverlay>;
}
function StyleEditorModal({id,notify,afterChange,onClose}:{id?:number;notify:(m:string)=>void;afterChange:(msg:string)=>Promise<void>;onClose:()=>void}){
try{ await fetchDb(`/page-styles/${id}`, {method:"DELETE"}); await afterChange("样式已删除"); }
catch(e:any){ notify("删除失败: "+e.message); }
};
- return <div className="modal-backdrop" onMouseDown={onClose}>{confirmEl}<div className="modal" style={{width:"640px"}} onMouseDown={e=>e.stopPropagation()}>
- <header><h2>{id?"编辑页面样式":"新增页面样式"}</h2><button onClick={onClose}>×</button></header>
+ return <UiOverlay className="modal-backdrop" onMouseDown={onClose}>{confirmEl}<div className="modal" style={{width:"640px"}} onMouseDown={e=>e.stopPropagation()}>
+ <header><h2>{id?"编辑页面样式":"新增页面样式"}</h2><UiButton onClick={onClose}>×</UiButton></header>
<div className="modal-body">
<div className="modal-note">页面样式包含一份 Cube 仪表盘 JSON(配色与视觉主题)和一张预览图。名称用于在样式库中展示。</div>
- <Field label="样式名称" required><input value={name} onChange={e=>setName(e.target.value)} placeholder="例如:冰川浅蓝"/></Field>
+ <Field label="样式名称" required><UiInput value={name} onChange={e=>setName(e.target.value)} placeholder="例如:冰川浅蓝"/></Field>
<Field label="预览图" required={!id} hint="建议 16:10 比例的 PNG/JPG,将作为缩略图展示"><div className="style-upload-row">
<label className="upload" style={{flex:1,height:120}}>
<b>+</b><span>{image?"已选择新图片":"选择预览图"}</span>
{imageUrl&&<div className="style-upload-prev"><img src={imageUrl} alt="预览"/></div>}
</div></Field>
<Field label="仪表盘 JSON" required hint="可上传 Cube 仪表盘 JSON 文件,或直接粘贴内容(需为合法 JSON)"><div className="style-json-row">
- <textarea value={jsonText} onChange={e=>setJsonText(e.target.value)} placeholder='{"name":"...","grid":[...],"panels":[...]}'/>
+ <UiTextArea value={jsonText} onChange={e=>setJsonText(e.target.value)} placeholder='{"name":"...","grid":[...],"panels":[...]}'/>
<label className="upload" style={{width:120,height:64}}>
<b>↑</b><span>上传 JSON</span>
<input type="file" accept=".json,application/json" hidden onChange={onPickJson}/>
</div></Field>
</div>
<footer>{id&&<Button secondary onClick={remove} title="删除该样式">删除</Button>}<span className="spacer"/><Button secondary onClick={onClose}>取消</Button><Button disabled={saving} onClick={submit}>{saving?"保存中…":"保存"}</Button></footer>
- </div></div>;
+ </div></UiOverlay>;
+}
+
+function friendlyDomainName(name:string){
+ return /数据中心|AIDC/i.test(name||"") ? "AIDC 数据中心" : (name||"其他领域");
}
-function CompanyDomainLibrary({go,notify,onOpen,domainFilter,domainOptions}:{go:(p:PageKey)=>void;notify:(m:string)=>void;onOpen:(id:number)=>void;domainFilter:number|null;domainOptions:{id:number;name:string}[]}){
+function domainScene(domain:string){
+ if(/数据中心|AIDC/i.test(domain)) return {code:"AIDC",tone:"violet",desc:"面向数据中心基础设施的资产、监控、能效与运维能力。",twins:["机房三维总览","冷源系统孪生"],wiki:["数据中心基础设施百科","制冷与供配电设备说明"]};
+ if(/水务|污水|自来水/i.test(domain)) return {code:"WATER",tone:"blue",desc:"覆盖水处理工艺、泵站设备、运行监控与标准运维资料。",twins:["厂区工艺流程孪生","泵房设备场景"],wiki:["水处理工艺百科","泵阀与仪表设备说明"]};
+ if(/新能源|储能|光伏/i.test(domain)) return {code:"ENERGY",tone:"green",desc:"覆盖储能与新能源场站的设备、运行页面和安全管理规则。",twins:["储能电站全景","电池舱运行孪生"],wiki:["储能系统百科","电池与变流器设备说明"]};
+ return {code:"SITE",tone:"gray",desc:"沉淀该领域可复用的资产、页面、知识和菜单配置。",twins:["场站三维总览"],wiki:["领域基础百科","设备与工艺说明"]};
+}
+
+function ImportDomainPackage({doms,notify,onImported}:{doms:any[];notify:(m:string)=>void;onImported:(id:number)=>void}){
+ const [file,setFile]=useState<File|null>(null);
+ const [domainId,setDomainId]=useState(0);
+ const [name,setName]=useState("");
+ const [version,setVersion]=useState("1.0");
+ const [description,setDescription]=useState("");
+ const [busy,setBusy]=useState(false);
+ useEffect(()=>{ if(!domainId&&doms.length) setDomainId(Number(doms[0].id)); },[doms,domainId]);
+ const choose=(picked:File|null)=>{
+ if(!picked)return;
+ if(!/\.zip$/i.test(picked.name)){ notify("请选择 ZIP 格式的领域方案包"); return; }
+ setFile(picked);
+ if(!name)setName(picked.name.replace(/\.zip$/i,"").replace(/[-_]/g," "));
+ };
+ const submit=async()=>{
+ if(!file){notify("请先选择领域方案 ZIP 包");return;}
+ if(!domainId||!name.trim()){notify("请填写方案名称和所属领域");return;}
+ setBusy(true);
+ try{
+ const row=await fetchDb("/domain-templates",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({domain_id:domainId,name:name.trim(),description:description.trim()||`由 ${file.name} 人工导入的领域方案`,status:"available",version:version.trim()||"1.0",creator:CURRENT_USER_EMAIL})}) as any;
+ notify("领域方案导入完成,可继续维护各项能力");
+ onImported(Number(row.id));
+ }catch(e:any){notify("导入失败:"+(e?.message||e));}
+ finally{setBusy(false);}
+ };
+ return <div className="domain-import-page">
+ <Panel title="导入领域方案">
+ <div className="domain-import-layout">
+ <label className={"domain-import-drop"+(file?" selected":"")}>
+ <UploadOutlined/>
+ <b>{file?file.name:"选择领域方案 ZIP 包"}</b>
+ <span>{file?`${(file.size/1024).toFixed(1)} KB · 点击可重新选择`:"将客户现场已有的资产、页面、知识和菜单配置导入 StationBuilder"}</span>
+ <input type="file" accept=".zip,application/zip" hidden onChange={e=>choose(e.target.files?.[0]||null)}/>
+ </label>
+ <div className="domain-import-form">
+ <Field label="方案名称" required><UiInput value={name} onChange={e=>setName(e.target.value)} placeholder="例如:AIDC 标准场站方案"/></Field>
+ <Field label="所属领域" required><UiSelect value={domainId} onChange={e=>setDomainId(Number(e.target.value))}><option value={0}>请选择领域</option>{doms.map((d:any)=><option key={d.id} value={d.id}>{friendlyDomainName(d.name)}</option>)}</UiSelect></Field>
+ <Field label="方案版本"><UiInput value={version} onChange={e=>setVersion(e.target.value)} placeholder="1.0"/></Field>
+ <Field label="导入说明"><UiTextArea rows={4} value={description} onChange={e=>setDescription(e.target.value)} placeholder="说明方案来源、适用场景或需要现场调整的内容"/></Field>
+ </div>
+ </div>
+ <div className="domain-package-check"><b>方案包内容</b><span>资产配置</span><span>仪表板与 SC 页面</span><span>百科与文档</span><span>菜单配置</span></div>
+ <UiActions className="domain-import-actions"><Button disabled={busy||!file} onClick={submit}>{busy?"正在导入…":"开始导入"}</Button></UiActions>
+ </Panel>
+ </div>;
+}
+
+type CapabilityKey="assets"|"pages"|"twins"|"knowledge";
+
+function DomainCapabilityDetail({id,doms,notify,onBack,onEditAssets,onCreateSite}:{id:number;doms:any[];notify:(m:string)=>void;onBack:()=>void;onEditAssets:(domainName:string)=>void;onCreateSite:(templateId:number)=>Promise<void>}){
+ const [detail,setDetail]=useState<any>(null);
+ const [muTree,setMuTree]=useState<MuNode[]>([]);
+ const [loading,setLoading]=useState(true);
+ const [preview,setPreview]=useState<CapabilityKey|null>(null);
+ const [editing,setEditing]=useState(false);
+ const [dashboardEditorOpen,setDashboardEditorOpen]=useState(false);
+ const [dashboardDomain,setDashboardDomain]=useState("");
+ const [dashboardQuery,setDashboardQuery]=useState("");
+ const [packageOpen,setPackageOpen]=useState(false);
+ const [draft,setDraft]=useState<any>(null);
+ const [creatingSite,setCreatingSite]=useState(false);
+ const creatingSiteRef=useRef(false);
+ const createFromTemplate=async()=>{
+ if(creatingSiteRef.current) return;
+ creatingSiteRef.current=true;setCreatingSite(true);
+ try{await onCreateSite(id);}catch(e:any){notify("根据模板创建站点失败:"+(e?.message||e));}
+ finally{creatingSiteRef.current=false;setCreatingSite(false);}
+ };
+ const load=async()=>{
+ setLoading(true);
+ try{
+ const [d,mt]=await Promise.all([fetchDb(`/domain-templates/${id}`) as Promise<any>,fetchDb("/mu-tree") as Promise<MuNode[]>]);
+ setDetail(d);setDraft({domain_id:d.domain_id,name:d.name||"",version:d.version||"1.0",status:d.status||"available",description:d.description||""});setDashboardDomain(d.domain_name||doms.find((domain:any)=>domain.id===d.domain_id)?.name||"");setMuTree(mt||[]);
+ }catch(e:any){notify("加载方案失败:"+(e?.message||e));}
+ finally{setLoading(false);}
+ };
+ useEffect(()=>{load();},[id]);
+ const saveMeta=async()=>{
+ try{await fetchDb(`/domain-templates/${id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(draft)});notify("方案信息已保存");setEditing(false);await load();}
+ catch(e:any){notify("保存失败:"+(e?.message||e));}
+ };
+ if(loading||!detail)return <div className="empty-state">正在加载领域能力…</div>;
+ const rawDomain=detail.domain_name||doms.find((d:any)=>d.id===detail.domain_id)?.name||"";
+ const domainName=friendlyDomainName(rawDomain);
+ const scene=domainScene(rawDomain);
+ const assets=(detail.assets||[]).filter((a:any)=>a.device_template_id||a.device_name);
+ const pages=(detail.pages||[]) as DomainPage[];
+ const extensionPages=Array.from(new Map(pages.filter(page=>{
+ const json=(page.json_text||"").trim();
+ return page.name.trim()!=="新页面"&&json!==""&&json!=="{}"&&json!=="null";
+ }).map(page=>[page.name.trim(),page])).values());
+ const docs=(detail.docs||[]) as any[];
+ const knowledgeItems=[
+ {type:"document" as const,name:`${detail.name||domainName}运行维护手册`,meta:"PDF · 2026-08-18"},
+ {type:"document" as const,name:`${domainName}设备巡检规范`,meta:"DOCX · 2026-08-12"},
+ {type:"document" as const,name:"异常报警与处置规则",meta:"PDF · 2026-07-30"},
+ {type:"document" as const,name:"场站安全操作规程",meta:"PDF · 2026-07-16"},
+ {type:"link" as const,name:`${domainName}行业标准查询`,meta:"外部链接 · 已验证"},
+ {type:"link" as const,name:"设备厂商技术资料",meta:"外部链接 · 已验证"},
+ ];
+ const knowledgeDocuments=knowledgeItems.filter(item=>item.type==="document");
+ const knowledgeLinks=knowledgeItems.filter(item=>item.type==="link");
+ const twinScenes=(scene.twins.length?scene.twins:[`${detail.name||domainName}全景监控`,`${domainName}工艺流程场景`]).map((name:string,index:number)=>({name,status:index===0?"运行中":"已配置"}));
+ const download=(label:string)=>notify(`${label}配置 ZIP 已准备(设计示意)`);
+ const capabilityCards=[
+ {key:"assets" as CapabilityKey,icon:<ApartmentOutlined/>,title:"资产",subtitle:"设备列表与资产组结构",count:`${assets.length} 个类型`},
+ {key:"pages" as CapabilityKey,icon:<BlockOutlined/>,title:"领域扩展仪表板",subtitle:"领域仪表板页面",count:`${extensionPages.length} 个仪表板`},
+ {key:"twins" as CapabilityKey,icon:<AimOutlined/>,title:"数字孪生场景",subtitle:"SC 数字孪生场景画面",count:`${twinScenes.length} 个场景`},
+ {key:"knowledge" as CapabilityKey,icon:<SnippetsOutlined/>,title:"知识",subtitle:"领域文档与参考链接",count:`${knowledgeDocuments.length} 份文档 / ${knowledgeLinks.length} 个链接`},
+ ];
+ const previewTitle=capabilityCards.find(x=>x.key===preview)?.title||"";
+ const visibleDashboards=extensionPages.filter(page=>!dashboardQuery.trim()||`${page.name} ${page.description||""}`.toLowerCase().includes(dashboardQuery.trim().toLowerCase()));
+ if(dashboardEditorOpen)return <div className="domain-dashboard-manager">
+ <UiButton className="domain-capability-back" onClick={()=>setDashboardEditorOpen(false)}>← 返回领域方案</UiButton>
+ <section className="dashboard-manager-panel">
+ <header className="dashboard-manager-toolbar">
+ <label><span>领域分类</span><UiSelect value={dashboardDomain} onChange={event=>setDashboardDomain(event.target.value)}>{doms.map((domain:any)=><option key={domain.id} value={domain.name}>{friendlyDomainName(domain.name)}</option>)}</UiSelect></label>
+ <UiActions className="dashboard-manager-actions"><Button onClick={()=>notify("导入功能将在后续实现")}><UploadOutlined/> 导入</Button><Button onClick={()=>notify("新增功能将在后续实现")}><PlusOutlined/> 新增</Button><BrandSearchInput value={dashboardQuery} onChange={setDashboardQuery} placeholder="请输入仪表板名称"/></UiActions>
+ </header>
+ <div className="dashboard-manager-grid">{visibleDashboards.map((page,index)=><article key={page.id}>
+ <UiButton className="dashboard-card-more" type="button" aria-label={`${page.name}更多操作`}><EllipsisOutlined/></UiButton>
+ <h3>{page.name}</h3><p>更新时间:2026-{String((index%8)+1).padStart(2,"0")}-{String((index%21)+5).padStart(2,"0")} {String(9+(index%8)).padStart(2,"0")}:30:00</p>
+ {page.status!=="generated"&&<Status>未发布</Status>}
+ </article>)}</div>
+ {visibleDashboards.length===0&&<div className="empty-state">当前筛选条件下暂无仪表板</div>}
+ </section>
+ </div>;
+ return <div className="domain-capability-detail">
+ <UiButton className="domain-capability-back" onClick={onBack}>← 返回领域方案</UiButton>
+ <section className={`domain-capability-head ${scene.tone}`}>
+ <div className="domain-capability-code">{scene.code}</div>
+ <div className="domain-capability-copy"><div><DomainTag name={domainName}/><Status>{detail.status==="available"?"可用":"不可用"}</Status><span>版本 {detail.version||"1.0"}</span></div><h2>{detail.name||"未命名领域方案"}</h2><p>{detail.description||scene.desc}</p></div>
+ <UiActions className="domain-capability-actions"><Button secondary onClick={()=>setEditing(true)}><EditOutlined/> 维护方案</Button><Button secondary onClick={()=>setPackageOpen(true)}><DownloadOutlined/> 下载完整方案</Button><Button disabled={creatingSite} onClick={createFromTemplate} title="使用当前领域方案的基本信息、资产与标准页面配置创建草稿站点"><PlusOutlined/> {creatingSite?"正在创建站点…":"根据模板创建领域站点"}</Button></UiActions>
+ </section>
+ <div className="domain-capability-summary"><span><b>{assets.length}</b>设备类型</span><span><b>{extensionPages.length}</b>领域扩展仪表板</span><span><b>{twinScenes.length}</b>数字孪生场景</span><span><b>{knowledgeItems.length}</b>领域知识</span></div>
+ <div className="domain-capability-grid">{capabilityCards.map(card=><section key={card.key} className={`domain-capability-card ${card.key}`}>
+ <header><div className="domain-capability-icon">{card.icon}</div><div><h3>{card.title}</h3><p>{card.subtitle}</p></div><strong>{card.count}</strong></header>
+ {card.key==="assets"?<div className="domain-capability-body capability-asset-content">
+ <div className="capability-section-title"><b>设备模板</b><span>{assets.length} 个</span></div>
+ <div className="device-template-grid">{assets.map((asset:any,index:number)=><div className="device-template-tile" key={`${asset.device_template_id||asset.device_name||"template"}-${index}`} title={asset.device_name||"设备模板"}>
+ <span><BlockOutlined/></span><b>{asset.device_name||"设备模板"}</b>
+ </div>)}</div>
+ {assets.length===0&&<div className="device-template-empty">当前领域暂未关联设备模板</div>}
+ </div>:card.key==="pages"?<div className="domain-capability-body capability-page-content">
+ <div className="capability-section-title"><b>领域扩展仪表板</b><span>{extensionPages.length} 个</span></div>
+ <div className="extension-page-grid">{extensionPages.map(page=><article className="extension-page-tile" key={page.id} title={page.name}>
+ <DomainPageThumb jsonText={page.json_text} emptyLabel="页面缩略图" fit fixedHeight={92}/>
+ <b>{page.name}</b>
+ </article>)}</div>
+ {extensionPages.length===0&&<div className="device-template-empty">当前领域暂未配置扩展仪表板</div>}
+ </div>:card.key==="twins"?<div className="domain-capability-body capability-twin-content">
+ <div className="capability-section-title"><b>SC 数字孪生场景</b><span>{twinScenes.length} 个</span></div>
+ <div className="twin-scene-card-list">{twinScenes.map((item,index)=><article className="twin-scene-card" key={item.name}>
+ <div className={`sc-scene scene-${index}`}><i/><i/><i/><i/><span>SC 实时场景</span></div>
+ <div><b>{item.name}</b><small>{item.status}</small></div>
+ </article>)}</div>
+ </div>:card.key==="knowledge"?<div className="domain-capability-body capability-knowledge-content">
+ <div className="knowledge-group">
+ <div className="capability-section-title"><b>文档</b><span>{knowledgeDocuments.length} 份</span></div>
+ <div className="knowledge-item-list">{knowledgeDocuments.map(item=><div className="knowledge-item" key={item.name}>
+ <span className="knowledge-type document"><SnippetsOutlined/></span><div><b>{item.name}</b><small>{item.meta}</small></div>
+ </div>)}</div>
+ </div>
+ <div className="knowledge-group">
+ <div className="capability-section-title"><b>链接</b><span>{knowledgeLinks.length} 个</span></div>
+ <div className="knowledge-item-list">{knowledgeLinks.map(item=><div className="knowledge-item" key={item.name}>
+ <span className="knowledge-type link"><LinkOutlined/></span><div><b>{item.name}</b><small>{item.meta}</small></div>
+ </div>)}</div>
+ </div>
+ </div>:<div className="domain-capability-body capability-empty-slot" aria-label={`${card.title}内容占位`}/>}
+ <footer><UiButton type="button" className="capability-edit-button" onClick={card.key==="assets"?()=>onEditAssets(rawDomain):card.key==="pages"?()=>setDashboardEditorOpen(true):undefined}><EditOutlined/> 编辑</UiButton><UiButton onClick={()=>setPreview(card.key)}><EyeOutlined/> 预览</UiButton><UiButton onClick={()=>download(card.title)}><DownloadOutlined/> 下载配置 ZIP</UiButton></footer>
+ </section>)}</div>
+
+ {preview&&<UiOverlay className="modal-backdrop" onMouseDown={()=>setPreview(null)}><div className="modal capability-preview-modal" onMouseDown={e=>e.stopPropagation()}>
+ <header><h2>{previewTitle}能力预览</h2><UiButton onClick={()=>setPreview(null)}>×</UiButton></header>
+ <div className="modal-body">
+ {preview==="assets"&&<div className="capability-assets-preview"><section><h3>设备列表</h3><UiTable className="data-table"><thead><tr><th>设备模板</th><th>典型数量</th><th>命名规则</th></tr></thead><tbody>{assets.map((a:any,i:number)=><tr key={i}><td>{a.device_name||"设备模板"}</td><td>{a.count||0}</td><td>{a.rule||"—"}</td></tr>)}</tbody></UiTable>{assets.length===0&&<div className="empty-state">暂无设备配置</div>}</section><section><h3>资产组结构</h3><div className="capability-full-tree"><b>{detail.name}</b>{assets.map((a:any,i:number)=><div key={i}><span>{a.device_name||"设备"}</span><small>{formatInstances(a.rule||"DEV-##",Math.min(Number(a.count||0),4))||`${a.count||0} 个实例`}</small></div>)}</div></section></div>}
+ {preview==="pages"&&<div className="capability-dashboard-grid">{extensionPages.map(p=><article key={p.id}><DomainPageThumb jsonText={p.json_text} emptyLabel="仪表板预览"/><div><b>{p.name}</b><span>{p.description||"场站运行指标与状态展示"}</span></div></article>)}{extensionPages.length===0&&<div className="empty-state">暂无领域扩展仪表板</div>}</div>}
+ {preview==="twins"&&<div className="capability-twin-grid">{twinScenes.map((item,index)=><article key={item.name}><div className={`sc-scene scene-${index}`}><i/><i/><i/><i/><span>SC 实时场景</span></div><div><b>{item.name}</b><span>设备空间关系、运行状态与告警联动</span></div></article>)}</div>}
+ {preview==="knowledge"&&<div className="capability-knowledge-preview"><section><h3>领域百科</h3>{scene.wiki.map((name:string,i:number)=><div key={name}><SnippetsOutlined/><span><b>{name}</b><small>{i===0?"领域概念、工艺流程与管理指标":"常用设备、参数与运行原理"}</small></span></div>)}</section><section><h3>文档资料</h3>{docs.length?docs.map((d:any)=><div key={d.id}><span className="file-type">{d.doc_type==="link"?"URL":"DOC"}</span><span><b>{d.name}</b><small>{d.doc_type==="link"?"参考站点":"维修手册 / 规范 / 规则"}</small></span></div>):<div><span className="file-type">DOC</span><span><b>维修手册与报警处置规则</b><small>方案导入后可继续补充</small></span></div>}</section></div>}
+ </div><footer><Button secondary onClick={()=>setPreview(null)}>关闭</Button><Button onClick={()=>download(previewTitle)}><DownloadOutlined/> 下载配置 ZIP</Button></footer>
+ </div></UiOverlay>}
+
+ {editing&&draft&&<UiOverlay className="modal-backdrop" onMouseDown={()=>setEditing(false)}><div className="modal domain-maintain-modal" onMouseDown={e=>e.stopPropagation()}><header><h2>维护领域方案</h2><UiButton onClick={()=>setEditing(false)}>×</UiButton></header><div className="modal-body"><div className="form-grid"><Field label="方案名称" required><UiInput value={draft.name} onChange={e=>setDraft({...draft,name:e.target.value})}/></Field><Field label="所属领域" required><UiSelect value={draft.domain_id} onChange={e=>setDraft({...draft,domain_id:Number(e.target.value)})}>{doms.map((d:any)=><option key={d.id} value={d.id}>{friendlyDomainName(d.name)}</option>)}</UiSelect></Field><Field label="版本"><UiInput value={draft.version} onChange={e=>setDraft({...draft,version:e.target.value})}/></Field><Field label="状态"><UiSelect value={draft.status} onChange={e=>setDraft({...draft,status:e.target.value})}><option value="available">可用</option><option value="unavailable">不可用</option></UiSelect></Field><Field label="方案说明"><UiTextArea rows={4} value={draft.description} onChange={e=>setDraft({...draft,description:e.target.value})}/></Field></div><div className="domain-maintain-note">资产、页面、知识与菜单均可分别进入预览后维护;本页不再采用分步确认流程。</div></div><footer><Button secondary onClick={()=>setEditing(false)}>取消</Button><Button onClick={saveMeta}>保存</Button></footer></div></UiOverlay>}
+
+ {packageOpen&&<UiOverlay className="modal-backdrop" onMouseDown={()=>setPackageOpen(false)}><div className="modal capability-package-modal" onMouseDown={e=>e.stopPropagation()}><header><h2>下载完整领域方案</h2><UiButton onClick={()=>setPackageOpen(false)}>×</UiButton></header><div className="modal-body"><p>完整方案包将包含以下四类可导入配置:</p><div>{capabilityCards.map(card=><span key={card.key}>{card.icon}<b>{card.title}</b><small>{card.subtitle}</small></span>)}</div></div><footer><Button secondary onClick={()=>setPackageOpen(false)}>取消</Button><Button onClick={()=>{download("完整领域方案");setPackageOpen(false);}}>确认下载 ZIP</Button></footer></div></UiOverlay>}
+ </div>;
+}
+
+function CompanyDomainLibrary({notify,onOpen,domainFilter,domainOptions}:{go:(p:PageKey)=>void;notify:(m:string)=>void;onOpen:(id:number)=>void;domainFilter:number|null;domainOptions:{id:number;name:string}[]}){
+ const [list,setList]=useState<any[]>([]);
+ const [loading,setLoading]=useState(true);
+ const [query,setQuery]=useState("");
+ const [domainSel,setDomainSel]=useState(domainFilter==null?"":String(domainFilter));
+ const load=async()=>{try{setList(await fetchDb("/domain-templates") as any[]);}catch(e:any){notify("加载失败:"+e.message);}finally{setLoading(false);}};
+ useEffect(()=>{load();},[]);
+ useEffect(()=>{setDomainSel(domainFilter==null?"":String(domainFilter));},[domainFilter]);
+ const filtered=useMemo(()=>list.filter(row=>(!domainSel||String(row.domain_id)===domainSel)&&(!query.trim()||`${row.name} ${row.domain_name} ${row.description||""}`.toLowerCase().includes(query.trim().toLowerCase()))),[list,domainSel,query]);
+ const totals=useMemo(()=>({assets:list.reduce((n,x)=>n+Number(x.device_count||0),0),pages:list.reduce((n,x)=>n+Number(x.page_count||0),0),docs:list.reduce((n,x)=>n+Number(x.doc_count||0),0)}),[list]);
+ return <div className="domain-showcase-page">
+ <section className="domain-showcase-intro"><div><span>已沉淀领域</span><b>{list.length}</b></div><p>领域方案用于展示和复用成熟场站能力。每个方案都由资产、页面、知识和菜单四部分组成,可分别查看和下载。</p><div className="domain-showcase-total"><span>设备模板 <b>{totals.assets}</b></span><span>仪表板 <b>{totals.pages}</b></span><span>知识文档 <b>{totals.docs}</b></span></div></section>
+ <div className="domain-showcase-filter"><label><SearchOutlined/><UiInput value={query} onChange={e=>setQuery(e.target.value)} placeholder="搜索领域或方案名称"/></label><UiSelect value={domainSel} onChange={e=>setDomainSel(e.target.value)}><option value="">全部领域</option>{domainOptions.map(d=><option key={d.id} value={d.id}>{friendlyDomainName(d.name)}</option>)}</UiSelect><span>共 {filtered.length} 个方案</span></div>
+ {loading?<div className="empty-state">正在加载领域方案…</div>:<div className="domain-showcase-grid">{filtered.map(row=>{const scene=domainScene(row.domain_name||"");return <article key={row.id} className={`domain-showcase-card ${scene.tone}`} onClick={()=>onOpen(row.id)}>
+ <header><div className="domain-showcase-mark">{scene.code}</div><div><DomainTag name={friendlyDomainName(row.domain_name)}/><Status>{row.status==="available"?"可用":"不可用"}</Status></div></header>
+ <h2>{row.name||"未命名领域方案"}</h2><p>{row.description||scene.desc}</p>
+ <div className="domain-showcase-capabilities"><span><ApartmentOutlined/><b>资产</b><small>{row.device_count||0} 类设备</small></span><span><BlockOutlined/><b>页面</b><small>{row.page_count||0} 仪表板 + SC</small></span><span><SnippetsOutlined/><b>知识</b><small>{row.doc_count||0} 文档 + 百科</small></span><span><BarsOutlined/><b>菜单</b><small>标准结构</small></span></div>
+ <footer><span>版本 {row.version||"1.0"}</span><UiButton onClick={e=>{e.stopPropagation();onOpen(row.id);}}>查看方案 <span>→</span></UiButton></footer>
+ </article>})}</div>}
+ {!loading&&filtered.length===0&&<div className="empty-state">暂无符合条件的领域方案,可通过右上角导入。</div>}
+ </div>;
+}
+
+function LegacyCompanyDomainLibrary({go,notify,onOpen,domainFilter,domainOptions}:{go:(p:PageKey,sid?:number|"new")=>void;notify:(m:string)=>void;onOpen:(id:number)=>void;domainFilter:number|null;domainOptions:{id:number;name:string}[]}){
const [list,setList]=useState<any[]>([]);
const [loading,setLoading]=useState(true);
const [query,setQuery]=useState("");
const filtered=useMemo(()=>list.filter(row=>(!statusSel||(statusSel==="available"?row.status==="available":row.status!=="available"))&&(!domainSel||String(row.domain_id)===domainSel)&&(!query.trim()||`${row.name} ${row.domain_name} ${row.creator}`.toLowerCase().includes(query.trim().toLowerCase()))),[list,statusSel,domainSel,query]);
useEffect(()=>setPageIndex(1),[statusSel,domainSel,query]);
return <>{confirmEl}
- <div className="company-status-tabs" role="tablist" aria-label="领域方案状态筛选">{[
+ <UiTabbedRegion navigation={<div className="company-status-tabs" role="tablist" aria-label="领域方案状态筛选">{[
{key:"",label:"全部",count:counts.all},{key:"available",label:"可用",count:counts.available},{key:"unavailable",label:"不可用",count:counts.unavailable}
- ].map(item=><button key={item.key||"all"} role="tab" aria-selected={statusSel===item.key} className={statusSel===item.key?"active":""} onClick={()=>setStatusSel(item.key)}><span>{item.label}</span><b>{item.count}</b></button>)}</div>
+ ].map(item=><UiButton key={item.key||"all"} role="tab" aria-selected={statusSel===item.key} className={statusSel===item.key?"active":""} onClick={()=>setStatusSel(item.key)}><span>{item.label}</span><b>{item.count}</b></UiButton>)}</div>} content={<>
<Panel className="company-list-panel resource-list-panel">
<div className="filterbar company-filterbar">
- <label className="search-field"><span aria-hidden>⌕</span><input value={query} onChange={e=>setQuery(e.target.value)} placeholder="搜索方案名称、领域或创建人"/></label>
- <select value={domainSel} onChange={e=>setDomainSel(e.target.value)}><option value="">全部领域</option>{domainOptions.map(d=><option key={d.id} value={String(d.id)}>{d.name}</option>)}</select>
- <div className="filter-actions"><Button onClick={()=>notify(`已显示 ${filtered.length} 个领域方案`)}>查询</Button><Button secondary onClick={()=>{setQuery("");setDomainSel("");setStatusSel("");}}>重置</Button></div>
+ <BrandSearchInput value={query} onChange={setQuery} placeholder="搜索方案名称、领域或创建人"/>
+ <UiSelect value={domainSel} onChange={e=>setDomainSel(e.target.value)}><option value="">全部领域</option>{domainOptions.map(d=><option key={d.id} value={String(d.id)}>{d.name}</option>)}</UiSelect>
+ <UiActions className="filter-actions"><Button onClick={()=>notify(`已显示 ${filtered.length} 个领域方案`)}>查询</Button><Button secondary onClick={()=>{setQuery("");setDomainSel("");setStatusSel("");}}>重置</Button></UiActions>
</div>
- {loading?<div className="empty-state">加载中…</div>:<><table className="data-table company-table resource-table domain-resource-table"><thead><tr><th>方案名称</th><th>所属领域</th><th>版本</th><th>设备</th><th>页面</th><th>文档</th><th>状态</th><th>创建人</th><th>从模板创建</th><th>操作</th></tr></thead><tbody>{filtered.slice((pageIndex-1)*pageSize,pageIndex*pageSize).map(row=><tr key={row.id} onClick={()=>onOpen(row.id)}><td><button className="link company-record-link" title={row.name} onClick={e=>{e.stopPropagation();onOpen(row.id);}}><b>{row.name||"未命名方案"}</b></button></td><td>{row.domain_name||"—"}</td><td>{row.version||"—"}</td><td>{row.device_count||0}</td><td>{row.page_count||0}</td><td>{row.doc_count||0}</td><td><Status>{row.status==="available"?"可用":"不可用"}</Status></td><td>{row.creator||"—"}</td><td onClick={e=>e.stopPropagation()}><button className="link" onClick={async(e:React.MouseEvent)=>{e.stopPropagation();try{const s=await fetchDb(`/domain-templates/${row.id}/create-site`,{method:"POST",headers:{"Content-Type":"application/json"}}) as any;go("siteEdit",s.id);}catch(err:any){notify("创建站点失败:"+(err?.message||err));}}}>创建</button></td><td onClick={e=>e.stopPropagation()}><Dropdown trigger={["click"]} menu={{items:[{key:"edit",label:"编辑方案",icon:<EditOutlined/>},{key:"delete",label:"删除",danger:true,icon:<DeleteOutlined/>}],onClick:({key})=>key==="edit"?onOpen(row.id):remove(row)}}><AntButton type="text" aria-label="更多操作" icon={<EllipsisOutlined/>}/></Dropdown></td></tr>)}</tbody></table>{filtered.length===0&&<div className="empty-state">暂无符合条件的领域方案</div>}<div className="pagination"><span>{filtered.length} / 总数</span><button disabled={pageIndex<=1} onClick={()=>setPageIndex(p=>Math.max(1,p-1))}>‹</button><button className="active">{pageIndex}</button><button disabled={pageIndex>=Math.max(1,Math.ceil(filtered.length/pageSize))} onClick={()=>setPageIndex(p=>Math.min(Math.max(1,Math.ceil(filtered.length/pageSize)),p+1))}>›</button><select value={pageSize} onChange={e=>{setPageSize(Number(e.target.value));setPageIndex(1);}}><option value={10}>10 / 页</option><option value={20}>20 / 页</option><option value={50}>50 / 页</option></select></div></>}
- </Panel>
+ {loading?<div className="empty-state">加载中…</div>:<><UiTable className="data-table company-table resource-table domain-resource-table"><thead><tr><th>方案名称</th><th>所属领域</th><th>版本</th><th>设备</th><th>页面</th><th>文档</th><th>状态</th><th>创建人</th><th>从模板创建</th><th>操作</th></tr></thead><tbody>{filtered.slice((pageIndex-1)*pageSize,pageIndex*pageSize).map(row=><tr key={row.id} onClick={()=>onOpen(row.id)}><td><UiButton className="link company-record-link" title={row.name} onClick={e=>{e.stopPropagation();onOpen(row.id);}}><b>{row.name||"未命名方案"}</b></UiButton></td><td>{row.domain_name||"—"}</td><td>{row.version||"—"}</td><td>{row.device_count||0}</td><td>{row.page_count||0}</td><td>{row.doc_count||0}</td><td><Status>{row.status==="available"?"可用":"不可用"}</Status></td><td>{row.creator||"—"}</td><td onClick={e=>e.stopPropagation()}><UiButton className="link" onClick={async(e:React.MouseEvent)=>{e.stopPropagation();try{const s=await fetchDb(`/domain-templates/${row.id}/create-site`,{method:"POST",headers:{"Content-Type":"application/json"}}) as any;go("siteEdit",s.id);}catch(err:any){notify("创建站点失败:"+(err?.message||err));}}}>创建</UiButton></td><td onClick={e=>e.stopPropagation()}><Dropdown trigger={["click"]} menu={{items:[{key:"edit",label:"编辑方案",icon:<EditOutlined/>},{key:"delete",label:"删除",danger:true,icon:<DeleteOutlined/>}],onClick:({key})=>key==="edit"?onOpen(row.id):remove(row)}}><AntButton type="text" aria-label="更多操作" icon={<EllipsisOutlined/>}/></Dropdown></td></tr>)}</tbody></UiTable>{filtered.length===0&&<div className="empty-state">暂无符合条件的领域方案</div>}<BrandPagination current={pageIndex} totalPages={Math.max(1,Math.ceil(filtered.length/pageSize))} pageSize={pageSize} onPageChange={setPageIndex} onPageSizeChange={size=>{setPageSize(size);setPageIndex(1);}}/></>}
+ </Panel></>}/>
</>;
}
-function CompanyDeviceTemplates({notify,onViewCpPrompt,go,siteId}:{notify:(m:string)=>void;onViewCpPrompt:(id:number)=>void;go:(p:PageKey,sid?:number|"new")=>void;siteId:number}){
+function CompanyDeviceTemplates({notify,onViewCpPrompt,go,siteId,domainOptions,initialDomain}:{notify:(m:string)=>void;onViewCpPrompt:(id:number)=>void;go:(p:PageKey,sid?:number|"new")=>void;siteId:number|null;domainOptions:{id:number;name:string}[];initialDomain:string}){
const [list,setList]=useState<any[]>([]);
const [loading,setLoading]=useState(true);
const [openId,setOpenId]=useState<number|"create"|null>(null);
- const [preview,setPreview]=useState<any|null>(null);
const [query,setQuery]=useState("");
+ const [domainFilter,setDomainFilter]=useState(initialDomain||"");
const [pointFilter,setPointFilter]=useState<""|"configured"|"empty">("");
const [pageIndex,setPageIndex]=useState(1);
const [pageSize,setPageSize]=useState(10);
const {confirm,el:confirmEl}=useConfirm();
const load=async()=>{ try{ setList(await fetchDb("/device-templates") as any[]); }catch(e:any){ notify("加载失败:"+e.message); }finally{ setLoading(false); } };
useEffect(()=>{ load(); },[]);
+ useEffect(()=>{ setDomainFilter(initialDomain||""); },[initialDomain]);
// 支持从外部通过 URL 参数 openTemplate 自动打开指定模板编辑页
useEffect(()=>{ const p=new URLSearchParams(window.location.search); const tid=p.get("openTemplate"); if(tid){ setFromSite(true); setOpenId(Number(tid)); window.history.replaceState({}, "", "?page=deviceTemplates"); } },[]);
const pointCount=(row:any)=>(row.collection_points||[]).length;
- const domainNames=(row:any)=>{ try{ return JSON.parse(row.domain_names||"[]") as string[]; }catch{ return []; } };
- const remove=async(row:any)=>{ if(!(await confirm(`确定删除设备模板「${row.name}」?`))) return; try{ await fetchDb(`/device-templates/${row.id}`,{method:"DELETE"}); setPreview(null); await load(); notify("设备模板已删除"); }catch(e:any){ notify("删除失败:"+e.message); } };
+ const domainNames=(row:any):string[]=>{ if(Array.isArray(row.domain_names)) return row.domain_names.map(String); try{ return JSON.parse(row.domain_names||"[]") as string[]; }catch{ return []; } };
+ const remove=async(row:any)=>{ if(!(await confirm(`确定删除设备模板「${row.name}」?`))) return; try{ await fetchDb(`/device-templates/${row.id}`,{method:"DELETE"}); await load(); notify("设备模板已删除"); }catch(e:any){ notify("删除失败:"+e.message); } };
const counts=useMemo(()=>({all:list.length,configured:list.filter(x=>pointCount(x)>0).length,empty:list.filter(x=>pointCount(x)===0).length}),[list]);
- const filtered=useMemo(()=>list.filter(row=>(!pointFilter||(pointFilter==="configured"?pointCount(row)>0:pointCount(row)===0))&&(!query.trim()||`${row.name} ${row.description||""}`.toLowerCase().includes(query.trim().toLowerCase()))),[list,pointFilter,query]);
- useEffect(()=>setPageIndex(1),[pointFilter,query]);
- if(openId==="create") return <><div className="resource-back"><button className="text-button" onClick={()=>{setOpenId(null);if(fromSite)go("siteEdit",siteId);}}>{fromSite?"← 返回站点编辑":"← 返回设备模板列表"}</button></div><DeviceTemplateCreate notify={notify} onDone={async(id:number)=>{await load();setOpenId(id);}} onCancel={()=>setOpenId(null)}/></>;
+ const filtered=useMemo(()=>list.filter(row=>(!pointFilter||(pointFilter==="configured"?pointCount(row)>0:pointCount(row)===0))&&(!domainFilter||domainNames(row).includes(domainFilter))&&(!query.trim()||`${row.name} ${row.description||""}`.toLowerCase().includes(query.trim().toLowerCase()))),[list,pointFilter,domainFilter,query]);
+ useEffect(()=>setPageIndex(1),[pointFilter,domainFilter,query]);
+ if(openId==="create") return <><div className="resource-back"><UiButton className="text-button" onClick={()=>{setOpenId(null);if(fromSite&&siteId!=null)go("siteEdit",siteId);}}>{fromSite?"← 返回站点编辑":"← 返回设备模板列表"}</UiButton></div><DeviceTemplateCreate notify={notify} onDone={async(id:number)=>{await load();setOpenId(id);}} onCancel={()=>setOpenId(null)}/></>;
if(typeof openId==="number"){
const dev=list.find(d=>d.id===openId);
if(!dev) return <div className="empty-state">加载中…</div>;
- return <><div className="resource-back"><button className="text-button" onClick={()=>{setOpenId(null);if(fromSite)go("siteEdit",siteId);}}>{fromSite?"← 返回站点编辑":"← 返回设备模板列表"}</button></div><DeviceTemplateCard dev={dev} notify={notify} onChange={load} onBack={()=>setOpenId(null)} onViewCpPrompt={onViewCpPrompt}/></>;
+ return <><div className="resource-back"><UiButton className="text-button" onClick={()=>{setOpenId(null);if(fromSite&&siteId!=null)go("siteEdit",siteId);}}>{fromSite?"← 返回站点编辑":"← 返回设备模板列表"}</UiButton></div><DeviceTemplateCard dev={dev} notify={notify} onChange={load} onBack={()=>setOpenId(null)} onViewCpPrompt={onViewCpPrompt}/></>;
}
return <>{confirmEl}
- <div className="company-status-tabs" role="tablist" aria-label="设备模板状态筛选">{[
+ <UiTabbedRegion navigation={<div className="company-status-tabs" role="tablist" aria-label="设备模板状态筛选">{[
{key:"",label:"全部",count:counts.all},{key:"configured",label:"已配置采集点",count:counts.configured},{key:"empty",label:"待配置",count:counts.empty}
- ].map(item=><button key={item.key||"all"} role="tab" aria-selected={pointFilter===item.key} className={pointFilter===item.key?"active":""} onClick={()=>setPointFilter(item.key as ""|"configured"|"empty")}><span>{item.label}</span><b>{item.count}</b></button>)}</div>
+ ].map(item=><UiButton key={item.key||"all"} role="tab" aria-selected={pointFilter===item.key} className={pointFilter===item.key?"active":""} onClick={()=>setPointFilter(item.key as ""|"configured"|"empty")}><span>{item.label}</span><b>{item.count}</b></UiButton>)}</div>} content={<>
<Panel className="company-list-panel resource-list-panel">
- <div className="filterbar company-filterbar"><label className="search-field"><span aria-hidden>⌕</span><input value={query} onChange={e=>setQuery(e.target.value)} placeholder="搜索设备名称或说明"/></label><div className="filter-actions"><Button secondary onClick={()=>{setQuery("");setPointFilter("");}}>重置</Button><Button onClick={()=>setOpenId("create")}>+ 新增模板</Button></div></div>
- {loading?<div className="empty-state">加载中…</div>:<><table className="data-table company-table resource-table device-resource-table"><thead><tr><th>设备模板名称</th><th>采集点</th><th>配置状态</th><th>所属领域</th><th>操作</th></tr></thead><tbody>{filtered.slice((pageIndex-1)*pageSize,pageIndex*pageSize).map(row=><tr key={row.id} onClick={()=>setPreview(row)}><td><button className="link company-record-link" title={row.name} onClick={e=>{e.stopPropagation();setPreview(row);}}><b>{row.name||"未命名设备模板"}</b></button></td><td>{pointCount(row)}</td><td><Status>{pointCount(row)>0?"已配置":"待配置"}</Status></td><td className="domain-cell"><div className="domain-tag-list">{domainNames(row).map(n=><DomainTag key={n} name={n}/>)}</div></td><td onClick={e=>e.stopPropagation()}><Dropdown trigger={["click"]} menu={{items:[{key:"edit",label:"编辑模板",icon:<EditOutlined/>},{key:"delete",label:"删除",danger:true,icon:<DeleteOutlined/>}],onClick:({key})=>key==="edit"?setOpenId(row.id):remove(row)}}><AntButton type="text" aria-label="更多操作" icon={<EllipsisOutlined/>}/></Dropdown></td></tr>)}</tbody></table>{filtered.length===0&&<div className="empty-state">暂无符合条件的设备模板</div>}<div className="pagination"><span>{filtered.length} / 总数</span><button disabled={pageIndex<=1} onClick={()=>setPageIndex(p=>Math.max(1,p-1))}>‹</button><button className="active">{pageIndex}</button><button disabled={pageIndex>=Math.max(1,Math.ceil(filtered.length/pageSize))} onClick={()=>setPageIndex(p=>Math.min(Math.max(1,Math.ceil(filtered.length/pageSize)),p+1))}>›</button><select value={pageSize} onChange={e=>{setPageSize(Number(e.target.value));setPageIndex(1);}}><option value={10}>10 / 页</option><option value={20}>20 / 页</option><option value={50}>50 / 页</option></select></div></>}
- </Panel>
- <Drawer title={preview?.name||"设备模板详情"} width={440} open={!!preview} onClose={()=>setPreview(null)} extra={preview&&<Button onClick={()=>{setPreview(null);setOpenId(preview.id);}}>编辑模板</Button>}>
- {preview&&<div className="company-detail resource-detail">{preview.image_path&&<img className="resource-detail-image" src={`/api/db/device-templates/${preview.id}/image`} alt={preview.name}/>}<h3>基础信息</h3><dl><dt>模板名称</dt><dd>{preview.name}</dd><dt>采集点数量</dt><dd>{pointCount(preview)}</dd><dt>配置状态</dt><dd><Status>{pointCount(preview)>0?"已配置":"待配置"}</Status></dd></dl><h3>模板说明</h3><p>{preview.description||"暂无说明"}</p><h3>采集点预览</h3><div className="resource-point-list">{(preview.collection_points||[]).slice(0,8).map((cp:any)=><div key={cp.id||cp.name}><b>{cp.name}</b><span>{cp.unit||cp.value_range||"—"}</span></div>)}{pointCount(preview)===0&&<p>暂无采集点</p>}</div></div>}
- </Drawer>
+ <div className="filterbar company-filterbar"><BrandSearchInput value={query} onChange={setQuery} placeholder="搜索设备名称或说明"/><UiSelect aria-label="领域筛选" value={domainFilter} onChange={e=>setDomainFilter(e.target.value)}><option value="">全部领域</option>{domainOptions.map(domain=><option key={domain.id} value={domain.name}>{domain.name}</option>)}</UiSelect><UiActions className="filter-actions"><Button secondary onClick={()=>{setQuery("");setPointFilter("");setDomainFilter("");}}>重置</Button><Button onClick={()=>setOpenId("create")}>+ 新增模板</Button></UiActions></div>
+ {loading?<div className="empty-state">加载中…</div>:<><UiTable className="data-table company-table resource-table device-resource-table"><thead><tr><th>设备模板名称</th><th>采集点</th><th>配置状态</th><th>所属领域</th><th>操作</th></tr></thead><tbody>{filtered.slice((pageIndex-1)*pageSize,pageIndex*pageSize).map(row=><tr key={row.id} onClick={()=>setOpenId(row.id)}><td><UiButton className="link company-record-link" title={row.name} onClick={e=>{e.stopPropagation();setOpenId(row.id);}}><b>{row.name||"未命名设备模板"}</b></UiButton></td><td>{pointCount(row)}</td><td><Status>{pointCount(row)>0?"已配置":"待配置"}</Status></td><td className="domain-cell"><div className="domain-tag-list">{domainNames(row).map(n=><DomainTag key={n} name={n}/>)}</div></td><td onClick={e=>e.stopPropagation()}><Dropdown trigger={["click"]} menu={{items:[{key:"edit",label:"编辑模板",icon:<EditOutlined/>},{key:"delete",label:"删除",danger:true,icon:<DeleteOutlined/>}],onClick:({key})=>key==="edit"?setOpenId(row.id):remove(row)}}><AntButton type="text" aria-label="更多操作" icon={<EllipsisOutlined/>}/></Dropdown></td></tr>)}</tbody></UiTable>{filtered.length===0&&<div className="empty-state">暂无符合条件的设备模板</div>}<BrandPagination current={pageIndex} totalPages={Math.max(1,Math.ceil(filtered.length/pageSize))} pageSize={pageSize} onPageChange={setPageIndex} onPageSizeChange={size=>{setPageSize(size);setPageIndex(1);}}/></>}
+ </Panel></>}/>
</>;
}
const remove=async(row:PageStyle)=>{ if(!(await confirm(`确定删除页面样式「${row.name}」?此操作不可恢复`))) return; try{ await fetchDb(`/page-styles/${row.id}`,{method:"DELETE"}); await afterChange("页面样式已删除"); }catch(e:any){ notify("删除失败:"+e.message); } };
return <>{confirmEl}
<Panel className="company-list-panel resource-list-panel style-resource-panel">
- <div className="filterbar company-filterbar"><label className="search-field"><span aria-hidden>⌕</span><input value={query} onChange={e=>setQuery(e.target.value)} placeholder="搜索样式名称"/></label><div className="filter-actions"><Button secondary onClick={()=>setQuery("")}>重置</Button><Button onClick={()=>setEditor({open:true})}>+ 新增样式</Button></div></div>
- <table className="data-table company-table resource-table style-resource-table"><thead><tr><th>样式方案名称</th><th>缩略图</th><th>创建时间</th><th>预览</th><th>操作</th></tr></thead><tbody>{filtered.slice((pageIndex-1)*pageSize,pageIndex*pageSize).map(row=><tr key={row.id} onClick={()=>setPreview(row)}><td><button className="link company-record-link" title={row.name} onClick={e=>{e.stopPropagation();setPreview(row);}}><b>{row.name||"未命名页面样式"}</b></button></td><td><img className="style-list-thumbnail" src={`/api/db/page-styles/${row.id}/image?w=120`} alt={`${row.name}缩略图`}/></td><td>{row.created_at||"—"}</td><td><AntButton type="link" icon={<EyeOutlined/>} onClick={e=>{e.stopPropagation();setPreview(row);}}>查看预览</AntButton></td><td onClick={e=>e.stopPropagation()}><Dropdown trigger={["click"]} menu={{items:[{key:"edit",label:"编辑样式",icon:<EditOutlined/>},{key:"delete",label:"删除",danger:true,icon:<DeleteOutlined/>}],onClick:({key})=>key==="edit"?setEditor({open:true,id:row.id}):remove(row)}}><AntButton type="text" aria-label="更多操作" icon={<EllipsisOutlined/>}/></Dropdown></td></tr>)}</tbody></table>{filtered.length===0&&<div className="empty-state">暂无符合条件的页面样式</div>}<div className="pagination"><span>{filtered.length} / 总数</span><button disabled={pageIndex<=1} onClick={()=>setPageIndex(p=>Math.max(1,p-1))}>‹</button><button className="active">{pageIndex}</button><button disabled={pageIndex>=Math.max(1,Math.ceil(filtered.length/pageSize))} onClick={()=>setPageIndex(p=>Math.min(Math.max(1,Math.ceil(filtered.length/pageSize)),p+1))}>›</button><select value={pageSize} onChange={e=>{setPageSize(Number(e.target.value));setPageIndex(1);}}><option value={10}>10 / 页</option><option value={20}>20 / 页</option><option value={50}>50 / 页</option></select></div>
+ <div className="filterbar company-filterbar"><BrandSearchInput value={query} onChange={setQuery} placeholder="搜索样式名称"/><UiActions className="filter-actions"><Button secondary onClick={()=>setQuery("")}>重置</Button><Button onClick={()=>setEditor({open:true})}>+ 新增样式</Button></UiActions></div>
+ <UiTable className="data-table company-table resource-table style-resource-table"><thead><tr><th>样式方案名称</th><th>缩略图</th><th>创建时间</th><th>预览</th><th>操作</th></tr></thead><tbody>{filtered.slice((pageIndex-1)*pageSize,pageIndex*pageSize).map(row=><tr key={row.id} onClick={()=>setPreview(row)}><td><UiButton className="link company-record-link" title={row.name} onClick={e=>{e.stopPropagation();setPreview(row);}}><b>{row.name||"未命名页面样式"}</b></UiButton></td><td><img className="style-list-thumbnail" src={`/api/db/page-styles/${row.id}/image?w=120`} alt={`${row.name}缩略图`}/></td><td>{row.created_at||"—"}</td><td><AntButton type="link" icon={<EyeOutlined/>} onClick={e=>{e.stopPropagation();setPreview(row);}}>查看预览</AntButton></td><td onClick={e=>e.stopPropagation()}><Dropdown trigger={["click"]} menu={{items:[{key:"edit",label:"编辑样式",icon:<EditOutlined/>},{key:"delete",label:"删除",danger:true,icon:<DeleteOutlined/>}],onClick:({key})=>key==="edit"?setEditor({open:true,id:row.id}):remove(row)}}><AntButton type="text" aria-label="更多操作" icon={<EllipsisOutlined/>}/></Dropdown></td></tr>)}</tbody></UiTable>{filtered.length===0&&<div className="empty-state">暂无符合条件的页面样式</div>}<BrandPagination current={pageIndex} totalPages={Math.max(1,Math.ceil(filtered.length/pageSize))} pageSize={pageSize} onPageChange={setPageIndex} onPageSizeChange={size=>{setPageSize(size);setPageIndex(1);}}/>
</Panel>
- <Drawer title={preview?.name||"页面样式预览"} width={560} open={!!preview} onClose={()=>setPreview(null)} extra={preview&&<Button onClick={()=>{setEditor({open:true,id:preview.id});setPreview(null);}}>编辑样式</Button>}>
+ <Drawer title={preview?.name||"页面样式预览"} size={560} open={!!preview} onClose={()=>setPreview(null)} extra={preview&&<Button onClick={()=>{setEditor({open:true,id:preview.id});setPreview(null);}}>编辑样式</Button>}>
{preview&&<div className="style-drawer"><img src={`/api/db/page-styles/${preview.id}/image`} alt={preview.name}/><dl><dt>样式名称</dt><dd>{preview.name}</dd><dt>创建时间</dt><dd>{preview.created_at||"—"}</dd></dl></div>}
</Drawer>
{editor.open&&<StyleEditorModal id={editor.id} notify={notify} afterChange={afterChange} onClose={()=>setEditor({open:false})}/>}
if(!refineCube){ notify("反馈功能未正确初始化"); return; }
refineCube(feedbackCube, feedbackText);
};
- return <div className="modal-backdrop" onMouseDown={close}><div className="modal" onMouseDown={e=>e.stopPropagation()}><header><h2>{title}</h2><button onClick={close}>×</button></header><div className="modal-body">{type==="feedback"&&<><div className="impact-title">系统已识别本次修改范围(仅针对「{feedbackCube?.name}」页面)</div><Field label="修改意见"><textarea autoFocus value={feedbackText} onChange={e=>setFeedbackText(e.target.value)} placeholder="例如:顶部增加设备在线率,将告警列表移到页面底部,趋势图改为最近七天。"/></Field><p className="hint">在上方输入框填写修改意见,提交后会在当前页面已生成的 JSON 基础上按意见修改,并保留完整提示词(含本次修改意见),可随时通过「提示词」按钮查看。</p><ul className="impact-list"><li><b>当前页面 JSON</b><span>将依据修改意见重新生成</span></li><li><b>页面模拟数据</b><span>将同步更新</span></li><li><b>Asset Excel</b><span>不受影响</span></li><li><b>其他 Cube 页面</b><span>不受影响</span></li></ul>{(refining||refineLog)?<div className="modal-console"><div className="modal-console-head">AI 生成工作流</div><pre className="modal-console-body" ref={refineConsoleRef}>{refineLog||"(等待 AI 反馈…)"}</pre></div>:null}</>}{type==="download"&&<div className="download-list">{["Cube 页面 JSON","站点菜单配置","生成及校验报告"].map(x=><label key={x}><input type="checkbox" defaultChecked/><span>{x}</span></label>)}<div><b>station-package.zip</b><span>包含全部生成产物</span></div></div>}{type==="history"&&<div className="history-list">{[["V1","首次生成",new Date().toLocaleString()]].map((x,i)=><div key={x[0]}><i>{x[0]}</i><span><b>{x[1]}</b><small>{x[2]} · 王宇</small></span><Status>当前版本</Status></div>)}</div>}{type==="prompt"&&<><p className="hint">这是发送给 Cube 生成模型的完整组合提示词。在点击「开始生成」前可在此核对站点上下文、页面要求与 Cube 规则是否准确,便于定位生成质量问题。</p><pre className="prompt-view">{promptData?.text||"(暂无提示词)"}</pre></>}{type==="completePrompt"&&<><p className="hint">这是「AI 智能补全」发送给分析模型的完整提示词。它基于站点名称、所属领域与站点基本情况描述,要求模型先输出分析思路,再以「【补充描述】」给出最终要写入「补充要求」的文本。</p><pre className="prompt-view">{promptData?.text||"(暂无提示词)"}</pre></>}</div><footer><Button secondary onClick={close}>关闭</Button>{type==="feedback"&&<Button disabled={refining} onClick={submitFeedback}>{refining?"重新生成中…":"提交并重新生成"}</Button>}{type==="prompt"&&<Button onClick={()=>{ if(promptData?.text){ navigator.clipboard?.writeText(promptData.text).then(()=>notify("提示词已复制到剪贴板")).catch(()=>notify("复制失败,请手动选择")); } }}>复制提示词</Button>}</footer></div></div>}
+ return <UiOverlay className="modal-backdrop" onMouseDown={close}><div className="modal" onMouseDown={e=>e.stopPropagation()}><header><h2>{title}</h2><UiButton onClick={close}>×</UiButton></header><div className="modal-body">{type==="feedback"&&<><div className="impact-title">系统已识别本次修改范围(仅针对「{feedbackCube?.name}」页面)</div><Field label="修改意见"><UiTextArea autoFocus value={feedbackText} onChange={e=>setFeedbackText(e.target.value)} placeholder="例如:顶部增加设备在线率,将告警列表移到页面底部,趋势图改为最近七天。"/></Field><p className="hint">在上方输入框填写修改意见,提交后会在当前页面已生成的 JSON 基础上按意见修改,并保留完整提示词(含本次修改意见),可随时通过「提示词」按钮查看。</p><ul className="impact-list"><li><b>当前页面 JSON</b><span>将依据修改意见重新生成</span></li><li><b>页面模拟数据</b><span>将同步更新</span></li><li><b>Asset Excel</b><span>不受影响</span></li><li><b>其他 Cube 页面</b><span>不受影响</span></li></ul>{(refining||refineLog)?<div className="modal-console"><div className="modal-console-head">AI 生成工作流</div><pre className="modal-console-body" ref={refineConsoleRef}>{refineLog||"(等待 AI 反馈…)"}</pre></div>:null}</>}{type==="download"&&<div className="download-list">{["Cube 页面 JSON","站点菜单配置","生成及校验报告"].map(x=><label key={x}><UiInput type="checkbox" defaultChecked/><span>{x}</span></label>)}<div><b>station-package.zip</b><span>包含全部生成产物</span></div></div>}{type==="history"&&<div className="history-list">{[["V1","首次生成",new Date().toLocaleString()]].map((x,i)=><div key={x[0]}><i>{x[0]}</i><span><b>{x[1]}</b><small>{x[2]} · 王宇</small></span><Status>当前版本</Status></div>)}</div>}{type==="prompt"&&<><p className="hint">这是发送给 Cube 生成模型的完整组合提示词。在点击「开始生成」前可在此核对站点上下文、页面要求与 Cube 规则是否准确,便于定位生成质量问题。</p><pre className="prompt-view">{promptData?.text||"(暂无提示词)"}</pre></>}{type==="completePrompt"&&<><p className="hint">这是「AI 智能补全」发送给分析模型的完整提示词。它基于站点名称、所属领域与站点基本情况描述,要求模型先输出分析思路,再以「【补充描述】」给出最终要写入「补充要求」的文本。</p><pre className="prompt-view">{promptData?.text||"(暂无提示词)"}</pre></>}</div><footer><Button secondary onClick={close}>关闭</Button>{type==="feedback"&&<Button disabled={refining} onClick={submitFeedback}>{refining?"重新生成中…":"提交并重新生成"}</Button>}{type==="prompt"&&<Button onClick={()=>{ if(promptData?.text){ navigator.clipboard?.writeText(promptData.text).then(()=>notify("提示词已复制到剪贴板")).catch(()=>notify("复制失败,请手动选择")); } }}>复制提示词</Button>}</footer></div></UiOverlay>}
--- /dev/null
+export type MenuId = number | string;
+export type MenuEntry = {
+ id: MenuId; parent_id: MenuId | null; label: string; page_type: string | null;
+ ref_id: string | number | null; source_key: string; source_name: string;
+};
+export type MenuLeaf = { key: string; label: string; parentMenu: string; disabled?: boolean };
+export type MenuSource = { key: string; name: string; kind: string; ref: string | number; path: string[] };
+
+export const APPLICATION_COLOR_SCHEMES = [
+ { key: "blue", label: "蓝色", color: "#009fe3" },
+ { key: "green", label: "绿色", color: "#23a77b" },
+ { key: "orange", label: "橙色", color: "#ed8b2c" },
+ { key: "purple", label: "紫色", color: "#8860c3" },
+ { key: "red", label: "红色", color: "#df5c5c" },
+];
+
+export function effectiveStandardPageKeys(site: any, leaves: MenuLeaf[]): string[] {
+ const selected: string[] = site?.mu || [];
+ if (selected.length || site?.page_config?.standard_pages_initialized) return selected;
+ // Match the original standard-page defaults, while respecting an explicit empty selection.
+ return leaves.filter(leaf => !leaf.disabled).map(leaf => leaf.key);
+}
+
+export function selectedMenuSources(site: any, leaves: MenuLeaf[]): MenuSource[] {
+ const twins: string[] = site?.page_config?.twin_ids || [];
+ const cubeIds: number[] = site?.page_config?.dashboard_ids ?? (site?.cubes || []).map((cube: any) => cube.id);
+ const standardKeys = effectiveStandardPageKeys(site, leaves);
+ return [
+ ...twins.map(id => ({ key: `sc:${id}`, name: id === "site-overview" ? "场站全景" : id === "process-scene" ? "工艺与设备场景" : id, kind: "sc", ref: id, path: ["数字孪生"] })),
+ ...(site?.cubes || []).filter((cube: any) => cubeIds.includes(cube.id)).map((cube: any) => ({ key: `cube:${cube.id}`, name: cube.name, kind: "cube", ref: cube.id, path: ["领域监控"] })),
+ ...leaves.filter(leaf => !leaf.disabled && standardKeys.includes(leaf.key)).map(leaf => ({ key: `mu:${leaf.key}`, name: leaf.label, kind: "mu", ref: leaf.key, path: leaf.parentMenu ? leaf.parentMenu.split("/") : [] })),
+ ];
+}
+
+// Source identities survive database ID remapping and menu-name changes.
+// Step 4 controls membership; this step preserves edits for the pages still selected.
+export function buildSiteMenu(site: any, leaves: MenuLeaf[], saved: any[] = site?.menu || []): MenuEntry[] {
+ const result: MenuEntry[] = [];
+ const used = new Set<MenuId>();
+ const groups = new Map<string, MenuEntry>();
+ const findSaved = (key: string, fallback: (item: any) => boolean) =>
+ saved.find(item => !used.has(item.id) && item.source_key === key) || saved.find(item => !used.has(item.id) && !item.source_key && fallback(item));
+ const addGroup = (key: string, name: string, parent: MenuId | null, locked = false) => {
+ if (groups.has(key)) return groups.get(key)!;
+ const aliases = key === "group:sc" ? ["数字孪生", "数字孪生场景"] : key === "group:cube" ? ["领域监控", "领域仪表板"] : [name];
+ const existing = findSaved(key, item => !item.page_type && aliases.includes(item.label));
+ const entry: MenuEntry = { id: existing?.id ?? key, parent_id: parent, label: locked ? name : existing?.label || name, page_type: null, ref_id: null, source_key: key, source_name: name };
+ used.add(entry.id); groups.set(key, entry); result.push(entry); return entry;
+ };
+ const sc = addGroup("group:sc", "数字孪生", null, true);
+ const cube = addGroup("group:cube", "领域监控", null, true);
+ const sources = selectedMenuSources(site, leaves);
+ const dynamic = sources.filter(source => source.kind !== "mu");
+ // Keep customer ordering within the two domain groups, but pin the group positions.
+ dynamic.sort((a, b) => {
+ if (a.kind !== b.kind) return a.kind === "sc" ? -1 : 1;
+ const rank = (source: MenuSource) => { const index = saved.findIndex(item => item.source_key === source.key || (item.page_type === source.kind && String(item.ref_id) === String(source.ref))); return index < 0 ? Number.MAX_SAFE_INTEGER : index; };
+ return rank(a) - rank(b);
+ });
+ for (const source of [...dynamic, ...sources.filter(item => item.kind === "mu")]) {
+ let parent: MenuId | null = source.kind === "sc" ? sc.id : source.kind === "cube" ? cube.id : null;
+ if (source.kind === "mu") {
+ let path = "";
+ for (const segment of source.path) { path += `/${segment}`; parent = addGroup(`group:mu:${path}`, segment, parent).id; }
+ }
+ const existing = findSaved(source.key, item => item.page_type === source.kind && String(item.ref_id) === String(source.ref));
+ const entry: MenuEntry = { id: existing?.id ?? source.key, parent_id: parent, label: existing?.label || source.name, page_type: source.kind, ref_id: source.ref, source_key: source.key, source_name: source.name };
+ used.add(entry.id); result.push(entry);
+ }
+ return orderMenu(result);
+}
+
+export function orderMenu(menu: MenuEntry[]): MenuEntry[] {
+ const result: MenuEntry[] = []; const used = new Set<MenuId>();
+ const walk = (parent: MenuId | null) => { for (const item of menu.filter(item => item.parent_id === parent)) { if (used.has(item.id)) continue; used.add(item.id); result.push(item); walk(item.id); } };
+ walk(null); return result;
+}
+
+export function menuSelectionSignature(site: any, leaves: MenuLeaf[]): string {
+ return JSON.stringify(selectedMenuSources(site, leaves).map(source => [source.key, source.name, source.path]));
+}
--- /dev/null
+/* Station hierarchy and five-step configuration: company-style flat workspaces. */
+.station-application-settings{display:flex;flex-direction:column;gap:16px}
+.station-application-basic,.station-application-menu{background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border)}
+.station-application-basic>header,.station-application-menu>header{display:flex;align-items:center;justify-content:space-between;min-height:52px;padding:12px 20px;border-bottom:1px solid var(--ant-color-border);gap:16px}
+.station-application-settings h2{font-size:var(--ant-font-size);font-weight:600;margin:0;color:var(--ant-color-text-base)}
+.station-application-settings h2 small{font-size:var(--ant-font-size-sm);font-weight:400;color:var(--ant-color-text-secondary);margin-left:12px}
+.station-application-fields{display:grid;grid-template-columns:minmax(240px,1.2fr) minmax(280px,1.6fr) minmax(180px,.75fr);gap:32px;padding:20px;align-items:center}
+.station-application-fields>label,.station-application-icon{display:flex;align-items:center;gap:16px;min-width:0}
+.station-application-fields>label>span,.station-application-icon>span{flex-shrink:0;min-width:56px;white-space:nowrap;color:var(--ant-color-text-secondary)}
+.station-application-fields i:not(.station-application-scheme i),.station-menu-edit-fields>label>span>i{color:var(--ant-color-error);font-style:normal}
+.station-application-name input{width:100%;min-width:0}
+.station-application-settings input:not([type=file]),.station-application-settings select,.station-menu-edit-fields input{font:inherit;height:32px;min-width:0;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:4px 12px;background:var(--ant-color-bg-container)}
+.station-icon-control{display:flex;align-items:center;gap:12px;min-width:0}
+.station-icon-image{flex-shrink:0;width:40px;height:40px;border:1px solid var(--ant-color-border);display:flex;align-items:center;justify-content:center;background:var(--ant-color-bg-layout);color:var(--ant-color-primary);font-size:var(--ant-font-size-heading-3)}
+.station-icon-image img{width:100%;height:100%;object-fit:contain}
+.station-icon-upload{font-size:var(--ant-font-size);color:var(--ant-color-primary);cursor:pointer;white-space:nowrap;flex-shrink:0}
+.station-icon-upload.disabled{opacity:.5;cursor:default}
+.station-icon-file{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:200px}
+.station-icon-control>.link{border:0;background:none;white-space:nowrap;font-size:var(--ant-font-size-sm);padding:0}
+.station-application-scheme>div{display:flex;align-items:center;gap:12px;flex:1;min-width:0}
+.station-application-scheme>div>i{width:20px;height:20px;flex-shrink:0;border:1px solid color-mix(in srgb,var(--ant-color-border) 7%,transparent)}
+.station-application-scheme select{width:100%}
+.station-application-menu-actions{display:flex;align-items:center;gap:8px;white-space:nowrap}
+.station-application-menu-scroll{height:calc(100vh - 515px);min-height:240px;overflow:auto}
+.station-application-menu-table{table-layout:fixed;width:100%;min-width:680px}
+.station-application-menu-table thead{position:sticky;top:0;z-index:1}
+.station-application-menu-table th:first-child{width:40%}.station-application-menu-table th:nth-child(2){width:30%}.station-application-menu-table th:nth-child(3){width:18%}.station-application-menu-table th:last-child{width:120px}
+.station-application-menu-table td{height:48px;padding:8px 16px;font-size:var(--ant-font-size)}.station-application-menu-table th{font-size:var(--ant-font-size);font-weight:600}
+.station-application-menu-table tr.station-menu-group-row td{background:var(--ant-color-bg-layout);color:var(--ant-color-text-base)}
+.station-menu-name{display:flex;align-items:center;gap:8px;min-width:0}
+.station-menu-name>span:not(.station-menu-child-guide){white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+.station-menu-name small{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);flex-shrink:0;font-weight:400}
+.station-menu-disclosure{flex-shrink:0;border:0;background:none;width:16px;padding:0;color:var(--ant-color-text-secondary);cursor:pointer}
+.station-menu-child-guide{width:16px;flex-shrink:0;align-self:stretch;position:relative}
+.station-menu-child-guide:before{content:"";position:absolute;left:3px;top:3px;width:8px;height:9px;border-left:1px solid var(--ant-color-border);border-bottom:1px solid var(--ant-color-border)}
+.station-menu-association{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ant-color-text-secondary)}
+.station-application-settings .station-menu-row-actions .ant-btn{color:var(--ant-color-primary)}.station-application-settings .station-menu-row-actions .ant-btn:disabled{color:var(--ant-color-text-disabled)}
+.station-application-menu-note{padding:12px 20px;display:flex;justify-content:space-between;gap:16px;border-top:1px solid var(--ant-color-border);font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}
+.station-menu-edit-fields{display:grid;gap:24px;padding-top:12px}.station-menu-edit-fields>label{display:grid;grid-template-columns:80px minmax(0,1fr);gap:16px;align-items:center}.station-menu-edit-fields>label>span{text-align:right;color:var(--ant-color-text-secondary)}.station-menu-edit-fields input[readonly]{background:var(--ant-color-bg-layout);color:var(--ant-color-text-secondary)}
+.station-application-preview{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);min-height:calc(100vh - 150px)}
+.station-application-preview>header{display:flex;align-items:center;gap:12px;border-top:3px solid var(--application-color);border-bottom:1px solid var(--ant-color-border);padding:16px;font-size:var(--ant-font-size)}
+.station-application-preview>header img{height:36px;width:36px;object-fit:contain}.station-application-preview>header>.anticon{font-size:var(--ant-font-size-heading-3);color:var(--application-color)}.station-application-preview strong{font-weight:600}
+.station-application-preview nav{padding:8px 0}.station-application-preview nav>button{display:flex;align-items:center;justify-content:space-between;width:100%;border:0;background:var(--ant-color-bg-container);padding:16px 20px;font:inherit;text-align:left;color:var(--ant-color-text-base);cursor:pointer}
+.station-application-preview nav>button.group{background:var(--ant-color-bg-layout);font-size:var(--ant-font-size)}.station-application-preview nav>button.page.active{background:color-mix(in srgb,var(--application-color) 14%,var(--ant-color-bg-container));color:var(--application-color);border-left:3px solid var(--application-color)}
+.station-menu-preview-current{display:flex;flex-direction:column;gap:12px;border-top:1px solid var(--ant-color-border);padding:20px;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.station-menu-preview-current b{font-size:var(--ant-font-size);font-weight:600;color:var(--ant-color-text-base)}
+@media(max-width:1400px){.station-application-fields{gap:16px;grid-template-columns:minmax(210px,1.1fr) minmax(220px,1.2fr) minmax(150px,.75fr)}.station-icon-file{display:none}.station-application-menu-note>span:last-child{display:none}}
+@media(max-width:1050px){.station-application-fields{grid-template-columns:1fr;gap:20px}.station-application-fields>label>span,.station-application-icon>span{width:70px}.station-application-name input{max-width:450px}.station-application-scheme>div{max-width:260px}}
+.station-menu-configuration .station-menu-editor-table th:first-child{width:32%;white-space:nowrap}.station-menu-configuration .station-menu-editor-table th:nth-child(2){width:28%}.station-menu-configuration .station-menu-editor-table th:nth-child(3){width:20%}.station-menu-configuration .station-menu-editor-table th:last-child{width:110px;white-space:nowrap}.station-menu-configuration .station-menu-editor-table th,.station-menu-configuration .station-menu-editor-table td{font-size:var(--ant-font-size)}
+@media(max-width:1400px){.station-menu-configuration .station-menu-live-preview{display:block;grid-column:1/-1;border-left:0;border-top:1px solid var(--ant-color-border);max-height:220px;overflow:auto}.station-menu-configuration .station-menu-live-preview nav{display:flex;flex-wrap:wrap}.station-menu-configuration .station-menu-live-preview nav>div{min-width:150px}}
+.station-page-selection .station-module-grid{max-height:440px;overflow:auto}.station-page-selection .station-module-grid>section>div{grid-template-columns:repeat(2,minmax(0,1fr))}.station-page-selection .station-module-grid>section>div>label{font-size:var(--ant-font-size-sm)}
+.station-hierarchy-workspace .station-hierarchy-table{min-width:1100px;table-layout:fixed}
+.station-hierarchy-workspace .station-hierarchy-table th:first-child,.station-hierarchy-workspace .station-hierarchy-table td:first-child{width:28%;overflow:hidden}
+.station-hierarchy-workspace .station-hierarchy-table th:nth-child(2){width:14%}.station-hierarchy-workspace .station-hierarchy-table th:nth-child(3){width:17%}.station-hierarchy-workspace .station-hierarchy-table th:nth-child(4){width:10%}.station-hierarchy-workspace .station-hierarchy-table th:nth-child(5){width:6%}.station-hierarchy-workspace .station-hierarchy-table th:nth-child(6){width:15%}.station-hierarchy-workspace .station-hierarchy-table th:last-child,.station-hierarchy-workspace .station-hierarchy-table td:last-child{width:130px}
+.station-hierarchy-workspace .station-name-cell>.link{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+.station-hierarchy-workspace,.station-page-selection,.station-menu-configuration{font-size:var(--ant-font-size);color:var(--ant-color-text-base);min-width:0}
+.station-list-panel,.station-template-panel,.station-page-module-panel,.station-menu-panel,.station-menu-app-info{background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border)}
+.station-list-panel{min-height:calc(100vh - 260px);display:flex;flex-direction:column}
+.station-hierarchy-note{display:flex;align-items:center;gap:12px;padding:16px;border-bottom:1px solid var(--ant-color-border);color:var(--ant-color-text-secondary);background:var(--ant-color-bg-layout)}
+.station-hierarchy-note>.anticon{color:var(--ant-color-primary)}.station-hierarchy-note>span:last-child{margin-left:auto;white-space:nowrap}
+.station-list-toolbar{display:flex;gap:12px;align-items:center;padding:16px}.station-list-toolbar>div{margin-left:auto;display:flex;gap:8px}
+.station-search{display:flex;align-items:center;gap:8px;height:32px;width:260px;border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);padding:0 12px;background:var(--ant-color-bg-container);color:var(--ant-color-text-secondary)}.station-search input{border:0!important;outline:0;width:100%;height:28px;background:transparent}
+.station-list-toolbar select,.station-basic-fields select,.station-basic-fields input,.station-basic-fields textarea,.station-create-template>select,.station-template-filter select,.station-menu-app-info input,.station-menu-editor-table input,.station-menu-editor-table select{border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);background:var(--ant-color-bg-container);padding:4px 12px;min-height:32px;font:inherit}
+.station-list-toolbar select{width:190px}.station-table-scroll{overflow:auto;flex:1}.station-hierarchy-table th:first-child{width:29%}.station-hierarchy-table th:nth-child(3){width:19%}.station-hierarchy-table td{height:54px}.station-hierarchy-table th{white-space:nowrap}.station-name-cell{display:flex;align-items:center;gap:8px;min-width:210px}.station-name-cell>button:first-child:not(.link){border:0;background:none;width:18px;padding:0}.station-tree-spacer{width:18px}.station-name-cell small{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);border:1px solid var(--ant-color-border);padding:0 4px}.station-template-name{display:block}.station-pending-template{display:block;font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.station-table-actions{display:flex;align-items:center;white-space:nowrap}
+.station-drawer-actions{display:flex;justify-content:flex-end;gap:8px}.station-drawer-note{padding:12px;background:var(--ant-color-bg-layout);color:var(--ant-color-text-secondary);margin:0 0 24px}.station-basic-fields{display:grid;gap:20px}.station-basic-fields>label{display:grid;grid-template-columns:100px 1fr;gap:12px;align-items:start}.station-basic-fields>label>span{padding-top:8px;text-align:right}.station-basic-fields i{color:var(--ant-color-error);font-style:normal}.station-basic-fields textarea{resize:vertical}.station-create-template{margin-top:32px;border-top:1px solid var(--ant-color-border);padding-top:16px}.station-create-template h3{font-size:var(--ant-font-size);font-weight:600}.station-template-modes{display:flex;gap:24px;margin-bottom:16px}.station-template-modes input{accent-color:var(--ant-color-primary)}.station-create-template>select{width:100%}.station-selected-template{padding:16px;border:1px solid var(--ant-color-primary-bg);background:var(--ant-color-bg-layout);margin-top:16px}.station-selected-template p,.station-template-footnote{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);line-height:1.7}.station-selected-template span{color:var(--ant-color-text-secondary)}
+.station-config-context{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;padding:12px 16px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);font-size:var(--ant-font-size-sm)}.station-config-context>div{display:flex;align-items:center;gap:12px}.station-context-divider{height:16px;border-left:1px solid var(--ant-color-border)}.station-config-context>div:last-child>span{color:var(--ant-color-text-secondary)}.station-config-context .ant-tag{margin:0}
+.station-template-panel{border-top:3px solid var(--ant-color-primary);margin-bottom:16px}.station-template-panel>header,.station-page-module-panel>header,.station-menu-panel>header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:20px 20px;border-bottom:1px solid var(--ant-color-border)}.station-template-panel h2,.station-page-module-panel h2,.station-menu-panel h2{font-size:var(--ant-font-size);font-weight:600;margin:0 0 8px}.station-template-panel>header p,.station-page-module-panel>header p,.station-menu-panel>header p{margin:0;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.station-template-panel>header>div:last-child,.station-menu-panel>header>div:last-child{display:flex;gap:8px;white-space:nowrap}
+.station-template-filter{display:flex;align-items:center;gap:12px;padding:16px 20px;color:var(--ant-color-text-secondary)}.station-template-filter>span:last-child{margin-left:auto;font-size:var(--ant-font-size-sm)}.station-template-filter select{width:200px}.station-template-cards{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;padding:0 20px 20px;max-height:300px;overflow:auto}.station-template-cards>button{border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);text-align:left;padding:16px;min-width:0}.station-template-cards>button.selected{border-color:var(--ant-color-primary);background:var(--ant-color-bg-layout)}.station-template-cards>button>div{display:flex;align-items:center;gap:8px;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.station-template-cards .anticon{margin-left:auto;color:var(--ant-color-primary)}.station-template-cards h3{font-size:var(--ant-font-size);font-weight:600;margin:16px 0 8px}.station-template-cards p{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);height:34px;overflow:hidden;margin:0 0 16px}.station-template-cards footer{display:flex;gap:24px;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.station-template-cards footer b{color:var(--ant-color-text-base)}.station-template-empty{padding:28px;text-align:center;color:var(--ant-color-text-secondary)}
+.station-overview-layout .form-section-nav{display:none}.station-overview-layout .company-form-page{display:block}.station-overview-layout .single-column-form{max-width:none}.station-overview-layout .company-form-page>.single-column-form{width:100%}.station-opsflow-blank{background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);min-height:calc(100vh - 342px)}
+.station-unified-assets .asset-editor-workbench{height:calc(100vh - 390px);min-height:430px;grid-template-columns:220px minmax(0,1fr)}.station-unified-assets .asset-tree-canvas{min-height:0;height:calc(100% - 52px);overflow:auto}.station-unified-assets .asset-template-panel{max-height:calc(100vh - 575px);min-height:220px;overflow:auto}.station-unified-assets .asset-editor-toolbar{gap:8px;padding:0 8px}.station-unified-assets .asset-editor-toolbar>div{gap:4px}.station-unified-assets .asset-tree-search{width:160px}.station-unified-assets .asset-node-list{max-height:calc(100vh - 445px);overflow:auto}
+.station-page-selection{display:flex;flex-direction:column;gap:16px}.station-selection-summary{display:flex;gap:28px;border-bottom:1px solid var(--ant-color-border);padding:12px 20px;color:var(--ant-color-text-secondary);background:var(--ant-color-bg-layout)}.station-selection-summary>span:last-child{margin-left:auto;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.station-module-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;padding:20px}.station-module-grid>section{border:1px solid var(--ant-color-border)}.station-module-grid>section>header{display:flex;justify-content:space-between;padding:12px;background:var(--ant-color-bg-layout);border-bottom:1px solid var(--ant-color-border)}.station-module-grid>section>header span{color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm)}.station-module-grid label{display:flex;gap:8px;align-items:center}.station-module-grid>section>div{display:grid;gap:12px;padding:16px;max-height:210px;overflow:auto}.station-page-selection input[type=checkbox]{accent-color:var(--ant-color-primary)}
+.station-dashboard-selection{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px;padding:20px}.station-dashboard-selection article,.station-twin-selection article{border:1px solid var(--ant-color-border);min-width:0}.station-dashboard-image{display:block;width:100%;height:145px;padding:0;border:0;overflow:hidden;background:var(--ant-color-bg-layout);cursor:pointer;text-align:left;font:inherit}.station-dashboard-image:focus-visible{outline:2px solid var(--ant-color-primary);outline-offset:-2px}.station-dashboard-selection article.selected{border-color:var(--ant-color-primary)}.station-dashboard-selection article.selected footer{background:var(--ant-color-bg-layout)}.station-dashboard-image>div{height:100%}.station-dashboard-selection footer,.station-twin-selection footer{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-top:1px solid var(--ant-color-border)}.station-dashboard-selection footer label,.station-twin-selection footer label{display:flex;align-items:center;gap:8px}.station-twin-selection{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px;padding:20px}.station-twin-selection small{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}.station-scene-thumb{height:145px;background:#edf4f6;position:relative;overflow:hidden}.station-scene-thumb>span{position:absolute;right:10px;top:10px;font-size:11px;color:#65909e}.station-scene-thumb>div{position:absolute;left:10%;right:10%;bottom:20px;height:45px;background:#d0e0e5;transform:skew(-20deg);border:1px solid #a5c3ce}.station-scene-thumb i{position:absolute;width:45px;height:43px;bottom:54px;background:#7ba8b6;border:1px solid #608a97;box-shadow:5px -5px #bfd2d9;z-index:1}.station-scene-thumb i:nth-of-type(1){left:18%}.station-scene-thumb i:nth-of-type(2){left:40%;bottom:70px}.station-scene-thumb i:nth-of-type(3){left:64%}.station-scene-thumb i:nth-of-type(4){left:80%;width:22px;height:28px;bottom:78px}
+.station-menu-app-info{padding:16px 20px;margin-bottom:16px;display:flex;align-items:center;gap:20px}.station-menu-app-info label{display:flex;align-items:center;gap:16px}.station-menu-app-info input{width:300px}.station-menu-app-info p{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);margin:0}.station-menu-workbench{display:grid;grid-template-columns:250px minmax(0,1fr) 220px;min-height:calc(100vh - 480px)}.station-menu-workbench>aside{background:var(--ant-color-bg-layout)}.station-menu-source{border-right:1px solid var(--ant-color-border);padding:16px}.station-menu-workbench h3{font-size:var(--ant-font-size);font-weight:600;margin:0 0 16px}.station-menu-workbench h3 small{color:var(--ant-color-text-secondary);float:right}.station-menu-source .station-search{width:100%;margin-bottom:16px}.station-menu-source>div{max-height:calc(100vh - 585px);min-height:230px;overflow:auto}.station-menu-source>div>button{display:flex;align-items:center;justify-content:space-between;width:100%;padding:12px 8px;margin-bottom:8px;border:1px solid var(--ant-color-border);background:var(--ant-color-bg-container);text-align:left}.station-menu-source b{font-size:var(--ant-font-size-sm);font-weight:400}.station-menu-source small{display:block;color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);margin-top:4px}.station-menu-source .anticon{color:var(--ant-color-primary)}.station-menu-source button:disabled{opacity:.5;cursor:default}.station-menu-editor-table{overflow:auto;max-height:calc(100vh - 445px)}.station-menu-editor-table .data-table{min-width:520px}.station-menu-editor-table td{padding:12px}.station-menu-editor-table input,.station-menu-editor-table select{width:100%}.station-menu-row-actions{display:flex;gap:0}.station-menu-row-actions .ant-btn{width:28px}.station-menu-live-preview{border-left:1px solid var(--ant-color-border);padding:16px 0}.station-menu-live-preview h3{padding:0 16px}.station-menu-live-preview>b{display:block;padding:16px;background:var(--ant-color-bg-layout);font-size:var(--ant-font-size);font-weight:600}.station-menu-live-preview nav>div{padding:16px;font-size:var(--ant-font-size)}.station-menu-live-preview nav>.group{background:var(--ant-color-bg-layout)}.station-menu-live-preview nav>.page{color:var(--ant-color-primary)}.station-menu-live-preview .anticon{float:right}.station-menu-empty{padding:60px 16px;color:var(--ant-color-text-secondary);text-align:center}.station-warning{color:var(--ant-color-error);background:var(--ant-color-bg-layout);padding:16px}.station-replace-confirm{max-width:520px}
+@media(max-width:1400px){.station-menu-workbench{grid-template-columns:220px minmax(0,1fr)}.station-menu-live-preview{display:none}.station-hierarchy-note>span:last-child{display:none}.station-module-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.station-dashboard-selection,.station-twin-selection{grid-template-columns:repeat(3,minmax(0,1fr))}.station-unified-assets .asset-tree-search{width:120px}.station-template-cards{grid-template-columns:repeat(2,minmax(0,1fr))}}
--- /dev/null
+"use client";
+
+import { UiButton, UiInput, UiSelect, UiTable, UiTabbedRegion, UiActions, UiOverlay } from "./ui-system";
+import BrandSearchInput from "../src/components/search-input";
+import BrandPagination from "../src/components/pagination";
+import { Input as UiAntInput } from "antd";
+const UiTextArea = UiAntInput.TextArea;
+
+import { useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from "react";
+import { Button, Drawer, Dropdown, Tag, Tooltip } from "antd";
+import { ApartmentOutlined, ArrowDownOutlined, ArrowUpOutlined, CheckOutlined, DeleteOutlined, EditOutlined, EllipsisOutlined, EyeOutlined, PlusOutlined, SearchOutlined, CaretRightOutlined, CaretDownOutlined } from "@ant-design/icons";
+
+import { APPLICATION_COLOR_SCHEMES, buildSiteMenu, effectiveStandardPageKeys, menuSelectionSignature, orderMenu, selectedMenuSources, type MenuEntry, type MenuId } from "./site-menu-model";
+
+type Template = {id:number;name:string;domain_name:string;domain_id:number;status:string;version:string;description?:string;device_count:number;page_count:number};
+type SaveSite = (values:any,id?:number)=>Promise<any>;
+type SaveRegistration = (save:(()=>Promise<void>)|null)=>void;
+
+async function api(path:string,body?:unknown,method="POST") {
+ const response=await fetch(`/api/db${path}`,body===undefined?undefined:{method,headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});
+ if(!response.ok)throw new Error(await response.text()||`HTTP ${response.status}`);
+ return response.json();
+}
+
+function useTemplates(notify:(message:string)=>void){
+ const [templates,setTemplates]=useState<Template[]>([]);
+ const [loading,setLoading]=useState(true);
+ useEffect(()=>{api("/domain-templates").then(setTemplates).catch(error=>notify("加载模板失败:"+error.message)).finally(()=>setLoading(false));},[]);
+ return {templates,loading};
+}
+
+function isConfigured(site:any){return !!(site.template_applied_at||site.device_count||site.mu_count||site.cube_count);}
+function hasAncestor(sites:any[],id:number,ancestor:number):boolean{const seen=new Set<number>();let current=sites.find(site=>site.id===id);while(current&&!seen.has(current.id)){if(current.id===ancestor)return true;seen.add(current.id);current=sites.find(site=>site.id===current.parent_id);}return false;}
+function sitePaths(sites:any[],id:number|null):string[]{
+ const names:string[]=[];const visited=new Set<number>();let current=sites.find(site=>site.id===id);
+ while(current&&!visited.has(current.id)){visited.add(current.id);names.unshift(current.name||"未命名站点");current=sites.find(site=>site.id===current.parent_id);}
+ return names;
+}
+
+export function SiteConfigurationContext({site,onBack}:{site:any;onBack:()=>void}){
+ if(!site)return null;
+ return <div className="station-config-context"><div><UiButton className="link" onClick={onBack}>← 返回站点管理</UiButton></div><div><span>站点模板</span><b>{site.template_name||"未选择"}</b><Tag color={site.template_applied_at?"success":"default"}>{site.template_applied_at?"已应用":site.template_id?"待应用":(site.assets?.some((asset:any)=>asset.type)||site.cubes?.length||site.mu?.length)?"现有配置":"空白配置"}</Tag></div></div>;
+}
+
+export function StationHierarchyWorkspace({sites,domains,loading,onSave,onConfigure,onDelete,notify}:{sites:any[];domains:{id:number;name:string}[];loading:boolean;onSave:SaveSite;onConfigure:(id:number)=>void;onDelete:(ids:number[])=>Promise<void>;notify:(message:string)=>void}){
+ const {templates}=useTemplates(notify);
+ const [query,setQuery]=useState("");const [domain,setDomain]=useState("");const [status,setStatus]=useState("");
+ const [collapsed,setCollapsed]=useState<Set<number>>(new Set());const [page,setPage]=useState(1);const [pageSize,setPageSize]=useState(10);
+ const [draft,setDraft]=useState<any|null>(null);const [saving,setSaving]=useState(false);
+ const available=templates.filter(template=>template.status==="available");
+ const children=useMemo(()=>{const map=new Map<number,any[]>();const ids=new Set(sites.map(site=>site.id));for(const site of sites){const parent=ids.has(site.parent_id)?site.parent_id:0;if(!map.has(parent))map.set(parent,[]);map.get(parent)!.push(site);}return map;},[sites]);
+ const visible=useMemo(()=>{
+ const included=new Set<number>();
+ for(const site of sites){if((!query||site.name?.toLowerCase().includes(query.trim().toLowerCase()))&&(!domain||site.domain===domain)&&(!status||(status==="configured"?isConfigured(site):!isConfigured(site)))){
+ let current=site;const visited=new Set<number>();while(current&&!visited.has(current.id)){visited.add(current.id);included.add(current.id);current=sites.find(item=>item.id===current.parent_id);}
+ }}
+ const rows:{site:any;depth:number}[]=[];const visited=new Set<number>();
+ const walk=(parent:number,depth:number)=>{for(const site of children.get(parent)||[]){if(!included.has(site.id)||visited.has(site.id))continue;visited.add(site.id);rows.push({site,depth});if(!collapsed.has(site.id))walk(site.id,depth+1);}};walk(0,0);return rows;
+ },[sites,children,collapsed,query,domain,status]);
+ useEffect(()=>setPage(1),[query,domain,status,collapsed]);
+ const openCreate=(parentId:number|null=null)=>{const parent=sites.find(site=>site.id===parentId);setDraft({parent_id:parentId,name:"",domain:parent?.domain||"",description:"",template_id:parent?.template_id||"",mode:"template"});};
+ const openEdit=async(id:number)=>{try{const site=await api(`/sites/${id}`);setDraft({...site,mode:site.template_id?"template":"blank"});}catch(error:any){notify("加载站点信息失败:"+error.message);}};
+ const submit=async(keepOpen=false)=>{
+ if(saving||!draft)return;
+ if(!draft.name.trim()){notify("请填写站点名称");return;}
+ if(!draft.domain){notify("请选择所属领域");return;}
+ if(draft.mode==="template"&&!draft.template_id){notify("请选择站点模板,或切换为空白站点");return;}
+ setSaving(true);
+ try{await onSave({name:draft.name.trim(),domain:draft.domain,description:draft.description,parent_id:draft.parent_id||null,template_id:draft.mode==="template"?Number(draft.template_id):null,hierarchy_only:true},draft.id);notify(draft.id?"站点基本信息已保存":"站点已建立,可继续创建下级站点或稍后配置");if(keepOpen)setDraft({...draft,id:undefined,name:"",description:""});else setDraft(null);}catch(error:any){notify("保存失败:"+error.message);}finally{setSaving(false);}
+ };
+ const draftTemplates=available.filter(template=>!draft?.domain||template.domain_name===draft.domain);
+ const selectedTemplate=available.find(template=>template.id===Number(draft?.template_id));
+ const configuredCount=sites.filter(isConfigured).length;
+ return <div className="station-hierarchy-workspace">
+ <UiTabbedRegion navigation={<div className="company-status-tabs" role="tablist" aria-label="站点配置状态">{[{key:"",label:"全部站点",count:sites.length},{key:"pending",label:"待配置",count:sites.length-configuredCount},{key:"configured",label:"已配置",count:configuredCount}].map(item=><UiButton key={item.key} role="tab" aria-selected={status===item.key} className={status===item.key?"active":""} onClick={()=>setStatus(item.key)}><span>{item.label}</span><b>{item.count}</b></UiButton>)}</div>} content={<>
+ <section className="station-list-panel"><div className="station-hierarchy-note"><ApartmentOutlined/><span>先建立站点层级,再进入各站点配置。新建时可选择模板,暂不配置站点内部内容。</span><span>顶级站点 <b>{(children.get(0)||[]).length}</b> · 子站点 <b>{sites.length-(children.get(0)||[]).length}</b></span></div>
+ <div className="station-list-toolbar"><BrandSearchInput value={query} onChange={setQuery} placeholder="搜索站点名称"/><UiSelect aria-label="站点领域筛选" value={domain} onChange={event=>setDomain(event.target.value)}><option value="">全部领域</option>{domains.map(item=><option key={item.id}>{item.name}</option>)}</UiSelect><div><Button onClick={()=>{setQuery("");setDomain("");setStatus("");}}>重置</Button><Button onClick={()=>setCollapsed(collapsed.size?new Set():new Set(sites.filter(site=>(children.get(site.id)||[]).length).map(site=>site.id)))}>{collapsed.size?"展开全部":"收起全部"}</Button><Button type="primary" icon={<PlusOutlined/>} onClick={()=>openCreate()}>新增站点</Button></div></div>
+ <div className="station-table-scroll"><UiTable className="data-table company-table station-hierarchy-table"><thead><tr><th>站点名称</th><th>所属领域</th><th>站点模板</th><th>配置状态</th><th>子站点</th><th>最近修改</th><th>操作</th></tr></thead><tbody>{visible.slice((page-1)*pageSize,page*pageSize).map(({site,depth})=><tr key={site.id}><td><div className="station-name-cell" style={{paddingLeft:depth*24}}>{(children.get(site.id)||[]).length>0?<UiButton aria-label={`${collapsed.has(site.id)?"展开":"收起"}${site.name}`} onClick={()=>setCollapsed(current=>{const next=new Set(current);next.has(site.id)?next.delete(site.id):next.add(site.id);return next;})}>{collapsed.has(site.id)?<CaretRightOutlined/>:<CaretDownOutlined/>}</UiButton>:<span className="station-tree-spacer"/>}<UiButton className="link" onClick={()=>openEdit(site.id)}>{site.name||"未命名站点"}</UiButton>{depth===0&&<small>顶级</small>}</div></td><td>{site.domain||"—"}</td><td><span className="station-template-name">{site.template_name||"未选择模板"}</span>{site.template_id&&!site.template_applied_at&&<small className="station-pending-template">待应用</small>}</td><td><Tag color={isConfigured(site)?"success":"default"}>{isConfigured(site)?"已配置":"待配置"}</Tag></td><td>{(children.get(site.id)||[]).length}</td><td>{site.updated_at||"—"}</td><td><div className="station-table-actions"><Button type="link" onClick={()=>onConfigure(site.id)}>配置站点</Button><Dropdown trigger={["click"]} menu={{items:[{key:"basic",label:"编辑基本信息",icon:<EditOutlined/>},{key:"child",label:"新增子站点",icon:<PlusOutlined/>},{key:"delete",label:"删除站点",danger:true,icon:<DeleteOutlined/>}],onClick:({key})=>key==="basic"?openEdit(site.id):key==="child"?openCreate(site.id):onDelete([site.id])}}><Button type="text" icon={<EllipsisOutlined/>} aria-label={`${site.name}更多操作`}/></Dropdown></div></td></tr>)}</tbody></UiTable></div>
+ {(loading||!visible.length)&&<div className="empty-state">{loading?"加载中…":"暂无符合条件的站点,可点击“新增站点”建立层级"}</div>}
+ <BrandPagination current={page} totalPages={Math.max(1,Math.ceil(visible.length/pageSize))} pageSize={pageSize} onPageChange={setPage} onPageSizeChange={size=>{setPageSize(size);setPage(1);}}/>
+ </section></>}/>
+ <Drawer className="station-info-drawer" size={720} title={draft?.id?"编辑站点基本信息":"新增站点"} open={!!draft} onClose={()=>!saving&&setDraft(null)} footer={<UiActions className="station-drawer-actions"><Button disabled={saving} onClick={()=>setDraft(null)}>取消</Button>{!draft?.id&&<Button disabled={saving} onClick={()=>submit(true)}>创建并继续新增</Button>}<Button type="primary" loading={saving} onClick={()=>submit()}>{draft?.id?"保存":"创建站点"}</Button></UiActions>}>
+ {draft&&<><p className="station-drawer-note">此处只建立站点名称、基本属性和层级,内部配置稍后在“配置站点”中完成。</p><div className="station-basic-fields"><label><span>上级站点</span><UiSelect aria-label="上级站点" value={draft.parent_id||""} onChange={event=>setDraft({...draft,parent_id:event.target.value?Number(event.target.value):null})}><option value="">无,作为顶级站点</option>{sites.filter(site=>!draft.id||!hasAncestor(sites,site.id,draft.id)).map(site=><option key={site.id} value={site.id}>{sitePaths(sites,site.id).join(" / ")}</option>)}</UiSelect></label><label><span><i>*</i> 站点名称</span><UiInput aria-label="站点名称" value={draft.name} maxLength={20} placeholder="例如:北京市自来水厂 / 朝阳区 / 某乡镇站点" onChange={event=>setDraft({...draft,name:event.target.value})}/></label><label><span><i>*</i> 所属领域</span><UiSelect aria-label="站点所属领域" value={draft.domain} onChange={event=>setDraft({...draft,domain:event.target.value,template_id:""})}><option value="">请选择所属领域</option>{domains.map(item=><option key={item.id}>{item.name}</option>)}</UiSelect></label><label><span>基本情况</span><UiTextArea aria-label="站点基本情况" rows={3} value={draft.description} placeholder="填写站点用途、管理范围或位置等基本属性" onChange={event=>setDraft({...draft,description:event.target.value})}/></label></div>
+ <section className="station-create-template"><h3>站点模板</h3><div className="station-template-modes"><label><UiInput type="radio" checked={draft.mode==="template"} disabled={!!draft.template_applied_at} onChange={()=>setDraft({...draft,mode:"template"})}/> 从模板建立站点</label><label><UiInput type="radio" checked={draft.mode==="blank"} disabled={!!draft.template_applied_at} onChange={()=>setDraft({...draft,mode:"blank"})}/> 建立空白站点</label></div>{draft.mode==="template"&&<><UiSelect aria-label="选择站点模板" value={draft.template_id} disabled={!!draft.template_applied_at} onChange={event=>setDraft({...draft,template_id:event.target.value})}><option value="">请选择领域站点模板</option>{draftTemplates.map(template=><option key={template.id} value={template.id}>{template.name} · {template.version}</option>)}</UiSelect>{selectedTemplate&&<div className="station-selected-template"><b>{selectedTemplate.name}</b><p>{selectedTemplate.description||"使用该领域已积累的资产、页面与菜单配置"}</p><span>{selectedTemplate.device_count||0} 类设备模板 · {selectedTemplate.page_count||0} 个领域页面</span></div>}<p className="station-template-footnote">{draft.template_applied_at?"模板已应用;如需更换,请进入站点配置。":"这里只记录模板选择,进入站点配置后再应用,不影响继续建立子站点。"}</p></>}</section>
+ </>}
+ </Drawer>
+ </div>;
+}
+
+export function SiteTemplatePanel({site,onApply,onNext,notify}:{site:any;onApply:(templateId:number,replace:boolean)=>Promise<void>;onNext:()=>void;notify:(message:string)=>void}){
+ const {templates,loading}=useTemplates(notify);const [domain,setDomain]=useState(site?.domain||"");const [selected,setSelected]=useState<number|null>(site?.template_id||null);const [applying,setApplying]=useState(false);const [preview,setPreview]=useState<any|null>(null);const [replacementPending,setReplacementPending]=useState(false);
+ const available=templates.filter(template=>template.status==="available");const visible=available.filter(template=>!domain||template.domain_name===domain);const selectedTemplate=available.find(template=>template.id===selected);
+ useEffect(()=>{if(!loading&&domain&&!available.some(template=>template.domain_name===domain))setDomain("");},[loading]);
+ const hasConfiguration=!!(site?.template_applied_at||site?.assets?.some((asset:any)=>asset.type)||site?.cubes?.length||site?.mu?.length||site?.menu?.length);
+ const apply=async(replace=false)=>{if(!selected||applying)return;if(hasConfiguration&&!replace){setReplacementPending(true);return;}setApplying(true);try{await onApply(selected,replace);setReplacementPending(false);}catch(error:any){notify("应用模板失败:"+error.message);}finally{setApplying(false);}};
+ const showPreview=async()=>{if(!selected)return;try{setPreview(await api(`/domain-templates/${selected}`));}catch(error:any){notify("预览模板失败:"+error.message);}};
+ return <section className="station-template-panel"><header><div><h2>根据领域模板配置站点</h2><p>{site?.template_id&&!site.template_applied_at?"建立站点时已选择模板,点击应用后再调整资产、页面和菜单。":"优先复用领域模板,在已有配置基础上按现场需要调整。"}</p></div><div><Button icon={<EyeOutlined/>} disabled={!selected||applying} onClick={showPreview}>预览模板</Button>{site?.template_applied_at&&selected===site.template_id?<Button type="primary" onClick={onNext}>继续配置资产</Button>:<Button type="primary" loading={applying} disabled={!selected} onClick={()=>apply()}>应用模板并开始配置</Button>}</div></header>
+ <div className="station-template-filter"><span>所属领域</span><UiSelect aria-label="模板所属领域筛选" value={domain} onChange={event=>setDomain(event.target.value)}><option value="">全部领域</option>{Array.from(new Set(available.map(template=>template.domain_name))).map(name=><option key={name}>{name}</option>)}</UiSelect><span>{site?.template_applied_at?`当前已应用:${site.template_name}`:site?.template_id?"当前模板:已选择,待应用":"也可不应用模板,直接进行空白配置"}</span></div>
+ <div className="station-template-cards">{visible.map(template=><UiButton key={template.id} className={selected===template.id?"selected":""} onClick={()=>setSelected(template.id)}><div><Tag>{template.domain_name}</Tag><span>版本 {template.version}</span>{selected===template.id&&<CheckOutlined/>}</div><h3>{template.name}</h3><p>{template.description||"资产、页面与菜单的领域标准配置"}</p><footer><span><b>{template.device_count||0}</b> 类设备模板</span><span><b>{template.page_count||0}</b> 个领域页面</span></footer></UiButton>)}</div>
+ {(loading||!visible.length)&&<div className="station-template-empty">{loading?"正在加载模板…":"该领域暂无可用模板,可选择其他领域模板,或直接进行空白配置。"}</div>}
+ <Drawer className="station-info-drawer" size={680} title="站点模板预览" open={!!preview} onClose={()=>setPreview(null)}>{preview&&<div className="station-template-preview"><Tag>{preview.domain_name}</Tag><h2>{preview.name}</h2><p>{preview.description}</p><h3>设备模板</h3><div>{(preview.assets||[]).map((asset:any)=><Tag key={asset.id}>{asset.device_name} · {asset.count} 个</Tag>)}</div><h3>领域页面</h3><ul>{(preview.pages||[]).filter((page:any)=>page.name!=="新页面").map((page:any)=><li key={page.id}>{page.name}</li>)}</ul><h3>标准功能页面</h3><p>{(preview.mu||[]).length} 个</p><h3>菜单配置</h3><p>{(preview.menu||[]).length} 项</p></div>}</Drawer>
+ {replacementPending&&<UiOverlay className="modal-backdrop"><div className="modal station-replace-confirm"><header><h2>确认更换站点模板</h2><UiButton disabled={applying} onClick={()=>setReplacementPending(false)}>×</UiButton></header><div className="modal-body"><p>将使用“{selectedTemplate?.name}”重新初始化当前站点的资产、标准页面、领域仪表板及菜单。</p><p className="station-warning">已有内部配置将被替换。站点名称、基本属性、上下级关系和其他站点不变。</p></div><footer><Button disabled={applying} onClick={()=>setReplacementPending(false)}>取消</Button><Button type="primary" loading={applying} onClick={()=>apply(true)}>确认应用模板</Button></footer></div></UiOverlay>}
+ </section>;
+}
+
+type PageLeaf={key:string;label:string;parentMenu:string;disabled?:boolean};
+export function SitePageSelection({site,leaves,onMuChange,onPageConfigChange,onSiteChange,renderDashboard,renderDomainDashboard,notify,registerSave}:{site:any;leaves:PageLeaf[];onMuChange:(keys:string[])=>Promise<void>;onPageConfigChange:(config:any)=>Promise<void>;onSiteChange:(site:any)=>void;renderDashboard:(cube:any,large:boolean)=>ReactNode;renderDomainDashboard:(page:any,large:boolean)=>ReactNode;notify:(message:string)=>void;registerSave:SaveRegistration}){
+ const [query,setQuery]=useState("");const [saving,setSaving]=useState(false);const [preview,setPreview]=useState<any|null>(null);
+ const [catalog,setCatalog]=useState<any>({pages:[],local_cubes:[]});const [catalogLoading,setCatalogLoading]=useState(true);const [catalogError,setCatalogError]=useState(false);
+ useEffect(()=>{let active=true;setCatalogLoading(true);setCatalogError(false);api(`/sites/${site.id}/domain-dashboards`).then(data=>{if(active)setCatalog(data);}).catch((error:any)=>{if(active){setCatalogError(true);notify("加载领域仪表板失败:"+error.message);}}).finally(()=>{if(active)setCatalogLoading(false);});return()=>{active=false;};},[site.id,site.template_id,site.domain]);
+ const changeDomainDashboard=async(page:any,selected:boolean)=>{if(saving)return;setSaving(true);try{const result=await api(`/sites/${site.id}/domain-dashboards`,{page_id:page.id,selected},"PUT");setCatalog(result.catalog);onSiteChange(result.site);}catch(error:any){notify("保存领域仪表板选择失败:"+error.message);}finally{setSaving(false);}};
+ const domainCards=(catalog.pages||[]).map((page:any)=>({key:`domain-${page.id}`,name:page.name,page,cube:(site.cubes||[]).find((cube:any)=>cube.id===page.cube_id)}));
+ const dashboardCards=[...domainCards,...(site.cubes||[]).filter((cube:any)=>!domainCards.some((card:any)=>card.cube?.id===cube.id)).map((cube:any)=>({key:`cube-${cube.id}`,name:cube.name,cube,page:null}))];
+ const dashboardImage=(card:any,large=false)=>card.cube?renderDashboard(card.cube,large):renderDomainDashboard(card.page,large);
+ const moduleGroup=(leaf:PageLeaf)=>{const name=leaf.parentMenu||leaf.label;if(/设置|设定|分类|设备类型/.test(name))return "系统与业务设置";if(/维修/.test(name))return "维修管理";if(/保养/.test(name))return "保养管理";if(/点检|异常工单/.test(name))return "点检管理";if(/备件|仓库|库位|出入库/.test(name))return "备件管理";if(/台账/.test(name))return "资产台账";if(/工单中心|表单|流程|定时/.test(name))return "工单与流程";return name;};
+ const grouped=useMemo(()=>{const groups=new Map<string,PageLeaf[]>();for(const leaf of leaves.filter(leaf=>!query||`${leaf.label} ${leaf.parentMenu}`.includes(query))){const group=moduleGroup(leaf);if(!groups.has(group))groups.set(group,[]);groups.get(group)!.push(leaf);}return [...groups];},[leaves,query]);
+ useEffect(()=>{registerSave(saving?async()=>{throw new Error("页面选择正在保存,请稍后重试");}:!site?.mu?.length&&!site?.page_config?.standard_pages_initialized&&leaves.length?async()=>{await onMuChange(effectiveStandardPageKeys(site,leaves));}:null);return()=>registerSave(null);},[saving,site?.page_config?.standard_pages_initialized,(site?.mu||[]).join("|"),leaves.length]);
+ const selectedMu:string[]=effectiveStandardPageKeys(site,leaves);const dashboardIds:number[]=site?.page_config?.dashboard_ids??site?.cubes?.map((cube:any)=>cube.id)??[];const twinIds:string[]=site?.page_config?.twin_ids||[];
+ const twins=[{id:"site-overview",name:"场站全景",description:"SC 数字孪生场景画面"},{id:"process-scene",name:"工艺与设备场景",description:"SC 数字孪生场景画面"}];
+ const changeMu=async(keys:string[])=>{if(saving)return;setSaving(true);try{await onMuChange(keys);}catch(error:any){notify("保存页面选择失败:"+error.message);}finally{setSaving(false);}};
+ const changeConfig=async(config:any)=>{if(saving)return;setSaving(true);try{await onPageConfigChange(config);}catch(error:any){notify("保存页面选择失败:"+error.message);}finally{setSaving(false);}};
+ if(!site)return <div className="empty-state">正在加载站点…</div>;
+ return <div className="station-page-selection"><section className="station-page-module-panel"><header><div><h2>选择站点需要的功能页面</h2><p>选择可用的标准页面、领域仪表板及数字孪生场景;菜单名称、层级和顺序在下一步设置。</p></div><BrandSearchInput value={query} onChange={setQuery} placeholder="搜索功能或页面名称"/></header><div className="station-selection-summary"><span>标准页面 <b>{selectedMu.length}</b></span><span>领域仪表板 <b>{dashboardIds.length}</b></span><span>数字孪生场景 <b>{twinIds.length}</b></span><span>{saving?"保存中…":"选择后自动保存"}</span></div>
+ <div className="station-module-grid">{grouped.map(([name,items])=>{const enabled=items.filter(item=>!item.disabled);const allSelected=enabled.length>0&&enabled.every(item=>selectedMu.includes(item.key));return <section key={name}><header><label><UiInput type="checkbox" disabled={saving} checked={allSelected} onChange={()=>changeMu(allSelected?selectedMu.filter(key=>!enabled.some(item=>item.key===key)):Array.from(new Set([...selectedMu,...enabled.map(item=>item.key)])))}/><b>{name||"标准功能"}</b></label><span>{items.filter(item=>selectedMu.includes(item.key)).length} / {items.length}</span></header><div>{items.map(item=><label key={item.key}><UiInput type="checkbox" disabled={saving||item.disabled} checked={selectedMu.includes(item.key)} onChange={()=>changeMu(selectedMu.includes(item.key)?selectedMu.filter(key=>key!==item.key):[...selectedMu,item.key])}/>{item.label}</label>)}</div></section>;})}</div>
+ </section><section className="station-page-module-panel"><header><div><h2>领域扩展仪表板</h2><p>{catalog.template_name?`${catalog.template_name} · 预览领域页面,勾选后加入当前站点。`:"预览对应领域的仪表板,选择站点需要的页面。"}</p></div><span>{dashboardIds.length} / {dashboardCards.length} 个已选择</span></header><div className="station-dashboard-selection">{dashboardCards.map((card:any)=>{const selected=!!card.cube&&dashboardIds.includes(card.cube.id);return <article key={card.key} className={selected?"selected":""}><UiButton type="button" className="station-dashboard-image" aria-label={`预览${card.name}画面`} onClick={()=>setPreview(card)}>{dashboardImage(card)}</UiButton><footer><label><UiInput type="checkbox" aria-label={`选择仪表板${card.name}`} disabled={saving||catalogLoading} checked={selected} onChange={()=>card.page?changeDomainDashboard(card.page,!selected):changeConfig({...site.page_config,dashboard_ids:selected?dashboardIds.filter(id=>id!==card.cube.id):[...dashboardIds,card.cube.id],twin_ids:twinIds})}/>{card.name}</label><Button type="text" icon={<EyeOutlined/>} aria-label={`预览${card.name}`} onClick={()=>setPreview(card)}/></footer></article>;})}</div>{(catalogLoading||catalogError||!dashboardCards.length)&&<div className="station-template-empty">{catalogLoading?"正在加载领域仪表板…":catalogError?"领域仪表板暂时无法加载,请刷新后重试。":"当前领域暂无可预览的仪表板,可先完善领域模板。"}</div>}</section>
+ <section className="station-page-module-panel"><header><div><h2>数字孪生场景</h2><p>SC 场景选择,当前画面为设计模拟数据。</p></div><span>{twinIds.length} 个已选择</span></header><div className="station-twin-selection">{twins.map(twin=><article key={twin.id}><div className="station-scene-thumb"><span>SC 场景示意</span><i/><i/><i/><i/><div/></div><footer><label><UiInput type="checkbox" disabled={saving} checked={twinIds.includes(twin.id)} onChange={()=>changeConfig({...site.page_config,dashboard_ids:dashboardIds,twin_ids:twinIds.includes(twin.id)?twinIds.filter(id=>id!==twin.id):[...twinIds,twin.id]})}/>{twin.name}</label><small>模拟画面</small></footer></article>)}</div></section>
+ <Drawer className="station-info-drawer station-dashboard-preview-drawer" size="80vw" title={preview?.name||"仪表板预览"} open={!!preview} onClose={()=>setPreview(null)}>{preview&&dashboardImage(preview,true)}</Drawer>
+ </div>;
+}
+
+export function SiteMenuConfiguration({site,leaves,onSiteChange,notify,registerSave}:{site:any;leaves:PageLeaf[];onSiteChange:(site:any)=>void;notify:(message:string)=>void;registerSave:SaveRegistration}){
+ const [menu,setMenu]=useState<MenuEntry[]>(()=>buildSiteMenu(site,leaves));
+ const [appName,setAppName]=useState(site?.app_name||site?.name||"");
+ const [appIcon,setAppIcon]=useState<string|null>(site?.app_icon||null);
+ const [iconFilename,setIconFilename]=useState(site?.app_icon_filename||"");
+ const [scheme,setScheme]=useState(site?.app_color_scheme||"blue");
+ const [saving,setSaving]=useState(false);
+ const savingRef=useRef(false);
+ const [collapsed,setCollapsed]=useState<Set<MenuId>>(new Set());
+ const [previewCollapsed,setPreviewCollapsed]=useState<Set<MenuId>>(new Set());
+ const [previewOpen,setPreviewOpen]=useState(false);
+ const [previewPage,setPreviewPage]=useState<MenuId|null>(null);
+ const [editDraft,setEditDraft]=useState<MenuEntry|null>(null);
+ const [restoreConfirm,setRestoreConfirm]=useState(false);
+ const signature=menuSelectionSignature(site,leaves);
+ const signatureRef=useRef(signature);
+ useEffect(()=>{if(signatureRef.current!==signature){setMenu(current=>buildSiteMenu(site,leaves,current));signatureRef.current=signature;setCollapsed(new Set());}},[signature]);
+ const sources=selectedMenuSources(site,leaves);
+ const color=APPLICATION_COLOR_SCHEMES.find(item=>item.key===scheme)?.color||"#009fe3";
+ const ordered=useMemo(()=>orderMenu(menu),[menu]);
+ const children=(id:MenuId)=>menu.filter(item=>item.parent_id===id);
+ const depth=(item:MenuEntry)=>{let level=0;let parent=item.parent_id;const seen=new Set<MenuId>();while(parent!=null&&!seen.has(parent)){seen.add(parent);level++;parent=menu.find(other=>other.id===parent)?.parent_id??null;}return level;};
+ const visible=(item:MenuEntry,closed:Set<MenuId>)=>{let parent=item.parent_id;while(parent!=null){if(closed.has(parent))return false;parent=menu.find(other=>other.id===parent)?.parent_id??null;}return true;};
+ const toggle=(id:MenuId,preview=false)=>{const setter=preview?setPreviewCollapsed:setCollapsed;setter(current=>{const next=new Set(current);next.has(id)?next.delete(id):next.add(id);return next;});};
+ const movable=(item:MenuEntry)=>item.page_type==="sc"||item.page_type==="cube";
+ const move=(item:MenuEntry,direction:number)=>{
+ if(saving||!movable(item))return;
+ const siblings=children(item.parent_id!);const index=siblings.findIndex(other=>other.id===item.id);const target=siblings[index+direction];if(!target)return;
+ const next=[...menu];const a=next.findIndex(other=>other.id===item.id);const b=next.findIndex(other=>other.id===target.id);[next[a],next[b]]=[next[b],next[a]];setMenu(orderMenu(next));
+ };
+ const readIcon=(file:File|undefined)=>{
+ if(!file)return;
+ if(!["image/png","image/jpeg","image/svg+xml"].includes(file.type)||file.size>100*1024){notify("Icon 支持 PNG、JPG、SVG,文件不大于 100 KB");return;}
+ const reader=new FileReader();reader.onload=()=>{setAppIcon(reader.result as string);setIconFilename(file.name);};reader.onerror=()=>notify("Icon 读取失败,请重新选择");reader.readAsDataURL(file);
+ };
+ const save=async()=>{
+ if(savingRef.current)throw new Error("菜单正在保存,请稍后重试");
+ if(editDraft)throw new Error("请先确定或取消当前菜单编辑");
+ if(!appName.trim())throw new Error("请填写应用名称");
+ if(menu.some(item=>!item.label.trim()))throw new Error("请填写所有菜单名称");
+ const pageKeys=menu.filter(item=>item.page_type).map(item=>`${item.page_type}:${item.ref_id}`);
+ if(pageKeys.length!==sources.length||sources.some(source=>!pageKeys.includes(source.key)))throw new Error("页面选择已变化,请重新进入菜单配置");
+ savingRef.current=true;setSaving(true);
+ try{
+ await api(`/sites/${site.id}/menu`,{menu:orderMenu(menu),application:{app_name:appName.trim(),app_icon:appIcon,app_icon_filename:iconFilename,app_color_scheme:scheme}},"PUT");
+ const saved=await api(`/sites/${site.id}`);setMenu(buildSiteMenu(saved,leaves));onSiteChange(saved);
+ }finally{savingRef.current=false;setSaving(false);}
+ };
+ useEffect(()=>{registerSave(save);return()=>registerSave(null);},[menu,appName,appIcon,iconFilename,scheme,saving,signature,editDraft]);
+ const fixedGroup=(item:MenuEntry)=>["group:sc","group:cube"].includes(item.source_key);
+ const kind=(item:MenuEntry)=>item.page_type==="sc"?"数字孪生场景":item.page_type==="cube"?"领域仪表板":item.page_type==="mu"?"标准功能页面":"菜单分组";
+ const selectedPreview=menu.find(item=>item.id===previewPage);
+ return <div className="station-menu-configuration station-application-settings">
+ <section className="station-application-basic"><header><h2>基本信息</h2></header><div className="station-application-fields">
+ <label className="station-application-name"><span><i>*</i> 应用名称</span><UiInput aria-label="应用名称" maxLength={100} value={appName} disabled={saving} placeholder="请输入应用名称" onChange={event=>setAppName(event.target.value)}/></label>
+ <div className="station-application-icon"><span>Icon</span><div className="station-icon-control"><div className="station-icon-image">{appIcon?<img src={appIcon} alt="应用 Icon"/>:<ApartmentOutlined/>}</div><label className={saving?"station-icon-upload disabled":"station-icon-upload"}><span>{appIcon?"更换 Icon":"上传 Icon"}</span><input aria-label="上传应用 Icon" type="file" hidden disabled={saving} accept="image/png,image/jpeg,image/svg+xml" onChange={event=>{readIcon(event.target.files?.[0]);event.target.value="";}}/></label><span className="station-icon-file" title={iconFilename}>{iconFilename||"PNG / JPG / SVG,≤100 KB"}</span>{appIcon&&<UiButton className="link" disabled={saving} onClick={()=>{setAppIcon(null);setIconFilename("");}}>清除</UiButton>}</div></div>
+ <label className="station-application-scheme"><span>色系</span><div><i style={{background:color}}/><UiSelect aria-label="应用色系" value={scheme} disabled={saving} onChange={event=>setScheme(event.target.value)}>{APPLICATION_COLOR_SCHEMES.map(item=><option key={item.key} value={item.key}>{item.label}</option>)}</UiSelect></div></label>
+ </div></section>
+ <section className="station-application-menu"><header><h2>菜单列表 <small>{sources.length} 个页面</small></h2><UiActions className="station-application-menu-actions">
+ <Button disabled={saving} onClick={()=>setCollapsed(collapsed.size?new Set():new Set(menu.filter(item=>!item.page_type).map(item=>item.id)))}>{collapsed.size?"展开全部":"收起全部"}</Button>
+ <Button disabled={saving} onClick={()=>setRestoreConfirm(true)}>恢复默认</Button>
+ <Button type="primary" icon={<EyeOutlined/>} onClick={()=>{setPreviewOpen(true);setPreviewPage(null);setPreviewCollapsed(new Set());}}>预览菜单</Button>
+ </UiActions></header>
+ <div className="station-application-menu-scroll"><UiTable className="data-table company-table station-application-menu-table"><thead><tr><th>菜单名称</th><th>关联页面</th><th>页面类型</th><th>操作</th></tr></thead><tbody>{ordered.filter(item=>visible(item,collapsed)).map(item=>{
+ const siblings=item.parent_id==null?[]:children(item.parent_id);const index=siblings.findIndex(other=>other.id===item.id);const group=!item.page_type;
+ return <tr key={item.id} className={group?"station-menu-group-row":""}><td><div className="station-menu-name" style={{paddingLeft:depth(item)*28}}>{group?<UiButton className="station-menu-disclosure" aria-label={`${collapsed.has(item.id)?"展开":"收起"}菜单${item.label}`} aria-expanded={!collapsed.has(item.id)} onClick={()=>toggle(item.id)}>{collapsed.has(item.id)?<CaretRightOutlined/>:<CaretDownOutlined/>}</UiButton>:<span className="station-menu-child-guide"/>}<span>{item.label}</span>{group&&<small>{children(item.id).length} 项</small>}</div></td><td><span className={group?"station-menu-association muted":"station-menu-association"}>{group?"—":item.source_name}</span></td><td>{kind(item)}</td><td><div className="station-menu-row-actions">
+ <Tooltip title={!movable(item)?"保持原有菜单结构与顺序":"上移"}><Button type="text" aria-label={`上移菜单${item.label}`} disabled={saving||!movable(item)||index<=0} icon={<ArrowUpOutlined/>} onClick={()=>move(item,-1)}/></Tooltip>
+ <Tooltip title={!movable(item)?"保持原有菜单结构与顺序":"下移"}><Button type="text" aria-label={`下移菜单${item.label}`} disabled={saving||!movable(item)||index>=siblings.length-1} icon={<ArrowDownOutlined/>} onClick={()=>move(item,1)}/></Tooltip>
+ <Tooltip title={fixedGroup(item)?"领域菜单名称固定":"编辑"}><Button type="text" aria-label={`编辑菜单${item.label}`} disabled={saving||fixedGroup(item)} icon={<EditOutlined/>} onClick={()=>setEditDraft({...item})}/></Tooltip>
+ </div></td></tr>;
+ })}</tbody></UiTable></div>
+ <footer className="station-application-menu-note"><span>数字孪生、领域监控固定在前;标准功能保留原有结构与顺序。</span><span>{saving?"正在保存…":!site.mu?.length&&!site.page_config?.standard_pages_initialized?"标准功能默认全选,可在第四步调整":"增减页面请返回第四步“页面配置”"}</span></footer>
+ </section>
+ <Drawer className="station-info-drawer station-menu-edit-drawer" size={560} title="编辑菜单" open={!!editDraft} onClose={()=>setEditDraft(null)} footer={<UiActions className="station-drawer-actions"><Button onClick={()=>setEditDraft(null)}>取消</Button><Button type="primary" onClick={()=>{if(!editDraft?.label.trim()){notify("请填写菜单名称");return;}setMenu(current=>current.map(item=>item.id===editDraft.id?{...item,label:editDraft.label.trim()}:item));setEditDraft(null);}}>确定</Button></UiActions>}>
+ {editDraft&&<div className="station-menu-edit-fields"><label><span><i>*</i> 菜单名称</span><UiInput aria-label="编辑菜单名称" value={editDraft.label} maxLength={50} onChange={event=>setEditDraft({...editDraft,label:event.target.value})}/></label><label><span>上级菜单</span><UiInput readOnly value={menu.find(item=>item.id===editDraft.parent_id)?.label||"一级菜单"}/></label><label><span>关联页面</span><UiInput readOnly value={editDraft.page_type?editDraft.source_name:"菜单分组"}/></label></div>}
+ </Drawer>
+ <Drawer className="station-info-drawer station-application-preview-drawer" size={460} title="菜单预览" open={previewOpen} onClose={()=>setPreviewOpen(false)}>
+ <div className="station-application-preview" style={{"--application-color":color} as CSSProperties}><header>{appIcon?<img src={appIcon} alt="应用 Icon"/>:<ApartmentOutlined/>}<strong>{appName||"未命名应用"}</strong></header><nav>{ordered.filter(item=>visible(item,previewCollapsed)).map(item=><UiButton key={item.id} className={!item.page_type?"group":previewPage===item.id?"page active":"page"} style={{paddingLeft:18+depth(item)*26}} onClick={()=>item.page_type?setPreviewPage(item.id):toggle(item.id,true)}><span>{item.label}</span>{!item.page_type&&(previewCollapsed.has(item.id)?<CaretRightOutlined/>:<CaretDownOutlined/>)}</UiButton>)}</nav>{selectedPreview&&<div className="station-menu-preview-current"><span>{kind(selectedPreview)}</span><b>{selectedPreview.source_name}</b><small>菜单预览仅展示入口,不打开实际页面。</small></div>}</div>
+ </Drawer>
+ {restoreConfirm&&<UiOverlay className="modal-backdrop"><div className="modal station-replace-confirm"><header><h2>恢复默认菜单</h2><UiButton onClick={()=>setRestoreConfirm(false)}>×</UiButton></header><div className="modal-body"><p>按第四步的页面选择恢复菜单名称和排序。应用名称、Icon 与色系保持不变,点击“保存草稿”后生效。</p></div><footer><Button onClick={()=>setRestoreConfirm(false)}>取消</Button><Button type="primary" onClick={()=>{setMenu(buildSiteMenu(site,leaves,[]));setCollapsed(new Set());setRestoreConfirm(false);}}>恢复默认</Button></footer></div></UiOverlay>}
+ </div>;
+}
--- /dev/null
+/* Layout-only compatibility for existing custom antd button surfaces.
+ * Preserves approved navigation, thumbnail and canvas sizing, including responsive rules.
+ * Branding and states remain owned by theme.json and ui-system.css. */
+.side-nav>button.station-ui-button.ant-btn{height:41px;border:0;text-align:left;padding:0 16px;display:flex;align-items:center;gap:12px;}
+.side-nav>button.station-ui-button.ant-btn b{width:18px;text-align:center;}
+.side-nav>button.station-ui-button.ant-btn em{width:21px;height:21px;border:1px solid var(--ant-color-border);border-radius:50%;display:grid;}
+.steps button.station-ui-button.ant-btn{border:0;display:flex;align-items:center;position:relative;text-align:left;padding:0 20px;}
+.steps button.station-ui-button.ant-btn>i{width:29px;height:29px;border-radius:50%;border:1px solid var(--ant-color-border);display:grid;margin-right:12px;}
+.steps button.station-ui-button.ant-btn span{display:flex;flex-direction:column;gap:4px;}
+.steps button.station-ui-button.ant-btn>b{position:absolute;right:0;}
+.view-switch button.station-ui-button.ant-btn{width:31px;height:29px;border:1px solid var(--ant-color-border);}
+.link.station-ui-button.ant-btn{border:0;padding:0;}
+.trash.station-ui-button.ant-btn{border:0;}
+.pagination button.station-ui-button.ant-btn{width:28px;height:28px;border:0;}
+.tabs button.station-ui-button.ant-btn{padding:0 20px;border:0;}
+.mu-grid button.station-ui-button.ant-btn{border:0;padding:0;}
+.menu-tree>button.station-ui-button.ant-btn{border:1px dashed var(--ant-color-border);width:100%;height:34px;}
+.menu-preview button.station-ui-button.ant-btn{border:0;text-align:left;padding:12px;}
+.confirm-grid button.station-ui-button.ant-btn{position:absolute;right:10px;top:10px;border:0;}
+.text-button.station-ui-button.ant-btn{border:0;margin-top:12px;}
+.preview-toolbar button.station-ui-button.ant-btn{border:1px solid var(--ant-color-border);height:29px;padding:0 12px;}
+.cube-side button.station-ui-button.ant-btn{height:37px;border:0;text-align:left;padding-left:12px;}
+.cube-grid h3 button.station-ui-button.ant-btn{border:0;}
+.inspector>nav button.station-ui-button.ant-btn{flex:1;border:0;}
+.code-body header button.station-ui-button.ant-btn{border:0;}
+.modal>header button.station-ui-button.ant-btn{border:0;}
+.history-list button.station-ui-button.ant-btn{border:0;}
+.module-switch>button.station-ui-button.ant-btn{height:47px;border:1px solid var(--ant-color-border);display:flex;align-items:center;text-align:left;padding:0 8px;gap:8px;}
+.module-switch>button.station-ui-button.ant-btn>b{width:23px;height:23px;display:grid;}
+.module-switch>button.station-ui-button.ant-btn>span{display:flex;flex-direction:column;gap:4px;}
+.domain-grid footer button.station-ui-button.ant-btn{border:0;}
+.domain-grid footer button.station-ui-button.ant-btn:last-child{border:1px solid var(--ant-color-primary-bg);padding:4px 8px;}
+.domain-page-list footer button.station-ui-button.ant-btn{border:0;padding:0;margin-right:12px;}
+.knowledge-list button.station-ui-button.ant-btn{border:0;}
+.model-list>button.station-ui-button.ant-btn{width:100%;min-height:56px;border:0;border-radius:var(--ant-border-radius);display:flex;align-items:center;padding:8px 8px;text-align:left;position:relative;}
+.model-list>button.station-ui-button.ant-btn>i{width:25px;}
+.model-list>button.station-ui-button.ant-btn>span{display:flex;flex:1;flex-direction:column;gap:4px;}
+.model-list>button.station-ui-button.ant-btn>strong{margin-right:24px;}
+.model-list>button.station-ui-button.ant-btn>u{position:absolute;right:9px;}
+.wordbuddy-card>footer button.station-ui-button.ant-btn{border:0;}
+.ai-fab.station-ui-button.ant-btn{position:fixed;right:22px;bottom:22px;width:54px;height:54px;border-radius:50%;border:0;display:grid;}
+.ai-dialog .ai-close.station-ui-button.ant-btn{border:0;line-height:1;width:28px;height:28px;border-radius:var(--ant-border-radius);}
+.data-table.editable .trash.station-ui-button.ant-btn{border:0;line-height:1;}
+.data-table.editable .asset-eye-btn.station-ui-button.ant-btn{border:0;line-height:1;padding:0;display:inline-flex;align-items:center;}
+.cube-actions button.station-ui-button.ant-btn{border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius);height:26px;padding:0 8px;}
+.menu-row .trash.station-ui-button.ant-btn{border:1px solid var(--ant-color-error-bg);border-radius:var(--ant-border-radius);height:30px;width:34px;}
+.menu-preview .menu-section-title.station-ui-button.ant-btn{margin-bottom:8px;}
+.style-pick.station-ui-button.ant-btn{border:1px solid var(--ant-color-border);padding:8px;display:flex;flex-direction:column;gap:8px;text-align:center;}
+.module-switch>button.station-ui-button.ant-btn{height:50px;border:0;border-radius:0;padding:0 20px;gap:12px;}
+.module-switch>button.station-ui-button.ant-btn>b{width:18px;height:auto;}
+.module-switch>button.station-ui-button.ant-btn>span{display:block;}
+.module-switch>button.station-ui-button.ant-btn small{display:none;}
+.side-nav>button.station-ui-button.ant-btn{height:44px;padding:0 20px;gap:12px;}
+.side-nav>button.station-ui-button.ant-btn em{width:20px;height:20px;}
+.steps button.station-ui-button.ant-btn{padding:0 20px;}
+.steps button.station-ui-button.ant-btn>i{width:26px;height:26px;}
+.company-status-tabs button.station-ui-button.ant-btn{height:48px;min-width:120px;padding:0 24px;border:0;}
+.company-status-tabs button.station-ui-button.ant-btn span{margin-right:8px;}
+.company-status-tabs button.station-ui-button.ant-btn b{display:inline-grid;min-width:24px;height:20px;padding:0 8px;border-radius:var(--ant-border-radius);}
+.pagination button.station-ui-button.ant-btn{height:32px;border:1px solid var(--ant-color-border);}
+.pagination button.station-ui-button.ant-btn{width:34px;}
+.domain-select .text-button.station-ui-button.ant-btn{margin:0;}
+@media (max-width:900px){.company-status-tabs button.station-ui-button.ant-btn{min-width:100px;}}
+.menu-section button.station-ui-button.ant-btn{width:100%;border:0;text-align:left;}
+.menu-section-title.station-ui-button.ant-btn{height:56px;padding:0 20px;display:flex;align-items:center;justify-content:space-between;line-height:56px;}
+.menu-section-items button.station-ui-button.ant-btn{height:50px;padding:0 20px 0 40px;display:flex;align-items:center;line-height:50px;}
+.extension-third-menu>button.station-ui-button.ant-btn{height:50px;padding:0 20px 0 60px;display:flex;align-items:center;line-height:50px;border:0;width:100%;text-align:left;}
+.company-side-footer>button.station-ui-button.ant-btn{height:26px;padding:0;border:0;display:flex;align-items:center;justify-content:space-between;text-align:left;}
+.company-model-menu button.station-ui-button.ant-btn{width:100%;height:32px;border:0;display:flex;justify-content:space-between;align-items:center;padding:0 8px;}
+.site-edit-tabs>button.station-ui-button.ant-btn{height:64px;flex:1;min-width:0;border:0;display:flex;align-items:center;gap:12px;padding:0 20px;text-align:left;}
+.site-edit-tabs>button.station-ui-button.ant-btn i{width:26px;height:26px;border:1px solid var(--ant-color-border);border-radius:50%;display:grid;}
+.asset-config-tabs>button.station-ui-button.ant-btn{height:65px;min-width:210px;padding:12px 4px 8px;border:0;text-align:left;display:flex;flex-direction:column;justify-content:center;gap:4px;}
+.asset-editor-title>.text-button.station-ui-button.ant-btn{margin:0;padding-right:20px;}
+.asset-batch-entry.station-ui-button.ant-btn{width:100%;height:54px;border:0;text-align:left;padding:0 16px;}
+.asset-template-panel>button.station-ui-button.ant-btn{min-height:58px;border:1px solid var(--ant-color-border);display:grid;text-align:left;padding:8px;column-gap:8px;}
+.asset-template-panel>button.station-ui-button.ant-btn>span{align-self:center;}
+.node-drawer-tabs button.station-ui-button.ant-btn{border:0;padding:0 20px;}
+@media (max-width:1100px){.asset-config-tabs>button.station-ui-button.ant-btn{min-width:180px;}}
+.domain-resource-table .company-record-link.station-ui-button.ant-btn{display:block;max-width:100%;overflow:hidden;white-space:nowrap;text-align:left;}
+.device-resource-table .company-record-link.station-ui-button.ant-btn{display:block;max-width:100%;overflow:hidden;white-space:nowrap;text-align:left;}
+.resource-name-cell .company-record-link.station-ui-button.ant-btn{min-width:0;overflow:hidden;white-space:nowrap;text-align:left;}
+.resource-back .text-button.station-ui-button.ant-btn{margin:0;}
+.style-resource-table .company-record-link.station-ui-button.ant-btn{display:block;max-width:100%;overflow:hidden;white-space:nowrap;text-align:left;}
+.view-toggle button.station-ui-button.ant-btn{border:0;height:28px;padding:0 12px;}
+.settings-domain-table .text-button.station-ui-button.ant-btn{margin:0;}
+.domain-nav-row>button.station-ui-button.ant-btn:not(.domain-nav-del):not(.domain-nav-ok){flex:1;min-width:0;text-align:left;height:41px;border:0;padding:0 16px;display:flex;align-items:center;white-space:nowrap;}
+.domain-nav-row>button.station-ui-button.ant-btn b{width:18px;margin-right:8px;flex:0 0 auto;}
+.domain-nav-del.station-ui-button.ant-btn{border:0;width:30px;line-height:1;flex:0 0 auto;}
+.domain-nav-ok.station-ui-button.ant-btn{border:0;width:30px;flex:0 0 auto;}
+.domain-nav-add.station-ui-button.ant-btn{height:34px;border:0;text-align:left;padding:0 16px;display:flex;align-items:center;width:100%;}
+.overview-tabs button.station-ui-button.ant-btn{padding:0 16px;}
+.dev-row-head.station-ui-button.ant-btn{width:100%;border:0;display:flex;align-items:center;gap:12px;padding:12px 12px;text-align:left;}
+.wise-header button.station-ui-button.ant-btn{border:0;padding:0;}
+.wise-menu-button.station-ui-button.ant-btn{flex:0 0 24px;width:24px;height:24px;margin-right:12px;display:grid;}
+.wise-product-switch.station-ui-button.ant-btn{flex:0 0 auto;height:48px;margin-left:12px;display:flex;align-items:center;white-space:nowrap;}
+.wise-header-actions>button.station-ui-button.ant-btn:not(.wise-avatar){flex:0 0 40px;width:40px;height:40px;display:grid;border-radius:0;}
+.wise-header-actions>button.station-ui-button.ant-btn>img{display:block;width:24px;height:24px;}
+.wise-avatar.station-ui-button.ant-btn{flex:0 0 24px;width:24px;height:24px;margin:0 8px;border-radius:50%!important;line-height:24px;text-align:center;}
+@media (max-width:1120px){.wise-avatar.station-ui-button.ant-btn{margin-right:8px;}}
+@media (max-width:700px){.wise-header-actions>button.station-ui-button.ant-btn:not(.wise-avatar){width:36px;flex-basis:36px;}}
+@media (max-width:560px){.wise-product-switch.station-ui-button.ant-btn{margin-left:4px;}}
+@media (max-width:560px){.wise-header-actions>button.station-ui-button.ant-btn:nth-child(1),.wise-header-actions>button.station-ui-button.ant-btn:nth-child(2){display:none;}}
+.asset-batch-entry.station-ui-button.ant-btn{height:72px;padding:0 20px;}
+.asset-template-panel>button.station-ui-button.ant-btn{min-height:50px;border:0;padding:8px 8px;}
+.asset-template-panel>button.station-ui-button.ant-btn:hover{border:0;}
+.asset-view-switch button.station-ui-button.ant-btn{width:38px;border:0;}
+.asset-node-card>button.station-ui-button.ant-btn{align-self:center;justify-self:end;width:19px;height:19px;border:0;border-radius:50%;padding:0;display:grid;}
+.asset-node-list>button.station-ui-button.ant-btn{height:43px;display:grid;align-items:center;text-align:left;border:0;padding:0 16px;}
+.asset-node-drawer .node-drawer-tabs button.station-ui-button.ant-btn{height:49px;padding:0 20px;}
+.asset-node-drawer .node-drawer-tabs button.station-ui-button.ant-btn:first-child{padding-left:0;margin-right:20px;}
+.node-image-row .text-button.station-ui-button.ant-btn{margin:4px 0 0;}
+.edge-component-table button.station-ui-button.ant-btn{width:28px;height:28px;border:0;}
+.binding-component-tabs button.station-ui-button.ant-btn{height:34px;padding:0 20px;border:0;}
+.domain-showcase-card>footer button.station-ui-button.ant-btn{height:30px;border:0;padding:0;}
+.domain-capability-back.station-ui-button.ant-btn{align-self:flex-start;border:0;padding:0;height:28px;}
+.domain-capability-card>footer button.station-ui-button.ant-btn{height:29px;border:1px solid var(--ant-color-border);padding:0 12px;}
+.domain-capability-card>footer button.station-ui-button.ant-btn.capability-edit-button{margin-right:auto;}
+.capability-preview-tabs button.station-ui-button.ant-btn{height:44px;border:0;padding:0 20px;}
+.dashboard-card-more.station-ui-button.ant-btn{position:absolute;right:8px;top:10px;width:30px;height:30px;border:0;}
+.dashboard-card-more.station-ui-button.ant-btn{right:8px;top:9px;}
+.station-icon-control>.link.station-ui-button.ant-btn{border:0;white-space:nowrap;padding:0;}
+.station-menu-disclosure.station-ui-button.ant-btn{flex-shrink:0;border:0;width:16px;padding:0;}
+.station-application-preview nav>button.station-ui-button.ant-btn{display:flex;align-items:center;justify-content:space-between;width:100%;border:0;padding:16px 20px;text-align:left;}
+.station-hierarchy-workspace .station-name-cell>.link.station-ui-button.ant-btn{white-space:nowrap;overflow:hidden;}
+.station-name-cell>button.station-ui-button.ant-btn:first-child:not(.link){border:0;width:18px;padding:0;}
+.station-template-cards>button.station-ui-button.ant-btn{border:1px solid var(--ant-color-border);text-align:left;padding:16px;min-width:0;}
+.station-template-cards>button.station-ui-button.ant-btn>div{display:flex;align-items:center;gap:8px;}
+.station-dashboard-image.station-ui-button.ant-btn{display:block;width:100%;height:145px;padding:0;border:0;overflow:hidden;text-align:left;}
+.station-menu-source>div>button.station-ui-button.ant-btn{display:flex;align-items:center;justify-content:space-between;width:100%;padding:12px 8px;margin-bottom:8px;border:1px solid var(--ant-color-border);text-align:left;}
+.steps button.station-ui-button.ant-btn{height:100%;}
--- /dev/null
+/* Supplied Skill values are resolved in company-theme.tsx; no independent palette. */
+html,body,.ant-app{font-family:var(--ant-font-family);font-size:var(--ant-font-size);color:var(--ant-color-text-base);background:var(--ant-color-bg-layout)}
+.workspace{background:var(--ant-color-bg-layout);min-width:0}
+.page-heading{background:var(--ant-color-bg-layout)}
+.company-side-nav{font-family:var(--ant-font-family)}
+.company-side-nav .menu-section button{font-weight:400;font-size:var(--ant-font-size);border-radius:0}
+.company-side-nav .menu-section-title{font-weight:600!important}
+.company-side-nav .menu-section-items button.active{font-weight:400;background:var(--ant-color-primary-bg);color:var(--ant-color-primary)}
+.company-side-nav .menu-section-items button:hover{background:var(--ant-table-row-hover-bg);color:var(--ant-color-primary)}
+.extension-menu-item,.extension-third-menu>div,.extension-third-menu>button{font-family:var(--ant-font-family);font-size:var(--ant-font-size);font-weight:400}
+.button.ant-btn{height:var(--ant-control-height);font-size:var(--ant-font-size);border-radius:var(--ant-border-radius);padding-inline:var(--ant-padding);margin-left:0;box-shadow:none}
+.button.ant-btn-primary,.btn-ai.ant-btn{background:var(--ant-color-primary);border-color:var(--ant-color-primary);color:var(--ant-color-text-light-solid)}
+.button.ant-btn-primary:not(:disabled):hover,.btn-ai.ant-btn:not(:disabled):hover{background:var(--ant-color-primary-hover);border-color:var(--ant-color-primary-hover)}
+.button.secondary.ant-btn{background:var(--ant-color-bg-container);color:var(--ant-button-default-color);border-color:var(--ant-button-default-border-color)}
+.button.secondary.ant-btn:not(:disabled):hover{color:var(--ant-button-default-hover-color);border-color:var(--ant-button-default-hover-border-color)}
+.button.ant-btn:disabled,.button.ant-btn-primary:disabled{background:var(--ant-color-bg-container-disabled);border-color:var(--ant-color-border);color:var(--ant-color-text-disabled);opacity:1}
+.station-ui-select{min-width:120px;height:var(--ant-control-height);font-size:var(--ant-font-size)}
+.field>.station-ui-select,.form-grid .station-ui-select,.station-basic-fields .station-ui-select,.station-create-template>.station-ui-select,.node-basic-grid .station-ui-select,.alarm-form-grid .station-ui-select{width:100%}
+.station-ui-input.ant-input{height:var(--ant-control-height);font-size:var(--ant-font-size);border-radius:var(--ant-border-radius);padding:var(--ant-padding-xxs) var(--ant-padding-xs)}
+.station-ui-input.ant-input:focus{border-color:var(--ant-color-primary);box-shadow:0 0 0 2px var(--ant-color-primary-bg)}
+.station-ui-input.ant-input:disabled{background:var(--ant-color-bg-container-disabled);color:var(--ant-color-text-disabled)}
+.search-field>.station-ui-input,.station-search>.station-ui-input,.dashboard-manager-search>.station-ui-input{border:0!important;box-shadow:none!important;background:transparent!important;min-width:0;flex:1}
+.station-ui-choice{flex-shrink:0;margin:0}
+.station-ui-table{border-collapse:initial;max-width:100%}
+.station-ui-table table{width:100%}
+.station-ui-table .ant-table-thead>tr>th{font-size:var(--ant-font-size);font-weight:600;color:var(--ant-table-header-color);background:var(--ant-color-bg-layout);padding-block:var(--ant-padding-sm);border-color:var(--ant-table-border-color)}
+.station-ui-table .ant-table-tbody>tr>td{font-size:var(--ant-font-size);font-weight:400;color:var(--ant-color-text-base);padding-block:var(--ant-padding-xs);border-color:var(--ant-table-border-color)}
+.station-ui-table .ant-table-tbody>tr:hover>td,.station-ui-table .ant-table-tbody>tr>td.ant-table-cell-row-hover{background:var(--ant-table-row-hover-bg)}
+.station-ui-table input[type=checkbox]{appearance:auto;width:auto;height:auto;padding:0;border:0;background:none;margin:0;opacity:0}
+.station-ui-table .ant-checkbox-inner{border-radius:var(--ant-border-radius-sm)}
+.ant-tag.status,.ant-tag.tag-new{border-radius:var(--ant-border-radius);font-size:var(--ant-font-size-sm);line-height:var(--ant-line-height-sm);padding:var(--ant-padding-xxs) var(--ant-padding-xs)}
+.status.ok{color:var(--ant-color-success-text);background:var(--ant-color-success-bg);border-color:var(--ant-color-success-border)}
+.status.warn{color:var(--ant-color-warning-text);background:var(--ant-color-warning-bg);border-color:var(--ant-color-warning-border)}
+.status.bad{color:var(--ant-color-error-text);background:var(--ant-color-error-bg);border-color:var(--ant-color-error-border)}
+.status.muted{color:var(--ant-color-text-secondary);background:var(--ant-color-fill-quaternary);border-color:var(--ant-color-border)}
+.ant-drawer-title{font-size:var(--ant-font-size);font-weight:600}
+.ant-drawer-body{background:var(--ant-color-bg-container)}
+.ant-drawer-footer .station-drawer-actions,.ant-drawer-footer .drawer-actions{display:flex;flex-wrap:nowrap;justify-content:flex-end;gap:var(--ant-padding-xs)}
+.domain-capability-actions,.domain-capability-card>footer,.filter-actions,.station-application-menu-actions,.sticky-actions>div{display:flex;flex-wrap:nowrap;align-items:center;gap:var(--ant-padding-xs)}
+.domain-capability-card>footer{overflow-x:auto;flex-shrink:0}
+.domain-capability-card>footer .ant-btn{height:var(--ant-control-height);font-size:var(--ant-font-size);white-space:nowrap}
+.domain-capability-card,.station-page-module-panel,.station-list-panel,.station-application-basic,.station-application-menu{background:var(--ant-color-bg-container);border-color:var(--ant-color-border);border-radius:var(--ant-border-radius)}
+.domain-capability-card{overflow:hidden}
+.domain-capability-card>header h3,.station-application-settings h2{font-size:var(--ant-font-size);font-weight:600}
+.domain-capability-body{min-height:0;overflow:auto}
+.device-template-tile b{font-size:var(--ant-font-size-sm);font-weight:400;line-height:var(--ant-line-height-sm)}
+.station-dashboard-selection,.station-twin-selection{gap:var(--ant-padding);padding:var(--ant-padding)}
+.station-application-settings input:not([type=file]),.station-menu-edit-fields input{border-radius:var(--ant-border-radius);height:var(--ant-control-height)}
+.station-application-settings .station-menu-row-actions .ant-btn{color:var(--ant-color-primary)}
+.station-application-settings .station-menu-row-actions .ant-btn:disabled{color:var(--ant-color-text-disabled)}
+.station-hierarchy-workspace,.station-page-selection,.station-menu-configuration{font-size:var(--ant-font-size)}
+.station-application-fields{gap:var(--ant-padding-lg);padding:var(--ant-padding-lg)}
+.station-ui-table .station-menu-group-row>td{background:var(--ant-color-bg-layout)}
+.station-ui-table .station-menu-group-row:hover>td{background:var(--ant-table-row-hover-bg)}
+.station-opsflow-blank{background:var(--ant-color-bg-container)}
+@media(max-width:1200px){.domain-capability-actions{overflow:auto;max-width:100%}.station-application-fields{gap:var(--ant-padding)}}
+.panel.ant-card{border-radius:var(--ant-border-radius-lg)}
+.panel.ant-card>.ant-card-head{padding-inline:var(--ant-padding);min-height:48px}
+.panel.ant-card>.ant-card-head .ant-card-head-title{padding:var(--ant-padding-xs) 0;font-size:var(--ant-font-size);font-weight:600}
+.panel.ant-card>.ant-card-body{padding:var(--ant-padding-lg)}
+.company-list-panel.ant-card>.ant-card-body{padding:0 var(--ant-padding) var(--ant-padding-sm)}
+.field.ant-form-item{display:block;gap:initial;margin:0;min-width:0}
+.field.ant-form-item .ant-form-item-label{padding:0 0 var(--ant-padding-xs)}
+.field.ant-form-item .ant-form-item-label>label{height:auto;font-size:var(--ant-font-size);font-weight:400;color:var(--ant-color-text-base)}
+.field.ant-form-item .ant-form-item-extra{font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary)}
+.field.ant-form-item .ant-input:not(textarea){height:var(--ant-control-height)}
+.station-ui-tabs.company-status-tabs,.station-ui-tabs.node-drawer-tabs,.station-ui-tabs.asset-config-tabs,.station-ui-tabs.binding-component-tabs,.station-ui-tabs.tabs{display:block;height:auto;padding-top:0;gap:initial;border:0;margin:0;background:transparent}
+.station-ui-tabs>.ant-tabs-nav{margin:0 0 var(--ant-padding)}
+.station-ui-tabs>.ant-tabs-nav .ant-tabs-tab{padding:var(--ant-padding-sm) var(--ant-padding);font-size:var(--ant-font-size);font-weight:400}
+.station-ui-tabs>.ant-tabs-nav .ant-tabs-ink-bar{height:2px;background:var(--ant-color-primary)}
+.station-ui-tabs .ant-tabs-tab-btn{font-weight:400}
+.station-ui-tabs .ant-tabs-tab-btn b,.station-ui-tabs .ant-tabs-tab-btn i{margin-left:var(--ant-padding-xs);background:var(--ant-color-fill-secondary);color:var(--ant-color-text-secondary);font-size:var(--ant-font-size-sm);font-style:normal;font-weight:600;padding:0 var(--ant-padding-xxs);border-radius:var(--ant-border-radius)}
+.station-ui-tabs .ant-tabs-tab-active .ant-tabs-tab-btn b{background:var(--ant-color-success-bg);color:var(--ant-color-success-text)}
+.station-ui-tabs .ant-tabs-content-holder{min-width:0}
+.station-ui-tabs .ant-tabs-content{background:var(--ant-color-bg-layout)}
+.node-config-page .field.ant-form-item .ant-form-item-row{display:flex;flex-flow:row nowrap;align-items:center}
+.node-config-page .field.ant-form-item .ant-form-item-label{flex:0 0 100px;padding:0 var(--ant-padding-xs) 0 0;text-align:right}
+.node-config-page .field.ant-form-item .ant-form-item-control{flex:1;min-width:0}
+.alarm-enable-switch.ant-switch{min-width:var(--ant-switch-track-min-width);height:var(--ant-switch-track-height);padding:0;border:0}
+.station-inline-actions,.station-modal-actions,.station-drawer-actions,.drawer-actions,.result-actions,.settings-actions{display:flex;flex-wrap:nowrap;align-items:center;gap:var(--ant-padding-xs)}
+.station-modal-actions{justify-content:flex-end}
+/* Ant Modal owns positioning, focus and chrome; old class names only identify business content. */
+.station-ui-modal.modal,.station-ui-modal.dlg-box{display:block;position:relative;inset:auto;margin-inline:auto;padding:0;max-width:calc(100vw - 32px);max-height:none;height:auto;background:none;border:0;border-radius:0;box-shadow:none;overflow:visible;z-index:auto;color:var(--ant-color-text-base);font-size:var(--ant-font-size)}
+.station-ui-modal .ant-modal-container{padding:var(--ant-padding-lg);border-radius:var(--ant-border-radius-lg)}
+.station-ui-modal .ant-modal-title{font-size:var(--ant-font-size-heading-4);font-weight:600}
+.station-ui-modal .ant-modal-body{font-size:var(--ant-font-size);color:var(--ant-color-text-base)}
+.station-ui-modal .modal-body{padding:0;max-height:70vh;overflow:auto}
+.station-ui-modal .ant-modal-footer{margin-top:var(--ant-padding-lg)}
+.station-ui-modal .station-modal-actions .ant-btn{margin:0}
+.station-ui-select .ant-select-selector{font-size:var(--ant-font-size)}
+.filterbar.company-filterbar{height:auto;min-height:48px;flex-wrap:nowrap}
+.filterbar.company-filterbar .station-ui-select{flex-shrink:0;min-width:164px}
--- /dev/null
+"use client";
+
+import { Children, Fragment, isValidElement, cloneElement, type ReactNode, type ReactElement, type InputHTMLAttributes, type SelectHTMLAttributes, type ButtonHTMLAttributes, type TableHTMLAttributes, type HTMLAttributes, type ChangeEvent, type Ref } from "react";
+import { Button, Checkbox, Input, Radio, Select, Table, Tabs, Switch, Modal, Dropdown, type TableColumnsType } from "antd";
+import { EllipsisOutlined } from "@ant-design/icons";
+
+/** Keep existing business callbacks while rendering controls from the one UI library. */
+export function UiButton({type, className="", children, secondary:secondaryProp, ...props}: ButtonHTMLAttributes<HTMLButtonElement> & {ref?:Ref<HTMLButtonElement>;secondary?:boolean}) {
+ if(props.role==="switch") return <Switch checked={props["aria-checked"]===true} disabled={props.disabled} aria-label={props["aria-label"]} className={className} onClick={(_,event)=>props.onClick?.(event as never)}/>;
+ const action = /(^|\s)(button|btn-ai|ai-send|sso-button)(\s|$)/.test(className);
+ const secondary = secondaryProp||/(^|\s)secondary(\s|$)/.test(className);
+ const link = /(^|\s)(link|text-button|domain-capability-back)(\s|$)/.test(className);
+ return <Button {...props as React.ComponentProps<typeof Button>} htmlType={type||"button"} type={link?"link":action?secondary?"default":"primary":"text"} danger={/(^|\s)(danger|trash)(\s|$)/.test(className)} className={className+" station-ui-button"}>{children}</Button>;
+}
+
+export function UiInput({type, className="", onChange, ...props}: InputHTMLAttributes<HTMLInputElement>) {
+ if(type==="checkbox"||type==="radio"){
+ const Choice=type==="checkbox"?Checkbox:Radio;
+ const {value,checked,defaultChecked,disabled,id,name,onClick,"aria-label":label}=props;
+ return <Choice value={value} checked={checked} defaultChecked={defaultChecked} disabled={disabled} id={id} name={name} aria-label={label} className={className+" station-ui-choice"} onClick={onClick as never} onChange={event=>onChange?.(event as unknown as ChangeEvent<HTMLInputElement>)}/>;
+ }
+ return <Input {...props as React.ComponentProps<typeof Input>} type={type} className={className+" station-ui-input"} onChange={onChange}/>;
+}
+
+type ElementProps = HTMLAttributes<HTMLElement> & {children?:ReactNode;value?:string|number;disabled?:boolean;colSpan?:number;rowSpan?:number;width?:number|string};
+function elements(children:ReactNode):ReactElement<ElementProps>[] {
+ return Children.toArray(children).flatMap(child=>isValidElement<ElementProps>(child)?child.type===Fragment?elements(child.props.children):[child]:[]);
+}
+function textOf(children:ReactNode):string {
+ return Children.toArray(children).map(child=>isValidElement<ElementProps>(child)?textOf(child.props.children):String(child)).join("");
+}
+
+/** Filtering tabs still own their shared table inside each items[].children. */
+export function UiTabbedRegion({navigation,content}:{navigation:ReactElement<ElementProps>;content:ReactNode}){
+ const buttons=elements(navigation.props.children);
+ const active=buttons.find(button=>button.props["aria-selected"]===true||/(^|\s)active(\s|$)/.test(button.props.className||""))||buttons[0];
+ const activeKey=String(active?.key??0);
+ return <Tabs className={(navigation.props.className||"")+" station-ui-tabs"} activeKey={activeKey} onChange={key=>{
+ const button=buttons.find((item,index)=>String(item.key??index)===key);
+ button?.props.onClick?.(undefined as never);
+ }} items={buttons.map((button,index)=>({key:String(button.key??index),label:button.props.children,children:content}))}/>;
+}
+
+export function UiSelect({children,value,defaultValue,onChange,className="",style,disabled,id,name,"aria-label":label}: SelectHTMLAttributes<HTMLSelectElement>) {
+ const options=elements(children).map(option=>({value:String(option.props.value??textOf(option.props.children)),label:option.props.children,disabled:option.props.disabled}));
+ return <Select id={id} disabled={disabled} aria-label={label} value={value===undefined?undefined:String(value)} defaultValue={defaultValue===undefined?options.find(option=>!option.disabled)?.value:String(defaultValue)} options={options} className={className+" station-ui-select"} style={style} onChange={selected=>{
+ onChange?.({target:{value:selected,name},currentTarget:{value:selected,name}} as ChangeEvent<HTMLSelectElement>);
+ }}/>;
+}
+
+type ActionProps = ElementProps & {type?:string;secondary?:boolean;disabled?:boolean;title?:string};
+const businessAction=(action:ReactElement<ActionProps>)=>!!(action.type as unknown as {uiPrimaryAction?:boolean}).uiPrimaryAction;
+/** At most three visible actions and one primary; remaining actions stay available in More. */
+export function UiActions({children,...props}:HTMLAttributes<HTMLDivElement>){
+ const parts=elements(children) as ReactElement<ActionProps>[];
+ const actionable=(part:ReactElement<ActionProps>)=>part.type===UiButton||part.type===Button||part.type===Dropdown||businessAction(part);
+ let actions=parts.filter(actionable);
+ // File inputs, search fields and other auxiliary content must remain mounted, not enter More.
+ const extras=parts.filter(part=>!actionable(part));
+ const isPrimary=(action:ReactElement<ActionProps>)=>action.props.type==="primary"||businessAction(action)&&!action.props.secondary||action.type===UiButton&&/button|btn-ai/.test(action.props.className||"")&&!action.props.secondary&&!/secondary/.test(action.props.className||"");
+ const primaryIndex=actions.findLastIndex(isPrimary);
+ actions=actions.map((action,index)=>{
+ if(index===primaryIndex||!isPrimary(action))return action;
+ const custom=action.type===UiButton||businessAction(action);
+ return cloneElement(action,custom?{secondary:true}:{type:"default"});
+ });
+ if(actions.length<=3)return <div {...props}>{[...actions,...extras]}</div>;
+ return <div {...props}>{[...actions.slice(0,2),<Dropdown key="more" trigger={["click"]} menu={{items:actions.slice(2).map((action,index)=>({key:String(index),label:action.props.children,disabled:action.props.disabled})),onClick:({key,domEvent})=>actions[Number(key)+2].props.onClick?.(domEvent as never)}}><Button aria-label="更多操作" icon={<EllipsisOutlined/>}>更多</Button></Dropdown>,...extras]}</div>;
+}
+
+/** Replace custom backdrops with antd's focus-trapped, keyboard-aware modal surface. */
+export function UiOverlay({children,onMouseDown,onClick}:HTMLAttributes<HTMLDivElement>){
+ const inner=elements(children).find(child=>/\b(modal|dlg-box)\b/.test(child.props.className||""));
+ if(!inner)return null;
+ const parts=elements(inner.props.children),header=parts.find(part=>part.type==="header");
+ const heading=elements(header?.props.children).find(part=>part.type==="h2")||parts.find(part=>part.type==="h2");
+ const footer=parts.find(part=>part.type==="footer"||part.props.className?.split(" ").includes("dlg-actions"));
+ const content=Children.toArray(inner.props.children).filter(part=>!isValidElement(part)||part!==header&&part!==heading&&part!==footer);
+ // Children.toArray creates element clones. Use type/key matching for excluded pieces.
+ const body=content.filter(part=>!isValidElement<ElementProps>(part)||!(header&&part.type==="header")&&!(heading&&part.type==="h2")&&!(footer&&(part.type==="footer"||part.props.className?.split(" ").includes("dlg-actions"))));
+ const name=inner.props.className||"",fallbackWidth=/preview|prompt/.test(name)?1200:/editor|batch-create/.test(name)?960:/maintain|import|package/.test(name)?720:480;
+ const width=inner.props.style?.width&&inner.props.style.width!=="auto"?inner.props.style.width:fallbackWidth;
+ const close=onMouseDown||onClick||elements(header?.props.children).find(part=>part.props.onClick&&!part.props.disabled)?.props.onClick||elements(footer?.props.children).find(part=>textOf(part.props.children).includes("取消")&&!part.props.disabled)?.props.onClick;
+ const extras=Children.toArray(children).filter(child=>!isValidElement<ElementProps>(child)||! /\b(modal|dlg-box)\b/.test(child.props.className||""));
+ return <>{extras}<Modal open centered title={heading?.props.children} width={width} className={name+" station-ui-modal"} closable={!!close} keyboard={!!close} onCancel={event=>close?.(event as never)} mask={{closable:!!close}} footer={footer?<UiActions className="station-modal-actions">{footer.props.children}</UiActions>:null}>{body}</Modal></>;
+}
+
+type UiRow = {key:string;cells:ReactElement<ElementProps>[];props:ElementProps};
+/** Translate JSX cells to antd's data/column model, preserving row and cell actions. */
+export function UiTable({children,className="",style,...props}:TableHTMLAttributes<HTMLTableElement>){
+ const sections=elements(children);
+ const head=sections.find(section=>section.type==="thead");
+ const body=sections.find(section=>section.type==="tbody");
+ const headerRow=elements(head?.props.children)[0];
+ const headers=elements(headerRow?.props.children);
+ const cols=elements(sections.find(section=>section.type==="colgroup")?.props.children);
+ const data:UiRow[]=elements(body?.props.children).map((row,index)=>({key:String(row.key??index),cells:elements(row.props.children),props:row.props}));
+ const columns:TableColumnsType<UiRow>=headers.map((header,index)=>({
+ key:String(index),title:header.props.children,width:cols[index]?.props.style?.width||cols[index]?.props.width||header.props.style?.width,
+ onHeaderCell:()=>{const {children,...rest}=header.props;return rest;},
+ render:(_,row)=>row.cells[index]?.props.children,
+ onCell:row=>{const cell=row.cells[index];if(!cell)return {colSpan:0};const {children,...rest}=cell.props;return rest;},
+ }));
+ return <Table<UiRow> {...props as React.ComponentProps<typeof Table<UiRow>>} className={className+" station-ui-table"} style={style} columns={columns} dataSource={data} pagination={false} size="medium" onRow={row=>{const {children,...rest}=row.props;return rest;}} locale={{emptyText:"暂无数据"}} />;
+}
import * as XLSX from "xlsx";
import { PDFParse } from "pdf-parse";
import sharp from "sharp";
+import { applySiteTemplate, saveSiteMenu, summarizeAssetStructure, validateSiteParent, siteDashboardCatalog, selectDomainDashboard, saveSiteApplication, saveStandardPageSelection } from "./lib/site-workspace.mjs";
-const PORT = 37823;
+const PORT = Number(process.env.STATIONBUILDER_DB_PORT || 37823);
const PROJECT_DIR = dirname(fileURLToPath(import.meta.url));
const DB_DIR = join(PROJECT_DIR, "data", "db");
-const DB_PATH = join(DB_DIR, "StationBuilder.sqlite");
+const DB_PATH = process.env.STATIONBUILDER_DB_PATH || join(DB_DIR, "StationBuilder.sqlite");
const UPLOAD_DIR = join(DB_DIR, "uploads");
const GEN_DIR = join(DB_DIR, "generated");
const CODEBUDDY = "/root/.local/bin/codebuddy";
ensureColumn("sites", "app_name", "TEXT");
ensureColumn("sites", "app_icon", "TEXT");
ensureColumn("sites", "app_icon_filename", "TEXT");
+ensureColumn("sites", "app_color_scheme", "TEXT DEFAULT 'blue'");
+ensureColumn("menu_items", "source_key", "TEXT");
+ensureColumn("sites", "template_id", "INTEGER");
+ensureColumn("sites", "template_applied_at", "TEXT");
+ensureColumn("sites", "page_config_json", "TEXT");
ensureColumn("domain_templates", "supplement", "TEXT");
ensureColumn("domain_templates", "style_id", "INTEGER");
ensureColumn("domain_pages", "status", "TEXT DEFAULT 'pending'");
}
function getSite(id) {
- const row = db.prepare("SELECT * FROM sites WHERE id = ?").get(id);
+ const row = db.prepare("SELECT s.*,dt.name AS template_name FROM sites s LEFT JOIN domain_templates dt ON dt.id=s.template_id WHERE s.id = ?").get(id);
if (!row) return null;
return row;
}
site.assets = db.prepare("SELECT id,type,count,rule,sort_order FROM assets WHERE site_id = ? ORDER BY sort_order,id").all(id);
site.cubes = db.prepare("SELECT c.id,c.name,c.content,c.status,c.sort_order,c.prompt,gf.id as gen_file_id,gf.filename as gen_filename,gf.created_at as generated_at FROM cubes c LEFT JOIN gen_files gf ON gf.id = c.gen_file_id WHERE c.site_id = ? ORDER BY c.sort_order,c.id").all(id);
site.mu = db.prepare("SELECT page_key,selected FROM mu_selections WHERE site_id = ?").all(id).map(r => r.page_key);
- site.menu = db.prepare("SELECT id,parent_id,label,page_type,ref_id,sort_order FROM menu_items WHERE site_id = ? ORDER BY sort_order,id").all(id);
+ site.menu = db.prepare("SELECT id,parent_id,label,page_type,ref_id,sort_order,source_key FROM menu_items WHERE site_id = ? ORDER BY sort_order,id").all(id);
site.files = db.prepare("SELECT id,filename,mime,size,created_at FROM reference_files WHERE site_id = ?").all(id);
+ try { site.page_config = JSON.parse(site.page_config_json || "null"); } catch { site.page_config = null; }
return site;
}
function normalizeAssetGroup(row) {
}
function siteList() {
return db.prepare(`
- SELECT s.*,
+ SELECT s.*,dt.name AS template_name,
(SELECT COALESCE(SUM(count),0) FROM assets WHERE site_id = s.id) as device_count,
(SELECT COUNT(*) FROM cubes WHERE site_id = s.id) as cube_count,
(SELECT COUNT(*) FROM cubes WHERE site_id = s.id AND gen_file_id IS NOT NULL) as gen_count,
(SELECT COUNT(*) FROM mu_selections WHERE site_id = s.id AND selected = 1) as mu_count
- FROM sites s ORDER BY s.updated_at DESC
+ FROM sites s LEFT JOIN domain_templates dt ON dt.id=s.template_id ORDER BY s.updated_at DESC
`).all();
}
for (const f of await readDirSafe(siteDir)) {
if (f !== "station-package.zip") await rm(join(siteDir, f), { force: true });
}
- const files = db.prepare("SELECT gf.filename,gf.json_text,c.name FROM gen_files gf JOIN cubes c ON c.id = gf.cube_id WHERE gf.site_id = ?").all(siteId);
+ const site = hydrateSite(siteId);
+ const files = db.prepare("SELECT gf.filename,gf.json_text,c.name,c.id AS cube_id FROM gen_files gf JOIN cubes c ON c.id = gf.cube_id WHERE gf.site_id = ?").all(siteId).filter(file=>!site.page_config?.dashboard_ids||site.page_config.dashboard_ids.includes(file.cube_id));
for (const f of files) {
const filename = f.filename || `${f.name}.json`;
await writeFile(join(siteDir, filename), f.json_text, "utf-8");
}
- // also write menu json
- const site = hydrateSite(siteId);
+ // also write menu json and saved asset/page configuration
+ await writeFile(join(siteDir, "asset-groups.json"), JSON.stringify(listAssetGroups(siteId), null, 2), "utf-8");
+ await writeFile(join(siteDir, "page-config.json"), JSON.stringify({mu:site.mu,...site.page_config}, null, 2), "utf-8");
await writeFile(join(siteDir, "menu-config.json"), JSON.stringify(site.menu, null, 2), "utf-8");
const zipPath = join(siteDir, "station-package.zip");
await new Promise((resolve, reject) => {
- const proc = spawn("zip", ["-r", zipPath, "."], { cwd: siteDir, stdio: "ignore" });
+ const proc = spawn("zip", ["-FS", "-r", zipPath, "."], { cwd: siteDir, stdio: "ignore" });
proc.on("error", reject);
proc.on("close", code => (code === 0 ? resolve() : reject(new Error("zip exit " + code))));
});
}
}
}
- const cubes = db.prepare("SELECT * FROM cubes WHERE site_id = ? ORDER BY sort_order,id").all(siteId);
+ const cubes = db.prepare("SELECT * FROM cubes WHERE site_id = ? ORDER BY sort_order,id").all(siteId).filter(cube=>!site.page_config?.dashboard_ids||site.page_config.dashboard_ids.includes(cube.id));
// 实时转发模型输出(text_delta)作为只读过程日志
const onEvent = (o) => {
if (o && o.type === "stream_event" && o.event?.type === "content_block_delta" && o.event.delta?.type === "text_delta" && o.event.delta.text) {
async function handleCreateSiteFromTemplate(req, res, tmplId) {
const dt = db.prepare("SELECT dt.*, d.name AS domain_name FROM domain_templates dt JOIN domains d ON d.id=dt.domain_id WHERE dt.id=?").get(tmplId);
if (!dt) return respondError(res, 404, "领域方案不存在");
- // 创建站点
+ if (dt.status !== "available") return respondError(res, 400, "该领域模板当前不可用");
+ db.exec("SAVEPOINT create_template_site");
+ try {
const info = db.prepare("INSERT INTO sites (name, domain, description, supplement, status) VALUES (?,?,?,?,?)").run(
(dt.name || "") + " - 站点", dt.domain_name || "", dt.description || "", dt.supplement || "", "draft"
);
const siteId = info.lastInsertRowid;
- // 复制模板资产到站点
- const tmplAssets = db.prepare("SELECT device_template_id, count, rule, sort_order FROM domain_template_assets WHERE domain_template_id=? ORDER BY sort_order,id").all(tmplId);
- const insertAsset = db.prepare("INSERT INTO assets (site_id, type, count, rule, sort_order) VALUES (?,?,?,?,?)");
- for (const a of tmplAssets) {
- insertAsset.run(siteId, String(a.device_template_id), a.count || 0, a.rule || "", a.sort_order ?? 0);
- }
- if (tmplAssets.length === 0) {
- for (let i = 0; i < 4; i++) insertAsset.run(siteId, "", 0, "", i);
- }
- // 复制模板 MU 页面选择
- const muKeys = db.prepare("SELECT page_key FROM domain_template_mu WHERE domain_template_id=?").all(tmplId).map(r => r.page_key);
- if (muKeys.length > 0) {
- db.prepare("UPDATE sites SET mu=? WHERE id=?").run(JSON.stringify(muKeys), siteId);
- }
- // 复制 style_id
- if (dt.style_id != null) {
- db.prepare("UPDATE sites SET style_id=? WHERE id=?").run(dt.style_id, siteId);
- }
+ applySiteTemplate(db, Number(siteId), tmplId);
+ db.exec("RELEASE create_template_site");
const site = hydrateSite(siteId);
respondJson(res, 200, site);
+ } catch (error) {
+ db.exec("ROLLBACK TO create_template_site; RELEASE create_template_site");
+ throw error;
+ }
}
// ===== 领域方案 AI 补全 / Cube 生成(复用站点侧提示词构造与生成逻辑)=====
const mSiteAssets = path.match(/^\/api\/sites\/(\d+)\/assets$/);
const mSiteAssetGroups = path.match(/^\/api\/sites\/(\d+)\/asset-groups$/);
const mSiteAssetGroup = path.match(/^\/api\/sites\/(\d+)\/asset-groups\/(\d+)$/);
+ const mSiteApplyTemplate = path.match(/^\/api\/sites\/(\d+)\/apply-template$/);
+ const mSiteDomainDashboards = path.match(/^\/api\/sites\/(\d+)\/domain-dashboards$/);
const mSiteMu = path.match(/^\/api\/sites\/(\d+)\/mu$/);
const mSiteCubes = path.match(/^\/api\/sites\/(\d+)\/cubes$/);
const mSiteCube = path.match(/^\/api\/sites\/(\d+)\/cubes\/(\d+)$/);
if (req.method === "GET") return respondJson(res, 200, siteList());
if (req.method === "POST") {
const body = await parseBody(req);
- const info = db.prepare("INSERT INTO sites (name, domain, description, supplement, status, parent_id) VALUES (?,?,?,?,'draft',?)").run(body.name || "", body.domain || "", body.description || "", body.supplement || "", body.parent_id != null ? Number(body.parent_id) : null);
+ if (body.hierarchy_only && !String(body.name || "").trim()) return respondError(res, 400, "请填写站点名称");
+ const parentId = validateSiteParent(db, null, body.parent_id);
+ const templateId = body.template_id ? Number(body.template_id) : null;
+ if (templateId && !db.prepare("SELECT id FROM domain_templates WHERE id=? AND status='available'").get(templateId)) return respondError(res, 400, "请选择可用的领域模板");
+ const info = db.prepare("INSERT INTO sites (name, domain, description, supplement, status, parent_id,template_id) VALUES (?,?,?,?,'draft',?,?)").run(body.name || "", body.domain || "", body.description || "", body.supplement || "", parentId, templateId);
// insert default empty assets
const siteId = info.lastInsertRowid;
const insertAsset = db.prepare("INSERT INTO assets (site_id, type, count, rule, sort_order) VALUES (?,?,?,?,?)");
- for (let i = 0; i < 4; i++) insertAsset.run(siteId, "", 0, "", i);
+ if (!body.hierarchy_only) for (let i = 0; i < 4; i++) insertAsset.run(siteId, "", 0, "", i);
return respondJson(res, 200, hydrateSite(siteId));
}
if (req.method === "DELETE") {
const body = await parseBody(req);
const ids = Array.isArray(body.ids) ? body.ids.map(Number).filter(n => Number.isInteger(n) && n > 0) : [];
+ const selectedIds=new Set(ids);
+ for(const id of ids){const children=db.prepare("SELECT id FROM sites WHERE parent_id=?").all(id);if(children.some(child=>!selectedIds.has(child.id)))return respondError(res,409,"该站点仍有下级站点,请先调整其上级或删除下级站点");}
for (const id of ids) {
db.prepare("DELETE FROM sites WHERE id=?").run(id);
await rm(join(GEN_DIR, String(id)), { recursive: true, force: true });
}
if (req.method === "PUT") {
const body = await parseBody(req);
- db.prepare("UPDATE sites SET name=?, domain=?, description=?, supplement=?, style_id=?, parent_id=?, app_name=?, app_icon=?, app_icon_filename=?, updated_at=CURRENT_TIMESTAMP WHERE id=?").run(
- body.name ?? "", body.domain ?? "", body.description ?? "", body.supplement ?? "",
- body.style_id != null ? Number(body.style_id) : null,
- body.parent_id != null ? Number(body.parent_id) : null,
- body.app_name ?? null, body.app_icon ?? null, body.app_icon_filename ?? null,
+ const current = getSite(siteId);
+ if (!current) return respondError(res, 404, "站点不存在");
+ const next = { ...current, ...body };
+ const parentId = validateSiteParent(db, siteId, next.parent_id);
+ const templateId = next.template_id ? Number(next.template_id) : null;
+ if (templateId && templateId !== current.template_id && !db.prepare("SELECT id FROM domain_templates WHERE id=? AND status='available'").get(templateId)) return respondError(res, 400, "请选择可用的领域模板");
+ if (current.template_applied_at && templateId !== current.template_id) return respondError(res, 409, "已应用模板的站点,请通过模板配置入口更换模板");
+ db.prepare("UPDATE sites SET name=?, domain=?, description=?, supplement=?, style_id=?, parent_id=?, app_name=?, app_icon=?, app_icon_filename=?,template_id=?,page_config_json=?, updated_at=CURRENT_TIMESTAMP WHERE id=?").run(
+ next.name ?? "", next.domain ?? "", next.description ?? "", next.supplement ?? "",
+ next.style_id != null ? Number(next.style_id) : null,
+ parentId,
+ next.app_name ?? null, next.app_icon ?? null, next.app_icon_filename ?? null,
+ templateId, body.page_config !== undefined ? JSON.stringify(body.page_config) : current.page_config_json ?? null,
siteId
);
return respondJson(res, 200, hydrateSite(siteId));
return respondJson(res, 200, db.prepare("SELECT * FROM assets WHERE site_id=? ORDER BY sort_order,id").all(siteId));
}
}
+ if (mSiteDomainDashboards) {
+ const siteId = Number(mSiteDomainDashboards[1]);
+ if (req.method === "GET") return respondJson(res, 200, siteDashboardCatalog(db, siteId));
+ if (req.method === "PUT") {
+ const body = await parseBody(req);
+ selectDomainDashboard(db, siteId, body.page_id, body.selected);
+ return respondJson(res, 200, { site: hydrateSite(siteId), catalog: siteDashboardCatalog(db, siteId) });
+ }
+ }
+ if (mSiteApplyTemplate && req.method === "POST") {
+ const body = await parseBody(req);
+ const siteId = Number(mSiteApplyTemplate[1]);
+ applySiteTemplate(db, siteId, Number(body.template_id), body.replace === true);
+ return respondJson(res, 200, hydrateSite(siteId));
+ }
if (mSiteAssetGroups) {
const siteId = Number(mSiteAssetGroups[1]);
if (!getSite(siteId)) return respondError(res, 404, "站点不存在");
const body = await parseBody(req);
const name = String(body.name ?? current.name).trim();
if (!name) return respondError(res, 400, "资产组名称不能为空");
+ db.exec("SAVEPOINT save_asset_group");
+ try {
db.prepare("UPDATE asset_groups SET name=?,category=?,template_name=?,structure_json=?,updated_at=CURRENT_TIMESTAMP WHERE id=? AND site_id=?").run(
name,
String(body.category ?? current.category).trim() || "站点资产",
siteId
);
db.prepare("UPDATE sites SET updated_at=CURRENT_TIMESTAMP WHERE id=?").run(siteId);
+ summarizeAssetStructure(db, siteId, Array.isArray(body.structure) ? body.structure : JSON.parse(current.structure_json || "[]"));
+ db.exec("RELEASE save_asset_group");
+ } catch(error) { db.exec("ROLLBACK TO save_asset_group; RELEASE save_asset_group"); throw error; }
return respondJson(res, 200, normalizeAssetGroup(db.prepare("SELECT * FROM asset_groups WHERE id=?").get(groupId)));
}
if (req.method === "DELETE") {
if (req.method === "GET") return respondJson(res, 200, db.prepare("SELECT page_key,selected FROM mu_selections WHERE site_id=?").all(siteId));
if (req.method === "PUT") {
const body = await parseBody(req);
- db.prepare("DELETE FROM mu_selections WHERE site_id=?").run(siteId);
- const insert = db.prepare("INSERT INTO mu_selections (site_id, page_key, selected) VALUES (?,?,?)");
- for (const key of body.selected || []) insert.run(siteId, key, 1);
- db.prepare("UPDATE sites SET updated_at=CURRENT_TIMESTAMP WHERE id=?").run(siteId);
- return respondJson(res, 200, { ok: true });
+ saveStandardPageSelection(db, siteId, body.selected || []);
+ return respondJson(res, 200, hydrateSite(siteId));
}
}
if (mSiteMenu) {
if (req.method === "GET") return respondJson(res, 200, db.prepare("SELECT * FROM menu_items WHERE site_id=? ORDER BY sort_order,id").all(siteId));
if (req.method === "PUT") {
const body = await parseBody(req);
- db.prepare("DELETE FROM menu_items WHERE site_id=?").run(siteId);
- const insert = db.prepare("INSERT INTO menu_items (site_id, parent_id, label, page_type, ref_id, sort_order) VALUES (?,?,?,?,?,?)");
- for (let i = 0; i < (body.menu || []).length; i++) {
- const m = body.menu[i];
- insert.run(siteId, m.parent_id || null, m.label, m.page_type || null, m.ref_id || null, i);
- }
- db.prepare("UPDATE sites SET updated_at=CURRENT_TIMESTAMP WHERE id=?").run(siteId);
- return respondJson(res, 200, { ok: true });
+ const menu = body.application ? saveSiteApplication(db, siteId, body.menu || [], body.application, MU_TREE.flatMap(node=>(node.tabs||[]).filter(tab=>typeof tab==="string"||!tab.disabled).map(tab=>`${node.key}/${typeof tab==="string"?tab:tab.label}`))) : saveSiteMenu(db, siteId, body.menu || []);
+ return respondJson(res, 200, { ok: true, menu });
}
}
if (mSiteFiles) {
respondError(res, 404, "not found");
} catch (e) {
console.error("[db-sidecar error]", e);
- respondError(res, 500, e.message || "internal error");
+ respondError(res, e.status || 500, e.message || "internal error");
}
}
--- /dev/null
+<!doctype html>
+<html lang="zh-CN">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>StationBuilder 建站工具</title>
+ </head>
+ <body>
+ <div id="root"></div>
+ <script type="module" src="/src/main.tsx"></script>
+ </body>
+</html>
--- /dev/null
+import { randomUUID } from "node:crypto";
+
+function workspaceError(message, status = 400) {
+ const error = new Error(message);
+ error.status = status;
+ return error;
+}
+
+function readPageConfig(site) {
+ try { return JSON.parse(site.page_config_json || "null") || {}; } catch { return {}; }
+}
+
+function domainFamily(name) {
+ if (/^水务/.test(name || "")) return "water";
+ if (/AIDC|数据中心/i.test(name || "")) return "aidc";
+ if (/^新能源/.test(name || "")) return "energy";
+ return name || "";
+}
+
+export function siteDashboardCatalog(db, siteId) {
+ const site = db.prepare("SELECT * FROM sites WHERE id=?").get(siteId);
+ if (!site) throw workspaceError("站点不存在", 404);
+ const templates = db.prepare("SELECT dt.*,d.name AS domain_name FROM domain_templates dt JOIN domains d ON d.id=dt.domain_id WHERE dt.status='available' ORDER BY dt.id").all();
+ let candidates = site.template_id ? templates.filter(item => item.id === site.template_id) : templates.filter(item => item.domain_name === site.domain);
+ // Older stations use labels such as “水务,污水厂”. Only infer when there is one unambiguous template.
+ if (!site.template_id && !candidates.length) {
+ const family = templates.filter(item => domainFamily(item.domain_name) === domainFamily(site.domain));
+ if (family.length === 1) candidates = family;
+ }
+ const links = readPageConfig(site).domain_dashboard_links || {};
+ const cubes = db.prepare("SELECT * FROM cubes WHERE site_id=? ORDER BY sort_order,id").all(siteId);
+ const used = new Set();
+ const pages = candidates.flatMap(template => Array.from(new Map(db.prepare("SELECT * FROM domain_pages WHERE domain_template_id=? ORDER BY id").all(template.id).filter(page => {
+ if (!page.name || page.name === "新页面") return false;
+ try { const json = JSON.parse(page.json_text); return json && typeof json === "object" && !Array.isArray(json) && Object.keys(json).length > 0; } catch { return false; }
+ }).map(page => [page.name.trim(), page])).values()).map(page => {
+ const cube = cubes.find(item => item.id === Number(links[page.id]) && !used.has(item.id)) || cubes.find(item => item.name === page.name && !used.has(item.id));
+ if (cube) used.add(cube.id);
+ return { ...page, template_name: template.name, cube_id: cube?.id ?? null };
+ }));
+ return { template_name: candidates.map(item => item.name).join(" / "), pages, local_cubes: cubes.filter(cube => !used.has(cube.id)) };
+}
+
+export function selectDomainDashboard(db, siteId, pageId, selected) {
+ if (!Number.isSafeInteger(pageId) || typeof selected !== "boolean") throw workspaceError("请选择有效的领域仪表板");
+ const catalog = siteDashboardCatalog(db, siteId);
+ const page = catalog.pages.find(item => item.id === pageId);
+ if (!page) throw workspaceError("该仪表板不属于当前站点的领域模板", 404);
+ const site = db.prepare("SELECT * FROM sites WHERE id=?").get(siteId);
+ const config = readPageConfig(site);
+ const ids = new Set(config.dashboard_ids ?? db.prepare("SELECT id FROM cubes WHERE site_id=?").all(siteId).map(cube => cube.id));
+ const links = { ...(config.domain_dashboard_links || {}) };
+ db.exec("SAVEPOINT select_domain_dashboard");
+ try {
+ let cubeId = page.cube_id;
+ if (selected && !cubeId) {
+ const sort = (db.prepare("SELECT MAX(sort_order) AS n FROM cubes WHERE site_id=?").get(siteId).n ?? -1) + 1;
+ cubeId = Number(db.prepare("INSERT INTO cubes (site_id,name,content,status,sort_order) VALUES (?,?,?,'generated',?)").run(siteId, page.name, page.description || "", sort).lastInsertRowid);
+ const fileId = Number(db.prepare("INSERT INTO gen_files (site_id,cube_id,filename,json_text) VALUES (?,?,?,?)").run(siteId, cubeId, `${page.name}.json`, page.json_text).lastInsertRowid);
+ db.prepare("UPDATE cubes SET gen_file_id=? WHERE id=?").run(fileId, cubeId);
+ }
+ if (cubeId) { links[pageId] = cubeId; selected ? ids.add(cubeId) : ids.delete(cubeId); }
+ db.prepare("UPDATE sites SET page_config_json=?,updated_at=CURRENT_TIMESTAMP WHERE id=?").run(JSON.stringify({ ...config, dashboard_ids: [...ids], twin_ids: config.twin_ids || [], domain_dashboard_links: links }), siteId);
+ db.exec("RELEASE select_domain_dashboard");
+ } catch (error) {
+ db.exec("ROLLBACK TO select_domain_dashboard; RELEASE select_domain_dashboard");
+ throw error;
+ }
+}
+
+export function validateSiteParent(db, siteId, parentId) {
+ if (parentId == null || parentId === "") return null;
+ const parent = Number(parentId);
+ if (!Number.isSafeInteger(parent) || parent <= 0) throw workspaceError("请选择有效的上级站点");
+ const visited = new Set(siteId == null ? [] : [Number(siteId)]);
+ let current = parent;
+ while (current != null) {
+ if (visited.has(current)) throw workspaceError("上级站点不能是自身或其下级站点");
+ visited.add(current);
+ const row = db.prepare("SELECT id,parent_id FROM sites WHERE id=?").get(current);
+ if (!row) throw workspaceError("上级站点不存在", 404);
+ current = row.parent_id == null ? null : Number(row.parent_id);
+ }
+ return parent;
+}
+
+export function saveSiteMenu(db, siteId, items) {
+ if (!Array.isArray(items)) throw workspaceError("菜单格式不正确");
+ const ids = new Set();
+ const normalized = items.map((item, index) => {
+ const id = item.id ?? `new-${index}`;
+ if (ids.has(String(id))) throw workspaceError("菜单节点编号重复");
+ ids.add(String(id));
+ if (!String(item.label || "").trim()) throw workspaceError("请填写菜单名称");
+ return { ...item, id };
+ });
+ const byId = new Map(normalized.map(item => [String(item.id), item]));
+ for (const item of normalized) {
+ const visited = new Set([String(item.id)]);
+ let parent = item.parent_id;
+ while (parent != null && parent !== 0) {
+ const key = String(parent);
+ if (visited.has(key)) throw workspaceError("菜单分组不能形成循环");
+ visited.add(key);
+ const parentItem = byId.get(key);
+ if (!parentItem || parentItem.page_type) throw workspaceError("请选择有效的菜单分组");
+ parent = parentItem.parent_id;
+ }
+ }
+ db.exec("SAVEPOINT save_site_menu");
+ try {
+ db.prepare("DELETE FROM menu_items WHERE site_id=?").run(siteId);
+ const insert = db.prepare("INSERT INTO menu_items (site_id,parent_id,label,page_type,ref_id,sort_order,source_key) VALUES (?,NULL,?,?,?,?,?)");
+ const mapping = new Map();
+ normalized.forEach((item, index) => {
+ const result = insert.run(siteId, item.label.trim(), item.page_type || null, item.ref_id ?? null, index, item.source_key || null);
+ mapping.set(String(item.id), Number(result.lastInsertRowid));
+ });
+ for (const item of normalized) {
+ if (item.parent_id != null && item.parent_id !== 0) {
+ db.prepare("UPDATE menu_items SET parent_id=? WHERE id=? AND site_id=?").run(mapping.get(String(item.parent_id)), mapping.get(String(item.id)), siteId);
+ }
+ }
+ db.prepare("UPDATE sites SET updated_at=CURRENT_TIMESTAMP WHERE id=?").run(siteId);
+ db.exec("RELEASE save_site_menu");
+ return db.prepare("SELECT * FROM menu_items WHERE site_id=? ORDER BY sort_order,id").all(siteId);
+ } catch (error) {
+ db.exec("ROLLBACK TO save_site_menu; RELEASE save_site_menu");
+ throw error;
+ }
+}
+
+export function saveStandardPageSelection(db, siteId, selected) {
+ const site = db.prepare("SELECT * FROM sites WHERE id=?").get(siteId);
+ if (!site) throw workspaceError("站点不存在", 404);
+ if (!Array.isArray(selected) || selected.some(key => typeof key !== "string")) throw workspaceError("请选择有效的标准页面");
+ db.exec("SAVEPOINT save_standard_selection");
+ try {
+ db.prepare("DELETE FROM mu_selections WHERE site_id=?").run(siteId);
+ const insert = db.prepare("INSERT INTO mu_selections (site_id,page_key,selected) VALUES (?,?,1)");
+ for (const key of new Set(selected)) insert.run(siteId,key);
+ db.prepare("UPDATE sites SET page_config_json=?,updated_at=CURRENT_TIMESTAMP WHERE id=?").run(JSON.stringify({...readPageConfig(site),standard_pages_initialized:true}),siteId);
+ db.exec("RELEASE save_standard_selection");
+ } catch(error) { db.exec("ROLLBACK TO save_standard_selection; RELEASE save_standard_selection");throw error; }
+}
+
+export function saveSiteApplication(db, siteId, menu, application, defaultStandardKeys = []) {
+ const site = db.prepare("SELECT * FROM sites WHERE id=?").get(siteId);
+ if (!site) throw workspaceError("站点不存在", 404);
+ const name = String(application.app_name || "").trim();
+ if (!name || name.length > 100) throw workspaceError("请填写应用名称,最多 100 个字符");
+ const scheme = application.app_color_scheme || "blue";
+ if (!["blue", "green", "orange", "purple", "red"].includes(scheme)) throw workspaceError("请选择有效的色系");
+ const icon = application.app_icon || null;
+ if (icon && (typeof icon !== "string" || !/^data:image\/(png|jpeg|svg\+xml);base64,[A-Za-z0-9+/=]+$/.test(icon) || Buffer.from(icon.split(",")[1], "base64").length > 100 * 1024)) throw workspaceError("Icon 仅支持 PNG、JPG、SVG,且不大于 100 KB");
+ if (!Array.isArray(menu)) throw workspaceError("菜单格式不正确");
+ const config = readPageConfig(site);
+ const storedStandardKeys = db.prepare("SELECT page_key FROM mu_selections WHERE site_id=? AND selected=1").all(siteId).map(item=>item.page_key);
+ const standardKeys = storedStandardKeys.length || config.standard_pages_initialized ? storedStandardKeys : defaultStandardKeys;
+ const cubeIds = config.dashboard_ids ?? db.prepare("SELECT id FROM cubes WHERE site_id=?").all(siteId).map(cube => cube.id);
+ const expected = [
+ ...standardKeys.map(key => `mu:${key}`),
+ ...db.prepare("SELECT id FROM cubes WHERE site_id=?").all(siteId).filter(item => cubeIds.includes(item.id)).map(item => `cube:${item.id}`),
+ ...(config.twin_ids || []).map(id => `sc:${id}`),
+ ];
+ const actual = menu.filter(item => item.page_type).map(item => `${item.page_type}:${item.ref_id}`);
+ if (actual.length !== expected.length || new Set(actual).size !== actual.length || expected.some(key => !actual.includes(key))) throw workspaceError("页面选择已变化,请重新进入菜单配置同步第四步的选择", 409);
+ const roots = menu.filter(item => item.parent_id == null);
+ if (roots[0]?.label !== "数字孪生" || roots[0]?.page_type || roots[1]?.label !== "领域监控" || roots[1]?.page_type) throw workspaceError("数字孪生和领域监控必须是前两个一级菜单");
+ for (const item of menu) {
+ if ((item.page_type === "sc" && item.parent_id !== roots[0].id) || (item.page_type === "cube" && item.parent_id !== roots[1].id)) throw workspaceError("领域页面必须位于对应一级菜单下");
+ if (item.page_type === "mu" && [roots[0].id, roots[1].id].includes(item.parent_id)) throw workspaceError("标准功能页面须保留自己的菜单分组");
+ }
+ db.exec("SAVEPOINT save_site_application");
+ try {
+ saveStandardPageSelection(db, siteId, standardKeys);
+ const saved = saveSiteMenu(db, siteId, menu);
+ db.prepare("UPDATE sites SET app_name=?,app_icon=?,app_icon_filename=?,app_color_scheme=?,updated_at=CURRENT_TIMESTAMP WHERE id=?").run(name, icon, icon ? String(application.app_icon_filename || "").slice(0,255) : null, scheme, siteId);
+ db.exec("RELEASE save_site_application");
+ return saved;
+ } catch (error) {
+ db.exec("ROLLBACK TO save_site_application; RELEASE save_site_application");
+ throw error;
+ }
+}
+
+export function summarizeAssetStructure(db, siteId, structure) {
+ const summary = new Map();
+ const visit = nodes => {
+ for (const node of nodes || []) {
+ if (node.node_type === "device") {
+ const type = node.template_name || node.name || "未分类设备";
+ summary.set(type, (summary.get(type) || 0) + 1);
+ }
+ visit(node.children);
+ }
+ };
+ visit(structure);
+ const oldRules = new Map(db.prepare("SELECT type,rule FROM assets WHERE site_id=?").all(siteId).map(item => [item.type, item.rule]));
+ db.prepare("DELETE FROM assets WHERE site_id=?").run(siteId);
+ const insert = db.prepare("INSERT INTO assets (site_id,type,count,rule,sort_order) VALUES (?,?,?,?,?)");
+ let index = 0;
+ for (const [type, count] of summary) insert.run(siteId, type, count, oldRules.get(type) || "", index++);
+}
+
+export function applySiteTemplate(db, siteId, templateId, replace = false) {
+ const site = db.prepare("SELECT * FROM sites WHERE id=?").get(siteId);
+ if (!site) throw workspaceError("站点不存在", 404);
+ const template = db.prepare("SELECT dt.*,d.name AS domain_name FROM domain_templates dt JOIN domains d ON d.id=dt.domain_id WHERE dt.id=?").get(templateId);
+ if (!template) throw workspaceError("领域模板不存在", 404);
+ if (template.status !== "available") throw workspaceError("该领域模板当前不可用");
+ const hasConfiguration = site.template_applied_at ||
+ db.prepare("SELECT COUNT(*) AS n FROM assets WHERE site_id=? AND type<>''").get(siteId).n ||
+ db.prepare("SELECT COUNT(*) AS n FROM cubes WHERE site_id=?").get(siteId).n ||
+ db.prepare("SELECT COUNT(*) AS n FROM mu_selections WHERE site_id=?").get(siteId).n ||
+ db.prepare("SELECT COUNT(*) AS n FROM menu_items WHERE site_id=?").get(siteId).n ||
+ db.prepare("SELECT COUNT(*) AS n FROM asset_groups WHERE site_id=?").get(siteId).n;
+ if (hasConfiguration && !replace) throw workspaceError("当前站点已有配置,请确认后再替换模板", 409);
+ const assets = db.prepare("SELECT a.*,d.name AS device_name FROM domain_template_assets a JOIN device_templates d ON d.id=a.device_template_id WHERE a.domain_template_id=? ORDER BY a.sort_order,a.id").all(templateId);
+ const mu = db.prepare("SELECT page_key FROM domain_template_mu WHERE domain_template_id=?").all(templateId);
+ const pages = db.prepare("SELECT * FROM domain_pages WHERE domain_template_id=? ORDER BY id").all(templateId).filter(page => page.name && page.name !== "新页面" && page.json_text && !["", "{}", "null"].includes(page.json_text.trim()));
+ const menu = db.prepare("SELECT * FROM template_menu_items WHERE domain_template_id=? ORDER BY sort_order,id").all(templateId);
+ db.exec("SAVEPOINT apply_site_template");
+ try {
+ for (const table of ["assets", "menu_items", "cubes", "gen_files", "mu_selections", "asset_groups"]) db.prepare(`DELETE FROM ${table} WHERE site_id=?`).run(siteId);
+ const assetInsert = db.prepare("INSERT INTO assets (site_id,type,count,rule,sort_order) VALUES (?,?,?,?,?)");
+ assets.forEach((asset, index) => assetInsert.run(siteId, asset.device_name, asset.count || 0, asset.rule || "", index));
+ const makeNode = (name, type, templateName, children = []) => ({ id: `node-${randomUUID()}`, name, alias: name, asset_type: type === "device" ? "Machine" : "Group", node_type: type, template_name: templateName, description: "", properties: [], bindings: [], children });
+ const root = makeNode(site.name || "未命名站点", "group", template.name, assets.map(asset => makeNode(asset.device_name, "group", asset.device_name,
+ Array.from({ length: Math.max(0, Math.min(10000, Number(asset.count) || 0)) }, (_, index) => {
+ const rule = asset.rule || `${asset.device_name}-##`;
+ const name = rule.includes("#") ? rule.replace(/#+/g, match => String(index + 1).padStart(match.length, "0")) : `${rule}-${String(index + 1).padStart(2, "0")}`;
+ return makeNode(name, "device", asset.device_name);
+ }))));
+ db.prepare("INSERT INTO asset_groups (site_id,name,category,template_name,structure_json) VALUES (?,?,?,?,?)").run(siteId, `${site.name || "未命名站点"}资产组`, site.domain || template.domain_name, template.name, JSON.stringify([root]));
+ const muInsert = db.prepare("INSERT INTO mu_selections (site_id,page_key,selected) VALUES (?,?,1)");
+ for (const item of mu) muInsert.run(siteId, item.page_key);
+ const pageMapping = new Map();
+ const dashboardIds = [];
+ for (const [index, page] of pages.entries()) {
+ const cubeId = Number(db.prepare("INSERT INTO cubes (site_id,name,content,status,sort_order) VALUES (?,?,?,'generated',?)").run(siteId, page.name, page.description || "", index).lastInsertRowid);
+ const fileId = Number(db.prepare("INSERT INTO gen_files (site_id,cube_id,filename,json_text) VALUES (?,?,?,?)").run(siteId, cubeId, `${page.name}.json`, page.json_text).lastInsertRowid);
+ db.prepare("UPDATE cubes SET gen_file_id=? WHERE id=?").run(fileId, cubeId);
+ pageMapping.set(String(page.id), cubeId);
+ dashboardIds.push(cubeId);
+ }
+ const copiedMenu = menu.map(item => ({ ...item, ref_id: item.page_type === "cube" ? (pageMapping.get(String(item.ref_id)) ?? null) : item.ref_id }));
+ saveSiteMenu(db, siteId, copiedMenu.filter(item => item.page_type !== "cube" || item.ref_id != null));
+ db.prepare("UPDATE sites SET template_id=?,template_applied_at=CURRENT_TIMESTAMP,page_config_json=?,status='draft',updated_at=CURRENT_TIMESTAMP WHERE id=?").run(templateId, JSON.stringify({ dashboard_ids: dashboardIds, twin_ids: [], standard_pages_initialized: true }), siteId);
+ db.exec("RELEASE apply_site_template");
+ return { template, assetCount: assets.length, dashboardCount: dashboardIds.length };
+ } catch (error) {
+ db.exec("ROLLBACK TO apply_site_template; RELEASE apply_site_template");
+ throw error;
+ }
+}
"name": "istation-site-builder",
"version": "0.1.0",
"dependencies": {
+ "@ant-design/icons": "^6.0.0",
+ "antd": "^6.6.4",
"drizzle-orm": "0.45.2",
"mammoth": "^1.12.1",
"next": "16.2.6",
"pdf-parse": "^2.4.5",
"react": "19.2.6",
"react-dom": "19.2.6",
+ "sharp": "0.34.5",
"xlsx": "^0.18.5"
},
"devDependencies": {
"drizzle-kit": "0.31.10",
"eslint": "9.39.4",
"eslint-config-next": "16.2.6",
+ "less": "^4.9.0",
"react-server-dom-webpack": "19.2.6",
"tailwindcss": "4.2.1",
"typescript": "5.9.3",
"vinext": "0.0.50",
"vite": "8.0.13",
+ "vite-plugin-svgr": "^5.2.0",
"wrangler": "4.92.0"
},
"engines": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@ant-design/colors": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz",
+ "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/fast-color": "^3.0.0"
+ }
+ },
+ "node_modules/@ant-design/cssinjs": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz",
+ "integrity": "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "@emotion/hash": "^0.8.0",
+ "@emotion/unitless": "^0.7.5",
+ "@rc-component/util": "^1.4.0",
+ "clsx": "^2.1.1",
+ "csstype": "^3.1.3",
+ "stylis": "^4.3.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@ant-design/cssinjs-utils": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.2.tgz",
+ "integrity": "sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/cssinjs": "^2.1.2",
+ "@babel/runtime": "^7.23.2",
+ "@rc-component/util": "^1.4.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/@ant-design/fast-color": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz",
+ "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
+ "node_modules/@ant-design/icons": {
+ "version": "6.3.4",
+ "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.3.4.tgz",
+ "integrity": "sha512-kDoUlppczVyCUTFthF6cHmPzMBYqNZQhu9j6EyIX/YIdM9j9CZNZoJUjzVw7/1v1VO8WwVMmPp15r/uwIAS0AQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/colors": "^8.0.1",
+ "@ant-design/icons-svg": "^4.6.0",
+ "@rc-component/util": "^1.11.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@ant-design/icons-svg": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.6.0.tgz",
+ "integrity": "sha512-PRomU725ABMf/lnQp5HiB7my1kjEbFY0D10N4lXYxK6TIB1gKjIVD5MRThpDaezLgw1D774J8eOeeDB0M6wHrQ==",
+ "license": "MIT"
+ },
+ "node_modules/@ant-design/react-slick": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-2.0.0.tgz",
+ "integrity": "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.4",
+ "clsx": "^2.1.1",
+ "json2mq": "^0.2.0",
+ "throttle-debounce": "^5.0.0"
+ },
+ "peerDependencies": {
+ "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
"node": ">=6.0.0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
+ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
+ "node_modules/@emotion/hash": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
+ "license": "MIT"
+ },
"node_modules/@esbuild-kit/core-utils": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
- "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=18"
"cpu": [
"arm64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"cpu": [
"x64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"cpu": [
"arm64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"cpu": [
"x64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"cpu": [
"arm"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"cpu": [
"arm64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"cpu": [
"ppc64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"cpu": [
"riscv64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"cpu": [
"s390x"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"cpu": [
"x64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"cpu": [
"arm64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"cpu": [
"x64"
],
- "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"cpu": [
"arm"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"cpu": [
"arm64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"cpu": [
"ppc64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"cpu": [
"riscv64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"cpu": [
"s390x"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"cpu": [
"x64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"cpu": [
"arm64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"cpu": [
"x64"
],
- "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"cpu": [
"wasm32"
],
- "dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"cpu": [
"arm64"
],
- "dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"cpu": [
"ia32"
],
- "dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"cpu": [
"x64"
],
- "dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"dev": true,
"license": "MIT"
},
- "node_modules/@resvg/resvg-wasm": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/@resvg/resvg-wasm/-/resvg-wasm-2.4.0.tgz",
- "integrity": "sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==",
- "dev": true,
- "license": "MPL-2.0",
+ "node_modules/@rc-component/async-validator": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-6.0.0.tgz",
+ "integrity": "sha512-D3AGQwdyE58gmvx6waVSXJ80JGO+IY5L2O8HDnSOex7JNlzB3GuN/4hyHNTdhy2qtOhkpbIjmeAN3tL993wKbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.4"
+ },
"engines": {
- "node": ">= 10"
+ "node": ">=14.x"
}
},
- "node_modules/@rolldown/binding-android-arm64": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz",
- "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "node_modules/@rc-component/cascader": {
+ "version": "1.22.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.22.0.tgz",
+ "integrity": "sha512-SffrA57aS9oub3VuI7ajPhJTPtaNxngSvtRhD40Rd8dwJ5vfWPSrVanWgeepdWFGBt7EHftIK5RUU0u3rCTwWw==",
"license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "dependencies": {
+ "@rc-component/select": "~1.10.0",
+ "@rc-component/tree": "~1.4.0",
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
}
},
- "node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz",
- "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "node_modules/@rc-component/checkbox": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-2.0.0.tgz",
+ "integrity": "sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==",
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "dependencies": {
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
}
},
- "node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz",
- "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
+ "node_modules/@rc-component/collapse": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.2.0.tgz",
+ "integrity": "sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==",
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/motion": "^1.1.4",
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
}
},
- "node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz",
- "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
+ "node_modules/@rc-component/color-picker": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-3.1.1.tgz",
+ "integrity": "sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==",
"license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "dependencies": {
+ "@ant-design/fast-color": "^3.0.1",
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
}
},
- "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz",
- "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
+ "node_modules/@rc-component/context": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-2.0.2.tgz",
+ "integrity": "sha512-uiGpAlblCNlziHPwj4S4Iy/oemeuz/hR03mbiEjTCXwsqOIN3BOzsRMyDwpyO5Fm0vIEEJRUf9ZtbRLbhksuTA==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "dependencies": {
+ "@rc-component/util": "^1.11.0"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
}
},
- "node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz",
- "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "node_modules/@rc-component/dialog": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/dialog/-/dialog-1.10.0.tgz",
+ "integrity": "sha512-eDukNlz9vNszAGv7i3zKXdxEd3wgVmNxuJijYt8zvTh17QwTu8KK/bdURRd/lU4qaMzhO1HKKmMrwOnkaw0BvQ==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "dependencies": {
+ "@rc-component/motion": "^1.3.3",
+ "@rc-component/portal": "^2.1.0",
+ "@rc-component/util": "^1.9.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
}
},
- "node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz",
- "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "node_modules/@rc-component/drawer": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/drawer/-/drawer-1.4.2.tgz",
+ "integrity": "sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "dependencies": {
+ "@rc-component/motion": "^1.1.4",
+ "@rc-component/portal": "^2.1.3",
+ "@rc-component/util": "^1.9.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
}
},
- "node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz",
- "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
+ "node_modules/@rc-component/dropdown": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.3.tgz",
+ "integrity": "sha512-YTST/N6kpqpDz3IMuM/PSSZnrDpSOA6dgHv12gPA90ZTSLv2CoqkZ0+9NtwTY6BeO7dstPblSic2QJg7dSFy/g==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "dependencies": {
+ "@rc-component/trigger": "^3.0.0",
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.11.0",
+ "react-dom": ">=16.11.0"
}
},
- "node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz",
- "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
+ "node_modules/@rc-component/form": {
+ "version": "1.8.6",
+ "resolved": "https://registry.npmjs.org/@rc-component/form/-/form-1.8.6.tgz",
+ "integrity": "sha512-1EXVsSKPZC6kGrIqxVck7kAWM6T65b6et/n5wIcyiaIa41VrDshD0nVLHoBDEIZr2ATQsOP6icb4XQkNFMLBAw==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "@rc-component/async-validator": "^6.0.0",
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
}
},
- "node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz",
- "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
+ "node_modules/@rc-component/image": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/image/-/image-1.10.0.tgz",
+ "integrity": "sha512-BjeZCRQ+hw+4WAhvrw8rJvy5fckA2xpf/X2XQEOABUHvLTNB9inB98X3Mp54jYQ7g10DfWERQWHXeC4ylxp1Uw==",
"license": "MIT",
- "optional": true,
- "os": [
+ "dependencies": {
+ "@rc-component/motion": "^1.0.0",
+ "@rc-component/portal": "^2.1.2",
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/input": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/input/-/input-1.3.1.tgz",
+ "integrity": "sha512-iFvTUT9W+JC/MSin2aGAk8NqsVlTzcExNC9DZariON1IWirju9NoNeEk47an4Q8iHazkoVI/y1LnDi88+CPcig==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/resize-observer": "^1.1.1",
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@rc-component/input-number": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/input-number/-/input-number-1.6.2.tgz",
+ "integrity": "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/mini-decimal": "^1.0.1",
+ "@rc-component/util": "^1.4.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/listy": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rc-component/listy/-/listy-1.2.3.tgz",
+ "integrity": "sha512-IXiMjV5s0rczLBlfh7G5nB4M3365mrEeedjwKtf5I+Ns3PqRUsebR2h5u8CeFarsVfLUPC2I5p0h09TNoOWyvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/motion": "^1.1.4",
+ "@rc-component/portal": "^2.0.0",
+ "@rc-component/resize-observer": "^1.0.0",
+ "@rc-component/util": "^1.3.1",
+ "@rc-component/virtual-list": "^1.4.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/mentions": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/mentions/-/mentions-1.12.0.tgz",
+ "integrity": "sha512-v5MBx8zDcqCT+uybJgKpcx3Fgrvj0m3u+A/15RlSBaMx12lLoJZTTYggC3pbYYfohcKujWWUyBz6O6Gp2vGiNA==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/input": "~1.3.1",
+ "@rc-component/menu": "~1.5.0",
+ "@rc-component/trigger": "^3.0.0",
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/menu": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/menu/-/menu-1.5.0.tgz",
+ "integrity": "sha512-RjvzPsZkwVEg3xu+/jDDqDeUCBJAnWylONFlGeQgg5enlNAnlpybqhaMGE/UPEIvr6hL90PGhjFBguJqj0Qb2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/motion": "^1.1.4",
+ "@rc-component/overflow": "^1.0.0",
+ "@rc-component/trigger": "^3.0.0",
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/mini-decimal": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.4.tgz",
+ "integrity": "sha512-xiuXcaCwyOWpD8a8scdExFl+bntNphAW8XeenL1ig2en0AAZY0Pcp4pC0dI22qJ+NvxKn9RoNIoRdqYU3BLH4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
+ "node_modules/@rc-component/motion": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@rc-component/motion/-/motion-1.3.3.tgz",
+ "integrity": "sha512-Xh3IszxvlSv3/PLYFyC2UZi9LNB83yOnkB/LNmRzaypZLvkhqUIPS7MQpGZcCMWrNsXV2p6YTSWbSGvFpEle9A==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.11.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/mutate-observer": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz",
+ "integrity": "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/notification": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@rc-component/notification/-/notification-2.0.8.tgz",
+ "integrity": "sha512-MTRA3b8eHE14gh5R9nlXzbWZEDEBFVkEHj/FI1VOXw2zrSnSxqbuuU3drsq+k53e6MHwtegoFay1tnNYhtl61Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/motion": "^1.1.4",
+ "@rc-component/util": "^1.11.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/overflow": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/overflow/-/overflow-1.0.1.tgz",
+ "integrity": "sha512-syfmgAABaHCnCDzPwHZ/2tuvIcpOO3jefYZMmfkN+pmo8HKTzsfhS57vxo4ksPdN0By+uWVJhJWNFozNBxi2eA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "@rc-component/resize-observer": "^1.0.1",
+ "@rc-component/util": "^1.4.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/pagination": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.4.0.tgz",
+ "integrity": "sha512-CW1g7P9V8u+e8JQdUsl2RWg+GCsoee0mtJjZUCCxn/vb3jzOwDKm6hAdwddHCVBfWJ58eGUBZz3IvnU8rRktjw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/picker": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.12.2.tgz",
+ "integrity": "sha512-ZB1A+tzTQkgtR4flPrAMvxIAOelIIOOOsp22fU2Nad6PbZcJacnyGJUJqUH7ZS9mwxeUwurf1pLMKXrVsK1tlA==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/overflow": "^1.0.0",
+ "@rc-component/resize-observer": "^1.0.0",
+ "@rc-component/trigger": "^3.6.15",
+ "@rc-component/util": "^1.13.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=12.x"
+ },
+ "peerDependencies": {
+ "date-fns": ">= 2.x",
+ "dayjs": ">= 1.x",
+ "luxon": ">= 3.x",
+ "moment": ">= 2.x",
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ },
+ "peerDependenciesMeta": {
+ "date-fns": {
+ "optional": true
+ },
+ "dayjs": {
+ "optional": true
+ },
+ "luxon": {
+ "optional": true
+ },
+ "moment": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rc-component/portal": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-2.2.1.tgz",
+ "integrity": "sha512-ck+r1kW/JSv0wxPji3KN2ss9K6Z0qqwusw/mf/0JobXhZ8hC2ejZwCJObW/SvDi0uhA0VzmCnx0CaCci95tcmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.11.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=12.x"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/progress": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rc-component/progress/-/progress-1.0.3.tgz",
+ "integrity": "sha512-Q1nPLIvKr95el/q76yaBh5nv1hqCsWaHQb8U7vXEDMefEhHgM7n3CPoN+o4LHnTyANQHmMFgoS/naAT4Bhxuaw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/qrcode": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-2.0.0.tgz",
+ "integrity": "sha512-aAv3QhPP1xyafuTZOxub6a54pCeBnN3IwQkpETrBtthq4BL5IgxnCbuoBWPDpdLw1y1j6BgBUCAKV92+yX06Dw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/rate": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/rate/-/rate-1.0.1.tgz",
+ "integrity": "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/resize-observer": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.1.2.tgz",
+ "integrity": "sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/segmented": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/segmented/-/segmented-1.4.0.tgz",
+ "integrity": "sha512-NZlpKGSplOYlmF5ukUF1fCLr0ns5WfjNoqMlMkf4AHP0iu90wyiP7hn2qZKRHEuRlG4CvEEz/lF9qRiYnLTofQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^8.0.0",
+ "@rc-component/motion": "^1.1.4",
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@rc-component/segmented/node_modules/@babel/runtime": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-8.0.5.tgz",
+ "integrity": "sha512-7NK+Lz3spQ52XsUGTxIEVU4jYN2/dIaX8sTxRFAUtYmttYZnVh3aehiihv+/Gb7+duMNHl2OrFcBQRyOHScxpg==",
+ "license": "MIT"
+ },
+ "node_modules/@rc-component/select": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.10.1.tgz",
+ "integrity": "sha512-H+yQsl+qED9NilQ3g6zdpsMwUgwVjrcMTkNHAWRVU/MoNCYgTbDgU+MIMgZDK+rVdd2JUfI/MkysMcZZ0cyQKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/overflow": "^1.0.0",
+ "@rc-component/trigger": "^3.0.0",
+ "@rc-component/util": "^1.11.1",
+ "@rc-component/virtual-list": "^1.2.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/@rc-component/slider": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/slider/-/slider-1.1.1.tgz",
+ "integrity": "sha512-LSzgWGYDgeCDgR4r1XlU29gbYws6HpLnvJd/uMhLeW/vQgxldeR+Wb4uzHDCHiYEbr1bnEHWdjkPxjJRHxuiig==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/steps": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rc-component/steps/-/steps-1.2.3.tgz",
+ "integrity": "sha512-/b9gHcvDGjTDWJZW7+1kKFvzs0jrEeQ8mFvvIgX+h7xNT0FsOYODJYKjyJR4fPbvMBCfCOEqaBK66HBxov5BQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/switch": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rc-component/switch/-/switch-1.0.3.tgz",
+ "integrity": "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/table": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.11.1.tgz",
+ "integrity": "sha512-OWdS6DMmeWb7bJBGqPxYZpQbzBlBiXZUu2sqo6Ii7Sjs9GeK1IsrXrWk26SL2c6KEseabswdxrRj7WUm9LdECw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/context": "^2.0.1",
+ "@rc-component/resize-observer": "^1.0.0",
+ "@rc-component/util": "^1.11.1",
+ "@rc-component/virtual-list": "^1.0.1",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/tabs": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.13.0.tgz",
+ "integrity": "sha512-3FXr+9ZII8tFLWuDWrgjRaxZd2mazkKJAGRFEfVmp4r1qnBglOlxQ78l54Td1aj6/iFaaEReKSAF2HToztU5uw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/dropdown": "~1.0.0",
+ "@rc-component/menu": "~1.5.0",
+ "@rc-component/motion": "^1.1.3",
+ "@rc-component/resize-observer": "^1.0.0",
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/tooltip": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/tooltip/-/tooltip-1.5.2.tgz",
+ "integrity": "sha512-VLq4BclU3LgRe46g2hTr2bdsM8tFYs7dVwpuD88rPNNg4lCGe8mBSS4FiRzYzS+ffnZ46y4SI8KYKo9K7qBnhw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/trigger": "^3.10.0",
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/tour": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-2.4.0.tgz",
+ "integrity": "sha512-aui4r4TqmTzwaBgcQxHYep8kM8PTjZFufjokObpy35KfFeZ0k9ArquWFZqegQlH24P14t+F0qO0mGTgzlav1yg==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/portal": "^2.2.0",
+ "@rc-component/trigger": "^3.0.0",
+ "@rc-component/util": "^1.7.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/tree": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/tree/-/tree-1.4.0.tgz",
+ "integrity": "sha512-dGsJGDJQedA0BqqVgj3F8BvHXTSZijyhTXdbAdkcx8lynzZkty/CV3Z3LOm/fxz+BCfl3dfGiAQpb7Q5XNvl0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/motion": "^1.0.0",
+ "@rc-component/util": "^1.11.1",
+ "@rc-component/virtual-list": "^1.2.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=10.x"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/@rc-component/tree-select": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.16.1.tgz",
+ "integrity": "sha512-a1Oi6EJhqAhdOxxupdJi6fP0RPHMKn5TcfkX2+llaQ4lF4nwfH7b6SCHcnsybaa2s+pk1yZYwVyeOYkDnEBRdg==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/select": "~1.10.0",
+ "@rc-component/tree": "~1.4.0",
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/@rc-component/trigger": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.10.1.tgz",
+ "integrity": "sha512-mXlDN0IXdtV8Yqqm8195ECCyrbmfvvfKvwVvSlH0+qvKD6BUF8gRhEjSy0FOcD1+CcDRHgTiX99LoxfQrmh3Cw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/motion": "^1.3.3",
+ "@rc-component/portal": "^2.2.1",
+ "@rc-component/resize-observer": "^1.1.2",
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/upload": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/upload/-/upload-1.1.1.tgz",
+ "integrity": "sha512-GvYWSKeaJTOxxC5p6+nOSadzfvXA1h8C/iHFPFZX+szH3JUXrvs+DLiW8YUTBgvMh8m63mJeHrlYlJzAlg+pDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/util": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.13.0.tgz",
+ "integrity": "sha512-IXf2zBfZrbQGT+G8LxnoICDBGnMHIPIiq5HCRn3EDfKUx28x1rnbZBj46ULJf4sM8ImTG1SepqfNBdxZziqQ4w==",
+ "license": "MIT",
+ "dependencies": {
+ "is-mobile": "^5.0.0",
+ "react-is": "^19.2.7"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/util/node_modules/react-is": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.3.0.tgz",
+ "integrity": "sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==",
+ "license": "MIT"
+ },
+ "node_modules/@rc-component/virtual-list": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.5.1.tgz",
+ "integrity": "sha512-boqHxdtyWC88u8quYgEO49bcBy5fzRiOcnBge+N4nLzs2k8hUQ/yw7JE9dM6yCBE4jSm5YSHVCVMS+suBuJGKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^8.0.0",
+ "@rc-component/resize-observer": "^1.0.1",
+ "@rc-component/util": "^1.4.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/virtual-list/node_modules/@babel/runtime": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-8.0.5.tgz",
+ "integrity": "sha512-7NK+Lz3spQ52XsUGTxIEVU4jYN2/dIaX8sTxRFAUtYmttYZnVh3aehiihv+/Gb7+duMNHl2OrFcBQRyOHScxpg==",
+ "license": "MIT"
+ },
+ "node_modules/@resvg/resvg-wasm": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-wasm/-/resvg-wasm-2.4.0.tgz",
+ "integrity": "sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz",
+ "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz",
+ "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz",
+ "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz",
+ "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz",
+ "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz",
+ "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz",
+ "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz",
+ "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz",
+ "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz",
+ "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
"linux"
],
"engines": {
"dev": true,
"license": "MIT"
},
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz",
+ "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/pluginutils/node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
"dev": true,
"license": "CC0-1.0"
},
+ "node_modules/@svgr/babel-plugin-add-jsx-attribute": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
+ "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-remove-jsx-attribute": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz",
+ "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz",
+ "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz",
+ "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-svg-dynamic-title": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz",
+ "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-svg-em-dimensions": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz",
+ "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-transform-react-native-svg": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz",
+ "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-transform-svg-component": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz",
+ "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-preset": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz",
+ "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@svgr/babel-plugin-add-jsx-attribute": "8.0.0",
+ "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0",
+ "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0",
+ "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0",
+ "@svgr/babel-plugin-svg-dynamic-title": "8.0.0",
+ "@svgr/babel-plugin-svg-em-dimensions": "8.0.0",
+ "@svgr/babel-plugin-transform-react-native-svg": "8.1.0",
+ "@svgr/babel-plugin-transform-svg-component": "8.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/core": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz",
+ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.21.3",
+ "@svgr/babel-preset": "8.1.0",
+ "camelcase": "^6.2.0",
+ "cosmiconfig": "^8.1.3",
+ "snake-case": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/hast-util-to-babel-ast": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz",
+ "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.21.3",
+ "entities": "^4.4.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/plugin-jsx": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz",
+ "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.21.3",
+ "@svgr/babel-preset": "8.1.0",
+ "@svgr/hast-util-to-babel-ast": "8.0.0",
+ "svg-parser": "^2.0.4"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@svgr/core": "*"
+ }
+ },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/antd": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/antd/-/antd-6.6.4.tgz",
+ "integrity": "sha512-nKFi2oY+KrsfAkU8/KwgS0MPKpryIPTVIgoLQRGiT/BcCMakBf5+G8MPy90RdHjEwb0/bulNJV1dxLhHm9cg7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/colors": "^8.0.1",
+ "@ant-design/cssinjs": "^2.1.2",
+ "@ant-design/cssinjs-utils": "^2.1.2",
+ "@ant-design/fast-color": "^3.0.1",
+ "@ant-design/icons": "^6.3.4",
+ "@ant-design/react-slick": "~2.0.0",
+ "@babel/runtime": "^7.29.2",
+ "@rc-component/cascader": "~1.22.0",
+ "@rc-component/checkbox": "~2.0.0",
+ "@rc-component/collapse": "~1.2.0",
+ "@rc-component/color-picker": "~3.1.1",
+ "@rc-component/dialog": "~1.10.0",
+ "@rc-component/drawer": "~1.4.2",
+ "@rc-component/dropdown": "~1.0.3",
+ "@rc-component/form": "~1.8.6",
+ "@rc-component/image": "~1.10.0",
+ "@rc-component/input": "~1.3.1",
+ "@rc-component/input-number": "~1.6.2",
+ "@rc-component/listy": "~1.2.3",
+ "@rc-component/mentions": "~1.12.0",
+ "@rc-component/menu": "~1.5.0",
+ "@rc-component/motion": "^1.3.3",
+ "@rc-component/mutate-observer": "^2.0.1",
+ "@rc-component/notification": "~2.0.8",
+ "@rc-component/pagination": "~1.4.0",
+ "@rc-component/picker": "~1.12.2",
+ "@rc-component/progress": "~1.0.3",
+ "@rc-component/qrcode": "~2.0.0",
+ "@rc-component/rate": "~1.0.1",
+ "@rc-component/resize-observer": "^1.1.2",
+ "@rc-component/segmented": "~1.4.0",
+ "@rc-component/select": "~1.10.1",
+ "@rc-component/slider": "~1.1.1",
+ "@rc-component/steps": "~1.2.3",
+ "@rc-component/switch": "~1.0.3",
+ "@rc-component/table": "~1.11.1",
+ "@rc-component/tabs": "~1.13.0",
+ "@rc-component/tooltip": "~1.5.2",
+ "@rc-component/tour": "~2.4.0",
+ "@rc-component/tree": "~1.4.0",
+ "@rc-component/tree-select": "~1.16.1",
+ "@rc-component/trigger": "^3.10.1",
+ "@rc-component/upload": "~1.1.1",
+ "@rc-component/util": "^1.13.0",
+ "clsx": "^2.1.1",
+ "dayjs": "^1.11.11",
+ "scroll-into-view-if-needed": "^3.1.0",
+ "throttle-debounce": "^5.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ant-design"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"node": ">=6"
}
},
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/camelize": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/codepage": {
"version": "1.15.0",
"resolved": "http://mirrors.tencent.com/npm/codepage/-/codepage-1.15.0.tgz",
"license": "MIT",
"peer": true
},
+ "node_modules/compute-scroll-into-view": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
+ "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
+ "license": "MIT"
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"url": "https://opencollective.com/express"
}
},
+ "node_modules/copy-anything": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
+ "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-what": "^4.1.8"
+ },
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "http://mirrors.tencent.com/npm/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
+ "node_modules/cosmiconfig": {
+ "version": "8.3.6",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
+ "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0",
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "http://mirrors.tencent.com/npm/crc-32/-/crc-32-1.2.2.tgz",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/damerau-levenshtein": {
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/dayjs": {
+ "version": "1.11.23",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz",
+ "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==",
+ "license": "MIT"
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "devOptional": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
"node": ">=0.10.0"
}
},
+ "node_modules/dot-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
+ "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "no-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
"node_modules/drizzle-kit": {
"version": "0.31.10",
"resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.10.tgz",
"node": ">=10.13.0"
}
},
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/errno": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
+ "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "prr": "~1.0.1"
+ },
+ "bin": {
+ "errno": "cli.js"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
"node_modules/error-stack-parser-es": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz",
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/is-async-function": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-mobile": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz",
+ "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==",
+ "license": "MIT"
+ },
"node_modules/is-negative-zero": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-what": {
+ "version": "4.1.16",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
+ "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
"node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"dev": true,
"license": "MIT"
},
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"dev": true,
"license": "MIT"
},
+ "node_modules/json2mq": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
+ "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "string-convert": "^0.2.0"
+ }
+ },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"node": ">=0.10"
}
},
+ "node_modules/less": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/less/-/less-4.9.1.tgz",
+ "integrity": "sha512-orp15PfJvvNDIqJdVWzMI9Sjpjp3VTiw3sfvbB+67LlISTEn8uVT2EdYSuyl02BLvaftv6sdk9Umnxmm5rckmg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "copy-anything": "^3.0.5",
+ "parse-node-version": "^1.0.1"
+ },
+ "bin": {
+ "lessc": "bin/lessc"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "errno": "^0.1.1",
+ "graceful-fs": "^4.1.2",
+ "make-dir": "^5.1.0",
+ "mime": "^1.4.1",
+ "needle": "^3.1.0",
+ "probe-image-size": "^7.2.3",
+ "source-map": "~0.6.0"
+ }
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"unicode-trie": "^2.0.0"
}
},
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/loader-runner": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz",
"underscore": "^1.13.1"
}
},
+ "node_modules/lower-case": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
+ "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.3"
+ }
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
+ "node_modules/make-dir": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-5.1.0.tgz",
+ "integrity": "sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/mammoth": {
"version": "1.12.1",
"resolved": "http://mirrors.tencent.com/npm/mammoth/-/mammoth-1.12.1.tgz",
"node": ">=8.6"
}
},
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"dev": true,
"license": "MIT"
},
+ "node_modules/needle": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/needle/-/needle-3.5.0.tgz",
+ "integrity": "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "iconv-lite": "^0.6.3",
+ "sax": "^1.2.4"
+ },
+ "bin": {
+ "needle": "bin/needle"
+ },
+ "engines": {
+ "node": ">= 4.4.x"
+ }
+ },
"node_modules/neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/no-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
+ "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lower-case": "^2.0.2",
+ "tslib": "^2.0.3"
+ }
+ },
"node_modules/node-exports-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
"hex-rgb": "^4.1.0"
}
},
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parse-node-version": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
+ "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"dev": true,
"license": "MIT"
},
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"node": ">= 0.8.0"
}
},
+ "node_modules/probe-image-size": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.4.0.tgz",
+ "integrity": "sha512-cdEprVtZxV+awMde9X+4jILBFYh4CARxVrQaMl4wY4YcPWbul9jntXrIW95NInBDyJwcVUP3U0T6yukN8rMBaQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "lodash.merge": "^4.6.2",
+ "needle": "^2.5.2",
+ "stream-parser": "~0.3.1"
+ }
+ },
+ "node_modules/probe-image-size/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/probe-image-size/node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/probe-image-size/node_modules/needle": {
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz",
+ "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "debug": "^3.2.6",
+ "iconv-lite": "^0.4.4",
+ "sax": "^1.2.4"
+ },
+ "bin": {
+ "needle": "bin/needle"
+ },
+ "engines": {
+ "node": ">= 4.4.x"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "http://mirrors.tencent.com/npm/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"react-is": "^16.13.1"
}
},
+ "node_modules/prr": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+ "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/satori": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/satori/-/satori-0.16.0.tgz",
"node": ">=16"
}
},
+ "node_modules/sax": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz",
+ "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "optional": true,
+ "engines": {
+ "node": ">=11.0.0"
+ }
+ },
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"license": "MIT",
"peer": true
},
+ "node_modules/scroll-into-view-if-needed": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
+ "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "compute-scroll-into-view": "^3.0.2"
+ }
+ },
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
- "devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
- "devOptional": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/snake-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
+ "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dot-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"node": ">= 0.4"
}
},
+ "node_modules/stream-parser": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz",
+ "integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "debug": "2"
+ }
+ },
+ "node_modules/stream-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/stream-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "http://mirrors.tencent.com/npm/string_decoder/-/string_decoder-1.1.1.tgz",
"safe-buffer": "~5.1.0"
}
},
+ "node_modules/string-convert": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
+ "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==",
+ "license": "MIT"
+ },
"node_modules/string.prototype.codepointat": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
}
}
},
+ "node_modules/stylis": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz",
+ "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==",
+ "license": "MIT"
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/svg-parser": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.1.0.tgz",
+ "integrity": "sha512-bwLf38YmY+TDYHJw1Ex0Co8c4yeXuJAo8YnXGZrscxrvYoVVIvLeniEkV1Ks/54VteMnL4FtyN1+P+TZJdUPmQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/tailwindcss": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz",
}
}
},
+ "node_modules/throttle-debounce": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
+ "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.22"
+ }
+ },
"node_modules/tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
"node": ">= 6"
}
},
+ "node_modules/vite-plugin-svgr": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-5.2.0.tgz",
+ "integrity": "sha512-qj2eAKF8C6PZWemVTvQA0xgQIcP1hHU6Buh7fl6BhvayWwnuxE+z417miKxeDvRWbDrupQ1oK99hfxElopJ3sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.3.0",
+ "@svgr/core": "^8.1.0",
+ "@svgr/plugin-jsx": "^8.1.0"
+ },
+ "peerDependencies": {
+ "vite": ">=3.0.0"
+ }
+ },
"node_modules/vite-tsconfig-paths": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-6.1.1.tgz",
"db:generate": "bash scripts/sites-env.sh -- drizzle-kit generate"
},
"dependencies": {
- "@ant-design/icons": "^5.6.1",
- "antd": "^5.29.3",
+ "@ant-design/icons": "^6.0.0",
+ "antd": "^6.6.4",
"drizzle-orm": "0.45.2",
"mammoth": "^1.12.1",
"next": "16.2.6",
"drizzle-kit": "0.31.10",
"eslint": "9.39.4",
"eslint-config-next": "16.2.6",
+ "less": "^4.9.0",
"react-server-dom-webpack": "19.2.6",
"tailwindcss": "4.2.1",
"typescript": "5.9.3",
"vinext": "0.0.50",
"vite": "8.0.13",
+ "vite-plugin-svgr": "^5.2.0",
"wrangler": "4.92.0"
},
"type": "module"
.:
dependencies:
'@ant-design/icons':
- specifier: ^5.6.1
- version: 5.6.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ specifier: ^6.0.0
+ version: 6.3.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
antd:
- specifier: ^5.29.3
- version: 5.29.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ specifier: ^6.6.4
+ version: 6.6.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
drizzle-orm:
specifier: 0.45.2
version: 0.45.2
devDependencies:
'@cloudflare/vite-plugin':
specifier: 1.37.1
- version: 1.37.1(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))(workerd@1.20260515.1)(wrangler@4.92.0)
+ version: 1.37.1(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13))(workerd@1.20260515.1)(wrangler@4.92.0)
'@tailwindcss/postcss':
specifier: 4.2.1
version: 4.2.1
version: 19.2.3(@types/react@19.2.14)
'@vitejs/plugin-react':
specifier: 6.0.2
- version: 6.0.2(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))
+ version: 6.0.2(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13))
'@vitejs/plugin-rsc':
specifier: 0.5.26
- version: 0.5.26(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))
+ version: 0.5.26(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13))
drizzle-kit:
specifier: 0.31.10
version: 0.31.10
eslint-config-next:
specifier: 16.2.6
version: 16.2.6(@typescript-eslint/parser@8.69.0(eslint@9.39.4(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
+ less:
+ specifier: ^4.9.0
+ version: 4.9.1(supports-color@10.2.2)
react-server-dom-webpack:
specifier: 19.2.6
version: 19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26))
version: 5.9.3
vinext:
specifier: 0.0.50
- version: 0.0.50(@vitejs/plugin-react@6.0.2(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)))(@vitejs/plugin-rsc@0.5.26(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)))(next@16.2.6(@babel/core@7.29.7(supports-color@10.2.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(supports-color@10.2.2)(typescript@5.9.3)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))
+ version: 0.0.50(@vitejs/plugin-react@6.0.2(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)))(@vitejs/plugin-rsc@0.5.26(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)))(next@16.2.6(@babel/core@7.29.7(supports-color@10.2.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(supports-color@10.2.2)(typescript@5.9.3)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13))
vite:
specifier: 8.0.13
- version: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)
+ version: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)
+ vite-plugin-svgr:
+ specifier: ^5.2.0
+ version: 5.2.0(supports-color@10.2.2)(typescript@5.9.3)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13))
wrangler:
specifier: 4.92.0
version: 4.92.0
resolution: {integrity: sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==}
engines: {node: '>=10'}
- '@ant-design/colors@7.2.1':
- resolution: {integrity: sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==}
+ '@ant-design/colors@8.0.1':
+ resolution: {integrity: sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==}
- '@ant-design/cssinjs-utils@1.1.3':
- resolution: {integrity: sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==}
+ '@ant-design/cssinjs-utils@2.1.2':
+ resolution: {integrity: sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==}
peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
+ react: '>=18'
+ react-dom: '>=18'
- '@ant-design/cssinjs@1.24.0':
- resolution: {integrity: sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==}
+ '@ant-design/cssinjs@2.1.2':
+ resolution: {integrity: sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==}
peerDependencies:
react: '>=16.0.0'
react-dom: '>=16.0.0'
- '@ant-design/fast-color@2.0.6':
- resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==}
+ '@ant-design/fast-color@3.0.1':
+ resolution: {integrity: sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==}
engines: {node: '>=8.x'}
'@ant-design/icons-svg@4.6.0':
resolution: {integrity: sha512-PRomU725ABMf/lnQp5HiB7my1kjEbFY0D10N4lXYxK6TIB1gKjIVD5MRThpDaezLgw1D774J8eOeeDB0M6wHrQ==}
- '@ant-design/icons@5.6.1':
- resolution: {integrity: sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==}
+ '@ant-design/icons@6.3.4':
+ resolution: {integrity: sha512-kDoUlppczVyCUTFthF6cHmPzMBYqNZQhu9j6EyIX/YIdM9j9CZNZoJUjzVw7/1v1VO8WwVMmPp15r/uwIAS0AQ==}
engines: {node: '>=8'}
peerDependencies:
react: '>=16.0.0'
react-dom: '>=16.0.0'
- '@ant-design/react-slick@1.1.2':
- resolution: {integrity: sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==}
+ '@ant-design/react-slick@2.0.0':
+ resolution: {integrity: sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==}
peerDependencies:
- react: '>=16.9.0'
+ react: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@babel/code-frame@7.29.7':
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
engines: {node: '>=6.9.0'}
+ '@babel/runtime@8.0.5':
+ resolution: {integrity: sha512-7NK+Lz3spQ52XsUGTxIEVU4jYN2/dIaX8sTxRFAUtYmttYZnVh3aehiihv+/Gb7+duMNHl2OrFcBQRyOHScxpg==}
+
'@babel/template@7.29.7':
resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
engines: {node: '>=6.9.0'}
'@poppinss/exception@1.2.3':
resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==}
- '@rc-component/async-validator@5.1.2':
- resolution: {integrity: sha512-WYbrZSjzznU1ekD0qFq2qRxt309VoS61MTG5npnFQlKYcoy9IzU8T+ZCIhq5bGAXRbXysABFWTspicMfmWFwow==}
+ '@rc-component/async-validator@6.0.0':
+ resolution: {integrity: sha512-D3AGQwdyE58gmvx6waVSXJ80JGO+IY5L2O8HDnSOex7JNlzB3GuN/4hyHNTdhy2qtOhkpbIjmeAN3tL993wKbA==}
engines: {node: '>=14.x'}
- '@rc-component/color-picker@2.0.1':
- resolution: {integrity: sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==}
+ '@rc-component/cascader@1.22.0':
+ resolution: {integrity: sha512-SffrA57aS9oub3VuI7ajPhJTPtaNxngSvtRhD40Rd8dwJ5vfWPSrVanWgeepdWFGBt7EHftIK5RUU0u3rCTwWw==}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
+ '@rc-component/checkbox@2.0.0':
+ resolution: {integrity: sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==}
+ peerDependencies:
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ '@rc-component/collapse@1.2.0':
+ resolution: {integrity: sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
+ '@rc-component/color-picker@3.1.1':
+ resolution: {integrity: sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==}
+ peerDependencies:
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ '@rc-component/context@2.0.2':
+ resolution: {integrity: sha512-uiGpAlblCNlziHPwj4S4Iy/oemeuz/hR03mbiEjTCXwsqOIN3BOzsRMyDwpyO5Fm0vIEEJRUf9ZtbRLbhksuTA==}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
+ '@rc-component/dialog@1.10.0':
+ resolution: {integrity: sha512-eDukNlz9vNszAGv7i3zKXdxEd3wgVmNxuJijYt8zvTh17QwTu8KK/bdURRd/lU4qaMzhO1HKKmMrwOnkaw0BvQ==}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
+ '@rc-component/drawer@1.4.2':
+ resolution: {integrity: sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
+ '@rc-component/dropdown@1.0.3':
+ resolution: {integrity: sha512-YTST/N6kpqpDz3IMuM/PSSZnrDpSOA6dgHv12gPA90ZTSLv2CoqkZ0+9NtwTY6BeO7dstPblSic2QJg7dSFy/g==}
+ peerDependencies:
+ react: '>=16.11.0'
+ react-dom: '>=16.11.0'
+
+ '@rc-component/form@1.8.6':
+ resolution: {integrity: sha512-1EXVsSKPZC6kGrIqxVck7kAWM6T65b6et/n5wIcyiaIa41VrDshD0nVLHoBDEIZr2ATQsOP6icb4XQkNFMLBAw==}
+ engines: {node: '>=8.x'}
+ peerDependencies:
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ '@rc-component/image@1.10.0':
+ resolution: {integrity: sha512-BjeZCRQ+hw+4WAhvrw8rJvy5fckA2xpf/X2XQEOABUHvLTNB9inB98X3Mp54jYQ7g10DfWERQWHXeC4ylxp1Uw==}
peerDependencies:
react: '>=16.9.0'
react-dom: '>=16.9.0'
- '@rc-component/context@1.4.0':
- resolution: {integrity: sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==}
+ '@rc-component/input-number@1.6.2':
+ resolution: {integrity: sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==}
peerDependencies:
react: '>=16.9.0'
react-dom: '>=16.9.0'
+ '@rc-component/input@1.3.1':
+ resolution: {integrity: sha512-iFvTUT9W+JC/MSin2aGAk8NqsVlTzcExNC9DZariON1IWirju9NoNeEk47an4Q8iHazkoVI/y1LnDi88+CPcig==}
+ peerDependencies:
+ react: '>=16.0.0'
+ react-dom: '>=16.0.0'
+
+ '@rc-component/listy@1.2.3':
+ resolution: {integrity: sha512-IXiMjV5s0rczLBlfh7G5nB4M3365mrEeedjwKtf5I+Ns3PqRUsebR2h5u8CeFarsVfLUPC2I5p0h09TNoOWyvQ==}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
+ '@rc-component/mentions@1.12.0':
+ resolution: {integrity: sha512-v5MBx8zDcqCT+uybJgKpcx3Fgrvj0m3u+A/15RlSBaMx12lLoJZTTYggC3pbYYfohcKujWWUyBz6O6Gp2vGiNA==}
+ peerDependencies:
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ '@rc-component/menu@1.5.0':
+ resolution: {integrity: sha512-RjvzPsZkwVEg3xu+/jDDqDeUCBJAnWylONFlGeQgg5enlNAnlpybqhaMGE/UPEIvr6hL90PGhjFBguJqj0Qb2Q==}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
'@rc-component/mini-decimal@1.1.4':
resolution: {integrity: sha512-xiuXcaCwyOWpD8a8scdExFl+bntNphAW8XeenL1ig2en0AAZY0Pcp4pC0dI22qJ+NvxKn9RoNIoRdqYU3BLH4w==}
engines: {node: '>=8.x'}
- '@rc-component/mutate-observer@1.1.0':
- resolution: {integrity: sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==}
+ '@rc-component/motion@1.3.3':
+ resolution: {integrity: sha512-Xh3IszxvlSv3/PLYFyC2UZi9LNB83yOnkB/LNmRzaypZLvkhqUIPS7MQpGZcCMWrNsXV2p6YTSWbSGvFpEle9A==}
+ peerDependencies:
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ '@rc-component/mutate-observer@2.0.1':
+ resolution: {integrity: sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==}
+ engines: {node: '>=8.x'}
+ peerDependencies:
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ '@rc-component/notification@2.0.8':
+ resolution: {integrity: sha512-MTRA3b8eHE14gh5R9nlXzbWZEDEBFVkEHj/FI1VOXw2zrSnSxqbuuU3drsq+k53e6MHwtegoFay1tnNYhtl61Q==}
+ engines: {node: '>=8.x'}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
+ '@rc-component/overflow@1.0.1':
+ resolution: {integrity: sha512-syfmgAABaHCnCDzPwHZ/2tuvIcpOO3jefYZMmfkN+pmo8HKTzsfhS57vxo4ksPdN0By+uWVJhJWNFozNBxi2eA==}
+ peerDependencies:
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ '@rc-component/pagination@1.4.0':
+ resolution: {integrity: sha512-CW1g7P9V8u+e8JQdUsl2RWg+GCsoee0mtJjZUCCxn/vb3jzOwDKm6hAdwddHCVBfWJ58eGUBZz3IvnU8rRktjw==}
+ peerDependencies:
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ '@rc-component/picker@1.12.2':
+ resolution: {integrity: sha512-ZB1A+tzTQkgtR4flPrAMvxIAOelIIOOOsp22fU2Nad6PbZcJacnyGJUJqUH7ZS9mwxeUwurf1pLMKXrVsK1tlA==}
+ engines: {node: '>=12.x'}
+ peerDependencies:
+ date-fns: '>= 2.x'
+ dayjs: '>= 1.x'
+ luxon: '>= 3.x'
+ moment: '>= 2.x'
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+ peerDependenciesMeta:
+ date-fns:
+ optional: true
+ dayjs:
+ optional: true
+ luxon:
+ optional: true
+ moment:
+ optional: true
+
+ '@rc-component/portal@2.2.1':
+ resolution: {integrity: sha512-ck+r1kW/JSv0wxPji3KN2ss9K6Z0qqwusw/mf/0JobXhZ8hC2ejZwCJObW/SvDi0uhA0VzmCnx0CaCci95tcmA==}
+ engines: {node: '>=12.x'}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
+ '@rc-component/progress@1.0.3':
+ resolution: {integrity: sha512-Q1nPLIvKr95el/q76yaBh5nv1hqCsWaHQb8U7vXEDMefEhHgM7n3CPoN+o4LHnTyANQHmMFgoS/naAT4Bhxuaw==}
+ peerDependencies:
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ '@rc-component/qrcode@2.0.0':
+ resolution: {integrity: sha512-aAv3QhPP1xyafuTZOxub6a54pCeBnN3IwQkpETrBtthq4BL5IgxnCbuoBWPDpdLw1y1j6BgBUCAKV92+yX06Dw==}
+ engines: {node: '>=8.x'}
+ peerDependencies:
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ '@rc-component/rate@1.0.1':
+ resolution: {integrity: sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==}
+ engines: {node: '>=8.x'}
+ peerDependencies:
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ '@rc-component/resize-observer@1.1.2':
+ resolution: {integrity: sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==}
+ peerDependencies:
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ '@rc-component/segmented@1.4.0':
+ resolution: {integrity: sha512-NZlpKGSplOYlmF5ukUF1fCLr0ns5WfjNoqMlMkf4AHP0iu90wyiP7hn2qZKRHEuRlG4CvEEz/lF9qRiYnLTofQ==}
+ peerDependencies:
+ react: '>=16.0.0'
+ react-dom: '>=16.0.0'
+
+ '@rc-component/select@1.10.1':
+ resolution: {integrity: sha512-H+yQsl+qED9NilQ3g6zdpsMwUgwVjrcMTkNHAWRVU/MoNCYgTbDgU+MIMgZDK+rVdd2JUfI/MkysMcZZ0cyQKw==}
+ engines: {node: '>=8.x'}
+ peerDependencies:
+ react: '*'
+ react-dom: '*'
+
+ '@rc-component/slider@1.1.1':
+ resolution: {integrity: sha512-LSzgWGYDgeCDgR4r1XlU29gbYws6HpLnvJd/uMhLeW/vQgxldeR+Wb4uzHDCHiYEbr1bnEHWdjkPxjJRHxuiig==}
engines: {node: '>=8.x'}
peerDependencies:
react: '>=16.9.0'
react-dom: '>=16.9.0'
- '@rc-component/portal@1.1.2':
- resolution: {integrity: sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==}
+ '@rc-component/steps@1.2.3':
+ resolution: {integrity: sha512-/b9gHcvDGjTDWJZW7+1kKFvzs0jrEeQ8mFvvIgX+h7xNT0FsOYODJYKjyJR4fPbvMBCfCOEqaBK66HBxov5BQA==}
engines: {node: '>=8.x'}
peerDependencies:
react: '>=16.9.0'
react-dom: '>=16.9.0'
- '@rc-component/qrcode@1.1.3':
- resolution: {integrity: sha512-aGv6alnn4HbDEsURzKP+jv13rbi1VxmAYfBNZr5GKF1iohMNWy5tAVoJ1E3cOvzMB1kbUPvCXchM6zSFlRGPhA==}
+ '@rc-component/switch@1.0.3':
+ resolution: {integrity: sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==}
+ peerDependencies:
+ react: '>=16.9.0'
+ react-dom: '>=16.9.0'
+
+ '@rc-component/table@1.11.1':
+ resolution: {integrity: sha512-OWdS6DMmeWb7bJBGqPxYZpQbzBlBiXZUu2sqo6Ii7Sjs9GeK1IsrXrWk26SL2c6KEseabswdxrRj7WUm9LdECw==}
+ engines: {node: '>=8.x'}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
+ '@rc-component/tabs@1.13.0':
+ resolution: {integrity: sha512-3FXr+9ZII8tFLWuDWrgjRaxZd2mazkKJAGRFEfVmp4r1qnBglOlxQ78l54Td1aj6/iFaaEReKSAF2HToztU5uw==}
engines: {node: '>=8.x'}
peerDependencies:
react: '>=16.9.0'
react-dom: '>=16.9.0'
- '@rc-component/tour@1.15.1':
- resolution: {integrity: sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==}
+ '@rc-component/tooltip@1.5.2':
+ resolution: {integrity: sha512-VLq4BclU3LgRe46g2hTr2bdsM8tFYs7dVwpuD88rPNNg4lCGe8mBSS4FiRzYzS+ffnZ46y4SI8KYKo9K7qBnhw==}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
+ '@rc-component/tour@2.4.0':
+ resolution: {integrity: sha512-aui4r4TqmTzwaBgcQxHYep8kM8PTjZFufjokObpy35KfFeZ0k9ArquWFZqegQlH24P14t+F0qO0mGTgzlav1yg==}
engines: {node: '>=8.x'}
peerDependencies:
react: '>=16.9.0'
react-dom: '>=16.9.0'
- '@rc-component/trigger@2.3.1':
- resolution: {integrity: sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==}
+ '@rc-component/tree-select@1.16.1':
+ resolution: {integrity: sha512-a1Oi6EJhqAhdOxxupdJi6fP0RPHMKn5TcfkX2+llaQ4lF4nwfH7b6SCHcnsybaa2s+pk1yZYwVyeOYkDnEBRdg==}
+ peerDependencies:
+ react: '*'
+ react-dom: '*'
+
+ '@rc-component/tree@1.4.0':
+ resolution: {integrity: sha512-dGsJGDJQedA0BqqVgj3F8BvHXTSZijyhTXdbAdkcx8lynzZkty/CV3Z3LOm/fxz+BCfl3dfGiAQpb7Q5XNvl0Q==}
+ engines: {node: '>=10.x'}
+ peerDependencies:
+ react: '*'
+ react-dom: '*'
+
+ '@rc-component/trigger@3.10.1':
+ resolution: {integrity: sha512-mXlDN0IXdtV8Yqqm8195ECCyrbmfvvfKvwVvSlH0+qvKD6BUF8gRhEjSy0FOcD1+CcDRHgTiX99LoxfQrmh3Cw==}
engines: {node: '>=8.x'}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
+ '@rc-component/upload@1.1.1':
+ resolution: {integrity: sha512-GvYWSKeaJTOxxC5p6+nOSadzfvXA1h8C/iHFPFZX+szH3JUXrvs+DLiW8YUTBgvMh8m63mJeHrlYlJzAlg+pDA==}
peerDependencies:
react: '>=16.9.0'
react-dom: '>=16.9.0'
+ '@rc-component/util@1.13.0':
+ resolution: {integrity: sha512-IXf2zBfZrbQGT+G8LxnoICDBGnMHIPIiq5HCRn3EDfKUx28x1rnbZBj46ULJf4sM8ImTG1SepqfNBdxZziqQ4w==}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
+ '@rc-component/virtual-list@1.5.1':
+ resolution: {integrity: sha512-boqHxdtyWC88u8quYgEO49bcBy5fzRiOcnBge+N4nLzs2k8hUQ/yw7JE9dM6yCBE4jSm5YSHVCVMS+suBuJGKA==}
+ engines: {node: '>=8.x'}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
'@resvg/resvg-wasm@2.4.0':
resolution: {integrity: sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==}
engines: {node: '>= 10'}
'@rolldown/pluginutils@1.0.1':
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
+ '@rollup/pluginutils@5.4.0':
+ resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
'@speed-highlight/core@1.2.24':
resolution: {integrity: sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==}
+ '@svgr/babel-plugin-add-jsx-attribute@8.0.0':
+ resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-remove-jsx-attribute@8.0.0':
+ resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0':
+ resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0':
+ resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-svg-dynamic-title@8.0.0':
+ resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-svg-em-dimensions@8.0.0':
+ resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-transform-react-native-svg@8.1.0':
+ resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-transform-svg-component@8.0.0':
+ resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-preset@8.1.0':
+ resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/core@8.1.0':
+ resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==}
+ engines: {node: '>=14'}
+
+ '@svgr/hast-util-to-babel-ast@8.0.0':
+ resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==}
+ engines: {node: '>=14'}
+
+ '@svgr/plugin-jsx@8.1.0':
+ resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@svgr/core': '*'
+
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
- antd@5.29.3:
- resolution: {integrity: sha512-3DdbGCa9tWAJGcCJ6rzR8EJFsv2CtyEbkVabZE14pfgUHfCicWCj0/QzQVLDYg8CPfQk9BH7fHCoTXHTy7MP/A==}
+ antd@6.6.4:
+ resolution: {integrity: sha512-nKFi2oY+KrsfAkU8/KwgS0MPKpryIPTVIgoLQRGiT/BcCMakBf5+G8MPy90RdHjEwb0/bulNJV1dxLhHm9cg7w==}
peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
+ camelcase@6.3.0:
+ resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
+ engines: {node: '>=10'}
+
camelize@1.0.1:
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==}
engines: {node: '>=6.0'}
- classnames@2.5.1:
- resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
-
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
codepage@1.15.0:
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
engines: {node: '>=0.8'}
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
- copy-to-clipboard@3.3.3:
- resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
+ copy-anything@3.0.5:
+ resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
+ engines: {node: '>=12.13'}
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
+ cosmiconfig@8.3.6:
+ resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ typescript: '>=4.9.5'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
crc-32@1.2.2:
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
engines: {node: '>=0.8'}
dayjs@1.11.23:
resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==}
+ debug@2.6.9:
+ resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
+ dot-case@3.0.4:
+ resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
+
drizzle-kit@0.31.10:
resolution: {integrity: sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==}
hasBin: true
resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==}
engines: {node: '>=10.13.0'}
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
+ errno@0.1.8:
+ resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
+ hasBin: true
+
+ error-ex@1.3.4:
+ resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
+
error-stack-parser-es@1.0.5:
resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
+ estree-walker@2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==}
engines: {node: '>=6'}
+ iconv-lite@0.4.24:
+ resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
+ engines: {node: '>=0.10.0'}
+
+ iconv-lite@0.6.3:
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
engines: {node: '>= 0.4'}
+ is-arrayish@0.2.1:
+ resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+
is-async-function@2.1.1:
resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
engines: {node: '>= 0.4'}
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
engines: {node: '>= 0.4'}
+ is-mobile@5.0.0:
+ resolution: {integrity: sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==}
+
is-negative-zero@2.0.3:
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
engines: {node: '>= 0.4'}
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
engines: {node: '>= 0.4'}
+ is-what@4.1.16:
+ resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
+ engines: {node: '>=12.13'}
+
isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+ json-parse-even-better-errors@2.3.1:
+ resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
engines: {node: '>=0.10'}
+ less@4.9.1:
+ resolution: {integrity: sha512-orp15PfJvvNDIqJdVWzMI9Sjpjp3VTiw3sfvbB+67LlISTEn8uVT2EdYSuyl02BLvaftv6sdk9Umnxmm5rckmg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
linebreak@1.1.0:
resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==}
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lop@0.4.2:
resolution: {integrity: sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==}
+ lower-case@2.0.2:
+ resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
+
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+ make-dir@5.1.0:
+ resolution: {integrity: sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==}
+ engines: {node: '>=18'}
+
mammoth@1.12.2:
resolution: {integrity: sha512-MH2vkgafD/2MYUaEOtoXLKrHQZ7yLYTHGQgyLNtYZHiUxU1K1QQ+8qMFquDAfhBm06wSGSws3J+Q9QTsKtR44g==}
engines: {node: '>=12.0.0'}
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
engines: {node: '>= 0.6'}
+ mime@1.6.0:
+ resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
miniflare@4.20260515.0:
resolution: {integrity: sha512-2j0oQWizk1Eu4Cm8tDX7Z+Nsjd0nebIj1TQcQ+Oy1QKeo0Ay9+bdn8wfLAtOj9znDCybDCUlnS1+nYvKXEdfNg==}
engines: {node: '>=22.0.0'}
uglify-js:
optional: true
+ ms@2.0.0:
+ resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ needle@2.9.1:
+ resolution: {integrity: sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==}
+ engines: {node: '>= 4.4.x'}
+ hasBin: true
+
+ needle@3.5.0:
+ resolution: {integrity: sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==}
+ engines: {node: '>= 4.4.x'}
+ hasBin: true
+
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
sass:
optional: true
+ no-case@3.0.4:
+ resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
+
node-exports-info@1.6.2:
resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==}
engines: {node: '>= 0.4'}
parse-css-color@0.2.1:
resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==}
+ parse-json@5.2.0:
+ resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
+ engines: {node: '>=8'}
+
+ parse-node-version@1.0.1:
+ resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
+ engines: {node: '>= 0.10'}
+
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
path-to-regexp@6.3.0:
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
+ path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
+ probe-image-size@7.4.0:
+ resolution: {integrity: sha512-cdEprVtZxV+awMde9X+4jILBFYh4CARxVrQaMl4wY4YcPWbul9jntXrIW95NInBDyJwcVUP3U0T6yukN8rMBaQ==}
+
process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ prr@1.0.1:
+ resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
- rc-cascader@3.34.0:
- resolution: {integrity: sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-checkbox@3.5.0:
- resolution: {integrity: sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==}
+ react-dom@19.2.6:
+ resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==}
peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
+ react: ^19.2.6
- rc-collapse@3.9.0:
- resolution: {integrity: sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
- rc-dialog@9.6.0:
- resolution: {integrity: sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
+ react-is@19.3.0:
+ resolution: {integrity: sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==}
- rc-drawer@7.3.0:
- resolution: {integrity: sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==}
+ react-server-dom-webpack@19.2.6:
+ resolution: {integrity: sha512-762YXjBPc6hw2V16k4x1sdnw0mxghcSPzoiOhefGYjiTguJLCImGBUz6EjznPIfqKhWgLtpaQMlBhp66N8dKrw==}
+ engines: {node: '>=0.10.0'}
peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
+ react: ^19.2.6
+ react-dom: ^19.2.6
+ webpack: ^5.59.0
- rc-dropdown@4.2.1:
- resolution: {integrity: sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==}
- peerDependencies:
- react: '>=16.11.0'
- react-dom: '>=16.11.0'
+ react@19.2.6:
+ resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==}
+ engines: {node: '>=0.10.0'}
- rc-field-form@2.7.1:
- resolution: {integrity: sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==}
- engines: {node: '>=8.x'}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-image@7.12.0:
- resolution: {integrity: sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-input-number@9.5.0:
- resolution: {integrity: sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-input@1.8.0:
- resolution: {integrity: sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==}
- peerDependencies:
- react: '>=16.0.0'
- react-dom: '>=16.0.0'
-
- rc-mentions@2.20.0:
- resolution: {integrity: sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-menu@9.16.1:
- resolution: {integrity: sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-motion@2.9.5:
- resolution: {integrity: sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-notification@5.6.4:
- resolution: {integrity: sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==}
- engines: {node: '>=8.x'}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-overflow@1.5.0:
- resolution: {integrity: sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-pagination@5.1.0:
- resolution: {integrity: sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-picker@4.11.3:
- resolution: {integrity: sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==}
- engines: {node: '>=8.x'}
- peerDependencies:
- date-fns: '>= 2.x'
- dayjs: '>= 1.x'
- luxon: '>= 3.x'
- moment: '>= 2.x'
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
- peerDependenciesMeta:
- date-fns:
- optional: true
- dayjs:
- optional: true
- luxon:
- optional: true
- moment:
- optional: true
-
- rc-progress@4.0.0:
- resolution: {integrity: sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-rate@2.13.1:
- resolution: {integrity: sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==}
- engines: {node: '>=8.x'}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-resize-observer@1.4.3:
- resolution: {integrity: sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-segmented@2.7.1:
- resolution: {integrity: sha512-izj1Nw/Dw2Vb7EVr+D/E9lUTkBe+kKC+SAFSU9zqr7WV2W5Ktaa9Gc7cB2jTqgk8GROJayltaec+DBlYKc6d+g==}
- peerDependencies:
- react: '>=16.0.0'
- react-dom: '>=16.0.0'
-
- rc-select@14.16.8:
- resolution: {integrity: sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==}
- engines: {node: '>=8.x'}
- peerDependencies:
- react: '*'
- react-dom: '*'
-
- rc-slider@11.1.9:
- resolution: {integrity: sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==}
- engines: {node: '>=8.x'}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-steps@6.0.1:
- resolution: {integrity: sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==}
- engines: {node: '>=8.x'}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-switch@4.1.0:
- resolution: {integrity: sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-table@7.54.0:
- resolution: {integrity: sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==}
- engines: {node: '>=8.x'}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-tabs@15.7.0:
- resolution: {integrity: sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==}
- engines: {node: '>=8.x'}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-textarea@1.10.2:
- resolution: {integrity: sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-tooltip@6.4.0:
- resolution: {integrity: sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-tree-select@5.27.0:
- resolution: {integrity: sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==}
- peerDependencies:
- react: '*'
- react-dom: '*'
-
- rc-tree@5.13.1:
- resolution: {integrity: sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==}
- engines: {node: '>=10.x'}
- peerDependencies:
- react: '*'
- react-dom: '*'
-
- rc-upload@4.11.0:
- resolution: {integrity: sha512-ZUyT//2JAehfHzjWowqROcwYJKnZkIUGWaTE/VogVrepSl7AFNbQf4+zGfX4zl9Vrj/Jm8scLO0R6UlPDKK4wA==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-util@5.44.4:
- resolution: {integrity: sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- rc-virtual-list@3.19.2:
- resolution: {integrity: sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==}
- engines: {node: '>=8.x'}
- peerDependencies:
- react: '>=16.9.0'
- react-dom: '>=16.9.0'
-
- react-dom@19.2.6:
- resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==}
- peerDependencies:
- react: ^19.2.6
-
- react-is@16.13.1:
- resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
-
- react-is@18.3.1:
- resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
-
- react-server-dom-webpack@19.2.6:
- resolution: {integrity: sha512-762YXjBPc6hw2V16k4x1sdnw0mxghcSPzoiOhefGYjiTguJLCImGBUz6EjznPIfqKhWgLtpaQMlBhp66N8dKrw==}
- engines: {node: '>=0.10.0'}
- peerDependencies:
- react: ^19.2.6
- react-dom: ^19.2.6
- webpack: ^5.59.0
-
- react@19.2.6:
- resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==}
- engines: {node: '>=0.10.0'}
-
- readable-stream@2.3.8:
- resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
+ readable-stream@2.3.8:
+ resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
reflect.getprototypeof@1.0.10:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
- resize-observer-polyfill@1.5.1:
- resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
-
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
engines: {node: '>= 0.4'}
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
satori@0.16.0:
resolution: {integrity: sha512-ZvHN3ygzZ8FuxjSNB+mKBiF/NIoqHzlBGbD0MJiT+MvSsFOvotnWOhdTjxKzhHRT2wPC1QbhLzx2q/Y83VhfYQ==}
engines: {node: '>=16'}
+ sax@1.6.1:
+ resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==}
+ engines: {node: '>=11.0.0'}
+
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==}
engines: {node: '>= 0.4'}
+ snake-case@3.0.4:
+ resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
+
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
+ stream-parser@0.3.1:
+ resolution: {integrity: sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==}
+
string-convert@0.2.1:
resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==}
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ svg-parser@2.1.0:
+ resolution: {integrity: sha512-bwLf38YmY+TDYHJw1Ex0Co8c4yeXuJAo8YnXGZrscxrvYoVVIvLeniEkV1Ks/54VteMnL4FtyN1+P+TZJdUPmQ==}
+ engines: {node: '>=8'}
+
tailwindcss@4.2.1:
resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==}
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
- toggle-selection@1.0.6:
- resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
-
ts-api-utils@2.5.0:
resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
engines: {node: '>=18.12'}
vite-plugin-dynamic-import@1.6.0:
resolution: {integrity: sha512-TM0sz70wfzTIo9YCxVFwS8OA9lNREsh+0vMHGSkWDTZ7bgd1Yjs5RV8EgB634l/91IsXJReg0xtmuQqP0mf+rg==}
+ vite-plugin-svgr@5.2.0:
+ resolution: {integrity: sha512-qj2eAKF8C6PZWemVTvQA0xgQIcP1hHU6Buh7fl6BhvayWwnuxE+z417miKxeDvRWbDrupQ1oK99hfxElopJ3sQ==}
+ peerDependencies:
+ vite: '>=3.0.0'
+
vite-tsconfig-paths@6.1.1:
resolution: {integrity: sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==}
peerDependencies:
'@alloc/quick-lru@5.3.0': {}
- '@ant-design/colors@7.2.1':
+ '@ant-design/colors@8.0.1':
dependencies:
- '@ant-design/fast-color': 2.0.6
+ '@ant-design/fast-color': 3.0.1
- '@ant-design/cssinjs-utils@1.1.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ '@ant-design/cssinjs-utils@2.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
- '@ant-design/cssinjs': 1.24.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@ant-design/cssinjs': 2.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@babel/runtime': 7.29.7
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
- '@ant-design/cssinjs@1.24.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ '@ant-design/cssinjs@2.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
'@babel/runtime': 7.29.7
'@emotion/hash': 0.8.0
'@emotion/unitless': 0.7.5
- classnames: 2.5.1
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
csstype: 3.2.3
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
stylis: 4.4.0
- '@ant-design/fast-color@2.0.6':
- dependencies:
- '@babel/runtime': 7.29.7
+ '@ant-design/fast-color@3.0.1': {}
'@ant-design/icons-svg@4.6.0': {}
- '@ant-design/icons@5.6.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ '@ant-design/icons@6.3.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
- '@ant-design/colors': 7.2.1
+ '@ant-design/colors': 8.0.1
'@ant-design/icons-svg': 4.6.0
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
- '@ant-design/react-slick@1.1.2(react@19.2.6)':
+ '@ant-design/react-slick@2.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
'@babel/runtime': 7.29.7
- classnames: 2.5.1
+ clsx: 2.1.1
json2mq: 0.2.0
react: 19.2.6
- resize-observer-polyfill: 1.5.1
+ react-dom: 19.2.6(react@19.2.6)
throttle-debounce: 5.0.2
'@babel/code-frame@7.29.7':
'@babel/runtime@7.29.7': {}
+ '@babel/runtime@8.0.5': {}
+
'@babel/template@7.29.7':
dependencies:
'@babel/code-frame': 7.29.7
optionalDependencies:
workerd: 1.20260515.1
- '@cloudflare/vite-plugin@1.37.1(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))(workerd@1.20260515.1)(wrangler@4.92.0)':
+ '@cloudflare/vite-plugin@1.37.1(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13))(workerd@1.20260515.1)(wrangler@4.92.0)':
dependencies:
'@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260515.1)
miniflare: 4.20260515.0
unenv: 2.0.0-rc.24
- vite: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)
+ vite: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)
wrangler: 4.92.0
ws: 8.18.0
transitivePeerDependencies:
'@poppinss/exception@1.2.3': {}
- '@rc-component/async-validator@5.1.2':
+ '@rc-component/async-validator@6.0.0':
dependencies:
'@babel/runtime': 7.29.7
- '@rc-component/color-picker@2.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ '@rc-component/cascader@1.22.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
- '@ant-design/fast-color': 2.0.6
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/select': 1.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/tree': 1.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/checkbox@2.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
- '@rc-component/context@1.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ '@rc-component/collapse@1.2.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
'@babel/runtime': 7.29.7
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/motion': 1.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/color-picker@3.1.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@ant-design/fast-color': 3.0.1
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/context@2.0.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/dialog@1.10.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/motion': 1.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/portal': 2.2.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/drawer@1.4.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/motion': 1.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/portal': 2.2.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/dropdown@1.0.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/trigger': 3.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/form@1.8.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/async-validator': 6.0.0
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/image@1.10.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/motion': 1.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/portal': 2.2.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/input-number@1.6.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/mini-decimal': 1.1.4
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/input@1.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/listy@1.2.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/motion': 1.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/portal': 2.2.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/virtual-list': 1.5.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/mentions@1.12.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/input': 1.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/menu': 1.5.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/trigger': 3.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/menu@1.5.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/motion': 1.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/overflow': 1.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/trigger': 3.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
dependencies:
'@babel/runtime': 7.29.7
- '@rc-component/mutate-observer@1.1.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ '@rc-component/motion@1.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
- '@rc-component/portal@1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ '@rc-component/mutate-observer@2.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
- '@rc-component/qrcode@1.1.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ '@rc-component/notification@2.0.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
- '@babel/runtime': 7.29.7
+ '@rc-component/motion': 1.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
- '@rc-component/tour@1.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ '@rc-component/overflow@1.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
'@babel/runtime': 7.29.7
- '@rc-component/portal': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@rc-component/trigger': 2.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
- '@rc-component/trigger@2.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ '@rc-component/pagination@1.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/picker@1.12.2(dayjs@1.11.23)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/overflow': 1.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/trigger': 3.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+ optionalDependencies:
+ dayjs: 1.11.23
+
+ '@rc-component/portal@2.2.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/progress@1.0.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/qrcode@2.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
'@babel/runtime': 7.29.7
- '@rc-component/portal': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- classnames: 2.5.1
- rc-motion: 2.9.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-resize-observer: 1.4.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/rate@1.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/resize-observer@1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/segmented@1.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@babel/runtime': 8.0.5
+ '@rc-component/motion': 1.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/select@1.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/overflow': 1.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/trigger': 3.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/virtual-list': 1.5.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/slider@1.1.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/steps@1.2.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/switch@1.0.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/table@1.11.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/context': 2.0.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/virtual-list': 1.5.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/tabs@1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/dropdown': 1.0.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/menu': 1.5.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/motion': 1.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/tooltip@1.5.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/trigger': 3.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/tour@2.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/portal': 2.2.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/trigger': 3.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/tree-select@1.16.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/select': 1.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/tree': 1.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/tree@1.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/motion': 1.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/virtual-list': 1.5.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/trigger@3.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/motion': 1.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/portal': 2.2.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/upload@1.1.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+
+ '@rc-component/util@1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ is-mobile: 5.0.0
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+ react-is: 19.3.0
+
+ '@rc-component/virtual-list@1.5.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@babel/runtime': 8.0.5
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
'@rolldown/pluginutils@1.0.1': {}
+ '@rollup/pluginutils@5.4.0':
+ dependencies:
+ '@types/estree': 1.0.9
+ estree-walker: 2.0.2
+ picomatch: 4.0.7
+
'@rtsao/scc@1.1.0': {}
'@shuding/opentype.js@1.4.0-beta.0':
'@speed-highlight/core@1.2.24': {}
+ '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.7(supports-color@10.2.2))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@10.2.2)
+
+ '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.7(supports-color@10.2.2))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@10.2.2)
+
+ '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.7(supports-color@10.2.2))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@10.2.2)
+
+ '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.7(supports-color@10.2.2))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@10.2.2)
+
+ '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.7(supports-color@10.2.2))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@10.2.2)
+
+ '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.7(supports-color@10.2.2))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@10.2.2)
+
+ '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.7(supports-color@10.2.2))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@10.2.2)
+
+ '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.7(supports-color@10.2.2))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@10.2.2)
+
+ '@svgr/babel-preset@8.1.0(@babel/core@7.29.7(supports-color@10.2.2))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@10.2.2)
+ '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.7(supports-color@10.2.2))
+ '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.7(supports-color@10.2.2))
+ '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.7(supports-color@10.2.2))
+ '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.7(supports-color@10.2.2))
+ '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.7(supports-color@10.2.2))
+ '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.7(supports-color@10.2.2))
+ '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.7(supports-color@10.2.2))
+ '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.7(supports-color@10.2.2))
+
+ '@svgr/core@8.1.0(supports-color@10.2.2)(typescript@5.9.3)':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@10.2.2)
+ '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7(supports-color@10.2.2))
+ camelcase: 6.3.0
+ cosmiconfig: 8.3.6(typescript@5.9.3)
+ snake-case: 3.0.4
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
+ '@svgr/hast-util-to-babel-ast@8.0.0':
+ dependencies:
+ '@babel/types': 7.29.8
+ entities: 4.5.0
+
+ '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(supports-color@10.2.2)(typescript@5.9.3))(supports-color@10.2.2)':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@10.2.2)
+ '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7(supports-color@10.2.2))
+ '@svgr/core': 8.1.0(supports-color@10.2.2)(typescript@5.9.3)
+ '@svgr/hast-util-to-babel-ast': 8.0.0
+ svg-parser: 2.1.0
+ transitivePeerDependencies:
+ - supports-color
+
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
'@resvg/resvg-wasm': 2.4.0
satori: 0.16.0
- '@vitejs/plugin-react@6.0.2(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))':
+ '@vitejs/plugin-react@6.0.2(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13))':
dependencies:
'@rolldown/pluginutils': 1.0.1
- vite: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)
+ vite: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)
- '@vitejs/plugin-rsc@0.5.26(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))':
+ '@vitejs/plugin-rsc@0.5.26(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13))':
dependencies:
'@rolldown/pluginutils': 1.0.0-rc.18
es-module-lexer: 2.3.2
srvx: 0.11.22
strip-literal: 3.1.0
turbo-stream: 3.2.1
- vite: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)
- vitefu: 1.1.3(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))
+ vite: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)
+ vitefu: 1.1.3(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13))
optionalDependencies:
react-server-dom-webpack: 19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26))
dependencies:
color-convert: 2.0.1
- antd@5.29.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
+ antd@6.6.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
dependencies:
- '@ant-design/colors': 7.2.1
- '@ant-design/cssinjs': 1.24.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@ant-design/cssinjs-utils': 1.1.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@ant-design/fast-color': 2.0.6
- '@ant-design/icons': 5.6.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@ant-design/react-slick': 1.1.2(react@19.2.6)
+ '@ant-design/colors': 8.0.1
+ '@ant-design/cssinjs': 2.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@ant-design/cssinjs-utils': 2.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@ant-design/fast-color': 3.0.1
+ '@ant-design/icons': 6.3.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@ant-design/react-slick': 2.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@babel/runtime': 7.29.7
- '@rc-component/color-picker': 2.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@rc-component/mutate-observer': 1.1.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@rc-component/qrcode': 1.1.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@rc-component/tour': 1.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@rc-component/trigger': 2.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- classnames: 2.5.1
- copy-to-clipboard: 3.3.3
+ '@rc-component/cascader': 1.22.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/checkbox': 2.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/collapse': 1.2.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/color-picker': 3.1.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/dialog': 1.10.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/drawer': 1.4.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/dropdown': 1.0.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/form': 1.8.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/image': 1.10.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/input': 1.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/input-number': 1.6.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/listy': 1.2.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/mentions': 1.12.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/menu': 1.5.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/motion': 1.3.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/mutate-observer': 2.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/notification': 2.0.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/pagination': 1.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/picker': 1.12.2(dayjs@1.11.23)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/progress': 1.0.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/qrcode': 2.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/rate': 1.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/segmented': 1.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/select': 1.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/slider': 1.1.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/steps': 1.2.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/switch': 1.0.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/table': 1.11.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/tabs': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/tooltip': 1.5.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/tour': 2.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/tree': 1.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/tree-select': 1.16.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/trigger': 3.10.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/upload': 1.1.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@rc-component/util': 1.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ clsx: 2.1.1
dayjs: 1.11.23
- rc-cascader: 3.34.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-checkbox: 3.5.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-collapse: 3.9.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-dialog: 9.6.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-drawer: 7.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-dropdown: 4.2.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-field-form: 2.7.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-image: 7.12.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-input: 1.8.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-input-number: 9.5.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-mentions: 2.20.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-menu: 9.16.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-motion: 2.9.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-notification: 5.6.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-pagination: 5.1.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-picker: 4.11.3(dayjs@1.11.23)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-progress: 4.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-rate: 2.13.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-resize-observer: 1.4.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-segmented: 2.7.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-select: 14.16.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-slider: 11.1.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-steps: 6.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-switch: 4.1.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-table: 7.54.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-tabs: 15.7.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-textarea: 1.10.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-tooltip: 6.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-tree: 5.13.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-tree-select: 5.27.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-upload: 4.11.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
scroll-into-view-if-needed: 3.1.0
callsites@3.1.0: {}
+ camelcase@6.3.0: {}
+
camelize@1.0.1: {}
caniuse-lite@1.0.30001810: {}
chrome-trace-event@1.0.4: {}
- classnames@2.5.1: {}
-
client-only@0.0.1: {}
+ clsx@2.1.1: {}
+
codepage@1.15.0: {}
color-convert@2.0.1:
cookie@1.1.1: {}
- copy-to-clipboard@3.3.3:
+ copy-anything@3.0.5:
dependencies:
- toggle-selection: 1.0.6
+ is-what: 4.1.16
core-util-is@1.0.3: {}
+ cosmiconfig@8.3.6(typescript@5.9.3):
+ dependencies:
+ import-fresh: 3.3.1
+ js-yaml: 4.3.2
+ parse-json: 5.2.0
+ path-type: 4.0.0
+ optionalDependencies:
+ typescript: 5.9.3
+
crc-32@1.2.2: {}
cross-spawn@7.0.6:
dayjs@1.11.23: {}
+ debug@2.6.9(supports-color@10.2.2):
+ dependencies:
+ ms: 2.0.0
+ optionalDependencies:
+ supports-color: 10.2.2
+ optional: true
+
debug@3.2.7(supports-color@10.2.2):
dependencies:
ms: 2.1.3
dependencies:
esutils: 2.0.3
+ dot-case@3.0.4:
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.8.1
+
drizzle-kit@0.31.10:
dependencies:
'@drizzle-team/brocli': 0.10.2
graceful-fs: 4.2.11
tapable: 2.3.3
+ entities@4.5.0: {}
+
+ errno@0.1.8:
+ dependencies:
+ prr: 1.0.1
+ optional: true
+
+ error-ex@1.3.4:
+ dependencies:
+ is-arrayish: 0.2.1
+
error-stack-parser-es@1.0.5: {}
es-abstract-get@1.0.0:
estraverse@5.3.0: {}
+ estree-walker@2.0.2: {}
+
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.9
hex-rgb@4.3.0: {}
+ iconv-lite@0.4.24:
+ dependencies:
+ safer-buffer: 2.1.2
+ optional: true
+
+ iconv-lite@0.6.3:
+ dependencies:
+ safer-buffer: 2.1.2
+ optional: true
+
ignore@5.3.2: {}
ignore@7.0.8: {}
call-bound: 1.0.4
get-intrinsic: 1.3.0
+ is-arrayish@0.2.1: {}
+
is-async-function@2.1.1:
dependencies:
async-function: 1.0.0
is-map@2.0.3: {}
+ is-mobile@5.0.0: {}
+
is-negative-zero@2.0.3: {}
is-number-object@1.1.1:
call-bound: 1.0.4
get-intrinsic: 1.3.0
+ is-what@4.1.16: {}
+
isarray@1.0.0: {}
isarray@2.0.5: {}
json-buffer@3.0.1: {}
+ json-parse-even-better-errors@2.3.1: {}
+
json-schema-traverse@0.4.1: {}
json-schema-traverse@1.0.0: {}
dependencies:
language-subtag-registry: 0.3.23
+ less@4.9.1(supports-color@10.2.2):
+ dependencies:
+ copy-anything: 3.0.5
+ parse-node-version: 1.0.1
+ optionalDependencies:
+ errno: 0.1.8
+ graceful-fs: 4.2.11
+ make-dir: 5.1.0
+ mime: 1.6.0
+ needle: 3.5.0
+ probe-image-size: 7.4.0(supports-color@10.2.2)
+ source-map: 0.6.1
+ transitivePeerDependencies:
+ - supports-color
+
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
base64-js: 0.0.8
unicode-trie: 2.0.0
+ lines-and-columns@1.2.4: {}
+
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
option: 0.2.4
underscore: 1.13.8
+ lower-case@2.0.2:
+ dependencies:
+ tslib: 2.8.1
+
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
dependencies:
'@jridgewell/sourcemap-codec': 1.6.0
+ make-dir@5.1.0:
+ optional: true
+
mammoth@1.12.2:
dependencies:
'@xmldom/xmldom': 0.8.15
mime-db@1.54.0: {}
+ mime@1.6.0:
+ optional: true
+
miniflare@4.20260515.0:
dependencies:
'@cspotcode/source-map-support': 0.8.1
lightningcss: 1.33.0
postcss: 8.5.26
+ ms@2.0.0:
+ optional: true
+
ms@2.1.3: {}
nanoid@3.3.18: {}
natural-compare@1.4.0: {}
+ needle@2.9.1(supports-color@10.2.2):
+ dependencies:
+ debug: 3.2.7(supports-color@10.2.2)
+ iconv-lite: 0.4.24
+ sax: 1.6.1
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
+ needle@3.5.0:
+ dependencies:
+ iconv-lite: 0.6.3
+ sax: 1.6.1
+ optional: true
+
neo-async@2.6.2: {}
next@16.2.6(@babel/core@7.29.7(supports-color@10.2.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- '@babel/core'
- babel-plugin-macros
+ no-case@3.0.4:
+ dependencies:
+ lower-case: 2.0.2
+ tslib: 2.8.1
+
node-exports-info@1.6.2:
dependencies:
array.prototype.flatmap: 1.3.3
color-name: 1.1.4
hex-rgb: 4.3.0
+ parse-json@5.2.0:
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ error-ex: 1.3.4
+ json-parse-even-better-errors: 2.3.1
+ lines-and-columns: 1.2.4
+
+ parse-node-version@1.0.1: {}
+
path-exists@4.0.0: {}
path-is-absolute@1.0.1: {}
path-to-regexp@6.3.0: {}
+ path-type@4.0.0: {}
+
pathe@2.0.3: {}
pdf-parse@2.4.5:
prelude-ls@1.2.1: {}
+ probe-image-size@7.4.0(supports-color@10.2.2):
+ dependencies:
+ lodash.merge: 4.6.2
+ needle: 2.9.1(supports-color@10.2.2)
+ stream-parser: 0.3.1(supports-color@10.2.2)
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
process-nextick-args@2.0.1: {}
prop-types@15.8.1:
object-assign: 4.1.1
react-is: 16.13.1
+ prr@1.0.1:
+ optional: true
+
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
- rc-cascader@3.34.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-select: 14.16.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-tree: 5.13.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-checkbox@3.5.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-collapse@3.9.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-motion: 2.9.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-dialog@9.6.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- '@rc-component/portal': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- classnames: 2.5.1
- rc-motion: 2.9.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-drawer@7.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- '@rc-component/portal': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- classnames: 2.5.1
- rc-motion: 2.9.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-dropdown@4.2.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- '@rc-component/trigger': 2.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-field-form@2.7.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- '@rc-component/async-validator': 5.1.2
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-image@7.12.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- '@rc-component/portal': 1.1.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- classnames: 2.5.1
- rc-dialog: 9.6.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-motion: 2.9.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-input-number@9.5.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- '@rc-component/mini-decimal': 1.1.4
- classnames: 2.5.1
- rc-input: 1.8.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-input@1.8.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-mentions@2.20.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- '@rc-component/trigger': 2.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- classnames: 2.5.1
- rc-input: 1.8.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-menu: 9.16.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-textarea: 1.10.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-menu@9.16.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- '@rc-component/trigger': 2.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- classnames: 2.5.1
- rc-motion: 2.9.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-overflow: 1.5.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-motion@2.9.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-notification@5.6.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-motion: 2.9.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-overflow@1.5.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-resize-observer: 1.4.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-pagination@5.1.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-picker@4.11.3(dayjs@1.11.23)(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- '@rc-component/trigger': 2.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- classnames: 2.5.1
- rc-overflow: 1.5.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-resize-observer: 1.4.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
- optionalDependencies:
- dayjs: 1.11.23
-
- rc-progress@4.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-rate@2.13.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-resize-observer@1.4.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
- resize-observer-polyfill: 1.5.1
-
- rc-segmented@2.7.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-motion: 2.9.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-select@14.16.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- '@rc-component/trigger': 2.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- classnames: 2.5.1
- rc-motion: 2.9.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-overflow: 1.5.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-virtual-list: 3.19.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-slider@11.1.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-steps@6.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-switch@4.1.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-table@7.54.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- '@rc-component/context': 1.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- classnames: 2.5.1
- rc-resize-observer: 1.4.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-virtual-list: 3.19.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-tabs@15.7.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-dropdown: 4.2.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-menu: 9.16.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-motion: 2.9.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-resize-observer: 1.4.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-textarea@1.10.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-input: 1.8.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-resize-observer: 1.4.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-tooltip@6.4.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- '@rc-component/trigger': 2.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-tree-select@5.27.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-select: 14.16.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-tree: 5.13.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-tree@5.13.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-motion: 2.9.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-virtual-list: 3.19.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-upload@4.11.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
- rc-util@5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
- react-is: 18.3.1
-
- rc-virtual-list@3.19.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
- dependencies:
- '@babel/runtime': 7.29.7
- classnames: 2.5.1
- rc-resize-observer: 1.4.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- rc-util: 5.44.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
-
react-dom@19.2.6(react@19.2.6):
dependencies:
react: 19.2.6
react-is@16.13.1: {}
- react-is@18.3.1: {}
+ react-is@19.3.0: {}
react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)):
dependencies:
require-from-string@2.0.2: {}
- resize-observer-polyfill@1.5.1: {}
-
resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
es-errors: 1.3.0
is-regex: 1.2.1
+ safer-buffer@2.1.2:
+ optional: true
+
satori@0.16.0:
dependencies:
'@shuding/opentype.js': 1.4.0-beta.0
postcss-value-parser: 4.2.0
yoga-layout: 3.2.1
+ sax@1.6.1:
+ optional: true
+
scheduler@0.27.0: {}
schema-utils@4.3.3:
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
+ snake-case@3.0.4:
+ dependencies:
+ dot-case: 3.0.4
+ tslib: 2.8.1
+
source-map-js@1.2.1: {}
source-map-support@0.5.21:
es-errors: 1.3.0
internal-slot: 1.1.0
+ stream-parser@0.3.1(supports-color@10.2.2):
+ dependencies:
+ debug: 2.6.9(supports-color@10.2.2)
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
string-convert@0.2.1: {}
string.prototype.codepointat@0.2.1: {}
supports-preserve-symlinks-flag@1.0.0: {}
+ svg-parser@2.1.0: {}
+
tailwindcss@4.2.1: {}
tapable@2.3.3: {}
dependencies:
is-number: 7.0.0
- toggle-selection@1.0.6: {}
-
ts-api-utils@2.5.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
util-deprecate@1.0.2: {}
- vinext@0.0.50(@vitejs/plugin-react@6.0.2(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)))(@vitejs/plugin-rsc@0.5.26(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)))(next@16.2.6(@babel/core@7.29.7(supports-color@10.2.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(supports-color@10.2.2)(typescript@5.9.3)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)):
+ vinext@0.0.50(@vitejs/plugin-react@6.0.2(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)))(@vitejs/plugin-rsc@0.5.26(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)))(next@16.2.6(@babel/core@7.29.7(supports-color@10.2.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(supports-color@10.2.2)(typescript@5.9.3)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)):
dependencies:
'@unpic/react': 1.0.2(next@16.2.6(@babel/core@7.29.7(supports-color@10.2.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@vercel/og': 0.8.6
- '@vitejs/plugin-react': 6.0.2(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))
+ '@vitejs/plugin-react': 6.0.2(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13))
image-size: 2.0.2
ipaddr.js: 2.5.0
magic-string: 0.30.21
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
- vite: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)
+ vite: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)
vite-plugin-commonjs: 0.10.4
- vite-tsconfig-paths: 6.1.1(supports-color@10.2.2)(typescript@5.9.3)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))
+ vite-tsconfig-paths: 6.1.1(supports-color@10.2.2)(typescript@5.9.3)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13))
web-vitals: 4.2.4
optionalDependencies:
- '@vitejs/plugin-rsc': 0.5.26(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))
+ '@vitejs/plugin-rsc': 0.5.26(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)))(react@19.2.6)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13))
react-server-dom-webpack: 19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.110.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26))
transitivePeerDependencies:
- next
fast-glob: 3.3.3
magic-string: 0.30.21
- vite-tsconfig-paths@6.1.1(supports-color@10.2.2)(typescript@5.9.3)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)):
+ vite-plugin-svgr@5.2.0(supports-color@10.2.2)(typescript@5.9.3)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)):
+ dependencies:
+ '@rollup/pluginutils': 5.4.0
+ '@svgr/core': 8.1.0(supports-color@10.2.2)(typescript@5.9.3)
+ '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(supports-color@10.2.2)(typescript@5.9.3))(supports-color@10.2.2)
+ vite: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)
+ transitivePeerDependencies:
+ - rollup
+ - supports-color
+ - typescript
+
+ vite-tsconfig-paths@6.1.1(supports-color@10.2.2)(typescript@5.9.3)(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)):
dependencies:
debug: 4.4.3(supports-color@10.2.2)
globrex: 0.1.2
tsconfck: 3.1.6(typescript@5.9.3)
- vite: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)
+ vite: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)
transitivePeerDependencies:
- supports-color
- typescript
- vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13):
+ vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13):
dependencies:
lightningcss: 1.33.0
picomatch: 4.0.7
esbuild: 0.28.2
fsevents: 2.3.3
jiti: 2.7.0
+ less: 4.9.1(supports-color@10.2.2)
terser: 5.51.2
tsx: 4.23.13
- vitefu@1.1.3(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)):
+ vitefu@1.1.3(vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)):
optionalDependencies:
- vite: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)
+ vite: 8.0.13(@types/node@22.19.19)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.1(supports-color@10.2.2))(terser@5.51.2)(tsx@4.23.13)
watchpack@2.5.2:
dependencies:
--- /dev/null
+// Read-only codemod: emit a patch for the apply_patch tool; never writes files.
+const fs=require('node:fs'),path=require('node:path'),ts=require('typescript');
+let patch='*** Begin Patch\n';
+for(const file of ['app/page.tsx','app/site-workspace.tsx']){
+ const original=fs.readFileSync(file,'utf8'),ast=ts.createSourceFile(file,original,99,true,4);
+ const edits=[];
+ function walk(node){
+ if(ts.isJsxElement(node)&&node.openingElement.tagName.getText(ast)==='label'){
+ const attrs=node.openingElement.attributes.properties;
+ const cls=attrs.find(a=>a.name?.getText(ast)==='className')?.initializer?.text;
+ if(['search-field','station-search','dashboard-manager-search'].includes(cls)){
+ const input=node.children.find(n=>ts.isJsxSelfClosingElement(n)&&n.tagName.getText(ast)==='UiInput');
+ if(input){
+ const get=name=>input.attributes.properties.find(a=>a.name?.getText(ast)===name)?.initializer;
+ const value=get('value')?.expression?.getText(ast),placeholder=get('placeholder')?.getText(ast);
+ const handler=get('onChange')?.expression?.getText(ast);
+ const setter=handler?.match(/=>\s*(\w+)\(\w+\.target\.value\)/)?.[1];
+ if(value&&placeholder&&setter)edits.push([node.getStart(ast),node.end,'<BrandSearchInput value={'+value+'} onChange={'+setter+'} placeholder='+placeholder+'/>']);
+ }
+ }
+ }
+ if(ts.isJsxElement(node)&&node.openingElement.tagName.getText(ast)==='div'){
+ const cls=node.openingElement.attributes.properties.find(a=>a.name?.getText(ast)==='className')?.initializer?.text;
+ const text=node.getText(ast);
+ if(cls==='pagination'&&text.includes('filtered.length'))edits.push([node.getStart(ast),node.end,'<BrandPagination current={pageIndex} totalPages={Math.max(1,Math.ceil(filtered.length/pageSize))} pageSize={pageSize} onPageChange={setPageIndex} onPageSizeChange={size=>{setPageSize(size);setPageIndex(1);}}/>']);
+ if(cls==='pagination'&&text.includes('visible.length'))edits.push([node.getStart(ast),node.end,'<BrandPagination current={page} totalPages={Math.max(1,Math.ceil(visible.length/pageSize))} pageSize={pageSize} onPageChange={setPage} onPageSizeChange={size=>{setPageSize(size);setPage(1);}}/>']);
+ }
+ ts.forEachChild(node,walk);
+ }walk(ast);
+ let next=original;
+ for(const [start,end,replacement] of edits.sort((a,b)=>b[0]-a[0]))next=next.slice(0,start)+replacement+next.slice(end);
+ if(file.includes('site-workspace')){
+ next=next.replace('import { Input as UiAntInput }','import BrandSearchInput from "../src/components/search-input";\nimport BrandPagination from "../src/components/pagination";\nimport { Input as UiAntInput }');
+ next=next.replace('const [collapsed,setCollapsed]=useState<Set<number>>(new Set());const [page,setPage]=useState(1);','const [collapsed,setCollapsed]=useState<Set<number>>(new Set());const [page,setPage]=useState(1);const [pageSize,setPageSize]=useState(10);');
+ next=next.replace('visible.slice((page-1)*15,page*15)','visible.slice((page-1)*pageSize,page*pageSize)');
+ }
+ const old=original.split(/\r?\n/),changed=next.split(/\r?\n/);
+ // Preserve one-to-one lines by grouping adjacent changed lines, including injected imports.
+ if(file.includes('site-workspace')){
+ const importIndex=old.findIndex(line=>line.startsWith('import { Input as UiAntInput }'));
+ changed.splice(importIndex,3,changed.slice(importIndex,importIndex+3).join('\n'));
+ }
+ if(old.length!==changed.length)throw Error('Unexpected line mapping '+file);
+ patch+='*** Update File: '+path.resolve(file).replace(/\\/g,'/')+'\n';
+ old.forEach((line,index)=>{if(line!==changed[index])patch+='@@\n-'+line+'\n'+changed[index].split('\n').map(v=>'+'+v).join('\n')+'\n';});
+}
+patch+='*** End Patch';
+process.stdout.write(patch);
--- /dev/null
+// Portable preview mounts the real StationBuilder component, not a separate mock UI.
+export { default } from "../app/page";
--- /dev/null
+/* antd v6 兜底覆盖
+ *
+ * 这是 L0 的第 5 层:token 与 CSS 变量都表达不了、只能靠选择器命中的覆盖。
+ * 尽量少写 —— 内部选择器依赖 v6 的 DOM 结构,大版本升级即可能失效。
+ *
+ * 能改 token 的 → src/theme.json
+ * 能改变量或只是渐变的 → src/theme/overrides.css
+ *
+ * 已知待收窄项(中性灰暂无对应 antd token):#EDEDED / #FAFAFA / #E1E1E1 / #89898A
+ */
+
+// Breadcrumb:token 里没有字重项
+.ant-breadcrumb {
+ .ant-breadcrumb-item {
+ font-weight: 700;
+ }
+}
+
+// Tabs 公共样式
+.ant-tabs {
+ background: #EDEDED;
+ font-family: var(--ant-font-family);
+
+ .ant-tabs-nav {
+ margin-bottom: 0;
+
+ &::before {
+ border-bottom: 1px solid #E1E1E1;
+ }
+ }
+
+ &.ant-tabs-card {
+ .ant-tabs-tab {
+ padding: 12px 24px;
+ background: #EDEDED;
+ border-bottom: 1px solid #E1E1E1;
+
+ .ant-tabs-tab-btn {
+ border: none;
+ font-weight: 700;
+
+ &>span {
+ font-size: 1rem;
+ line-height: 24px;
+ font-weight: 700;
+ color: #89898A;
+ }
+ }
+
+ &.ant-tabs-tab-active {
+ background: var(--ant-color-bg-container);
+ border: 1px solid #E1E1E1;
+ border-bottom-color: transparent;
+
+ .ant-tabs-tab-btn {
+ &>span {
+ color: var(--ant-color-primary);
+ }
+ }
+ }
+ }
+ }
+
+ .ant-tabs-content {
+ background: #FAFAFA;
+ }
+
+ // Tabs 中数据条目数量应用此类名
+ .count-badge {
+ background-color: #e1e1e1;
+ border: 1px solid #c8c8c8;
+ color: #c8c8c8;
+ border-radius: 9999px;
+ font-size: 12px;
+ min-width: 16px;
+ line-height: 16px;
+ font-weight: 700;
+ padding: 0 4px;
+ box-sizing: border-box;
+ margin-left: 8px;
+
+ &.active {
+ background-color: #b6f0d3;
+ border: 1px solid var(--ant-color-success);
+ color: var(--ant-color-success);
+ }
+ }
+}
--- /dev/null
+<svg width="20" height="20" viewBox="6 14 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6 14H12V20H6V14Z" fill="currentColor"/>
+<path d="M6 21H12V27H6V21Z" fill="currentColor"/>
+<path d="M19 14H13V20H19V14Z" fill="currentColor"/>
+<path d="M13 21H19V27H13V21Z" fill="currentColor"/>
+<path d="M26 14H20V20H26V14Z" fill="currentColor"/>
+<path d="M20 21H26V27H20V21Z" fill="currentColor"/>
+<path d="M12 28H6V34H12V28Z" fill="currentColor"/>
+<path d="M13 28H19V34H13V28Z" fill="currentColor"/>
+<path d="M26 28H20V34H26V28Z" fill="currentColor"/>
+</svg>
--- /dev/null
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M11 9V23H9V9H11ZM23 10.707L17.707 16L23 21.293L21.5859 22.707L14.8789 16L21.5859 9.29297L23 10.707Z" fill="currentColor"/>
+</svg>
--- /dev/null
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M23 23H21V9H23V23ZM17.1211 16L10.4141 22.707L9 21.293L14.293 16L9 10.707L10.4141 9.29297L17.1211 16Z" fill="currentColor"/>
+</svg>
--- /dev/null
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M13.7068 9.29303L20.4138 16.0004L13.7068 22.7069L12.2928 21.2929L17.586 16.0001L12.2928 10.707L13.7068 9.29303Z" fill="currentColor"/>
+</svg>
--- /dev/null
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M18.9999 9.29303L12.2928 16.0004L18.9999 22.7069L20.4138 21.2929L15.1207 16.0001L20.4138 10.707L18.9999 9.29303Z" fill="currentColor"/>
+</svg>
--- /dev/null
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_211_32681)">
+<path d="M23.855 10.3101L35.715 17.1551V30.8451L23.855 37.6901L12 30.8451V17.1551L23.855 10.3101Z" fill="url(#paint0_linear_211_32681)"/>
+<path d="M23.855 10.31L35.71 17.155V30.845L23.855 37.69L12 30.845V17.155L23.855 10.31ZM23.855 8L10 16V32L23.855 40L37.71 32V16L23.855 8Z" fill="url(#paint1_linear_211_32681)"/>
+<path d="M24.855 12.04V17.73L29.785 20.575V26.27L34.715 29.115V17.73L24.855 12.04Z" fill="url(#paint2_linear_211_32681)"/>
+<path d="M18.9299 15.465V21.155L23.8549 24V29.69L28.7849 32.535V21.155L18.9299 15.465Z" fill="url(#paint3_linear_211_32681)"/>
+<path d="M13 18.885L22.855 24.575V35.96L13 30.27V18.885Z" fill="url(#paint4_linear_211_32681)"/>
+</g>
+<defs>
+<linearGradient id="paint0_linear_211_32681" x1="12" y1="10.3101" x2="39.0997" y2="33.7823" gradientUnits="userSpaceOnUse">
+<stop offset="0.1" stop-color="white"/>
+<stop offset="0.8" stop-color="#BEDFFF"/>
+</linearGradient>
+<linearGradient id="paint1_linear_211_32681" x1="23.855" y1="8" x2="23.855" y2="40" gradientUnits="userSpaceOnUse">
+<stop offset="0.2" stop-color="#1E90FF"/>
+<stop offset="1" stop-color="#003CA6"/>
+</linearGradient>
+<linearGradient id="paint2_linear_211_32681" x1="29.785" y1="12.04" x2="29.785" y2="29.115" gradientUnits="userSpaceOnUse">
+<stop offset="0.2" stop-color="#1E90FF"/>
+<stop offset="1" stop-color="#003CA6"/>
+</linearGradient>
+<linearGradient id="paint3_linear_211_32681" x1="23.8574" y1="15.465" x2="23.8574" y2="32.535" gradientUnits="userSpaceOnUse">
+<stop offset="0.2" stop-color="#1E90FF"/>
+<stop offset="1" stop-color="#003CA6"/>
+</linearGradient>
+<linearGradient id="paint4_linear_211_32681" x1="17.9275" y1="18.885" x2="17.9275" y2="35.96" gradientUnits="userSpaceOnUse">
+<stop offset="0.2" stop-color="#1E90FF"/>
+<stop offset="1" stop-color="#003CA6"/>
+</linearGradient>
+<clipPath id="clip0_211_32681">
+<rect width="40" height="40" fill="white" transform="translate(4 4)"/>
+</clipPath>
+</defs>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8 9L12.0001 15L16 9H8Z" fill="currentColor"/>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9 16L15 11.9999L9 8L9 16Z" fill="currentColor"/>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M19.3254 15.2654L21.5741 15.0103C21.8091 13.9692 22.0001 13.0862 22.0001 12.0007C22.0001 10.9143 21.8096 10.03 21.5729 8.98855L19.3254 8.73284C19.2052 8.45275 19.0691 8.17876 18.9186 7.91261C18.7683 7.64732 18.6028 7.38958 18.4228 7.14142L19.3268 5.0293C18.5662 4.30021 17.9035 3.68575 16.9856 3.13806C16.0683 2.59066 15.2386 2.3123 14.2448 2L12.9043 3.86483C12.6024 3.83204 12.3042 3.80823 12.0001 3.80823C11.697 3.80823 11.3991 3.83174 11.0984 3.86454L9.75728 2.00029C8.76289 2.31202 7.93404 2.59066 7.01591 3.13777C6.09464 3.68692 5.43502 4.29469 4.67166 5.02524L5.57873 7.14056C5.39873 7.38872 5.23291 7.64645 5.08267 7.91202C4.93216 8.1779 4.79581 8.45159 4.67534 8.73227L2.42718 8.98885C2.18992 10.0308 2 10.9138 2 12.0007C2 13.0871 2.1902 13.9689 2.42746 15.0103L4.67421 15.2654C4.79467 15.5464 4.93102 15.8209 5.08182 16.088C5.23235 16.3536 5.3976 16.611 5.5776 16.8586L4.67194 18.9748C5.43473 19.7044 6.09408 20.3116 7.01448 20.8608C7.93178 21.4079 8.76176 21.6871 9.75501 22L11.0964 20.134C11.3977 20.168 11.6965 20.1912 12.0001 20.1912C12.3042 20.1912 12.6024 20.1668 12.9043 20.134L14.2448 22C15.2386 21.6868 16.0692 21.4073 16.9868 20.8602C17.9077 20.3108 18.5671 19.7039 19.3305 18.9739L18.4239 16.8571C18.6039 16.609 18.7698 16.3515 18.92 16.0857C19.0694 15.8212 19.2049 15.5475 19.3254 15.2654ZM20 12.0007C20 12.3899 19.9682 12.7577 19.9181 13.1417L17.8757 13.3736L17.8353 13.4948C17.6521 14.0446 17.4861 14.5425 17.1931 15.0617C16.8956 15.5882 16.5545 15.987 16.1752 16.4306L16.1752 16.4306L16.1007 16.5178L16.9236 18.439C16.6262 18.6787 16.3133 18.8973 15.9867 19.092C15.6681 19.2821 15.3362 19.4502 14.9934 19.5936L13.7779 17.9014L13.6271 17.9319C13.0708 18.0446 12.5843 18.1432 12.0001 18.1432C11.4129 18.1432 10.9499 18.0489 10.3916 17.9352L10.3916 17.9352L10.2239 17.9011L9.00667 19.5939C8.66425 19.4502 8.33259 19.2824 8.01454 19.0926C7.68828 18.8981 7.37534 18.6796 7.07855 18.4398L7.90144 16.5181L7.82349 16.427C7.44547 15.9856 7.10535 15.5884 6.80869 15.064C6.51751 14.549 6.35033 14.0483 6.16785 13.5018L6.12497 13.3736L4.08204 13.1417C4.03188 12.7574 4.00012 12.3902 4.00012 12.0007C4.00012 11.6101 4.03188 11.242 4.08233 10.8566L6.12441 10.6238L6.16413 10.5053C6.34838 9.95514 6.5161 9.4543 6.80954 8.93602C7.10617 8.41143 7.44674 8.0127 7.82454 7.57039L7.82455 7.57039L7.90258 7.47898L7.07911 5.55929C7.37647 5.31955 7.68913 5.1007 8.01597 4.90595C8.3343 4.71641 8.66567 4.54837 9.00866 4.40498L10.2259 6.09741L10.3744 6.06722C10.9318 5.95362 11.4097 5.85622 12.0001 5.85622C12.5922 5.85622 13.0714 5.95363 13.6311 6.0674L13.7777 6.09712L14.9934 4.40527C15.3359 4.54866 15.6672 4.71641 15.9856 4.90624C16.3147 5.10274 16.6279 5.321 16.9227 5.55813L16.1004 7.48072L16.1753 7.5686C16.5537 8.01204 16.8943 8.41117 17.1917 8.9366C17.4823 9.45045 17.6494 9.94985 17.8319 10.4951L17.8754 10.625L19.9178 10.8569C19.9677 11.242 20 11.6103 20 12.0007ZM14.0002 11.9993C14.0002 13.1257 13.1005 14.047 12.0003 14.047C10.8988 14.047 10.0002 13.1286 10.0002 11.9993C10.0002 10.8714 10.8974 9.95159 12.0003 9.95159C13.105 9.95159 14.0002 10.8682 14.0002 11.9993ZM12.0003 16.0949C9.80009 16.0949 8.00009 14.2542 8.00009 11.9993C8.00009 9.74696 9.79896 7.90361 12.0003 7.90361C14.198 7.90361 16.0003 9.74871 16.0003 11.9993C16.0003 14.2557 14.204 16.0949 12.0003 16.0949Z" fill="currentColor"/>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.6139 1.99976H1.99976V21.9999H21.9999V3.99944H11.6135L9.6139 1.99976ZM3.99989 5.99957H19.9998V19.9998H3.99989V5.99957Z" fill="currentColor"/>
+</svg>
--- /dev/null
+<svg width="123" height="40" viewBox="0 0 123 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_48899_475337)">
+<path d="M44.9346 7.54688C47.4309 7.54689 50.0724 8.61211 51.9639 10.708L49.3818 14.3203C47.9591 13.1276 46.6216 12.4796 44.9434 12.4795C43.3501 12.4795 42.3613 13.1874 42.3613 14.4824C42.3615 15.7772 43.7248 16.3224 45.6416 17.1572L48.3428 18.3506C51.0093 19.5434 52.6113 21.5454 52.6113 24.9277C52.6113 28.9236 49.5945 32.4424 44.3633 32.4424C41.5518 32.4423 38.5271 31.2839 36.2949 28.9922H36.2871L39.2178 25.0215C40.7599 26.4698 42.7621 27.4756 44.4746 27.4756C46.3915 27.4755 47.2861 26.6654 47.2861 25.3789C47.286 23.9905 46.0933 23.5388 44.2363 22.6699L41.4756 21.3838C39.2093 20.3869 37.0957 18.2903 37.0957 14.8057C37.0958 10.7758 40.3509 7.54688 44.9346 7.54688ZM99.1133 13.1191C103.339 13.1192 107.216 16.6805 107.216 22.7979C107.216 28.9151 103.314 32.4423 99.1133 32.4424C94.913 32.4424 90.9854 28.881 90.9854 22.7979C90.9854 16.6805 94.8874 13.1191 99.1133 13.1191ZM6.6709 18.9297L7.54004 26.5381H7.65918C8.06813 23.9906 8.50299 21.409 8.9375 18.9297L11.1445 7.99805H15.498L17.7646 18.9297C18.1992 21.3834 18.5733 23.9651 19.0078 26.5381H19.1533C19.4174 23.9566 19.7063 21.409 19.9619 18.9297L21.3594 7.99805H26.293L22.5439 31.999H16.0088L14.0664 21.5449C13.7171 19.7048 13.4271 17.7709 13.2227 15.9648H13.0781C12.814 17.7709 12.5581 19.7047 12.2344 21.5449L10.3516 31.999H3.94434L0 7.99805H5.30762L6.6709 18.9297ZM33.5518 31.999H28.3545V7.99805H33.5518V31.999ZM69.0293 12.7695H60.5264V17.2168H67.751V22.0566H60.5264V27.1514H69.3525V31.9912H55.3291V7.99805H69.0293V12.7695ZM88.207 31.9912H82.9932V7.90527H88.207V31.9912ZM122.458 12.7012H116.57V31.9912H111.356V12.7012H105.503V7.91309H122.458V12.7012ZM99.1133 17.7793C97.1963 17.7793 96.2842 19.7221 96.2842 22.7979C96.2842 25.8735 97.1622 27.7822 99.1133 27.7822C101.064 27.7822 101.908 25.8393 101.908 22.7979C101.908 19.7222 101.03 17.7794 99.1133 17.7793ZM79.9092 24.6719H71.6953V20.6416H79.9092V24.6719Z" fill="#004280"/>
+</g>
+<defs>
+<clipPath id="clip0_48899_475337">
+<rect width="123" height="40" fill="white"/>
+</clipPath>
+</defs>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M2 2H8V8H2V2Z" fill="currentColor"/>
+<path d="M2 9H8V15H2V9Z" fill="currentColor"/>
+<path d="M15 2H9V8H15V2Z" fill="currentColor"/>
+<path d="M9 9H15V15H9V9Z" fill="currentColor"/>
+<path d="M22 2H16V8H22V2Z" fill="currentColor"/>
+<path d="M16 9H22V15H16V9Z" fill="currentColor"/>
+<path d="M8 16H2V22H8V16Z" fill="currentColor"/>
+<path d="M9 16H15V22H9V16Z" fill="currentColor"/>
+<path d="M22 16H16V22H22V16Z" fill="currentColor"/>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M4 5H20.0002V7H4V5ZM4 17H20.0002V19H4V17ZM20.0002 11.0001H4V13.0001H20.0002V11.0001Z" fill="currentColor"/>
+</svg>
--- /dev/null
+<svg width="20" height="20" viewBox="6 14 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M17.001 15.084C19.875 15.5689 21.9999 18.0774 22 21V23.7217C23.2804 25.1778 24 27.0598 24 29.001L23.9971 31.001H19C18.9994 32.6537 17.6536 33.9997 16.001 34C14.3478 34 13.0006 32.6541 13 31.001H8.00488L8 29.001C8 27.0601 8.71905 25.1775 10 23.7217V21C10.0001 18.082 12.1299 15.5683 15 15.084V14H17.001V15.084ZM15 31.001C15.0006 31.5506 15.4513 32 16.001 32C16.5501 31.9997 16.9994 31.5502 17 31.001H15ZM16.001 17C13.7953 17 12 18.7944 12 21V24.5127C10.7229 25.8185 10 27.1169 10 29C10 29 21.952 29.0003 22 29C22 27.1173 21.2767 25.8183 20 24.5127V21C20 18.7973 18.2036 17.0002 16.001 17Z" fill="currentColor"/>
+<path d="M25 17.5C25 19.433 23.433 21 21.5 21C19.567 21 18 19.433 18 17.5C18 15.567 19.567 14 21.5 14C23.433 14 25 15.567 25 17.5Z" fill="#F55D3E"/>
+</svg>
--- /dev/null
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M14.473 15.887L18.293 19.707L19.708 18.293L15.888 14.473C16.6122 13.4595 17.001 12.2447 17 10.999C17 7.69402 14.305 4.99902 11 4.99902C7.696 4.99902 5 7.69602 5 10.999C5 14.302 7.698 16.999 11 16.999C12.2454 16.9999 13.4598 16.6111 14.473 15.887ZM15 11C15 13.2 13.202 15 11 15C8.795 15 7 13.203 7 11C7 8.79602 8.795 7.00002 11 7.00002C13.201 7.00002 15 8.79702 15 11Z" fill="currentColor"/>
+</svg>
--- /dev/null
+<svg width="20" height="20" viewBox="6 14 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M6 24.0003C6 29.5092 10.4926 34.0001 16.0012 34.0001C21.5098 34.0001 26.0001 29.5086 26.0001 24.0003C26.0001 18.4921 21.51 14 16.0012 14C10.4926 14 6 18.4915 6 24.0003ZM24 24.0003C24 28.4051 20.4062 32 16.0012 32C11.5964 32 8.00012 28.4057 8.00012 24.0003C8.00012 19.595 11.5962 16.0001 16.0012 16.0001C20.4065 16.0001 24 19.5956 24 24.0003ZM15.1417 26.4355H16.8918C16.8825 25.906 16.9267 25.5397 17.0248 25.3342C17.1228 25.129 17.3748 24.856 17.7807 24.5198C18.5671 23.8667 19.0782 23.3508 19.3186 22.973C19.5586 22.5948 19.6777 22.1912 19.6777 21.7688C19.6777 21.0012 19.3511 20.3316 18.6978 19.7553C18.0444 19.1788 17.1648 18.8896 16.0587 18.8896C15.0088 18.8896 14.1595 19.1742 13.5132 19.746C12.8666 20.3175 12.5191 21.0105 12.4678 21.8295L14.2318 22.0537C14.3554 21.4819 14.5818 21.0573 14.9107 20.7772C15.242 20.4994 15.6505 20.3597 16.1403 20.3597C16.6492 20.3597 17.0528 20.4926 17.3513 20.7585C17.6523 21.0222 17.8017 21.3419 17.8017 21.713C17.8017 21.9811 17.7178 22.2286 17.5475 22.4525C17.4378 22.5948 17.1019 22.8936 16.5395 23.3485C15.9771 23.8061 15.6037 24.2165 15.4169 24.5805C15.2282 24.947 15.1346 25.4113 15.1346 25.976C15.1346 26.0318 15.1372 26.1835 15.1417 26.4355ZM15.1418 29.0117V27.0938H16.892V29.0117H15.1418Z" fill="currentColor"/>
+</svg>
--- /dev/null
+# DropdownButton
+
+带下拉菜单的品牌按钮组件,基于 antd `Dropdown` 封装,适用于筛选器、工具栏。
+
+> Props(含 `blue` / `filter` / `multiple` 修饰类)、用法与注意事项见 `references/custom-components.md`,本文件不重复。
--- /dev/null
+//带有菜单的按钮
+.dropdown-btn {
+ height: 34px;
+ padding: 0;
+ border: 1px solid #C8C8C8;
+ background: #FFFFFF;
+ box-shadow: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-family: 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
+ 'Helvetica Neue', sans-serif;
+
+ &:hover,
+ &:focus {
+ border-color: #008CD6;
+ }
+
+ &:focus {
+ outline: none;
+ box-shadow: 0px 2px 8px 0px rgba(0, 140, 214, 0.5);
+ }
+
+ .btn-content {
+ height: 100%;
+ padding: 0 8px;
+ display: flex;
+ flex-wrap: nowrap;
+ justify-content: left;
+ }
+
+ &.blue {
+ color: #008CD6;
+ }
+
+ &.blue:not(.disabled):not([disabled]) .btn-content {
+ &:hover {
+ color: #006AA5;
+ border-color: #89898A;
+ }
+
+ &:active,
+ &.ant-dropdown-open,
+ &.ant-dropdown-open:hover {
+ color: #008CD6;
+ background: linear-gradient(315deg, rgba(11, 76, 217, 0.1) 0%, rgba(0, 140, 214, 0.1) 100%), #FFFFFF;
+ border-color: #008CD6;
+ }
+ }
+
+ &.filter {
+ color: #323233;
+
+ .btn-content {
+ flex-direction: row-reverse;
+ padding: 0 8px;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 16px;
+ line-height: 24px;
+
+ .filter-text {
+ font-weight: 400;
+ height: 24px;
+ line-height: 24px;
+ color: #323233;
+ display: inline-block;
+ max-width: 212px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ .text {
+ color: #89898A;
+ font-weight: 700;
+ }
+
+ // 图标颜色由 currentColor 驱动
+ .btn-icon {
+ display: block;
+ color: #89898A;
+ transition: color .3s ease-in;
+ }
+
+ &.ant-dropdown-open {
+ .btn-icon {
+ color: var(--ant-color-primary);
+ }
+ }
+
+ &:active:not([disabled]),
+ &.ant-dropdown-open,
+ &.ant-dropdown-open:hover {
+ background: transparent !important;
+
+ .text {
+ color: #008CD6 !important;
+ }
+ }
+ }
+ }
+}
+
+// 下拉菜单(挂载到 body)
+.self-dropdown.ant-dropdown {
+ .ant-dropdown-menu {
+ max-height: 300px;
+ overflow-y: auto;
+ padding: 8px 0;
+ border: 1px solid #008CD6;
+ border-radius: 4px;
+ box-shadow: 0 2px 8px 0 rgba(50, 50, 51, 0.2);
+
+ .ant-dropdown-menu-item {
+ font-size: 1rem;
+ line-height: 24px;
+ padding: 8px 12px;
+
+ &.selected-menu,
+ &:hover,
+ &.selected,
+ &.selected:hover {
+ background: linear-gradient(180deg, rgba(0, 140, 214, 0.10) 0%, rgba(0, 110, 255, 0.10) 100%);
+ }
+
+ .blue {
+ color: #008CD6;
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+// 带有菜单的按钮(适配 antd v6)
+import React from 'react';
+import { Dropdown } from 'antd';
+import SvgIcon, { type IconName } from '../icons/svgIcon';
+import './index.less';
+
+interface DropdownButtonProps {
+ menu: any;
+ label: string;
+ /** 按钮图标名(src/assets/icons 下的文件名,不含 .svg),如 "arrow-down" */
+ btnIcon?: IconName;
+ trigger?: any[];
+ className?: string;
+ disabled?: boolean;
+ placement?: any;
+ value?: any;
+ onVisibleChange?: any;
+ visible?: boolean;
+}
+
+const DropdownButton: React.FC<DropdownButtonProps> = ({
+ className,
+ trigger,
+ menu,
+ label,
+ btnIcon,
+ disabled,
+ placement,
+ value,
+ onVisibleChange,
+ visible,
+}: DropdownButtonProps) => {
+ return (
+ <button type="button" className={`dropdown-btn ${className || ''}`}>
+ <Dropdown
+ disabled={disabled}
+ popupRender={() => menu}
+ trigger={[...(trigger || ['click'])]}
+ placement={placement || 'bottom'}
+ classNames={{ root: 'self-dropdown' }}
+ open={visible}
+ onOpenChange={onVisibleChange}
+ >
+ <div className={'btn-content'}>
+ {btnIcon && <SvgIcon name={btnIcon} size={24} className="btn-icon" />}
+ {value && <span className={'filter-text'}>{value}</span>}
+ <span className={'text'}>{label}</span>
+ </div>
+ </Dropdown>
+ </button>
+ );
+};
+
+export default DropdownButton;
--- /dev/null
+@g-brand-hover: linear-gradient(180deg, rgba(0, 140, 214, 0.05) 0%, rgba(0, 110, 255, 0.05) 100%), #FFF;
+@g-brand-active: linear-gradient(180deg, rgba(0, 140, 214, 0.10) 0%, rgba(0, 110, 255, 0.10) 100%), #FFF;
+
+.header {
+ background-color: var(--ant-color-bg-container);
+ box-shadow: 0px 2px 8px rgba(50, 50, 51, 0.20);
+ position: fixed;
+ width: 100%;
+ top: 0;
+ z-index: 100;
+ height: 56px;
+
+ .top-bar {
+ height: 8px;
+ // 顶部品牌渐变条(token 表达不了)
+ background: linear-gradient(90deg, #008cd6 0%, #004280 100%);
+ }
+
+ .header-info {
+ padding: 0 24px;
+
+ // 折叠开关图标:常态 / hover / active 由 currentColor 驱动
+ .menu-toggle {
+ color: #89898A;
+ cursor: pointer;
+ transition: color .3s ease-in;
+
+ &:hover {
+ color: var(--ant-color-text-base);
+ }
+
+ &:active {
+ color: var(--ant-color-primary);
+ }
+ }
+
+ .portal-logo {
+ height: 40px;
+ min-width: auto;
+ margin-right: 12px
+ }
+
+ .portal-name {
+ color: var(--ant-color-text-base);
+ font-size: 20px;
+ font-weight: 700;
+ line-height: 32px;
+ }
+
+ .drop-menu {
+ cursor: pointer;
+ height: 48px;
+ display: flex;
+ align-items: center;
+ transition: .4s ease-in;
+
+ &:hover {
+ background: @g-brand-hover;
+
+ .nav-icon.drop {
+ color: var(--ant-color-text-base);
+ }
+ }
+
+ .ant-dropdown-open {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ background: @g-brand-active;
+
+ &::after {
+ content: '';
+ height: 3px;
+ width: 100%;
+ background: var(--ant-color-primary);
+ position: absolute;
+ bottom: 0;
+ }
+ }
+
+ // 图标颜色由 SvgIcon 的 currentColor 驱动
+ .file-icon {
+ cursor: pointer;
+ color: #89898A;
+ transition: color .3s ease-in;
+
+ &.active {
+ color: var(--ant-color-primary);
+ }
+ }
+
+ .nav-icon {
+ cursor: pointer;
+ transition: color .3s ease-in;
+
+ &.drop {
+ color: #89898A;
+
+ &.active {
+ color: var(--ant-color-primary);
+ }
+ }
+ }
+ }
+
+ .right-content {
+ padding: 8px 0;
+
+ // 右侧图标:常态 / hover / active 由 currentColor 驱动
+ .switch-manage,
+ .right-icon {
+ svg {
+ color: #89898A;
+ transition: color .3s ease-in;
+ }
+
+ &:hover svg {
+ color: var(--ant-color-text-base);
+ }
+
+ &:active svg {
+ color: var(--ant-color-primary);
+ }
+ }
+
+ .has-user {
+ width: 24px;
+ height: 24px;
+ line-height: 24px;
+ font-style: normal;
+ font-weight: 400;
+ font-size: 12px;
+ text-align: center;
+ color: var(--ant-color-primary);
+ // 品牌浅蓝,无对应 token
+ background: #B4E3F6;
+ mix-blend-mode: normal;
+ border-radius: 50%;
+ }
+
+ .nav-icon {
+ display: flex;
+ width: 24px;
+ height: 24px;
+ }
+ }
+ }
+
+ @media (max-width: 767px) {
+ .header-info {
+ padding: 0 12px;
+
+ .portal-name {
+ font-size: 16px;
+ line-height: 24px;
+ }
+ }
+ }
+
+ @media (max-width: 539px) {
+ .header-info {
+ .portal-logo {
+ display: none;
+ }
+ }
+ }
+}
+
+// Header 内部工具类:嵌套在 .header 内,作用域不外泄,
+// 避免与页面(含 LLM 生成的)类名冲突 —— 页面里能用的类必须都是显式定义的。
+.header {
+ .flex {
+ display: flex;
+ }
+
+ .items-center {
+ align-items: center;
+ }
+
+ .justify-center {
+ justify-content: center;
+ }
+
+ .justify-between {
+ justify-content: space-between;
+ }
+
+ .cursor-pointer {
+ cursor: pointer;
+ }
+
+ .gray {
+ color: #89898A;
+ }
+
+ .black {
+ color: var(--ant-color-text-base);
+ }
+
+ .blue {
+ color: var(--ant-color-primary);
+ }
+
+ .ml-1 {
+ margin-left: 4px;
+ }
+
+ .ml-2 {
+ margin-left: 8px;
+ }
+
+ .mr-4 {
+ margin-right: 16px;
+ }
+
+ .mr-12 {
+ margin-right: 12px;
+ }
+
+ .px-4 {
+ padding-left: 4px;
+ padding-right: 4px;
+ }
+
+ .text-small {
+ font-size: 12px;
+ line-height: 16px;
+ }
+
+ .text-normal {
+ font-size: 16px;
+ line-height: 24px;
+ }
+
+ .bold {
+ font-weight: 700;
+ }
+
+ .font-w-4 {
+ font-weight: 400;
+ }
+}
--- /dev/null
+import './index.less';
+import SvgIcon from '../icons/svgIcon';
+import { useState } from 'react';
+import { Col, Dropdown, Row } from 'antd';
+
+interface HeaderProps {
+ /** 产品名称,显示在 logo 右侧 */
+ portalName: string;
+ /** logo 图片路径(通常 import SVG) */
+ portalLogo: string;
+ /** 组织 / 工作区名称,显示在顶栏下拉按钮内(不传则用占位值) */
+ orgName?: string;
+ /** 用户邮箱,显示在右上角,首字母用作头像文字(不传则用占位值) */
+ userEmail?: string;
+ /** 租户 / 团队名称,显示在邮箱下方(不传则用占位值) */
+ tenantName?: string;
+ /** 传入后才渲染左侧菜单开关按钮(用于切换侧边栏折叠) */
+ onToggleCollapsed?: () => void;
+}
+
+// 占位默认值:example.com 为 RFC 2606 保留域。生成页面时应由调用方传入真实信息。
+const DEFAULT_ORG_NAME = 'Org001|IoT Edge';
+const DEFAULT_USER_EMAIL = 'test.user@example.com';
+const DEFAULT_TENANT_NAME = 'Tenant001';
+
+const Header = ({
+ portalName,
+ portalLogo,
+ orgName = DEFAULT_ORG_NAME,
+ userEmail = DEFAULT_USER_EMAIL,
+ tenantName = DEFAULT_TENANT_NAME,
+ onToggleCollapsed,
+}: HeaderProps) => {
+ const [fileOpen, setFileOpen] = useState(false);
+
+ return (
+ <div className="header">
+ <div className="top-bar"></div>
+ <div className="header-info flex items-center justify-between">
+ <div className="flex items-center justify-center mr-4">
+ {onToggleCollapsed && (
+ <SvgIcon name="menu" size={24} className="menu-toggle mr-4" onClick={onToggleCollapsed} />
+ )}
+ <img className="portal-logo" src={portalLogo} alt="logo" />
+ <div className="portal-name mr-4">{portalName}</div>
+ <div className="drop-menu">
+ <Dropdown
+ styles={{ root: { width: '240px' } }}
+ open={fileOpen}
+ popupRender={() => <div></div>}
+ trigger={['click']}
+ placement={'bottom'}
+ onOpenChange={(open: boolean) => { setFileOpen(open) }}>
+ <Col>
+ <Row align="middle">
+ <SvgIcon name="file" size={20} className={`file-icon ml-2 ${fileOpen ? 'active' : ''}`} />
+ <div className={`text-normal ml-2 font-w-4 ${fileOpen ? 'blue' : 'black'}`}>
+ {orgName}
+ </div>
+ <SvgIcon
+ name="arrow-down"
+ size={24}
+ className={`nav-icon drop ml-2 ${fileOpen ? 'active' : ''}`}
+ />
+ </Row>
+ </Col>
+ </Dropdown>
+ </div>
+ </div>
+ <div className="right-content flex items-center">
+ <div className="flex items-center cursor-pointer switch-manage px-4 mr-12">
+ <SvgIcon name="notifications" size={20} />
+ </div>
+ <div className="flex items-center cursor-pointer right-icon px-4 mr-12">
+ <SvgIcon name="support" size={20} />
+ </div>
+ <div className="flex items-center cursor-pointer right-icon px-4 mr-12">
+ <SvgIcon name="Apps" size={20} />
+ </div>
+ <div className="has-user cursor-pointer ml-1">{userEmail.charAt(0).toUpperCase()}</div>
+ <div className="gray text-small ml-2">
+ <div className="bold">{userEmail}</div>
+ <div className="font-w-4">{tenantName}</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+};
+
+export default Header;
--- /dev/null
+// SvgIcon —— 把 src/assets/icons/ 下的 SVG 渲染成 inline SVG
+//
+// 颜色:所有图标内部统一 fill="currentColor",颜色由 CSS 的 color 控制。
+// 常态 / :hover / :active / .disabled 全部写在样式里,组件不再接收 hoverColor / activeColor。
+// 新增图标:把 .svg 放进 src/assets/icons/,并在下面 ICONS 里登记一行
+// (tools/verify.js 会检查目录与映射是否一致,漏登记会失败)。
+import type { CSSProperties, MouseEventHandler } from 'react';
+
+import Apps from '@/assets/icons/Apps.svg?react';
+import First from '@/assets/icons/First.svg?react';
+import Last from '@/assets/icons/Last.svg?react';
+import Next from '@/assets/icons/Next.svg?react';
+import Previous from '@/assets/icons/Previous.svg?react';
+import WISE_SRP from '@/assets/icons/WISE_SRP.svg?react';
+import arrowDown from '@/assets/icons/arrow-down.svg?react';
+import arrowRight from '@/assets/icons/arrow-right.svg?react';
+import config from '@/assets/icons/config.svg?react';
+import file from '@/assets/icons/file.svg?react';
+import iotSuite from '@/assets/icons/iot_suite.svg?react';
+import manage from '@/assets/icons/manage.svg?react';
+import menu from '@/assets/icons/menu.svg?react';
+import notifications from '@/assets/icons/notifications.svg?react';
+import search from '@/assets/icons/search.svg?react';
+import support from '@/assets/icons/support.svg?react';
+
+/** 图标名 → 组件。key 必须与 src/assets/icons/<key>.svg 文件名一致 */
+const ICONS = {
+ Apps,
+ First,
+ Last,
+ Next,
+ Previous,
+ WISE_SRP,
+ 'arrow-down': arrowDown,
+ 'arrow-right': arrowRight,
+ config,
+ file,
+ iot_suite: iotSuite,
+ manage,
+ menu,
+ notifications,
+ search,
+ support,
+};
+
+export type IconName = keyof typeof ICONS;
+
+interface SvgIconProps {
+ /** 图标名(src/assets/icons/ 下的文件名,不含 .svg)。写错名字 tsc 会报错 */
+ name: IconName;
+ /** 尺寸(px),同时设置宽高,默认 24 */
+ size?: number;
+ /** 颜色,默认跟随 CSS 的 color(currentColor) */
+ color?: string;
+ className?: string;
+ style?: CSSProperties;
+ onClick?: MouseEventHandler<SVGSVGElement>;
+}
+
+const SvgIcon = ({ name, size = 24, color, className, style, onClick }: SvgIconProps) => {
+ const Icon = ICONS[name];
+
+ return (
+ <Icon
+ width={size}
+ height={size}
+ className={className}
+ style={{ color, ...style }}
+ onClick={onClick}
+ />
+ );
+};
+
+export default SvgIcon;
--- /dev/null
+# SideMenu
+
+左侧导航菜单组件,基于 antd `Menu` 封装(固定 `mode="inline"` + `theme="light"`,折叠态由 `collapsed` prop 传入)。
+
+> Props、用法与注意事项见 `references/custom-components.md`,本文件不重复。
--- /dev/null
+// SideMenu 私有样式。选择器为全局 .ant-menu:折叠态子菜单由 antd 渲染到 body 下的 portal,
+// 限定作用域会漏掉 popup(等效 L0 第 5 层兜底)。
+@g-brand-active: linear-gradient(180deg, rgba(0, 140, 214, 0.10) 0%, rgba(0, 110, 255, 0.10) 100%), #FFF;
+
+.ant-menu {
+ width: 316px;
+ height: 100%;
+ background: #E1E1E1;
+
+ /* 折叠态弹出子菜单 */
+ &.ant-menu-submenu-popup {
+ width: unset;
+ height: unset;
+ padding-left: 0;
+ border: 1px solid var(--ant-color-primary);
+ box-shadow: 0px 2px 8px 0px rgba(0, 140, 214, 0.5);
+
+ .ant-menu {
+ width: unset;
+ height: unset;
+
+ .ant-menu-item {
+ width: unset;
+ margin-inline: 0;
+
+ &:hover {
+ background: @g-brand-active !important;
+ }
+
+ &.ant-menu-item-selected {
+ &::before {
+ display: none;
+ }
+ }
+ }
+
+ .ant-menu-submenu {
+ &:hover {
+ background: @g-brand-active !important;
+ }
+
+ .ant-menu-submenu-title {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-inline: 16px;
+ }
+ }
+ }
+ }
+
+ .ant-menu-submenu-expand-icon {
+ width: auto;
+ }
+
+ .ant-menu-item {
+ margin: 0;
+ margin-inline: 0;
+ width: 100%;
+
+ /* 渐变背景:antd 用 background-color 无法设渐变,必须直接覆盖 background */
+ &:hover {
+ background: @g-brand-active !important;
+ }
+
+ &.ant-menu-item-selected {
+ background: @g-brand-active;
+
+ &::before {
+ content: "";
+ display: block;
+ position: absolute;
+ left: 0;
+ width: 4px;
+ height: 24px;
+ background-color: var(--ant-color-primary);
+ }
+ }
+ }
+
+ .ant-menu-submenu {
+ .ant-menu-submenu-title {
+ margin: 0;
+ width: 100%;
+ &:hover {
+ background: @g-brand-active !important;
+ }
+ }
+
+ &.ant-menu-submenu-selected {
+ background: #FAFAFA;
+
+ .ant-menu-sub {
+ background: #FAFAFA;
+ }
+ }
+
+ .ant-menu-sub {
+ position: relative;
+
+ &::before {
+ content: '';
+ width: 1px;
+ height: 100%;
+ background: #C8C8C8;
+ position: absolute;
+ left: 33px;
+ top: 0;
+ z-index: 2;
+ }
+
+ .ant-menu-submenu {
+ svg {
+ position: relative;
+ left: -3px;
+ }
+
+ .ant-menu-submenu-title {
+ margin: 0 0 0 3px;
+ }
+
+ .ant-menu-sub {
+ .ant-menu-item {
+ .ant-menu-title-content {
+ &::before {
+ content: '';
+ width: 1px;
+ height: 100%;
+ background: #C8C8C8;
+ position: absolute;
+ left: 53px;
+ top: 0;
+ }
+ }
+ }
+ }
+ }
+
+ .ant-menu-item {
+ margin-left: 3px;
+
+ &.ant-menu-item-selected {
+ &::before {
+ content: "";
+ display: block;
+ position: relative;
+ left: -18px;
+ top: 0;
+ width: 4px;
+ height: 24px;
+ background-color: var(--ant-color-primary);
+ }
+
+ .ant-menu-title-content {
+ margin-left: -4px;
+ }
+ }
+ }
+ }
+ }
+
+ &.ant-menu-inline-collapsed {
+ width: 72px;
+ background: #E1E1E1;
+ border-right: 1px solid #C8C8C8;
+
+ .ant-menu-item {
+ margin-top: 4px;
+
+ &:hover {
+ background: @g-brand-active !important;
+ }
+
+ &::before {
+ top: 8px;
+ }
+ }
+
+ .ant-menu-submenu {
+ .ant-menu-submenu-title:hover {
+ background: @g-brand-active;
+ }
+
+ &.ant-menu-submenu-selected {
+ position: relative;
+ background: @g-brand-active;
+
+ &::before {
+ content: "";
+ display: block;
+ position: absolute;
+ left: 0;
+ top: 8px;
+ width: 4px;
+ height: 24px;
+ background-color: var(--ant-color-primary);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// 左侧多级导航(antd Menu 封装)
+import { Menu, type MenuProps } from 'antd';
+import './index.less';
+import SvgIcon from '../icons/svgIcon';
+
+interface SideMenuProps extends Pick<MenuProps, 'selectedKeys' | 'openKeys' | 'onClick' | 'onOpenChange' | 'className'> {
+ /** 菜单数据,结构同 antd Menu 的 items */
+ items: any;
+ /** 折叠状态(展开 316px / 折叠 72px),由使用方传入 */
+ collapsed?: boolean;
+ /** 默认选中的菜单 key(不传则由 antd 决定,避免与具体 items 的 key 耦合) */
+ defaultSelectedKeys?: string[];
+ /** 默认展开的子菜单 key(不传则由 antd 决定) */
+ defaultOpenKeys?: string[];
+}
+
+const SideMenu = ({
+ items,
+ collapsed = false,
+ defaultSelectedKeys,
+ defaultOpenKeys,
+ ...controlledProps
+}: SideMenuProps) => (
+ <Menu
+ {...controlledProps}
+ defaultSelectedKeys={defaultSelectedKeys}
+ defaultOpenKeys={defaultOpenKeys}
+ mode="inline"
+ theme="light"
+ inlineCollapsed={collapsed}
+ items={items}
+ expandIcon={({ isOpen }) => (
+ <SvgIcon name={isOpen ? 'arrow-down' : 'arrow-right'} size={24} />
+ )}
+ />
+);
+
+export default SideMenu;
--- /dev/null
+// 底部分页栏
+@g-brand-hover: linear-gradient(180deg, rgba(0, 140, 214, 0.05) 0%, rgba(0, 110, 255, 0.05) 100%), #FFF;
+@g-brand-active: linear-gradient(180deg, rgba(0, 140, 214, 0.10) 0%, rgba(0, 110, 255, 0.10) 100%), #FFF;
+
+.page-footer {
+ background: var(--ant-color-bg-layout);
+ border-top: 1px solid #E1E1E1;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ padding: 8px 24px;
+
+ .pagination {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ .page-indicator {
+ margin: 0 8px;
+ color: var(--ant-color-text-base);
+ }
+
+ // 翻页箭头:常态 / hover / active / disabled 由 currentColor 驱动
+ .page-icon {
+ color: #89898A;
+ cursor: pointer;
+ transition: color .3s ease-in;
+
+ &:hover {
+ color: var(--ant-color-text-base);
+ }
+
+ &:active {
+ color: var(--ant-color-primary);
+ }
+
+ &.disabled {
+ color: #C0C0C0;
+ cursor: not-allowed;
+ pointer-events: none;
+ }
+ }
+
+ .page-size {
+ margin-left: 24px;
+ }
+ }
+
+ // Select(pageSize 切换)
+ .ant-select {
+ border-radius: var(--ant-border-radius);
+
+ .ant-select-selector {
+ background: var(--ant-color-bg-layout);
+ border: none !important;
+ font-size: 16px;
+ line-height: 24px;
+ padding: 4px 8px;
+
+ .ant-select-selection-item {
+ color: var(--ant-color-text-base);
+ }
+ }
+
+ // 箭头图标通过 suffixIcon 传入,颜色由 currentColor 驱动
+ .ant-select-arrow {
+ margin-left: 4px;
+ top: 40%;
+ right: 0;
+ width: 24px;
+ height: 24px;
+ color: #89898A;
+ transition: color .3s ease-in;
+ }
+
+ &:hover {
+ .ant-select-selector {
+ background: @g-brand-hover;
+ }
+
+ .ant-select-arrow {
+ color: var(--ant-color-text-base);
+ }
+ }
+
+ &.ant-select-focused {
+ .ant-select-selector {
+ box-shadow: none;
+ background: @g-brand-active;
+
+ .ant-select-selection-item {
+ color: var(--ant-color-primary);
+ }
+ }
+
+ .ant-select-arrow {
+ color: var(--ant-color-primary);
+ }
+ }
+ }
+}
--- /dev/null
+// 底部分页栏
+import { Select } from 'antd';
+import SvgIcon from '../icons/svgIcon';
+import './index.less';
+
+interface PaginationProps {
+ current: number;
+ totalPages: number;
+ pageSize: number;
+ onPageChange: (page: number) => void;
+ onPageSizeChange: (size: number) => void;
+}
+
+const Pagination = ({ current, totalPages, pageSize, onPageChange, onPageSizeChange }: PaginationProps) => {
+ const atFirst = current <= 1;
+ const atLast = current >= totalPages;
+
+ return (
+ <footer className="page-footer">
+ <div className="pagination">
+ <SvgIcon
+ name="First"
+ size={24}
+ className={`page-icon ${atFirst ? 'disabled' : ''}`}
+ onClick={() => { if (!atFirst) onPageChange(1); }}
+ />
+ <SvgIcon
+ name="Previous"
+ size={24}
+ className={`page-icon ${atFirst ? 'disabled' : ''}`}
+ onClick={() => { if (!atFirst) onPageChange(current - 1); }}
+ />
+ <span className="page-indicator">
+ {current} / {totalPages}
+ </span>
+ <SvgIcon
+ name="Next"
+ size={24}
+ className={`page-icon ${atLast ? 'disabled' : ''}`}
+ onClick={() => { if (!atLast) onPageChange(current + 1); }}
+ />
+ <SvgIcon
+ name="Last"
+ size={24}
+ className={`page-icon ${atLast ? 'disabled' : ''}`}
+ onClick={() => { if (!atLast) onPageChange(totalPages); }}
+ />
+ <Select
+ className="page-size"
+ value={pageSize}
+ onChange={(v) => onPageSizeChange(v)}
+ options={[10, 20, 50, 100].map((v) => ({ label: `${v} / page`, value: v }))}
+ suffixIcon={<SvgIcon name="arrow-down" size={24} />}
+ />
+ </div>
+ </footer>
+ );
+};
+
+export default Pagination;
--- /dev/null
+// Input(作用域限定到本组件,避免影响页面上其它带 affix 的 Input)
+.search-input.ant-input-affix-wrapper {
+ width: 240px;
+ padding: 4px 8px;
+ font-size: 1rem;
+ line-height: 24px;
+ border-color: #C8C8C8;
+
+ &:hover {
+ border-color: #008CD6;
+ }
+
+ .ant-input {
+ font-size: 1rem;
+ line-height: 24px;
+ }
+
+ // 搜索图标:颜色由 currentColor 驱动
+ .search-prefix svg {
+ color: #89898A;
+ transition: color .3s ease-in;
+
+ &:hover {
+ color: var(--ant-color-text-base);
+ }
+
+ &:active {
+ color: var(--ant-color-primary);
+ }
+ }
+
+ &.ant-input-affix-wrapper-focused {
+ border-color: #008CD6;
+ box-shadow: 0px 2px 8px 0px rgba(0, 140, 214, 0.5);
+ }
+}
\ No newline at end of file
--- /dev/null
+// 带搜索图标和下拉箭头的搜索输入框
+import { Input } from 'antd';
+import { CaretDownOutlined } from '@ant-design/icons';
+import SvgIcon from '../icons/svgIcon';
+import './index.less';
+
+interface SearchInputProps {
+ value: string;
+ placeholder?: string;
+ className?: string;
+ onChange?: (value: string) => void;
+}
+
+const SearchInput = ({ value, placeholder, className, onChange }: SearchInputProps) => (
+ <Input
+ className={`search-input ${className || ''}`}
+ placeholder={placeholder}
+ allowClear
+ value={value}
+ onChange={(e) => onChange?.(e.target.value)}
+ prefix={
+ <span className="search-prefix">
+ <SvgIcon name="search" size={24} />
+ <CaretDownOutlined />
+ </span>
+ }
+ />
+);
+
+export default SearchInput;
--- /dev/null
+import React from "react";
+import ReactDOM from "react-dom/client";
+import App from "./App";
+import CompanyTheme from "../app/company-theme";
+import "antd/dist/reset.css";
+import "../app/globals.css";
+import "../app/site-workspace.css";
+import "../app/ui-system.css";
+import "../app/ui-layout-compat.css";
+import "./antd.less";
+import "./theme/overrides.css";
+import "./styles.less";
+import "../app/brand-layout.css";
+
+ReactDOM.createRoot(document.getElementById("root")!).render(
+ <React.StrictMode><CompanyTheme><App/></CompanyTheme></React.StrictMode>
+);
--- /dev/null
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: var(--ant-font-family);
+ background: var(--ant-color-bg-layout);
+ color: var(--ant-color-text-base);
+}
+
+#root {
+ min-height: 100vh;
+}
+
+/* ===== 整体布局 ===== */
+// Header 高度(含 8px 顶部渐变条),同时也是内容区的避让量
+@header-height: 56px;
+
+.page {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ padding-top: @header-height;
+
+ // 左右两栏:宽度全部交给 flex,不写死 calc
+ .body {
+ display: flex;
+ flex: 1 1 auto;
+ align-items: stretch;
+ }
+
+ // 左栏宽度由 SideMenu 自身决定(展开 316px / 折叠 72px),随整页一起滚动
+ .left-menu {
+ flex: 0 0 auto;
+ }
+
+ /* 注:#ededed 暂无对应 antd token(colorBgLayout 已被覆盖为 #fafafa,与此值不同) */
+ .main {
+ // flex:1 自动占满剩余宽度;min-width:0 允许收缩,避免内部宽内容把布局撑破
+ flex: 1 1 auto;
+ min-width: 0;
+ padding: 8px 0 0;
+ background: #ededed;
+
+ .page-header {
+ padding: 0 24px;
+ }
+ }
+}
--- /dev/null
+# 详情页模板
+
+待开发
--- /dev/null
+# 编辑/表单页模板
+
+待开发
--- /dev/null
+# 列表页模板
+
+通用数据列表页,支持搜索、筛选、批量操作、导出。复制后替换 columns 和数据即可。
+
+## 最小使用示例
+
+```tsx
+import ListTemplate, { type ListItem } from '@/templates/list';
+import type { TableColumnsType } from 'antd';
+
+const data: ListItem[] = [
+ { id: '1', name: '张三', role: '管理员' },
+ { id: '2', name: '李四', role: '编辑' },
+];
+
+const columns: TableColumnsType<ListItem> = [
+ { title: '名称', dataIndex: 'name', key: 'name' },
+ { title: '角色', dataIndex: 'role', key: 'role' },
+];
+
+export default function MyPage() {
+ return (
+ <ListTemplate
+ title="用户管理"
+ data={data}
+ columns={columns}
+ onAdd={() => console.log('新建')}
+ onEdit={(record) => console.log('编辑', record)}
+ onDelete={(record) => console.log('删除', record)}
+ />
+ );
+}
+```
+
+## Props 速查
+
+| Prop | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `data` | `ListItem[]` | 必填 | 数据源 |
+| `columns` | `ColumnsType<ListItem>` | 必填 | 列定义 |
+| `title` | `string` | `'列表管理'` | 页面标题 |
+| `filters` | `FilterConfig[]` | `[]` | 筛选器配置 |
+| `rowKey` | `string` | `'id'` | 行唯一键 |
+| `loading` | `boolean` | `false` | 加载状态 |
+| `onAdd` | `() => void` | — | 传入后显示新建按钮 |
+| `onEdit` | `(record) => void` | — | 传入后自动添加操作列 |
+| `onDelete` | `(record) => void` | — | 传入后自动添加操作列(带确认弹窗) |
+| `onBatchDelete` | `(ids) => void` | — | 传入后显示批量删除 |
+| `onExport` | `(ids?) => void` | — | 传入后显示导出按钮 |
+| `showBatchActions` | `boolean` | `true` | 是否显示批量操作栏 |
--- /dev/null
+import { useState, useMemo } from 'react';
+import { Table, Button, Input, Select, Space, Popconfirm, App } from 'antd';
+import { SearchOutlined, PlusOutlined, EditOutlined, DeleteOutlined, ExportOutlined } from '@ant-design/icons';
+import type { TableColumnsType } from 'antd';
+import './styles.css';
+
+// 通用列表项类型——必须有 id,其余字段自由扩展
+export interface ListItem {
+ id: string;
+ [key: string]: any;
+}
+
+// 筛选器配置
+export interface FilterConfig {
+ key: string;
+ label: string;
+ options: { label: string; value: string }[];
+}
+
+// 列表模板 Props
+export interface ListTemplateProps {
+ title?: string;
+ data: ListItem[];
+ columns: TableColumnsType<ListItem>;
+ filters?: FilterConfig[];
+ rowKey?: string;
+ loading?: boolean;
+ onAdd?: () => void;
+ onEdit?: (record: ListItem) => void;
+ onDelete?: (record: ListItem) => void;
+ onBatchDelete?: (ids: string[]) => void;
+ onExport?: (ids?: string[]) => void;
+ showBatchActions?: boolean;
+}
+
+function ListTemplate({
+ title = '列表管理',
+ data,
+ columns,
+ filters = [],
+ rowKey = 'id',
+ loading = false,
+ onAdd,
+ onEdit,
+ onDelete,
+ onBatchDelete,
+ onExport,
+ showBatchActions = true,
+}: ListTemplateProps) {
+ const { message } = App.useApp();
+ const [searchValue, setSearchValue] = useState('');
+ const [filterValues, setFilterValues] = useState<Record<string, string | undefined>>({});
+ const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
+
+ // 过滤后的数据
+ const filteredData = useMemo(() => {
+ return data.filter((item) => {
+ const matchSearch = !searchValue ||
+ Object.values(item).some((val) =>
+ String(val).toLowerCase().includes(searchValue.toLowerCase())
+ );
+
+ const matchFilters = filters.every((filter) => {
+ const filterValue = filterValues[filter.key];
+ return !filterValue || item[filter.key] === filterValue;
+ });
+
+ return matchSearch && matchFilters;
+ });
+ }, [data, searchValue, filterValues, filters]);
+
+ // 清除筛选
+ const clearFilters = () => {
+ setSearchValue('');
+ setFilterValues({});
+ };
+
+ // 批量删除
+ const handleBatchDelete = () => {
+ if (onBatchDelete) {
+ onBatchDelete(selectedRowKeys as string[]);
+ setSelectedRowKeys([]);
+ message.success(`已删除 ${selectedRowKeys.length} 项`);
+ }
+ };
+
+ // 导出
+ const handleExport = () => {
+ if (onExport) {
+ onExport(selectedRowKeys.length > 0 ? (selectedRowKeys as string[]) : undefined);
+ }
+ };
+
+ // 带操作列的完整列定义
+ const columnsWithActions = useMemo(() => {
+ if (!onEdit && !onDelete) return columns;
+
+ const actionColumn: TableColumnsType<ListItem>[0] = {
+ title: '操作',
+ key: 'action',
+ width: 180,
+ render: (_: any, record: ListItem) => (
+ <Space size="small">
+ {onEdit && (
+ <Button type="link" size="small" icon={<EditOutlined />} onClick={() => onEdit(record)}>
+ 编辑
+ </Button>
+ )}
+ {onDelete && (
+ <Popconfirm
+ title="确认删除"
+ description="确定要删除此项吗?"
+ onConfirm={() => {
+ onDelete(record);
+ message.success('删除成功');
+ }}
+ okText="确定"
+ cancelText="取消"
+ >
+ <Button type="link" size="small" danger icon={<DeleteOutlined />}>
+ 删除
+ </Button>
+ </Popconfirm>
+ )}
+ </Space>
+ ),
+ };
+
+ return [...columns, actionColumn];
+ }, [columns, onEdit, onDelete]);
+
+ return (
+ <div className="list-template">
+ {/* 页面标题 */}
+ {title && <h1 className="list-template-title">{title}</h1>}
+
+ {/* 操作栏 */}
+ <div className="list-template-toolbar">
+ <Space wrap>
+ {onAdd && (
+ <Button type="primary" icon={<PlusOutlined />} onClick={onAdd}>
+ 新建
+ </Button>
+ )}
+ <Input
+ placeholder="搜索..."
+ prefix={<SearchOutlined />}
+ value={searchValue}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchValue(e.target.value)}
+ allowClear
+ className="list-template-search"
+ />
+ {filters.map((filter) => (
+ <Select
+ key={filter.key}
+ placeholder={filter.label}
+ value={filterValues[filter.key]}
+ onChange={(value: string) => setFilterValues({ ...filterValues, [filter.key]: value })}
+ allowClear
+ className="list-template-filter"
+ options={filter.options}
+ />
+ ))}
+ <Button onClick={clearFilters}>清除筛选</Button>
+ </Space>
+ {onExport && (
+ <Button icon={<ExportOutlined />} onClick={handleExport}>
+ 导出
+ </Button>
+ )}
+ </div>
+
+ {/* 批量操作栏 */}
+ {showBatchActions && selectedRowKeys.length > 0 && (
+ <div className="list-template-batch-bar">
+ <span className="list-template-batch-label">已选择 {selectedRowKeys.length} 项</span>
+ {onExport && <Button onClick={handleExport}>导出</Button>}
+ {onBatchDelete && (
+ <Popconfirm
+ title="确认批量删除"
+ description={`确定要删除选中的 ${selectedRowKeys.length} 项吗?`}
+ onConfirm={handleBatchDelete}
+ okText="确定"
+ cancelText="取消"
+ >
+ <Button danger>删除</Button>
+ </Popconfirm>
+ )}
+ </div>
+ )}
+
+ {/* 表格 */}
+ <Table
+ rowKey={rowKey}
+ columns={columnsWithActions}
+ dataSource={filteredData}
+ loading={loading}
+ locale={{ emptyText: '暂无匹配数据,请调整搜索或筛选条件' }}
+ rowSelection={
+ showBatchActions
+ ? {
+ selectedRowKeys,
+ onChange: setSelectedRowKeys,
+ }
+ : undefined
+ }
+ pagination={{
+ showSizeChanger: true,
+ showQuickJumper: true,
+ showTotal: (total: number) => `共 ${total} 条`,
+ }}
+ />
+ </div>
+ );
+}
+
+export default ListTemplate;
--- /dev/null
+import type { ListItem } from './index';
+
+// 用户管理示例数据
+export const mockData: ListItem[] = [
+ {
+ id: '1',
+ name: '张三',
+ email: 'zhangsan@example.com',
+ role: '管理员',
+ status: 'active',
+ lastLogin: '2026-08-20 10:30',
+ },
+ {
+ id: '2',
+ name: '李四',
+ email: 'lisi@example.com',
+ role: '编辑',
+ status: 'inactive',
+ lastLogin: '2026-08-19 14:20',
+ },
+ {
+ id: '3',
+ name: '王五',
+ email: 'wangwu@example.com',
+ role: '管理员',
+ status: 'active',
+ lastLogin: '2026-08-18 09:15',
+ },
+ {
+ id: '4',
+ name: '赵六',
+ email: 'zhaoliu@example.com',
+ role: '访客',
+ status: 'active',
+ lastLogin: '2026-08-17 16:45',
+ },
+ {
+ id: '5',
+ name: '钱七',
+ email: 'qianqi@example.com',
+ role: '编辑',
+ status: 'inactive',
+ lastLogin: '2026-08-16 11:00',
+ },
+];
--- /dev/null
+.list-template {
+ padding: 24px;
+}
+
+.list-template-title {
+ font-size: 24px;
+ font-weight: 700;
+ margin-bottom: 16px;
+ color: var(--ant-color-text);
+}
+
+.list-template-toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 16px;
+ padding: 12px 16px;
+ background: var(--ant-color-bg-container);
+ border-radius: var(--ant-border-radius);
+}
+
+.list-template-search {
+ width: 240px;
+}
+
+.list-template-filter {
+ width: 120px;
+}
+
+.list-template-batch-bar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 16px;
+ padding: 8px 16px;
+ background: var(--ant-color-bg-layout);
+ border-radius: var(--ant-border-radius);
+ /* 品牌中性灰,暂无对应 antd token(归属待定) */
+ border-top: 1px solid #e1e1e1;
+}
+
+.list-template-batch-label {
+ font-weight: 700;
+}
--- /dev/null
+# 登录/注册页模板
+
+待开发
--- /dev/null
+{
+ "token": {
+ "colorPrimary": "#008cd6",
+ "colorInfo": "#008cd6",
+ "colorSuccess": "#00cc66",
+ "colorError": "#f55d3e",
+ "colorWarning": "#ffa400",
+ "colorTextBase": "#323233",
+ "colorBgLayout": "#fafafa",
+ "fontSize": 16,
+ "borderRadius": 4,
+ "fontFamily": "'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
+ "fontFamilyCode": "\"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", sans-serif"
+ },
+ "components": {
+ "Table": {
+ "borderColor": "rgb(225,225,225)",
+ "headerColor": "rgb(50,50,51)",
+ "footerColor": "rgb(50,50,51)",
+ "rowHoverBg": "rgb(230,242,254)",
+ "rowSelectedBg": "rgb(230,242,254)",
+ "cellPaddingBlock": 8
+ },
+ "Button": {
+ "defaultActiveBorderColor": "rgb(0,140,214)",
+ "defaultActiveColor": "rgb(0,140,214)",
+ "defaultBorderColor": "rgb(200,200,200)",
+ "defaultColor": "rgb(137,137,138)",
+ "defaultHoverBorderColor": "rgb(137,137,138)",
+ "defaultHoverColor": "rgb(50,50,51)",
+ "defaultActiveBg": "rgb(229,243,252)",
+ "fontWeight": 600
+ },
+ "Switch": {
+ "handleSize": 12,
+ "trackHeight": 16,
+ "trackMinWidth": 24,
+ "trackPadding": 2,
+ "innerMaxMargin": 16
+ },
+ "Menu": {
+ "itemColor": "rgb(50,50,51)",
+ "itemHoverColor": "rgb(50,50,51)",
+ "subMenuItemBg": "rgb(255,255,255)",
+ "subMenuItemBorderRadius": 0,
+ "itemBorderRadius": 0
+ },
+ "Breadcrumb": {
+ "itemColor": "rgb(137,137,138)",
+ "lastItemColor": "rgb(137,137,138)",
+ "linkColor": "rgb(137,137,138)",
+ "linkHoverColor": "rgb(0,140,214)",
+ "separatorColor": "rgb(192,192,192)"
+ },
+ "Select": {
+ "optionActiveBg": "rgb(229,242,253)",
+ "optionSelectedBg": "rgb(229,242,253)",
+ "optionSelectedColor": "rgb(0,140,214)",
+ "optionPadding": "8px 12px"
+ }
+ }
+}
--- /dev/null
+/* ============================================================
+ * overrides.css — L0 设计覆盖
+ *
+ * 只放 antd token 表达不了的东西:
+ * - 渐变(token 只能设单一颜色)
+ * - 多层阴影
+ * - 同一组件内不同部位要不同值(如表头 vs 表体)
+ *
+ * 能用 token 表达的一律写进 src/theme.json。
+ * 消费品牌值一律用 var(--ant-*),不要硬编码。
+ * ============================================================ */
+
+/* ── Menu:hover / 选中的渐变背景 ──
+ * 载体在 src/components/menu/index.less(需覆盖 inline 与 portal 两种 DOM),此处不重复。
+ */
+
+/* ── Select 下拉 ──
+ * 注意:Select 没有 dropdownPadding / boxShadow 组件 token,
+ * 写 --ant-select-dropdown-padding / --ant-select-box-shadow 是空操作,必须用真实声明。
+ */
+.ant-select-dropdown {
+ padding: 8px 0;
+ border: 1px solid var(--ant-color-primary);
+ box-shadow: 0 2px 8px 0 rgba(50, 50, 51, 0.2);
+}
+
+.ant-select.ant-select-focused {
+ box-shadow: 0 2px 8px 0 rgba(50, 50, 51, 0.2);
+}
+
+/* 选项 hover 渐变(渐变 token 表达不了) */
+.ant-select-dropdown .ant-select-item:hover,
+.ant-select-dropdown .ant-select-item-option-active {
+ background: linear-gradient(
+ 180deg,
+ rgba(0, 140, 214, 0.10) 0%,
+ rgba(0, 110, 255, 0.10) 100%
+ ), #FFF !important;
+}
+
+/* ── Table:表头与表体内边距不同 ──
+ * cellPaddingBlock 作用于所有单元格(已设 8),表头要 12,
+ * token 区分不了表头/表体,只能用变量。
+ */
+.ant-table .ant-table-thead > tr > th {
+ --ant-table-cell-padding-block: 12px;
+}
--- /dev/null
+/// <reference types="vite/client" />
+/// <reference types="vite-plugin-svgr/client" />
--- /dev/null
+import {test} from 'node:test';
+import assert from 'node:assert/strict';
+import {readFileSync} from 'node:fs';
+import {createHash} from 'node:crypto';
+import {createRequire,Module} from 'node:module';
+import {fileURLToPath} from 'node:url';
+const require=createRequire(import.meta.url),ts=require('typescript');
+const file=fileURLToPath(new URL('../app/brand-token-css.ts',import.meta.url)),mod=new Module(file);
+mod._compile(ts.transpileModule(readFileSync(file,'utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText,file);
+const {tokenCssValue}=mod.exports;
+test('line-height and weights stay unitless, actual dimensions retain px',()=>{
+ for(const token of ['lineHeight','lineHeightSM','lineHeightHeading4']) assert.equal(tokenCssValue(token,1.5),'1.5');
+ assert.equal(tokenCssValue('fontWeightStrong',600),'600');
+ assert.equal(tokenCssValue('sizeStep',4),'4');
+ for(const token of ['fontSize','controlHeight','trackHeight','paddingBlock','cellPaddingBlock']) assert.equal(tokenCssValue(token,16),'16px');
+ assert.equal(tokenCssValue('rowHoverBg','rgb(230,242,254)'),'rgb(230,242,254)');
+});
+test('canonical brand assets remain exact Skill copies',()=>{
+ const hashes={
+ 'src/theme.json':'1c4c80d9b2772f6d6080e9fb1b28293a6748431fb5e0cf198df937c58e738b0e',
+ 'src/antd.less':'84d09dd7fb9a985088e478c0ada1502d6fca589cc4705e950f8f7fcb798e1d11',
+ 'src/theme/overrides.css':'bf9f34dbc2f838f4f0636164de7d7645c1e8784aba27bfd7884b68db2b92ff5d',
+ };
+ for(const [path,hash] of Object.entries(hashes)) assert.equal(createHash('sha256').update(readFileSync(new URL('../'+path,import.meta.url))).digest('hex'),hash,path);
+});
+test('business shell consumes branded menu, search and pagination without adding menu icons',()=>{
+ const page=readFileSync(new URL('../app/page.tsx',import.meta.url),'utf8');
+ assert.match(page,/className="app-shell page sb-brand-shell"/);
+ assert.match(page,/<BrandSideMenu/);
+ assert.match(page,/onClick=\{\(\)=>setNavCollapsed/);
+ assert.match(page,/<BrandSearchInput/); assert.match(page,/<BrandPagination/);
+ const block=page.slice(page.indexOf('<BrandSideMenu'),page.indexOf('</nav>',page.indexOf('<BrandSideMenu')));
+ assert.doesNotMatch(block,/icon:/);
+ assert.match(readFileSync(new URL('../app/site-workspace.tsx',import.meta.url),'utf8'),/visible\.slice\(\(page-1\)\*pageSize,page\*pageSize\)/);
+});
--- /dev/null
+import {test} from "node:test";
+import assert from "node:assert/strict";
+import {DatabaseSync} from "node:sqlite";
+import {buildSiteMenu,effectiveStandardPageKeys,menuSelectionSignature} from "../app/site-menu-model.ts";
+import {saveSiteApplication,saveStandardPageSelection} from "../lib/site-workspace.mjs";
+
+const leaves=[
+ {key:"设备监控/设备总览",label:"设备总览",parentMenu:"设备监控"},
+ {key:"设备监控/设备详情",label:"设备详情",parentMenu:"设备监控"},
+ {key:"数据分析/数据分析",label:"数据分析",parentMenu:"数据分析"},
+ {key:"系统设置/设备类型",label:"设备类型",parentMenu:"系统设置"},
+];
+const station=(patch={})=>({id:1,name:"测试站点",mu:[],cubes:[{id:10,name:"供电监控"},{id:11,name:"环境监控"}],page_config:{dashboard_ids:[10,11],twin_ids:["site-overview","process-scene"]},menu:[],...patch});
+
+test("领域菜单之后完整展示标准功能,保持原一级二级结构及目录顺序",()=>{
+ const site=station({mu:[leaves[3].key,leaves[1].key,leaves[0].key,leaves[2].key]});
+ const menu=buildSiteMenu(site,leaves);
+ assert.deepEqual(menu.filter(item=>item.parent_id===null).map(item=>item.label),["数字孪生","领域监控","设备监控","数据分析","系统设置"]);
+ for(const kind of ["sc","cube"]){const root=menu.find(item=>item.source_key===`group:${kind}`);assert.ok(menu.filter(item=>item.page_type===kind).every(item=>item.parent_id===root.id));}
+ assert.deepEqual(menu.filter(item=>item.page_type==="mu").map(item=>item.ref_id),leaves.map(item=>item.key));
+ const group=menu.find(item=>item.label==="设备监控");
+ assert.deepEqual(menu.filter(item=>item.parent_id===group.id).map(item=>item.label),["设备总览","设备详情"]);
+});
+test("旧版未配置默认包含标准页面,明确取消全部则保持为空",()=>{
+ assert.deepEqual(effectiveStandardPageKeys(station(),leaves),leaves.map(item=>item.key));
+ assert.equal(buildSiteMenu(station(),leaves).filter(item=>item.page_type==="mu").length,4);
+ const empty=station({page_config:{dashboard_ids:[10],twin_ids:[],standard_pages_initialized:true}});
+ assert.deepEqual(effectiveStandardPageKeys(empty,leaves),[]);
+ assert.equal(buildSiteMenu(empty,leaves).filter(item=>item.page_type==="mu").length,0);
+ assert.deepEqual(effectiveStandardPageKeys(station({mu:[leaves[1].key]}),leaves),[leaves[1].key]);
+});
+test("菜单改名与数据库ID重映射后,选择同步不丢名称或标准结构",()=>{
+ const original=station();const menu=buildSiteMenu(original,leaves);
+ const renamed=menu.map(item=>({...item,label:item.source_key==="group:mu:/设备监控"?"设备管理":item.source_key==="mu:设备监控/设备总览"?"运行总览":item.label}));
+ const next=station({mu:[leaves[0].key],menu:renamed});
+ const result=buildSiteMenu(next,leaves);
+ assert.deepEqual(result.filter(item=>item.parent_id===null).map(item=>item.label),["数字孪生","领域监控","设备管理"]);
+ assert.equal(result.find(item=>item.page_type==="mu").label,"运行总览");
+ assert.notEqual(menuSelectionSignature(original,leaves),menuSelectionSignature(next,leaves));
+});
+
+function database(){
+ const db=new DatabaseSync(":memory:");
+ db.exec(`
+ CREATE TABLE sites(id INTEGER PRIMARY KEY,name TEXT,page_config_json TEXT,app_name TEXT,app_icon TEXT,app_icon_filename TEXT,app_color_scheme TEXT,updated_at TEXT);
+ CREATE TABLE cubes(id INTEGER PRIMARY KEY,site_id INTEGER,name TEXT);
+ CREATE TABLE mu_selections(site_id INTEGER,page_key TEXT,selected INTEGER);
+ CREATE TABLE menu_items(id INTEGER PRIMARY KEY,site_id INTEGER,parent_id INTEGER,label TEXT,page_type TEXT,ref_id TEXT,sort_order INTEGER,source_key TEXT);
+ INSERT INTO sites(id,name,page_config_json) VALUES(1,'测试站点','{"dashboard_ids":[10,11],"twin_ids":["site-overview","process-scene"]}');
+ INSERT INTO cubes VALUES(10,1,'供电监控'),(11,1,'环境监控');
+ `);return db;
+}
+test("保存草稿同时保存默认标准页面和菜单,重新载入后结构保持一致",()=>{
+ const db=database();const site=station();const menu=buildSiteMenu(site,leaves);
+ saveSiteApplication(db,1,menu,{app_name:"场站平台",app_color_scheme:"green"},leaves.map(item=>item.key));
+ const stored=db.prepare("SELECT * FROM sites WHERE id=1").get();
+ const mu=db.prepare("SELECT page_key FROM mu_selections WHERE site_id=1").all().map(item=>item.page_key);
+ const saved=db.prepare("SELECT * FROM menu_items WHERE site_id=1 ORDER BY sort_order").all();
+ assert.deepEqual(mu,leaves.map(item=>item.key));
+ assert.equal(stored.app_name,"场站平台");assert.equal(stored.app_color_scheme,"green");
+ assert.equal(JSON.parse(stored.page_config_json).standard_pages_initialized,true);
+ assert.deepEqual(buildSiteMenu({...site,mu,menu:saved,page_config:JSON.parse(stored.page_config_json)},leaves).map(item=>item.label),menu.map(item=>item.label));
+ assert.throws(()=>saveSiteApplication(db,1,menu.filter(item=>item.page_type!=="mu"),{app_name:"失败修改"},leaves.map(item=>item.key)),/页面选择已变化/);
+ assert.equal(db.prepare("SELECT app_name FROM sites WHERE id=1").get().app_name,"场站平台");db.close();
+});
+test("第四步取消标准页面保存初始化标记,不影响仪表板和场景选择",()=>{
+ const db=database();saveStandardPageSelection(db,1,[]);
+ const config=JSON.parse(db.prepare("SELECT page_config_json FROM sites WHERE id=1").get().page_config_json);
+ assert.equal(config.standard_pages_initialized,true);assert.deepEqual(config.dashboard_ids,[10,11]);assert.deepEqual(config.twin_ids,["site-overview","process-scene"]);
+ const menu=buildSiteMenu(station({page_config:config}),leaves);
+ saveSiteApplication(db,1,menu,{app_name:"保持取消"},leaves.map(item=>item.key));
+ assert.equal(db.prepare("SELECT COUNT(*) n FROM mu_selections").get().n,0);db.close();
+});
--- /dev/null
+import {DatabaseSync} from "node:sqlite";
+import {copyFileSync,mkdtempSync} from "node:fs";
+import {join} from "node:path";
+import assert from "node:assert/strict";
+import {applySiteTemplate} from "../lib/site-workspace.mjs";
+const temp=mkdtempSync(join(process.cwd(),"tests/site-workspace-fixture-"));
+copyFileSync("data/db/StationBuilder-before-site-workspace-20260916.sqlite",join(temp,"test.sqlite"));
+const db=new DatabaseSync(join(temp,"test.sqlite"));
+db.exec("PRAGMA foreign_keys=ON");
+for(const [name,type] of [["template_id","INTEGER"],["template_applied_at","TEXT"],["page_config_json","TEXT"]])if(!db.prepare("PRAGMA table_info(sites)").all().some(column=>column.name===name))db.exec(`ALTER TABLE sites ADD COLUMN ${name} ${type}`);
+if(!db.prepare("PRAGMA table_info(menu_items)").all().some(column=>column.name==="source_key"))db.exec("ALTER TABLE menu_items ADD COLUMN source_key TEXT");
+const templates=db.prepare("SELECT dt.id,d.name FROM domain_templates dt JOIN domains d ON d.id=dt.domain_id WHERE dt.status='available'").all();
+for(const template of templates){
+ const id=Number(db.prepare("INSERT INTO sites(name,domain) VALUES(?,?)").run("集成测试站点",template.name).lastInsertRowid);
+ applySiteTemplate(db,id,template.id);
+ const assets=db.prepare("SELECT * FROM assets WHERE site_id=?").all(id);
+ assert.ok(assets.every(asset=>!/^\d+$/.test(asset.type)),"设备名称应为名称,而不是模板编号");
+ assert.equal(db.prepare("SELECT COUNT(*) n FROM asset_groups WHERE site_id=?").get(id).n,1);
+ applySiteTemplate(db,id,template.id,true);
+ console.log(`真实项目模板 ${template.id} 应用及替换通过;${assets.length} 类设备`);
+}
+db.close();console.log("测试只修改隔离副本:"+temp);
--- /dev/null
+import {test} from "node:test";
+import assert from "node:assert/strict";
+import {DatabaseSync} from "node:sqlite";
+import {applySiteTemplate,saveSiteMenu,summarizeAssetStructure,validateSiteParent,siteDashboardCatalog,selectDomainDashboard,saveSiteApplication} from "../lib/site-workspace.mjs";
+function fixture(){
+ const db=new DatabaseSync(":memory:");
+ db.exec(`
+ CREATE TABLE sites(id INTEGER PRIMARY KEY,name TEXT,parent_id INTEGER,domain TEXT,app_name TEXT,app_icon TEXT,app_icon_filename TEXT,app_color_scheme TEXT,template_id INTEGER,template_applied_at TEXT,page_config_json TEXT,status TEXT,updated_at TEXT);
+ CREATE TABLE domains(id INTEGER PRIMARY KEY,name TEXT);
+ CREATE TABLE domain_templates(id INTEGER PRIMARY KEY,domain_id INTEGER,name TEXT,status TEXT);
+ CREATE TABLE device_templates(id INTEGER PRIMARY KEY,name TEXT);
+ CREATE TABLE domain_template_assets(id INTEGER PRIMARY KEY,domain_template_id INTEGER,device_template_id INTEGER,count INTEGER,rule TEXT,sort_order INTEGER);
+ CREATE TABLE domain_template_mu(domain_template_id INTEGER,page_key TEXT);
+ CREATE TABLE domain_pages(id INTEGER PRIMARY KEY,domain_template_id INTEGER,name TEXT,description TEXT,json_text TEXT);
+ CREATE TABLE template_menu_items(id INTEGER PRIMARY KEY,domain_template_id INTEGER,parent_id INTEGER,label TEXT,page_type TEXT,ref_id TEXT,sort_order INTEGER);
+ CREATE TABLE assets(id INTEGER PRIMARY KEY,site_id INTEGER,type TEXT,count INTEGER,rule TEXT,sort_order INTEGER);
+ CREATE TABLE cubes(id INTEGER PRIMARY KEY,site_id INTEGER,name TEXT,content TEXT,status TEXT,sort_order INTEGER,gen_file_id INTEGER);
+ CREATE TABLE gen_files(id INTEGER PRIMARY KEY,site_id INTEGER,cube_id INTEGER,filename TEXT,json_text TEXT);
+ CREATE TABLE asset_groups(id INTEGER PRIMARY KEY,site_id INTEGER,name TEXT,category TEXT,template_name TEXT,structure_json TEXT);
+ CREATE TABLE mu_selections(site_id INTEGER,page_key TEXT,selected INTEGER);
+ CREATE TABLE menu_items(id INTEGER PRIMARY KEY,site_id INTEGER,parent_id INTEGER,label TEXT,page_type TEXT,ref_id TEXT,sort_order INTEGER,source_key TEXT);
+ INSERT INTO sites(id,name,parent_id,domain) VALUES(1,'北京市自来水厂',NULL,'水务'),(2,'朝阳区',1,'水务'),(3,'乡镇',2,'水务'),(4,'独立站点',NULL,'水务');
+ INSERT INTO domains VALUES(1,'水务');INSERT INTO domain_templates VALUES(10,1,'水厂模板','available');
+ INSERT INTO device_templates VALUES(20,'水泵');INSERT INTO domain_template_assets VALUES(30,10,20,2,'P-##',0);
+ INSERT INTO domain_template_mu VALUES(10,'设备/监控');
+ INSERT INTO domain_pages VALUES(40,10,'综合总览','总览','{"widgets":[]}'),(41,10,'新页面','','{}');
+ INSERT INTO template_menu_items VALUES(50,10,NULL,'运行监控',NULL,NULL,0),(51,10,50,'领域总览','cube','40',1),(52,10,50,'设备监控','mu','设备/监控',2);
+ INSERT INTO assets(site_id,type,count,rule,sort_order) VALUES(4,'独立设备',8,'D-##',0);
+ `);return db;
+}
+
+test("未应用模板的站点也能预览领域页面,过滤空白和非法配置",()=>{const db=fixture();db.exec("INSERT INTO domain_pages VALUES(42,10,'错误页面','','bad-json')");const catalog=siteDashboardCatalog(db,2);assert.equal(catalog.template_name,"水厂模板");assert.deepEqual(catalog.pages.map(page=>page.id),[40]);assert.equal(catalog.pages[0].cube_id,null);assert.equal(db.prepare("SELECT COUNT(*) n FROM cubes").get().n,0);db.close();});
+test("领域仪表板勾选复制配置、取消不删页面、再次选择不重复复制",()=>{const db=fixture();selectDomainDashboard(db,2,40,true);const cube=db.prepare("SELECT * FROM cubes WHERE site_id=2").get();let site=db.prepare("SELECT * FROM sites WHERE id=2").get();assert.deepEqual(JSON.parse(site.page_config_json).dashboard_ids,[cube.id]);assert.equal(db.prepare("SELECT json_text FROM gen_files WHERE cube_id=?").get(cube.id).json_text,'{"widgets":[]}');assert.equal(site.template_applied_at,null);assert.equal(db.prepare("SELECT COUNT(*) n FROM asset_groups WHERE site_id=2").get().n,0);selectDomainDashboard(db,2,40,false);site=db.prepare("SELECT * FROM sites WHERE id=2").get();assert.deepEqual(JSON.parse(site.page_config_json).dashboard_ids,[]);assert.equal(siteDashboardCatalog(db,2).pages[0].cube_id,cube.id);selectDomainDashboard(db,2,40,true);assert.equal(db.prepare("SELECT COUNT(*) n FROM cubes WHERE site_id=2").get().n,1);assert.equal(db.prepare("SELECT COUNT(*) n FROM gen_files WHERE site_id=2").get().n,1);assert.throws(()=>selectDomainDashboard(db,2,99,true),/不属于/);assert.throws(()=>selectDomainDashboard(db,2,40,"true"),/有效/);assert.equal(db.prepare("SELECT count FROM assets WHERE site_id=4").get().count,8);db.close();});
+test("复用既有同名仪表板,不覆盖现场配置并保留其他页面和场景选择",()=>{const db=fixture();db.exec(`INSERT INTO cubes VALUES(80,2,'综合总览','现场修改','generated',0,90),(81,2,'现场专用页面','','generated',1,NULL);INSERT INTO gen_files VALUES(90,2,80,'现场.json','{"custom":true}');UPDATE sites SET page_config_json='{"dashboard_ids":[81],"twin_ids":["site-overview"]}' WHERE id=2`);selectDomainDashboard(db,2,40,true);const config=JSON.parse(db.prepare("SELECT page_config_json FROM sites WHERE id=2").get().page_config_json);assert.deepEqual(config.dashboard_ids,[81,80]);assert.deepEqual(config.twin_ids,["site-overview"]);assert.equal(db.prepare("SELECT json_text FROM gen_files WHERE id=90").get().json_text,'{"custom":true}');assert.equal(siteDashboardCatalog(db,2).local_cubes[0].id,81);db.close();});
+test("优先采用站点所选模板,旧领域别名仅在模板唯一时映射",()=>{const db=fixture();db.exec("UPDATE sites SET domain='水务,污水厂' WHERE id=2");assert.equal(siteDashboardCatalog(db,2).pages.length,1);db.exec("INSERT INTO domain_templates VALUES(11,1,'另一水厂模板','available')");assert.equal(siteDashboardCatalog(db,2).pages.length,0);db.exec("UPDATE sites SET template_id=10 WHERE id=2");assert.equal(siteDashboardCatalog(db,2).pages[0].id,40);db.close();});
+test("站点上级校验:保持三级层级,阻止自身、后代和不存在的上级",()=>{const db=fixture();assert.equal(validateSiteParent(db,3,2),2);assert.throws(()=>validateSiteParent(db,1,3),/下级/);assert.throws(()=>validateSiteParent(db,2,2),/自身/);assert.throws(()=>validateSiteParent(db,null,99),/不存在/);db.close();});
+test("模板应用复制设备名称、实例、标准页面与仪表板,重映射菜单并保留站点层级",()=>{const db=fixture();applySiteTemplate(db,2,10);const site=db.prepare("SELECT * FROM sites WHERE id=2").get();assert.equal(site.name,"朝阳区");assert.equal(site.parent_id,1);assert.equal(site.template_id,10);assert.equal(db.prepare("SELECT type,count FROM assets WHERE site_id=2").get().type,"水泵");const root=JSON.parse(db.prepare("SELECT structure_json FROM asset_groups WHERE site_id=2").get().structure_json)[0];assert.deepEqual(root.children[0].children.map(node=>node.name),["P-01","P-02"]);assert.equal(db.prepare("SELECT COUNT(*) n FROM mu_selections WHERE site_id=2").get().n,1);const pages=db.prepare("SELECT * FROM cubes WHERE site_id=2").all();assert.equal(pages.length,1);const menu=db.prepare("SELECT * FROM menu_items WHERE site_id=2 ORDER BY sort_order").all();assert.equal(menu[1].parent_id,menu[0].id);assert.equal(Number(menu[1].ref_id),pages[0].id);assert.deepEqual(JSON.parse(site.page_config_json).dashboard_ids,[pages[0].id]);assert.equal(db.prepare("SELECT count FROM assets WHERE site_id=4").get().count,8);assert.throws(()=>applySiteTemplate(db,2,10),/确认/);applySiteTemplate(db,2,10,true);assert.equal(db.prepare("SELECT COUNT(*) n FROM cubes WHERE site_id=2").get().n,1);db.close();});
+test("菜单保存允许临时编号和重排,循环校验失败不破坏原菜单",()=>{const db=fixture();const menu=saveSiteMenu(db,2,[{id:-2,parent_id:-1,label:"子页面",page_type:"mu",ref_id:"x"},{id:-1,parent_id:null,label:"分组",page_type:""}]);assert.equal(menu[0].parent_id,menu[1].id);assert.throws(()=>saveSiteMenu(db,2,[{id:1,parent_id:2,label:"a"},{id:2,parent_id:1,label:"b"}]),/循环/);assert.equal(db.prepare("SELECT COUNT(*) n FROM menu_items WHERE site_id=2").get().n,2);db.close();});
+test("资产树保存同步设备摘要:分组不计数,嵌套设备按模板统计",()=>{const db=fixture();summarizeAssetStructure(db,2,[{node_type:"group",template_name:"不计数",children:[{node_type:"device",template_name:"水泵",children:[]},{node_type:"group",children:[{node_type:"device",template_name:"水泵"},{node_type:"device",template_name:"水池"}]}]}]);assert.deepEqual(db.prepare("SELECT type,count FROM assets WHERE site_id=2 ORDER BY sort_order").all().map(row=>({...row})),[{type:"水泵",count:2},{type:"水池",count:1}]);db.close();});
--- /dev/null
+import {test} from 'node:test';
+import assert from 'node:assert/strict';
+import {readFileSync} from 'node:fs';
+import {createRequire,Module} from 'node:module';
+import {fileURLToPath} from 'node:url';
+import {createHash} from 'node:crypto';
+import {dirname} from 'node:path';
+import React from 'react';
+import {renderToStaticMarkup} from 'react-dom/server';
+import {ConfigProvider,Input,Checkbox,Radio,Button,Select,Table,Tabs,Switch,Modal} from 'antd';
+const require=createRequire(import.meta.url),ts=require('typescript');
+const source=fileURLToPath(new URL('../app/ui-system.tsx',import.meta.url));
+const mod=new Module(source);mod.filename=source;mod.paths=Module._nodeModulePaths(dirname(source));
+mod._compile(ts.transpileModule(readFileSync(source,'utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,jsx:ts.JsxEmit.ReactJSX,target:ts.ScriptTarget.ES2022}}).outputText,source);
+const {UiButton,UiInput,UiSelect,UiTable,UiTabbedRegion,UiActions,UiOverlay}=mod.exports,h=React.createElement;
+const brand=JSON.parse(readFileSync(new URL('../src/theme.json',import.meta.url)));
+
+test('single brand source matches supplied seeds and component overrides',()=>{
+ assert.equal(createHash('sha256').update(JSON.stringify(brand)).digest('hex'),'dcb661a0c72383073ec7f9cb30a7defffb7bfbbb690b92e2823ca174218ec7eb','exact supplied theme, not a partial approximation');
+ assert.equal(brand.token.colorPrimary,'#008cd6');assert.equal(brand.token.fontSize,16);assert.equal(brand.token.borderRadius,4);
+ assert.equal(brand.token.colorSuccess,'#00cc66');assert.equal(brand.token.colorWarning,'#ffa400');assert.equal(brand.token.colorError,'#f55d3e');
+ assert.equal(brand.components.Table.rowHoverBg,'rgb(230,242,254)');assert.equal(brand.components.Switch.trackHeight,16);assert.equal(brand.components.Button.fontWeight,600);
+});
+test('visible form controls render only the shared antd components',()=>{
+ assert.equal(UiButton({className:'button',children:'保存'}).type,Button);
+ assert.equal(UiButton({role:'switch','aria-checked':true}).type,Switch);
+ assert.equal(UiInput({type:'text'}).type,Input);assert.equal(UiInput({type:'checkbox'}).type,Checkbox);assert.equal(UiInput({type:'radio'}).type,Radio);
+});
+test('single-select retains defaults, numeric ids, disabled options and legacy callbacks',()=>{
+ let selected;const element=UiSelect({value:7,name:'domain',onChange:event=>selected=event.target.value,children:[h('option',{value:'',disabled:true,key:0},'请选择'),h('option',{value:7,key:7},'水务')]});
+ assert.equal(element.type,Select);assert.equal(element.props.value,'7');assert.equal(element.props.defaultValue,'7');assert.equal(element.props.options[0].disabled,true);
+ element.props.onChange('7');assert.equal(selected,'7');
+});
+test('table keeps row/cell click callbacks and spanning empty rows',()=>{
+ let row=false,cell=false;
+ const element=UiTable({children:[h('thead',{key:'head'},h('tr',null,h('th',null,'名称'),h('th',null,'采集点'))),h('tbody',{key:'body'},h('tr',{key:'one',onClick:()=>row=true},h('td',{onClick:()=>cell=true},'水泵'),h('td',null,12)),h('tr',{key:'empty'},h('td',{colSpan:2},'暂无数据')))]});
+ assert.equal(element.type,Table);assert.equal(element.props.columns.length,2);assert.equal(element.props.dataSource.length,2);
+ const [first,empty]=element.props.dataSource;
+ element.props.onRow(first).onClick();element.props.columns[0].onCell(first).onClick();assert.ok(row&&cell);
+ assert.equal(element.props.columns[0].render(null,first),'水泵');assert.equal(element.props.columns[0].onCell(empty).colSpan,2);assert.equal(element.props.columns[1].onCell(empty).colSpan,0);
+});
+test('filter and editor tabs own their business content and preserve tab actions',()=>{
+ let selected;const content=h('p',null,'属性列表');
+ const element=UiTabbedRegion({navigation:h('div',{className:'tabs'},h(UiButton,{className:'active',onClick:()=>selected='basic'},'基本信息'),h(UiButton,{onClick:()=>selected='props'},'属性')),content});
+ assert.equal(element.type,Tabs);assert.equal(element.props.items.length,2);assert.ok(element.props.items.every(item=>item.children===content));
+ element.props.onChange(element.props.items[1].key);assert.equal(selected,'props');
+});
+test('action groups cap visible buttons at three and retain overflow handlers',()=>{
+ let invoked=false;
+ const children=Array.from({length:5},(_,i)=>h(UiButton,{key:i,className:'button',onClick:()=>invoked=i===4},'操作'+i));
+ const result=UiActions({children}),visible=React.Children.toArray(result.props.children);assert.equal(visible.length,3);
+ assert.ok(visible.slice(0,2).every(action=>action.props.secondary));
+ const dropdown=visible[2];assert.equal(dropdown.props.menu.items.length,3);
+ dropdown.props.menu.onClick({key:'2',domEvent:{stopPropagation(){}}});assert.ok(invoked);
+ const two=UiActions({children:children.slice(0,2)}).props.children;assert.equal(two.filter(action=>!action.props.secondary).length,1);
+ const MinifiedComponent=()=>null;MinifiedComponent.uiPrimaryAction=true;
+ const marked=UiActions({children:[h(MinifiedComponent,{key:'a'},'导入'),h(MinifiedComponent,{key:'b'},'新增')]}).props.children;
+ assert.equal(marked[0].props.secondary,true);assert.equal(marked[1].props.secondary,undefined,'works independently of build-time function names');
+ const picker=h('input',{type:'file',hidden:true,key:'file'}),search=h('label',{key:'search'},'搜索');
+ const withAux=UiActions({children:[...children,picker,search]}).props.children;
+ assert.equal(withAux.length,5);assert.equal(withAux[3].type,'input');assert.equal(withAux[3].props.hidden,true);assert.equal(withAux[4].type,'label');
+});
+test('modal owns title/footer, preserves nested confirmation and closes via original callback',()=>{
+ let closed=false;const confirmation=h('span',{key:'confirm'},'nested confirmation');
+ const element=UiOverlay({onClick:()=>closed=true,children:[confirmation,h('div',{className:'modal editor-modal',style:{width:640},key:'modal'},h('header',null,h('h2',null,'编辑模板')),h('div',{className:'modal-body'},'配置'),h('footer',null,h(UiButton,{className:'button secondary'},'取消'),h(UiButton,{className:'button'},'保存')))]});
+ const modal=element.props.children[1];assert.equal(modal.type,Modal);assert.equal(modal.props.title,'编辑模板');assert.equal(modal.props.width,640);
+ assert.equal(modal.props.children.length,1);assert.equal(modal.props.footer.type,UiActions);assert.equal(element.props.children[0].length,1);
+ modal.props.onCancel({});assert.ok(closed);
+});
+test('antd table and choices render accessible server markup',()=>{
+ const html=renderToStaticMarkup(h(ConfigProvider,{theme:brand},h(UiTable,null,h('thead',null,h('tr',null,h('th',null,'选择'))),h('tbody',null,h('tr',null,h('td',null,h(UiInput,{type:'checkbox','aria-label':'选择模板'})))))));
+ assert.match(html,/ant-table/);assert.match(html,/ant-checkbox/);assert.match(html,/aria-label="选择模板"/);
+});
+test('business source has no bare visible input, select, textarea, button or table',()=>{
+ for(const file of ['page.tsx','site-workspace.tsx']){
+ const text=readFileSync(new URL('../app/'+file,import.meta.url),'utf8'),ast=ts.createSourceFile(file,text,99,true,4),violations=[];
+ function visit(node){if(ts.isJsxOpeningElement(node)||ts.isJsxSelfClosingElement(node)){
+ const tag=node.tagName.getText(ast);if(['button','select','textarea','table'].includes(tag))violations.push(tag);
+ if(tag==='input'){const type=node.attributes.properties.find(p=>p.name?.getText(ast)==='type')?.initializer?.text;if(!['file','hidden'].includes(type))violations.push('input:'+type);}
+ }ts.forEachChild(node,visit);}visit(ast);assert.deepEqual(violations,[],file);
+ }
+ const styles=['globals.css','site-workspace.css','ui-system.css','ui-layout-compat.css'].map(file=>readFileSync(new URL('../app/'+file,import.meta.url),'utf8')).join('\n');
+ assert.doesNotMatch(styles,/--(?:company|brand)-/);
+});
+test('thumbnail and custom navigation sizing survive antd base styles, with responsive rules retained',()=>{
+ const css=readFileSync(new URL('../app/ui-layout-compat.css',import.meta.url),'utf8');
+ assert.match(css,/\.station-dashboard-image\.station-ui-button\.ant-btn\{[^}]*height:145px/);
+ assert.match(css,/\.ai-fab\.station-ui-button\.ant-btn\{[^}]*border-radius:50%/);
+ assert.match(css,/@media/);assert.doesNotMatch(css,/(?:font-family|font-size|font-weight|background|box-shadow|color):/);
+ const layout=readFileSync(new URL('../app/layout.tsx',import.meta.url),'utf8');
+ assert.ok(layout.indexOf('site-workspace.css')<layout.indexOf('ui-system.css'));
+});
}
],
"paths": {
+ "@/assets/*": ["./src/assets/*"],
+ "@/components/*": ["./src/components/*"],
+ "@/templates/*": ["./src/templates/*"],
"@/*": ["./*"]
}
},
".next/dev/types/**/*.ts",
"**/*.mts"
],
- "exclude": ["node_modules"]
+ "exclude": ["node_modules", "node_modules-before-ui-skill", "dist"]
}
},
"include": [
"next-env.d.ts",
+ "src/vite-env.d.ts",
"app/**/*.ts",
"app/**/*.tsx"
],
import vinext from "vinext";
+import svgr from "vite-plugin-svgr";
+import { fileURLToPath } from "node:url";
import { defineConfig } from "vite";
import hostingConfig from "./.openai/hosting.json";
import { sites } from "./build/sites-vite-plugin";
return {
// 让 Vite/VinExt 整体挂在 /t/stationbuilder 子路径下(与 nginx 反向代理前缀一致)
base: "/t/stationbuilder",
+ resolve: { alias: { "@/assets": fileURLToPath(new URL("./src/assets", import.meta.url)) } },
server: {
host: "0.0.0.0",
allowedHosts: true,
: {}),
},
plugins: [
+ svgr(),
vinext(),
sites(),
cloudflare({
-import vinext from "vinext";
+import react from "@vitejs/plugin-react";
+import svgr from "vite-plugin-svgr";
+import { fileURLToPath } from "node:url";
import { defineConfig, type Plugin } from "vite";
/**
*
* The supplied source archive intentionally omits the Sites hosting metadata
* and generated hosting plugin imported by vite.config.ts. This config keeps
- * the same application base path while allowing local development and visual
- * regression work without production-only files.
+ * the same application base path and business component, using a client-only
+ * local preview without production hosting metadata or the RSC development runtime.
*/
export default defineConfig({
base: "/t/stationbuilder",
+ resolve: { alias: { "@/assets": fileURLToPath(new URL("./src/assets", import.meta.url)) } },
server: {
host: "127.0.0.1",
allowedHosts: true,
},
},
},
- plugins: [vinext(), stripInlineSourcemap()],
+ plugins: [svgr(), react(), stripInlineSourcemap()],
});
--- /dev/null
+# 站点管理重构功能规格
+
+### 标准功能菜单补齐
+
+第五步自动包含第四步已选的标准功能页面,并按原目录的一、二级结构和顺序排列在“数字孪生”“领域监控”之后,不归入领域监控。旧版尚未配置标准页面的站点沿用默认全选逻辑,第四步显示同一选择;第四步保存草稿/下一步或第五步保存时持久化默认选择。显式取消全部页面后记录初始化标记,不恢复默认全选。再次进入或修改第四步选择时同步菜单,保留仍有效的菜单名称编辑。
+
+### 2026-09-16 页面配置补充
+
+“领域扩展仪表板”采用与数字孪生场景一致的缩略图卡片及复选框。优先展示站点选择的领域模板页面,未选择模板时按所属领域匹配;未应用整套模板也可预览、选择仪表板。相同模板的同名页面沿用领域方案页的展示逻辑,仅展示最新有效配置。
+
+点击缩略图或预览图标打开放大预览。勾选后仅复制该仪表板的配置到当前站点并自动保存,下一步可加入菜单。已有同名现场页面优先复用,不覆盖其配置;取消选择不会删除配置,再次选择不重复复制。保留其他页面及数字孪生场景的选择。
+
+## 1. 核心逻辑
+
+先建立完整站点层级,再逐个进入站点配置。建立站点不等于完成建站,不要求创建时连续执行配置向导。
+
+站点管理以可展开的层级表格为主:站点名称、所属领域、站点模板、配置状态、直接子站点数、最近修改、操作。支持名称搜索、领域单选、待配置/已配置筛选、展开或收起全部。筛选命中下级时保留祖先以呈现管理归属。支持任意层级,禁止自身或后代作为上级。
+
+## 2. 建立站点
+
+新增站点打开右侧抽屉,仅填写上级站点、站点名称、所属领域、基本情况与站点模板。默认为“从模板建立站点”,也可明确选择“建立空白站点”。可从现有站点的更多操作新增子站点,自动带入上级、领域和模板选择。
+
+选择模板只记录待应用的模板,不在此时创建站点内部资产、页面或菜单。创建后留在列表;“创建并继续新增”保留同一上级、领域与模板,方便连续建立同级站点。保存普通基本信息不会复制或覆盖内部配置。
+
+站点名称用于打开基本信息,独立的“配置站点”进入内部配置。存在下级站点时禁止单独删除上级,需先调整下级归属或处理下级站点,避免产生孤立层级。列表的“已配置”表示存在配置内容,并不等价于已经生成或发布成功;最终交付沿用生成与结果页。
+
+## 3. 站点内部配置
+
+顶部始终保留同一行五个步骤,显示当前站点切换器与模板待应用/已应用状态,不重复展示站点名称;底部保持上一步、保存草稿、下一步三个按钮。
+
+| 步骤 | 内容及职责 |
+| --- | --- |
+| 1 站点信息 | 保留站点基本信息及资料维护,不重复展示领域模板配置区 |
+| 2 资产设置 | 直接打开当前站点唯一资产组工作区;不再设置快速/手动二次入口 |
+| 3 运维流程配置 | 中间留白;保留步骤 Header 和底部按钮,供后续设计 |
+| 4 页面配置 | 按功能模块选择标准页面,选择模板中的领域扩展仪表板及 SC 场景;不设“设备运维”分类入口 |
+| 5 菜单配置 | 从第四步已选择的页面添加菜单,修改名称、上级分组、顺序,并预览菜单结构 |
+
+五步之后通过“保存并进入生成确认”进入原有生成、校验、结果及下载流程,不将生成确认挤占第五步。新一轮生成与打包按第四步选择的仪表板范围执行;交付包同步写入已保存的资产组与页面选择配置。云端实际生成仍需在 Linux 环境集成验证。
+
+### 模板应用
+
+建立站点时选择的模板自动成为第一步当前选择。首次应用复制设备模板与实例结构、标准页面选择、已有领域仪表板 JSON、菜单及页面关联;站点名称、基本情况、上级关系保持不变。应用成功直接进入资产工作区,配置人员以模板内容为起点进行调整。
+
+已有内部配置时必须确认替换后才能应用模板,服务端同样执行保护。模板复制采用事务,失败回滚;仅影响当前站点,不联动覆盖父子站点。页面、菜单编号重新关联,不能直接复用领域模板的原始编号。
+
+### 资产工作区
+
+左侧站点资产组名称、批量创建与领域设备模板;中间资产树及复制、深复制、节点属性、删除、添加同级/子级、模板应用、树/列表视图、搜索、缩放工具。节点右侧抽屉保留基础信息设定、属性设定、数据绑定三个 Tab。批量创建、Excel 导入及单节点编辑都在当前工作区完成,不跳去设备模板管理。
+
+节点抽屉“确定”先写入当前草稿,工作区“保存”、底部“保存草稿”或切换步骤时落库。正在编辑节点或批量配置时阻止切换,要求先确定或取消。保存后按设备节点统计设备模板数量,资产分组不计为设备,避免树结构与生成使用的设备清单不一致。旧版数字模板编号在工作区还原为真实设备模板名称,并在明确保存时修正。
+
+### 页面和菜单职责分离
+
+第四步负责“要哪些页面”,标准页面按业务功能分组勾选,领域仪表板显示缩略图并可预览。标准页面与扩展页面选择自动保存。第五步仅负责“如何组织入口”,不再次提供 MU/Cube 功能选择向导。一个页面默认仅添加一个入口;菜单分组支持多层级,禁止循环。
+
+页面选择取消后,不自动删除客户已经编辑的菜单;菜单保存时检查已取消页面的引用,提示移除失效入口后保存。这样不会静默丢失菜单名称和层级。
+
+## 4. UI约束
+
+沿用现有公司统一 Header、外层产品菜单、Roboto 与中文回退字体、蓝色主操作、浅灰表头、白色内容、细边框及右侧抽屉。不额外建立第二套产品侧边菜单。内容区资产模板与页面来源属于编辑面板而非产品菜单。
+
+全局操作最多三个按钮,在同一行;多余记录操作收进更多菜单。底部按钮统一为上一步、保存草稿、下一步。内容区可滚动,长列表使用内部滚动,不通过堆叠大按钮展开功能。
+
+本次参考网址在当前浏览器环境跳转登录页,未进入登录后的最新工单界面;本版依据工程中已建立的公司主题和此前确认过的截图样式设计,不能视为完成登录后参考页面逐像素比对。
+
+## 5. 本次实现边界与后续开发
+
+已实现本地预览工程的层级管理、基本信息创建、模板记录与应用、五步配置、统一资产工作区、页面选择、菜单编辑及数据库持久化。
+
+运维流程中间区域严格留白,未实现运维工作流;SC 场景画面为模拟设计数据,选择与菜单配置可以记录,但尚未接入实际 SC 服务,不能称为已部署场景。原有云端 AI 生成与产品服务集成沿用原工程,未在本机验证生产调用。
+
+云端正式实现仍需补充权限、并发修改控制、模板替换快照/恢复、领域模板的运维流程和 SC 场景导入以及取消页面后的交付校验。
+
+## 6. 验证
+
+TypeScript 检查及本地 Vite 生产构建通过;新增四项内存 SQLite 测试覆盖站点层级循环、模板复制及替换保护、菜单关联与循环校验、资产数量摘要同步。真实项目数据库隔离副本中三个可用领域模板的应用及替换测试均通过。
+
+数据库迁移只增加模板和页面选择字段,保留原数据;迁移前备份为 data/db/StationBuilder-before-site-workspace-20260916.sqlite。测试使用内存数据库,不覆盖现场站点配置。