Files
klp-oa/klp-ui/src/views/crm/contract/components/ContractTabs.vue
砂糖 475a051b1c fix: 修复多处表单与接口适配问题,优化钢卷状态筛选逻辑
1. 修复SalaryPanel输入框边框缺失样式
2. 调整wms报表日期范围计算与视图类型默认值
3. 统一多页面钢卷状态筛选组件:替换为下拉选项新增历史卷,新增状态转换逻辑,修正接口传参格式
4. 同步crm合同、业务员页面的钢卷筛选适配改动
2026-07-13 11:19:27 +08:00

556 lines
21 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">
<el-form :inline="true" size="small" class="coil-filter-bar">
<el-form-item label="入场卷号">
<el-input v-model="productCoilBo.enterCoilNo" placeholder="请输入入场钢卷号" clearable @keyup.enter.native="handleProductFilter" style="width: 160px;" />
</el-form-item>
<el-form-item label="当前卷号">
<el-input v-model="productCoilBo.currentCoilNo" placeholder="请输入当前钢卷号" clearable @keyup.enter.native="handleProductFilter" style="width: 160px;" />
</el-form-item>
<el-form-item label="逻辑库区">
<warehouse-select v-model="productCoilBo.warehouseId" placeholder="请选择逻辑库位" style="width: 160px;" clearable />
</el-form-item>
<el-form-item label="物料类型">
<el-select v-model="productCoilBo.itemType" placeholder="全部" clearable style="width: 100px;" @change="handleProductFilter">
<el-option label="成品" value="product" />
<el-option label="原料" value="raw_material" />
</el-select>
</el-form-item>
<el-form-item label="钢卷状态">
<el-select v-model="productCoilBo.coilStatusType" placeholder="全部" clearable style="width: 100px;" @change="handleProductCoilStatusChange">
<el-option label="在库" value="in_stock" />
<el-option label="已发货" value="shipped" />
<el-option label="历史卷" value="history" />
</el-select>
</el-form-item>
<el-form-item label="质量状态">
<muti-select v-model="productCoilBo.qualityStatusCsv" :options="dict.type.coil_quality_status" placeholder="请选择品质" clearable style="width: 160px;" />
</el-form-item>
<template v-if="productCoilBo.itemType">
<el-form-item label="品名">
<muti-select v-model="productCoilBo.itemName" :options="dict.type.coil_itemname" placeholder="请选择物料" clearable style="width: 160px;" />
</el-form-item>
<el-form-item label="材质">
<muti-select v-model="productCoilBo.itemMaterial" :options="dict.type.coil_material" placeholder="请选择材质" clearable style="width: 160px;" />
</el-form-item>
<el-form-item label="规格">
<memo-input v-model="productCoilBo.itemSpecification" storageKey="coilSpec" placeholder="请选择规格" clearable style="width: 160px;" />
</el-form-item>
<el-form-item label="厂家">
<muti-select v-model="productCoilBo.itemManufacturer" :options="dict.type.coil_manufacturer" placeholder="请选择厂家" clearable style="width: 160px;" />
</el-form-item>
</template>
<el-form-item>
<el-button type="primary" size="mini" @click="handleProductFilter">查询</el-button>
<el-button size="mini" @click="resetProductFilter">重置</el-button>
</el-form-item>
</el-form>
<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">
<el-form :inline="true" size="small" class="coil-filter-bar">
<el-form-item label="入场卷号">
<el-input v-model="deliveryCoilBo.enterCoilNo" placeholder="请输入入场钢卷号" clearable @keyup.enter.native="handleDeliveryFilter" style="width: 160px;" />
</el-form-item>
<el-form-item label="当前卷号">
<el-input v-model="deliveryCoilBo.currentCoilNo" placeholder="请输入当前钢卷号" clearable @keyup.enter.native="handleDeliveryFilter" style="width: 160px;" />
</el-form-item>
<el-form-item label="逻辑库区">
<warehouse-select v-model="deliveryCoilBo.warehouseId" placeholder="请选择逻辑库位" style="width: 160px;" clearable />
</el-form-item>
<el-form-item label="物料类型">
<el-select v-model="deliveryCoilBo.itemType" placeholder="全部" clearable style="width: 100px;" @change="handleDeliveryFilter">
<el-option label="成品" value="product" />
<el-option label="原料" value="raw_material" />
</el-select>
</el-form-item>
<el-form-item label="钢卷状态">
<el-select v-model="deliveryCoilBo.coilStatusType" placeholder="全部" clearable style="width: 100px;" @change="handleDeliveryCoilStatusChange">
<el-option label="在库" value="in_stock" />
<el-option label="已发货" value="shipped" />
<el-option label="历史卷" value="history" />
</el-select>
</el-form-item>
<el-form-item label="质量状态">
<muti-select v-model="deliveryCoilBo.qualityStatusCsv" :options="dict.type.coil_quality_status" placeholder="请选择品质" clearable style="width: 160px;" />
</el-form-item>
<template v-if="deliveryCoilBo.itemType">
<el-form-item label="品名">
<muti-select v-model="deliveryCoilBo.itemName" :options="dict.type.coil_itemname" placeholder="请选择物料" clearable style="width: 160px;" />
</el-form-item>
<el-form-item label="材质">
<muti-select v-model="deliveryCoilBo.itemMaterial" :options="dict.type.coil_material" placeholder="请选择材质" clearable style="width: 160px;" />
</el-form-item>
<el-form-item label="规格">
<memo-input v-model="deliveryCoilBo.itemSpecification" storageKey="coilSpec" placeholder="请选择规格" clearable style="width: 160px;" />
</el-form-item>
<el-form-item label="厂家">
<muti-select v-model="deliveryCoilBo.itemManufacturer" :options="dict.type.coil_manufacturer" placeholder="请选择厂家" clearable style="width: 160px;" />
</el-form-item>
</template>
<el-form-item>
<el-button type="primary" size="mini" @click="handleDeliveryFilter">查询</el-button>
<el-button size="mini" @click="resetDeliveryFilter">重置</el-button>
</el-form-item>
</el-form>
<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 WarehouseSelect from "@/components/KLPService/WarehouseSelect";
import MemoInput from "@/components/MemoInput";
import MutiSelect from "@/components/MutiSelect";
// 导入可能需要的组件
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,
WarehouseSelect,
MemoInput,
MutiSelect
},
dicts: ['coil_itemname', 'coil_material', 'coil_manufacturer', 'coil_quality_status'],
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,
productCoilBo: {},
deliveryCoilList: [],
deliveryCoilStatistics: {},
deliveryPagination: { total: 0, currentPage: 1, pageSize: 20 },
deliveryCoilLoading: false,
deliveryCoilBo: {},
};
},
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({
orderId: this.orderId,
pageNum: this.deliveryPagination.currentPage,
pageSize: this.deliveryPagination.pageSize,
coilBo: this.deliveryCoilBo
}).then(res => {
this.deliveryCoilList = res.rows || [];
this.deliveryPagination.total = res.total || 0;
}).finally(() => {
this.deliveryCoilLoading = false;
});
},
fetchProductCoils() {
this.productCoilLoading = true;
listProductCoilsByContract({
orderId: this.orderId,
pageNum: this.productPagination.currentPage,
pageSize: this.productPagination.pageSize,
coilBo: this.productCoilBo
}).then(res => {
this.productCoilList = res.rows || [];
this.productPagination.total = res.total || 0;
}).finally(() => {
this.productCoilLoading = false;
});
},
fetchDeliveryCoilStatistics() {
getDeliveryCoilsStatisticsByOrder({ orderId: this.orderId, coilBo: this.deliveryCoilBo }).then(res => {
this.deliveryCoilStatistics = res.data || {};
});
},
fetchProductCoilStatistics() {
getProductCoilsStatisticsByContract({ orderId: this.orderId, coilBo: this.productCoilBo }).then(res => {
this.productCoilStatistics = res.data || {};
});
},
handleProductFilter() {
this.productCoilBo.selectType = this.productCoilBo.itemType || '';
this.productPagination.currentPage = 1;
this.fetchProductCoils();
this.fetchProductCoilStatistics();
},
resetProductFilter() {
this.productCoilBo = {};
this.handleProductFilter();
},
handleProductCoilStatusChange(val) {
if (val === 'in_stock') {
this.productCoilBo.status = 0;
this.productCoilBo.dataType = 1;
} else if (val === 'shipped') {
this.productCoilBo.status = 1;
this.productCoilBo.dataType = undefined;
} else if (val === 'history') {
this.productCoilBo.status = 0;
this.productCoilBo.dataType = 0;
} else {
this.productCoilBo.status = undefined;
this.productCoilBo.dataType = undefined;
}
},
handleDeliveryFilter() {
this.deliveryCoilBo.selectType = this.deliveryCoilBo.itemType || '';
this.deliveryPagination.currentPage = 1;
this.fetchDeliveryCoils();
this.fetchDeliveryCoilStatistics();
},
resetDeliveryFilter() {
this.deliveryCoilBo = {};
this.handleDeliveryFilter();
},
handleDeliveryCoilStatusChange(val) {
if (val === 'in_stock') {
this.deliveryCoilBo.status = 0;
this.deliveryCoilBo.dataType = 1;
} else if (val === 'shipped') {
this.deliveryCoilBo.status = 1;
this.deliveryCoilBo.dataType = undefined;
} else if (val === 'history') {
this.deliveryCoilBo.status = 0;
this.deliveryCoilBo.dataType = 0;
} else {
this.deliveryCoilBo.status = undefined;
this.deliveryCoilBo.dataType = undefined;
}
},
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>