feat: P1批次1 设计域(AI推荐/分享体系/阳光房绘图/量尺标注/逐格指定编辑)
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user