2026-04-22 18:33:04 +08:00
|
|
|
|
<template>
|
2026-04-28 16:53:35 +08:00
|
|
|
|
<div class="app-container product-detail">
|
|
|
|
|
|
<el-card class="detail-card" v-loading="loading">
|
2026-04-22 18:33:04 +08:00
|
|
|
|
<template #header>
|
|
|
|
|
|
<div class="card-header">
|
2026-04-28 16:53:35 +08:00
|
|
|
|
<div class="header-title">
|
|
|
|
|
|
<div class="product-name">{{ productDetail.productName || '-' }}</div>
|
|
|
|
|
|
<div class="sub-title">产品详情</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-button type="primary" plain @click="handleBack">返回列表</el-button>
|
2026-04-22 18:33:04 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
2026-04-28 16:53:35 +08:00
|
|
|
|
|
|
|
|
|
|
<el-row :gutter="16">
|
|
|
|
|
|
<el-col :xs="24" :md="14">
|
|
|
|
|
|
<el-descriptions class="detail-desc" :column="2" border>
|
|
|
|
|
|
<el-descriptions-item label="产品名称">
|
|
|
|
|
|
{{ productDetail.productName || '-' }}
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="产品单价">
|
|
|
|
|
|
{{ formatDecimal(productDetail.unitPrice) }} 元
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="产品规格">
|
|
|
|
|
|
{{ productDetail.spec || '-' }}
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="产品型号">
|
|
|
|
|
|
{{ productDetail.model || '-' }}
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="备注" :span="2">
|
|
|
|
|
|
<span class="remark-text">{{ productDetail.remark || '无' }}</span>
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="section" v-loading="additionLoading">
|
|
|
|
|
|
<div class="section-title">附加属性</div>
|
|
|
|
|
|
<el-empty v-if="!productAdditionList.length" description="暂无附加属性" />
|
|
|
|
|
|
<el-descriptions v-else :column="2" border size="small">
|
|
|
|
|
|
<el-descriptions-item
|
|
|
|
|
|
v-for="item in productAdditionList"
|
|
|
|
|
|
:key="item.addId || item.attrName"
|
|
|
|
|
|
:label="item.attrName"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ item.attrValue || '-' }}
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
</el-descriptions>
|
2026-04-27 10:40:56 +08:00
|
|
|
|
</div>
|
2026-04-28 16:53:35 +08:00
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
|
|
<el-col :xs="24" :md="10">
|
|
|
|
|
|
<el-tabs class="media-tabs" type="border-card">
|
|
|
|
|
|
<el-tab-pane label="图片">
|
|
|
|
|
|
<el-empty
|
|
|
|
|
|
v-if="!(productDetail.productImages && productDetail.productImages.trim())"
|
|
|
|
|
|
description="暂无图片"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div v-else class="image-grid">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="(image, index) in productDetail.productImages.split(',').filter(img => img.trim())"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
class="image-item"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-image
|
|
|
|
|
|
:src="image"
|
|
|
|
|
|
:preview-src-list="productDetail.productImages.split(',').filter(img => img.trim())"
|
|
|
|
|
|
fit="cover"
|
|
|
|
|
|
class="image"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
|
|
|
|
|
|
<el-tab-pane label="说明书">
|
|
|
|
|
|
<el-empty v-if="!pdfDisplayList.length" description="暂无说明书" />
|
|
|
|
|
|
<div v-else class="pdf-list">
|
|
|
|
|
|
<div v-for="(pdf, index) in pdfDisplayList" :key="index" class="pdf-item">
|
|
|
|
|
|
<div class="pdf-left">
|
|
|
|
|
|
<el-icon class="pdf-icon"><Document /></el-icon>
|
|
|
|
|
|
<div class="pdf-meta">
|
|
|
|
|
|
<div class="pdf-name" :title="pdf.name">{{ pdf.name }}</div>
|
|
|
|
|
|
<div class="pdf-sub">{{ pdf.ossId ? `ID: ${pdf.ossId}` : '' }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="pdf-actions">
|
|
|
|
|
|
<el-button type="primary" link @click="previewPdf(pdf)">预览</el-button>
|
|
|
|
|
|
<el-button type="success" link @click="downloadPdf(pdf)">下载</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
</el-tabs>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
2026-04-22 18:33:04 +08:00
|
|
|
|
</el-card>
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
<el-card class="detail-card" v-loading="materialLoading">
|
2026-04-22 18:33:04 +08:00
|
|
|
|
<template #header>
|
|
|
|
|
|
<div class="card-header">
|
2026-04-28 16:53:35 +08:00
|
|
|
|
<div class="header-title">
|
|
|
|
|
|
<div class="product-name">成本明细</div>
|
|
|
|
|
|
<div class="sub-title">主材 / 辅材 / 工价</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="total-badge">
|
|
|
|
|
|
合计:{{ formatDecimal(totalAmount) }} 元
|
|
|
|
|
|
</div>
|
2026-04-22 18:33:04 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
<div class="cost-section">
|
|
|
|
|
|
<div class="cost-title">主材</div>
|
|
|
|
|
|
<el-empty v-if="!mainMaterials.length" description="暂无主材" />
|
|
|
|
|
|
<div v-else>
|
|
|
|
|
|
<el-table :data="mainMaterials" border stripe>
|
|
|
|
|
|
<el-table-column prop="materialName" label="配料名称" min-width="140" />
|
|
|
|
|
|
<el-table-column prop="spec" label="材料规格" min-width="140" />
|
|
|
|
|
|
<el-table-column prop="quantity" label="数量" width="120" align="center" />
|
|
|
|
|
|
<el-table-column prop="price" label="单价" width="120" align="center">
|
|
|
|
|
|
<template #default="scope">{{ formatDecimal(scope.row.price) }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="subtotal" label="小计" width="120" align="center">
|
|
|
|
|
|
<template #default="scope">{{ formatDecimal(scope.row.subtotal) }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
<div class="section-summary">
|
|
|
|
|
|
主材小计:{{ formatDecimal(mainMaterials.reduce((sum, item) => sum + item.subtotal, 0)) }} 元
|
|
|
|
|
|
</div>
|
2026-04-27 10:40:56 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
<div class="cost-section">
|
|
|
|
|
|
<div class="cost-title">辅材</div>
|
|
|
|
|
|
<el-empty v-if="!auxiliaryMaterials.length" description="暂无辅材" />
|
|
|
|
|
|
<div v-else>
|
|
|
|
|
|
<el-table :data="auxiliaryMaterials" border stripe>
|
|
|
|
|
|
<el-table-column prop="materialName" label="配料名称" min-width="140" />
|
|
|
|
|
|
<el-table-column prop="spec" label="材料规格" min-width="140" />
|
|
|
|
|
|
<el-table-column prop="quantity" label="数量" width="120" align="center" />
|
|
|
|
|
|
<el-table-column prop="price" label="单价" width="120" align="center">
|
|
|
|
|
|
<template #default="scope">{{ formatDecimal(scope.row.price) }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="subtotal" label="小计" width="120" align="center">
|
|
|
|
|
|
<template #default="scope">{{ formatDecimal(scope.row.subtotal) }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
<div class="section-summary">
|
|
|
|
|
|
辅材小计:{{ formatDecimal(auxiliaryMaterials.reduce((sum, item) => sum + item.subtotal, 0)) }} 元
|
|
|
|
|
|
</div>
|
2026-04-22 18:33:04 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
<div class="cost-section">
|
|
|
|
|
|
<div class="cost-title">工价</div>
|
|
|
|
|
|
<el-empty v-if="!productLaborList.length" description="暂无工价" />
|
|
|
|
|
|
<div v-else>
|
|
|
|
|
|
<el-table :data="productLaborList" border stripe>
|
|
|
|
|
|
<el-table-column prop="laborName" label="工价说明" min-width="200" />
|
|
|
|
|
|
<el-table-column prop="laborPrice" label="金额(元)" width="160" align="center">
|
|
|
|
|
|
<template #default="scope">{{ formatDecimal(scope.row.laborPrice) }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
<div class="section-summary">
|
|
|
|
|
|
工价小计:{{ formatDecimal(productLaborList.reduce((sum, item) => sum + (Number(item.laborPrice) || 0), 0)) }} 元
|
|
|
|
|
|
</div>
|
2026-04-22 18:33:04 +08:00
|
|
|
|
</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";
|
2026-04-27 10:40:56 +08:00
|
|
|
|
import { listProductLabor } from "@/api/mat/productLabor";
|
|
|
|
|
|
import { listByIds, listOss } from "@/api/system/oss";
|
2026-04-22 18:33:04 +08:00
|
|
|
|
import { formatDecimal } from '@/utils/gear';
|
2026-04-27 10:40:56 +08:00
|
|
|
|
import { Document } from '@element-plus/icons-vue';
|
2026-04-22 18:33:04 +08:00
|
|
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
|
|
|
|
|
|
|
const productDetail = ref({});
|
|
|
|
|
|
const loading = ref(true);
|
|
|
|
|
|
const materialLoading = ref(false);
|
|
|
|
|
|
const additionLoading = ref(false);
|
2026-04-27 10:40:56 +08:00
|
|
|
|
const pdfFiles = ref([]);
|
|
|
|
|
|
const pdfUrlFiles = ref([]);
|
2026-04-22 18:33:04 +08:00
|
|
|
|
|
|
|
|
|
|
// 材料明细数据
|
|
|
|
|
|
const productMaterialRelationList = ref([]);
|
|
|
|
|
|
|
|
|
|
|
|
// 产品附加属性数据
|
|
|
|
|
|
const productAdditionList = ref([]);
|
2026-04-27 10:40:56 +08:00
|
|
|
|
const productLaborList = ref([]);
|
2026-04-22 18:33:04 +08:00
|
|
|
|
|
|
|
|
|
|
// 计算主材、辅材和工本
|
|
|
|
|
|
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 totalAmount = computed(() => {
|
|
|
|
|
|
const mainTotal = mainMaterials.value.reduce((sum, item) => sum + item.subtotal, 0);
|
|
|
|
|
|
const auxiliaryTotal = auxiliaryMaterials.value.reduce((sum, item) => sum + item.subtotal, 0);
|
2026-04-27 10:40:56 +08:00
|
|
|
|
const manualLaborTotal = productLaborList.value.reduce((sum, item) => {
|
|
|
|
|
|
const price = item && item.laborPrice !== undefined && item.laborPrice !== null ? Number(item.laborPrice) : 0;
|
|
|
|
|
|
return sum + (Number.isFinite(price) ? price : 0);
|
|
|
|
|
|
}, 0);
|
2026-04-28 16:53:35 +08:00
|
|
|
|
return mainTotal + auxiliaryTotal + manualLaborTotal;
|
2026-04-27 10:40:56 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const isOssIdList = (val) => {
|
|
|
|
|
|
if (val === null || val === undefined) return false;
|
|
|
|
|
|
const str = String(val).trim();
|
|
|
|
|
|
return /^[0-9]+(,[0-9]+)*$/.test(str);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const pdfDisplayList = computed(() => {
|
|
|
|
|
|
const raw = String(productDetail.value.productPdfs || '').trim();
|
|
|
|
|
|
if (!raw) return [];
|
|
|
|
|
|
if (isOssIdList(raw)) return pdfFiles.value;
|
|
|
|
|
|
return pdfUrlFiles.value;
|
2026-04-22 18:33:04 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 获取产品详情
|
|
|
|
|
|
function getProductDetail() {
|
|
|
|
|
|
const productId = route.params.id;
|
|
|
|
|
|
if (productId) {
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
getProduct(productId).then(response => {
|
|
|
|
|
|
productDetail.value = response.data;
|
|
|
|
|
|
loading.value = false;
|
2026-04-27 10:40:56 +08:00
|
|
|
|
resolvePdfFiles();
|
2026-04-22 18:33:04 +08:00
|
|
|
|
// 获取材料明细
|
|
|
|
|
|
getMaterialDetail(productId);
|
|
|
|
|
|
// 获取产品附加属性
|
|
|
|
|
|
getProductAddition(productId);
|
2026-04-27 10:40:56 +08:00
|
|
|
|
getProductLabor(productId);
|
2026-04-22 18:33:04 +08:00
|
|
|
|
}).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;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-27 10:40:56 +08:00
|
|
|
|
function getProductLabor(productId) {
|
|
|
|
|
|
listProductLabor({ productId }).then(response => {
|
|
|
|
|
|
productLaborList.value = response.rows || [];
|
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
|
productLaborList.value = [];
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolvePdfFiles() {
|
|
|
|
|
|
const raw = String(productDetail.value.productPdfs || '').trim();
|
|
|
|
|
|
if (!raw) {
|
|
|
|
|
|
pdfFiles.value = [];
|
|
|
|
|
|
pdfUrlFiles.value = [];
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (isOssIdList(raw)) {
|
|
|
|
|
|
pdfUrlFiles.value = [];
|
|
|
|
|
|
listByIds(raw).then(res => {
|
|
|
|
|
|
const list = res.data || [];
|
|
|
|
|
|
pdfFiles.value = list.map(oss => ({
|
|
|
|
|
|
name: oss.originalName || oss.fileName || String(oss.ossId),
|
|
|
|
|
|
url: oss.url,
|
|
|
|
|
|
ossId: oss.ossId
|
|
|
|
|
|
}));
|
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
|
pdfFiles.value = [];
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pdfFiles.value = [];
|
|
|
|
|
|
const urls = raw.split(',').map(s => String(s).trim()).filter(Boolean);
|
|
|
|
|
|
Promise.all(urls.map((url) =>
|
|
|
|
|
|
listOss({ url, pageNum: 1, pageSize: 1 })
|
|
|
|
|
|
.then(r => (r && r.rows && r.rows[0]) ? r.rows[0] : null)
|
|
|
|
|
|
.catch(() => null)
|
|
|
|
|
|
)).then(rows => {
|
|
|
|
|
|
pdfUrlFiles.value = urls.map((url, index) => {
|
|
|
|
|
|
const row = rows[index];
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: (row && (row.originalName || row.fileName)) || getFileName(url),
|
|
|
|
|
|
url: (row && row.url) || url,
|
|
|
|
|
|
ossId: row && row.ossId
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
|
pdfUrlFiles.value = urls.map(url => ({ name: getFileName(url), url }));
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-22 18:33:04 +08:00
|
|
|
|
// 返回列表
|
|
|
|
|
|
function handleBack() {
|
|
|
|
|
|
router.back();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-27 10:40:56 +08:00
|
|
|
|
// 获取文件名
|
|
|
|
|
|
function getFileName(url) {
|
|
|
|
|
|
if (!url) return '';
|
|
|
|
|
|
return url.substring(url.lastIndexOf('/') + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 预览PDF
|
|
|
|
|
|
function previewPdf(pdf) {
|
|
|
|
|
|
if (!pdf || !pdf.url) return;
|
|
|
|
|
|
window.open(pdf.url, '_blank');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 下载PDF
|
|
|
|
|
|
function downloadPdf(pdf) {
|
|
|
|
|
|
if (!pdf || !pdf.url) return;
|
|
|
|
|
|
const link = document.createElement('a');
|
|
|
|
|
|
link.href = pdf.url;
|
|
|
|
|
|
link.download = pdf.name || getFileName(pdf.url);
|
|
|
|
|
|
link.click();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-22 18:33:04 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
getProductDetail();
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.detail-card {
|
|
|
|
|
|
margin-bottom: 16px;
|
2026-04-22 18:33:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.header-title {
|
2026-04-22 18:33:04 +08:00
|
|
|
|
display: flex;
|
2026-04-28 16:53:35 +08:00
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 4px;
|
2026-04-22 18:33:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.product-name {
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
line-height: 22px;
|
2026-04-22 18:33:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.sub-title {
|
|
|
|
|
|
color: var(--el-text-color-secondary);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
line-height: 18px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-desc :deep(.el-descriptions__label) {
|
|
|
|
|
|
width: 88px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.remark-text {
|
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
|
word-break: break-word;
|
2026-04-22 18:33:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.section {
|
|
|
|
|
|
margin-top: 14px;
|
2026-04-22 18:33:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.section-title {
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-tabs :deep(.el-tabs__content) {
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.image-grid {
|
2026-04-22 18:33:04 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.image-item {
|
|
|
|
|
|
width: 112px;
|
|
|
|
|
|
height: 112px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
border: 1px solid var(--el-border-color-light);
|
|
|
|
|
|
background: var(--el-fill-color-blank);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.image {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
2026-04-27 10:40:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pdf-list {
|
2026-04-28 16:53:35 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 10px;
|
2026-04-27 10:40:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pdf-item {
|
2026-04-28 16:53:35 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
|
border: 1px solid var(--el-border-color-light);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: var(--el-fill-color-blank);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pdf-left {
|
2026-04-27 10:40:56 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
2026-04-28 16:53:35 +08:00
|
|
|
|
min-width: 0;
|
2026-04-27 10:40:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pdf-icon {
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.pdf-meta {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-27 10:40:56 +08:00
|
|
|
|
.pdf-name {
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
2026-04-28 16:53:35 +08:00
|
|
|
|
max-width: 320px;
|
2026-04-27 10:40:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.pdf-sub {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: var(--el-text-color-secondary);
|
|
|
|
|
|
line-height: 18px;
|
2026-04-22 18:33:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.pdf-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
2026-04-22 18:33:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.total-badge {
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #f56c6c;
|
2026-04-22 18:33:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.cost-section {
|
|
|
|
|
|
margin-top: 14px;
|
2026-04-22 18:33:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.cost-title {
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
margin-bottom: 10px;
|
2026-04-22 18:33:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 16:53:35 +08:00
|
|
|
|
.section-summary {
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
background-color: var(--el-fill-color-light);
|
|
|
|
|
|
border-top: 1px solid var(--el-border-color-light);
|
|
|
|
|
|
margin-top: -1px;
|
2026-04-22 18:33:04 +08:00
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
2026-04-27 10:40:56 +08:00
|
|
|
|
</style>
|