Files
klp-oa/klp-ui/src/views/crm/contract/components/ContractTabs.vue

556 lines
21 KiB
Vue
Raw Normal View History

<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>