feat: 单据收起汇总明细,CDN改新地址并升至1.1.18

This commit is contained in:
2026-11-06 20:27:03 +08:00
parent 69608b92d7
commit 5557819e64
29 changed files with 662 additions and 524 deletions
@@ -0,0 +1,326 @@
<template>
<view>
<view class="summary-title">{{ title }}</view>
<slot name="before-list" />
<view class="summary-wrap">
<scroll-view scroll-x class="summary-scroll">
<view class="summary-table" :style="{ width: tableWidth }">
<view class="summary-row summary-head">
<view
v-for="col in columns"
:key="col.key"
class="summary-cell"
:style="cellStyle(col)"
>{{ col.label }}</view>
</view>
<view v-if="!rows.length" class="summary-empty">{{ emptyText }}</view>
<view
v-for="row in rows"
:key="row.group_key"
class="summary-row"
:class="{ 'summary-row--open': expandedKey === row.group_key }"
@click="toggleRow(row.group_key)"
>
<view
v-for="col in columns"
:key="col.key"
class="summary-cell"
:style="cellStyle(col)"
>{{ formatCell(row, col.key) }}</view>
</view>
<view v-if="rows.length" class="summary-row summary-total">
<view
v-for="col in columns"
:key="col.key"
class="summary-cell"
:style="cellStyle(col)"
>{{ totalCell(col.key) }}</view>
</view>
</view>
</scroll-view>
<view v-if="expandedRow" class="summary-fine">
<view class="summary-fineTitle">
细码({{ expandedRow.fine_codes.length }})
<text class="summary-fineHint">{{ expandedKey ? '点击行收起' : '' }}</text>
</view>
<view v-if="!expandedRow.fine_codes.length" class="summary-empty">暂无细码</view>
<scroll-view v-else scroll-x class="summary-scroll">
<view class="summary-table" :style="{ width: fineTableWidth }">
<view class="summary-row summary-head">
<view
v-for="col in fineCols"
:key="col.key"
class="summary-cell"
:style="cellStyle(col)"
>{{ col.label }}</view>
</view>
<view
v-for="(fc, fcIndex) in expandedRow.fine_codes"
:key="fc.id || fcIndex"
class="summary-row"
>
<view
v-for="col in fineCols"
:key="col.key"
class="summary-cell"
:class="{ 'summary-action': col.key === 'action' }"
:style="cellStyle(col)"
@click="onFineCellTap(col.key, fc)"
>{{ formatFineCell(fc, col.key) }}</view>
</view>
</view>
</scroll-view>
<view v-if="deletable && expandedRow.fine_codes.length" class="summary-tip">
点击「删除」移除对应细码
</view>
</view>
</view>
</view>
</template>
<script>
const SUMMARY_COLUMNS = [
{ label: '成品编号', key: 'product_code', width: 260 },
{ label: '色号', key: 'product_color_code', width: 220 },
{ label: '匹数', key: 'roll', width: 140 },
{ label: '数量', key: 'weight', width: 160 },
{ label: '染整单', key: 'quote_order_no', width: 260 },
{ label: '缸号', key: 'dye_factory_dyelot_number', width: 220 },
];
const FINE_COLUMNS = [
{ label: '卷号', key: 'volume_number', width: 140 },
{ label: '外部卷号', key: 'extern_volume_number', width: 180 },
{ label: '匹数', key: 'roll', width: 120 },
{ label: '数量', key: 'weight', width: 150 },
{ label: '仓位', key: 'warehouse_bin_name', width: 180 },
{ label: '缸号', key: 'dye_factory_dyelot_number', width: 200 },
];
const TARE_COLUMNS = [
{ label: '纸筒重量', key: 'paper_tube_weight', width: 180 },
{ label: '包装重量', key: 'package_weight', width: 180 },
];
export default {
props: {
groups: { type: Array, default: () => [] },
title: { type: String, default: '明细' },
emptyText: { type: String, default: '暂无明细' },
rollKey: { type: String, default: 'in_roll' },
weightKey: { type: String, default: 'in_weight' },
deletable: { type: Boolean, default: false },
showTareColumns: { type: Boolean, default: false },
showBarCode: { type: Boolean, default: false },
},
data() {
return {
expandedKey: '',
};
},
computed: {
columns() {
return SUMMARY_COLUMNS;
},
rows() {
const rows = [];
(this.groups || []).forEach((product) => {
(product.colors || []).forEach((color) => {
rows.push({
group_key: color.group_key,
product_code: product.product_code || '',
product_name: product.product_name || '',
product_color_code: color.product_color_code || '',
product_color_name: color.product_color_name || '',
quote_order_no: color.quote_order_no || '',
dye_factory_dyelot_number: color.dye_factory_dyelot_number || '',
roll: color[this.rollKey],
weight: color[this.weightKey],
fine_codes: color.fine_codes || [],
});
});
});
return rows;
},
expandedRow() {
return this.rows.find((row) => row.group_key === this.expandedKey) || null;
},
fineCols() {
const cols = [...FINE_COLUMNS];
if (this.showBarCode) {
const idx = cols.findIndex((col) => col.key === 'weight');
cols.splice(idx + 1, 0, { label: '条码', key: 'bar_code', width: 280 });
}
if (this.showTareColumns) {
const idx = cols.findIndex((col) => col.key === 'weight');
cols.splice(idx + 1, 0, ...TARE_COLUMNS);
}
if (this.deletable) {
cols.push({ label: '操作', key: 'action', width: 120 });
}
return cols;
},
tableWidth() {
return this.sumWidth(this.columns);
},
fineTableWidth() {
return this.sumWidth(this.fineCols);
},
totalRoll() {
return this.sumField('roll');
},
totalWeight() {
return this.sumField('weight');
},
},
watch: {
rows() {
if (this.expandedKey && !this.rows.some((row) => row.group_key === this.expandedKey)) {
this.expandedKey = '';
}
},
},
methods: {
sumWidth(cols) {
const total = cols.reduce((sum, col) => sum + (col.width || 120), 0);
return `${total}rpx`;
},
sumField(key) {
const total = this.rows.reduce((sum, row) => sum + Number(row[key] || 0), 0);
return Number(total.toFixed(2));
},
cellStyle(col) {
return {
width: `${col.width}rpx`,
minWidth: `${col.width}rpx`,
maxWidth: `${col.width}rpx`,
};
},
joinName(code, name) {
const main = code || '-';
return name ? `${main} ${name}` : main;
},
formatCell(row, key) {
if (key === 'product_code') return this.joinName(row.product_code, row.product_name);
if (key === 'product_color_code') return this.joinName(row.product_color_code, row.product_color_name);
if (key === 'roll' || key === 'weight') {
const val = row[key];
if (val === 0) return '0';
return val != null && val !== '' ? val : '-';
}
const val = row[key];
if (val === 0) return '0';
return val != null && val !== '' ? val : '-';
},
totalCell(key) {
if (key === 'product_code') return '合计';
if (key === 'roll') return this.totalRoll;
if (key === 'weight') return this.totalWeight;
return '';
},
toggleRow(key) {
this.expandedKey = this.expandedKey === key ? '' : key;
},
formatFineCell(fc, key) {
if (key === 'action') return '删除';
const val = fc[key];
if (val === 0) return '0';
return val != null && val !== '' ? val : '-';
},
onFineCellTap(key, fc) {
if (!this.deletable || key !== 'action') return;
this.$emit('delete', fc);
},
},
};
</script>
<style scoped lang="scss">
@import '@/common/pda-typography.scss';
.summary-title {
font-size: $pda-font-body;
font-weight: bold;
padding: 24rpx 26rpx 12rpx;
border-left: 6rpx solid #007AFF;
margin: 20rpx 26rpx 0;
}
.summary-wrap {
margin: 0 26rpx 16rpx;
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.summary-scroll {
width: 100%;
}
.summary-table {
border-bottom: 1rpx solid #eee;
}
.summary-row {
display: flex;
align-items: stretch;
border-bottom: 1rpx solid #f0f0f0;
}
.summary-row:last-child {
border-bottom: none;
}
.summary-head {
background: #f7f8fa;
}
.summary-row--open {
background: #f0f7ff;
}
.summary-total {
background: #fff7e6;
}
.summary-cell {
box-sizing: border-box;
display: flex;
align-items: center;
min-height: $pda-row-min-height;
padding: 12rpx 16rpx;
font-size: $pda-font-body;
color: #333;
border-right: 1rpx solid #f0f0f0;
word-break: break-all;
}
.summary-head .summary-cell {
font-weight: bold;
color: #666;
}
.summary-total .summary-cell {
font-weight: bold;
}
.summary-cell:last-child {
border-right: none;
}
.summary-empty {
padding: 28rpx 16rpx;
font-size: $pda-font-placeholder;
color: #999;
}
.summary-fine {
padding: 8rpx 0 16rpx;
}
.summary-fineTitle {
display: flex;
justify-content: space-between;
padding: 12rpx 16rpx 8rpx;
font-size: $pda-font-action;
color: #2979ff;
}
.summary-fineHint {
color: #999;
font-size: $pda-font-popup-sub;
}
.summary-action {
color: #f5222d;
}
.summary-tip {
padding: 8rpx 16rpx 0;
font-size: $pda-font-popup-sub;
color: #999;
}
</style>