.data-table input:focus,.data-table select:focus,.data-table textarea:focus{border-color:var(--blue);outline:none}
.data-table .number{width:100%;text-align:right}
.data-table input::placeholder{color:#b9bfc3}
+/* 站点列表:列宽可手动拖动调整(拖拽表头右缘的分隔条) */
+.data-table.resizable-cols{table-layout:fixed;width:100%}
+.data-table.resizable-cols th{position:relative}
+.data-table .col-resizer{position:absolute;top:0;right:0;width:6px;height:100%;cursor:col-resize;z-index:2;user-select:none}
+.data-table .col-resizer::after{content:"";position:absolute;top:0;right:2px;width:2px;height:100%;background:transparent}
+.data-table .col-resizer:hover::after,.data-table .col-resizer.active::after{background:var(--blue)}
/* 可编辑表格使用固定列宽,避免输入框在自动布局下被压窄(设备类型框变窄的 BUG) */
.data-table.editable{table-layout:fixed}
.data-table.editable th:nth-child(1){width:40%}
const batchDelete = async ()=>{ await onDelete([...selected]); setSelected(new Set()); };
const statusText=(status:string)=>status==="generated"?"生成完成":status==="partial"?"部分失败":status==="running"?"生成中":"草稿";
const openProject=(row:SiteSummary)=>go("siteEdit",row.id);
+ const [colWidths,setColWidths]=useState<(number|null)[]>([34,null,110,90,90,90,90,150,90]);
+ const resizing=useRef<{idx:number;startX:number;startW:number}|null>(null);
+ const startResize=(idx:number)=>(e:React.MouseEvent)=>{
+ e.preventDefault(); e.stopPropagation();
+ const th=e.currentTarget.parentElement as HTMLTableCellElement|null;
+ if(!th) return;
+ const startW=th.getBoundingClientRect().width;
+ resizing.current={idx,startX:e.clientX,startW};
+ const onMove=(ev:MouseEvent)=>{
+ if(!resizing.current) return;
+ const dx=ev.clientX-resizing.current.startX;
+ const newW=Math.max(40, Math.round(resizing.current.startW+dx));
+ const i=resizing.current.idx;
+ setColWidths(prev=>{ const n=[...prev]; n[i]=newW; return n; });
+ };
+ const onUp=()=>{
+ resizing.current=null;
+ document.removeEventListener("mousemove",onMove);
+ document.removeEventListener("mouseup",onUp);
+ document.body.style.userSelect="";
+ (document.querySelector(".data-table .col-resizer.active") as HTMLElement|null)?.classList.remove("active");
+ };
+ document.addEventListener("mousemove",onMove);
+ document.addEventListener("mouseup",onUp);
+ document.body.style.userSelect="none";
+ (e.currentTarget as HTMLElement).classList.add("active");
+ };
const [collapsed,setCollapsed]=useState<Set<number>>(new Set());
const toggleCollapse=(id:number)=>setCollapsed(prev=>{ const n=new Set(prev); if(n.has(id)) n.delete(id); else n.add(id); return n; });
const childMap=useMemo(()=>{
<select value={domain} onChange={e=>setDomain(e.target.value)}><option value="">全部领域</option>{domains.map(d=><option key={d} value={d}>{d}</option>)}</select>
<div className="filter-actions"><Button onClick={()=>notify(`已显示 ${filtered.length} 个站点`)}>查询</Button><Button secondary onClick={()=>{setQuery("");setDomain("");setStatusFilter("");}}>重置</Button>{selected.size>0&&<Button secondary onClick={batchDelete}>删除所选 ({selected.size})</Button>}</div>
</div>
- <table className="data-table company-table"><thead><tr><th><input type="checkbox" checked={allSelected} onChange={toggleAll}/></th><th>站点名称</th><th>所属领域</th><th>设备数量</th><th>MU 页面</th><th>Cube 页面</th><th>状态</th><th>最近修改</th><th>操作</th></tr></thead><tbody>{buildRows(0,0)}</tbody></table>
+ <table className="data-table company-table resizable-cols"><colgroup>{colWidths.map((w,i)=><col key={i} style={w!=null?{width:w}:undefined}/>)}</colgroup><thead><tr><th><input type="checkbox" checked={allSelected} onChange={toggleAll}/></th>{["站点名称","所属领域","设备数量","MU 页面","Cube 页面","状态","最近修改"].map((label,i)=><th key={label} className="col-h">{label}<span className="col-resizer" onMouseDown={startResize(i+1)}/></th>)}<th>操作</th></tr></thead><tbody>{buildRows(0,0)}</tbody></table>
{visibleIds.size===0&&<div className="empty-state">没有符合条件的站点</div>}
<div className="pagination"><span>{visibleIds.size} / 总数</span><button disabled>‹</button><button className="active">1</button><button disabled>›</button><select><option>10 / 页</option></select></div>
</Panel>