feat: P0-2 订单BOM计算单+Excel导出(openpyxl)
This commit is contained in:
@@ -41,3 +41,13 @@ export async function api<T = any>(method: string, url: string, body?: object):
|
||||
const res = await http.request({ method, url, data: body })
|
||||
return res.data.data as T
|
||||
}
|
||||
|
||||
/** 下载二进制文件(自动带 token),filename 为保存文件名 */
|
||||
export async function apiDownload(url: string, filename: string): Promise<void> {
|
||||
const res = await http.get(url, { responseType: 'blob' })
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(res.data)
|
||||
link.download = filename
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
</el-descriptions>
|
||||
<div class="canvas-actions">
|
||||
<el-button size="small" type="success" plain @click="quoteRef.open({ order_id: detail.id })">算料报价</el-button>
|
||||
<el-button size="small" type="primary" plain @click="openBom">BOM 单</el-button>
|
||||
<el-button size="small" @click="printOrder">打印订单</el-button>
|
||||
</div>
|
||||
<el-tabs model-value="canvas">
|
||||
@@ -130,6 +131,35 @@
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<!-- BOM 计算单 -->
|
||||
<el-dialog v-model="bomVisible" :title="`BOM 计算单 — ${bom?.order_no ?? ''}`" width="640px">
|
||||
<div v-if="bom" v-loading="bomLoading">
|
||||
<el-descriptions :column="3" border size="small">
|
||||
<el-descriptions-item label="窗型尺寸">{{ bom.width_mm }} × {{ bom.height_mm }} mm</el-descriptions-item>
|
||||
<el-descriptions-item label="玻璃面积">{{ bom.glass_area_m2 }} ㎡</el-descriptions-item>
|
||||
<el-descriptions-item label="五金">{{ bom.sash_count }} 套</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-table :data="bom.bars" size="small" border style="margin-top:10px">
|
||||
<el-table-column prop="note" label="用途" min-width="120">
|
||||
<template #default="{ row }">{{ row.note || row.kind }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="length" label="单件长度(mm)" width="120" />
|
||||
<el-table-column prop="qty" label="数量(件)" width="100" />
|
||||
<el-table-column label="总长(m)" width="100">
|
||||
<template #default="{ row }">{{ (row.length * row.qty / 1000).toFixed(2) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="bom-total">
|
||||
型材合计:{{ ((bom.bars ?? []).reduce((s: number, b: any) => s + b.length * b.qty, 0) / 1000).toFixed(2) }} m
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="bomVisible = false">关闭</el-button>
|
||||
<el-button type="primary" @click="exportOrder">导出 Excel</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<QuoteDialog ref="quoteRef" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -138,7 +168,7 @@
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { api } from '../api'
|
||||
import { api, apiDownload } from '../api'
|
||||
import WindoorCanvas from '../components/WindoorCanvas.vue'
|
||||
import QuoteDialog from '../components/QuoteDialog.vue'
|
||||
|
||||
@@ -315,6 +345,22 @@ async function updateProcess() {
|
||||
processActive.value = detail.value.processes.filter((p: any) => p.status === 2).length
|
||||
}
|
||||
|
||||
// ---- BOM 计算单 ----
|
||||
const bomVisible = ref(false)
|
||||
const bomLoading = ref(false)
|
||||
const bom = ref<any>(null)
|
||||
|
||||
async function openBom() {
|
||||
bomVisible.value = true
|
||||
bomLoading.value = true
|
||||
try { bom.value = await api<any>('get', `/api/orders/${detail.value.id}/bom`) } finally { bomLoading.value = false }
|
||||
}
|
||||
|
||||
async function exportOrder() {
|
||||
await apiDownload(`/api/orders/${detail.value.id}/export`, `order_${detail.value.order_no}.xlsx`)
|
||||
ElMessage.success('已导出')
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
@@ -325,4 +371,5 @@ onMounted(load)
|
||||
.empty-tip { height: 100%; display: flex; align-items: center; justify-content: center; color: #909399; font-size: 14px; }
|
||||
.tip { color: #909399; font-size: 12px; }
|
||||
.live-price { color: #e6483d; font-size: 15px; font-weight: 700; }
|
||||
.bom-total { margin-top: 8px; text-align: right; font-weight: 600; color: #303133; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user