Files
klp-oa/klp-ui/src/views/crm/contract/components/ContractTabs.vue
砂糖 f617bb884d refactor(crm/contract): 重构合同页面布局与Tab组件
1.  移除合同页面的拖拽拆分面板功能,合并预览区到Tab页签
2.  将原生el-tabs替换为自定义Tab头部,新增合同信息页签
3.  调整产品类型列表列宽,优化页面展示效果
4.  精简冗余代码与样式,提升页面性能与可维护性
2026-07-07 11:22:17 +08:00

403 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="contract-tabs">
<div v-if="orderId" class="tabs-content">
<!-- 自定义Tab头部 -->
<div class="tab-header">
<span
class="tab-item"
:class="{ active: activeTab === 'contract' }"
@click="activeTab = 'contract'"
>合同信息</span>
<span
class="tab-item"
:class="{ active: activeTab === 'finance' }"
@click="activeTab = 'finance'"
>财务状态</span>
<span
class="tab-item"
:class="{ active: activeTab === 'dispute' }"
@click="activeTab = 'dispute'"
>订单异议</span>
<span
class="tab-item"
:class="{ active: activeTab === 'product' }"
@click="activeTab = 'product'"
>生产成果</span>
<span
class="tab-item"
:class="{ active: activeTab === 'coil' }"
@click="activeTab = 'coil'"
>发货配卷</span>
<span
class="tab-item"
:class="{ active: activeTab === 'delivery' }"
@click="activeTab = 'delivery'"
>发货单据</span>
<span
class="tab-item"
:class="{ active: activeTab === 'attachment' }"
@click="activeTab = 'attachment'"
>合同附件</span>
</div>
<!-- Tab内容区域可滚动 -->
<div class="tab-body">
<!-- 合同信息 -->
<div v-show="activeTab === 'contract'">
<ContractPreview :contract="currentOrder" @updateStatus="$emit('updateStatus', $event)" />
</div>
<!-- 财务状态 -->
<div v-show="activeTab === 'finance'">
<ReceiveTable :order="currentOrder" />
</div>
<!-- 订单异议 -->
<div v-show="activeTab === 'dispute'">
<OrderObjection :order="currentOrder" />
</div>
<!-- 生产成果 -->
<div v-show="activeTab === 'product'" v-loading="productCoilLoading">
<CoilTable ref="productCoilTable" :data="productCoilList || []" :showSelection="true"
:pagination="productPagination" :total-statistics="productTotalStatistics"
@selection-change="handleProductSelectionChange"
@page-change="handleProductPageChange">
<template slot="filter-actions" slot-scope="{ selectedRows }">
<el-button type="primary" size="mini" icon="el-icon-refresh"
:disabled="!selectedRows.length" @click="handleBatchTransferContract(selectedRows)">
批量转单
</el-button>
</template>
</CoilTable>
</div>
<!-- 发货配卷 -->
<div v-show="activeTab === 'coil'" v-loading="deliveryCoilLoading">
<CoilTable :data="deliveryCoilList || []"
:pagination="deliveryPagination" :total-statistics="deliveryTotalStatistics"
@page-change="handleDeliveryPageChange" />
</div>
<!-- 发货单据 -->
<div v-show="activeTab === 'delivery'">
<DeliveryTable :data="deliveryWaybillList || []" />
</div>
<!-- 合同附件 -->
<div v-show="activeTab === 'attachment'">
<div class="attachment-item">
<h4>商务附件</h4>
<FileList :oss-ids="contractAttachment" />
</div>
<div class="attachment-item">
<h4>技术附件</h4>
<FileList :oss-ids="technicalAgreement" />
</div>
<div class="attachment-item">
<h4>排产函</h4>
<FileList :oss-ids="otherAttachment" />
</div>
<div class="attachment-item">
<h4>其他附件</h4>
<FileList :oss-ids="form.annexFiles" />
</div>
</div>
</div>
<!-- 批量转单 - 选择目标合同弹窗 -->
<el-dialog title="批量转单" :visible.sync="batchTransferDialogVisible" width="500px" append-to-body
@closed="batchTargetContractId = ''">
<div style="margin-bottom: 12px;">
已选择 <strong>{{ (batchTransferCoilIds || []).length }}</strong> 卷钢卷请选择目标合同
</div>
<ContractSelect v-model="batchTargetContractId" mode="all" />
<span slot="footer">
<el-button @click="batchTransferDialogVisible = false">取消</el-button>
<el-button type="primary" :disabled="!batchTargetContractId" :loading="batchTransferLoading"
@click="confirmBatchTransfer">确认转单</el-button>
</span>
</el-dialog>
</div>
<div v-else class="no-selection">
<el-empty description="请先选择合同" />
</div>
</div>
</template>
<script>
import CoilTable from "../../components/CoilTable.vue";
import ContractSelect from "@/components/KLPService/ContractSelect";
import { batchUpdateContractCoil } from "@/api/wms/coil";
import FileList from "@/components/FileList";
import DeliveryTable from "../../components/DeliveryTable.vue";
import ContractPreview from "./ContractPreview.vue";
// 导入可能需要的组件
import OrderDetail from "../../components/OrderDetail.vue";
import OrderEdit from "../../components/OrderEdit.vue";
import ReceiveTable from "../../components/ReceiveTable.vue";
import OrderObjection from "../../components/OrderObjection.vue";
import OrderRecord from "../../components/OrderRecord.vue";
import { listDeliveryCoilsByOrder, getDeliveryCoilsStatisticsByOrder, listProductCoilsByContract, getProductCoilsStatisticsByContract } from "@/api/crm/coil";
export default {
name: "ContractTabs",
components: {
CoilTable,
ContractSelect,
FileList,
DeliveryTable,
ContractPreview,
OrderDetail,
OrderEdit,
ReceiveTable,
OrderObjection,
OrderRecord
},
props: {
orderId: {
type: [Number, String],
default: null
},
loading: {
type: Boolean,
default: false
},
// 附件字段
contractAttachment: {
type: String,
default: ''
},
technicalAgreement: {
type: String,
default: ''
},
otherAttachment: {
type: String,
default: ''
},
// 新增必要的props
form: {
type: Object,
default: () => ({})
},
currentOrder: {
type: Object,
default: () => ({})
},
deliveryWaybillList: {
type: Array,
default: () => []
}
},
data() {
return {
// 活动tab
activeTab: "contract",
selectedProductRows: [],
batchTransferDialogVisible: false,
batchTargetContractId: '',
batchTransferCoilIds: [],
batchTransferLoading: false,
productCoilList: [],
productCoilStatistics: {},
productPagination: { total: 0, currentPage: 1, pageSize: 20 },
productCoilLoading: false,
deliveryCoilList: [],
deliveryCoilStatistics: {},
deliveryPagination: { total: 0, currentPage: 1, pageSize: 20 },
deliveryCoilLoading: false,
};
},
computed: {
productTotalStatistics() {
const stats = this.productCoilStatistics;
if (!stats || Object.keys(stats).length === 0) return null;
return {
totalCoils: stats.total_count || 0,
totalNetWeight: stats.total_net_weight || 0
};
},
deliveryTotalStatistics() {
const stats = this.deliveryCoilStatistics;
if (!stats || Object.keys(stats).length === 0) return null;
return {
totalCoils: stats.total_count || 0,
totalNetWeight: stats.total_net_weight || 0
};
}
},
watch: {
orderId: {
handler(newVal, oldVal) {
if (newVal && newVal !== oldVal) {
this.productPagination.currentPage = 1;
this.deliveryPagination.currentPage = 1;
this.fetchDeliveryCoils();
this.fetchProductCoils();
this.fetchDeliveryCoilStatistics();
this.fetchProductCoilStatistics();
}
},
immediate: true
}
},
methods: {
handleProductSelectionChange(selection) {
this.selectedProductRows = selection;
},
handleBatchTransferContract(selectedRows) {
if (!selectedRows || !selectedRows.length) {
this.$message.warning('请先选择需要转单的钢卷');
return;
}
this.batchTransferCoilIds = selectedRows.map(row => row.coilId);
this.batchTargetContractId = '';
this.batchTransferDialogVisible = true;
},
confirmBatchTransfer() {
const coilIds = this.batchTransferCoilIds;
const contractId = this.batchTargetContractId;
if (!contractId) {
this.$message.warning('请选择目标合同');
return;
}
this.batchTransferLoading = true;
batchUpdateContractCoil(contractId, coilIds).then(res => {
this.$message.success(`成功将 ${coilIds.length} 卷钢卷转入目标合同`);
this.batchTransferDialogVisible = false;
// 刷新生产成果列表
this.fetchProductCoilStatistics();
this.fetchProductCoils();
// 清除选中
if (this.$refs.productCoilTable) {
this.$refs.productCoilTable.clearSelection();
}
}).catch(err => {
console.error('批量转单失败:', err);
this.$message.error('批量转单失败,请重试');
}).finally(() => {
this.batchTransferLoading = false;
});
},
// 解析时间
parseTime(time, pattern) {
if (!time) return '';
const d = new Date(time);
const year = d.getFullYear();
const month = (d.getMonth() + 1).toString().padStart(2, '0');
const day = d.getDate().toString().padStart(2, '0');
const hours = d.getHours().toString().padStart(2, '0');
const minutes = d.getMinutes().toString().padStart(2, '0');
const seconds = d.getSeconds().toString().padStart(2, '0');
return pattern
.replace('{y}', year)
.replace('{m}', month)
.replace('{d}', day)
.replace('{h}', hours)
.replace('{i}', minutes)
.replace('{s}', seconds);
},
fetchDeliveryCoils() {
this.deliveryCoilLoading = true;
listDeliveryCoilsByOrder(this.orderId, {
pageNum: this.deliveryPagination.currentPage,
pageSize: this.deliveryPagination.pageSize
}).then(res => {
this.deliveryCoilList = res.rows || [];
this.deliveryPagination.total = res.total || 0;
}).finally(() => {
this.deliveryCoilLoading = false;
});
},
fetchProductCoils() {
this.productCoilLoading = true;
listProductCoilsByContract(this.orderId, {
pageNum: this.productPagination.currentPage,
pageSize: this.productPagination.pageSize
}).then(res => {
this.productCoilList = res.rows || [];
this.productPagination.total = res.total || 0;
}).finally(() => {
this.productCoilLoading = false;
});
},
fetchDeliveryCoilStatistics() {
getDeliveryCoilsStatisticsByOrder(this.orderId).then(res => {
this.deliveryCoilStatistics = res.data || {};
});
},
fetchProductCoilStatistics() {
getProductCoilsStatisticsByContract(this.orderId).then(res => {
this.productCoilStatistics = res.data || {};
});
},
handleProductPageChange({ currentPage, pageSize }) {
this.productPagination.currentPage = currentPage;
this.productPagination.pageSize = pageSize;
this.fetchProductCoils();
},
handleDeliveryPageChange({ currentPage, pageSize }) {
this.deliveryPagination.currentPage = currentPage;
this.deliveryPagination.pageSize = pageSize;
this.fetchDeliveryCoils();
},
}
};
</script>
<style scoped>
.contract-tabs {
height: 100%;
display: flex;
flex-direction: column;
}
.tabs-content {
height: 100%;
display: flex;
flex-direction: column;
}
.tab-header {
flex-shrink: 0;
display: flex;
border-bottom: 2px solid #e4e7ed;
background: #fff;
}
.tab-item {
padding: 10px 20px;
cursor: pointer;
color: #606266;
font-size: 14px;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
transition: color .3s, border-color .3s;
user-select: none;
}
.tab-item:hover {
color: #409eff;
}
.tab-item.active {
color: #409eff;
border-bottom-color: #409eff;
}
.tab-body {
flex: 1;
overflow-y: auto;
}
.no-selection {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
</style>