feat: P1批次1 设计域(AI推荐/分享体系/阳光房绘图/量尺标注/逐格指定编辑)

This commit is contained in:
HHY
2026-09-25 06:17:29 -07:00
parent 6726a9fa3a
commit 76fd0ced14
21 changed files with 1154 additions and 20 deletions
+139 -1
View File
@@ -15,6 +15,7 @@
<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>
<el-button size="small" type="warning" plain @click="openDec">格条</el-button>
<el-button size="small" type="success" plain @click="openAssign">扇/玻璃</el-button>
</template>
<template v-else-if="selected">
<span class="dim-input">梃位 <el-input-number v-model="mulPos" :min="0" :step="10" size="small"
@@ -55,6 +56,51 @@
<el-button type="primary" @click="applyDec">应用</el-button>
</template>
</el-dialog>
<!-- 扇/玻璃逐格指定对话框 -->
<el-dialog v-model="assignVisible" title="扇 / 玻璃逐格指定" width="460px" append-to-body>
<el-form label-width="90px" size="small">
<el-form-item label="开启类型">
<el-radio-group v-model="assignForm.sash.kind">
<el-radio-button value="fixed">固定玻璃</el-radio-button>
<el-radio-button value="casement">平开扇</el-radio-button>
<el-radio-button value="awning">上悬</el-radio-button>
<el-radio-button value="sliding">推拉</el-radio-button>
<el-radio-button value="kfc">内开内倒</el-radio-button>
</el-radio-group>
</el-form-item>
<template v-if="assignForm.sash.kind !== 'fixed'">
<el-form-item label="开启方向">
<el-radio-group v-model="assignForm.sash.openToward">
<el-radio-button value="inward">内开</el-radio-button>
<el-radio-button value="outward">外开</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="铰链侧">
<el-radio-group v-model="assignForm.sash.openDirection" :disabled="assignForm.sash.kind === 'awning'">
<el-radio-button value="left">左侧</el-radio-button>
<el-radio-button value="right">右侧</el-radio-button>
</el-radio-group>
</el-form-item>
</template>
<el-form-item label="副梃">
<el-switch v-model="assignForm.subBar" />
<span class="assign-tip">扇中梃(按长度计价)</span>
</el-form-item>
<el-form-item label="玻璃配置">
<el-select v-model="assignForm.glass" style="width:200px" clearable>
<el-option value="单玻 5mm" /><el-option value="中空 5+12A+5" />
<el-option value="中空 5+18A+5" /><el-option value="三玻两腔 5+9A+5+9A+5" />
<el-option value="夹胶 5+0.76PVB+5" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button v-if="hasAssign" type="warning" plain @click="removeAssign">清除该格指定</el-button>
<el-button @click="assignVisible = false">取消</el-button>
<el-button type="primary" @click="applyAssign">应用</el-button>
</template>
</el-dialog>
</div>
</template>
@@ -77,7 +123,7 @@ import { onMounted, reactive, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import {
bboxOf, DEC_PRESETS, inferSashGlyphs, parseDrawing, regionPoints, replayShape, spAxis, spPos,
type DecBar, type DrawingData, type FrameShape, type Pt, type Segment, type SashShape, type SplitLine,
type DecBar, type DrawingData, type FrameShape, type Pt, type SashAssign, type Segment, type SashShape, type SplitLine,
} from '../types/drawing'
const props = defineProps<{ drawingData: string | object; editable?: boolean }>()
@@ -436,6 +482,49 @@ function drawFrame(
ctx.restore()
}
// 1.6) 逐格指定(自研 assigns): 副梃竖线 + 开扇符号 + 玻璃配置角标
for (const a of (shape.assigns ?? []) as SashAssign[]) {
const segs = rp.regions[a.ri]
if (!segs) continue
const apts = regionPoints(segs)
if (apts.length < 3) continue
const abb = bboxOf(apts)
const ax0 = tx(abb.x0), ay0 = ty(abb.y0), ax1 = tx(abb.x1), ay1 = ty(abb.y1)
const aw = ax1 - ax0, ah = ay1 - ay0
if (aw < 14 || ah < 14) continue
const acx = (ax0 + ax1) / 2, acy = (ay0 + ay1) / 2
if (a.subBar) {
ctx.strokeStyle = STYLE.profileDark
ctx.lineWidth = 2
ctx.beginPath(); ctx.moveTo(acx, ay0 + 2); ctx.lineTo(acx, ay1 - 2); ctx.stroke()
}
if (a.sash) {
ctx.save()
ctx.setLineDash([7, 5])
ctx.strokeStyle = 'rgba(40,60,90,0.75)'
ctx.lineWidth = 1.6
ctx.beginPath()
if (a.sash.kind === 'sliding') {
ctx.moveTo(ax0 + 4, acy); ctx.lineTo(ax1 - 4, acy)
ctx.moveTo(ax1 - 10, acy - 4); ctx.lineTo(ax1 - 4, acy); ctx.lineTo(ax1 - 10, acy + 4)
} else if (a.sash.kind === 'awning') {
ctx.moveTo(ax0 + 4, ay1 - 4); ctx.lineTo(ax1 - 4, ay1 - 4); ctx.lineTo(acx, ay0 + 6)
} else {
const hingeLeft = a.sash.openDirection !== 'right'
if (hingeLeft) { ctx.moveTo(ax0 + 5, ay0 + 5); ctx.lineTo(ax0 + 5, ay1 - 5); ctx.lineTo(ax1 - 6, acy) }
else { ctx.moveTo(ax1 - 5, ay0 + 5); ctx.lineTo(ax1 - 5, ay1 - 5); ctx.lineTo(ax0 + 6, acy) }
}
ctx.closePath()
ctx.stroke()
ctx.restore()
}
if (a.glass) {
ctx.fillStyle = 'rgba(43,108,176,0.85)'
ctx.font = `${Math.max(9, Math.min(12, aw / 8))}px sans-serif`
ctx.fillText(a.glass, ax0 + 4, ay1 - 5)
}
}
// 2) 中梃(按目标区域裁剪, 型材三段线; 选中高亮) + 与外框交点白块(官方连接件符号)
for (let spIdx = 0; spIdx < rp.clips.length; spIdx++) {
const { sp, bb } = rp.clips[spIdx]
@@ -919,6 +1008,54 @@ function removeDec() {
emit('change')
ElMessage.success('格条已清除')
}
// ---- 扇/玻璃逐格指定(P1-逐格编辑) ----
const assignVisible = ref(false)
const hasAssign = ref(false)
const assignForm = reactive<SashAssign>({
ri: 0, sash: { kind: 'fixed', openToward: 'inward', openDirection: 'left' }, subBar: false, glass: '',
})
function openAssign() {
if (!selRegion.value || !dd) return
const { shapeIdx, ri } = selRegion.value
const existing = (dd.sm[shapeIdx]?.assigns ?? []).find(a => a.ri === ri)
hasAssign.value = !!existing
const blank: SashAssign = { ri, sash: { kind: 'fixed', openToward: 'inward', openDirection: 'left' }, subBar: false, glass: '' }
Object.assign(assignForm, JSON.parse(JSON.stringify(existing ?? blank)), { ri })
assignVisible.value = true
}
function applyAssign() {
if (!selRegion.value || !dd) return
const { shapeIdx } = selRegion.value
const shape = dd.sm[shapeIdx]
if (!shape) return
undoStack.push(JSON.stringify(dd))
shape.assigns = shape.assigns ?? []
const found = shape.assigns.find(a => a.ri === assignForm.ri)
const copy = JSON.parse(JSON.stringify(assignForm)) as SashAssign
if (found) Object.assign(found, copy)
else shape.assigns.push(copy)
assignVisible.value = false
render()
emit('change')
ElMessage.success('已指定该格')
}
function removeAssign() {
if (!selRegion.value || !dd) return
const { shapeIdx, ri } = selRegion.value
const shape = dd.sm[shapeIdx]
if (!shape?.assigns) return
undoStack.push(JSON.stringify(dd))
shape.assigns = shape.assigns.filter(a => a.ri !== ri)
hasAssign.value = false
assignVisible.value = false
render()
emit('change')
ElMessage.success('已清除该格指定')
}
defineExpose({ exportDrawing })
// ============ 视图控制 ============
@@ -1088,4 +1225,5 @@ canvas:active { cursor: grabbing; }
.dec-preset.active { border-color: #409eff; background: #ecf5ff; }
.dec-thumb { width: 100%; height: 44px; border: 1px solid #dcdfe6; border-radius: 4px; overflow: hidden; }
.dec-thumb-bar { width: 100%; height: 100%; }
.assign-tip { color: #909399; font-size: 12px; margin-left: 8px; }
</style>