From: root Date: Thu, 20 Aug 2026 08:54:55 +0000 (+0800) Subject: 领域分类管理:删除工业制造/工业园区/智慧楼宇,新增其他,支持删除领域并迁移方案 X-Git-Url: http://acesimba.cloud/gitweb/?a=commitdiff_plain;h=6555962787a6ce903096b9c788d92b383d17748b;p=StationBuilder.git 领域分类管理:删除工业制造/工业园区/智慧楼宇,新增其他,支持删除领域并迁移方案 - DEFAULT_DOMAINS 移除 工业制造/工业园区/智慧楼宇,新增 其他(重启不再复活) - 后端新增 DELETE /api/domains/:id:禁止删除其他,删除前将 domain_templates 与 device_template_domains 迁移至其他,再删除领域 - 前端领域方案库新增「管理领域」面板,其他标注默认保留不可删,其余提供删除并自动刷新列表/清除筛选 - 更新设计文档 v0.20 --- diff --git a/app/globals.css b/app/globals.css index 94c43d6..b83cf99 100644 --- a/app/globals.css +++ b/app/globals.css @@ -284,4 +284,11 @@ .dlg-box input:focus{outline:1px solid var(--blue)} .dlg-actions{display:flex;justify-content:flex-end;gap:10px} .dlg-actions .button{margin-left:0} +/* 领域方案库 · 管理领域 */ +.domain-manage-list{display:flex;flex-direction:column;gap:8px} +.domain-manage-row{display:flex;align-items:center;gap:12px;padding:9px 12px;border:1px solid var(--line);border-radius:8px;background:#fff} +.domain-manage-name{font-weight:600;color:#333} +.domain-manage-row .muted{font-size:11px} +.text-button.danger{color:#d92d20} +.text-button.danger:hover{text-decoration:underline} diff --git a/app/page.tsx b/app/page.tsx index 5591796..87ed177 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -334,6 +334,19 @@ export default function Home(){ }).catch(e=>notify("加载失败: "+e.message)).finally(()=>setLoading(false)); },[]); + const reloadDomains = async () => { + try { + const d = await fetchDb("/domains") as {id:number;name:string}[]; + const arr = d || []; + setDomainOptions(arr.map(x=>({id:x.id,name:x.name}))); + setDomains(arr.map(x=>x.name)); + } catch(e:any){ notify("加载领域失败: "+e.message); } + }; + const handleDomainDeleted = async (id:number) => { + await reloadDomains(); + if (domainFilter === id) setDomainFilter(null); + }; + const loadSite=async(id:number)=>{ const data:SiteDetail = await fetchDb(`/sites/${id}`); setSite(data); @@ -488,7 +501,7 @@ export default function Home(){ {page==="projects"&&} {(page==="domains"||page==="domainDetail")&&(openDomainTemplateId!=null ? setOpenDomainTemplateId(null)} onCreated={setOpenDomainTemplateId} viewCompletePrompt={viewDtCompletePrompt} viewCompleteAssetsPrompt={viewDtCompleteAssetsPrompt} viewCompleteCubesPrompt={viewDtCompleteCubesPrompt}/> - : setOpenDomainTemplateId(id)} domainFilter={domainFilter} domainOptions={domainOptions}/>)} + : setOpenDomainTemplateId(id)} domainFilter={domainFilter} domainOptions={domainOptions} onDomainDeleted={handleDomainDeleted}/>)} {page==="styleLibrary"&&} {page==="deviceTemplates"&&} {page==="basic"&&} @@ -1044,7 +1057,7 @@ function usePrompt(){ return { prompt, el }; } -function DomainLibrary({go,notify,onOpen,domainFilter,domainOptions}:{go:(p:PageKey)=>void;notify:(m:string)=>void;onOpen:(id:number)=>void;domainFilter:number|null;domainOptions:{id:number;name:string}[]}){ +function DomainLibrary({go,notify,onOpen,domainFilter,domainOptions,onDomainDeleted}:{go:(p:PageKey)=>void;notify:(m:string)=>void;onOpen:(id:number)=>void;domainFilter:number|null;domainOptions:{id:number;name:string}[];onDomainDeleted:(id:number)=>void}){ const [list,setList]=useState([]); const [loading,setLoading]=useState(true); const [view,setView]=useState<"list"|"card">("list"); @@ -1056,6 +1069,7 @@ function DomainLibrary({go,notify,onOpen,domainFilter,domainOptions}:{go:(p:Page useEffect(()=>{ load(); },[]); const {confirm, el:confirmEl}=useConfirm(); const del=async(id:number)=>{ if(!(await confirm("确定删除该领域方案?此操作不可恢复"))) return; try{ await fetchDb(`/domain-templates/${id}`,{method:"DELETE"}); notify("已删除"); load(); }catch(e:any){ notify("删除失败:"+e.message); } }; + const manageDel=async(id:number,name:string)=>{ if(name==="其他") return; if(!(await confirm(`确定删除领域「${name}」?其下的方案将移入「其他」领域。`))) return; try{ await fetchDb(`/domains/${id}`,{method:"DELETE"}); notify("已删除领域,相关方案已归入「其他」"); await onDomainDeleted(id); }catch(e:any){ notify("删除失败:"+e.message); } }; const statusLabel=(s:string)=>s==="available"?"可用":"不可用"; const toggleSort=(key:"domain"|"name"|"status"|"creator")=>setSort(s=>s.key===key?{key,dir:(s.dir*-1) as 1|-1}:{key,dir:1}); const sortInd=(key:"domain"|"name"|"status"|"creator")=>sort.key===key?({sort.dir===1?"▲":"▼"}):null; @@ -1078,6 +1092,21 @@ function DomainLibrary({go,notify,onOpen,domainFilter,domainOptions}:{go:(p:Page
DOMAIN SOLUTION LIBRARY

领域方案库

沉淀各领域标准方案,供客户站点直接使用或快速微调。

{domainOptions.map(d=>
{list.filter(t=>t.domain_id===d.id).length}{d.name}
)}
+ 删除领域后,其方案将归入「其他」}> +
+ {domainOptions.map(d=>( +
+ {d.name} + {list.filter((t:any)=>t.domain_id===d.id).length} 个方案 + + {d.name==="其他" + ? (默认保留) + : } + +
+ ))} +
+
diff --git a/db-sidecar.mjs b/db-sidecar.mjs index bd0975f..93478fb 100644 --- a/db-sidecar.mjs +++ b/db-sidecar.mjs @@ -31,8 +31,8 @@ const SEED_PAGE_STYLES = [ ]; const DEFAULT_DOMAINS = [ - "工业园区", "数据中心", "智慧楼宇", "智慧水务", "工业制造", - "燃气", "水务 · 自来水厂", "新能源", + "数据中心", "智慧水务", "燃气", "水务 · 自来水厂", + "新能源", "其他", ]; const MU_TREE = [ @@ -2151,6 +2151,7 @@ async function route(req, res) { const path = parsed.pathname; const mSites = path.match(/^\/api\/sites(?:\/(\d+))?$/); const mDomains = path === "/api/domains"; + const mDomainOne = path.match(/^\/api\/domains\/(\d+)$/); const mHealth = path === "/api/health"; const mMuTree = path === "/api/mu-tree"; const mSiteAssets = path.match(/^\/api\/sites\/(\d+)\/assets$/); @@ -2205,6 +2206,20 @@ async function route(req, res) { try { if (mHealth && req.method === "GET") return respondJson(res, 200, { ok: true }); if (mMuTree && req.method === "GET") return respondJson(res, 200, MU_TREE); + if (mDomainOne && req.method === "DELETE") { + const id = Number(mDomainOne[1]); + const dom = db.prepare("SELECT * FROM domains WHERE id=?").get(id); + if (!dom) return respondError(res, 404, "领域不存在"); + if (dom.name === "其他") return respondError(res, 400, "不能删除「其他」领域"); + // 确保「其他」领域存在,作为被删除领域方案的归宿 + let other = db.prepare("SELECT * FROM domains WHERE name='其他'").get(); + if (!other) { const info = db.prepare("INSERT INTO domains (name) VALUES ('其他')").run(); other = { id: Number(info.lastInsertRowid) }; } + // 先将关联的方案 / 设备-领域关联迁移到「其他」,再删除领域(避免 ON DELETE CASCADE 误删方案) + db.prepare("UPDATE domain_templates SET domain_id=? WHERE domain_id=?").run(other.id, id); + db.prepare("UPDATE device_template_domains SET domain_id=? WHERE domain_id=?").run(other.id, id); + db.prepare("DELETE FROM domains WHERE id=?").run(id); + return respondJson(res, 200, { ok: true, movedTo: "其他" }); + } if (mDomains) { if (req.method === "GET") return respondJson(res, 200, db.prepare("SELECT * FROM domains ORDER BY name").all()); if (req.method === "POST") { diff --git a/public/design-doc.html b/public/design-doc.html index 5c4ac31..4e4bf06 100644 --- a/public/design-doc.html +++ b/public/design-doc.html @@ -115,7 +115,7 @@
-

文档版本:v0.19 最后更新:2026-08-20 修改人:wang.yu 状态:开发中(演示数据已标注)

+

文档版本:v0.20 最后更新:2026-08-20 修改人:wang.yu 状态:开发中(演示数据已标注)

StationBuilder 产品与交互设计方案

1. 文档说明

@@ -505,6 +505,7 @@ 元素功能数据来源交互效果 筛选栏(查找 / 所属领域 / 状态 / 条数)列表实时筛选与计数本地 state(q / domainSel / statusSel),作用于 GET /api/db/domain-templates 结果查找框按 方案名称/创建人 模糊匹配;所属领域、状态下拉精确过滤;右上角显示「共 N 条」 领域方案列表表格展示全部领域方案(所属领域 / 方案名称 / 版本 / 状态 / 创建人 / 设备·页面·文档数 / 操作)GET /api/db/domain-templates(含 domain_name、device_count、page_count、doc_count)每行「编辑」→ 打开该方案详情编辑器;「删除」→ 确认后 DELETE /api/db/domain-templates/:id(级联删除 device_template_domain_templates)并刷新;空列表提示「点击右上角新增」 + 管理领域面板领域分类管理:列出全部领域及方案数,支持删除领域GET /api/domains(领域列表);删除调用 DELETE /api/domains/:id「其他」标注「(默认保留)」且无删除按钮;其余领域显示「删除」按钮 → 自定义确认弹窗二次确认 → 调用 DELETE /api/domains/:id(后端先将其下 domain_templates / device_template_domains 迁移至「其他」,再删除领域)→ 前端刷新领域列表、若当前筛选命中该领域则清除筛选 「+ 新增领域方案」新建领域方案,进入新增表单—同页切到 CreateTemplate 表单:所属领域(select 来自 GET /api/domains)、模板名称、版本、状态(可用/不可用)、创建人、领域描述;POST /api/db/domain-templates 成功后自动打开该方案详情编辑器 搜索 / 筛选 / 同步方案库(历史功能)—已按计划 Phase 0 移除整行(含「同步方案库」占位按钮) 已移除 @@ -751,6 +752,7 @@

11.1 版本变更记录(Changelog)

+
版本日期修改人说明
v0.202026-08-20wang.yu领域分类管理增强:① 默认分类调整——移除「工业制造 / 工业园区 / 智慧楼宇」,新增「其他」(DEFAULT_DOMAINS 同步更新,重启不再复活被删分类);② 领域方案库新增「管理领域」面板,列出全部领域及其方案数,「其他」标注「默认保留」且不可删除,其余领域提供「删除」按钮,经自定义确认弹窗二次确认后调用 DELETE /api/domains/:id;③ 后端新增 DELETE /api/domains/:id:禁止删除「其他」,删除前将待删领域的 domain_templates 与 device_template_domains 关联迁移至「其他」(避免 ON DELETE CASCADE 误删方案),再删除领域;④ 删除后前端刷新领域列表,并在当前筛选命中该领域时自动清除筛选。
v0.192026-08-20wang.yu修复领域方案「功能与菜单 → Cube 扩展页面」两处问题:①「生成」按钮改为流式请求(消费 NDJSON 响应体以保持连接),生成期间按钮显示「生成中…」并禁用、底部「AI 生成页面」控制台实时输出进度并据服务端 {status:failed} 事件弹失败提示;此前前端用普通 fetch 不读取响应体,连接被提前关闭导致服务端 AI 生成中断(status='failed')、Json 未写入。② AI 智能补全(complete-cubes)由「逐条静默 .catch(()=>{}) 插入」改为「收集结果后逐个 await 插入、失败显式 notify」,并捕获服务端 {status:failed} 事件弹失败提示,避免补全内容静默丢失、列表无变化。
v0.172026-08-19wang.yu修复左侧目录(TOC)点击章节后消失:根因为顶部版本说明段落含全角空格不换行,在 flex 布局下把 main#doc 撑到约 2238px 宽(flex item 默认 min-width:auto 不收缩),点击锚点时浏览器水平滚动将 sticky 目录推出左边界。给 main#doc 增加 min-width:0 使其可收缩、内容正常折行,消除水平滚动,目录任意宽度常驻可见。
v0.182026-08-20wang.yu领域方案「功能与菜单 → Cube 扩展页面」表格增强:① 新增「来源」列,以徽标区分 上传 / AI生成 / 手动;② 原「生成文件」列更名为「Json文件」,对上传页面提供原始 .json 文件名下载、对 AI 生成页面提供生成的 Cube .json 下载(未生成显示"未生成");③ domain_pages 新增 source 字段,上传/生成时分别写入 source=upload/ai 与 gen_filename;④ 生成确认页状态徽标区分「AI已生成 / 已上传 / 未生成 · 来源」。同步更新 5.10 章节。