Files
klp-oa/klp-ui/src/views/crm/contract/components/ContractTabs.vue
砂糖 94876b1c86 feat(wms/crm): 升级钢卷查询筛选条件,新增多维度搜索能力
1. 调整筛选表单布局,拆分钢卷号为入场卷号和当前卷号
2. 新增逻辑库区、物料类型筛选,替换旧的材料类型选择
3. 将单选型质量状态改为多选,并新增品名、材质、规格、厂家筛选条件
4. 注册新增的组件并添加对应字典依赖
5. 重构接口传参方式,优化筛选参数提交逻辑
6. 修复发货状态选项,移除冗余的在途选项
2026-07-10 09:45:47 +08:00

524 lines
19 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.status" placeholder="全部" clearable style="width: 100px;">
<el-option label="在库" :value="0" />
<el-option label="已发货" :value="1" />
</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.status" placeholder="全部" clearable style="width: 100px;">
<el-option label="在库" :value="0" />
<el-option label="已发货" :value="1" />
</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,
...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({ ...this.productCoilBo, orderId: this.orderId }).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();
},
handleDeliveryFilter() {
this.deliveryCoilBo.selectType = this.deliveryCoilBo.itemType || '';
this.deliveryPagination.currentPage = 1;
this.fetchDeliveryCoils();
this.fetchDeliveryCoilStatistics();
},
resetDeliveryFilter() {
this.deliveryCoilBo = {};
this.handleDeliveryFilter();
},
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>