@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}
styleLibrary:{label:"页面样式库",icon:"❖",subtitle:"沉淀可复用的 Cube 仪表盘页面样式(配色与视觉主题),供站点页面快速套用"},
};
-const nav: PageKey[] = ["projects","domains","basic","assets","functions","generate","result","preview"];
const MODELS = [
{ name: "Auto", tag: "自动选择", rate: "—" },
// 显式要求新建站点:始终创建空白站点,避免复用/误建
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);
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>
</>
)}
</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}/>}