Files
OpenClaw Agent 2329955e32 baseline: 薪酬逻辑收口到 shared/payroll.js 后的当前全量状态
这是第一个包含完整工作区的提交,也是后续「拆 SFC / 上 Vite」的回滚基线。

此前的提交均为 index.html 单文件历史快照,由 .bak 文件重建。

当前状态(提交前已实测):
- index.html 内联薪酬算法已删除,改调 WHTPayroll
- shared/payroll.js 为唯一薪酬实现,浏览器与 Node 共用
- 对外接口未变:aggregateMonth / isCutEdge / payDayKind 等保留同名薄包装

未入库内容见 .gitignore:依赖、Chrome 验证 profile、验证截图、
运行时 sqlite 数据与日志、时间戳备份、.trash。
2026-09-05 14:35:00 +08:00

115 lines
6.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 验收:两套口径分区(挣得 / 进账)+ 归属期徽章 + 结转标注
const { spawn } = require('child_process');
const fs = require('fs');
const CHROME = '/usr/bin/chromium';
const OUT_DIR = __dirname;
const URL = 'http://127.0.0.1:8388/index.html';
const chrome = spawn(CHROME, [
'--headless=new','--no-sandbox','--disable-gpu','--disable-dev-shm-usage',
'--remote-debugging-port=9232', `--user-data-dir=${OUT_DIR}/.chrome-caliber`,
'--window-size=420,1200','about:blank'
], { stdio: ['ignore','ignore','pipe'] });
const stderr = []; chrome.stderr.on('data', d => stderr.push(d.toString()));
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function getWsUrl() {
for (let i=0;i<40;i++){ try{ const t=await(await fetch('http://127.0.0.1:9232/json/list')).json(); if(t.length)return t[0].webSocketDebuggerUrl; }catch(e){} await sleep(250);} throw new Error('CDP fail: '+stderr.join('').slice(-400));
}
(async () => {
const ws = new WebSocket(await getWsUrl());
let id=0; const pending=new Map(); const logs=[];
let openRes; const openP=new Promise(r=>openRes=r); ws.onopen=openRes;
ws.onmessage=e=>{ const m=JSON.parse(e.data);
if(m.id&&pending.has(m.id)){pending.get(m.id)(m);pending.delete(m.id);}
else if(m.method==='Runtime.consoleAPICalled'){const t=(m.params.args||[]).map(a=>a.value??a.description??'').join(' '); if(!/tailwindcss/.test(t))logs.push('C '+t);}
else if(m.method==='Runtime.exceptionThrown')logs.push('EXC '+JSON.stringify(m.params.exceptionDetails).slice(0,300)); };
await openP;
const send=(me,p={})=>new Promise(r=>{const i2=++id;pending.set(i2,r);ws.send(JSON.stringify({id:i2,method:me,params:p}));});
const ev=async(x)=>{const r=await send('Runtime.evaluate',{expression:x,awaitPromise:true,returnByValue:true});
if(r.result&&r.result.exceptionDetails)return{__err:JSON.stringify(r.result.exceptionDetails).slice(0,300)};
return r.result&&r.result.result&&r.result.result.value;};
const shot=async(n)=>{const s=await send('Page.captureScreenshot',{format:'png',captureBeyondViewport:true});fs.writeFileSync(OUT_DIR+'/'+n,Buffer.from(s.result.data,'base64'));};
await send('Runtime.enable'); await send('Page.enable');
await send('Page.navigate',{url:URL}); await sleep(1200);
// 8月满月打卡(含奖惩)+ 9月上半月打卡,制造出「挣得 ≠ 进账」的典型场景
await ev(`(function(){
const rec = {};
for (let d=1; d<=28; d++) rec['2026-08-'+String(d).padStart(2,'0')] = { status:'work', shift_type: d%3?'day':'night', hours: d%3?8:10, contraband_found: d===5?3:0, contraband_missed: d===9?2:0, other_penalty: d===12?50:null, penalty_reason: d===12?'迟到':'' };
for (let d=1; d<=20; d++) rec['2026-09-'+String(d).padStart(2,'0')] = { status:'work', shift_type:'day', hours:8, contraband_found: d===3?1:0, contraband_missed:0, other_penalty:null, penalty_reason:'' };
localStorage.setItem('work_records', JSON.stringify(rec));
localStorage.setItem('work_month_adjust', JSON.stringify({'2026-08':{status:'final',finalAmount:80,note:'与班长核对',finalizedAt:'2026-09-01T02:00:00.000Z'}}));
return 1;
})()`);
await send('Page.navigate',{url:URL}); await sleep(1800);
// ① 顶部看板:主数字换成「挣得」,副标题带结转说明
const board = await ev(`(function(){
const h = document.querySelector('header').innerText.replace(/\\s+/g,' ');
return { text: h.slice(0, 210) };
})()`);
console.log('① 看板:', JSON.stringify(board));
await shot('c1_board.png');
// ② 汇总页两分区
const sum = await ev(`new Promise(r=>{
Array.from(document.querySelectorAll('nav button')).find(b=>(b.innerText||'').trim()==='汇总').click();
setTimeout(()=>{
const sec = document.querySelector("section[class*='px-4'] , section");
const secs = Array.from(document.querySelectorAll('section'));
const s = secs.find(x=>x.innerText.includes('挣得'));
const t = s ? s.innerText.replace(/\\s+/g,' ') : null;
const badges = Array.from(document.querySelectorAll('.own-badge')).map(b=>({cls:b.className.replace('own-badge ',''),txt:b.textContent.trim()}));
return r({
has挣得: !!(t&&t.includes('9月挣得')), has进账: !!(t&&t.includes('9月进账')),
has何时到手: !!(t&&t.includes('何时到手')),
has合计应得: !!(t&&t.includes('合计应得')), has合计进账: !!(t&&t.includes('合计进账')),
has结转标注: !!(t&&t.includes('结转10月10号发')),
has本月22: !!(t&&t.includes('本月22号发')),
badges,
snippet: t ? t.slice(0, 420) : null
});
}, 500);
})`);
console.log('② 汇总分区:', JSON.stringify(sum, null, 1));
await shot('c2_summary.png');
// ③ 数字自洽校验:挣得 = 工时收入+奖惩净额;进账 = 两笔之和;结转 = payoutNextB
const nums = await ev(`(function(){
const b = document.body.innerText.replace(/\\s+/g,' ');
const g = re => { const m = b.match(re); return m ? m[1] : null; };
return {
工时收入: g(/工时收入\\S*?\\s*确定\\s*¥([\\d,.]+)/),
奖惩净额: g(/奖惩净额\\s*\\S+\\s*([+-]¥[\\d,.]+)/),
合计应得: g(/合计应得\\s*¥([\\d,.]+)/),
上半月已发: g(/9\\/22 上半月工时([^)]*)\\s*¥([\\d,.]+)/),
结转: g(/10\\/10 下半月工时\\+奖惩(结转)\\s*¥([\\d,.]+)/),
合计进账: g(/合计进账\\s*¥([\\d,.]+)/),
看板结转: g(/9月进账 ¥([\\d,.]+)/)
};
})()`);
console.log('③ 数字:', JSON.stringify(nums));
// ④ 文本报告分段
const rpt = await ev(`(function(){
const app = document.querySelector('#app');
return null;
})()`);
const rptText = await ev(`(function(){
// 通过点「复制文本报告」不便读剪贴板,改为直接取按钮上方渲染不到的内容:用 clipboard 拦截
let captured = null;
const orig = navigator.clipboard && navigator.clipboard.writeText;
return new Promise(res=>{
if (navigator.clipboard) navigator.clipboard.writeText = (t)=>{ captured = t; return Promise.resolve(); };
const btn = Array.from(document.querySelectorAll('button')).find(b=>/复制文本报告/.test(b.innerText||''));
btn.click();
setTimeout(()=>{ res(captured); }, 400);
});
})()`);
console.log('④ 报告:\n' + (rptText || '(未捕获)'));
console.log('\n=== 异常/控制台 ===');
console.log(logs.length?logs.join('\n'):'(无异常)');
ws.close(); chrome.kill(); process.exit(0);
})().catch(e=>{ console.error('FAILED:',e.message); console.error(stderr.join('').slice(-400)); try{chrome.kill();}catch(_){}process.exit(1); });