feat: 部位级材料选择(对应部位使用对应材料: 估算器overrides+series-materials聚合端点+QuoteDialog部位选材面板实时重算)
This commit is contained in:
@@ -22,7 +22,18 @@
|
||||
<span class="dim">按当前窗型 {{ result.series_estimate.width }}×{{ result.series_estimate.height }}mm 估算</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-row :gutter="10">
|
||||
<!-- 部位选材: 对应部位使用对应材料(可跨系列混搭) -->
|
||||
<div class="part-row">
|
||||
<div v-for="p in partKeys" :key="p.key" class="part-item">
|
||||
<div class="part-label">{{ p.label }}</div>
|
||||
<el-select v-model="parts[p.key]" placeholder="系列默认" clearable filterable size="small"
|
||||
style="width:100%" @change="requote">
|
||||
<el-option v-for="(m, i) in (materialGroups[p.key] ?? [])" :key="i" :value="i"
|
||||
:label="`${m.name} · ${m.unit_price}${p.key === 'glass' ? '元/㎡' : '元'}${m.series ? ' · ' + m.series : ''}`" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<el-row :gutter="10" style="margin-top:10px">
|
||||
<el-col :span="6"><el-statistic title="系列成本(元/樘)" :value="result.series_estimate.total" :precision="2" /></el-col>
|
||||
<el-col :span="6"><el-statistic title="每平成本(元/㎡)" :value="result.series_estimate.per_sqm" :precision="2" /></el-col>
|
||||
<el-col :span="6"><el-statistic title="建议售价(50%毛利)" :value="result.series_estimate.margin_ladder['50%']" :precision="2" /></el-col>
|
||||
@@ -58,7 +69,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { api } from '../api'
|
||||
|
||||
const visible = ref(false)
|
||||
@@ -66,6 +77,34 @@ const loading = ref(false)
|
||||
const result = ref<any>(null)
|
||||
let source: { drawing_data?: string; template_id?: number; order_id?: number; series_id?: number } = {}
|
||||
|
||||
// 部位选材: 对应部位使用对应材料(选项聚合自各系列用料清单, 可跨系列混搭)
|
||||
const partKeys = [
|
||||
{ key: 'frame', label: '外框型材' },
|
||||
{ key: 'mullion', label: '中梃型材' },
|
||||
{ key: 'sash', label: '扇框型材' },
|
||||
{ key: 'glass', label: '玻璃配置' },
|
||||
{ key: 'hardware', label: '五金套件' },
|
||||
] as const
|
||||
const materialGroups = ref<Record<string, any[]>>({})
|
||||
const parts = reactive<Record<string, number | undefined>>({ frame: undefined, mullion: undefined, sash: undefined, glass: undefined, hardware: undefined })
|
||||
|
||||
function buildMaterials(): Record<string, any> | undefined {
|
||||
const out: Record<string, any> = {}
|
||||
for (const { key } of partKeys) {
|
||||
const idx = parts[key]
|
||||
const m = idx !== undefined ? (materialGroups.value[key] ?? [])[idx] : undefined
|
||||
if (m) out[key] = { name: m.name, unit_price: m.unit_price, weight_per_m: m.weight_per_m }
|
||||
}
|
||||
return Object.keys(out).length ? out : undefined
|
||||
}
|
||||
|
||||
async function requote() {
|
||||
loading.value = true
|
||||
try {
|
||||
result.value = await api<any>('post', '/api/offer-price', { ...source, materials: buildMaterials() })
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function kindLabel(k: string) {
|
||||
return ({ frame: '外框', mullion: '中梃', sash: '扇框', glass: '玻璃', hardware: '五金' } as Record<string, string>)[k] ?? k
|
||||
}
|
||||
@@ -74,6 +113,11 @@ async function open(src: typeof source) {
|
||||
source = src
|
||||
visible.value = true
|
||||
loading.value = true
|
||||
// 重置部位选择
|
||||
for (const { key } of partKeys) parts[key] = undefined
|
||||
if (src.series_id && !Object.keys(materialGroups.value).length) {
|
||||
try { materialGroups.value = await api<any>('get', '/api/costbook/series-materials') } catch { /* 无系列数据时隐藏 */ }
|
||||
}
|
||||
try {
|
||||
result.value = await api<any>('post', '/api/offer-price', src)
|
||||
} finally { loading.value = false }
|
||||
@@ -89,7 +133,10 @@ defineExpose({ open })
|
||||
<style scoped>
|
||||
.total { color: #e6483d; font-size: 18px; font-weight: 700; }
|
||||
.series-card { margin-top: 10px; }
|
||||
.series-head { display: flex; align-items: center; gap: 8px; font-weight: 600; }
|
||||
.series-head { display: flex; align-items: center; gap: 8px; font-weight: 600; flex-wrap: wrap; }
|
||||
.series-head .dim { font-size: 12px; color: #909399; font-weight: 400; }
|
||||
@media print { .el-dialog__footer { display: none; } }
|
||||
.part-row { display: flex; gap: 8px; margin-top: 4px; }
|
||||
.part-item { flex: 1; min-width: 0; }
|
||||
.part-label { font-size: 12px; color: #909399; margin-bottom: 4px; }
|
||||
@media print { .el-dialog__footer, .part-row { display: none; } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user