fix: v2.16 样板库画布每块可编辑+模型框污染根治
- 预览对话框打开即编辑态, 报价/下单入口保留 - 格尺寸贴外框方向联动等比缩放整窗, 任意格任意方向均可编辑 - 根因修复: computeModelBox 误含扇 bd 退化点致 W/H 与梃位/缩放参考边全错 - 悬停格淡高亮 + pointer 光标; HUD 标签改格宽/格高 - 验证: 格尺寸/梃位/贴边缩放/组合写回四项截图全过, ui-shots 6 页无报错
This commit is contained in:
@@ -10,9 +10,9 @@
|
||||
<span class="dim-input">H <el-input-number v-model="sizeH" :min="300" :step="10" size="small"
|
||||
controls-position="right" style="width:104px" @change="onSizeInput" /></span>
|
||||
<template v-if="selRegion">
|
||||
<span class="dim-input">格W <el-input-number v-model="regW" :min="100" :step="10" size="small"
|
||||
<span class="dim-input">格宽 <el-input-number v-model="regW" :min="100" :step="10" size="small"
|
||||
controls-position="right" style="width:92px" @change="onRegionSize" /></span>
|
||||
<span class="dim-input">格H <el-input-number v-model="regH" :min="100" :step="10" size="small"
|
||||
<span class="dim-input">格高 <el-input-number v-model="regH" :min="100" :step="10" size="small"
|
||||
controls-position="right" style="width:92px" @change="onRegionSize" /></span>
|
||||
</template>
|
||||
<template v-else-if="selected">
|
||||
@@ -70,6 +70,7 @@ let moveDrag: null | { shapeIdx: number; spIdx: number; startPos: number; startX
|
||||
const selected = ref<{ shapeIdx: number; spIdx: number } | null>(null)
|
||||
// 细节编辑: 选中的玻璃格(可输入格宽高) ; selRegion 与 selected(梃) 互斥
|
||||
const selRegion = ref<{ shapeIdx: number; ri: number } | null>(null)
|
||||
let hoverRegion: { shapeIdx: number; ri: number } | null = null
|
||||
const regW = ref(0)
|
||||
const regH = ref(0)
|
||||
const mulPos = ref(0)
|
||||
@@ -121,36 +122,56 @@ function moveGroup(shapeIdx: number, axis: 'v' | 'h', pos: number, delta: number
|
||||
return applied
|
||||
}
|
||||
|
||||
/** 格尺寸编辑: 输入目标宽/高, 平移该格的边界中梃(优先右/下边界, 其次左/上) */
|
||||
/** 找格在该方向的可动边界梃: 优先 pos1(右/下)侧, 其次 pos0(左/上)侧; 返回 {pos, sign} 或 null */
|
||||
function findRegionEdge(sps: SplitLine[], axis: 'v' | 'h', bb: { x0: number; y0: number; x1: number; y1: number }) {
|
||||
const pos1 = axis === 'v' ? bb.x1 : bb.y1
|
||||
const e1 = sps.find(s => spAxis(s) === axis && Math.abs(spPos(s) - pos1) < 1.5)
|
||||
if (e1) return { pos: spPos(e1), sign: 1 }
|
||||
const pos0 = axis === 'v' ? bb.x0 : bb.y0
|
||||
const e0 = sps.find(s => spAxis(s) === axis && Math.abs(spPos(s) - pos0) < 1.5)
|
||||
if (e0) return { pos: spPos(e0), sign: -1 }
|
||||
return null
|
||||
}
|
||||
|
||||
/** 格尺寸编辑: 输入目标宽/高 → 平移边界中梃; 格两边都贴外框时联动等比缩放整窗 */
|
||||
function onRegionSize() {
|
||||
if (!selRegion.value || !dd) return
|
||||
undoStack.push(JSON.stringify(dd))
|
||||
const { shapeIdx, ri } = selRegion.value
|
||||
const bb = regionBb(shapeIdx, ri)
|
||||
if (!bb) return
|
||||
const sps = dd.sm[shapeIdx]?.mm?.sp ?? []
|
||||
const curW = bb.x1 - bb.x0, curH = bb.y1 - bb.y0
|
||||
const targetW = Math.max(100, Math.round(regW.value))
|
||||
const targetH = Math.max(100, Math.round(regH.value))
|
||||
let moved = false
|
||||
if (Math.abs(targetW - curW) >= 1) {
|
||||
const edge = sps.find(s => spAxis(s) === 'v' && Math.abs(spPos(s) - bb.x1) < 1.5)
|
||||
?? sps.find(s => spAxis(s) === 'v' && Math.abs(spPos(s) - bb.x0) < 1.5)
|
||||
let changed = false
|
||||
// 宽
|
||||
let bb = regionBb(shapeIdx, ri)
|
||||
if (!bb) { undoStack.pop(); return }
|
||||
let cur = bb.x1 - bb.x0
|
||||
if (Math.abs(targetW - cur) >= 1) {
|
||||
const edge = findRegionEdge(sps, 'v', bb)
|
||||
if (edge) {
|
||||
const sign = Math.abs(spPos(edge) - bb.x1) < 1.5 ? 1 : -1
|
||||
moveGroup(shapeIdx, 'v', spPos(edge), (targetW - curW) * sign)
|
||||
moved = true
|
||||
moveGroup(shapeIdx, 'v', edge.pos, (targetW - cur) * edge.sign)
|
||||
changed = true
|
||||
} else if (Math.abs(bb.x0 - modelBox.x0) < 1.5 && Math.abs(bb.x1 - modelBox.x1) < 1.5) {
|
||||
// 格宽=整窗宽(两边界均为外框): 等比缩放整窗
|
||||
resizeTo(Math.round((modelBox.x1 - modelBox.x0) * targetW / cur), Math.round(modelBox.y1 - modelBox.y0))
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if (Math.abs(targetH - curH) >= 1) {
|
||||
const edge = sps.find(s => spAxis(s) === 'h' && Math.abs(spPos(s) - bb.y1) < 1.5)
|
||||
?? sps.find(s => spAxis(s) === 'h' && Math.abs(spPos(s) - bb.y0) < 1.5)
|
||||
// 高(宽调整可能已缩放整窗, 重新取格 bbox)
|
||||
bb = regionBb(shapeIdx, ri)
|
||||
if (!bb) { undoStack.pop(); return }
|
||||
cur = bb.y1 - bb.y0
|
||||
if (Math.abs(targetH - cur) >= 1) {
|
||||
const edge = findRegionEdge(sps, 'h', bb)
|
||||
if (edge) {
|
||||
const sign = Math.abs(spPos(edge) - bb.y1) < 1.5 ? 1 : -1
|
||||
moveGroup(shapeIdx, 'h', spPos(edge), (targetH - curH) * sign)
|
||||
moved = true
|
||||
moveGroup(shapeIdx, 'h', edge.pos, (targetH - cur) * edge.sign)
|
||||
changed = true
|
||||
} else if (Math.abs(bb.y0 - modelBox.y0) < 1.5 && Math.abs(bb.y1 - modelBox.y1) < 1.5) {
|
||||
resizeTo(Math.round(modelBox.x1 - modelBox.x0), Math.round((modelBox.y1 - modelBox.y0) * targetH / cur))
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if (!moved) { syncRegionInputs(); ElMessage.warning('该格贴外框, 请先加分格梃再调整'); return }
|
||||
if (!changed) { undoStack.pop(); syncRegionInputs(); return }
|
||||
replayOne(shapeIdx)
|
||||
computeModelBox()
|
||||
syncSizeInputs()
|
||||
@@ -501,6 +522,19 @@ function drawFrame(
|
||||
}
|
||||
ctx.textBaseline = 'alphabetic'
|
||||
|
||||
// 悬停格淡高亮(可编辑提示)
|
||||
if (props.editable && hoverRegion && hoverRegion.shapeIdx === shapeIdx
|
||||
&& !(selRegion.value && selRegion.value.shapeIdx === shapeIdx && selRegion.value.ri === hoverRegion.ri)) {
|
||||
const r = rp.regions[hoverRegion.ri]
|
||||
if (r) {
|
||||
ctx.beginPath()
|
||||
regionPoints(r).forEach((p, i) => (i ? ctx.lineTo(tx(p.x), ty(p.y)) : ctx.moveTo(tx(p.x), ty(p.y))))
|
||||
ctx.closePath()
|
||||
ctx.fillStyle = 'rgba(64,158,255,0.14)'
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
|
||||
// 6) 选中玻璃格高亮: 蓝虚线框 + 格尺寸
|
||||
if (selRegion.value && selRegion.value.shapeIdx === shapeIdx) {
|
||||
const r = rp.regions[selRegion.value.ri]
|
||||
@@ -751,12 +785,10 @@ function computeModelBox() {
|
||||
if (!dd) return
|
||||
const pts: Pt[] = []
|
||||
for (const s of dd.sm) {
|
||||
// 外框只由基底多边形决定; 扇 bd 序列化常退化为单点, 计入会把模型框拉大
|
||||
for (const r of (s.polygon?.polygon ?? [])) {
|
||||
for (const seg of r) { pts.push(seg.ps); pts.push(seg.pe) }
|
||||
}
|
||||
for (const sub of (s.sm ?? []) as SashShape[]) {
|
||||
if (sub.bd) { pts.push(sub.bd.from, sub.bd.to) }
|
||||
}
|
||||
}
|
||||
if (!pts.length) { modelBox = { x0: 0, y0: 0, x1: 1000, y1: 1000 }; return }
|
||||
modelBox = bboxOf(pts)
|
||||
@@ -827,12 +859,30 @@ function onMove(e: MouseEvent) {
|
||||
}
|
||||
return
|
||||
}
|
||||
// 光标提示
|
||||
// 光标提示 + 悬停格高亮
|
||||
if (props.editable && !tool.value && rect) {
|
||||
const sx = e.clientX - rect.left, sy = e.clientY - rect.top
|
||||
const hit = hitResizeHandle(sx, sy)
|
||||
const m = hit ? null : hitMullion(sx, sy)
|
||||
let hover: { shapeIdx: number; ri: number } | null = null
|
||||
if (!hit && !m) {
|
||||
const p = toModel(sx, sy)
|
||||
let best = Infinity
|
||||
for (let si = 0; si < replays.length; si++) {
|
||||
const regions = replays[si].regions
|
||||
for (let ri = 0; ri < regions.length; ri++) {
|
||||
const pts = regionPoints(regions[ri])
|
||||
if (pts.length < 3 || !pointInPoly(p, pts)) continue
|
||||
const b = bboxOf(pts)
|
||||
const area = (b.x1 - b.x0) * (b.y1 - b.y0)
|
||||
if (area < best) { best = area; hover = { shapeIdx: si, ri } }
|
||||
}
|
||||
}
|
||||
}
|
||||
cvRef.value!.style.cursor = hit === 'wh' ? 'nwse-resize' : hit === 'w' ? 'ew-resize' : hit === 'h' ? 'ns-resize'
|
||||
: hitMullion(sx, sy) ? 'move' : 'grab'
|
||||
: m ? 'move' : hover ? 'pointer' : 'grab'
|
||||
const changed = hover?.shapeIdx !== hoverRegion?.shapeIdx || hover?.ri !== hoverRegion?.ri
|
||||
if (changed) { hoverRegion = hover; render() }
|
||||
}
|
||||
if (!drag) return
|
||||
view.cx += e.clientX - drag.x
|
||||
|
||||
@@ -39,14 +39,16 @@
|
||||
<span>尺寸限制: {{ current.low_width }}-{{ current.high_width }} × {{ current.low_height }}-{{ current.high_height }} mm</span>
|
||||
<span>引用次数: {{ current.times }}</span>
|
||||
<el-button type="success" size="small" @click="quoteRef.open({ template_id: current.id })">算料报价</el-button>
|
||||
<el-button size="small" @click="editing = true">编辑分格/尺寸</el-button>
|
||||
<el-button size="small" @click="editing = true">编辑样板</el-button>
|
||||
<el-button type="primary" size="small" @click="useInOrder">用此样板下单</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="tip">改 W/H 或拖右下角手柄缩放;点击玻璃格输入格宽高;点击中梃输入位置或拖动;+竖梃/+横梃点击分割</span>
|
||||
<span class="tip">点击玻璃格输入格宽高;点中梃输入位置或拖动;改 W/H 缩放整体;+竖梃/+横梃分割</span>
|
||||
<span class="live-price" v-if="liveTotal !== null">当前报价 ¥{{ liveTotal }}</span>
|
||||
<el-button type="success" size="small" @click="quoteRef.open({ template_id: current.id })">算料报价</el-button>
|
||||
<el-button size="small" @click="cancelEdit">取消</el-button>
|
||||
<el-button type="primary" size="small" :disabled="!dirty" @click="saveAsTemplate">另存为我的样板</el-button>
|
||||
<el-button size="small" @click="useInOrder">用此样板下单</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,7 +110,8 @@ async function load() {
|
||||
|
||||
async function preview(t: any) {
|
||||
current.value = await api<any>('get', `/api/windoor/templates/${t.id}`)
|
||||
editing.value = false
|
||||
// 点击进入即编辑: 每一块(玻璃格/中梃)都可直接编辑
|
||||
editing.value = true
|
||||
dirty.value = false
|
||||
liveTotal.value = null
|
||||
previewVisible.value = true
|
||||
|
||||
Reference in New Issue
Block a user