refactor: 取消 install.php 安装向导 + 下线 pgsql/mysql 支持 + 班级看板独立
- 删除 install.php:所有入口(index/dashboard/draw/login/api)不再跳安装,
未登录统一跳 login.php;连接信息变更在【设置】里直接修改 data/config.json
- 下线 pgsql/mysql:
* bootstrap.php 简化为单文件存储路径(ny_load_config/ny_save_config 直接读写 data/config.json),
删除 ny_db_config、配置迁移、配置双写分支
* lib/Db.php 改为纯 stub:pdo() 恒返回 null,避免上层误连;migrate/ensureRosterLogin 等为 no-op
* Roster.php 经 useDb 永远 false,自动走 roster.json
* api.php 不再返回 db_driver/db_connected/db_error 诊断
- 班级看板独立(dashboard.php 重写):
* 删除"← 返回总览"按钮 + 整体概况 #overview 区
* 进入即展示单班看板:6 张汇总卡 + 分组对比/成员榜
* 顶部 hero 区动态显示当前班级名 + 问卷名
* 工具栏新增 #classSel 班级选择器(切换班级不离开本页)
* URL 支持 ?pid=&class=&auto=1|0 分享与刷新恢复
* 下拉班级选项随数据动态刷新;保留自动刷新 + 全屏 + 缓存
- e2e 测试(test-config-save)加固 cleanup 钩子:
* 统一 cleanup() 函数接管所有退出路径
* 解决之前 e2e 留 residue 把 data/config.json 改成 mock 地址的问题(导致本机登录失败)
- php -l 全文件通过;node --check 内嵌 JS 通过;e2e test-config-save 27/27 + test-draw-alloc 29/29 全过
This commit is contained in:
+26
-10
@@ -37,18 +37,37 @@ fs.writeFileSync(cfgFile, JSON.stringify({
|
||||
layout: 'default',
|
||||
}, null, 2));
|
||||
|
||||
// 统一收尾钩子:恢复原 config + token + kill 子进程。任何退出路径都会触发
|
||||
let cleanedUp = false;
|
||||
function cleanup() {
|
||||
if (cleanedUp) return;
|
||||
cleanedUp = true;
|
||||
try { srv && srv.kill('SIGKILL'); } catch(e){}
|
||||
try { mock && mock.kill('SIGKILL'); } catch(e){}
|
||||
try {
|
||||
if (origCfg !== null) fs.writeFileSync(cfgFile, origCfg);
|
||||
else if (fs.existsSync(cfgFile)) fs.unlinkSync(cfgFile);
|
||||
} catch(e) { console.error('cleanup cfg failed:', e.message); }
|
||||
try {
|
||||
if (hasToken && fs.existsSync(tokenBak)) fs.renameSync(tokenBak, tokenFile);
|
||||
} catch(e) { console.error('cleanup token failed:', e.message); }
|
||||
}
|
||||
process.on('exit', cleanup);
|
||||
process.on('SIGINT', () => { cleanup(); process.exit(1); });
|
||||
process.on('uncaughtException', (e) => { console.error('FATAL', e); cleanup(); process.exit(2); });
|
||||
|
||||
// 2) 启动 mock 卷王
|
||||
const mock = spawn('node', ['tools/mock-server.js'], { cwd: ROOT, stdio: 'pipe' });
|
||||
let mock = null;
|
||||
let srv = null;
|
||||
mock = spawn('node', ['tools/mock-server.js'], { cwd: ROOT, stdio: 'pipe' });
|
||||
mock.stdout.on('data', d => process.stdout.write('[mock] ' + d));
|
||||
mock.stderr.on('data', d => process.stderr.write('[mock-err] ' + d));
|
||||
|
||||
// 3) 启动 PHP 内置服务器(-t 让所有请求走 index.php 路由)
|
||||
const phpArgs = INI.split(/\s+/).filter(Boolean).concat(['-S', '127.0.0.1:' + PORT, '-t', ROOT]);
|
||||
const srv = spawn(PHP, phpArgs, { cwd: ROOT, stdio: 'pipe' });
|
||||
srv = spawn(PHP, phpArgs, { cwd: ROOT, stdio: 'pipe' });
|
||||
srv.stdout.on('data', d => process.stdout.write('[php] ' + d));
|
||||
srv.stderr.on('data', d => process.stderr.write('[php-err] ' + d));
|
||||
process.on('exit', () => { try { srv.kill('SIGKILL'); mock.kill('SIGKILL'); } catch(e){} });
|
||||
process.on('SIGINT', () => { srv.kill('SIGKILL'); mock.kill('SIGKILL'); process.exit(1); });
|
||||
|
||||
let pass = 0, fail = 0;
|
||||
function chk(name, cond, extra) {
|
||||
@@ -279,10 +298,7 @@ async function login() {
|
||||
|
||||
console.log('\n结果:' + pass + ' 通过,' + fail + ' 失败');
|
||||
|
||||
// 清理
|
||||
mock.kill('SIGTERM');
|
||||
srv.kill('SIGTERM');
|
||||
if (origCfg !== null) fs.writeFileSync(cfgFile, origCfg); else if (fs.existsSync(cfgFile)) fs.unlinkSync(cfgFile);
|
||||
if (hasToken && fs.existsSync(tokenBak)) fs.renameSync(tokenBak, tokenFile);
|
||||
// 清理 + 退出(统一走 cleanup 钩子,保证 config/token 一定被还原)
|
||||
cleanup();
|
||||
setTimeout(() => process.exit(fail === 0 ? 0 : 1), 200);
|
||||
})().catch(e => { console.error('FATAL', e); try { mock.kill(); srv.kill(); } catch(z){}; process.exit(2); });
|
||||
})().catch(e => { console.error('FATAL', e); cleanup(); process.exit(2); });
|
||||
|
||||
Reference in New Issue
Block a user