fix: v2.2 前端完整性治理(vue-tsc 全量清零)
- 组合画布 3D 扇面板解构键名错误(rect/s→rect/sash)——3D 混乱根因之一 - WindoorCanvas isDoor 标注 cx 作用域泄漏修复 - WindoorThreed rebuild 函数定义补全(墙体开关可用) - 量尺页 openCreate 补定义(点击不再报错) - 状态筛选 ref 类型纳入空串语义(4 页面) - CombineCanvas 孤儿 @drop 移除+容器 dragover.prevent(拖放入库修复) - AIRenderCanvas style 属性名保留处理+表单联合类型 - 零散隐式 any 参数补类型 - 新增 check-frontend.cjs: 未定义引用扫描+API 路径一致性比对 - 全量回归: 18 GET+4 POST+7 SPA 全部通过
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# CHANGELOG
|
||||
|
||||
- [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)
|
||||
- [2026-09-21 12:25] FIX: v2.2 修复 3D 截图为纯色空白——墙体尺寸单位错误(bw=1/S*0.72 误用模型单位≈2304, 而放置坐标是世界单位±0.5), 相机被包在数千单位宽的巨墙内只见背景色; 改为世界单位(侧墙 0.55, 上下墙覆盖窗洞±2.2), 墙体不投影(避免自身遮挡), Node 验证四墙尺寸全部 ≤2.2 世界单位 (Files: frontend/src/components/WindoorThreed.vue)
|
||||
- [2026-09-21 12:10] FIX: 3D"解析失败"根因修复+错误可见性——①根因: watch immediate 首跑时 camera 未创建而 build() 触碰 camera 抛 TypeError; 移除 build 内相机定位(每帧 loop 已定位); ②shadowMap.type 赋值整行移除(three r186 已移除 PCFSoft 常量, 默认即 PCFShadowMap, 杜绝弃用警告); ③解析失败时显示真实错误信息(info+console.error), 便于后续定位; 重建 dist 后 PCFSoft 赋值触发路径已清零 (Files: frontend/src/components/WindoorThreed.vue)
|
||||
- [2026-09-21 11:30] FIX: v2.1 3D 渲染问题根治——①profileGeo 外轮廓路径自相交(闭合斜边穿过槽口区域)导致三角剖分破面, 重写为正确顶点顺序的 U 槽简单多边形; ②inferSashGlyphs 兜底 bboxOf 误传段对象(应为点对象)产生 NaN 扇矩形(JSON 序列化为 null), 修正为有限值点集; ③型材截面挤出抽为共享模块 three/profileGeo.ts(geometryIsValid 校验器); ④新增 scripts/validate3d.cjs 全量验证工具: 254 样板×3700 网格×798 扇逐网格 NaN 检测全部通过; ⑤过小面板(窄条扇)为合法数据跳过渲染不视为失败 (Files: frontend/src/components/WindoorThreed.vue, frontend/src/three/profileGeo.ts, frontend/src/types/drawing.ts, frontend/scripts/validate3d.cjs)
|
||||
|
||||
Generated
+105
-1
@@ -20,7 +20,8 @@
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.0",
|
||||
"typescript": "^5.5.0",
|
||||
"vite": "^5.4.0"
|
||||
"vite": "^5.4.0",
|
||||
"vue-tsc": "^3.3.11"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
@@ -1009,6 +1010,35 @@
|
||||
"vue": "^3.2.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/language-core": {
|
||||
"version": "2.4.28",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.28.tgz",
|
||||
"integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/source-map": "2.4.28"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/source-map": {
|
||||
"version": "2.4.28",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.28.tgz",
|
||||
"integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@volar/typescript": {
|
||||
"version": "2.4.28",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.28.tgz",
|
||||
"integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/language-core": "2.4.28",
|
||||
"path-browserify": "^1.0.1",
|
||||
"vscode-uri": "^3.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.43",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.43.tgz",
|
||||
@@ -1065,6 +1095,22 @@
|
||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/language-core": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.3.11.tgz",
|
||||
"integrity": "sha512-QJmpliwAVpC/OxubIByPAhNzsQPRc8/gxlN2qnVzVfIMjMDz/9RnXRFoetjz5yEgXVXyp4LqhXq3V53PjmNzFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/language-core": "2.4.28",
|
||||
"@vue/compiler-dom": "^3.5.0",
|
||||
"@vue/shared": "^3.5.0",
|
||||
"alien-signals": "^3.2.1",
|
||||
"muggle-string": "^0.4.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"picomatch": "^4.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.43",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.43.tgz",
|
||||
@@ -1172,6 +1218,13 @@
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/alien-signals": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-3.2.1.tgz",
|
||||
"integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/async-validator": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
|
||||
@@ -1648,6 +1701,13 @@
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/muggle-string": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz",
|
||||
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.19",
|
||||
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.19.tgz",
|
||||
@@ -1672,12 +1732,32 @@
|
||||
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/path-browserify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
|
||||
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.7.tgz",
|
||||
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pinia": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.3.1.tgz",
|
||||
@@ -1872,6 +1952,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-uri": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.2.0.tgz",
|
||||
"integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.43",
|
||||
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.43.tgz",
|
||||
@@ -1939,6 +2026,23 @@
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-tsc": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.3.11.tgz",
|
||||
"integrity": "sha512-gOb0B9rtU2+f1dszwPqSH5kAieIF9ReeLhD3kSRNHv5WZZUQz/JdVXW0RTdqhNTMlQkqKzrTTviqKr/4FYZraQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/typescript": "2.4.28",
|
||||
"@vue/language-core": "3.3.11"
|
||||
},
|
||||
"bin": {
|
||||
"vue-tsc": "bin/vue-tsc.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.0",
|
||||
"typescript": "^5.5.0",
|
||||
"vite": "^5.4.0"
|
||||
"vite": "^5.4.0",
|
||||
"vue-tsc": "^3.3.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env node
|
||||
/** 前端静态检查: ①SFC 脚本未定义引用扫描 ②前端 API 路径与后端路由一致性比对 */
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const SRC = path.join(ROOT, 'src');
|
||||
const API = path.join(ROOT, '..', 'src', 'huamenchuang', 'api');
|
||||
|
||||
const BUILTIN = new Set(['if','for','while','switch','catch','return','function','typeof','new','delete','void',
|
||||
'fetch','setTimeout','setInterval','clearTimeout','clearInterval','parseInt','parseFloat','isNaN','isFinite',
|
||||
'Number','String','Boolean','Array','Object','JSON','Math','Date','Promise','console','window','document',
|
||||
'localStorage','sessionStorage','navigator','location','alert','requestAnimationFrame','cancelAnimationFrame',
|
||||
'URL','Blob','File','FileReader','Error','TypeError','structuredClone','crypto','atob','btoa','history',
|
||||
'defineProps','defineEmits','defineExpose','withDefaults','require','module','Infinity','NaN','encodeURIComponent','decodeURIComponent']);
|
||||
const VUE = new Set(['ref','reactive','computed','watch','watchEffect','onMounted','onBeforeUnmount','onUnmounted','nextTick',
|
||||
'createApp','useRouter','useRoute','toRefs','toRef','unref','isRef','provide','inject','defineStore','storeToRefs']);
|
||||
|
||||
function walk(dir, out = []) {
|
||||
for (const f of fs.readdirSync(dir)) {
|
||||
const p = path.join(dir, f);
|
||||
if (fs.statSync(p).isDirectory()) walk(p, out);
|
||||
else out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ---- ① SFC 未定义引用 ----
|
||||
console.log('=== ① SFC 脚本未定义引用扫描 ===');
|
||||
const vueFiles = walk(SRC).filter(f => f.endsWith('.vue'));
|
||||
let issues = 0;
|
||||
for (const f of vueFiles) {
|
||||
const src = fs.readFileSync(f, 'utf8');
|
||||
const m = src.match(/<script setup lang="ts">([\s\S]*?)<\/script>/);
|
||||
if (!m) continue;
|
||||
const script = m[1];
|
||||
const declared = new Set([...BUILTIN, ...VUE]);
|
||||
// import 绑定
|
||||
for (const im of script.matchAll(/import\s+([^;]+?)\s+from/g)) {
|
||||
for (const bind of im[1].split(',')) {
|
||||
const name = bind.trim().split(/\s+as\s+/).pop().trim().replace(/[{}]/g, '').trim();
|
||||
if (name) declared.add(name);
|
||||
}
|
||||
}
|
||||
// 声明
|
||||
for (const d of script.matchAll(/\b(?:function|const|let|var)\s+([A-Za-z_$][\w$]*)/g)) declared.add(d[1]);
|
||||
// 函数参数(粗略)
|
||||
for (const p of script.matchAll(/\(([^()]{0,60}?)\)\s*(?:=>|{)/g)) {
|
||||
for (const part of p[1].split(',')) {
|
||||
const name = part.trim().replace(/^[{[]/, '').split(':')[0].split('=')[0].trim();
|
||||
if (/^[A-Za-z_$][\w$]*$/.test(name)) declared.add(name);
|
||||
}
|
||||
}
|
||||
for (const p of script.matchAll(/\(\s*([A-Za-z_$][\w$]*)\s*(?::[^)=]+)?\)\s*=>/g)) declared.add(p[1]);
|
||||
// 调用点
|
||||
for (const c of script.matchAll(/(?<![.\w$])([A-Za-z_$][\w$]*)\s*\(/g)) {
|
||||
const name = c[1];
|
||||
if (!declared.has(name)) {
|
||||
const rel = path.relative(SRC, f);
|
||||
console.log(` ${rel}: 调用了未定义的 "${name}"`);
|
||||
issues++;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(issues === 0 ? ' ✓ 未发现未定义调用' : ` ✗ 共 ${issues} 处`);
|
||||
|
||||
// ---- ② API 路径一致性 ----
|
||||
console.log('\n=== ② 前端 API 路径 vs 后端路由 ===');
|
||||
const fe = walk(SRC).filter(f => f.endsWith('.vue') || f.endsWith('.ts'));
|
||||
const fePaths = new Set();
|
||||
for (const f of fe) {
|
||||
const src = fs.readFileSync(f, 'utf8');
|
||||
for (const mm of src.matchAll(/['"`](\/api\/[^'"`?]*)/g)) fePaths.add(mm[1]);
|
||||
}
|
||||
const bePaths = new Set();
|
||||
const beDir = API;
|
||||
for (const f of fs.readdirSync(beDir).filter(f => f.endsWith('.py'))) {
|
||||
const src = fs.readFileSync(path.join(beDir, f), 'utf8');
|
||||
const prefixM = src.match(/router = APIRouter\(prefix="([^"]*)"/);
|
||||
const prefix = prefixM ? prefixM[1] : '';
|
||||
for (const r of src.matchAll(/@router\.(get|post|put|delete)\("([^"]*)"/g)) {
|
||||
let p = prefix + r[2];
|
||||
p = p.replace(/\{[^}]*\}/g, 'X');
|
||||
bePaths.add(p);
|
||||
}
|
||||
}
|
||||
let missing = 0;
|
||||
for (const p of [...fePaths].sort()) {
|
||||
const norm = p.replace(/\?.*$/, '').replace(/\/\d+/g, '/X');
|
||||
if ([...bePaths].some(b => b === norm || norm.startsWith(b + '/'))) continue;
|
||||
const found = [...bePaths].some(b => norm === b || norm.startsWith(b));
|
||||
if (!found) { console.log(` 前端调用但后端无: ${p}`); missing++; }
|
||||
}
|
||||
console.log(missing === 0 ? ' ✓ 前端调用的接口后端全部存在' : ` ✗ ${missing} 个前端调用缺少后端路由`);
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="cc-wrap" ref="wrapRef">
|
||||
<canvas ref="cvRef" @wheel.prevent="onWheel" @mousedown="onDown" @mousemove="onMove"
|
||||
@mouseup="onUp" @mouseleave="onUp" @dblclick="reset" @dragover.prevent @drop="onDrop" />
|
||||
@mouseup="onUp" @mouseleave="onUp" @dblclick="reset" />
|
||||
<div class="hud">
|
||||
<span>{{ hint }}</span>
|
||||
<el-button size="small" text @click="reset">重置视图</el-button>
|
||||
|
||||
@@ -257,6 +257,7 @@ function drawFrame(
|
||||
ctx.lineWidth = 1.3
|
||||
ctx.strokeRect(bx0 + 2, by0 + 2, w - 4, h - 4)
|
||||
ctx.setLineDash([])
|
||||
const cx = (bx0 + bx1) / 2, cy = (by0 + by1) / 2
|
||||
if (g.wtf) {
|
||||
// 双向开启门:对角虚线交叉符号(对齐原站图纸样式)
|
||||
ctx.strokeStyle = 'rgba(70,76,82,0.7)'
|
||||
@@ -267,7 +268,6 @@ function drawFrame(
|
||||
ctx.stroke()
|
||||
} else {
|
||||
const ang = ((g.od || 90) * Math.PI) / 180
|
||||
const cx = (bx0 + bx1) / 2, cy = (by0 + by1) / 2
|
||||
const L = Math.min(w, h) * 0.32
|
||||
const ax = cx + Math.cos(ang) * L, ay = cy - Math.sin(ang) * L
|
||||
const wx = Math.cos(ang + Math.PI / 2) * L * 0.38
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<!-- 开合控制 -->
|
||||
<div class="ctrl" v-if="hasOpenable">
|
||||
<span>开合</span>
|
||||
<el-slider v-model="openPct" style="width: 150px" :min="0" :max="100" :format-tooltip="v => v + '%'" />
|
||||
<el-slider v-model="openPct" style="width: 150px" :min="0" :max="100" :format-tooltip="(v: number) => v + '%'" />
|
||||
<el-checkbox v-model="autoPlay" size="small">自动演示</el-checkbox>
|
||||
<el-button size="small" text @click="openPct = openPct > 0 ? 0 : 100">{{ openPct > 0 ? '全关' : '全开' }}</el-button>
|
||||
</div>
|
||||
@@ -110,6 +110,12 @@ function overlapArea(a: BBox, b: BBox): number {
|
||||
return w > 0 && h > 0 ? w * h : 0
|
||||
}
|
||||
|
||||
/** 型材面宽(世界单位) */
|
||||
function fwOf(shape: any, S: number): number {
|
||||
const ew = shape.fm?.ew ?? [60, 60, 60, 60]
|
||||
return Math.max(0.018, (ew.reduce((a: number, b: number) => a + b, 0) / ew.length) * S)
|
||||
}
|
||||
|
||||
/** 型材截面: 多腔体 + 玻璃槽(比例依据真实 DXF 截面学习: 深度46-90/面宽65-130/腔体3-6/壁厚≈深度1/30) */
|
||||
function barX(parent: THREE.Group, mat: THREE.Material, len: number, fw: number,
|
||||
depth: number, cx: number, cy: number, cz: number, chambers = 2) {
|
||||
@@ -166,6 +172,16 @@ function applyView() {
|
||||
orbit.phi = p.phi
|
||||
}
|
||||
|
||||
/** 重建场景(墙体开关等触发) */
|
||||
function rebuild() {
|
||||
try {
|
||||
if (props.drawingData) build(parseDrawing(props.drawingData))
|
||||
} catch (e) {
|
||||
console.error('[WindoorThreed] rebuild 失败:', e)
|
||||
info.value = '解析失败: ' + (e as Error).message
|
||||
}
|
||||
}
|
||||
|
||||
function build(drawing: DrawingData) {
|
||||
dispose()
|
||||
scene = new THREE.Scene()
|
||||
@@ -227,7 +243,7 @@ function build(drawing: DrawingData) {
|
||||
transparent: true, opacity: 0.4, roughness: 0.06, metalness: 0.1, side: THREE.DoubleSide,
|
||||
})
|
||||
: glassMat
|
||||
const fw = Math.max(0.018, fwOf(shape))
|
||||
const fw = Math.max(0.018, fwOf(shape, S))
|
||||
|
||||
const regionBBs: BBox[] = regions.map(r => bboxOf(regionPoints(r)))
|
||||
const used = new Set<number>()
|
||||
@@ -261,7 +277,7 @@ function build(drawing: DrawingData) {
|
||||
})
|
||||
|
||||
// 扇面板(可开合)
|
||||
panels.forEach(({ rect, s }, k) => {
|
||||
panels.forEach(({ rect, sash: s }, k) => {
|
||||
const w = (rect.x1 - rect.x0) * S, h = (rect.y1 - rect.y0) * S
|
||||
const cxr = ((rect.x0 + rect.x1) / 2 - cx) * S
|
||||
const cyr = ((rect.y0 + rect.y1) / 2 - cy) * S
|
||||
@@ -314,7 +330,7 @@ function build(drawing: DrawingData) {
|
||||
const hh = gh * S / 2 + 0.05 // 窗洞半高
|
||||
const side = 0.55 // 上下墙高度/左右墙宽度
|
||||
const wt = DEPTH * 3
|
||||
const wallBoxes: [number, number, number, number, number][] = [
|
||||
const wallBoxes: number[][] = [
|
||||
[-hw - side, hh, side * 2 + hw * 2, side], // 上
|
||||
[-hw - side, -hh - side, side * 2 + hw * 2, side], // 下
|
||||
[-hw - side, -hh, side, hh * 2], // 左
|
||||
|
||||
@@ -59,7 +59,12 @@ const progress = ref(0)
|
||||
const stageText = ref('')
|
||||
const variants = ref<{ label: string; style: string; time: string; seed: number }[]>([])
|
||||
const pickIdx = ref(0)
|
||||
const form = reactive({ ai_style: 'modern', ai_time: 'day', scene: 'indoor', seed: 1 })
|
||||
const form = reactive<{
|
||||
ai_style: 'modern' | 'chinese' | 'european' | 'nordic'
|
||||
ai_time: 'day' | 'dusk' | 'night'
|
||||
scene: 'indoor' | 'outdoor' | 'studio'
|
||||
seed: number
|
||||
}>({ ai_style: 'modern', ai_time: 'day', scene: 'indoor', seed: 1 })
|
||||
|
||||
const variantCvs: (HTMLCanvasElement | null)[] = []
|
||||
function setVariantRef(el: any, i: number) { variantCvs[i] = el as HTMLCanvasElement }
|
||||
|
||||
@@ -53,7 +53,7 @@ const rows = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const page_size = 15
|
||||
const status = ref<number | undefined>()
|
||||
const status = ref<number | '' | undefined>(undefined)
|
||||
const loading = ref(false)
|
||||
const createVisible = ref(false)
|
||||
const form = reactive({ title: '', description: '', order_no: '' })
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="canvas-box" @drop="onDrop" style="position:relative">
|
||||
<div class="canvas-box" @dragover.prevent @drop="onDrop" style="position:relative">
|
||||
<CombineCanvas ref="canvasRef" :shapes="shapes" @select="onSelect" @change="onCanvasChange" />
|
||||
</div>
|
||||
<div class="save-bar">
|
||||
@@ -98,7 +98,7 @@ import { parseDrawing } from '../types/drawing'
|
||||
import CombineCanvas from '../components/CombineCanvas.vue'
|
||||
import WindoorThreed from '../components/WindoorThreed.vue'
|
||||
|
||||
interface UnitMeta { uid: number; name: string; pic: string; templateId: number }
|
||||
interface UnitMeta { uid: number; name: string; pic: string; templateId: number; w: number; h: number }
|
||||
|
||||
const lib = ref<any[]>([])
|
||||
const keyword = ref('')
|
||||
@@ -145,7 +145,7 @@ function translate(shapes: any[], dx: number, dy: number) {
|
||||
}
|
||||
|
||||
function registerUnit(detail: any, uid: number, name: string) {
|
||||
units.value = [...units.value, { uid, name, pic: detail.pic_url || '', templateId: detail.id }]
|
||||
units.value = [...units.value, { uid, name, pic: detail.pic_url || '', templateId: detail.id, w: 0, h: 0 }]
|
||||
}
|
||||
|
||||
function addShapesAt(detail: any, cx: number | null, cy: number | null, name: string) {
|
||||
|
||||
@@ -70,7 +70,7 @@ const total = ref(0)
|
||||
const page = ref(1)
|
||||
const page_size = 15
|
||||
const keyword = ref('')
|
||||
const status = ref<number | undefined>()
|
||||
const status = ref<number | '' | undefined>(undefined)
|
||||
const loading = ref(false)
|
||||
const createVisible = ref(false)
|
||||
const customerOptions = ref<any[]>([])
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<el-carousel-item v-for="a in announcements.slice(0, 4)" :key="a.id">
|
||||
<div class="ann">
|
||||
<b>{{ a.name }}</b>
|
||||
<p>{{ a.desc }}</p>
|
||||
<p>{{ (a as any).desc }}</p>
|
||||
</div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
@@ -50,7 +50,7 @@
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="10">
|
||||
<el-card header="业务导航">
|
||||
<el-menu class="nav-menu" @select="p => $router.push(p)">
|
||||
<el-menu class="nav-menu" @select="(p: string) => $router.push(p)">
|
||||
<el-menu-item index="/templates"><el-icon><Grid /></el-icon>浏览 254 套官方样板库</el-menu-item>
|
||||
<el-menu-item index="/quotations"><el-icon><PriceTag /></el-icon>手动报价单</el-menu-item>
|
||||
<el-menu-item index="/ai_render"><el-icon><MagicStick /></el-icon>AI 炫图生成效果图</el-menu-item>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<el-icon size="18" @click="annOpen = !annOpen"><Bell /></el-icon>
|
||||
</el-tooltip>
|
||||
</el-badge>
|
||||
<el-dropdown @command="cmd => cmd === 'logout' ? doLogout() : $router.push('/personal')">
|
||||
<el-dropdown @command="(cmd: string) => cmd === 'logout' ? doLogout() : $router.push('/personal')">
|
||||
<span class="user">
|
||||
<el-avatar :size="28" style="background:#6b8cae">{{ (auth.nickname || '用')[0] }}</el-avatar>
|
||||
{{ auth.nickname || '用户' }}
|
||||
|
||||
@@ -82,7 +82,7 @@ const rows = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const page_size = 15
|
||||
const status = ref<number | undefined>()
|
||||
const status = ref<number | '' | undefined>(undefined)
|
||||
const loading = ref(false)
|
||||
const editVisible = ref(false)
|
||||
const editingId = ref(0)
|
||||
@@ -99,6 +99,13 @@ async function load() {
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
Object.assign(form, { customer_name: '', phone: '', address: '', measure_date: null,
|
||||
surveyor: '', status: 0, data: [], note: '' })
|
||||
editingId.value = 0
|
||||
editVisible.value = true
|
||||
}
|
||||
|
||||
function openEdit(row?: any) {
|
||||
editingId.value = row?.id ?? 0
|
||||
Object.assign(form, row ?? { customer_name: '', phone: '', address: '', measure_date: null,
|
||||
|
||||
@@ -149,7 +149,7 @@ function printOrder() { window.print() }
|
||||
|
||||
const statusLabels: Record<number, string> = { 0: '意向单', 1: '正式单', 2: '生产中', 3: '已完工', 4: '已取消' }
|
||||
|
||||
const query = reactive({ page: 1, page_size: 15, keyword: '', status: undefined as number | undefined,
|
||||
const query = reactive({ page: 1, page_size: 15, keyword: '', status: undefined as number | '' | undefined,
|
||||
start_date: '', end_date: '' })
|
||||
const dateRange = ref<[string, string] | null>(null)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user