2026-05-21-00-58-25 增加WebGL降级并更新测试文档
This commit is contained in:
@@ -538,6 +538,7 @@ export function FusionThreeView({
|
|||||||
const modelPoseRef = useRef(modelPose);
|
const modelPoseRef = useRef(modelPose);
|
||||||
const [status, setStatus] = useState('准备融合 DICOM 与 STL');
|
const [status, setStatus] = useState('准备融合 DICOM 与 STL');
|
||||||
const [loadProgress, setLoadProgress] = useState(0);
|
const [loadProgress, setLoadProgress] = useState(0);
|
||||||
|
const [webglError, setWebglError] = useState<string | null>(null);
|
||||||
const [axisProjection, setAxisProjection] = useState<AxisProjection>(defaultAxisProjection);
|
const [axisProjection, setAxisProjection] = useState<AxisProjection>(defaultAxisProjection);
|
||||||
const axisProjectionSignatureRef = useRef(axisProjectionSignature(defaultAxisProjection));
|
const axisProjectionSignatureRef = useRef(axisProjectionSignature(defaultAxisProjection));
|
||||||
const resetFusionViewRef = useRef<() => void>(() => undefined);
|
const resetFusionViewRef = useRef<() => void>(() => undefined);
|
||||||
@@ -551,6 +552,7 @@ export function FusionThreeView({
|
|||||||
if (!container || !volume) return;
|
if (!container || !volume) return;
|
||||||
|
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
setWebglError(null);
|
||||||
setStatus('正在构建三维融合场景...');
|
setStatus('正在构建三维融合场景...');
|
||||||
setLoadProgress(8);
|
setLoadProgress(8);
|
||||||
setAxisProjection(defaultAxisProjection);
|
setAxisProjection(defaultAxisProjection);
|
||||||
@@ -568,7 +570,19 @@ export function FusionThreeView({
|
|||||||
camera.up.set(0, 0, 1);
|
camera.up.set(0, 0, 1);
|
||||||
camera.lookAt(0, 0, 0);
|
camera.lookAt(0, 0, 0);
|
||||||
|
|
||||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
let renderer: THREE.WebGLRenderer;
|
||||||
|
try {
|
||||||
|
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||||
|
} catch {
|
||||||
|
const message = '当前浏览器无法创建 WebGL 三维上下文';
|
||||||
|
setStatus(message);
|
||||||
|
setLoadProgress(100);
|
||||||
|
setWebglError('三维融合视图暂不可用,请检查浏览器硬件加速、显卡驱动或远程桌面图形支持。二维 DICOM 与逆向分割映射功能仍可继续使用。');
|
||||||
|
resetFusionViewRef.current = () => undefined;
|
||||||
|
return () => {
|
||||||
|
container.innerHTML = '';
|
||||||
|
};
|
||||||
|
}
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||||
renderer.setSize(width, height);
|
renderer.setSize(width, height);
|
||||||
renderer.localClippingEnabled = true;
|
renderer.localClippingEnabled = true;
|
||||||
@@ -897,6 +911,15 @@ export function FusionThreeView({
|
|||||||
return (
|
return (
|
||||||
<div className="relative h-full min-h-[520px] overflow-hidden rounded-3xl border border-slate-800 bg-black shadow-xl">
|
<div className="relative h-full min-h-[520px] overflow-hidden rounded-3xl border border-slate-800 bg-black shadow-xl">
|
||||||
<div ref={containerRef} className="absolute inset-0 cursor-grab active:cursor-grabbing" />
|
<div ref={containerRef} className="absolute inset-0 cursor-grab active:cursor-grabbing" />
|
||||||
|
{webglError && (
|
||||||
|
<div className="absolute inset-0 z-20 flex items-center justify-center bg-slate-950/92 px-8 text-center">
|
||||||
|
<div className="max-w-md rounded-2xl border border-amber-300/20 bg-slate-900/90 p-6 text-amber-50 shadow-2xl">
|
||||||
|
<AlertCircle className="mx-auto mb-3 text-amber-300" size={30} />
|
||||||
|
<p className="text-sm font-bold">三维融合视图无法启动</p>
|
||||||
|
<p className="mt-3 text-xs leading-6 text-amber-100/75">{webglError}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="pointer-events-none absolute left-4 top-4 rounded-xl border border-white/10 bg-black/60 px-3 py-2 text-[10px] font-mono text-white/60">
|
<div className="pointer-events-none absolute left-4 top-4 rounded-xl border border-white/10 bg-black/60 px-3 py-2 text-[10px] font-mono text-white/60">
|
||||||
{status}
|
{status}
|
||||||
</div>
|
</div>
|
||||||
@@ -953,6 +976,7 @@ function CutSectionPreview({
|
|||||||
}) {
|
}) {
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const modelPoseRef = useRef(modelPose);
|
const modelPoseRef = useRef(modelPose);
|
||||||
|
const [webglError, setWebglError] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
modelPoseRef.current = modelPose;
|
modelPoseRef.current = modelPose;
|
||||||
@@ -963,6 +987,7 @@ function CutSectionPreview({
|
|||||||
if (!container || !project || !volume) return;
|
if (!container || !project || !volume) return;
|
||||||
|
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
setWebglError(null);
|
||||||
let disposed = false;
|
let disposed = false;
|
||||||
let animationId = 0;
|
let animationId = 0;
|
||||||
const scene = new THREE.Scene();
|
const scene = new THREE.Scene();
|
||||||
@@ -975,7 +1000,15 @@ function CutSectionPreview({
|
|||||||
camera.up.set(0, 0, 1);
|
camera.up.set(0, 0, 1);
|
||||||
camera.lookAt(0, 0, 0);
|
camera.lookAt(0, 0, 0);
|
||||||
|
|
||||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
let renderer: THREE.WebGLRenderer;
|
||||||
|
try {
|
||||||
|
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||||
|
} catch {
|
||||||
|
setWebglError('当前浏览器无法创建 WebGL 三维上下文,STL 切面三维预览暂不可用。');
|
||||||
|
return () => {
|
||||||
|
container.innerHTML = '';
|
||||||
|
};
|
||||||
|
}
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||||
renderer.setSize(width, height);
|
renderer.setSize(width, height);
|
||||||
renderer.localClippingEnabled = true;
|
renderer.localClippingEnabled = true;
|
||||||
@@ -1218,6 +1251,13 @@ function CutSectionPreview({
|
|||||||
return (
|
return (
|
||||||
<div className="relative h-full min-h-[420px] overflow-hidden rounded-3xl border border-slate-800 bg-slate-950 shadow-2xl">
|
<div className="relative h-full min-h-[420px] overflow-hidden rounded-3xl border border-slate-800 bg-slate-950 shadow-2xl">
|
||||||
<div ref={containerRef} className="absolute inset-0 cursor-grab active:cursor-grabbing" />
|
<div ref={containerRef} className="absolute inset-0 cursor-grab active:cursor-grabbing" />
|
||||||
|
{webglError && (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center px-6 text-center">
|
||||||
|
<div className="rounded-2xl border border-amber-300/20 bg-slate-900/90 p-5 text-xs font-bold leading-6 text-amber-100">
|
||||||
|
{webglError}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{(!project || !volume) && (
|
{(!project || !volume) && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center text-xs font-bold text-white/40">
|
<div className="absolute inset-0 flex items-center justify-center text-xs font-bold text-white/40">
|
||||||
正在载入 STL 切面...
|
正在载入 STL 切面...
|
||||||
|
|||||||
53
工程分析/实现方案-2026-05-21-00-58-25.md
Normal file
53
工程分析/实现方案-2026-05-21-00-58-25.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# 实现方案-2026-05-21-00-58-25
|
||||||
|
|
||||||
|
实现方案文档路径:`工程分析/实现方案-2026-05-21-00-58-25.md`
|
||||||
|
|
||||||
|
## 修改目标
|
||||||
|
|
||||||
|
- 对系统进行静态、构建、API 与浏览器交互测试。
|
||||||
|
- 根据测试结果修复真实影响使用的问题。
|
||||||
|
- 更新软著说明书、登记表、代码汇总、素材清单,并重新生成说明书截图和 Word 文档。
|
||||||
|
- 按项目工作流提交代码与工程分析文档,软著材料不提交。
|
||||||
|
|
||||||
|
## 涉及路径
|
||||||
|
|
||||||
|
- 可能涉及:`WebSite/server.ts`、`WebSite/src/components/*`、`WebSite/src/lib/*`、`WebSite/src/types.ts`。
|
||||||
|
- 工程分析:`工程分析/需求分析-2026-05-21-00-58-25.md`、`工程分析/实现方案-2026-05-21-00-58-25.md`、`工程分析/测试方案-2026-05-21-00-58-25.md`、`工程分析/经验记录.md`。
|
||||||
|
- 软著材料:`新撰写软著文档/`,仅本地更新,不纳入 Gitea。
|
||||||
|
|
||||||
|
## 技术路线
|
||||||
|
|
||||||
|
1. 运行 `npm run lint`、`npm run build`、API 健康检查与关键接口抽查。
|
||||||
|
2. 使用浏览器自动化访问登录、总体概况、项目库、逆向工作区、系统管理等核心路径,观察控制台错误与页面状态。
|
||||||
|
3. 对发现的问题做小范围修复,避免引入新的数据结构风险。
|
||||||
|
4. 重新运行测试并部署。
|
||||||
|
5. 使用最新界面重新截取说明书图片,更新 Markdown 与 docx。
|
||||||
|
|
||||||
|
## 执行步骤
|
||||||
|
|
||||||
|
- 确认服务、依赖、脚本和软著材料现状。
|
||||||
|
- 执行静态与构建测试。
|
||||||
|
- 执行浏览器回归测试和页面截图。
|
||||||
|
- 修复测试中发现的缺陷。
|
||||||
|
- 重新生成软著文档和图片。
|
||||||
|
- 追加经验记录,精确暂存并提交代码/工程文档。
|
||||||
|
- 重新部署并验证服务。
|
||||||
|
|
||||||
|
## 兼容性与回滚方案
|
||||||
|
|
||||||
|
- 只修改明确发现的问题和文档内容,不改变默认医学数据。
|
||||||
|
- 如某项修复造成构建失败,回退该小范围 patch 并记录原因。
|
||||||
|
- 软著材料不提交,必要时可从本地历史文件恢复。
|
||||||
|
|
||||||
|
## 预计文件变更
|
||||||
|
|
||||||
|
- 新增本次工程分析三件套。
|
||||||
|
- 追加 `工程分析/经验记录.md`。
|
||||||
|
- 视测试结果修改少量前后端文件。
|
||||||
|
- 本地更新 `新撰写软著文档/` 下 Markdown、docx 和图片。
|
||||||
|
|
||||||
|
## 提交与部署策略
|
||||||
|
|
||||||
|
- Gitea commit 只包含本次代码修复和工程分析文档。
|
||||||
|
- Commit message 包含 `2026-05-21-00-58-25` 与简要描述。
|
||||||
|
- 使用 `tmux` 会话 `revoxelseg-dicom` 重启 `npm run serve -- --host 0.0.0.0 --port 4000`。
|
||||||
63
工程分析/测试方案-2026-05-21-00-58-25.md
Normal file
63
工程分析/测试方案-2026-05-21-00-58-25.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# 测试方案-2026-05-21-00-58-25
|
||||||
|
|
||||||
|
测试方案文档路径:`工程分析/测试方案-2026-05-21-00-58-25.md`
|
||||||
|
|
||||||
|
## 静态检查
|
||||||
|
|
||||||
|
- `cd WebSite && npm run lint`
|
||||||
|
- 检查 TypeScript 类型、组件属性和 API 调用签名。
|
||||||
|
|
||||||
|
## 构建检查
|
||||||
|
|
||||||
|
- `cd WebSite && npm run build`
|
||||||
|
- 确认 Vite 生产构建无错误。
|
||||||
|
|
||||||
|
## 关键业务场景验证
|
||||||
|
|
||||||
|
- 登录、总体概况、项目库三类数据页、逆向工作区、系统管理工作区可访问。
|
||||||
|
- 项目库导入 DICOM/STL 的入口、覆盖提醒、进度/失败反馈可见。
|
||||||
|
- 逆向分割结果视图、二维映射视图、Overlay Label Map、位置重置和切片导航布局符合当前功能。
|
||||||
|
- 用户管理新增、编辑、改密、冲突提示和删除限制逻辑无明显异常。
|
||||||
|
|
||||||
|
## 医学影像数据相关边界验证
|
||||||
|
|
||||||
|
- 默认项目有 DICOM/STL 时可预览,不存在资产时给出导入提示。
|
||||||
|
- 大体积/压缩包导入失败时主操作区保留错误,不造成浏览器无反馈。
|
||||||
|
- 导出项目及结果响应头文件名符合“项目名_时间”语义。
|
||||||
|
|
||||||
|
## 软著材料验证
|
||||||
|
|
||||||
|
- 说明书每个 `## X.` 章节均有图片引用。
|
||||||
|
- 图片文件存在且为最新重新拍摄。
|
||||||
|
- 代码汇总 Markdown 行数不少于 1600 行。
|
||||||
|
- 能重新生成 `.docx` 文件。
|
||||||
|
|
||||||
|
## 部署验证
|
||||||
|
|
||||||
|
- `http://127.0.0.1:4000/api/health`
|
||||||
|
- `http://127.0.0.1:4000/`
|
||||||
|
|
||||||
|
## Git/Gitea 备份验证
|
||||||
|
|
||||||
|
- 只暂存本次代码和工程分析文档。
|
||||||
|
- 不暂存 `新撰写软著文档/`、`3279-STL.zip`、默认医学数据或历史无关删除。
|
||||||
|
- `git push` 成功后记录 commit。
|
||||||
|
|
||||||
|
## 风险与回归关注点
|
||||||
|
|
||||||
|
- 三维/WebGL 页面加载可能较慢,截图前需等待稳定。
|
||||||
|
- 软著截图必须避免局部裁剪和敏感信息暴露。
|
||||||
|
- 修复导入或缓存逻辑时要避免跨项目串用旧资产。
|
||||||
|
|
||||||
|
## 实际执行结果
|
||||||
|
|
||||||
|
- `npm run lint`:通过。
|
||||||
|
- `npm run build`:通过,仅保留 Vite 大包体积提示。
|
||||||
|
- `/api/health`:通过。
|
||||||
|
- 项目列表接口:确认默认项目与用户项目可读取。
|
||||||
|
- 临时 ZIP/STL 导入回归:创建临时项目、上传含 `tiny.stl` 的 ZIP、返回 `modelCount=1` 后删除临时项目,通过。
|
||||||
|
- 重名用户接口回归:新增 `admin` 同名账号返回 409 与 `账号已存在`,通过。
|
||||||
|
- 导出响应头抽查:`export-bundle?targets=stl` 返回 `filename*` 中文文件名,通过。
|
||||||
|
- 浏览器自动化:登录、项目库、DICOM 信息、3D 模型、逆向分割结果、逆向工作区、保存、导出面板、系统管理、退出均可访问;SwiftShader WebGL 模式无 React fatal 错误。
|
||||||
|
- WebGL 异常模式:验证三维融合视图显示降级提示,页面未崩溃。
|
||||||
|
- 软著材料:23 个章节、23 个图片引用、23 张 `chapter-*.png` 均为 1680x1050;说明书 docx 中 23 个图片位置;代码汇总 10810 行;系统使用视频已刷新为约 48 秒 MP4。
|
||||||
18
工程分析/经验记录.md
18
工程分析/经验记录.md
@@ -1441,3 +1441,21 @@ C. 解决问题方案
|
|||||||
D. 后续如何避免问题
|
D. 后续如何避免问题
|
||||||
|
|
||||||
所有文件导入、导出、保存这类长操作失败时,都要在主操作路径旁保留显眼错误卡,不能只写入边栏或短暂 toast。后端对 4xx/5xx 的可恢复业务错误应记录足够上下文,尤其是项目 ID、导入类型、文件数量和原始异常消息,方便从用户截图快速定位。
|
所有文件导入、导出、保存这类长操作失败时,都要在主操作路径旁保留显眼错误卡,不能只写入边栏或短暂 toast。后端对 4xx/5xx 的可恢复业务错误应记录足够上下文,尤其是项目 ID、导入类型、文件数量和原始异常消息,方便从用户截图快速定位。
|
||||||
|
|
||||||
|
## 2026-05-21-00-58-25 三维视图需要 WebGL 降级保护且软著截图要强制视口
|
||||||
|
|
||||||
|
A. 具体问题
|
||||||
|
|
||||||
|
自动化浏览器回归进入逆向工作区时,Headless Chrome 在默认图形参数下无法创建 WebGL 上下文,`FusionThreeView` 直接抛出 Three.js 渲染错误,导致工作区组件崩溃。软著截图第一次生成时浏览器实际截图尺寸为 1680x963,与素材清单中约定的 1680x1050 不一致。
|
||||||
|
|
||||||
|
B. 产生问题原因
|
||||||
|
|
||||||
|
三维融合视图和 STL 切面预览此前默认 `new THREE.WebGLRenderer` 一定成功,没有捕获无 GPU、远程桌面、浏览器禁用硬件加速或 Headless 图形实现异常。截图脚本只设置了 Chrome 窗口大小,没有通过 DevTools `Emulation.setDeviceMetricsOverride` 强制页面视口,因此截图高度受 Headless 浏览器可用视区影响。
|
||||||
|
|
||||||
|
C. 解决问题方案
|
||||||
|
|
||||||
|
在 `FusionThreeView` 与 `CutSectionPreview` 初始化 WebGLRenderer 时增加 try/catch,创建失败时显示三维视图不可用提示,并保留二维 DICOM、逆向分割映射、保存和导出等功能继续可用。软著截图改用 Chrome Headless + SwiftShader WebGL 参数,并通过 DevTools 强制 1680x1050 视口;截图后用文件尺寸、章节图片数量、Word drawing 数量和代码汇总行数做校验。
|
||||||
|
|
||||||
|
D. 后续如何避免问题
|
||||||
|
|
||||||
|
所有 WebGL/Three.js 视图都应把渲染器创建失败视为可恢复状态,不能让单个三维画布拖垮整个工作区。自动生成软著或验收截图时,除了设置浏览器窗口,还必须设置设备视口,并在生成后校验图片尺寸、非空像素、章节数量、图片引用和 docx 内嵌图片位置数量。
|
||||||
|
|||||||
51
工程分析/需求分析-2026-05-21-00-58-25.md
Normal file
51
工程分析/需求分析-2026-05-21-00-58-25.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# 需求分析-2026-05-21-00-58-25
|
||||||
|
|
||||||
|
开始时间:2026-05-21-00-58-25
|
||||||
|
|
||||||
|
## 原始需求摘要
|
||||||
|
|
||||||
|
用户要求对当前程序进行细致测试,自主发现并修复问题;同时更新 `新撰写软著文档/` 中的所有材料,按最新功能重新细化说明,并重新拍摄软著说明书中的界面图片。
|
||||||
|
|
||||||
|
## 业务目标
|
||||||
|
|
||||||
|
- 提高当前系统在项目库、逆向工作区、导入导出、用户管理和医学影像/模型预览场景下的稳定性。
|
||||||
|
- 对自动测试、构建和浏览器交互进行回归检查,发现影响真实使用的问题后直接修复。
|
||||||
|
- 让软著说明书、登记表、代码汇总、素材清单和说明书截图与最新功能保持一致。
|
||||||
|
|
||||||
|
## 输入与输出
|
||||||
|
|
||||||
|
- 输入:
|
||||||
|
- 现有前后端代码与运行服务。
|
||||||
|
- 默认演示数据 `Head_CT_DICOM/`、`Head_CT_ReConstruct/`。
|
||||||
|
- 现有软著材料目录 `新撰写软著文档/`。
|
||||||
|
- 输出:
|
||||||
|
- 必要的程序修复。
|
||||||
|
- 本次工程分析三件套与经验记录追加。
|
||||||
|
- 更新后的软著 Markdown/Word/图片材料,但软著材料不纳入 Gitea 提交。
|
||||||
|
- 重新部署并验证后的服务。
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
|
||||||
|
- `WebSite/` 前后端程序、测试和构建流程。
|
||||||
|
- 项目库、逆向工作区、导入导出、软著截图所依赖的页面展示。
|
||||||
|
- `新撰写软著文档/` 下的说明书、登记表、代码汇总、素材清单和截图。
|
||||||
|
|
||||||
|
## 关键约束
|
||||||
|
|
||||||
|
- 必须遵循 `工程分析/代码编纂工作流.md`。
|
||||||
|
- 不提交软著撰写相关内容到 Gitea。
|
||||||
|
- 不混入历史无关删除、大型运行态产物、用户压缩包或默认医学数据的非必要变化。
|
||||||
|
- 修复时优先沿用现有 React/Express/TypeScript 结构。
|
||||||
|
|
||||||
|
## 风险点
|
||||||
|
|
||||||
|
- 大体积 DICOM/STL/ZIP 导入和三维预览容易造成浏览器内存压力。
|
||||||
|
- 软著截图可能包含敏感患者字段,截图前需尽量使用系统页面或脱敏内容。
|
||||||
|
- 当前工作树已有历史删除与未跟踪文件,提交时必须精确暂存。
|
||||||
|
- 若浏览器自动化无法加载 WebGL 或服务未启动,截图和交互验证需要先部署。
|
||||||
|
|
||||||
|
## 默认假设
|
||||||
|
|
||||||
|
- 用户授权本轮自主测试并修复发现的问题。
|
||||||
|
- `新撰写软著文档/` 可在本地更新,但不做 Gitea 备份提交。
|
||||||
|
- 默认部署端口仍为 `4000`,tmux 会话优先使用 `revoxelseg-dicom`。
|
||||||
Reference in New Issue
Block a user