From 7271071caa0354646657869f468a1e8dae667413 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 11 Sep 2026 17:49:17 +0800 Subject: [PATCH] =?utf8?q?=E7=AB=99=E7=82=B9=E7=BB=93=E6=9E=84=E6=A0=91(?= =?utf8?q?=E4=BE=A7=E6=A0=8F)=20+=20=E5=9F=BA=E6=9C=AC=E6=83=85=E5=86=B5?= =?utf8?q?=E6=8F=8F=E8=BF=B0=E9=9D=9E=E5=BF=85=E5=A1=AB=20+=20=E9=A1=B5?= =?utf8?q?=E9=9D=A2=E9=85=8D=E7=BD=AE=E6=96=B0=E5=A2=9E=E5=9B=9B=E4=B8=AAT?= =?utf8?q?ab?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- app/globals.css | 5 ++++ app/page.tsx | 62 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/app/globals.css b/app/globals.css index e2bdc19..f1b5605 100644 --- a/app/globals.css +++ b/app/globals.css @@ -111,6 +111,11 @@ .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} diff --git a/app/page.tsx b/app/page.tsx index de8d92a..f4ac85f 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -55,6 +55,7 @@ type ReferenceFile = { id:number; filename:string; mime:string; size:number; cre type SiteDetail = { id:number; name:string; domain:string; description:string; supplement:string; status:string; style_id?: number | null; + parent_id?: number | null; created_at:string; updated_at:string; assets: Asset[]; cubes: Cube[]; mu: string[]; menu: MenuItem[]; files: ReferenceFile[]; }; @@ -203,6 +204,7 @@ export default function Home(){ const [site,setSite]=useState(null); const [loading,setLoading]=useState(true); const [saving,setSaving]=useState(false); + const [dirty,setDirty]=useState(false); const [domains,setDomains]=useState([]); const [muTree,setMuTree]=useState([]); const [pageStyles,setPageStyles]=useState([]); @@ -215,6 +217,8 @@ export default function Home(){ const [view,setView]=useState<"table"|"card">("table"); const [siteEditSection,setSiteEditSection]=useState("basic"); const [assetConfigMode,setAssetConfigMode]=useState<"quick"|"manual">("quick"); + const [pageConfigTab,setPageConfigTab]=useState<"ops"|"dashboard"|"report"|"twin">("ops"); + const PAGE_CONFIG_LABEL:Record={ops:"设备运维",dashboard:"仪表板",report:"报表",twin:"数字孪生场景"}; const [functionTab,setFunctionTab]=useState<"mu"|"cube"|"menu">("mu"); const [previewTab,setPreviewTab]=useState<"info"|"json"|"feedback">("info"); const [feedbackCube,setFeedbackCube]=useState(null); @@ -422,6 +426,13 @@ export default function Home(){ }catch(e:any){ notify("删除失败: "+e.message); } }; + const confirmUnsaved=async():Promise=>{ + if(!dirty) return true; + return new Promise(resolve=>{ + const ok=window.confirm("当前站点有未保存的修改,切换站点将丢失这些修改。是否继续?"); + if(ok){ setDirty(false); resolve(true); } else resolve(false); + }); + }; const go=async(target:PageKey, sid?:number|"new")=>{ try{ if(target==="siteEdit")setSiteEditSection("basic"); @@ -447,6 +458,7 @@ export default function Home(){ return; } if(curId && (!site || site.id!==curId)){ + if(!(await confirmUnsaved())) return; await loadSite(curId); } setPage(target); @@ -470,10 +482,12 @@ export default function Home(){ if(!site) return; const next = {...site, ...patch}; setSite(next); + setDirty(true); setSaving(true); try{ await fetchDb(`/sites/${site.id}`, {method:"PUT", headers:{"Content-Type":"application/json"}, body:JSON.stringify(next)}); setSites(prev=>prev.map(s=>s.id===site.id?{...s, name:next.name, domain:next.domain, status:next.status}:s)); + setDirty(false); }catch(e:any){ notify("保存失败: "+e.message); } finally{ setSaving(false); } }; @@ -557,6 +571,18 @@ export default function Home(){ const systemArea = (["toolSettings","domainSettings"] as PageKey[]).includes(page); const extensionArea = (["dashboard","report-dashboard","report-doc","basic-components","func-components","filters","datasource"] as PageKey[]).includes(page); const siteEditMeta = meta[siteEditSection]; + + const siteHierarchy=useMemo(()=>{ + if(!siteId) return null; + const byId=new Map(sites.map(s=>[s.id,s])); + const current=byId.get(siteId); + if(!current) return null; + const ancestors:SiteSummary[]=[]; + let p:SiteSummary|undefined=current; + while(p){ ancestors.unshift(p); p=p.parent_id?byId.get(p.parent_id):undefined; } + 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")); return
@@ -651,7 +677,7 @@ export default function Home(){ )}
-
iStation StationBuilder / {["domains","domainDetail","styleLibrary","deviceTemplates","toolSettings","domainSettings"].includes(page)?meta[page].label:page==="projects"?"站点列表":page==="siteEdit"?`站点管理 / 站点编辑 / ${siteEditMeta.label}`:`站点详情 / ${meta[page].label}`}

{page==="siteEdit"?siteEditMeta.label:meta[page].label}

{page==="siteEdit"?siteEditMeta.subtitle:meta[page].subtitle}

{page==="projects"&&}{page==="siteEdit"&&}{page==="domains"&&openDomainTemplateId==null&&}{page==="domains"&&openDomainTemplateId!=null&&}{domainFilter!=null&&page==="domains"&&openDomainTemplateId==null&&}{page==="domainSettings"&&}
{page==="siteEdit"&&} {SITE_EDIT_STEPS.includes(page as SiteEditSection)&&}
+
iStation StationBuilder / {["domains","domainDetail","styleLibrary","deviceTemplates","toolSettings","domainSettings"].includes(page)?meta[page].label:page==="projects"?"站点列表":page==="siteEdit"?`站点管理 / 站点编辑 / ${siteEditMeta.label}`:`站点详情 / ${meta[page].label}`}

{page==="siteEdit"?siteEditMeta.label:meta[page].label}

{page==="siteEdit"?siteEditMeta.subtitle:meta[page].subtitle}

{page==="projects"&&}{page==="siteEdit"&&}{page==="domains"&&openDomainTemplateId==null&&}{page==="domains"&&openDomainTemplateId!=null&&}{domainFilter!=null&&page==="domains"&&openDomainTemplateId==null&&}{page==="domainSettings"&&}
{page==="siteEdit"&&} {SITE_EDIT_STEPS.includes(page as SiteEditSection)&&}
{page==="projects"&&} {page==="dashboard"&&} {page==="report-dashboard"&&} @@ -667,10 +693,18 @@ export default function Home(){ {page==="deviceTemplates"&&} {page==="toolSettings"&&} {page==="domainSettings"&&} - {(page==="basic"||(page==="siteEdit"&&siteEditSection==="basic"))&&} + {(page==="basic"||(page==="siteEdit"&&siteEditSection==="basic"))&&} {page==="assets"&&} {page==="siteEdit"&&siteEditSection==="assets"&&} - {(page==="pageConfig"||(page==="siteEdit"&&siteEditSection==="pageConfig"))&&
} + {(page==="pageConfig"||(page==="siteEdit"&&siteEditSection==="pageConfig"))&&
+
+ + + + +
+
内容待补充
+
} {page==="functions"&&} {page==="siteEdit"&&siteEditSection==="functions"&&} {page==="generate"&&} @@ -846,7 +880,7 @@ function Projects({view,setView,sites,loading,go,loadSite,notify,onDelete,onCrea } -function Basic({site,domains,onChange,onAddDomain,onUpload,saving,notify,onViewCompletePrompt}:{site:SiteDetail|null;domains:string[];onChange:(p:Partial)=>void;onAddDomain:(n:string)=>void;onUpload:(f:File)=>void;saving:boolean;notify:(m:string)=>void;onViewCompletePrompt:()=>void}){ +function Basic({site,domains,onChange,onAddDomain,onUpload,saving,notify,onViewCompletePrompt,go,siteId,siteHierarchy}:{site:SiteDetail|null;domains:string[];onChange:(p:Partial)=>void;onAddDomain:(n:string)=>void;onUpload:(f:File)=>void;saving:boolean;notify:(m:string)=>void;onViewCompletePrompt:()=>void;go:(p:PageKey,sid?:number|"new")=>void;siteId:number|null|undefined;siteHierarchy:{ancestors:SiteSummary[];children:SiteSummary[];current:SiteSummary|null}|null}){ const [newDomain,setNewDomain]=useState(""); const [domainInput,setDomainInput]=useState(site?.domain||""); const [showAdd,setShowAdd]=useState(false); @@ -884,7 +918,23 @@ function Basic({site,domains,onChange,onAddDomain,onUpload,saving,notify,onViewC if(!site) return
加载中…
; const nameTooLong = countChars(site.name)>20; const domainTooLong = newDomain.length>6; - return <>
+ const renderSiteBranch=(idx:number):JSX.Element|null=>{ + if(!siteHierarchy||idx>=siteHierarchy.ancestors.length) return null; + const a=siteHierarchy.ancestors[idx]; + const isLast=idx===siteHierarchy.ancestors.length-1; + return ( +
+
go("siteEdit",a.id)}>{a.name||"未命名站点"}
+ {isLast && siteHierarchy.children.map(c=>( +
+
go("siteEdit",c.id)}>{c.name||"未命名站点"}
+
+ ))} + {renderSiteBranch(idx+1)} +
+ ); + }; + return <>
onChange({name:e.target.value})} className={nameTooLong?"invalid":""}/>
@@ -898,7 +948,7 @@ function Basic({site,domains,onChange,onAddDomain,onUpload,saving,notify,onViewC
}

情况描述

说明站点用途、重点设备、管理目标及关注指标。

-