refactor: 前端JS拆分为8个模块(base/list/monitor/editor/tasks/tools/apps/admin,按依赖顺序加载)+ 删除废弃文件(start_web.bat/main.py/fix_web.bat/cleanup.py,web已全覆盖)+ 文档同步 + 修复/api/summary缺导入

This commit is contained in:
2026-08-11 14:53:04 +08:00
parent 2e491b19e2
commit 060157ea1a
19 changed files with 2584 additions and 2767 deletions
+94
View File
@@ -0,0 +1,94 @@
// 统一列表组件:setListPager 搜索 + 分页 + 排序(_lpSort)
// ================== 通用列表:搜索 + 分页 + 排序 ==================
// 用法:
// setListPager(key,{data:()=>array, filter:(item,kw)=>bool, render:(item)=>html,
// tbody:'tb-id', pager:'pager-id', pageSize:N,
// sortValue:(item,colKey)=>值, // 可排序列的取值函数
// empty:'<tr>...'});
// applyListPager(key); // 数据变化后重渲染(含搜索/翻页/排序状态)
// initListSearch(key,'search-id'); // 搜索框绑定
// _lpSort(key,'colKey'); // 表头排序:th 加 data-sort="colKey" + onclick,
// // 再点切换升降序,第三次取消;排序指示器自动更新
var _LIST_PAGERS={};
function _lp(key){ return _LIST_PAGERS[key] || (_LIST_PAGERS[key]={page:1,kw:'',cfg:null,sort:null}); }
function setListPager(key,cfg){
// 只更新配置,不重置 page/kw/sort —— 监控列表 5 秒自动刷新时保持用户当前状态
var st=_lp(key); st.cfg=cfg;
}
function initListSearch(key,inputId){
var input=document.getElementById(inputId);
if(!input||input.dataset.bound)return;
input.dataset.bound='1';
input.addEventListener('input',function(){
var st=_lp(key); st.kw=this.value.trim(); st.page=1; applyListPager(key);
});
}
function _lpSort(key,colKey){
var st=_lp(key);
if(st.sort&&st.sort.key===colKey){
if(st.sort.dir===1)st.sort.dir=-1; // 升序 → 降序
else if(st.sort.dir===-1)st.sort=null; // 降序 → 取消排序
else st.sort={key:colKey,dir:1};
}else{
st.sort={key:colKey,dir:1};
}
applyListPager(key);
}
function applyListPager(key){
var st=_lp(key);
if(!st.cfg)return;
var cfg=st.cfg;
var all=(cfg.data&&cfg.data())||[];
var kw=st.kw.toLowerCase();
var filtered=kw?all.filter(function(x){return cfg.filter(x,kw);}):all;
// 排序:cfg.sortValue 取列值;空值/缺失恒排最后;字符串用中文数字感知比较(10 排在 9 后)
if(st.sort&&cfg.sortValue){
var sk=st.sort.key, dir=st.sort.dir;
filtered=filtered.slice().sort(function(a,b){
var va=cfg.sortValue(a,sk), vb=cfg.sortValue(b,sk);
if(va===vb)return 0;
if(va==null||va==='')return 1;
if(vb==null||vb==='')return -1;
if(typeof va==='number'&&typeof vb==='number')return (va-vb)*dir;
return String(va).localeCompare(String(vb),'zh-Hans-CN',{numeric:true})*dir;
});
}
var pageSize=cfg.pageSize||10;
var pages=Math.max(1,Math.ceil(filtered.length/pageSize));
if(st.page>pages)st.page=pages;
if(st.page<1)st.page=1;
var start=(st.page-1)*pageSize;
var slice=filtered.slice(start,start+pageSize);
var tb=document.getElementById(cfg.tbody);
if(tb){
tb.innerHTML=slice.map(cfg.render).join('')||(cfg.empty||'<tr><td colspan="20" class="empty">无数据</td></tr>');
// 表头排序指示器(th[data-sort],所有表格统一)
if(cfg.sortValue){
var table=tb.closest('table');
if(table)table.querySelectorAll('thead th[data-sort]').forEach(function(th){
th.classList.add('sortable');
th.classList.toggle('sort-asc',!!st.sort&&st.sort.key===th.dataset.sort&&st.sort.dir===1);
th.classList.toggle('sort-desc',!!st.sort&&st.sort.key===th.dataset.sort&&st.sort.dir===-1);
});
}
}
var pg=document.getElementById(cfg.pager);
if(pg)pg.innerHTML=_lpPagerHtml(key,pages,st.page,filtered.length,pageSize);
}
function _lpPagerHtml(key,pages,cur,total,pageSize){
if(pages<=1)return total>0?'<div class="list-pager"><span class="lp-total">共 '+total+' 条</span></div>':'';
var h='<div class="list-pager"><span class="lp-total">共 '+total+' 条</span>';
h+='<button class="btn btn-xs" onclick="_lpGo(\''+key+'\','+(cur-1)+')"'+(cur<=1?' disabled':'')+'>‹ 上一页</button>';
for(var p=Math.max(1,cur-2);p<=Math.min(pages,cur+2);p++){
h+='<button class="btn btn-xs'+(p===cur?' btn-primary':'')+'" onclick="_lpGo(\''+key+'\','+p+')">'+p+'</button>';
}
h+='<button class="btn btn-xs" onclick="_lpGo(\''+key+'\','+(cur+1)+')"'+(cur>=pages?' disabled':'')+'>下一页 ›</button>';
h+='</div>';
return h;
}
function _lpGo(key,p){
var st=_lp(key); st.page=p; applyListPager(key);
}