From f46022d267a9f1219da85411d9b535c9713f57ae Mon Sep 17 00:00:00 2001 From: "wang.yu" Date: Tue, 18 Aug 2026 21:26:59 +0800 Subject: [PATCH] =?utf8?q?=E8=AE=BE=E5=A4=87=E6=A8=A1=E7=89=88=E9=A1=B5?= =?utf8?q?=E4=BA=A4=E4=BA=92=E4=BC=98=E5=8C=96=E4=B8=8E=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit - 设备图片改为长方形预览区(点击上传,object-fit:cover 不再溢出),后端新增 GET /api/db/device-templates/:id/image 图片服务 - 移除设备侧「所属领域」多选(改为由领域/方案侧选用设备) - 采集点改为整宽表格编辑:新增一行、勾选批量删除、保存;数值范围改为下拉选择器(预设 + 自定义) - 修复打开卡片时 collection_points 未加载导致的 runtime error,列表接口补充返回 collection_points - 设计文档升级至 v0.7,新增 5.12 设备模版章节 --- app/globals.css | 12 +++++++ app/page.tsx | 76 ++++++++++++++++++++++++++++++------------ db-sidecar.mjs | 11 ++++++ public/design-doc.html | 15 +++++++-- 4 files changed, 90 insertions(+), 24 deletions(-) diff --git a/app/globals.css b/app/globals.css index e81791d..a751758 100644 --- a/app/globals.css +++ b/app/globals.css @@ -218,4 +218,16 @@ .picker-item input{margin:0;accent-color:var(--blue)} .picker-name{font-weight:bold;font-size:12px} .picker-doms{margin-left:auto;color:#888;font-size:10px;max-width:42%;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +/* ===== 设备模版:图片与采集点编辑 ===== */ +.device-list{display:grid;grid-template-columns:1fr;gap:14px;padding:4px 0} +.device-img-field{margin-top:6px;border:1px dashed #c4ccd2;border-radius:6px;min-height:170px;background:#f7f9fa;display:flex;align-items:center;justify-content:center;cursor:pointer;overflow:hidden;position:relative} +.device-img-field:hover{border-color:var(--blue);background:#f0f8fc} +.device-img-field img{width:100%;height:170px;object-fit:cover;display:block} +.device-img-field .device-img-empty{color:#9aa1a5;font-size:12px} +.cp-list-head{display:flex;align-items:center;justify-content:space-between;margin:10px 0 6px} +.cp-list-head span{display:flex;gap:12px} +.cp-editor td{padding:4px 6px;vertical-align:middle} +.cp-editor input,.cp-editor select{width:100%;border:1px solid var(--line);border-radius:3px;height:30px;padding:0 7px;background:#fff;font-size:11px} +.cp-editor input:focus,.cp-editor select:focus{outline:1px solid var(--blue)} +.cp-range-custom{margin-top:5px} diff --git a/app/page.tsx b/app/page.tsx index 4397c88..6d86733 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1047,43 +1047,75 @@ function CreateTemplate({doms,notify,onCreated}:{doms:any[];notify:(m:string)=>v ; } -function DeviceTemplateCard({dev,doms,notify,onChange}:{dev:any;doms:any[];notify:(m:string)=>void;onChange:()=>void}){ +const CP_RANGE_PRESETS = ["0~100","0~1000","-20~80","-40~85","0~50","0~5","0~1","0~100%"]; + +function DeviceImageField({imageUrl,onPick}:{imageUrl:string|null;onPick:(f:File)=>void}){ + const ref=useRef(null); + return
ref.current?.click()}> + {imageUrl? 设备图片 : + 点击上传设备图片} + { const f=e.target.files?.[0]; if(f) onPick(f); e.target.value=""; }}/> +
; +} + +function DeviceTemplateCard({dev,notify,onChange}:{dev:any;notify:(m:string)=>void;onChange:()=>void}){ const [name,setName]=useState(dev.name); const [description,setDesc]=useState(dev.description||""); - const [domainIds,setDomainIds]=useState(dev.domain_ids||[]); - const [cps,setCps]=useState(dev.collection_points||[]); - const imgChanged=async(e:any)=>{ const f=e.target.files?.[0]; if(!f) return; const b=await fileToBase64(f); await fetchDb(`/device-templates/${dev.id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({name,description,domain_ids:domainIds,image:{filename:b.filename,mime:b.mime,data:b.data}})}); notify("已更新图片"); onChange(); }; - const save=async()=>{ await fetchDb(`/device-templates/${dev.id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({name,description,domain_ids:domainIds})}); notify("设备已保存"); onChange(); }; + const [imgPreview,setImgPreview]=useState(dev.image_path?`/api/db/device-templates/${dev.id}/image`:null); + const [imgB64,setImgB64]=useState<{filename:string;mime:string;data:string}|null>(null); + const [cps,setCps]=useState((dev.collection_points||[]).map((c:any)=>({...c,key:c.id,checked:false}))); + const onPick=async(f:File)=>{ setImgPreview(URL.createObjectURL(f)); const b=await fileToBase64(f); setImgB64({filename:b.filename,mime:b.mime,data:b.data}); }; + const save=async()=>{ const body:any={name,description}; if(imgB64) body.image=imgB64; await fetchDb(`/device-templates/${dev.id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)}); notify("设备已保存"); onChange(); }; const delDev=async()=>{ if(!confirm("删除该设备模板?")) return; await fetchDb(`/device-templates/${dev.id}`,{method:"DELETE"}); onChange(); }; - const addCp=async()=>{ const nm=prompt("采集点名称"); if(!nm) return; await fetchDb(`/device-templates/${dev.id}/collection-points`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:nm})}); onChange(); }; - const delCp=async(id:number)=>{ await fetchDb(`/collection-points/${id}`,{method:"DELETE"}); onChange(); }; + const addCpRow=()=>setCps([...cps,{key:-(Date.now()),name:"",collect_note:"",unit:"",value_range:"",description:"",checked:false}]); + const updCp=(k:number,field:string,val:any)=>setCps(cps.map(c=>c.key===k?{...c,[field]:val}:c)); + const toggleCp=(k:number)=>setCps(cps.map(c=>c.key===k?{...c,checked:!c.checked}:c)); + const allChecked=cps.length>0&&cps.every(c=>c.checked); + const toggleAll=()=>setCps(cps.map(c=>({...c,checked:!allChecked}))); + const delCpRows=async()=>{ const sel=cps.filter(c=>c.checked); if(sel.length===0){ notify("请先勾选要删除的采集点"); return; } if(!confirm(`确定删除选中的 ${sel.length} 个采集点?`)) return; for(const c of sel){ if(c.id) await fetchDb(`/collection-points/${c.id}`,{method:"DELETE"}); } setCps(cps.filter(c=>!c.checked)); onChange(); }; + const saveCpRows=async()=>{ try{ for(const c of cps){ const payload={name:c.name,collect_note:c.collect_note,unit:c.unit,value_range:c.value_range,description:c.description}; if(c.id){ await fetchDb(`/collection-points/${c.id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)}); } else { await fetchDb(`/device-templates/${dev.id}/collection-points`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)}); } } notify("采集点已保存"); onChange(); }catch(e:any){ notify("保存失败:"+e.message); } }; return
{name||"(未命名)"}
- +