feat: P0-7 玻璃装饰格条设计器(8风格/2D+3D渲染/算料计价)
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
controls-position="right" style="width:92px" @change="onRegionSize" /></span>
|
||||
<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>
|
||||
</template>
|
||||
<template v-else-if="selected">
|
||||
<span class="dim-input">梃位 <el-input-number v-model="mulPos" :min="0" :step="10" size="small"
|
||||
@@ -26,6 +27,34 @@
|
||||
</template>
|
||||
<el-button size="small" text @click="reset">重置视图</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 装饰格条设计对话框 -->
|
||||
<el-dialog v-model="decVisible" title="装饰格条设计" width="520px" append-to-body>
|
||||
<div class="dec-presets">
|
||||
<div v-for="p in DEC_PRESETS" :key="p.kind" class="dec-preset" :class="{ active: decForm.kind === p.kind }"
|
||||
@click="applyPreset(p)">
|
||||
<div class="dec-thumb" :style="thumbStyle(p)">
|
||||
<div class="dec-thumb-bar" :style="{ background: p.color }" />
|
||||
</div>
|
||||
<span>{{ p.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-form label-width="80px" style="margin-top:10px">
|
||||
<el-form-item label="横条数"><el-input-number v-model="decForm.rows" :min="0" :max="8" size="small" /></el-form-item>
|
||||
<el-form-item label="竖条数"><el-input-number v-model="decForm.cols" :min="0" :max="8" size="small" /></el-form-item>
|
||||
<el-form-item label="条宽 mm"><el-input-number v-model="decForm.bw" :min="8" :max="60" size="small" /></el-form-item>
|
||||
<el-form-item label="颜色">
|
||||
<el-radio-group v-model="decForm.color" size="small">
|
||||
<el-radio-button v-for="c in DEC_COLORS" :key="c.v" :value="c.v">{{ c.label }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button v-if="hasDec" type="warning" plain @click="removeDec">清除该格格条</el-button>
|
||||
<el-button @click="decVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="applyDec">应用</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -44,11 +73,11 @@
|
||||
* - 改梃: 直接点击中梃选中(高亮) → 拖动移动(line.pt 更新后重放) → 删除梃或 Del 键删除
|
||||
* - 撤销栈; exportDrawing() 取回 drawing_data; change 事件通知父组件
|
||||
*/
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
bboxOf, inferSashGlyphs, parseDrawing, regionPoints, replayShape, spAxis, spPos,
|
||||
type DrawingData, type FrameShape, type Pt, type Segment, type SashShape, type SplitLine,
|
||||
bboxOf, DEC_PRESETS, inferSashGlyphs, parseDrawing, regionPoints, replayShape, spAxis, spPos,
|
||||
type DecBar, type DrawingData, type FrameShape, type Pt, type Segment, type SashShape, type SplitLine,
|
||||
} from '../types/drawing'
|
||||
|
||||
const props = defineProps<{ drawingData: string | object; editable?: boolean }>()
|
||||
@@ -378,6 +407,35 @@ function drawFrame(
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// 1.5) 装饰格条(自研 decs 扩展, 挂在玻璃格上; ri 对齐重放区域索引)
|
||||
for (const dec of (shape.decs ?? []) as DecBar[]) {
|
||||
const segs = rp.regions[dec.ri]
|
||||
if (!segs) continue
|
||||
const pts = regionPoints(segs)
|
||||
if (pts.length < 3) continue
|
||||
const bb = bboxOf(pts)
|
||||
const px0 = tx(bb.x0), py0 = ty(bb.y0), px1 = tx(bb.x1), py1 = ty(bb.y1)
|
||||
const gw = px1 - px0, gh = py1 - py0
|
||||
if (gw < 12 || gh < 12) continue
|
||||
const barPx = Math.max(1.5, dec.bw * scale)
|
||||
ctx.save()
|
||||
ctx.beginPath(); ctx.rect(px0, py0, gw, gh); ctx.clip()
|
||||
ctx.fillStyle = dec.color
|
||||
ctx.strokeStyle = 'rgba(0,0,0,0.4)'
|
||||
ctx.lineWidth = 1
|
||||
for (let i = 1; i <= dec.rows; i++) {
|
||||
const y = py0 + (gh * i) / (dec.rows + 1)
|
||||
ctx.fillRect(px0, y - barPx / 2, gw, barPx)
|
||||
ctx.strokeRect(px0, y - barPx / 2, gw, barPx)
|
||||
}
|
||||
for (let i = 1; i <= dec.cols; i++) {
|
||||
const x = px0 + (gw * i) / (dec.cols + 1)
|
||||
ctx.fillRect(x - barPx / 2, py0, barPx, gh)
|
||||
ctx.strokeRect(x - barPx / 2, py0, barPx, gh)
|
||||
}
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
// 2) 中梃(按目标区域裁剪, 型材三段线; 选中高亮) + 与外框交点白块(官方连接件符号)
|
||||
for (let spIdx = 0; spIdx < rp.clips.length; spIdx++) {
|
||||
const { sp, bb } = rp.clips[spIdx]
|
||||
@@ -796,6 +854,71 @@ function undo() {
|
||||
function exportDrawing(): string | null {
|
||||
return dd ? JSON.stringify(dd) : null
|
||||
}
|
||||
|
||||
// ---- 装饰格条设计(P0-格条设计器) ----
|
||||
const DEC_COLORS = [
|
||||
{ label: '黑', v: '#2b2b2b' }, { label: '白', v: '#ffffff' }, { label: '灰', v: '#9aa0a6' },
|
||||
{ label: '金', v: '#c9a86a' }, { label: '棕', v: '#6b4a2b' }, { label: '蓝', v: '#3a6ea5' },
|
||||
]
|
||||
const decVisible = ref(false)
|
||||
const decForm = reactive<DecBar>({ ri: 0, kind: 'modern', rows: 1, cols: 1, bw: 25, color: '#2b2b2b' })
|
||||
const hasDec = ref(false)
|
||||
|
||||
function openDec() {
|
||||
if (!selRegion.value || !dd) return
|
||||
const { shapeIdx, ri } = selRegion.value
|
||||
const shape = dd.sm[shapeIdx]
|
||||
const existing = (shape?.decs ?? []).find(d => d.ri === ri)
|
||||
hasDec.value = !!existing
|
||||
Object.assign(decForm, existing ?? { ri, kind: 'modern', rows: 1, cols: 1, bw: 25, color: '#2b2b2b' }, { ri })
|
||||
decVisible.value = true
|
||||
}
|
||||
|
||||
function applyPreset(p: (typeof DEC_PRESETS)[number]) {
|
||||
Object.assign(decForm, { kind: p.kind, rows: p.rows, cols: p.cols, bw: p.bw, color: p.color })
|
||||
}
|
||||
|
||||
function thumbStyle(p: (typeof DEC_PRESETS)[number]) {
|
||||
// 纯 CSS 缩略图: 用线性渐变模拟格条
|
||||
const rowPct = p.rows ? 100 / (p.rows + 1) : 0
|
||||
const colPct = p.cols ? 100 / (p.cols + 1) : 0
|
||||
const horizontal = Array.from({ length: p.rows }, (_, i) => `${p.color} ${rowPct * (i + 1) - 2}% ${rowPct * (i + 1) + 2}%`).join(',')
|
||||
const vertical = Array.from({ length: p.cols }, (_, i) => `${p.color} ${colPct * (i + 1) - 2}% ${colPct * (i + 1) + 2}%`).join(',')
|
||||
const base = 'linear-gradient(135deg,#dceef0,#bcd9dc)'
|
||||
const grad = [base, ...(horizontal ? [`linear-gradient(0deg,transparent,${horizontal},transparent)`] : []),
|
||||
...(vertical ? [`linear-gradient(90deg,transparent,${vertical},transparent)`] : [])]
|
||||
return { background: grad.join(',') }
|
||||
}
|
||||
|
||||
function applyDec() {
|
||||
if (!selRegion.value || !dd) return
|
||||
const { shapeIdx } = selRegion.value
|
||||
const shape = dd.sm[shapeIdx]
|
||||
if (!shape) return
|
||||
undoStack.push(JSON.stringify(dd))
|
||||
shape.decs = shape.decs ?? []
|
||||
const found = shape.decs.find(d => d.ri === decForm.ri)
|
||||
if (found) Object.assign(found, JSON.parse(JSON.stringify(decForm)))
|
||||
else shape.decs.push(JSON.parse(JSON.stringify(decForm)))
|
||||
decVisible.value = false
|
||||
render()
|
||||
emit('change')
|
||||
ElMessage.success('格条已应用')
|
||||
}
|
||||
|
||||
function removeDec() {
|
||||
if (!selRegion.value || !dd) return
|
||||
const { shapeIdx, ri } = selRegion.value
|
||||
const shape = dd.sm[shapeIdx]
|
||||
if (!shape?.decs) return
|
||||
undoStack.push(JSON.stringify(dd))
|
||||
shape.decs = shape.decs.filter(d => d.ri !== ri)
|
||||
hasDec.value = false
|
||||
decVisible.value = false
|
||||
render()
|
||||
emit('change')
|
||||
ElMessage.success('格条已清除')
|
||||
}
|
||||
defineExpose({ exportDrawing })
|
||||
|
||||
// ============ 视图控制 ============
|
||||
@@ -958,4 +1081,11 @@ canvas:active { cursor: grabbing; }
|
||||
.hud { position: absolute; right: 10px; top: 10px; display: flex; gap: 8px; align-items: center;
|
||||
background: rgba(255,255,255,0.92); border-radius: 6px; padding: 2px 8px; font-size: 12px; color: #606266; flex-wrap: wrap; }
|
||||
.dim-input { display: inline-flex; align-items: center; gap: 4px; color: #409eff; font-weight: 600; }
|
||||
.dec-presets { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
|
||||
.dec-preset { display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: pointer;
|
||||
padding: 6px; border: 1.5px solid transparent; border-radius: 8px; font-size: 12px; color: #606266; }
|
||||
.dec-preset:hover { background: #f5f7fa; }
|
||||
.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%; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user