feat: 工程大样图支持上传手绘量尺登记表(逐窗转换保留分格/开启扇/手写尺寸, 纯草图模式无样板, 横版1536x1024)

This commit is contained in:
HHY
2026-10-06 23:28:11 -07:00
parent 94f9a26cdc
commit acf1586ccc
7 changed files with 62 additions and 28 deletions
+41 -12
View File
@@ -87,20 +87,26 @@
<!-- ============ 工程大样图(2D 图纸 → 黑白 CAD 大样图, 整合自 autocad 项目草图标准化能力) ============ -->
<template v-else-if="tab === 'blueprint'">
<div class="controls">
<el-select v-model="templateId" filterable placeholder="选择样板" style="width:220px" @change="loadTemplate">
<el-option v-for="t in templates" :key="t.id" :label="t.name || `样板#${t.id}`" :value="t.id" />
</el-select>
<span class="tip">以 2D 图纸为底图,AI 重绘为黑白 CAD 工程大样图(含尺寸线/标题栏,可作报价与合同附件)</span>
<el-upload :show-file-list="false" accept="image/*" :http-request="onSketchUpload">
<el-button type="primary" plain>{{ sketchPhoto ? '更换手绘草图' : '上传手绘草图' }}</el-button>
</el-upload>
<el-button v-if="sketchPhoto" size="small" text type="danger" @click="sketchPhoto = ''">移除草图</el-button>
<span class="tip">{{ sketchPhoto ? '已上传手绘草图,AI 将逐窗转为规范大样图' : '上传现场手绘量尺登记表照片;也可不传,用所选样板的 2D 图纸生成' }}</span>
<div style="flex:1" />
<el-button type="primary" :disabled="!drawing || generating" :loading="generating" @click="generateBlueprint">
<el-button type="primary" :disabled="(!sketchPhoto && !drawing) || generating" :loading="generating"
@click="generateBlueprint">
{{ generating ? 'AI 绘制中…' : '✦ AI 生成大样图' }}</el-button>
</div>
<el-progress v-if="generating" :percentage="progress" :stroke-width="10" style="margin: 10px 0"
:format="() => stageText" />
<div v-if="drawing" class="bp-stage">
<!-- 底图: 优先上传的手绘草图, 否则 2D 图纸 -->
<div v-if="sketchPhoto" class="bp-stage bp-photo">
<img :src="sketchPhoto" class="sketch-img" />
</div>
<div v-else-if="drawing" class="bp-stage">
<WindoorCanvas ref="bpCanvasRef" :drawing-data="drawing" :editable="false" />
</div>
<el-empty v-else description="请先选择样板" />
<el-empty v-else description="上传手绘草图照片,或先选择样板" />
</template>
<div v-if="variants.length" class="gallery-foot">
@@ -252,14 +258,14 @@ const aiImage = ref('')
const aiPrompt = ref('')
const aiFeatures = ref('')
async function generateCloud(mode: 'quick' | 'photo' | 'blueprint', sketch_b64?: string) {
if (!drawing.value) return
async function generateCloud(mode: 'quick' | 'photo' | 'blueprint', sketch_b64?: string, withoutTemplate = false) {
if (!drawing.value && !(mode === 'blueprint' && sketch_b64)) return
if (mode === 'photo' && (!photo.value || !rect.value)) return
generating.value = true
aiImage.value = ''
variants.value = []
progress.value = 8
stageText.value = mode === 'photo' ? '上传照片与窗型特征' : mode === 'blueprint' ? '上传 2D 图纸与分格特征' : '解析窗型结构'
stageText.value = mode === 'photo' ? '上传照片与窗型特征' : mode === 'blueprint' ? '上传手绘草图' : '解析窗型结构'
const timer = window.setInterval(() => {
progress.value = Math.min(92, progress.value + 2)
if (progress.value > 40) stageText.value = 'AI 绘制中(约 30~90 秒, 请勿关闭页面)'
@@ -275,7 +281,8 @@ async function generateCloud(mode: 'quick' | 'photo' | 'blueprint', sketch_b64?:
}
}
const d = await api<any>('post', '/api/ai-render/generate', {
template_id: templateId.value, mode,
// 上传手绘草图时脱离样板上下文(登记表窗型与样板无关, 避免特征误导)
template_id: withoutTemplate ? undefined : templateId.value, mode,
style: form.ai_style, time: form.ai_time,
scene: mode === 'photo' ? scene.value : form.scene, photo_b64, rect: rectPayload, sketch_b64,
}, { timeout: 200000 })
@@ -291,8 +298,28 @@ async function generateCloud(mode: 'quick' | 'photo' | 'blueprint', sketch_b64?:
}
}
/** 工程大样图: 以 2D 图纸画布截图为底图, AI 重绘为黑白 CAD 大样图 */
/** 工程大样图: 底图优先上传的手绘草图照片, 否则 2D 图纸画布截图; AI 重绘为黑白 CAD 大样图 */
const sketchPhoto = ref('')
function onSketchUpload(e: { file: unknown }) {
// el-upload http-request: options.file 即原始 File(部分版本包 raw)
const opt = e.file as { raw?: File } | File
const file = (opt as { raw?: File }).raw ?? (opt as File)
if (!(file instanceof File)) return
const reader = new FileReader()
reader.onload = () => {
sketchPhoto.value = String(reader.result)
ElMessage.success('草图已上传')
}
reader.readAsDataURL(file)
}
async function generateBlueprint() {
// 上传的手绘草图优先(脱离样板上下文); 无上传时用 2D 图纸画布截图
if (sketchPhoto.value) {
await generateCloud('blueprint', sketchPhoto.value.split(',')[1], true)
return
}
await nextTick()
const dataUrl = bpCanvasRef.value?.exportImage?.()
if (!dataUrl) { ElMessage.warning('图纸截图失败,请稍后重试'); return }
@@ -396,6 +423,8 @@ onMounted(async () => {
background: rgba(0,0,0,0.5); padding: 4px 10px; border-radius: 10px; pointer-events: none; }
.preview { height: 58vh; }
.bp-stage { height: 58vh; border: 1px solid #e4e7ed; border-radius: 6px; overflow: hidden; }
.bp-photo { display: flex; align-items: center; justify-content: center; background: #f5f6f8; }
.sketch-img { max-width: 100%; max-height: 100%; object-fit: contain; }
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; margin-top: 14px; }
.gallery-foot { margin-top: 8px; }
.ai-result { margin-top: 14px; text-align: center; }