]> acesimba.cloud Git - StationBuilder.git/commitdiff
refactor: 左侧导航重构为站点列表/站点详情,移除重复图标栏并修复误建未命名站点
authorwang.yu <wang.yu@advantech.com>
Mon, 17 Aug 2026 07:43:25 +0000 (15:43 +0800)
committerwang.yu <wang.yu@advantech.com>
Mon, 17 Aug 2026 07:43:25 +0000 (15:43 +0800)
- 删除最左侧 icon-rail 图标栏及 const nav,消除与主菜单重复
- go() 详情类页面无 siteId 时不再自动建站,跳回站点列表并提示
- 侧栏分为「站点列表」与「站点详情」,详情含站点切换器 <select>
- 面包屑区分 站点列表 / 站点详情 / 子视图名
- CSS 调整 side-nav 左偏移与 app-shell 内边距,新增 site-switcher 样式

app/globals.css
app/page.tsx

index e90c8582c8e72a6fa5c76d581dc055546a8fc53f..2c307adc9471c909e1642a14b0f3410908d3b094 100644 (file)
@@ -1,5 +1,5 @@
 @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:Arial,"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:42px;padding-left:232px}.topbar{position:fixed;z-index:50;left:0;right:0;top:0;height:42px;background:#fff;border-top:4px solid var(--blue2);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}.icon-rail{position:fixed;z-index:40;top:42px;bottom:0;left:0;width:42px;background:#f7f7f7;border-right:1px solid #d4d7da;display:flex;flex-direction:column;align-items:center}.icon-rail button{width:100%;height:45px;border:0;border-left:3px solid transparent;background:transparent;color:#777;font-size:18px}.icon-rail button:hover,.icon-rail button.active{color:var(--blue);background:#eaf7fd;border-left-color:var(--blue)}.icon-rail .rail-bottom{margin-top:auto;border-top:1px solid var(--line)}.side-nav{position:fixed;z-index:35;top:42px;bottom:0;left:42px;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}.side-nav>button:hover,.side-nav>button.active{background:#dff1fa;color:#007fbe;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}
+: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:Arial,"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:42px;padding-left:190px}.topbar{position:fixed;z-index:50;left:0;right:0;top:0;height:42px;background:#fff;border-top:4px solid var(--blue2);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:42px;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}.side-nav>button:hover,.side-nav>button.active{background:#dff1fa;color:#007fbe;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}
index cb4092a29a754c32c39372287a446198d14f42f9..a70abbebea3170f6741ab747dd0fb97c68f296d7 100644 (file)
@@ -19,7 +19,6 @@ const meta: Record<PageKey, { label: string; icon: string; subtitle: string }> =
   styleLibrary:{label:"页面样式库",icon:"❖",subtitle:"沉淀可复用的 Cube 仪表盘页面样式(配色与视觉主题),供站点页面快速套用"},
 };
 
-const nav: PageKey[] = ["projects","domains","basic","assets","functions","generate","result","preview"];
 
 const MODELS = [
   { name: "Auto", tag: "自动选择", rate: "—" },
@@ -273,9 +272,15 @@ export default function Home(){
       // 显式要求新建站点:始终创建空白站点,避免复用/误建
       if(sid==="new"){ await createSite(); setPage(target); window.history.replaceState({}, "", "?page="+target); return; }
       const curId = sid ?? siteId;
-      if(["basic","assets","functions","generate","result"].includes(target) && !curId){
-        await createSite();
-      } else if(curId && (!site || site.id!==curId)){
+      // 详情类页面必须依附于已选站点:无站点时不自动建站,回站点列表并提示
+      const needsSite = ["basic","assets","functions","generate","result","preview"].includes(target);
+      if(needsSite && !curId){
+        setPage("projects");
+        window.history.replaceState({}, "", "?page=projects");
+        notify("请先选择或新建站点");
+        return;
+      }
+      if(curId && (!site || site.id!==curId)){
         await loadSite(curId);
       }
       setPage(target);
@@ -366,8 +371,7 @@ export default function Home(){
 
   return <div className="app-shell">
     <header className="topbar"><button className="hamburger">☰</button><div className="brand-mark">⬡</div><strong>iStation</strong><span className="divider"/><span>iStation Team</span><span className="divider"/><span>StationBuilder⌄</span><div className="top-actions"><button title="查看内部登录页" className="avatar avatar-button" onClick={()=>go("login")}>W</button><small>wang.yu@advantech.com<br/>iStation Team</small></div></header>
-    <aside className="icon-rail">{nav.map(item=><button title={meta[item].label} key={item} onClick={()=>go(item)} className={page===item?"active":""}>{meta[item].icon}</button>)}<button className="rail-bottom">»</button></aside>
-    <aside className="side-nav"><div className="module-switch"><button className={!(["domains","domainDetail"] as PageKey[]).includes(page)?"active":""} onClick={()=>go("projects")}><b>▦</b><span>StationBuilder<small>站点生成工具</small></span></button><button className={(["domains","domainDetail"] as PageKey[]).includes(page)?"active":""} onClick={()=>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"] as PageKey[]).includes(page)?<><small>方案库导航</small><button className={page==="domains"?"active":""} onClick={()=>go("domains")}><b>▦</b>全部领域方案</button><button className={page==="domainDetail"?"active":""} onClick={()=>go("domainDetail")}><b>◇</b>方案详情</button><small>领域分类</small>{["燃气","水务 · 自来水厂","新能源","数据中心"].map((x,i)=><button key={x} className={i===1&&page==="domainDetail"?"active":""} onClick={()=>i===1?go("domainDetail"):notify(x+"方案预览已切换")}><b>{["♨","≋","☀","▥"][i]}</b>{x}</button>)}</>:<><small>站点管理</small><button className={page==="projects"?"active":""} onClick={()=>go("projects")}><b>▦</b>站点项目</button><small>生成向导</small>{(["basic","assets","functions","generate"] as PageKey[]).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">
+    <aside className="side-nav"><div className="module-switch"><button className={!(["domains","domainDetail"] as PageKey[]).includes(page)?"active":""} onClick={()=>go("projects")}><b>▦</b><span>StationBuilder<small>站点生成工具</small></span></button><button className={(["domains","domainDetail"] as PageKey[]).includes(page)?"active":""} onClick={()=>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"] as PageKey[]).includes(page)?<><small>方案库导航</small><button className={page==="domains"?"active":""} onClick={()=>go("domains")}><b>▦</b>全部领域方案</button><button className={page==="domainDetail"?"active":""} onClick={()=>go("domainDetail")}><b>◇</b>方案详情</button><small>领域分类</small>{["燃气","水务 · 自来水厂","新能源","数据中心"].map((x,i)=><button key={x} className={i===1&&page==="domainDetail"?"active":""} onClick={()=>i===1?go("domainDetail"):notify(x+"方案预览已切换")}><b>{["♨","≋","☀","▥"][i]}</b>{x}</button>)}</>:<><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"].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>{(["basic","assets","functions","generate"] as PageKey[]).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">
     <b>内部工具 · v0.3</b>
     <span className="model-trigger" onClick={() => setModelOpen(o => !o)}>
       Wordbuddy AI · {model}<span className="caret">▾</span>
@@ -389,7 +393,7 @@ export default function Home(){
       </>
     )}
   </div></aside>
-    <main className="workspace"><div className="crumb">iStation StationBuilder / <b>{meta[page].label}</b></div><div className="page-heading"><div><h1>{meta[page].label}</h1><p>{meta[page].subtitle}</p></div><div>{!["projects","domains","domainDetail","styleLibrary"].includes(page)&&<Button secondary onClick={()=>notify("草稿已保存")}>保存草稿</Button>}{page==="projects"&&<Button onClick={()=>go("basic","new")}>+ 新建站点</Button>}{page==="domains"&&<Button onClick={()=>notify("已打开新增领域方案向导")}>+ 新增领域方案</Button>}{page==="domainDetail"&&<Button onClick={()=>{notify("已应用水务方案模板");go("basic")}}>使用此方案创建站点</Button>}</div></div>{(["basic","assets","functions","generate"] as PageKey[]).includes(page)&&<Steps page={page} go={go}/>}<div className="content">
+    <main className="workspace"><div className="crumb">iStation StationBuilder / <b>{["domains","domainDetail","styleLibrary"].includes(page)?meta[page].label:page==="projects"?"站点列表":`站点详情 / ${meta[page].label}`}</b></div><div className="page-heading"><div><h1>{meta[page].label}</h1><p>{meta[page].subtitle}</p></div><div>{!["projects","domains","domainDetail","styleLibrary"].includes(page)&&<Button secondary onClick={()=>notify("草稿已保存")}>保存草稿</Button>}{page==="projects"&&<Button onClick={()=>go("basic","new")}>+ 新建站点</Button>}{page==="domains"&&<Button onClick={()=>notify("已打开新增领域方案向导")}>+ 新增领域方案</Button>}{page==="domainDetail"&&<Button onClick={()=>{notify("已应用水务方案模板");go("basic","new")}}>使用此方案创建站点</Button>}</div></div>{(["basic","assets","functions","generate"] as PageKey[]).includes(page)&&<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}/>}
       {page==="domains"&&<DomainLibrary go={go} notify={notify}/>}
       {page==="domainDetail"&&<DomainDetail go={go} notify={notify}/>}