]> acesimba.cloud Git - StationBuilder.git/commitdiff
站点资产设置-设备类型改为领域内设备模板下拉,新增查看模板眼睛图标,优化列宽
authorroot <root@localhost.localdomain>
Mon, 14 Sep 2026 03:24:07 +0000 (11:24 +0800)
committerroot <root@localhost.localdomain>
Mon, 14 Sep 2026 03:24:07 +0000 (11:24 +0800)
app/globals.css
app/page.tsx

index 5bd8a7ef132eddf11a8b3a7e469c7852493b0070..af580adc1479785dc8e9b16b21a2848a893ef5c2 100644 (file)
 .data-table.editable{table-layout:fixed}
 .data-table.editable th:nth-child(1){width:40%}
 .data-table.editable th:nth-child(2){width:92px}
-.data-table.editable th:nth-child(3){width:34%}
-.data-table.editable th:nth-child(4){width:44px}
+.data-table.editable th:nth-child(3){width:56px}
+.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}
 
 /* ===== 领域方案库:列表筛选栏 + 可排序列头 ===== */
 .filter-bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:10px}
index 07c67e202056ba7ea1d612c5a072e6dcacd2b428..e9b08ed0f0c6ee88eaac5cec605b0c42a3278bcb 100644 (file)
@@ -694,8 +694,8 @@ export default function Home(){
       {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}/>}
-      {page==="siteEdit"&&siteEditSection==="assets"&&<Assets site={site} onChange={saveAssets} saving={saving} notify={notify} onViewCompletePrompt={viewCompleteAssetsPrompt} mode={assetConfigMode} onModeChange={setAssetConfigMode}/>}
+      {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>
@@ -984,11 +984,14 @@ function Basic({site,domains,onChange,onAddDomain,onUpload,saving,notify,onViewC
   </Drawer></>;
 }
 
-function Assets({site,onChange,saving,notify,onViewCompletePrompt,mode,onModeChange}:{site:SiteDetail|null;onChange:(assets:Asset[])=>void;saving:boolean;notify:(m:string)=>void;onViewCompletePrompt:()=>void;mode:"quick"|"manual";onModeChange:(mode:"quick"|"manual")=>void}){
+function Assets({site,onChange,saving,notify,onViewCompletePrompt,mode,onModeChange,go}:{site:SiteDetail|null;onChange:(assets:Asset[])=>void;saving:boolean;notify:(m:string)=>void;onViewCompletePrompt:()=>void;mode:"quick"|"manual";onModeChange:(mode:"quick"|"manual")=>void;go:(p:PageKey)=>void}){
   const [completing,setCompleting]=useState(false);
   const [completeLog,setCompleteLog]=useState("");
   const completeConsoleRef=useRef<HTMLPreElement>(null);
+  const [deviceTemplates,setDeviceTemplates]=useState<any[]>([]);
   useEffect(()=>{ if(completeConsoleRef.current) completeConsoleRef.current.scrollTop=completeConsoleRef.current.scrollHeight; },[completeLog]);
+  // 加载设备模板(按站点领域过滤)
+  useEffect(()=>{ (async()=>{ try{ const all=await fetchDb("/device-templates") as any[]; setDeviceTemplates(all.filter((t:any)=>{ try{ return JSON.parse(t.domain_names||"[]").includes(site?.domain); }catch{ return false; } })); }catch(e:any){ /* 静默失败,下拉框为空 */ } })(); },[site?.domain]);
   const appendAssets=(items:Array<{type:string;count:number;rule:string}>)=>{
     const base = site?.assets || [];
     const next = [...base, ...items.map((it,i)=>({type:it.type, count:Number(it.count)||0, rule:it.rule||"", sort_order: base.length+i}))];
@@ -1039,13 +1042,14 @@ function Assets({site,onChange,saving,notify,onViewCompletePrompt,mode,onModeCha
       <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">
-          <thead><tr><th>设备类型 *</th><th>数量 *</th><th>命名规则</th><th>操作</th></tr></thead>
-          <tbody>{rows.map((row,i)=><tr key={i} className={!row.type?"empty-row":""}>
-            <td><input value={row.type} placeholder="例如:冷水机组" onChange={e=>update(i,{type:e.target.value})}/></td>
+          <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>
-          </tr>)}</tbody>
+          </tr>})}</tbody>
         </table>
       </Panel>
       <Panel title="资产层级预览"><div className="asset-tree-preview">
@@ -2656,6 +2660,8 @@ function CompanyDeviceTemplates({notify,onViewCpPrompt}:{notify:(m:string)=>void
   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(); },[]);
+  // 支持从外部通过 URL 参数 openTemplate 自动打开指定模板编辑页
+  useEffect(()=>{ const p=new URLSearchParams(window.location.search); const tid=p.get("openTemplate"); if(tid){ 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); } };