Browse Source

天线导入

ecss-tx
han\hanst 2 weeks ago
parent
commit
cfedb78a96
  1. 2
      config/index.js
  2. 2
      src/api/ecss/ecss.js
  3. 3261
      src/views/modules/ecss/codelnotifyTX.vue
  4. 639
      src/views/modules/ecss/components/PackingDetailTabPallet.vue
  5. 10
      src/views/modules/ecss/createDeclaration.vue
  6. 1924
      src/views/modules/ecss/del_upload_excel_tx.vue

2
config/index.js

@ -13,7 +13,7 @@ module.exports = {
// 代理列表, 是否开启代理通过[./dev.env.js]配置
proxyTable: devEnv.OPEN_PROXY === false ? {} : {
'/proxyApi': {
target: 'http://127.0.0.1:9000',
target: 'http://127.0.0.1:9001',
changeOrigin: true,
pathRewrite: {
// 把 /proxyApi 替换成 /

2
src/api/ecss/ecss.js

@ -3,7 +3,9 @@ import { createAPI } from "@/utils/httpRequest.js";
export const previewExcel = data => createAPI(`/ecss/coDel/previewExcel`,'post',data)
export const previewExcelTX = data => createAPI(`/ecss/coDel/previewExcelTX`,'post',data)
export const saveEcssCoDelNotifyByExcel = data => createAPI(`/ecss/coDel/saveEcssCoDelNotifyByExcel`,'post',data)
export const saveEcssCoDelNotifyByExcelTX = data => createAPI(`/ecss/coDel/saveEcssCoDelNotifyByExcelTX`,'post',data)
export const modifyNotifyDetailByExcel = data => createAPI(`/ecss/coDel/modifyNotifyDetailByExcel`,'post',data)
export const searchEcssCoDelNotifyHeader = data => createAPI(`/ecss/coDel/searchEcssCoDelNotifyHeader`,'post',data)

3261
src/views/modules/ecss/codelnotifyTX.vue
File diff suppressed because it is too large
View File

639
src/views/modules/ecss/components/PackingDetailTabPallet.vue

@ -0,0 +1,639 @@
<template>
<div class="packing-detail-tab">
<!-- 批量操作按钮 -->
<div class="batch-edit-toolbar" v-if="showActions">
<el-button type="warning" size="small" icon="el-icon-s-operation" @click="openAdjustWeightDialog">
调整总毛重
</el-button>
</div>
<!-- 行合并表格 - 托盘和明细一目了然 -->
<div style="height: 350px">
<el-table
:data="flattenedTableData"
:max-height="350"
border
v-loading="dataListLoading"
:element-loading-text="loadingText"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.6)"
style="width: 100%;"
:span-method="mainTableSpanMethod"
:row-class-name="rowClassName"
:header-cell-class-name="getHeaderClassName"
:cell-class-name="getCellClassName"
show-summary
@cell-mouse-enter="handleCellMouseEnter"
@cell-mouse-leave="handleCellMouseLeave"
:summary-method="getSummaries"
:header-cell-style="{background:'#f5f7fa', color:'#606266', fontWeight:'600'}">
<!-- 托盘信息列会合并 -->
<el-table-column label="托号" prop="pallet_no" width="120" align="center">
<template slot-scope="scope">
<span class="box-info-text">{{ scope.row.pallet_no || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="托数" prop="pallet_qty" width="120" align="center">
<template slot-scope="scope">
<span class="box-info-text">{{ scope.row.pallet_qty || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="总体积" prop="volume" width="120" align="center">
<template slot-scope="scope">
<span class="box-info-text">{{ scope.row.volume !== null && scope.row.volume !== undefined ? Number(scope.row.volume).toFixed(2) : '-' }}</span>
</template>
</el-table-column>
<!-- 箱信息列会合并 -->
<el-table-column label="毛重" prop="gross_weight" width="120" align="center">
<template slot-scope="scope">
<span class="box-weight-text">{{ scope.row.gross_weight || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="净重" prop="net_weight" width="120" align="center">
<template slot-scope="scope">
<span class="box-weight-text">{{ scope.row.net_weight || '-' }}</span>
</template>
</el-table-column>
<!-- 明细信息列不合并 -->
<el-table-column label="PN" prop="pn" min-width="120" align="left">
<template slot-scope="scope">
<span class="detail-text">{{ scope.row.pn || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="数量" prop="qty" min-width="100" align="center">
<template slot-scope="scope">
<span class="detail-text">{{ formatDetailValue(scope.row.qty, 'qty') }}</span>
</template>
</el-table-column>
<el-table-column label="数量/箱" prop="qty_per_box" min-width="100" align="center">
<template slot-scope="scope">
<span class="detail-text">{{ scope.row.qty_per_box || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="箱数" prop="box_qty" min-width="100" align="center">
<template slot-scope="scope">
<span class="detail-text">{{ scope.row.box_qty || '-' }}</span>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
import { searchEcssCoDelPalletDetailData, searchEcssCoDelPalletHeaderData, selectBoxList, adjustTotalGrossWeight, updateTotalVolume } from "@/api/ecss/ecss.js"
export default {
name: "PackingDetailTabPallet",
props: {
currentRow: {
type: Object,
default: () => ({})
},
height: {
type: Number,
default: 200
},
showActions: {
type: Boolean,
default: true
}
},
data() {
return {
flattenedTableData: [], //
dataListLoading: false,
loadingText: '加载中...',
selectedBoxes: [],
currentLoadingId: null,
palletWeight: 0,
totalGrossWeight: 0,
totalVolume: null,
//
adjustWeightDialogVisible: false,
adjustWeightSaving: false,
adjustWeightForm: {
actualGrossWeight: ''
},
hoveredPalletKey: null,
hoveredBoxKey: null
}
},
computed: {
currentTotalGrossWeight() {
return this.totalGrossWeight;
}
},
watch: {
currentRow: {
handler(newVal, oldVal) {
if (oldVal && newVal && oldVal.delNo !== newVal.delNo) {
this.flattenedTableData = [];
this.selectedBoxes = [];
}
if (newVal && newVal.site) {
this.loadBoxList();
}
},
deep: true,
immediate: true
}
},
methods: {
async loadBoxList() {
if (!this.currentRow || !this.currentRow.site) {
this.flattenedTableData = [];
this.palletWeight = 0;
this.totalGrossWeight = 0;
this.totalVolume = null;
this.selectedBoxes = [];
this.currentLoadingId = null;
return;
}
const loadingId = Date.now() + '_' + Math.random();
this.currentLoadingId = loadingId;
this.flattenedTableData = [];
this.selectedBoxes = [];
this.dataListLoading = true;
try {
const queryParams = {
site: this.currentRow.site,
buNo: this.currentRow.buNo,
delNo: this.currentRow.delNo
};
const [palletResponse, boxResponse, detailResponse] = await Promise.all([
searchEcssCoDelPalletHeaderData(queryParams),
selectBoxList(queryParams),
searchEcssCoDelPalletDetailData(queryParams)
]);
if (this.currentLoadingId !== loadingId) return;
const pallets = palletResponse.data && palletResponse.data.code === 0 ? palletResponse.data.rows || [] : [];
const boxes = boxResponse.data && boxResponse.data.code === 0 ? boxResponse.data.rows || boxResponse.data.data || [] : [];
const details = detailResponse.data && detailResponse.data.code === 0 ? detailResponse.data.rows || [] : [];
this.palletWeight = pallets
.filter(item => item.palletQty > 0)
.reduce((sum, item) => sum + (Number(item.weight) || 0), 0);
// Group boxes by pallet's seq_no
const boxesMap = new Map();
boxes.forEach(box => {
const palletSeqNo = String(box.seq_no || box.seqNo);
if (!boxesMap.has(palletSeqNo)) {
boxesMap.set(palletSeqNo, []);
}
boxesMap.get(palletSeqNo).push(box);
});
// Group details by box's item_no (which is detail.seqNo)
const detailsMap = new Map();
details.forEach(detail => {
const boxItemNo = String(detail.seqNo || detail.seq_no);
if (!detailsMap.has(boxItemNo)) {
detailsMap.set(boxItemNo, []);
}
detailsMap.get(boxItemNo).push(detail);
});
const flattenedData = [];
pallets.forEach(pallet => {
const palletSeqNo = String(pallet.seqNo);
const palletBoxes = boxesMap.get(palletSeqNo) || [];
let palletRowSpan = 0;
const palletRows = [];
if (palletBoxes.length > 0) {
palletBoxes.forEach((box, boxIdx) => {
const boxItemNo = String(box.item_no || box.itemNo);
const boxDetails = detailsMap.get(boxItemNo) || [];
if (boxDetails.length > 0) {
boxDetails.forEach((detail, detailIdx) => {
palletRowSpan++;
palletRows.push({
_palletKey: palletSeqNo,
_boxKey: boxItemNo,
_isFirstRowOfPallet: boxIdx === 0 && detailIdx === 0,
_isFirstRowOfBox: detailIdx === 0,
_boxRowSpan: detailIdx === 0 ? boxDetails.length : 0,
pallet_no: pallet.palletNo,
pallet_qty: pallet.palletQty,
volume: pallet.volume,
gross_weight: box.gross_weight || box.grossWeight,
net_weight: box.net_weight || box.netWeight,
_hasDetail: true,
seqNo: detail.seqNo,
itemNo: detail.itemNo,
notifyDetailItemNo: detail.notifyDetailItemNo,
pn: detail.pn,
qty: detail.qty,
box_qty: detail.boxQty,
qty_per_box: detail.boxQty && detail.boxQty > 0 ? (detail.qty / detail.boxQty).toFixed(2) : '-'
});
});
} else {
palletRowSpan++;
palletRows.push({
_palletKey: palletSeqNo,
_boxKey: boxItemNo,
_isFirstRowOfPallet: boxIdx === 0,
_isFirstRowOfBox: true,
_boxRowSpan: 1,
pallet_no: pallet.palletNo,
pallet_qty: pallet.palletQty,
volume: pallet.volume,
gross_weight: box.gross_weight || box.grossWeight,
net_weight: box.net_weight || box.netWeight,
_hasDetail: false,
pn: '',
qty: '',
box_qty: box.box_qty || box.boxQty || '',
qty_per_box: ''
});
}
});
} else {
// Pallet has no boxes
palletRowSpan++;
palletRows.push({
_palletKey: palletSeqNo,
_boxKey: 'empty_box_' + palletSeqNo,
_isFirstRowOfPallet: true,
_isFirstRowOfBox: true,
_boxRowSpan: 1,
pallet_no: pallet.palletNo,
pallet_qty: pallet.palletQty,
volume: pallet.volume,
gross_weight: '',
net_weight: '',
_hasDetail: false,
pn: '',
qty: '',
box_qty: '',
qty_per_box: ''
});
}
if (palletRows.length > 0) {
palletRows[0]._palletRowSpan = palletRowSpan;
flattenedData.push(...palletRows);
}
});
this.flattenedTableData = flattenedData;
const palletGrossWeight = pallets.reduce((sum, item) => sum + (Number(item.grossWeight) || 0), 0);
this.totalGrossWeight = palletGrossWeight + this.palletWeight;
this.totalVolume = this.currentRow.totalVolume !== undefined && this.currentRow.totalVolume !== null
? this.currentRow.totalVolume
: null;
} catch (error) {
console.error(`[${loadingId}] 加载数据失败:`, error);
if (this.currentLoadingId === loadingId) {
this.flattenedTableData = [];
this.palletWeight = 0;
this.totalGrossWeight = 0;
}
} finally {
if (this.currentLoadingId === loadingId) {
this.dataListLoading = false;
}
}
},
mainTableSpanMethod({ row, column, rowIndex, columnIndex }) {
if (!row._palletKey) {
return { rowspan: 1, colspan: 1 };
}
//
if (columnIndex >= 0 && columnIndex <= 2) {
if (row._isFirstRowOfPallet) {
return { rowspan: row._palletRowSpan, colspan: 1 };
} else {
return { rowspan: 0, colspan: 0 };
}
}
//
if (columnIndex >= 3 && columnIndex <= 4) {
if (row._isFirstRowOfBox) {
return { rowspan: row._boxRowSpan, colspan: 1 };
} else {
return { rowspan: 0, colspan: 0 };
}
}
return { rowspan: 1, colspan: 1 };
},
rowClassName({ row, rowIndex }) {
let classes = [];
//
if (row._isLastRowOfPallet) {
classes.push('pallet-bottom-border');
}
//
if (this.hoveredPalletKey && row._palletKey === this.hoveredPalletKey) {
classes.push('hover-pallet-group');
} else if (this.hoveredBoxKey && row._boxKey === this.hoveredBoxKey) {
classes.push('hover-box-group');
}
return classes.join(' ');
},
handleCellMouseEnter(row, column, cell, event) {
if (column.property === 'pallet_no' || column.property === 'pallet_qty' || column.property === 'volume') {
this.hoveredPalletKey = row._palletKey;
this.hoveredBoxKey = null;
} else if (column.property === 'gross_weight' || column.property === 'net_weight') {
this.hoveredBoxKey = row._boxKey;
this.hoveredPalletKey = null;
}
},
handleCellMouseLeave(row, column, cell, event) {
this.hoveredPalletKey = null;
this.hoveredBoxKey = null;
},
getHeaderClassName({ column, columnIndex }) {
if (columnIndex >= 0 && columnIndex <= 2) {
return 'pallet-header';
} else if (columnIndex >= 3 && columnIndex <= 4) {
return 'box-header';
} else if (columnIndex >= 5 && columnIndex <= 8) {
return 'detail-header';
}
return '';
},
getCellClassName({ row, column, rowIndex, columnIndex }) {
if (columnIndex >= 0 && columnIndex <= 2) {
return 'pallet-cell';
} else if (columnIndex >= 3 && columnIndex <= 4) {
return 'box-cell';
} else if (columnIndex >= 5 && columnIndex <= 8) {
return 'detail-cell';
}
return '';
},
formatDetailValue(value, columnProp) {
if (value === null || value === undefined || value === '') {
return '-';
}
if (columnProp === 'qty' ) {
return value.toLocaleString();
}
return value;
},
refresh() {
this.loadBoxList();
},
getSummaries(param) {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
return;
}
if (column.property === 'pallet_qty') {
const palletFirstRows = data.filter(item => item._isFirstRowOfPallet);
const sum = palletFirstRows.reduce((prev, curr) => {
const value = Number(curr.pallet_qty);
return !isNaN(value) ? prev + value : prev;
}, 0);
sums[index] = sum;
} else if (column.property === 'volume') {
const palletFirstRows = data.filter(item => item._isFirstRowOfPallet);
const sum = palletFirstRows.reduce((prev, curr) => {
const value = Number(curr.volume);
return !isNaN(value) ? prev + value : prev;
}, 0);
sums[index] = sum.toFixed(2);
} else if (column.property === 'gross_weight') {
const boxFirstRows = data.filter(item => item._isFirstRowOfBox);
const sum = boxFirstRows.reduce((prev, curr) => {
const value = Number(curr.gross_weight);
return !isNaN(value) ? prev + value : prev;
}, 0);
sums[index] = sum.toFixed(2);
} else if (column.property === 'net_weight') {
const boxFirstRows = data.filter(item => item._isFirstRowOfBox);
const sum = boxFirstRows.reduce((prev, curr) => {
const value = Number(curr.net_weight);
return !isNaN(value) ? prev + value : prev;
}, 0);
sums[index] = sum.toFixed(2);
} else if (column.property === 'qty') {
const sum = data.filter(item => item._hasDetail).reduce((prev, curr) => {
const value = Number(curr.qty);
return !isNaN(value) ? prev + value : prev;
}, 0);
sums[index] = sum > 0 ? sum.toLocaleString() : '';
} else if (column.property === 'box_qty') {
//
let sum = 0;
const boxFirstRows = data.filter(item => item._isFirstRowOfBox);
boxFirstRows.forEach(boxRow => {
// Find all details for this box
const boxDetails = data.filter(item => item._boxKey === boxRow._boxKey && item._hasDetail);
if (boxDetails.length > 0) {
// Sum box_qty from details
boxDetails.forEach(detail => {
const value = Number(detail.box_qty);
if (!isNaN(value)) sum += value;
});
} else {
// Use box_qty from box level if no details
const value = Number(boxRow.box_qty);
if (!isNaN(value)) sum += value;
}
});
sums[index] = sum;
} else {
sums[index] = '';
}
});
return sums;
},
openAdjustWeightDialog() {
this.adjustWeightForm.actualGrossWeight = '';
this.adjustWeightDialogVisible = true;
},
async submitAdjustWeight() {
const actualGrossWeight = parseFloat(this.adjustWeightForm.actualGrossWeight);
if (isNaN(actualGrossWeight) || actualGrossWeight <= 0) {
this.$message.warning('请输入有效的实际总毛重');
return;
}
this.adjustWeightSaving = true;
try {
const adjustParams = {
site: this.currentRow.site,
buNo: this.currentRow.buNo,
delNo: this.currentRow.delNo,
actualGrossWeight: actualGrossWeight,
updateBy: this.$store.state.user.name
};
const response = await adjustTotalGrossWeight(adjustParams);
if (response.data && response.data.code === 0) {
this.$message.success('总毛重调整成功');
this.adjustWeightDialogVisible = false;
this.loadBoxList();
this.$emit('refresh');
} else {
this.$alert(response.data.msg || '调整失败', '错误', { confirmButtonText: '确定' });
}
} catch (error) {
this.$message.error('调整失败');
} finally {
this.adjustWeightSaving = false;
}
}
}
}
</script>
<style scoped>
.packing-detail-tab {
width: 100%;
}
.batch-edit-toolbar {
margin-bottom: 10px;
display: flex;
justify-content: flex-start;
gap: 10px;
}
.box-info-text {
font-weight: 600;
color: #303133;
font-size: 14px;
}
.box-weight-text {
font-weight: 600;
color: #606266;
font-size: 14px;
}
.detail-text {
color: #606266;
font-size: 13px;
}
/deep/ .el-table {
font-size: 13px;
}
/deep/ .el-table th {
background-color: #f5f7fa !important;
color: #606266 !important;
padding: 5px 0;
}
/deep/ .el-table td {
padding: 8px 0;
}
/deep/ .el-table__body td[rowspan] .cell {
display: flex;
align-items: center;
justify-content: center;
min-height: 40px;
}
/* 托盘级别颜色 */
/deep/ .el-table .pallet-header {
background-color: #e8effa !important;
}
/deep/ .el-table .pallet-cell {
background-color: #ffffff !important;
}
/* 托盘悬浮颜色 */
/deep/ .el-table--enable-row-hover .el-table__body tr.hover-row > td.pallet-cell,
/deep/ .el-table__body tr.hover-row td.pallet-cell[rowspan] {
background-color: #f5f7fa !important;
}
/* 箱级别颜色 */
/deep/ .el-table .box-header {
background-color: #f8f4eb !important;
}
/deep/ .el-table .box-cell {
background-color: #ffffff !important;
}
/* 箱悬浮颜色 */
/deep/ .el-table--enable-row-hover .el-table__body tr.hover-row > td.box-cell,
/deep/ .el-table__body tr.hover-row td.box-cell[rowspan] {
background-color: #f5f7fa !important;
}
/* 明细级别颜色 (纯白) */
/deep/ .el-table .detail-header {
background-color: #f5f7fa !important;
}
/deep/ .el-table .detail-cell {
background-color: #ffffff !important;
}
/* 明细悬浮颜色 */
/deep/ .el-table--enable-row-hover .el-table__body tr.hover-row > td.detail-cell {
background-color: #f5f7fa !important;
}
/* 整个托盘组悬浮高亮效果 */
/deep/ .el-table .hover-pallet-group td.pallet-cell {
background-color: #f5f7fa !important;
}
/deep/ .el-table .hover-pallet-group td.box-cell {
background-color: #f5f7fa !important;
}
/deep/ .el-table .hover-pallet-group td.detail-cell {
background-color: #f5f7fa !important;
}
/* 整个箱组悬浮高亮效果 */
/deep/ .el-table .hover-box-group td.box-cell {
background-color: #f5f7fa !important;
}
/deep/ .el-table .hover-box-group td.detail-cell {
background-color: #f5f7fa !important;
}
/* 增加行之间的边框,让层级更明显 */
/deep/ .el-table td, /deep/ .el-table th.is-leaf {
border-bottom: 1px solid #ebeef5;
}
/deep/ .el-table--border td, /deep/ .el-table--border th, /deep/ .el-table__body-wrapper .el-table--border.is-scrolling-left~.el-table__fixed {
border-right: 1px solid #ebeef5;
}
/* 托盘之间的分隔线 */
/deep/ .el-table .pallet-bottom-border td {
border-bottom: 1px solid #ebeef5 !important;
}
/* 移除明细最后一行的默认底边框,避免冲突 */
/deep/ .el-table::before {
height: 0px;
}
</style>

10
src/views/modules/ecss/createDeclaration.vue

@ -136,11 +136,19 @@
</el-tab-pane>
<el-tab-pane label="装箱明细" name="pallet">
<packing-detail-tab
v-if="currentRow.buNo === '01-Label' || currentRow.buNo === '03-RFID'"
ref="packingDetailTab"
:current-row="currentRow"
:height="height"
:show-actions="false">
</packing-detail-tab>
<packing-detail-tab-pallet
v-else
ref="packingDetailTabPallet"
:current-row="currentRow"
:height="height"
:show-actions="false">
</packing-detail-tab-pallet>
</el-tab-pane>
</el-tabs>
<el-dialog title="创建报关单" :close-on-click-modal="false" v-drag :visible.sync="declarationFlag" width="1100px">
@ -485,6 +493,7 @@
import {} from "@/api/sysLanguage.js"
import PackingDetailTab from "./components/PackingDetailTab.vue";
import PackingDetailTabPallet from "./components/PackingDetailTabPallet.vue";
import {
searchEcssCoDelNotifyHeaderForDanZheng,
searchEcssCoDelNotifyDetail,
@ -497,6 +506,7 @@
name: "null",
components:{
PackingDetailTab,
PackingDetailTabPallet,
},
data() {
return {

1924
src/views/modules/ecss/del_upload_excel_tx.vue
File diff suppressed because it is too large
View File

Loading…
Cancel
Save