360 lines
11 KiB
Vue
360 lines
11 KiB
Vue
<template>
|
||
<div class="app-container">
|
||
<el-card class="mb20">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>{{ productDetail.productName }} - 产品详情</span>
|
||
<el-button type="primary" plain size="small" @click="handleBack">返回列表</el-button>
|
||
</div>
|
||
</template>
|
||
<div class="product-info">
|
||
<div class="info-item">
|
||
<span class="label">产品名称:</span>
|
||
<span class="value">{{ productDetail.productName }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">产品规格:</span>
|
||
<span class="value">{{ productDetail.spec }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">产品型号:</span>
|
||
<span class="value">{{ productDetail.model }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">产品单价:</span>
|
||
<span class="value">{{ formatDecimal(productDetail.unitPrice) }} 元</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">备注:</span>
|
||
<span class="value">{{ productDetail.remark || '无' }}</span>
|
||
</div>
|
||
</div>
|
||
<!-- 产品附加属性 -->
|
||
<div class="product-addition" v-if="productAdditionList.length > 0">
|
||
<h4>产品附加属性</h4>
|
||
<el-table :data="productAdditionList" style="width: 100%" border>
|
||
<el-table-column prop="attrName" label="属性名" width="150" />
|
||
<el-table-column prop="attrValue" label="属性值" />
|
||
</el-table>
|
||
</div>
|
||
<div class="product-images" v-if="productDetail.productImages && productDetail.productImages.trim()">
|
||
<h4>产品图片</h4>
|
||
<div class="image-list">
|
||
<el-image
|
||
v-for="(image, index) in productDetail.productImages.split(',').filter(img => img.trim())"
|
||
:key="index"
|
||
:src="image"
|
||
:preview-src-list="productDetail.productImages.split(',').filter(img => img.trim())"
|
||
style="width: 100px; height: 100px; margin-right: 10px;"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<el-card>
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>材料明细</span>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 主材部分 -->
|
||
<div class="material-section">
|
||
<h3 class="section-title">主材</h3>
|
||
<el-table :data="mainMaterials" style="width: 100%" border>
|
||
<el-table-column prop="materialName" label="配料名称" width="150" />
|
||
<el-table-column prop="spec" label="材料规格" width="150" />
|
||
<el-table-column prop="quantity" label="数量" width="100" align="center" />
|
||
<el-table-column prop="price" label="价格" width="100" align="center">
|
||
<template #default="scope">
|
||
{{ formatDecimal(scope.row.price) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="subtotal" label="小计" width="100" align="center">
|
||
<template #default="scope">
|
||
{{ formatDecimal(scope.row.subtotal) }}
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="section-summary" v-if="mainMaterials.length > 0">
|
||
<span>主材小计:{{ formatDecimal(mainMaterials.reduce((sum, item) => sum + item.subtotal, 0)) }} 元</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 辅材部分 -->
|
||
<div class="material-section">
|
||
<h3 class="section-title">辅材</h3>
|
||
<el-table :data="auxiliaryMaterials" style="width: 100%" border>
|
||
<el-table-column prop="materialName" label="配料名称" width="150" />
|
||
<el-table-column prop="spec" label="材料规格" width="150" />
|
||
<el-table-column prop="quantity" label="数量" width="100" align="center" />
|
||
<el-table-column prop="price" label="价格" width="100" align="center">
|
||
<template #default="scope">
|
||
{{ formatDecimal(scope.row.price) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="subtotal" label="小计" width="100" align="center">
|
||
<template #default="scope">
|
||
{{ formatDecimal(scope.row.subtotal) }}
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="section-summary" v-if="auxiliaryMaterials.length > 0">
|
||
<span>辅材小计:{{ formatDecimal(auxiliaryMaterials.reduce((sum, item) => sum + item.subtotal, 0)) }} 元</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 工本部分 -->
|
||
<div class="material-section">
|
||
<h3 class="section-title">工本</h3>
|
||
<el-table :data="laborMaterials" style="width: 100%" border>
|
||
<el-table-column prop="materialName" label="项目名称" width="150" />
|
||
<el-table-column prop="spec" label="规格" width="150" />
|
||
<el-table-column prop="quantity" label="数量" width="100" align="center" />
|
||
<el-table-column prop="price" label="价格" width="100" align="center">
|
||
<template #default="scope">
|
||
{{ formatDecimal(scope.row.price) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="subtotal" label="小计" width="100" align="center">
|
||
<template #default="scope">
|
||
{{ formatDecimal(scope.row.subtotal) }}
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="section-summary" v-if="laborMaterials.length > 0">
|
||
<span>工本小计:{{ formatDecimal(laborMaterials.reduce((sum, item) => sum + item.subtotal, 0)) }} 元</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 总计部分 -->
|
||
<div class="total-section">
|
||
<div class="total-item">
|
||
<span class="total-label">合计:</span>
|
||
<span class="total-value">{{ formatDecimal(totalAmount) }} 元</span>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup name="ProductDetail">
|
||
import { ref, computed, onMounted } from 'vue';
|
||
import { useRouter, useRoute } from 'vue-router';
|
||
import { getProduct } from "@/api/mat/product";
|
||
import { listProductMaterialRelation } from "@/api/mat/productMaterialRelation";
|
||
import { getMaterial } from "@/api/mat/material";
|
||
import { listProductAddition } from "@/api/mat/productAddition";
|
||
import { formatDecimal } from '@/utils/gear';
|
||
|
||
const router = useRouter();
|
||
const route = useRoute();
|
||
|
||
const productDetail = ref({});
|
||
const loading = ref(true);
|
||
const materialLoading = ref(false);
|
||
const additionLoading = ref(false);
|
||
|
||
// 材料明细数据
|
||
const productMaterialRelationList = ref([]);
|
||
|
||
// 产品附加属性数据
|
||
const productAdditionList = ref([]);
|
||
|
||
// 计算主材、辅材和工本
|
||
const mainMaterials = computed(() => {
|
||
// 主材(材料类型为2)
|
||
return productMaterialRelationList.value
|
||
.filter(item => item.material && item.material.materialType === 2)
|
||
.map(item => ({
|
||
materialName: item.material.materialName,
|
||
spec: item.material.spec,
|
||
quantity: item.materialNum + (item.material.unit || ''),
|
||
price: item.material.unitPrice || 0,
|
||
subtotal: (item.materialNum * (item.material.unitPrice || 0)) || 0
|
||
}));
|
||
});
|
||
|
||
const auxiliaryMaterials = computed(() => {
|
||
// 辅材(材料类型为1)
|
||
return productMaterialRelationList.value
|
||
.filter(item => item.material && item.material.materialType === 1)
|
||
.map(item => ({
|
||
materialName: item.material.materialName,
|
||
spec: item.material.spec,
|
||
quantity: item.materialNum + (item.material.unit || ''),
|
||
price: item.material.unitPrice || 0,
|
||
subtotal: (item.materialNum * (item.material.unitPrice || 0)) || 0
|
||
}));
|
||
});
|
||
|
||
const laborMaterials = computed(() => {
|
||
// 工本(材料类型为3)
|
||
return productMaterialRelationList.value
|
||
.filter(item => item.material && item.material.materialType === 3)
|
||
.map(item => ({
|
||
materialName: item.material.materialName,
|
||
spec: item.material.spec,
|
||
quantity: item.materialNum + (item.material.unit || ''),
|
||
price: item.material.unitPrice || 0,
|
||
subtotal: (item.materialNum * (item.material.unitPrice || 0)) || 0
|
||
}));
|
||
});
|
||
|
||
// 计算总金额
|
||
const totalAmount = computed(() => {
|
||
const mainTotal = mainMaterials.value.reduce((sum, item) => sum + item.subtotal, 0);
|
||
const auxiliaryTotal = auxiliaryMaterials.value.reduce((sum, item) => sum + item.subtotal, 0);
|
||
const laborTotal = laborMaterials.value.reduce((sum, item) => sum + item.subtotal, 0);
|
||
return mainTotal + auxiliaryTotal + laborTotal;
|
||
});
|
||
|
||
// 获取产品详情
|
||
function getProductDetail() {
|
||
const productId = route.params.id;
|
||
if (productId) {
|
||
loading.value = true;
|
||
getProduct(productId).then(response => {
|
||
productDetail.value = response.data;
|
||
loading.value = false;
|
||
// 获取材料明细
|
||
getMaterialDetail(productId);
|
||
// 获取产品附加属性
|
||
getProductAddition(productId);
|
||
}).catch(error => {
|
||
console.error('获取产品详情失败:', error);
|
||
loading.value = false;
|
||
});
|
||
}
|
||
}
|
||
|
||
// 获取产品附加属性
|
||
function getProductAddition(productId) {
|
||
additionLoading.value = true;
|
||
listProductAddition({ productId }).then(response => {
|
||
productAdditionList.value = response.rows || [];
|
||
additionLoading.value = false;
|
||
}).catch(error => {
|
||
console.error('获取产品附加属性失败:', error);
|
||
additionLoading.value = false;
|
||
});
|
||
}
|
||
|
||
// 获取材料明细
|
||
function getMaterialDetail(productId) {
|
||
materialLoading.value = true;
|
||
listProductMaterialRelation({ productId }).then(response => {
|
||
const relations = response.rows;
|
||
// 为每个配方项获取物料详细信息,包括物料类型
|
||
const promises = relations.map(item => {
|
||
return getMaterial(item.materialId).then(materialResponse => {
|
||
item.material = materialResponse.data;
|
||
return item;
|
||
});
|
||
});
|
||
return Promise.all(promises);
|
||
}).then(relationsWithMaterial => {
|
||
productMaterialRelationList.value = relationsWithMaterial;
|
||
materialLoading.value = false;
|
||
}).catch(error => {
|
||
console.error('获取材料明细失败:', error);
|
||
materialLoading.value = false;
|
||
});
|
||
}
|
||
|
||
// 返回列表
|
||
function handleBack() {
|
||
router.back();
|
||
}
|
||
|
||
onMounted(() => {
|
||
getProductDetail();
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.app-container {
|
||
padding: 20px;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.product-info {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 20px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.label {
|
||
font-weight: bold;
|
||
min-width: 80px;
|
||
}
|
||
|
||
.product-images {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.image-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.material-section {
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.section-title {
|
||
background-color: #f5f7fa;
|
||
padding: 10px;
|
||
border-left: 4px solid #409eff;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.section-summary {
|
||
text-align: right;
|
||
padding: 10px;
|
||
background-color: #f9f9f9;
|
||
border-top: 1px solid #e4e7ed;
|
||
margin-top: -1px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.total-section {
|
||
margin-top: 30px;
|
||
padding: 20px;
|
||
background-color: #f0f9eb;
|
||
border: 1px solid #b7eb8f;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.total-item {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
align-items: center;
|
||
gap: 20px;
|
||
}
|
||
|
||
.total-label {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.total-value {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #f56c6c;
|
||
}
|
||
</style> |