| | |
| | | <div class="kpi-label">总生产成本</div> |
| | | <div class="kpi-value">¥{{ formatMoney(overview.totalCost) }}</div> |
| | | </div> |
| | | <div class="kpi-item kpi-raw"> |
| | | <div class="kpi-label">原料成本</div> |
| | | <div class="kpi-value">¥{{ formatMoney(overview.rawCost) }}</div> |
| | | </div> |
| | | <div class="kpi-item kpi-aux"> |
| | | <div class="kpi-label">辅料成本</div> |
| | | <div class="kpi-value">¥{{ formatMoney(overview.auxCost) }}</div> |
| | | <div class="kpi-item kpi-avg"> |
| | | <div class="kpi-label">每订单平均成本</div> |
| | | <div class="kpi-value">¥{{ formatMoney(overview.avgCostPerOrder) }}</div> |
| | | </div> |
| | | <div class="kpi-item kpi-order"> |
| | | <div class="kpi-label">订单数量</div> |
| | |
| | | </template> |
| | | <el-table :data="categorySummary" stripe class="lux-table" height="260"> |
| | | <el-table-column prop="category" label="产品类别" min-width="140" /> |
| | | <el-table-column prop="rawCost" label="原料成本(元)" align="right"> |
| | | <template #default="scope"> |
| | | <span class="price-value">{{ formatMoney(scope.row.rawCost) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="auxCost" label="辅料成本(元)" align="right"> |
| | | <template #default="scope"> |
| | | <span class="price-value">{{ formatMoney(scope.row.auxCost) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="totalCost" label="总成本(元)" align="right"> |
| | | <el-table-column prop="totalCost" label="成本(元)" align="right"> |
| | | <template #default="scope"> |
| | | <span class="cost-value">¥{{ formatMoney(scope.row.totalCost) }}</span> |
| | | </template> |
| | |
| | | <el-table-column prop="timeLabel" :label="timeColumnLabel" min-width="110" /> |
| | | <el-table-column prop="category" label="产品类别" min-width="120" /> |
| | | <el-table-column prop="orderNo" label="生产订单" min-width="150" /> |
| | | <el-table-column prop="rawCost" label="原料成本(元)" align="right"> |
| | | <template #default="scope"> |
| | | <span class="price-value">{{ formatMoney(scope.row.rawCost) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="auxCost" label="辅料成本(元)" align="right"> |
| | | <template #default="scope"> |
| | | <span class="price-value">{{ formatMoney(scope.row.auxCost) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="totalCost" label="总成本(元)" align="right"> |
| | | <el-table-column prop="totalCost" label="成本(元)" align="right"> |
| | | <template #default="scope"> |
| | | <span class="cost-value">¥{{ formatMoney(scope.row.totalCost) }}</span> |
| | | </template> |
| | |
| | | </div> |
| | | <el-table :data="detailMaterials" class="lux-table" stripe> |
| | | <el-table-column prop="materialName" label="物料名称" min-width="120" /> |
| | | <el-table-column prop="materialType" label="类型" width="94"> |
| | | <template #default="scope"> |
| | | <span |
| | | class="material-type-tag" |
| | | :class="scope.row.materialType === '原料' ? 'is-raw' : 'is-aux'" |
| | | > |
| | | {{ scope.row.materialType }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="quantity" label="投入量" align="right" min-width="140"> |
| | | <template #default="scope"> |
| | | <span class="quantity-cell"> |
| | |
| | | </el-table-column> |
| | | </el-table> |
| | | <div class="drawer-foot"> |
| | | <div class="foot-item"> |
| | | <span class="foot-label">原料</span> |
| | | <span class="foot-value no-wrap-money">¥{{ formatMoney(detailRawCost) }}</span> |
| | | </div> |
| | | <div class="foot-item"> |
| | | <span class="foot-label">辅料</span> |
| | | <span class="foot-value no-wrap-money">¥{{ formatMoney(detailAuxCost) }}</span> |
| | | </div> |
| | | <div class="foot-item total"> |
| | | <span class="foot-label">合计</span> |
| | | <span class="foot-label">成本合计</span> |
| | | <span class="foot-value no-wrap-money">¥{{ formatMoney(detailTotalCost) }}</span> |
| | | </div> |
| | | </div> |
| | |
| | | const key = keyFn(item); |
| | | if (!map.has(key)) { |
| | | map.set(key, { |
| | | rawCost: 0, |
| | | auxCost: 0, |
| | | totalCost: 0, |
| | | materials: [], |
| | | }); |
| | | } |
| | | const bucket = map.get(key); |
| | | if (item.materialType === "原料") bucket.rawCost += item.cost; |
| | | if (item.materialType === "辅料") bucket.auxCost += item.cost; |
| | | bucket.totalCost += item.cost; |
| | | bucket.materials.push(item); |
| | | } |
| | |
| | | timeLabel, |
| | | category, |
| | | orderNo, |
| | | rawCost: val.rawCost, |
| | | auxCost: val.auxCost, |
| | | totalCost: val.totalCost, |
| | | materials: val.materials, |
| | | }); |
| | |
| | | for (const [category, val] of map) { |
| | | rows.push({ |
| | | category, |
| | | rawCost: val.rawCost, |
| | | auxCost: val.auxCost, |
| | | totalCost: val.totalCost, |
| | | }); |
| | | } |
| | |
| | | }); |
| | | |
| | | const overview = computed(() => { |
| | | const rawCost = filteredRecords.value |
| | | .filter((item) => item.materialType === "原料") |
| | | .reduce((sum, item) => sum + item.cost, 0); |
| | | const auxCost = filteredRecords.value |
| | | .filter((item) => item.materialType === "辅料") |
| | | .reduce((sum, item) => sum + item.cost, 0); |
| | | const orderCount = new Set(filteredRecords.value.map((item) => item.orderNo)).size; |
| | | const totalCost = filteredRecords.value.reduce((sum, item) => sum + item.cost, 0); |
| | | return { |
| | | rawCost, |
| | | auxCost, |
| | | totalCost: rawCost + auxCost, |
| | | totalCost, |
| | | orderCount, |
| | | avgCostPerOrder: orderCount === 0 ? 0 : totalCost / orderCount, |
| | | }; |
| | | }); |
| | | |
| | |
| | | |
| | | const detailMaterials = computed(() => detailRow.value?.materials || []); |
| | | |
| | | const detailRawCost = computed(() => |
| | | detailMaterials.value |
| | | .filter((item) => item.materialType === "原料") |
| | | .reduce((sum, item) => sum + item.cost, 0) |
| | | ); |
| | | const detailAuxCost = computed(() => |
| | | detailMaterials.value |
| | | .filter((item) => item.materialType === "辅料") |
| | | .reduce((sum, item) => sum + item.cost, 0) |
| | | ); |
| | | const detailTotalCost = computed(() => |
| | | detailMaterials.value.reduce((sum, item) => sum + item.cost, 0) |
| | | ); |
| | |
| | | }; |
| | | |
| | | const handleExport = () => { |
| | | const headers = [timeColumnLabel.value, "产品类别", "生产订单", "原料成本", "辅料成本", "总成本"]; |
| | | const headers = [timeColumnLabel.value, "产品类别", "生产订单", "成本(元)"]; |
| | | const lines = tableData.value.map((row) => |
| | | [ |
| | | row.timeLabel, |
| | | row.category, |
| | | row.orderNo, |
| | | row.rawCost.toFixed(2), |
| | | row.auxCost.toFixed(2), |
| | | row.totalCost.toFixed(2), |
| | | ].join(",") |
| | | ); |
| | |
| | | |
| | | .kpi-strip { |
| | | display: grid; |
| | | grid-template-columns: repeat(4, minmax(0, 1fr)); |
| | | grid-template-columns: repeat(3, minmax(0, 1fr)); |
| | | gap: 12px; |
| | | } |
| | | |
| | |
| | | background: linear-gradient(135deg, rgba(22, 163, 74, 0.1), rgba(255, 255, 255, 0.86)); |
| | | } |
| | | |
| | | .kpi-aux { |
| | | background: linear-gradient(135deg, rgba(245, 158, 11, 0.1), rgba(255, 255, 255, 0.86)); |
| | | .kpi-avg { |
| | | background: linear-gradient(135deg, rgba(99, 102, 241, 0.14), rgba(255, 255, 255, 0.86)); |
| | | } |
| | | |
| | | .kpi-order { |