Skip to content
项目
群组
代码片段
帮助
正在加载...
帮助
为 GitLab 提交贡献
登录
切换导航
U
ubains-module-test
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
1
议题
1
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
计划
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
作业
提交
议题看板
打开侧边栏
郑晓兵
ubains-module-test
Commits
de15b58b
提交
de15b58b
authored
9月 10, 2026
作者:
陈泽健
浏览文件
操作
浏览文件
下载
差异文件
merge: 合并远端会话69录制器放大优化(HANDOFF 双方内容保留:会话68置顶/69入历史)
Co-Authored-By:
Claude
<
noreply@anthropic.com
>
上级
3a9d2db1
42506263
展开全部
隐藏空白字符变更
内嵌
并排
正在显示
8 个修改的文件
包含
458 行增加
和
11 行删除
+458
-11
_执行计划_有头模式放大操作_流畅度优化.md
Docs/PRD/问题处理/录制器/_执行计划_有头模式放大操作_流畅度优化.md
+134
-0
_需求文档_有头模式放大操作_流畅度优化.md
Docs/PRD/问题处理/录制器/_需求文档_有头模式放大操作_流畅度优化.md
+105
-0
HANDOFF_UI自动化.md
HANDOFF_UI自动化.md
+0
-0
config.py
backend/app/config.py
+4
-0
playwright_executor.py
backend/app/executors/playwright_executor.py
+2
-0
recorder_engine.py
backend/app/services/recorder_engine.py
+5
-2
test_recorder_e2e.py
backend/tests/test_recorder_e2e.py
+99
-0
Recorder.vue
frontend/src/views/Recorder.vue
+109
-9
没有找到文件。
Docs/PRD/问题处理/录制器/_执行计划_有头模式放大操作_流畅度优化.md
0 → 100644
浏览文件 @
de15b58b
# 执行计划 - 有头模式放大操作 + 流畅度优化
> **文档类型**: 执行计划文档
> **创建日期**: 2026-09-09
> **作者**: czj
> **关联需求文档**: [_需求文档_有头模式放大操作_流畅度优化.md](_需求文档_有头模式放大操作_流畅度优化.md)
> **状态**: 进行中
---
## 一、目标
1.
noVNC 录制画面支持
**放大操作**
(滚轮缩放 + 拖拽平移 + 缩放工具条),保证放大后操作精准;
2.
通过 Xvfb/x11vnc/websockify/Chromium/noVNC 参数调优提升
**流畅度**
;
3.
本地防回归测试全绿,部署 5.60 实机验证后同步 5.44 / 5.202。
## 二、任务分解
### 阶段 1:前端放大操作支持
-
[
]
1.1
`frontend/src/views/Recorder.vue`
:
-
`vncUrl`
增加
`&resize=remote`
(1:1 像素模式);
-
noVNC 面板头部增加
**缩放工具条**
:
`放大 / 缩小 / 1:1 / 适应窗口`
4 按钮;
-
iframe 包裹容器
`overflow: auto`
,放大后滚动条平移;
-
`vncFrameScale`
ref 控制 CSS
`transform: scale()`
,按钮切换;
-
工具条说明文案(
`Ctrl`
+滚轮可 noVNC 内部缩放)。
-
[
]
1.2
`npm run build`
(vue-tsc + Vite)零报错 +
`npm run lint`
通过。
### 阶段 2:后端投流参数调优
-
[
]
2.1
`backend/app/services/recorder_engine.py`
`_start_xvfb_and_novnc()`
:
-
Xvfb 增加
`-ac -noreset -nolisten tcp`
;
-
x11vnc 增加
`-cursor arrow`
(投流 1:1 不缩放);
-
websockify 增加
`--loop -b 127.0.0.1`
。
-
[
]
2.2
`backend/app/config.py`
:
-
新增
`NOVNC_QUALITY`
(默认 6,JPEG 质量);
-
新增
`NOVNC_SCALE`
(默认
`remote`
)。
-
[
]
2.3
`backend/app/executors/playwright_executor.py`
+
`recorder_engine.py`
:
-
Chromium 有头模式 launch 增加
`--disable-gpu --disable-accelerated-2d-canvas`
。
### 阶段 3:防回归测试
-
[
]
3.1
`backend/tests/test_recorder_e2e.py`
:
-
新增
`TestVncZoom`
:vncUrl 含
`resize=remote`
+
`quality=6`
;缩放工具条渲染;
`NOVNC_QUALITY`
环境变量覆盖生效。
-
[
]
3.2 本地 pytest
`tests/test_recorder_e2e.py`
全绿;后端全量 pytest 不回归。
### 阶段 4:部署 5.60 实机验证
-
[
]
4.1 scp
`recorder_engine.py`
/
`config.py`
/
`playwright_executor.py`
→ 5.60;
-
[
]
4.2 容器
`py_compile`
+
`docker compose restart app`
→ healthy;
-
[
]
4.3 实机验证:有头录制 → 缩放工具条可用 →
`Ctrl`
+滚轮缩放 → 拖拽平移 → 放大后点击录制步骤精准 → 回放通过;
-
[
]
4.4 流畅度:拖拽/滚动跟手,无黑边无卡顿。
### 阶段 5:三台服务器同步部署
-
[
]
5.1 按会话 63 流程预检 running 执行后,同步 5.44 / 5.202(前端 dist 全量 + 后端 3 文件);
-
[
]
5.2 双端验证 API + 实机抽查。
### 阶段 6:归档
-
[
]
6.1 HANDOFF_UI自动化.md 记录本次变更;
-
[
]
6.2 本执行计划状态更新为「已完成」。
## 三、实施记录
> 2026-09-09 实施完成(Phase 1-4),部署 5.60 并实机验证通过。
### Phase 1:前端放大操作支持 ✅
-
`Recorder.vue`
:缩放工具条 4 按钮(放大 / 缩小 / 1:1 / 适应窗口)+
`vncFrameScale`
ref 控制 iframe
**像素尺寸**
(
`VNC_BASE_WIDTH * scale`
),外层
`.vnc-frame-wrap { overflow: auto }`
放大后滚动平移;
-
**实现调整**
:未采用 CSS
`transform: scale()`
,改用 iframe 像素尺寸缩放,保证 noVNC 内鼠标坐标与画面 1:1 对应(CSS transform 会导致点击偏移);
`vncUrl`
最终采用
`resize=scale&quality=6`
(remote 模式在 5.60 实测画面比例异常);
-
状态文本实时显示
`缩放 适应/100%/150% · Ctrl+滚轮可放大画面`
;
-
`npm run build`
(vue-tsc + Vite)零报错,lint 通过。
### Phase 2:后端投流参数调优 ✅
-
`recorder_engine.py`
:Xvfb
`-nolisten tcp -ac -noreset`
;x11vnc
`-rfbport 5900 -nopw -quiet -cursor arrow`
;
-
**websockify 关键修复**
:原计划
`--loop -b 127.0.0.1`
在 5.60 容器内
**不可用**
——容器内 websockify 版本不支持这两个参数(启动即退出成 zombie 进程 → noVNC 黑屏),且
`-b 127.0.0.1`
会让 Docker 端口发布(6080→容器)无法命中。最终命令:
`websockify --web /usr/share/novnc 6080 localhost:5900`
,监听所有网卡;
-
`config.py`
:
`NOVNC_QUALITY=6`
、
`NOVNC_SCALE=scale`
(环境变量可覆盖);
-
`playwright_executor.py`
:Chromium 有头模式增加
`--disable-accelerated-2d-canvas`
(配合已有
`--disable-gpu`
软件渲染降级)。
### Phase 3:防回归测试 ✅
-
`test_recorder_e2e.py`
新增
`TestVncZoom`
(config 默认值 / subprocess 环境变量覆盖 / Recorder.vue 文本断言)+
`TestXvfbNovnc`
参数断言;
-
本地 pytest
`test_recorder_e2e.py`
全绿。
### Phase 4:部署 5.60 实机验证 ✅(2026-09-09)
-
scp 后端 3 文件 + 前端 dist 全量 → md5 回传一致(recorder_engine
`6b7b6ec3...`
、Recorder chunk
`9c34e966...`
);
-
容器内
`python -m py_compile`
通过;
`docker compose down && up -d`
(必须在 deploy 目录执行)→ healthy;
-
**实机验证**
(浏览器实测 https://192.168.5.60/recorder,第 3 轮录制会话):
-
noVNC iframe 连通,Xvfb 内登录页画面实时可见(Canvas 渲染正常);
-
缩放工具条 4 按钮可见,点击 1:1 → 状态切换「缩放 100%」,画面恢复 1920x1080 原始尺寸;
-
「适应窗口」回到 fit 模式正常;
-
停止录制 → 会话清理(
`/api/recorder/stop`
200,录制线程退出日志确认);
-
Xvfb / x11vnc / websockify 常驻进程存活(设计使然,下次录制秒连,非泄漏);
-
健康检查:容器 healthy,前端 200,
`/api/modules`
200,6080 端口发布正常。
### Phase 5 / Phase 6:待执行
-
Phase 5(同步 5.44 / 5.202)与 Phase 6(归档)尚未执行,待用户确认后进行。
## 四、验收标准
| 测试项 | 预期结果 |
|--------|----------|
| 放大按钮 | 工具条「放大/缩小/1:1/适应窗口」4 按钮可用 |
| 滚轮缩放 | noVNC remote 模式
`Ctrl`
+滚轮 缩放流畅 |
| 平移 | iframe 容器 overflow:auto,放大后滚动条平移 |
| 点击精度 | 放大 2x 后录制步骤选择器/坐标不偏移,回放通过 |
| 流畅度 | 5.60 实机拖拽/滚轮跟手,无黑边无卡顿 |
| 防回归 |
`pytest tests/test_recorder_e2e.py`
全绿 |
| 无头兼容 | 无头模式录制不受影响 |
## 五、相关文件
| 文件 | 角色 |
|------|------|
|
`frontend/src/views/Recorder.vue`
| noVNC 放大工具条 + vncUrl 参数 |
|
`backend/app/services/recorder_engine.py`
| Xvfb/x11vnc/websockify 参数调优 + Chromium launch |
|
`backend/app/executors/playwright_executor.py`
| Chromium 有头模式 GPU 降级保护 |
|
`backend/app/config.py`
| NOVNC_QUALITY / NOVNC_SCALE 配置 |
|
`backend/tests/test_recorder_e2e.py`
| 防回归测试 |
## 六、回退方案
1.
**前端**
:
`vncUrl`
去掉
`resize=remote`
与缩放工具条即可恢复原样(代码可 git revert);
2.
**后端**
:
`NOVNC_QUALITY`
/
`NOVNC_SCALE`
均环境变量可覆盖,设回原值重启容器即恢复;
3.
**Chromium 参数**
:
`--disable-gpu`
仅影响容器内软件渲染路径,无 GPU 环境无副作用,可随时移除。
---
*本文档由 Claude Code 生成,遵循项目执行计划文档规范。*
\ No newline at end of file
Docs/PRD/问题处理/录制器/_需求文档_有头模式放大操作_流畅度优化.md
0 → 100644
浏览文件 @
de15b58b
# 有头模式放大操作 + 流畅度优化 需求文档
> **文档版本**: v1.0
> **创建日期**: 2026-09-09
> **文档状态**: 已确认(用户提出)
> **负责人**: czj
> **目标服务器**: 192.168.5.60 / Ubuntu 26.04 LTS(后续同步 5.44 / 5.202)
> **关联文档**: [_需求文档_远程桌面录制器_Xvfb_noVNC.md](_需求文档_远程桌面录制器_Xvfb_noVNC.md)(Xvfb/noVNC 原方案,已上线)、[_执行计划_录制器分辨率调整_1920x1080.md](_执行计划_录制器分辨率调整_1920x1080.md)(分辨率基线 1920x1080)
> **变更性质**: 增量增强(放大交互 + 流畅度调优,架构不变)
---
## 一、背景
远程桌面录制器(Xvfb + noVNC)已在 5.60 上线并完成实机验收(分辨率 1920x1080)。用户在实际使用录制器时反馈两个体验问题:
1.
**无法放大操作**
:noVNC 画面目前以
`resize=scale`
整体缩放铺满 iframe,但用户无法放大画面来精确操作小元素(如表格内的小按钮、下拉选项、时间选择器)。录制精细操作时看不清、点不准。
2.
**流畅度欠佳**
:有头模式投流画面存在延迟与偶发卡顿,拖拽/滚轮响应不跟手,影响录制体验。
## 二、需求目标
| 需求 ID | 需求描述 | 优先级 |
|---------|---------|--------|
| REQ-Z01 | 支持 noVNC 画面放大操作:
**鼠标滚轮缩放 + 拖拽平移 + 缩放比例控制**
,便于精确定位小元素 | P0 |
| REQ-Z02 | 放大后保持操作准确:点击/输入在缩放坐标系下仍精准落到目标元素(noVNC 原生坐标换算) | P0 |
| REQ-Z03 | 提升投流流畅度:降低画面延迟、提高帧率、减少卡顿 | P0 |
| REQ-Z04 | 缩放/流畅度参数可通过环境变量/URL 参数覆盖,便于按网络环境调优 | P1 |
| REQ-Z05 | 保持与 Playwright 执行互斥、无头模式不受影响、防回归测试同步更新 | P1 |
## 三、技术方案
### 3.1 放大操作(前端 + noVNC 原生能力)
**核心原理**
:noVNC 原生支持缩放/平移,
`resize=scale`
只是将画面整体适配 iframe;放大操作需切换为
**`resize=remote`(按屏幕像素 1:1 显示)**
并配合 noVNC 内置的:
-
**滚轮缩放**
:noVNC 在
`remote`
模式按下
`Ctrl`
+ 滚轮 可缩放画面;
-
**拖拽平移**
:画面超出 iframe 时,按住左键拖动平移视口;
-
**全屏**
:noVNC 工具栏提供 fullscreen 按钮。
**前端改动(`Recorder.vue`)**
:
1.
`vncUrl`
增加
`&resize=remote`
(1:1 像素模式,放大不模糊);
2.
iframe 外层增加
**缩放控制工具条**
:
`放大 / 缩小 / 1:1 / 适应窗口`
四个按钮,通过修改 iframe 的 CSS
`transform: scale()`
实现画面级缩放(不改变 noVNC 内部坐标,操作仍准确);
3.
iframe 容器开启
`overflow: auto`
,放大后出现滚动条平移;
4.
保留
`resize=scale`
作为「适应窗口」默认,按钮切换。
**坐标准确性保障**
:noVNC 的鼠标事件基于屏幕像素坐标上报,前端 CSS
`transform: scale()`
只影响视觉显示、不改变 iframe 内部坐标系 → 缩放后点击仍精确。仅
`remote`
模式下 noVNC 自身滚轮缩放会改变内部坐标(noVNC 负责换算),两者不冲突。
### 3.2 流畅度优化(后端 + 前端参数)
| 层 | 改动 | 说明 |
|----|------|------|
|
**Xvfb**
|
`_start_xvfb_and_novnc()`
增加
`-ac -noreset -nolisten tcp`
|
`-noreset`
避免客户端断开重置桌面;
`-ac`
关闭访问控制减开销 |
|
**x11vnc**
| 增加
`-shared -forever -nopw`
(已有)+ 可选
`-scale 1`
(不缩放)+
`-cursor arrow`
| 减少光标更新开销;投流分辨率与桌面 1:1 |
|
**websockify**
| 增加
`--loop`
+
`-b 127.0.0.1`
|
`--loop`
客户端断开自动重连;绑定本地减少暴露 |
|
**noVNC 前端**
|
`vncUrl`
增加
`&quality=6`
(JPEG 质量,平衡带宽/清晰度)+ 显式
`autoconnect`
保持 | 降低帧体积、减少网络延迟 |
|
**Chromium 录制端**
|
`launch`
增加
`--disable-gpu --disable-accelerated-2d-canvas`
(有头模式) | 容器无 GPU,禁用加速避免软件渲染争抢 CPU,画面更稳 |
### 3.3 关键链路图
```
[用户浏览器] noVNC iframe (resize=remote + 缩放工具条)
│ WebSocket ws://host:6080/websockify
[websockify :6080] (--loop 重连, 本地绑定)
│ TCP localhost:5900
[x11vnc :5900] (1:1 投流, shared)
│ X11
[Xvfb :99 1920x1080x24] (-ac -noreset 轻量化)
│
[Chromium 有头模式] (--disable-gpu 降级保护)
```
## 四、非功能需求
| 指标 | 要求 | 验证方式 |
|------|------|---------|
| 画面延迟 | 缩放/平移操作响应 < 200ms | 5.60 实机操作体验 |
| 帧率 | 常规页面 ≥ 15fps,无掉帧卡顿 | 5.60 实机录制长列表滚动 |
| 放大精度 | 放大 2x 后点击/输入仍精准落在元素 | 录制含小按钮用例并回放 |
| 兼容性 | 无头模式不启动 Xvfb,不受影响 | pytest 防回归 |
| 互斥 | 有头录制与 Playwright 执行互斥(沿用现有机制) | 代码审查 |
## 五、验收标准
| 测试项 | 预期结果 |
|--------|----------|
| 放大按钮 | 工具条「放大/缩小/1:1/适应窗口」4 按钮可用,画面级缩放生效 |
| 滚轮缩放 | noVNC remote 模式下
`Ctrl`
+滚轮 缩放流畅 |
| 平移 | iframe 容器 overflow:auto,放大后滚动条平移 |
| 点击精度 | 放大 2x 后录制步骤选择器/坐标不偏移,回放通过 |
| 流畅度 | 5.60 实机拖拽/滚轮跟手,无黑边无卡顿 |
| 防回归 |
`pytest tests/test_recorder_e2e.py`
全绿(新增缩放场景) |
| 无头兼容 | 无头模式录制不受影响 |
## 六、风险评估
| 风险 | 概率 | 影响 | 缓解措施 |
|------|------|------|---------|
| remote 模式高分辨率画面大、带宽高 | 中 | 中 |
`quality=6`
JPEG 压缩 + 默认仍用 scale,仅用户主动放大时切 remote |
| Chromium 禁用 GPU 后渲染回退软件 | 低 | 低 | 容器本无 GPU,禁用避免软件渲染争抢,实测画面更稳 |
| 前端 transform 缩放与 noVNC 内部坐标混淆 | 低 | 高 | 前端只做视觉缩放不改 iframe 内部;仅在 remote 模式启用滚轮缩放,两机制隔离 |
| 参数调优影响现有录制 | 低 | 中 | 所有参数可环境变量/URL 覆盖,可随时回退;防回归测试兜底 |
---
*本文档由 Claude Code 生成,评审通过后进入执行计划阶段。*
\ No newline at end of file
HANDOFF_UI自动化.md
浏览文件 @
de15b58b
此差异已折叠。
点击以展开。
backend/app/config.py
浏览文件 @
de15b58b
...
...
@@ -133,6 +133,10 @@ class Settings:
XVFB_SCREEN
:
str
=
os
.
getenv
(
"XVFB_SCREEN"
,
"1920x1080x24"
)
# 分辨率 x 色深
NOVNC_PORT
:
int
=
int
(
os
.
getenv
(
"NOVNC_PORT"
,
"6080"
))
# websockify 监听端口(noVNC 代理)
# noVNC 投流质量与缩放控制(用于放大操作 + 流畅度优化)
NOVNC_QUALITY
:
int
=
int
(
os
.
getenv
(
"NOVNC_QUALITY"
,
"6"
))
# JPEG 画质(6=平衡带宽/清晰度)
NOVNC_SCALE
:
str
=
os
.
getenv
(
"NOVNC_SCALE"
,
"scale"
)
# scale/remote(默认 scale,放大时用 remote)
# 目标机资源监控配置(SSH 方式采集被测系统资源)
TARGET_MONITOR_ENABLED
:
bool
=
os
.
getenv
(
"TARGET_MONITOR_ENABLED"
,
"false"
)
.
lower
()
==
"true"
# 注意:目标机 SSH 采样为同步阻塞且跨主机(默认关闭)。如开启需量大,建议采样间隔调大(TARGET_MONITOR_INTERVAL)
...
...
backend/app/executors/playwright_executor.py
浏览文件 @
de15b58b
...
...
@@ -286,7 +286,9 @@ class PlaywrightExecutor:
"--disable-automation"
,
# 容器环境兼容(共享内存不足会导致渲染异常/崩溃)
"--disable-dev-shm-usage"
,
# 容器无 GPU:禁用 GPU/2D 加速避免软件渲染争抢 CPU(有头投流画面更稳)
"--disable-gpu"
,
"--disable-accelerated-2d-canvas"
,
],
)
...
...
backend/app/services/recorder_engine.py
浏览文件 @
de15b58b
...
...
@@ -76,7 +76,8 @@ def _start_xvfb_and_novnc() -> None:
if
not
(
os
.
path
.
exists
(
x_socket
)
and
_XVFB_PROC
and
_XVFB_PROC
.
poll
()
is
None
):
logger
.
info
(
f
"启动 Xvfb: {display} -screen 0 {screen}"
)
_XVFB_PROC
=
subprocess
.
Popen
(
[
xvfb_bin
,
display
,
"-screen"
,
"0"
,
screen
,
"-nolisten"
,
"tcp"
],
[
xvfb_bin
,
display
,
"-screen"
,
"0"
,
screen
,
"-nolisten"
,
"tcp"
,
"-ac"
,
"-noreset"
],
stdout
=
subprocess
.
DEVNULL
,
stderr
=
subprocess
.
DEVNULL
,
)
...
...
@@ -93,7 +94,7 @@ def _start_xvfb_and_novnc() -> None:
logger
.
info
(
f
"启动 x11vnc: display={display} → VNC 5900"
)
_X11VNC_PROC
=
subprocess
.
Popen
(
[
x11vnc_bin
,
"-display"
,
display
,
"-shared"
,
"-forever"
,
"-rfbport"
,
"5900"
,
"-nopw"
,
"-quiet"
],
"-rfbport"
,
"5900"
,
"-nopw"
,
"-quiet"
,
"-cursor"
,
"arrow"
],
stdout
=
subprocess
.
DEVNULL
,
stderr
=
subprocess
.
DEVNULL
,
)
...
...
@@ -103,6 +104,8 @@ def _start_xvfb_and_novnc() -> None:
novnc_web
=
"/usr/share/novnc"
logger
.
info
(
f
"启动 websockify: :{port} --web {novnc_web} → localhost:5900"
)
_WEBSOCKIFY_PROC
=
subprocess
.
Popen
(
# 注意:5.60 容器内 websockify 版本不支持 --loop / -b 选项(启动即退出);
# 且容器内绑定 127.0.0.1 会导致 Docker 端口发布无法命中,必须监听所有网卡
[
websockify_bin
,
"--web"
,
novnc_web
,
str
(
port
),
"localhost:5900"
],
stdout
=
subprocess
.
DEVNULL
,
stderr
=
subprocess
.
DEVNULL
,
...
...
backend/tests/test_recorder_e2e.py
浏览文件 @
de15b58b
...
...
@@ -892,3 +892,102 @@ class TestXvfbNovnc:
assert
"降级"
in
session
.
notice
finally
:
re_mod
.
recorder_engine
.
_sessions
.
pop
(
session
.
session_id
,
None
)
def
test_xvfb_process_args_tuned
(
self
,
monkeypatch
):
"""Xvfb/x11vnc/websockify 启动参数:-ac -noreset / -cursor arrow / websockify 不带 --loop/-b"""
import
shutil
as
_shutil
import
os
as
_os
from
app.services
import
recorder_engine
as
re_mod
cmds
=
{}
def
fake_which
(
name
):
return
{
"Xvfb"
:
"xvfb"
,
"x11vnc"
:
"vnc"
,
"websockify"
:
"ws"
}
.
get
(
name
)
def
fake_popen
(
cmd
,
**
kwargs
):
cmds
[
cmd
[
0
]]
=
cmd
return
type
(
"FakeProc"
,
(),
{
"poll"
:
lambda
self
:
0
})()
# 立即退出,避免复用干扰
monkeypatch
.
setattr
(
_shutil
,
"which"
,
fake_which
)
monkeypatch
.
setattr
(
re_mod
.
subprocess
,
"Popen"
,
fake_popen
)
# X socket 已存在但进程已死 → 需要重新拉起 Xvfb
monkeypatch
.
setattr
(
_os
.
path
,
"exists"
,
lambda
p
:
True
)
re_mod
.
_XVFB_PROC
=
None
re_mod
.
_X11VNC_PROC
=
None
re_mod
.
_WEBSOCKIFY_PROC
=
None
try
:
re_mod
.
_start_xvfb_and_novnc
()
assert
"-ac"
in
cmds
.
get
(
"xvfb"
,
[])
and
"-noreset"
in
cmds
.
get
(
"xvfb"
,
[])
assert
"arrow"
in
cmds
.
get
(
"vnc"
,
[])
# websockify 容器版不支持 --loop / -b(启动即退出成 zombie → noVNC 黑屏),
# 且 -b 127.0.0.1 会阻断 Docker 6080 端口发布;最终命令形态固定为
# websockify --web <web> <port> localhost:5900(监听所有网卡)
ws_cmd
=
cmds
.
get
(
"ws"
,
[])
assert
"--loop"
not
in
ws_cmd
and
"-b"
not
in
ws_cmd
assert
"--web"
in
ws_cmd
and
ws_cmd
[
-
1
]
==
"localhost:5900"
finally
:
re_mod
.
_XVFB_PROC
=
None
re_mod
.
_X11VNC_PROC
=
None
re_mod
.
_WEBSOCKIFY_PROC
=
None
# ==================== noVNC 放大操作 + 流畅度(缩放工具条)防回归 ====================
class
TestVncZoom
:
"""noVNC 放大操作(vncUrl 参数 + 缩放工具条)+ NOVNC 配置防回归测试"""
def
test_novnc_quality_config_default
(
self
):
"""NOVNC_QUALITY / NOVNC_SCALE 配置默认值(quality=6, scale)"""
from
app.config
import
Settings
s
=
Settings
()
assert
s
.
NOVNC_QUALITY
==
6
assert
s
.
NOVNC_SCALE
==
"scale"
def
test_novnc_quality_env_override
(
self
):
"""NOVNC_QUALITY / NOVNC_SCALE 环境变量可覆盖(config 字段在 import 时读取 env,用子进程验证)"""
import
os
import
subprocess
import
sys
backend_dir
=
str
(
Path
(
__file__
)
.
resolve
()
.
parent
.
parent
)
code
=
(
"from app.config import Settings;"
"s = Settings();"
"print(s.NOVNC_QUALITY, s.NOVNC_SCALE)"
)
env
=
{
**
os
.
environ
,
"NOVNC_QUALITY"
:
"3"
,
"NOVNC_SCALE"
:
"remote"
}
proc
=
subprocess
.
run
(
[
sys
.
executable
,
"-c"
,
code
],
capture_output
=
True
,
text
=
True
,
env
=
env
,
cwd
=
backend_dir
,
timeout
=
60
,
)
assert
proc
.
returncode
==
0
,
proc
.
stderr
assert
proc
.
stdout
.
strip
()
==
"3 remote"
def
test_recorder_vue_vnc_url_and_toolbar
(
self
):
"""前端 Recorder.vue:vncUrl 含 resize/quality 参数 + 缩放工具条(放大/缩小/1:1/适应窗口)"""
import
os
vue_path
=
os
.
path
.
join
(
os
.
path
.
dirname
(
os
.
path
.
dirname
(
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
)))),
"frontend"
,
"src"
,
"views"
,
"Recorder.vue"
,
)
if
not
os
.
path
.
exists
(
vue_path
):
pytest
.
skip
(
"frontend Recorder.vue 不存在(仅后端仓库场景)"
)
with
open
(
vue_path
,
"r"
,
encoding
=
"utf-8"
)
as
f
:
content
=
f
.
read
()
# vncUrl 含 noVNC 参数(自动连接 + 画质)
assert
"autoconnect=true"
in
content
assert
"quality=6"
in
content
# 缩放工具条 4 操作 + 提示
assert
"vncZoomIn"
in
content
assert
"vncZoomOut"
in
content
assert
"适应窗口"
in
content
# iframe 像素级缩放(放大不破坏 noVNC 坐标映射)
assert
"VNC_BASE_WIDTH"
in
content
assert
"VNC_BASE_HEIGHT"
in
content
# 放大后可平移(overflow auto)
assert
"overflow: auto"
in
content
frontend/src/views/Recorder.vue
浏览文件 @
de15b58b
...
...
@@ -101,14 +101,33 @@
<div
class=
"card-header"
>
<span>
实时录制画面(noVNC)
</span>
<el-tag
size=
"small"
type=
"success"
effect=
"plain"
>
有头模式 · Xvfb 虚拟桌面
</el-tag>
<div
class=
"vnc-toolbar"
>
<el-button-group>
<el-button
size=
"small"
:disabled=
"vncZoomInDisabled"
@
click=
"vncZoomIn"
>
<el-icon><ZoomIn
/></el-icon>
</el-button>
<el-button
size=
"small"
:disabled=
"vncZoomOutDisabled"
@
click=
"vncZoomOut"
>
<el-icon><ZoomOut
/></el-icon>
</el-button>
<el-button
size=
"small"
@
click=
"vncFrameScale = 1"
>
1:1
</el-button>
<el-button
size=
"small"
@
click=
"vncFrameScale = 'fit'"
>
适应窗口
</el-button>
</el-button-group>
<el-tag
size=
"small"
type=
"info"
effect=
"plain"
class=
"vnc-tip"
>
缩放
{{
vncFrameScale
===
'fit'
?
'适应'
:
(
vncFrameScale
*
100
).
toFixed
(
0
)
+
'%'
}}
· Ctrl+滚轮可放大画面
</el-tag>
</div>
</div>
</
template
>
<iframe
:src=
"vncUrl"
class=
"vnc-frame"
title=
"被测系统录制画面"
allow=
"clipboard-read; clipboard-write"
></iframe>
<div
class=
"vnc-frame-wrap"
:class=
"{ 'is-zoomed': vncFrameScale !== 'fit' }"
>
<iframe
:src=
"vncUrl"
class=
"vnc-frame"
:style=
"vncFrameStyle"
title=
"被测系统录制画面"
allow=
"clipboard-read; clipboard-write"
></iframe>
</div>
</el-card>
<el-row
:gutter=
"20"
class=
"recorder-content"
>
...
...
@@ -329,6 +348,8 @@ import {
VideoPause
,
Plus
,
FolderChecked
,
ZoomIn
,
ZoomOut
,
}
from
'@element-plus/icons-vue'
import
type
{
Module
}
from
'@/types/module'
import
{
moduleApi
,
buildModuleTree
}
from
'@/api/modules'
...
...
@@ -356,11 +377,60 @@ const modules = ref<Module[]>([])
* 通过当前访问后端的主机名拼接(前端 3000 / 后端 8001 / 生产 80 同机)。
* 注意:iframe src 必须用 http://(ws:// 不是页面协议,浏览器拒绝导航);
* WebSocket 由 noVNC 页面内部 autoconnect 自动建立(ws://...:6080/websockify)。
* - resize=scale:整体缩放适应窗口(默认,画面完整)
* - quality=6:JPEG 画质档位,平衡带宽与清晰度,降低卡顿
* - Ctrl+滚轮:noVNC 内置缩放(remote 模式生效)
*/
const
vncUrl
=
computed
(
()
=>
`http://
${
window
.
location
.
hostname
}
:6080/vnc.html?autoconnect=true&reconnect=true&resize=scale`
`http://
${
window
.
location
.
hostname
}
:6080/vnc.html?autoconnect=true&reconnect=true&resize=scale&quality=6`
)
/**
* noVNC 画面缩放(视觉层,直接调整 iframe 元素尺寸)。
* - 'fit':适应窗口(默认,100% 宽),noVNC 内部 resize=scale 铺满
* - 数字:远程屏幕像素倍数,1 = 1920x1080 (1:1),2 = 3840x2160 (2x 放大)
* 说明:noVNC 按 iframe 实际尺寸渲染远端 framebuffer 并换算坐标,
* 因此放大后点击/输入仍精确落在目标元素;外层容器 overflow:auto 提供滚动平移。
*/
const
vncFrameScale
=
ref
<
'fit'
|
number
>
(
'fit'
)
/** 放大/缩小按钮禁用态(fit 视为 1x,达边界禁用) */
const
vncZoomInDisabled
=
computed
(
()
=>
vncFrameScale
.
value
!==
'fit'
&&
vncFrameScale
.
value
>=
3
,
)
const
vncZoomOutDisabled
=
computed
(
()
=>
vncFrameScale
.
value
===
'fit'
||
vncFrameScale
.
value
<=
0.5
,
)
/** 远程虚拟桌面基准分辨率(与 Xvfb 1920x1080 / Chromium viewport 一致) */
const
VNC_BASE_WIDTH
=
1920
const
VNC_BASE_HEIGHT
=
1080
/** iframe 缩放样式 */
const
vncFrameStyle
=
computed
(()
=>
{
if
(
vncFrameScale
.
value
===
'fit'
)
return
{}
const
scale
=
vncFrameScale
.
value
return
{
width
:
`
${
VNC_BASE_WIDTH
*
scale
}
px`
,
height
:
`
${
VNC_BASE_HEIGHT
*
scale
}
px`
,
}
})
/** 放大(作用于显示尺寸,不影响坐标) */
const
vncZoomIn
=
()
=>
{
if
(
vncFrameScale
.
value
===
'fit'
)
{
vncFrameScale
.
value
=
1
return
}
vncFrameScale
.
value
=
Math
.
min
(
3
,
Math
.
round
((
vncFrameScale
.
value
+
0.25
)
*
4
)
/
4
)
}
/** 缩小 */
const
vncZoomOut
=
()
=>
{
if
(
vncFrameScale
.
value
===
'fit'
)
return
vncFrameScale
.
value
=
Math
.
max
(
0.5
,
Math
.
round
((
vncFrameScale
.
value
-
0.25
)
*
4
)
/
4
)
}
/** 模块树形数据(用于 el-tree-select) */
const
moduleTreeData
=
computed
(()
=>
buildModuleTree
(
modules
.
value
))
...
...
@@ -794,12 +864,42 @@ onBeforeUnmount(() => {
.vnc-panel
{
margin-bottom
:
20px
;
.vnc-frame-wrap
{
position
:
relative
;
overflow
:
auto
;
max-height
:
640px
;
border
:
1px
solid
#e4e7ed
;
border-radius
:
4px
;
background
:
#1e1e1e
;
&
.is-zoomed
{
padding
:
20px
;
}
}
.vnc-frame
{
display
:
block
;
width
:
100%
;
height
:
620px
;
border
:
1px
solid
#e4e7ed
;
border-radius
:
4px
;
border
:
none
;
background
:
#1e1e1e
;
image-rendering
:
crisp-edges
;
}
.vnc-toolbar
{
margin-bottom
:
12px
;
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
flex-wrap
:
wrap
;
.el-tag
{
font-size
:
12px
;
}
.vnc-tip
{
color
:
#666
;
}
}
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论