fix(homework,island,verify): 全屏作业看板自适应 + 已安装副本覆盖升级 + 新增打包版冒烟
① 全屏作业看板自适应(用户反馈:全屏模式没有自适应,卡片挤在左上角) - 全屏看板按「可用宽 × 可用高」枚举 1..N 列,取**单卡面积最大且宽高比最接近 1.15** 的方案, 注入 `--board-cols`(不再靠 CSS auto-fit 猜);列数变了再量一次缩放系数, `transform-origin: top center` 保证整体居中、不溢出屏幕 - 两个全屏弹窗各自持有 ref(原来共用同名 ref,后挂载的会覆盖前一个,量到的是另一个弹窗); 列表型「单个科目全屏」补上 `transform: scale()`(该规则原先只作用于 `.board`,列表全屏从未缩放) - 弹窗 `@opened` 后轮询等待布局就绪(最多 20×60ms)再计算;窗口 resize 时自动重算; 另加一条全局 `.board-fullscreen .el-dialog__body` 兜底,保证内容区铺满整屏 - 实测:6 个科目 → 3 列、排版宽 1155px(视口 1187px)、可视底 678px(视口高 765px) ② 「灵动岛设置没办法生效」的真正原因:用户机器上的副本一直没被更新 - 学生端实际运行的是安装目录 `D:\class\@classhelperdesktop-client` 里的 `resources/app.asar`, 而 `pnpm dist:win` 只更新 `release/`;只发新版 release 不动安装目录,用户看到的还是旧界面 - 已就地覆盖升级该目录,并用同一套冒烟实测**那个副本**:`packaged: true`、76/76 全绿 - README 补充「把修复交付到已安装的客户端」步骤(robocopy 覆盖 + verify:packaged 实测) ③ 新增/加固自动化验证 - `verify:desktop` 新增「作业看板全屏自适应(铺满屏幕、内容不溢出)」:进作业页 → 切看板 → 打开全屏 → 断言排版宽铺满视口、可视底不超出屏幕;再把卡片补到 6 张触发 resize, 要求列数自动变多(1 → 3)且仍不溢出 - 点击/命中这类异步链路由"固定 sleep 后读一次"改为**轮询等待**(曾偶发误判为"点不开"), 失败时把渲染进程当前类名、窗口可见性、岛体矩形写进结论,便于区分真 bug 与竞态 - 新增 `pnpm verify:packaged`:对**打包后/已安装**的 EXE 跑同一套冒烟(含二次启动校验文件锁), 避免"开发产物全绿但交付给学生的 EXE 是旧的"这类问题 - 实测:`verify:desktop` 76/76(离线复跑 68/68);`verify:packaged` 对 `release/win-unpacked` 与 `D:\class\@classhelperdesktop-client` 均 76/76、`packaged: true`; `verify:e2e` 149/149(安装实例);`typecheck` / `lint` / `format:check` 全绿
This commit is contained in:
1 parent
11008928a1
commit
0ff2dc228b
7 files changed
+466
-67
No files matched your search
@@ -475,6 +475,113 @@ export async function scheduleTimelineSelfTest(): Promise<SmokeCheckResult> {
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 冒烟自检 8(需后端在线):作业看板**全屏自适应**。
|
||||
*
|
||||
* 用户反馈"全屏模式没有自适应":点开全屏后卡片挤在左上角、内容显示不全。
|
||||
* 这里走真实界面链路:进入作业页 → 切到「看板」→ 点「全屏看板」→
|
||||
* 断言 ①布局宽度铺满视口(列数由算法注入 `--board-cols`)②缩放后可视底部不超出屏幕(无需滚动)。
|
||||
*/
|
||||
export async function homeworkBoardSelfTest(): Promise<SmokeCheckResult> {
|
||||
const { router } = await import('../router/index.js');
|
||||
try {
|
||||
await router.push('/homeworks');
|
||||
if (!(await waitUntil(() => Boolean(document.querySelector('.homework-page')), 6000))) {
|
||||
return { ok: false, detail: '作业页未挂载' };
|
||||
}
|
||||
|
||||
// 必须是「看板」模式(列表模式没有全屏看板入口)
|
||||
const boardTab = Array.from(document.querySelectorAll('.el-radio-button')).find((node) =>
|
||||
(node.textContent ?? '').includes('看板'),
|
||||
);
|
||||
if (!(boardTab instanceof HTMLElement)) return { ok: false, detail: '找不到「看板」切换按钮' };
|
||||
boardTab.click();
|
||||
await waitUntil(() => Boolean(document.querySelector('.board-config')), 3000);
|
||||
|
||||
// 今天可能恰好没有作业:先关掉「只看今天」,保证看板一定有卡片(断言才有意义)
|
||||
if (!document.querySelector('.board-host')) {
|
||||
const todaySwitch = document.querySelector('.page-header .el-switch');
|
||||
if (todaySwitch instanceof HTMLElement) todaySwitch.click();
|
||||
}
|
||||
const hasBoard = await waitUntil(() => Boolean(document.querySelector('.board-host')), 4000);
|
||||
|
||||
const fullscreenButton = Array.from(document.querySelectorAll('button')).find((node) =>
|
||||
(node.textContent ?? '').includes('全屏看板'),
|
||||
);
|
||||
if (!(fullscreenButton instanceof HTMLElement)) return { ok: false, detail: '找不到「全屏看板」按钮' };
|
||||
fullscreenButton.click();
|
||||
|
||||
const dialogReady = await waitUntil(
|
||||
() => Boolean(document.querySelector('.board-fullscreen .board-lg')),
|
||||
6000,
|
||||
);
|
||||
if (!dialogReady) return { ok: false, detail: '全屏看板弹窗未出现' };
|
||||
|
||||
// 等自适应轮询跑完(最多 20 次 × 60ms)
|
||||
await new Promise((resolve) => setTimeout(resolve, 1600));
|
||||
|
||||
const board = document.querySelector('.board-fullscreen .board-lg');
|
||||
const host = document.querySelector('.board-fullscreen .board-host-lg');
|
||||
if (!(board instanceof HTMLElement) || !(host instanceof HTMLElement)) {
|
||||
return { ok: false, detail: '全屏看板容器缺失' };
|
||||
}
|
||||
const rect = board.getBoundingClientRect();
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
const layoutWidth = board.offsetWidth;
|
||||
const columns = Number(board.style.getPropertyValue('--board-cols') || 0);
|
||||
const cards = board.querySelectorAll('.board-card').length;
|
||||
const scale = Number((getComputedStyle(board).transform.match(/matrix\(([-\d.]+)/) ?? [])[1] ?? 1);
|
||||
|
||||
const fillsWidth = layoutWidth >= viewportWidth * 0.9;
|
||||
const fitsScreen = rect.bottom <= viewportHeight + 2 && rect.width <= viewportWidth + 2;
|
||||
|
||||
// 第二阶段:模拟"科目很多"(真实班级看板常有 5~8 个科目)。
|
||||
// 克隆卡片到 6 张 → 触发 resize 让自适应算法重算 → 断言列数自动变多且仍然铺满、不溢出。
|
||||
let manyCards = '';
|
||||
let manyOk = false;
|
||||
const sample = board.querySelector('.board-card');
|
||||
if (sample instanceof HTMLElement) {
|
||||
while (board.querySelectorAll('.board-card').length < 6) {
|
||||
const clone = sample.cloneNode(true) as HTMLElement;
|
||||
clone.setAttribute('data-smoke-filler', '1');
|
||||
board.appendChild(clone);
|
||||
}
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
const cardsMany = board.querySelectorAll('.board-card').length;
|
||||
const columnsMany = Number(board.style.getPropertyValue('--board-cols') || 0);
|
||||
const layoutWidthMany = board.offsetWidth;
|
||||
const rectMany = board.getBoundingClientRect();
|
||||
manyOk =
|
||||
cardsMany === 6 &&
|
||||
columnsMany >= 2 &&
|
||||
columnsMany <= cardsMany &&
|
||||
layoutWidthMany >= viewportWidth * 0.9 &&
|
||||
rectMany.bottom <= viewportHeight + 2;
|
||||
manyCards =
|
||||
`多科目:卡片=${cardsMany} 列数=${columnsMany} 排版宽=${layoutWidthMany} ` +
|
||||
`可视底=${Math.round(rectMany.bottom)} 通过=${manyOk}`;
|
||||
}
|
||||
|
||||
const closeButton = document.querySelector('.board-fullscreen .el-dialog__headerbtn');
|
||||
if (closeButton instanceof HTMLElement) closeButton.click();
|
||||
|
||||
return {
|
||||
ok: hasBoard && cards > 0 && columns >= 1 && columns <= cards && fillsWidth && fitsScreen && manyOk,
|
||||
detail:
|
||||
`卡片=${cards} 列数=${columns} 缩放=${Number.isFinite(scale) ? scale.toFixed(3) : '-'} ` +
|
||||
`排版宽=${layoutWidth}(视口宽=${viewportWidth})可视底=${Math.round(rect.bottom)}(视口高=${viewportHeight}) ` +
|
||||
`铺满宽=${fillsWidth} 不超出屏幕=${fitsScreen};${manyCards}`,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
detail: `看板自适应自检异常:${error instanceof Error ? error.message : String(error)}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** 冒烟收尾:断开实时通道并退出登录,保证下次冒烟从登录页开始 */
|
||||
export async function sessionCleanup(): Promise<SmokeCheckResult> {
|
||||
const [{ useAuthStore }, { useRealtimeStore }] = await Promise.all([
|
||||
@@ -501,6 +608,7 @@ export function registerSmokeHooks(): void {
|
||||
islandRealtimeCleanup,
|
||||
islandReadState,
|
||||
scheduleTimelineSelfTest,
|
||||
homeworkBoardSelfTest,
|
||||
sessionCleanup,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user