diff --git a/reports/digital-human-simple-auth-20260909/01-configuration.png b/reports/digital-human-simple-auth-20260909/01-configuration.png
new file mode 100644
index 0000000..d37cc18
Binary files /dev/null and b/reports/digital-human-simple-auth-20260909/01-configuration.png differ
diff --git a/reports/digital-human-simple-auth-20260909/02-connection.png b/reports/digital-human-simple-auth-20260909/02-connection.png
new file mode 100644
index 0000000..6d50186
Binary files /dev/null and b/reports/digital-human-simple-auth-20260909/02-connection.png differ
diff --git a/reports/digital-human-simple-auth-20260909/index.html b/reports/digital-human-simple-auth-20260909/index.html
new file mode 100644
index 0000000..e924160
--- /dev/null
+++ b/reports/digital-human-simple-auth-20260909/index.html
@@ -0,0 +1 @@
+
数字人 APPID + Secret 简化验证 数字人 APPID + Secret 简化验证 2026-09-09 · 开发环境真实联调 · 18 项通过
移除两个额外密钥文件及运行依赖;现有 Secret 迁移至接口配置表 app_secret。继续使用现有登录身份调用 Auth,数字人开放服务通过 APPID + Secret 换取短期令牌。
✓ integration-secret.key 已移除 ✓ integration-internal.key 已移除 ✓ 接口配置列表不返回 Secret 字段 ✓ 原有 APPID 和密钥配置保留 ✓ 留空 Secret 保存配置成功且不回显密钥 ✓ 移除密钥文件后 APPID + Secret 连接测试成功 ✓ 内部调用继续要求现有登录身份 ✓ 复用登录身份获取运行令牌,无额外服务密钥 ✓ Tran → Auth → 数字人系统能力调用成功 ✓ 授权资源 videos 查询成功 ✓ 授权资源 video-folders 查询成功 ✓ 授权资源 avatars 查询成功 ✓ 授权资源 voices 查询成功 ✓ 授权资源 backgrounds 查询成功 ✓ 配置弹窗密钥留空,仅显示已配置状态 ✓ 页面留空密钥保存成功 ✓ 页面连接检查实际响应成功 ✓ 页面连接测试成功 后端 mvn clean package 通过;新增认证、密钥保存和转发测试。039 迁移的密文保护、正常迁移、重复执行已验证。原有应用保持不变;本轮未重新创建视频生成任务。
配置弹窗 真实连接测试
\ No newline at end of file
diff --git a/reports/digital-human-simple-auth-20260909/results.json b/reports/digital-human-simple-auth-20260909/results.json
new file mode 100644
index 0000000..45b2f0f
--- /dev/null
+++ b/reports/digital-human-simple-auth-20260909/results.json
@@ -0,0 +1,77 @@
+{
+ "time": "2026-09-09T15:49:58.729Z",
+ "checks": [
+ {
+ "name": "integration-secret.key 已移除",
+ "result": "PASS"
+ },
+ {
+ "name": "integration-internal.key 已移除",
+ "result": "PASS"
+ },
+ {
+ "name": "接口配置列表不返回 Secret 字段",
+ "result": "PASS"
+ },
+ {
+ "name": "原有 APPID 和密钥配置保留",
+ "result": "PASS"
+ },
+ {
+ "name": "留空 Secret 保存配置成功且不回显密钥",
+ "result": "PASS"
+ },
+ {
+ "name": "移除密钥文件后 APPID + Secret 连接测试成功",
+ "result": "PASS"
+ },
+ {
+ "name": "内部调用继续要求现有登录身份",
+ "result": "PASS"
+ },
+ {
+ "name": "复用登录身份获取运行令牌,无额外服务密钥",
+ "result": "PASS"
+ },
+ {
+ "name": "Tran → Auth → 数字人系统能力调用成功",
+ "result": "PASS"
+ },
+ {
+ "name": "授权资源 videos 查询成功",
+ "result": "PASS"
+ },
+ {
+ "name": "授权资源 video-folders 查询成功",
+ "result": "PASS"
+ },
+ {
+ "name": "授权资源 avatars 查询成功",
+ "result": "PASS"
+ },
+ {
+ "name": "授权资源 voices 查询成功",
+ "result": "PASS"
+ },
+ {
+ "name": "授权资源 backgrounds 查询成功",
+ "result": "PASS"
+ },
+ {
+ "name": "配置弹窗密钥留空,仅显示已配置状态",
+ "result": "PASS"
+ },
+ {
+ "name": "页面留空密钥保存成功",
+ "result": "PASS"
+ },
+ {
+ "name": "页面连接检查实际响应成功",
+ "result": "PASS"
+ },
+ {
+ "name": "页面连接测试成功",
+ "result": "PASS"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/reports/guide-style-20260909/01-list.png b/reports/guide-style-20260909/01-list.png
new file mode 100644
index 0000000..e89e548
Binary files /dev/null and b/reports/guide-style-20260909/01-list.png differ
diff --git a/reports/guide-style-20260909/02-list-actions.png b/reports/guide-style-20260909/02-list-actions.png
new file mode 100644
index 0000000..811a099
Binary files /dev/null and b/reports/guide-style-20260909/02-list-actions.png differ
diff --git a/reports/guide-style-20260909/03-editor-dark.png b/reports/guide-style-20260909/03-editor-dark.png
new file mode 100644
index 0000000..e1cf9b2
Binary files /dev/null and b/reports/guide-style-20260909/03-editor-dark.png differ
diff --git a/reports/guide-style-20260909/03-editor.png b/reports/guide-style-20260909/03-editor.png
new file mode 100644
index 0000000..933cfcf
Binary files /dev/null and b/reports/guide-style-20260909/03-editor.png differ
diff --git a/reports/guide-style-20260909/03-guide-preview.png b/reports/guide-style-20260909/03-guide-preview.png
new file mode 100644
index 0000000..3ed333e
Binary files /dev/null and b/reports/guide-style-20260909/03-guide-preview.png differ
diff --git a/reports/guide-style-20260909/04-templates.png b/reports/guide-style-20260909/04-templates.png
new file mode 100644
index 0000000..c9a8bda
Binary files /dev/null and b/reports/guide-style-20260909/04-templates.png differ
diff --git a/reports/guide-style-20260909/05-template-preview.png b/reports/guide-style-20260909/05-template-preview.png
new file mode 100644
index 0000000..2054b94
Binary files /dev/null and b/reports/guide-style-20260909/05-template-preview.png differ
diff --git a/reports/guide-style-20260909/06-api.png b/reports/guide-style-20260909/06-api.png
new file mode 100644
index 0000000..8ee4fc5
Binary files /dev/null and b/reports/guide-style-20260909/06-api.png differ
diff --git a/reports/guide-style-20260909/07-api-result.png b/reports/guide-style-20260909/07-api-result.png
new file mode 100644
index 0000000..8ab66b4
Binary files /dev/null and b/reports/guide-style-20260909/07-api-result.png differ
diff --git a/reports/guide-style-20260909/08-api-dark.png b/reports/guide-style-20260909/08-api-dark.png
new file mode 100644
index 0000000..eb57b82
Binary files /dev/null and b/reports/guide-style-20260909/08-api-dark.png differ
diff --git a/reports/guide-style-20260909/08-api-graphite.png b/reports/guide-style-20260909/08-api-graphite.png
new file mode 100644
index 0000000..0f7d814
Binary files /dev/null and b/reports/guide-style-20260909/08-api-graphite.png differ
diff --git a/reports/guide-style-20260909/08-api-technology.png b/reports/guide-style-20260909/08-api-technology.png
new file mode 100644
index 0000000..a0a9daf
Binary files /dev/null and b/reports/guide-style-20260909/08-api-technology.png differ
diff --git a/reports/guide-style-20260909/09-api-laptop.png b/reports/guide-style-20260909/09-api-laptop.png
new file mode 100644
index 0000000..242a23c
Binary files /dev/null and b/reports/guide-style-20260909/09-api-laptop.png differ
diff --git a/reports/guide-style-20260909/10-templates-laptop.png b/reports/guide-style-20260909/10-templates-laptop.png
new file mode 100644
index 0000000..c65722f
Binary files /dev/null and b/reports/guide-style-20260909/10-templates-laptop.png differ
diff --git a/reports/guide-style-20260909/11-templates-dark.png b/reports/guide-style-20260909/11-templates-dark.png
new file mode 100644
index 0000000..bdf9205
Binary files /dev/null and b/reports/guide-style-20260909/11-templates-dark.png differ
diff --git a/reports/guide-style-20260909/12-list-dark.png b/reports/guide-style-20260909/12-list-dark.png
new file mode 100644
index 0000000..98fb7af
Binary files /dev/null and b/reports/guide-style-20260909/12-list-dark.png differ
diff --git a/reports/guide-style-20260909/13-create-wizard.png b/reports/guide-style-20260909/13-create-wizard.png
new file mode 100644
index 0000000..ed5d1d5
Binary files /dev/null and b/reports/guide-style-20260909/13-create-wizard.png differ
diff --git a/reports/guide-style-20260909/index.html b/reports/guide-style-20260909/index.html
new file mode 100644
index 0000000..648b7fe
--- /dev/null
+++ b/reports/guide-style-20260909/index.html
@@ -0,0 +1,36 @@
+
+
+作业指导页面风格统一 · 验证报告
+
+
+本次调整 补充调整:模板库以完整白色内容卡片承载模板、分页与结构预览;对外接口的主内容、接口说明及契约区域统一使用系统白色背景。截图已更新,并重新通过构建与浏览器回归。
页面 调整内容
+指导书列表 使用系统搜索框、状态/排序选择器、带边框表格和统一分页;搜索需明确提交,支持重置。进入编制为主要操作,详情、预览、版本、下载和删除收进“更多”,权限及禁用条件保留。
+模板库 移除重复大标题,统一页面边距、模板卡片、选中态和分页;较窄窗口改为两列,结构预览保留。
+编制、新建、预览 统一工具栏、按钮、表单、侧栏和选中态颜色。编制器按视口分配高度,内部滚动。文档纸张及插图保留原有颜色。
+对外接口 统一三栏面板、留白、输入框及按钮;长路径换行,较窄窗口将契约说明放到下方。暗色主题补齐背景与文字对比。
+验证范围及结果 检查 结果
+pnpm run build:类型检查、Vite 构建、模型编辑器产物检查 通过
+12 条临时指导书:明确搜索、无结果、重置、下一页/上一页 通过
+更多菜单、项目详情打开/关闭、进入编制、指导书预览、新建入口 通过
+4 个模板:选择切换、结构预览、模板预览弹窗 通过
+接口查询:执行 GET 项目列表,自检返回 200 通过
+1920×911、1366×768;军绿、科技、石墨、暗色主题截图检查 通过
+所有截取页面无整体横向溢出;无浏览器未捕获异常;局域网入口返回 200 通过
+使用独立、用后关闭的浏览器上下文,在其本地指导书仓库准备 STYLE-1~STYLE-12 测试数据,没有修改用户浏览器中的现有指导书。未执行发布、删除、外部写入或下载生成的全面业务回归。本轮为样式调整与主要交互回归;接口页现有查询仍是本地工程自检,不代表新增了 HTTP 服务。
+编制器和模板预览保留现有自定义渲染及事件处理,宿主页负责系统控件和主题适配,避免样式调整影响文档编制与导出逻辑。构建保留现有大分包、Three.js URL 提示,无新增构建失败。
+指导书列表:统一筛选、表格和主要操作
+更多菜单:保留全部次要操作及禁用状态
+模板库:统一外边距、卡片、分页和结构预览
+模板预览
+指导书编制
+指导书编制 · 暗色主题
+指导书动态预览:纸张、插图保持原色
+对外接口:导航、参数及契约说明
+接口查询结果
+暗色主题下的接口页面
+较窄窗口:模板库自动调整列数
+新建指导书入口
+
+
diff --git a/reports/guide-style-20260909/results.json b/reports/guide-style-20260909/results.json
new file mode 100644
index 0000000..86751d8
--- /dev/null
+++ b/reports/guide-style-20260909/results.json
@@ -0,0 +1,94 @@
+{
+ "checks": [
+ {
+ "name": "01-list",
+ "result": "PASS",
+ "screenshot": "01-list.png"
+ },
+ {
+ "name": "02-list-actions",
+ "result": "PASS",
+ "screenshot": "02-list-actions.png"
+ },
+ {
+ "name": "03-editor",
+ "result": "PASS",
+ "screenshot": "03-editor.png"
+ },
+ {
+ "name": "03-editor-dark",
+ "result": "PASS",
+ "screenshot": "03-editor-dark.png"
+ },
+ {
+ "name": "03-guide-preview",
+ "result": "PASS",
+ "screenshot": "03-guide-preview.png"
+ },
+ {
+ "name": "04-templates",
+ "result": "PASS",
+ "screenshot": "04-templates.png"
+ },
+ {
+ "name": "05-template-preview",
+ "result": "PASS",
+ "screenshot": "05-template-preview.png"
+ },
+ {
+ "name": "06-api",
+ "result": "PASS",
+ "screenshot": "06-api.png"
+ },
+ {
+ "name": "07-api-result",
+ "result": "PASS",
+ "screenshot": "07-api-result.png"
+ },
+ {
+ "name": "08-api-technology",
+ "result": "PASS",
+ "screenshot": "08-api-technology.png"
+ },
+ {
+ "name": "08-api-graphite",
+ "result": "PASS",
+ "screenshot": "08-api-graphite.png"
+ },
+ {
+ "name": "08-api-dark",
+ "result": "PASS",
+ "screenshot": "08-api-dark.png"
+ },
+ {
+ "name": "09-api-laptop",
+ "result": "PASS",
+ "screenshot": "09-api-laptop.png"
+ },
+ {
+ "name": "10-templates-laptop",
+ "result": "PASS",
+ "screenshot": "10-templates-laptop.png"
+ },
+ {
+ "name": "11-templates-dark",
+ "result": "PASS",
+ "screenshot": "11-templates-dark.png"
+ },
+ {
+ "name": "12-list-dark",
+ "result": "PASS",
+ "screenshot": "12-list-dark.png"
+ },
+ {
+ "name": "13-create-wizard",
+ "result": "PASS",
+ "screenshot": "13-create-wizard.png"
+ },
+ {
+ "name": "搜索/重置、详情、编制入口、模板选择/预览、GET 接口自检、主题切换、局域网入口",
+ "result": "PASS"
+ }
+ ],
+ "errors": []
+}
\ No newline at end of file
diff --git a/reports/interface-dialog-height-20260909/1280x600-dark.png b/reports/interface-dialog-height-20260909/1280x600-dark.png
new file mode 100644
index 0000000..33c19f8
Binary files /dev/null and b/reports/interface-dialog-height-20260909/1280x600-dark.png differ
diff --git a/reports/interface-dialog-height-20260909/1366x768-military.png b/reports/interface-dialog-height-20260909/1366x768-military.png
new file mode 100644
index 0000000..2073bb4
Binary files /dev/null and b/reports/interface-dialog-height-20260909/1366x768-military.png differ
diff --git a/reports/interface-dialog-height-20260909/390x844-military.png b/reports/interface-dialog-height-20260909/390x844-military.png
new file mode 100644
index 0000000..d781f83
Binary files /dev/null and b/reports/interface-dialog-height-20260909/390x844-military.png differ
diff --git a/reports/interface-dialog-height-20260909/844x390-military.png b/reports/interface-dialog-height-20260909/844x390-military.png
new file mode 100644
index 0000000..3cb3962
Binary files /dev/null and b/reports/interface-dialog-height-20260909/844x390-military.png differ
diff --git a/reports/interface-dialog-height-20260909/index.html b/reports/interface-dialog-height-20260909/index.html
new file mode 100644
index 0000000..70979c0
--- /dev/null
+++ b/reports/interface-dialog-height-20260909/index.html
@@ -0,0 +1 @@
+Service configuration dialog Service configuration dialog 2026-09-09. Local and LAN development environment. Account: root. 4 viewport checks passed: dialog stays inside viewport, form scrolls to final field, footer remains fixed and visible, cancel works. No business data modified.
1366x768 / military / PASS 1280x600 / dark / PASS 390x844 / military / PASS 844x390 / military / PASS
\ No newline at end of file
diff --git a/reports/interface-dialog-height-20260909/results.json b/reports/interface-dialog-height-20260909/results.json
new file mode 100644
index 0000000..e978d9d
--- /dev/null
+++ b/reports/interface-dialog-height-20260909/results.json
@@ -0,0 +1,31 @@
+{
+ "checks": [
+ {
+ "viewport": "1366x768",
+ "theme": "military",
+ "result": "PASS",
+ "screenshot": "1366x768-military.png"
+ },
+ {
+ "viewport": "1280x600",
+ "theme": "dark",
+ "result": "PASS",
+ "screenshot": "1280x600-dark.png"
+ },
+ {
+ "viewport": "390x844",
+ "theme": "military",
+ "result": "PASS",
+ "screenshot": "390x844-military.png"
+ },
+ {
+ "viewport": "844x390",
+ "theme": "military",
+ "result": "PASS",
+ "screenshot": "844x390-military.png"
+ }
+ ],
+ "lan": "PASS",
+ "account": "root",
+ "dataChanges": "none"
+}
\ No newline at end of file
diff --git a/reports/interface-dialog-height-build.log b/reports/interface-dialog-height-build.log
new file mode 100644
index 0000000..93a7009
Binary files /dev/null and b/reports/interface-dialog-height-build.log differ
diff --git a/tools/digital-human-simple-auth.mjs b/tools/digital-human-simple-auth.mjs
new file mode 100644
index 0000000..2eccc8d
--- /dev/null
+++ b/tools/digital-human-simple-auth.mjs
@@ -0,0 +1,77 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import assert from 'node:assert/strict'
+import { fileURLToPath } from 'node:url'
+import { createRequire } from 'node:module'
+import { login, call } from './role-permissions-live.mjs'
+
+const report = fileURLToPath(new URL('../reports/digital-human-simple-auth-20260909/', import.meta.url))
+fs.mkdirSync(report, { recursive:true })
+const checks = []
+function check(name, condition) { assert.ok(condition, name); checks.push({ name, result:'PASS' }); console.log(`PASS: ${name}`) }
+const clean = value => !/"(?:appSecret|secretCipher|app_secret|secret_cipher)"\s*:/.test(JSON.stringify(value))
+let browser
+try {
+ for (const name of ['integration-secret.key', 'integration-internal.key']) {
+ check(`${name} 已移除`, !fs.existsSync(fileURLToPath(new URL(`../../unreal_tran_api/config/${name}`, import.meta.url))))
+ }
+ const session = await login('root')
+ const token = session.accessToken
+ const configs = await call('auth/v1/interfaces?size=100', token)
+ check('接口配置列表不返回 Secret 字段', configs.status === 200 && clean(configs.data))
+ const row = configs.data.records.find(item => item.code === 'digital-human' && item.environmentCode === 'default')
+ assert.ok(row, 'Digital-human configuration exists')
+ const settings = JSON.parse(row.extraConfig)
+ check('原有 APPID 和密钥配置保留', settings.appId === 'AP_238179E0A10F97927EA49FA3' && settings.secretConfigured)
+ const configuration = { ...row, environment:row.environmentCode, version:row.dataVersion }
+ const saved = await call(`auth/v1/interfaces/${row.id}/digital-human`, token, { configuration, appId:settings.appId, appSecret:'' }, 'PUT')
+ check('留空 Secret 保存配置成功且不回显密钥', saved.status === 200 && saved.code === 0 && clean(saved.data))
+ const tested = await call(`auth/v1/interfaces/${row.id}/test`, token, {})
+ check('移除密钥文件后 APPID + Secret 连接测试成功', tested.status === 200 && tested.data?.success)
+ const runtimeUrl = 'http://127.0.0.1:6101/internal/v1/interfaces/digital-human/runtime'
+ const anonymous = await fetch(runtimeUrl, { method:'POST' })
+ check('内部调用继续要求现有登录身份', anonymous.status === 401)
+ const runtime = await fetch(runtimeUrl, { method:'POST', headers:{ Authorization:`Bearer ${token}` } })
+ const runtimeData = await runtime.json()
+ check('复用登录身份获取运行令牌,无额外服务密钥', runtime.status === 200 && runtimeData.data?.accessToken && clean(runtimeData) && runtime.headers.get('cache-control') === 'no-store')
+ const capabilities = await call('tran/v1/content/digital-human/capabilities', token)
+ check('Tran → Auth → 数字人系统能力调用成功', capabilities.status === 200 && capabilities.data?.appId === settings.appId)
+ for (const kind of ['videos', 'video-folders', 'avatars', 'voices', 'backgrounds']) {
+ const response = await call(`tran/v1/content/digital-human/resources/${kind}`, token)
+ check(`授权资源 ${kind} 查询成功`, response.status === 200 && response.code === 0)
+ }
+ const { chromium, expect } = createRequire(new URL('../package.json', import.meta.url))('@playwright/test')
+ browser = await chromium.launch({ channel:'msedge', headless:true })
+ const context = await browser.newContext({ viewport:{ width:1600, height:1000 } })
+ await context.addInitScript(({ accessToken, refreshToken }) => {
+ sessionStorage.setItem('unreal-tran:web:access-token:v1', accessToken)
+ sessionStorage.setItem('unreal-tran:web:refresh-token:v1', refreshToken)
+ }, session)
+ const page = await context.newPage()
+ await page.goto('http://127.0.0.1:6180/system/interfaces')
+ const configRow = page.locator('.el-table__body tr').filter({ hasText:'digital-human' }).first()
+ await expect(configRow).toBeVisible({ timeout:60000 })
+ await configRow.getByRole('button', { name:'编辑配置' }).click()
+ const dialog = page.getByRole('dialog', { name:'编辑服务配置' })
+ const secret = dialog.locator('input[type="password"]')
+ await expect(secret).toHaveValue('')
+ await expect(secret).toHaveAttribute('placeholder', '已配置,留空保留现有密钥')
+ check('配置弹窗密钥留空,仅显示已配置状态', true)
+ await page.screenshot({ path:path.join(report, '01-configuration.png'), animations:'disabled' })
+ const saveResponse = page.waitForResponse(response => response.request().method() === 'PUT' && response.url().endsWith(`/interfaces/${row.id}/digital-human`))
+ await dialog.getByRole('button', { name:'保存配置' }).click()
+ check('页面留空密钥保存成功', (await saveResponse).status() === 200)
+ await expect(dialog).toBeHidden()
+ const testResponse = page.waitForResponse(response => response.request().method() === 'POST' && response.url().endsWith(`/interfaces/${row.id}/test`))
+ await configRow.getByRole('button', { name:'连接检查' }).click()
+ const testedFromPage = await (await testResponse).json()
+ check('页面连接检查实际响应成功', testedFromPage.data?.success)
+ await expect(configRow.getByText('连接正常', { exact:true })).toBeVisible({ timeout:30000 })
+ await expect(page.locator('.el-loading-mask:visible')).toHaveCount(0)
+ await page.screenshot({ path:path.join(report, '02-connection.png'), animations:'disabled' })
+ check('页面连接测试成功', true)
+} finally {
+ await browser?.close()
+ fs.writeFileSync(path.join(report, 'results.json'), JSON.stringify({ time:new Date().toISOString(), checks }, null, 2))
+}
+fs.writeFileSync(path.join(report, 'index.html'), `数字人 APPID + Secret 简化验证 数字人 APPID + Secret 简化验证 2026-09-09 · 开发环境真实联调 · ${checks.length} 项通过
移除两个额外密钥文件及运行依赖;现有 Secret 迁移至接口配置表 app_secret。继续使用现有登录身份调用 Auth,数字人开放服务通过 APPID + Secret 换取短期令牌。
${checks.map(x=>`✓ ${x.name} `).join('')} 后端 mvn clean package 通过;新增认证、密钥保存和转发测试。039 迁移的密文保护、正常迁移、重复执行已验证。原有应用保持不变;本轮未重新创建视频生成任务。
配置弹窗 真实连接测试 `)
diff --git a/tools/guide-style-review.mjs b/tools/guide-style-review.mjs
new file mode 100644
index 0000000..a59f9b3
--- /dev/null
+++ b/tools/guide-style-review.mjs
@@ -0,0 +1,114 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import { createRequire } from 'node:module'
+import { fileURLToPath } from 'node:url'
+import assert from 'node:assert/strict'
+import { login } from './role-permissions-live.mjs'
+const { chromium, expect } = createRequire(new URL('../package.json', import.meta.url))('@playwright/test')
+const report = fileURLToPath(new URL('../reports/guide-style-20260909/', import.meta.url))
+fs.mkdirSync(report, { recursive:true })
+const browser = await chromium.launch({ channel:'msedge', headless:true, args:['--enable-unsafe-swiftshader'] })
+const checks = [], errors = []
+const session = await login('root')
+const context = await browser.newContext({ viewport:{ width:1920, height:911 } })
+await context.addInitScript(({ accessToken, refreshToken }) => {
+ sessionStorage.setItem('unreal-tran:web:access-token:v1', accessToken)
+ sessionStorage.setItem('unreal-tran:web:refresh-token:v1', refreshToken)
+}, session)
+const page = await context.newPage()
+page.on('pageerror', error => errors.push(error.message))
+async function shot(name) {
+ const overflow = await page.locator('.legacy-guide-page').evaluate(el => el.scrollWidth > el.clientWidth + 2)
+ assert.equal(overflow, false, `${name}: page has no horizontal overflow`)
+ await page.screenshot({ path:path.join(report, `${name}.png`), animations:'disabled' })
+ checks.push({ name, result:'PASS', screenshot:`${name}.png` })
+}
+async function theme(mode) {
+ await page.evaluate(async mode => (await import('/src/stores/preferences.ts')).usePreferencesStore().setThemeMode(mode), mode)
+}
+try {
+ await page.goto('http://127.0.0.1:6180/content/ofd')
+ await expect(page.locator('.guide-project-list')).toBeVisible({ timeout:60000 })
+ await theme('military')
+ // Fixtures exist only in this disposable browser's local guide repository.
+ await page.evaluate(async () => {
+ const { legacyGuideContentStore } = await import('/src/features/guide-legacy/runtime.ts')
+ for (let i=1;i<=12;i++) legacyGuideContentStore.createOFDProject({ id:`STYLE-${i}`, code:`STYLE-${String(i).padStart(3,'0')}`, title:`液压系统检查作业指导书 · 样式验证 ${i}`, author:'样式验证' })
+ })
+ await expect(page.locator('.guide-project-title')).toHaveCount(10)
+ console.log('List loaded', await page.locator('.guide-project-title').count())
+ await shot('01-list')
+ const count = await page.locator('.guide-project-title').count()
+ await page.getByRole('textbox', { name:'搜索指导书', exact:true }).fill('NO-MATCH-GUIDE-STYLE')
+ assert.equal(await page.locator('.guide-project-title').count(), count, 'Search waits for explicit submission')
+ await page.locator('.guide-project-list').getByRole('button', { name:'搜索', exact:true }).click()
+ await expect(page.locator('.guide-project-title')).toHaveCount(0)
+ await page.getByRole('button', { name:'重置', exact:true }).click()
+ await expect(page.locator('.guide-project-title')).toHaveCount(count)
+ await page.locator('.system-pagination .btn-next').click()
+ await expect(page.locator('.guide-project-title')).toHaveCount(2)
+ await page.locator('.system-pagination .btn-prev').click()
+ await expect(page.locator('.guide-project-title')).toHaveCount(10)
+ if (count) {
+ await page.locator('.guide-project-actions').first().getByRole('button', { name:'更多' }).click()
+ await expect(page.getByRole('menuitem', { name:'项目详情', exact:true })).toBeVisible()
+ await shot('02-list-actions')
+ await page.getByRole('menuitem', { name:'项目详情', exact:true }).click()
+ await expect(page.getByRole('button', { name:'关闭项目详情' })).toBeVisible()
+ await page.getByRole('button', { name:'关闭项目详情' }).click()
+ await page.locator('.guide-project-actions').first().getByRole('button', { name:'进入编制', exact:true }).click()
+ await expect(page.locator('.ofd-view-editor')).toBeVisible({ timeout:60000 })
+ await shot('03-editor')
+ await theme('dark')
+ await shot('03-editor-dark')
+ await theme('military')
+ await page.locator('.ofd-top-actions [data-action="open-preview"]').click()
+ await expect(page.locator('.ofd-workspace-dialog.preview')).toBeVisible()
+ await shot('03-guide-preview')
+ console.log('Editor loaded', page.url())
+ }
+ await page.goto('http://127.0.0.1:6180/content/ofd/templates')
+ await expect(page.locator('.guide-tpl-card')).toHaveCount(4, { timeout:30000 })
+ await page.locator('[data-guide-tpl-action="select"]').nth(1).click()
+ await expect(page.locator('.guide-tpl-card').nth(1)).toHaveClass(/is-active/)
+ await shot('04-templates')
+ await page.locator('.guide-tpl-card').nth(1).getByRole('button', { name:'预览', exact:true }).click()
+ await expect(page.locator('.guide-tpl-modal')).toBeVisible()
+ await shot('05-template-preview')
+ await page.goto('http://127.0.0.1:6180/content/ofd/api')
+ await expect(page.locator('.ofd-api-layout')).toBeVisible({ timeout:30000 })
+ await shot('06-api')
+ await page.getByRole('button', { name:'执行调用', exact:true }).click()
+ await expect(page.locator('.ofd-api-result')).toBeVisible()
+ await expect(page.locator('.ofd-api-result header b')).toHaveText('200')
+ await shot('07-api-result')
+ for (const mode of ['technology','graphite','dark']) {
+ await theme(mode)
+ await shot(`08-api-${mode}`)
+ }
+ await page.setViewportSize({ width:1366, height:768 })
+ await theme('military')
+ await shot('09-api-laptop')
+ await page.goto('http://127.0.0.1:6180/content/ofd/templates')
+ await expect(page.locator('.guide-tpl-card')).toHaveCount(4)
+ await shot('10-templates-laptop')
+ await theme('dark')
+ await shot('11-templates-dark')
+ await page.goto('http://127.0.0.1:6180/content/ofd')
+ await expect(page.locator('.guide-project-list')).toBeVisible()
+ await shot('12-list-dark')
+ await theme('military')
+ await page.getByRole('button', { name:'新建指导书', exact:true }).click()
+ await expect(page.locator('.ofd-app')).toBeVisible()
+ await shot('13-create-wizard')
+ assert.equal((await context.request.get('http://192.168.31.168:6180/')).status(),200)
+ assert.deepEqual(errors, [], 'No unhandled browser exceptions')
+ checks.push({ name:'搜索/重置、详情、编制入口、模板选择/预览、GET 接口自检、主题切换、局域网入口', result:'PASS' })
+} catch(error) {
+ await page.screenshot({ path:path.join(report,'failure.png') }).catch(()=>{})
+ console.error('Browser errors:',errors)
+ throw error
+} finally {
+ fs.writeFileSync(path.join(report,'results.json'), JSON.stringify({ checks, errors },null,2))
+ await browser.close()
+}