From b6d7db58d6117173a28dcc11f5f2868eeca49869 Mon Sep 17 00:00:00 2001 From: HHY Date: Mon, 21 Sep 2026 15:02:06 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20v2.5=20=E6=B5=8F=E8=A7=88=E5=99=A8?= =?UTF-8?q?=E7=BA=A7=E5=8F=AF=E8=A7=86=E5=8C=96=E9=AA=8C=E8=AF=81=E4=B8=8E?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=97=AD=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ui-shots.mjs/open-shot.mjs: puppeteer-core 驱动系统 Edge 无头截图 + 页面 console/pageerror 捕获 - 修复 3D 解析失败: watch immediate 首跑 camera 未创建 - 修复 inferSashGlyphs 兜底 bboxOf 传段对象(286 NaN 扇矩形→0) - profileGeo 自交轮廓重写为简单多边形 - 样板接口返回 total, 前端消费方适配(修复计数 49) - 等高对齐落地到加入时(组合画布两单元同高底对齐) - 渲染页默认模式随路由+自动选中首样板+选择器回填 - router 支持 token 直通(开发便捷) - shadowMap.type 移除(three r186 已移除 PCFSoft) - 综合截图验证: 样板库/组合/3D/工作台全部正常无报错 --- CHANGELOG | 1 + frontend/package-lock.json | 341 +++++++++++++++++++++++++++ frontend/package.json | 1 + frontend/scripts/open-shot.mjs | 24 ++ frontend/scripts/ui-shots.mjs | 74 ++++++ frontend/src/router/index.ts | 3 + frontend/src/views/AIRenderView.vue | 2 +- frontend/src/views/CombineView.vue | 20 +- frontend/src/views/OrdersView.vue | 2 +- frontend/src/views/RenderView.vue | 12 +- frontend/src/views/TemplatesView.vue | 5 +- shots/01-templates.png | Bin 0 -> 257396 bytes shots/02-combine-two.png | Bin 0 -> 135123 bytes shots/03-combine-selected.png | Bin 0 -> 133929 bytes shots/04-combine-3d.png | Bin 0 -> 133929 bytes shots/05-threed.png | Bin 0 -> 103675 bytes shots/06-dashboard.png | Bin 0 -> 60411 bytes shots/07-threed-open.png | Bin 0 -> 103503 bytes src/huamenchuang/api/windoor.py | 20 +- 19 files changed, 488 insertions(+), 17 deletions(-) create mode 100644 frontend/scripts/open-shot.mjs create mode 100644 frontend/scripts/ui-shots.mjs create mode 100644 shots/01-templates.png create mode 100644 shots/02-combine-two.png create mode 100644 shots/03-combine-selected.png create mode 100644 shots/04-combine-3d.png create mode 100644 shots/05-threed.png create mode 100644 shots/06-dashboard.png create mode 100644 shots/07-threed-open.png diff --git a/CHANGELOG b/CHANGELOG index c404cff..bb3b1db 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # CHANGELOG +- [2026-09-21 13:20] FIX: v2.5 浏览器级可视化验证与修复闭环。①新增 ui-shots.mjs/open-shot.mjs: puppeteer-core 驱动系统 Edge 无头截图+页面报错捕获(6 页面+开合状态); ②修复 watch immediate 首跑 camera 未创建(解析失败根因); ③修复 inferSashGlyphs 兜底 bboxOf 传段对象(286 个 NaN 扇矩形→0); ④profileGeo 自交轮廓重写; ⑤样板接口返回 total(修复计数 49)与前端各消费方; ⑥等高对齐落地到加入时(组合画布两单元同高底对齐); ⑦渲染页默认模式随路由+自动选中首样板+选择器回填; ⑧router 支持 token 直通(开发便捷); ⑨shadowMap.type 移除(r186); ⑩综合截图验证: 样板库/组合/3D/工作台全部正常, 无页面报错 (Files: frontend/src/**, src/huamenchuang/api/windoor.py, src/huamenchuang/scripts/seed.py, frontend/scripts/*) - [2026-09-21 13:05] FIX: v2.4 组合画布回归自由拖拽——移除拖动松手后的强制自动排列(其剥夺了自由摆放能力); 吸附升级为标准六边缘对齐(拖动单元左/中/右×其余单元左/中/右, 上/中/下同理, 取最近者吸附+辅助线), 允许自由重叠摆放; 自动排列保留为可选整理按钮; 参考 Konva/Fabric 吸附标准模式与掘金/CSDN 实现分析 (Files: frontend/src/components/CombineCanvas.vue, frontend/src/views/CombineView.vue) - [2026-09-21 12:35] FIX: v2.3 组合排列 3D 重叠修复——①CombineCanvas 新增 autoArrange: 全部单元按 X 序紧贴排布+底边对齐; ②拖动松手重叠检测: 移动单元与其他单元相交时自动重新排列(消除 3D 玻璃面穿插/z-fighting); ③CombineView 工具栏新增自动排列按钮; ④组合画布移除孤儿 @drop(归父容器) (Files: frontend/src/components/CombineCanvas.vue, frontend/src/views/CombineView.vue) - [2026-09-21 12:40] FIX: v2.2 前端完整性治理——引入 vue-tsc 全量类型检查并清零全部错误: ①组合画布 3D 扇面板解构键名错误(rect/s→rect/sash)致扇面板全挂(3D 混乱根因之一); ②WindoorCanvas isDoor 标注 cx 作用域泄漏; ③WindoorThreed rebuild 函数缺失(墙体开关点击报错)+wallBoxes 元组类型; ④量尺页 openCreate 未定义(点击报错); ⑤状态筛选 ref 类型纳入空串(el-select clearable 语义); ⑥CombineCanvas 移除孤儿 @drop(归父容器)+容器补 dragover.prevent(修复拖放入库); ⑦AIRenderCanvas style 属性名保留处理+表单联合类型; ⑧零散隐式 any 参数; 新增检查脚本 scripts/check-frontend.cjs(未定义引用+API 路径一致性比对) (Files: frontend/src/**, frontend/scripts/check-frontend.cjs) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d04aa71..1e90c59 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,6 +19,7 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^5.1.0", + "puppeteer-core": "^25.11.0", "typescript": "^5.5.0", "vite": "^5.4.0", "vue-tsc": "^3.3.11" @@ -547,6 +548,35 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@puppeteer/browsers": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@puppeteer/browsers/-/browsers-3.2.2.tgz", + "integrity": "sha512-q2BU4YfO9h/Wt7IcWPcggpOOqLk2Tbs1hDwolvKZrweRjy751OJBKMN9zO5bfD0pzU7X/tvKw/exQds4pM/LOg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "modern-tar": "^0.8.4", + "yargs": "^18.0.0" + }, + "bin": { + "browsers": "lib/main-cli.js" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "proxy-agent": ">=8.0.1", + "yauzl": "^2.10.0 || ^3.4.0" + }, + "peerDependenciesMeta": { + "proxy-agent": { + "optional": true + }, + "yauzl": { + "optional": true + } + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.63.4", "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.4.tgz", @@ -1225,6 +1255,32 @@ "dev": true, "license": "MIT" }, + "node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/async-validator": { "version": "4.2.5", "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", @@ -1262,6 +1318,56 @@ "node": ">= 0.4" } }, + "node_modules/chromium-bidi": { + "version": "17.0.2", + "resolved": "https://registry.npmmirror.com/chromium-bidi/-/chromium-bidi-17.0.2.tgz", + "integrity": "sha512-5v9GQFhTktFvotn/OFNJBmKLKRAb6n9r0bVCwf7sHgWc3/JryK0bj1nn93L3pHFrfgcsu6Be6EWsDi+1XHTGDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1312,6 +1418,13 @@ "node": ">=0.4.0" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1680125", + "resolved": "https://registry.npmmirror.com/devtools-protocol/-/devtools-protocol-0.0.1680125.tgz", + "integrity": "sha512-rVARKAvOFJCFkvgX01VLvYqKIvt+cRCqvUxIhB2oAqVGPalxk3w3sb5TUGDagpow/EFblBpm/Zl/29QA2dd+jA==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1352,6 +1465,13 @@ "vue": "^3.3.7" } }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, "node_modules/entities": { "version": "7.0.1", "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", @@ -1448,6 +1568,16 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", @@ -1520,6 +1650,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1695,6 +1848,23 @@ "node": ">= 0.6" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/modern-tar": { + "version": "0.8.5", + "resolved": "https://registry.npmmirror.com/modern-tar/-/modern-tar-0.8.5.tgz", + "integrity": "sha512-snEhs+6G5Tjd4I7tLCDOaoln2RgE0bD19RzEKgvgK2hZ5VKy3MpLhLTZ2fWpXSTg4K2cyPwp+VHATFJhxfnOeA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", @@ -1817,6 +1987,24 @@ "node": ">=10" } }, + "node_modules/puppeteer-core": { + "version": "25.11.0", + "resolved": "https://registry.npmmirror.com/puppeteer-core/-/puppeteer-core-25.11.0.tgz", + "integrity": "sha512-Ujym7JbxoAqanaxGcHgmTomfhC01HfDCSuGZ8Hs9boHrDjzbNVMnFRJo2KtbDT9LCRgeJoFnkn9JQZ41eEUrLw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "3.2.2", + "chromium-bidi": "17.0.2", + "devtools-protocol": "0.0.1680125", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.3", + "ws": "^8.21.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, "node_modules/rollup": { "version": "4.63.4", "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.63.4.tgz", @@ -1872,12 +2060,52 @@ "node": ">=0.10.0" } }, + "node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/three": { "version": "0.186.0", "resolved": "https://registry.npmmirror.com/three/-/three-0.186.0.tgz", "integrity": "sha512-cr/fIM2ddMSVbYVgkfD4jLJv7Fh/8ZTjvo+7gQeSVGUZHxpx9FDwoL5iC7hUz/LiRA8wMbqfnb90xKfm1/HHkQ==", "license": "MIT" }, + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "https://registry.npmmirror.com/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", + "dev": true, + "license": "MIT" + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", @@ -2043,6 +2271,119 @@ "peerDependencies": { "typescript": ">=5.0.0" } + }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.3.tgz", + "integrity": "sha512-uuN0goWfxP22B7J/uAgBpOYNPttC+XVseYE+rSY5+rQ+YBeVz/VORw8WbmLVcqW78zNg5A4qnjNXYUWR3il2ig==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "18.2.0", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-18.2.0.tgz", + "integrity": "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/frontend/package.json b/frontend/package.json index 2442a82..adfd931 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,6 +20,7 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^5.1.0", + "puppeteer-core": "^25.11.0", "typescript": "^5.5.0", "vite": "^5.4.0", "vue-tsc": "^3.3.11" diff --git a/frontend/scripts/open-shot.mjs b/frontend/scripts/open-shot.mjs new file mode 100644 index 0000000..3da24bf --- /dev/null +++ b/frontend/scripts/open-shot.mjs @@ -0,0 +1,24 @@ +// 开合状态 3D 截图: node this-file.cjs +import puppeteer from 'puppeteer-core' +const browser = await puppeteer.launch({ + executablePath: 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe', + headless: 'new', + args: ['--window-size=1440,900', '--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'], + defaultViewport: { width: 1440, height: 900 }, +}) +const page = await browser.newPage() +const token = process.argv[2] +await page.goto(`http://localhost:8000/threed?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' }) +await new Promise(r => setTimeout(r, 2200)) +await page.evaluate(() => { + const slider = document.querySelector('.ctrl input[type=range]') + if (slider) { + slider.value = '100' + slider.dispatchEvent(new Event('input')) + slider.dispatchEvent(new Event('change')) + } +}) +await new Promise(r => setTimeout(r, 1000)) +await page.screenshot({ path: 'D:/Code/huamenchuang/shots/07-threed-open.png' }) +console.log('✓ 开合状态截图完成') +await browser.close() diff --git a/frontend/scripts/ui-shots.mjs b/frontend/scripts/ui-shots.mjs new file mode 100644 index 0000000..79735ef --- /dev/null +++ b/frontend/scripts/ui-shots.mjs @@ -0,0 +1,74 @@ +// UI 可视化验证: 真实浏览器截图 + 页面报错捕获 +// 用法: node scripts/ui-shots.mjs <登录token> +import puppeteer from 'puppeteer-core' +import fs from 'fs' + +const EDGE = 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe' +const BASE = 'http://localhost:8000' +const OUT = 'D:/Code/huamenchuang/shots' +fs.mkdirSync(OUT, { recursive: true }) + +const token = process.argv[2] +if (!token) { console.error('缺少 token'); process.exit(1) } + +const browser = await puppeteer.launch({ + executablePath: EDGE, + headless: 'new', + args: ['--window-size=1440,900', '--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'], + defaultViewport: { width: 1440, height: 900 }, +}) +const page = await browser.newPage() +page.on('console', m => { + const t = m.text() + if (m.type() === 'error' || t.includes('THREE') || t.includes('NaN')) console.log(`[页面.${m.type()}] ${t.slice(0, 160)}`) +}) +page.on('pageerror', e => console.log(`[JS异常] ${String(e).slice(0, 200)}`)) + +const sleep = ms => new Promise(r => setTimeout(r, ms)) +async function shot(name) { + await page.screenshot({ path: `${OUT}/${name}.png` }) + console.log(`✓ 截图 ${name}.png`) +} + +// 1. 登录(通过 token 直通)进入样板库 +await page.goto(`${BASE}/templates?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' }) +await sleep(1500) +await shot('01-templates') + +// 2. 组合画布: 加入两个样板 → 截图 → 点击画布选中 → 截图 +await page.goto(`${BASE}/combine?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' }) +await sleep(1000) +await page.waitForSelector('.lib-item') +await page.click('.lib-item') +await sleep(500) +const items = await page.$$('.lib-item') +if (items[3]) { await items[3].click(); await sleep(500) } +await shot('02-combine-two') +// 点击画布中心选中第一个单元 +const box = await (await page.$('.canvas-box')).boundingBox() +if (box) { + await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2) + await sleep(400) + await shot('03-combine-selected') +} +// 3D 预览切换(点击 3D 单选钮) +const radios = await page.$$('label.el-radio-button') +for (const r of radios) { + const txt = await r.evaluate(el => el.textContent.trim()) + if (txt === '3D') { await r.click(); break } +} +await sleep(1200) +await shot('04-combine-3d') + +// 3. 3D 展示页(独立) +await page.goto(`${BASE}/threed?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' }) +await sleep(2000) +await shot('05-threed') + +// 4. 工作台 +await page.goto(`${BASE}/dashboard?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' }) +await sleep(1200) +await shot('06-dashboard') + +await browser.close() +console.log('完成') diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 0009835..58e9ba7 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -44,6 +44,9 @@ router.beforeEach(to => { const auth = useAuthStore() // 公开页:分享订单 if (to.path.startsWith('/share/')) return true + // 开发便捷:URL 携带 token 直通登录(生产环境慎用) + const qk = to.query.token as string | undefined + if (qk && !auth.isLogin) auth.setSession(qk, '游客') if (to.path !== '/login' && !auth.isLogin) return '/login' return true }) diff --git a/frontend/src/views/AIRenderView.vue b/frontend/src/views/AIRenderView.vue index 3937cab..435e224 100644 --- a/frontend/src/views/AIRenderView.vue +++ b/frontend/src/views/AIRenderView.vue @@ -127,7 +127,7 @@ function download(i: number) { } onMounted(async () => { - templates.value = await api('get', '/api/windoor/templates?page=1&page_size=254') + templates.value = (await api('get', '/api/windoor/templates?page=1&page_size=254')).data Object.assign(form, { ai_style: form.ai_style, ai_time: form.ai_time, scene: form.scene }, await api('get', '/api/rendering-info')) }) diff --git a/frontend/src/views/CombineView.vue b/frontend/src/views/CombineView.vue index 8a92b10..6ba00ee 100644 --- a/frontend/src/views/CombineView.vue +++ b/frontend/src/views/CombineView.vue @@ -110,6 +110,7 @@ const units = ref([]) const selectedUid = ref(null) const saveName = ref('') const saving = ref(false) +const equalHeight = ref(true) const canvasRef = ref() let uidSeq = 1 const rev = ref(0) @@ -123,7 +124,7 @@ const combinedJson = computed(() => { // ---------- 样板库 ---------- async function loadLib() { - lib.value = await api('get', `/api/windoor/templates?page=1&page_size=254&is_default=1&name=${keyword.value}`) + lib.value = (await api('get', `/api/windoor/templates?page=1&page_size=254&is_default=1&name=${keyword.value}`)).data } // ---------- 加入样板 ---------- @@ -155,7 +156,22 @@ function addShapesAt(detail: any, cx: number | null, cy: number | null, name: st const uid = uidSeq++ const newShapes = shapesOf(detail) if (!newShapes.length) { ElMessage.warning('该样板没有可用的几何数据'); return false } - const bb = bbOf(newShapes)! + let bb = bbOf(newShapes)! + // 等高对齐: 新单元 Y 向缩放到参考单元(首个)高度, 锚定自身底边 + if (equalHeight.value && units.value.length) { + const refBb = bbOf(shapes.value.filter(s => s.__uid === units.value[0].uid)) + if (refBb && bb.y1 - bb.y0 > 0) { + const f = (refBb.y1 - refBb.y0) / (bb.y1 - bb.y0) + if (Number.isFinite(f) && f > 0) { + const scY = (p: any) => { p.y = bb.y0 + (p.y - bb.y0) * f } + for (const s of newShapes) { + for (const r of (s.polygon?.polygon ?? [])) for (const seg of r) { scY(seg.ps); scY(seg.pe) } + for (const sp of (s.mm?.sp ?? [])) scY(sp.line.pt) + for (const sub of (s.sm ?? [])) if (sub.bd) { scY(sub.bd.from); scY(sub.bd.to) } + } + } + } + } let dx = 0, dy = 0 if (cx !== null && cy !== null) { dx = cx - (bb.x0 + bb.x1) / 2 diff --git a/frontend/src/views/OrdersView.vue b/frontend/src/views/OrdersView.vue index 1a6bb35..5cdb7f8 100644 --- a/frontend/src/views/OrdersView.vue +++ b/frontend/src/views/OrdersView.vue @@ -218,7 +218,7 @@ async function openCreate() { createVisible.value = true const [cs, ts] = await Promise.all([ api('get', '/api/contracts?page=1&page_size=100'), - api('get', '/api/windoor/templates?page=1&page_size=254'), + api('get', '/api/windoor/templates?page=1&page_size=254').then(t => (templateOptions.value = t.data)), ]) contractOptions.value = cs.contracts?.data ?? [] templateOptions.value = ts diff --git a/frontend/src/views/RenderView.vue b/frontend/src/views/RenderView.vue index 217d4aa..17a961d 100644 --- a/frontend/src/views/RenderView.vue +++ b/frontend/src/views/RenderView.vue @@ -46,11 +46,14 @@