From: root Date: Mon, 14 Sep 2026 06:44:14 +0000 (+0800) Subject: 站点编辑Step4更名为菜单设置;移除默认菜单结构展示区 X-Git-Url: http://acesimba.cloud/gitweb/?a=commitdiff_plain;h=133709f690d102803a0a5be2cbe739b4526dc0e1;p=StationBuilder.git 站点编辑Step4更名为菜单设置;移除默认菜单结构展示区 --- diff --git a/app/page.tsx b/app/page.tsx index 036e122..15a1348 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -14,7 +14,7 @@ const meta: Record = basic:{label:"站点基本属性",icon:"▤",subtitle:"描述站点业务背景,信息越完整,生成结果越符合预期"}, assets:{label:"站点资产设置",icon:"▧",subtitle:"通过快速配置或手动配置建立场站资产结构"}, pageConfig:{label:"页面配置",icon:"",subtitle:""}, - functions:{label:"功能与菜单",icon:"☷",subtitle:"选择 MU 标准页面,定义 Cube 扩展页面及菜单结构"}, + functions:{label:"菜单设置",icon:"☷",subtitle:"选择 MU 标准页面,定义 Cube 扩展页面及菜单结构"}, generate:{label:"生成确认",icon:"↻",subtitle:"确认生成范围,调用 Cube Skill 生成页面和模拟数据"}, result:{label:"站点结果",icon:"◫",subtitle:"集中查看、预览、校验并下载站点交付物"}, preview:{label:"Cube 页面预览",icon:"▣",subtitle:"检查单个 Cube 页面效果,并通过反馈快速重新生成"}, @@ -56,6 +56,7 @@ type SiteDetail = { id:number; name:string; domain:string; description:string; supplement:string; status:string; style_id?: number | null; parent_id?: number | null; + app_name?: string; app_icon?: string; app_icon_filename?: string; created_at:string; updated_at:string; assets: Asset[]; cubes: Cube[]; mu: string[]; menu: MenuItem[]; files: ReferenceFile[]; }; @@ -1052,25 +1053,6 @@ function Assets({site,onChange,saving,notify,onViewCompletePrompt,mode,onModeCha })} -
-
⌄ {site.name || "未命名站点"}
- {rows.filter(r=>r.type).map((row,i)=>
-
⌄ {row.type} {row.count}
- {row.count>0&&
{formatInstances(row.rule, row.count)}
} -
)} -
- -
-
  • 列出站点可能涉及的主要设备类型(泵、风机、换热器、仪表等)。
  • 为每种设备估算典型数量与命名规则。
  • 命名规则用 # 表示序号,如 CH-## 生成 CH-01/CH-02;纯前缀如 beng- 会自动补 -01/-02。
  • 资产层级按「站点 → 类型 → 实例」三级展开预览。
- -

基于站点名称 / 领域 / 描述 / 补充要求,由 AI 分析并补充设备类型、数量与命名规则到资产配置列表(位于您已填项下方)。

-
- - -
- {(completing||completeLog)&&
AI 智能补全
{completeLog||"(等待 AI 分析…)"}
} -
- {saving&&
正在保存…
}
} ; @@ -1238,153 +1220,80 @@ function AssetTreeBranch({node,selectedId,search,onSelect,onOpen,onAddChild}:{no } 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}){ - const [tab,setTab]=useState<"mu"|"cube"|"menu">("cube"); const leaves = useMemo(()=>buildMuLeaves(muTree),[muTree]); const allMuKeys = useMemo(()=>leaves.map(l=>l.key),[leaves]); const selectedMu = site?.mu || []; - // 默认全选中:site.mu 为空时按全部叶子键显示,首次勾选才落地保存 const effSelected = selectedMu.length ? selectedMu : allMuKeys; + const [appName,setAppName]=useState(site?.app_name||""); + const [appIcon,setAppIcon]=useState(site?.app_icon); + const [appIconFile,setAppIconFile]=useState(site?.app_icon_filename); if(!site) return
加载中…
; - const updateCube=(idx:number, patch:Partial)=>{ - const next = site.cubes.map((c,i)=>i===idx?{...c,...patch}:c); - onSiteChange({...site, cubes:next}); - }; - const saveCube=async(idx:number)=>{ - const cube = site.cubes[idx]; - try{ - if(cube.id){ - await fetchDb(`/sites/${site.id}/cubes/${cube.id}`, {method:"PUT", headers:{"Content-Type":"application/json"}, body:JSON.stringify(cube)}); - } else { - const created = await fetchDb(`/sites/${site.id}/cubes`, {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify(cube)}) as Cube; - const next = [...site.cubes]; next[idx] = {...cube, id:created.id!}; onSiteChange({...site, cubes:next}); - } - notify("Cube 页面已保存"); - }catch(e:any){ notify("保存失败: "+e.message); } - }; - const addCube=()=>onSiteChange({...site, cubes:[...site.cubes, {name:"",content:"",status:"pending",sort_order:site.cubes.length}]}); - const removeCube=async(idx:number)=>{ - const cube = site.cubes[idx]; - if(cube.id) try{ await fetchDb(`/sites/${site.id}/cubes/${cube.id}`, {method:"DELETE"}); }catch(e:any){ notify("删除失败: "+e.message); return; } - onSiteChange({...site, cubes:site.cubes.filter((_,i)=>i!==idx)}); - }; - const selectStyle=async(id:number|null)=>{ - try{ - const refreshed:SiteDetail = await fetchDb(`/sites/${site.id}`, {method:"PUT", headers:{"Content-Type":"application/json"}, body:JSON.stringify({ - name:site.name, domain:site.domain, description:site.description, supplement:site.supplement, style_id: id - })}) as SiteDetail; - onSiteChange(refreshed); - notify(id ? "已应用页面样式" : "已清除页面样式"); - }catch(e:any){ notify("应用样式失败: "+e.message); } - }; - const generateCube=async(idx:number)=>{ - const cube = site.cubes[idx]; - if(!cube.id) await saveCube(idx); - notify(`开始生成 ${cube.name}…`); - try{ - await fetchDb(`/sites/${site.id}/cubes/${cube.id}/generate`, {method:"POST"}); - notify(`${cube.name} 生成完成`); - const refreshed:SiteDetail = await fetchDb(`/sites/${site.id}`); - onSiteChange(refreshed); - }catch(e:any){ notify("生成失败: "+e.message); } - }; - const downloadCube=(cube:Cube)=>{ - if(!cube.id) return; - fetch(`/api/db/sites/${site.id}/cubes/${cube.id}/file`).then(r=>r.blob()).then(b=>downloadBlob(b, cube.gen_filename || `${cube.name}.json`)).catch(e=>notify("下载失败")); - }; - const previewCube=(cube:Cube)=>{ - notify(`预览 ${cube.name}`); + const toggleMu=(key:string)=>{ + const base = selectedMu.length ? selectedMu : allMuKeys; + onMuChange(base.includes(key)? base.filter(k=>k!==key) : [...base, key]); }; - - // ===== AI Cube 页面补全 ===== - const [completing,setCompleting]=useState(false); - const [completeLog,setCompleteLog]=useState(""); - const completeConsoleRef=useRef(null); - useEffect(()=>{ if(completeConsoleRef.current) completeConsoleRef.current.scrollTop=completeConsoleRef.current.scrollHeight; },[completeLog]); - const appendCubes=(items:Array<{name:string;content:string}>)=>{ - const base = site?.cubes || []; - const next = [...base, ...items.map((it,i)=>({name:it.name, content:it.content||"", status:"pending", sort_order: base.length+i}))]; - onSiteChange({...site, cubes:next}); - notify(`AI 已补充 ${items.length} 个 Cube 页面(位于您已填项下方)`); + const pickAppIcon=(e:React.ChangeEvent)=>{ + const f=e.target.files?.[0]; if(!f) return; + if(!["image/png","image/svg+xml","image/jpeg"].includes(f.type)||f.size>100*1024){ notify("仅支持 png/svg 格式,且小于 100KB"); return; } + const reader=new FileReader(); reader.onload=()=>{ setAppIcon(reader.result as string); setAppIconFile(f.name); }; reader.readAsDataURL(f); }; - const runCompleteCubes=async()=>{ - if(!site?.id || completing) return; - setCompleting(true); setCompleteLog(""); - const watchdog=setTimeout(()=>{ setCompleting(false); notify("AI Cube 补全超时(>6 分钟),请重试"); }, 360000); + const saveAppInfo=async()=>{ try{ - const res=await fetch(`/api/db/sites/${site.id}/complete-cubes`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:site.name,domain:site.domain,description:site.description,supplement:site.supplement,assets:site.assets})}); - if(!res.ok||!res.body) throw new Error("HTTP "+res.status); - const reader=res.body.getReader(); const dec=new TextDecoder(); let buf=""; - 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) setCompleteLog(prev=>prev+ev.log); - if(Array.isArray(ev.result)) appendCubes(ev.result); - }catch{} - } - } - notify("AI Cube 页面补全已完成"); - }catch(e:any){ notify("AI Cube 页面补全失败:"+(e?.message||e)); } - finally{ clearTimeout(watchdog); setCompleting(false); } + await fetchDb(`/sites/${site.id}`, {method:"PUT", headers:{"Content-Type":"application/json"}, body:JSON.stringify({ + name:site.name, domain:site.domain, description:site.description, supplement:site.supplement, + style_id:site.style_id, parent_id:site.parent_id, + app_name: appName, app_icon: appIcon, app_icon_filename: appIconFile + })}); + onSiteChange({...site, app_name: appName, app_icon: appIcon, app_icon_filename: appIconFile}); + notify("应用信息已保存"); + }catch(e:any){ notify("保存失败: "+e.message); } }; - return
- {/* Cube 扩展页面:左右两栏,右栏为填写建议 + AI 智能补全 */} - {tab==="cube"&&
-
-
{site.cubes.map((cube,i)=> - - - - - )}
页面名称 最多6字页面内容生成文件操作
updateCube(i,{name:e.target.value})}/>updateCube(i,{content:e.target.value})}/>{cube.status==="generated"? : 未生成}
-
-

站点页面样式 站点级 · 全部 Cube 页面共用

-

选择一个样式库方案作为本站点所有 Cube 页面的视觉风格(生成时参考其 JSON 主题参数)。

-
- - {pageStyles.map(s=>)} -
+ // 设备运维页面(复用 pageConfig-设备运维 的 MU 表格) + const opLeaves = useMemo(()=>buildMuLeaves(muTree),[muTree]); + const opGrouped = useMemo(()=>{ + const map=new Map(); + 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()); + },[opLeaves]); + + // 仪表盘示例名称 + const dashboardExamples = ["运行总览","能耗分析","设备健康度","报警事件"]; + // 数字孪生场景示例名称 + const twinExamples = ["3D 厂区漫游","设备数字孪生","管线可视化","实时数据大屏"]; + + return + {/* 应用信息 */} +
+ setAppName(e.target.value)} onBlur={saveAppInfo}/> + +
+ {appIcon?应用图标:
无
} + + {appIconFile&&{appIconFile}}
-
-
-
  • 从站点业务与设备资产出发,规划需要的监控维度(综合总览、能耗能效、设备健康、报警事件、环境监测、生产产量等)。
  • 每个 Cube 页面写明:页面目标、关键指标/图表、数据来源与模拟数据构造思路。
  • 页面名称简洁,不超过 6 个汉字。
  • AI 补充的页面会追加在您已创建页面下方,不会改动您原有的内容。
- -

基于站点名称 / 领域 / 描述 / 补充要求,以及设备资产层级,由 AI 分析并补充站点所需的 Cube 扩展页面(页面名称 + 详细内容),追加在您已建页面下方。

-
- - -
- {(completing||completeLog)&&
AI 智能补全
{completeLog||"(等待 AI 分析…)"}
} -
-
-
} - {/* 菜单结构:只读默认预览(MU 在上,Cube 全部归入「综合监控」一级菜单) */} - {tab==="menu"&&
-

默认菜单结构(展示用,无需编辑):MU 标准页面在上,所有 Cube 扩展页面归入「综合监控」一级菜单下。

-
-
⌄ {site.name || "未命名站点"}
-
-
MU 标准页面
- {leaves.filter(l=>effSelected.includes(l.key)).map(l=>
· {l.parentMenu}/{l.label}
)} - {leaves.filter(l=>effSelected.includes(l.key)).length===0&&
(未选择 MU 页面)
} -
-
-
综合监控
- {site.cubes.filter(c=>c.name&&c.name.trim()).map(c=>
· {c.name}
)} - {site.cubes.filter(c=>c.name&&c.name.trim()).length===0&&
(暂无 Cube 页面)
} -
-
-
} + +
+ + {/* 设备运维页面 */} +
+

设备运维页面

+
{opGrouped.map(g=>
{g.parentMenu}
{g.items.map(item=>)}
)}
+
+ + {/* 仪表盘页面 */} +
+

仪表盘页面

+

以下为预设仪表盘模板示例(Demo),实际内容待配置。

+
{dashboardExamples.map((name,i)=>
{name}
)}
+
+ + {/* 数字孪生场景 */} +
+

数字孪生场景

+

以下为预设数字孪生场景示例(Demo),实际内容待配置。

+
{twinExamples.map((name,i)=>
{name}
)}
+
; }