baseline: 薪酬逻辑收口到 shared/payroll.js 后的当前全量状态

这是第一个包含完整工作区的提交,也是后续「拆 SFC / 上 Vite」的回滚基线。

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

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

未入库内容见 .gitignore:依赖、Chrome 验证 profile、验证截图、
运行时 sqlite 数据与日志、时间戳备份、.trash。
This commit is contained in:
OpenClaw Agent
2026-09-05 14:35:00 +08:00
parent 7d48c4ac65
commit 2329955e32
94 changed files with 12770 additions and 237 deletions
+98
View File
@@ -0,0 +1,98 @@
// 无头实测:点齿轮 -> 设置抽屉是否弹出(returnByValue 版)
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:9522/index.html';
const chrome = spawn(CHROME, [
'--headless=new','--no-sandbox','--disable-gpu','--disable-dev-shm-usage',
'--remote-debugging-port=9225', `--user-data-dir=${OUT_DIR}/.chrome-test`,
'--window-size=420,900','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 tabs = await (await fetch('http://127.0.0.1:9225/json/list')).json();
if (tabs.length) return tabs[0].webSocketDebuggerUrl;
} catch (e) {}
await sleep(250);
}
throw new Error('CDP 连接失败: ' + stderr.join('').slice(-400));
}
(async () => {
try {
const wsUrl = await getWsUrl();
const ws = new WebSocket(wsUrl);
let id = 0; const pending = new Map(); const logs = [];
let openRes; const openP = new Promise(r => openRes = r);
ws.onopen = openRes;
ws.onmessage = ev => {
const m = JSON.parse(ev.data);
if (m.id && pending.has(m.id)) { pending.get(m.id)(m); pending.delete(m.id); }
else if (m.method === 'Runtime.consoleAPICalled') logs.push('C ' + (m.params.args||[]).map(a=>a.value??a.description??'').join(' '));
else if (m.method === 'Runtime.exceptionThrown') logs.push('EXC ' + JSON.stringify(m.params.exceptionDetails));
};
await openP;
const send = (method, params={}) => new Promise(res => { const mid=++id; pending.set(mid,res); ws.send(JSON.stringify({id:mid,method,params})); });
const ev = async (expr) => {
const r = await send('Runtime.evaluate', { expression: expr, awaitPromise: true, returnByValue: true });
if (r.result && r.result.exceptionDetails) return { __err: JSON.stringify(r.result.exceptionDetails) };
return r.result && r.result.result && r.result.result.value;
};
await send('Runtime.enable'); await send('Page.enable');
await send('Page.navigate', { url: URL }); await sleep(2200);
// 1) 初始状态有多少 .z-50
const before = await ev(`({ z50: document.querySelectorAll('.z-50').length, gear: !!document.querySelector('button[title="设置"]') })`);
console.log('初始:', JSON.stringify(before));
// 2) 点齿轮
const after = await ev(`
new Promise(r => {
const btn = document.querySelector('button[title="设置"]');
if (!btn) return r({ err: 'no gear btn' });
btn.click();
setTimeout(() => {
const all = document.querySelectorAll('.fixed.inset-0');
const viz = [];
all.forEach(d => {
const s = getComputedStyle(d);
viz.push({ cls: d.className.replace(/\\s+/g,' ').slice(0,70), disp: s.display, vis: s.visibility, op: s.opacity });
});
r({
z50: document.querySelectorAll('.z-50').length,
overlays: viz,
toolbarCooked: document.body.innerText.includes('薪酬规则'),
gearIcon: btn.outerHTML.slice(0,120)
});
}, 450);
})
`);
console.log('点齿轮后:', JSON.stringify(after, null, 2));
// 3) 强制直接验证 settingsOpen 是否被置 true(绕过 DOM)
const stateProbe = await ev(`(function(){
// 尝试从 app 实例读状态不可行(生产vue未暴露),改为再点一次+立即读 body
return 'probe';
})()`);
const shot = await send('Page.captureScreenshot', { format: 'png' });
fs.writeFileSync(OUT_DIR + '/gear_final.png', Buffer.from(shot.result.data, 'base64'));
console.log('截图 gear_final.png');
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);
}
})();