Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

64 linhas
7.5 KiB

  1. import fs from 'node:fs/promises';
  2. import path from 'node:path';
  3. import assert from 'node:assert/strict';
  4. import {pathToFileURL} from 'node:url';
  5. import {chromium} from '@playwright/test';
  6. const folder=path.resolve('reports/bridge-controls-review-20260915');
  7. const read=async file=>JSON.parse((await fs.readFile(path.join(folder,file),'utf8')).replace(/^\uFEFF/,''));
  8. const tasks=[
  9. {id:86,code:'005',title:'液压泵应急切换',steps:5},
  10. {id:87,code:'006',title:'液压泵应急替代',steps:7},
  11. {id:88,code:'007',title:'前泵换件修复',steps:11},
  12. {id:89,code:'008',title:'平衡阀拆拼互换',steps:16},
  13. ];
  14. for(const task of tasks){
  15. const result=await read(`task-${task.id}-verification.json`);
  16. assert.equal(result.pass,true);assert.deepEqual(result.errors,[]);
  17. assert.ok(result.writes.every(url=>url.endsWith(`/assignments/${task.id}/preview/validate`)));
  18. task.actions=result.checks.filter(check=>check.target).length;
  19. task.validations=result.writes.length;
  20. task.workflowTargets=result.checks[0].state.workflowCoverage.length;
  21. }
  22. const lan='../bridge-controls-lan-20260915';
  23. const small='../bridge-controls-small-20260915';
  24. assert.equal((await read(`${lan}/desktop-1920.json`)).pass,true);
  25. assert.equal((await read(`${small}/controls-1024.json`)).pass,true);
  26. const validation=await read('validation.json');assert.equal(validation.build.pass,true);
  27. const summary={pass:true,generatedAt:new Date().toISOString(),tasks,
  28. steps:tasks.reduce((n,t)=>n+t.steps,0),actions:tasks.reduce((n,t)=>n+t.actions,0),
  29. validationRequests:tasks.reduce((n,t)=>n+t.validations,0),validation,
  30. environments:['http://127.0.0.1:6180','http://192.168.31.168:6180'],
  31. resolutions:['1920×1080','1366×768','1280×720','1024×768'],
  32. boundary:'现有任务的管理员/教员预览;未创建任务或写入学员成绩。005 小屏使用点击,006–008 使用拖动;另在局域网大屏验证 005 拖动。',
  33. };
  34. await fs.writeFile(path.join(folder,'summary.json'),JSON.stringify(summary,null,2));
  35. const requirements=[
  36. ['底栏图标','收起/展开步骤、面板及全屏放到底栏右侧。按钮仅显示图标,title 和无障碍名称随状态切换;全屏仍保留底栏和退出按钮。'],
  37. ['初始化取景','进入场景显示整车与操作台总览,按可用显示区域取景,避开教员和工具。点击标签或进入后续步骤时再聚焦,普通聚焦与主动放大分别限制显示尺寸。'],
  38. ['全部流程辉光','训练/预览进入场景时,所有当前可见的流程部件以青色轮廓显示,当前操作目标以金色区分。正式考试保持原有提示限制。'],
  39. ['独立半透明工具','新增“其他半透明”,保持流程部件实体显示。与“高亮问题设备”独立切换;关闭后恢复保存的材质透明度,不影响地形。'],
  40. ['正面与动作方向','根据实际 GLB 校准各类部件观察方向。互救面板从 -X 侧正视,文字可正读;开关绕靠近面板的固定端向“开”的上方旋转。005、006 共用校准;取力器手柄铰点也改为靠近壳体的一端。'],
  41. ['同类回归','逐项执行四套流程,验证所有目标的实际网格命中、固定观察距离、灯光一致、标签不重叠和服务端预览校验。008 两处观察记录仍按界面流程填写并留给教员复核。'],
  42. ];
  43. const photo=(file,alt)=>`<a href="${file}"><img src="${file}" alt="${alt}" loading="eager"></a>`;
  44. const html=`<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>桥梁流程 · 取景与操作控件验收</title>
  45. <style>body{margin:0;background:#eef4f2;color:#163d37;font:15px/1.7 system-ui,sans-serif}main{max-width:1180px;margin:auto;padding:32px}h1{font-size:27px;margin:0}h2{font-size:21px;margin-top:30px}.status,.card{background:white;border:1px solid #c7dcd6;border-radius:10px;padding:18px}.status{border-left:5px solid #12866d}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px}img{width:100%;height:auto;border-radius:6px}table{width:100%;border-collapse:collapse;background:white}td,th{padding:10px 14px;border:1px solid #c7dcd6;text-align:left}a{color:#007a67}details{margin-top:10px}summary{cursor:pointer;font-weight:600}.steps{display:flex;flex-wrap:wrap;gap:8px}small{color:#58776e}@media(max-width:700px){main{padding:16px}.grid{grid-template-columns:1fr}}</style>
  46. <main><h1>桥梁流程 · 取景与操作控件验收</h1><p>2026-09-15 · 正式 HTML + JavaScript 工作区</p>
  47. <div class="status"><b>通过 ${summary.steps} 个步骤、${summary.actions} 个模型操作、${summary.validationRequests} 次服务端预览校验。</b><br>本地及局域网通过;生产构建、正式路由契约和 16 项相关测试通过。</div>
  48. <h2>本轮六项调整</h2><table>${requirements.map(([title,body])=>`<tr><th>${title}</th><td>${body}</td></tr>`).join('')}</table>
  49. <h2>初始总览</h2><div class="grid">${tasks.map(t=>`<article class="card"><b>TASK-VIRTUAL-${t.code} · ${t.title}</b><p>${t.steps} 步 / ${t.actions} 次模型操作 / ${t.workflowTargets} 个可见流程目标</p>${photo(`task-${t.id}-initial.png`,t.title+'初始总览')}<details><summary>逐步截图和校验</summary><div class="steps">${Array.from({length:t.steps},(_,i)=>`<a href="task-${t.id}-step-${i+1}.png">第 ${i+1} 步</a>`).join('')}</div><p><a href="task-${t.id}-verification.json">完整校验 JSON</a> · <a href="task-${t.id}-translucent.png">其他半透明</a></p></details></article>`).join('')}</div>
  50. <h2>大屏初始视角与互救开关</h2><div class="grid"><article class="card">${photo(`${lan}/task-86-initial.png`,'1920 初始总览')}<p>1920×1080:保留整车环境,初始不直接放大液压泵。</p></article><article class="card">${photo(`${lan}/task-86-rescue-moving.png`,'互救开关向上转动')}<p>实际拖动中:从面板正面观察,手柄端点向上移动,固定端不位移。脚本同时验证世界坐标变化。</p></article></div>
  51. <h2>底栏、全屏与小屏</h2><div class="grid"><article class="card">${photo('task-86-fullscreen.png','1280 全屏及底栏')}<p>1280×720 全屏:底栏右侧保留面板和退出全屏图标。</p></article><article class="card">${photo(`${small}/free-inspection-controls.png`,'1024 小屏控件')}<p>1024×768:模型自由查看、材质切换及 Esc 退出,无页面横向溢出。</p></article></div>
  52. <p><a href="${lan}/desktop-1920.json">局域网 1920 完整拖动流程</a> · <a href="${small}/controls-1024.json">局域网 1024 控件回归</a></p>
  53. <h2>验证范围</h2><p>${summary.boundary} 所有操作均使用真实鼠标输入;浏览器注入仅用于读取相机、材质、网格和旋转端点。观察距离、可操作性与方向校验对应现有模型,未新增机械受力仿真。</p><p><a href="summary.json">汇总 JSON</a> · <a href="validation.json">构建和命令记录</a></p><small>以本报告链接的通过记录为准;目录内可能保留此前调试截图。</small></main></html>`;
  54. await fs.writeFile(path.join(folder,'index.html'),html);
  55. const browser=await chromium.launch({channel:'msedge',headless:true});
  56. try{
  57. const page=await browser.newPage({viewport:{width:1440,height:1000}});
  58. await page.goto(pathToFileURL(path.join(folder,'index.html')).href);
  59. assert.ok(await page.locator('img').evaluateAll(imgs=>imgs.every(img=>img.complete&&img.naturalWidth>0)));
  60. await page.screenshot({path:path.join(folder,'report-overview.png')});
  61. }finally{await browser.close();}
  62. console.log(JSON.stringify({pass:true,steps:summary.steps,actions:summary.actions,report:path.join(folder,'index.html')}));