feat(crm): 新增配卷相关API并重构合同、销售员页面的配卷管理

1.  新增crm/coil.js封装配卷相关接口,包括列表查询和统计汇总
2.  重构合同页面:移除冗余的coilList状态,改用分页API加载生产成果和发货配卷数据,新增分页和统计展示
3.  重构销售员页面:改用独立分页API获取生产成果和计划发货数据,新增分页、加载状态和全局统计
4.  升级CoilTable组件:支持分页功能和双维度统计(本页/全部)
This commit is contained in:
2026-06-30 14:38:58 +08:00
parent 81dec034b3
commit 524f8f3333
5 changed files with 362 additions and 108 deletions

View File

@@ -0,0 +1,83 @@
import request from '@/utils/request'
// ==================== 配卷列表查询 ====================
// 指定客户的发货配卷列表
export function listDeliveryCoilsByCustomer(customerId) {
return request({
url: '/crm/orderItem/coils/customer/' + customerId,
method: 'get'
})
}
// 指定合同的发货配卷列表(分页)
export function listDeliveryCoilsByOrder(orderId, query) {
return request({
url: `/crm/orderItem/coils/order/${orderId}`,
method: 'get',
params: query
})
}
// 指定销售员的发货配卷列表(分页)
export function listDeliveryCoilsByPrincipal(query) {
return request({
url: '/wms/deliveryWaybillDetail/coilListByPrincipal',
method: 'get',
params: query
})
}
// 指定销售员的生产成果列表(分页)
export function listProductCoilsBySalesman(query) {
return request({
url: '/crm/orderItem/coils/bySalesman',
method: 'get',
params: query
})
}
// 指定合同的生产成果列表(分页)
export function listProductCoilsByContract(contractId, query) {
return request({
url: `/crm/orderItem/coils/byContract/${contractId}`,
method: 'get',
params: query
})
}
// ==================== 配卷统计汇总 ====================
// 根据订单ID统计发货配卷汇总
export function getDeliveryCoilsStatisticsByOrder(orderId) {
return request({
url: `/crm/orderItem/coils/order/${orderId}/statistics`,
method: 'get'
})
}
// 根据销售员统计生产成果汇总
export function getProductCoilsStatisticsBySalesman(salesman) {
return request({
url: '/crm/orderItem/coils/bySalesman/statistics',
method: 'get',
params: { salesman }
})
}
// 根据合同ID统计生产成果汇总
export function getProductCoilsStatisticsByContract(contractId) {
return request({
url: `/crm/orderItem/coils/byContract/${contractId}/statistics`,
method: 'get'
})
}
// 根据负责人统计发货配卷汇总
export function getDeliveryCoilsStatisticsByPrincipal(principal) {
return request({
url: '/wms/deliveryWaybillDetail/coilListByPrincipal/statistics',
method: 'get',
params: { principal }
})
}

View File

@@ -2,9 +2,25 @@
<div>
<!-- 统计信息 + 操作栏 -->
<div class="coil-toolbar" style="margin-bottom: 10px; padding: 10px; background-color: #f5f7fa; border-radius: 4px; display: flex; align-items: center; justify-content: space-between;">
<div style="display: flex; align-items: center; gap: 24px;">
<div class="coil-pagination" v-if="pagination" style="display: flex; align-items: center; gap: 6px;">
<el-button size="mini" icon="el-icon-arrow-left" :disabled="pagination.currentPage <= 1" @click="goPage(pagination.currentPage - 1)">上一页</el-button>
<el-input v-model.number="inputPage" size="mini" style="width: 60px;" @keyup.enter.native="handlePageInputChange" @blur="handlePageInputChange" />
<span style="white-space: nowrap;">/ {{ totalPages }} </span>
<el-button size="mini" icon="el-icon-arrow-right" :disabled="pagination.currentPage >= totalPages" @click="goPage(pagination.currentPage + 1)">下一页</el-button>
</div>
<div class="coil-stats">
<span style="margin-right: 20px;"><strong>总卷数{{ totalCoils }}</strong></span>
<span><strong>总净重{{ totalNetWeight }} kg</strong></span>
<template v-if="totalStatistics">
<span style="margin-right: 20px;"><strong>本页卷数{{ currentPageCoils }}</strong></span>
<span style="margin-right: 20px;"><strong>本页净重{{ currentPageNetWeight }} kg</strong></span>
<span style="margin-right: 20px; color: #409EFF;"><strong>全部卷数{{ totalStatistics.totalCoils }}</strong></span>
<span style="color: #409EFF;"><strong>全部净重{{ totalStatistics.totalNetWeight }} kg</strong></span>
</template>
<template v-else>
<span style="margin-right: 20px;"><strong>总卷数{{ currentPageCoils }}</strong></span>
<span><strong>总净重{{ currentPageNetWeight }} kg</strong></span>
</template>
</div>
</div>
<div class="coil-actions">
<slot name="filter-actions" :selectedRows="selectedRows" :clearSelection="clearSelection" />
@@ -100,6 +116,16 @@ export default {
showSelection: {
type: Boolean,
default: false
},
// 分页配置 { total, currentPage, pageSize },不为 null 时启用分页
pagination: {
type: Object,
default: null
},
// 全部统计数据 { totalCoils, totalNetWeight },由外部传入
totalStatistics: {
type: Object,
default: null
}
},
components: {
@@ -110,20 +136,26 @@ export default {
CoilNo
},
computed: {
// 计算卷数
totalCoils() {
// 计算本页卷数
currentPageCoils() {
return this.data.length;
},
// 计算净重
totalNetWeight() {
// 计算本页净重
currentPageNetWeight() {
return this.data.reduce((sum, item) => {
return sum + (Number(item.netWeight) || 0);
}, 0).toFixed(2);
},
// 总页数
totalPages() {
if (!this.pagination) return 1;
return Math.max(1, Math.ceil(this.pagination.total / this.pagination.pageSize));
}
},
data() {
return {
selectedRows: [],
inputPage: 1,
floatLayerConfig: {
columns: [
{ label: '入场卷号', prop: 'enterCoilNo' },
@@ -161,6 +193,17 @@ export default {
}
}
},
watch: {
pagination: {
handler(val) {
if (val) {
this.inputPage = val.currentPage;
}
},
immediate: true,
deep: true
}
},
methods: {
handleSelectionChange(selection) {
this.selectedRows = selection;
@@ -214,6 +257,29 @@ export default {
this.trace.loading = false;
});
},
/** 跳转到指定页 */
goPage(page) {
const p = Math.max(1, Math.min(page, this.totalPages));
if (p === this.pagination.currentPage) return;
this.inputPage = p;
const newPagination = { ...this.pagination, currentPage: p };
this.$emit('update:pagination', newPagination);
this.$emit('page-change', { currentPage: p, pageSize: this.pagination.pageSize });
},
/** 输入框页码变更 */
handlePageInputChange() {
const page = Number(this.inputPage);
if (!page || page < 1) {
this.inputPage = this.pagination.currentPage;
return;
}
const p = Math.min(page, this.totalPages);
if (p === this.pagination.currentPage) {
this.inputPage = p;
return;
}
this.goPage(p);
},
}
}
</script>

View File

@@ -2,12 +2,6 @@
<div class="contract-tabs">
<div v-if="orderId" class="tabs-content">
<el-tabs v-model="activeTab" type="border-card">
<!-- <el-tab-pane label="订单编辑" name="edit" v-hasPermi="['crm:order:edit']">
<div class="order-detail" v-if="activeTab === 'edit'">
<el-descriptions title="订单明细" />
<OrderDetail :orderId="currentOrder.orderId" />
</div>
</el-tab-pane> -->
<el-tab-pane label="财务状态" name="finance" v-hasPermi="['crm:order:finance']">
<div class="order-finance" v-if="activeTab === 'finance'">
<!-- 财务状态内容 -->
@@ -22,10 +16,12 @@
</div>
</el-tab-pane>
<el-tab-pane label="生产成果" name="product">
<div class="order-record" v-if="activeTab === 'product'">
<div class="order-record" v-if="activeTab === 'product'" v-loading="productCoilLoading">
<!-- 生产成果内容 -->
<CoilTable ref="productCoilTable" :data="productList || []" :showSelection="true"
@selection-change="handleProductSelectionChange">
<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)">
@@ -36,15 +32,17 @@
</div>
</el-tab-pane>
<el-tab-pane label="发货配卷" name="coil">
<div class="order-record" v-if="activeTab === 'coil'">
<div class="order-record" v-if="activeTab === 'coil'" v-loading="deliveryCoilLoading">
<!-- 发货配卷内容 -->
<CoilTable :data="coilList" />
<CoilTable :data="deliveryCoilList || []"
:pagination="deliveryPagination" :total-statistics="deliveryTotalStatistics"
@page-change="handleDeliveryPageChange" />
</div>
</el-tab-pane>
<el-tab-pane label="发货单据" name="delivery">
<div class="order-record" v-if="activeTab === 'delivery'">
<!-- 发货单内容 -->
<DeliveryTable :data="deliveryWaybillList" />
<DeliveryTable :data="deliveryWaybillList || []" />
</div>
</el-tab-pane>
<el-tab-pane label="合同附件" name="attachment" v-hasPermi="['crm:order:record']">
@@ -65,11 +63,6 @@
<FileList :oss-ids="form.annexFiles" />
</div>
</el-tab-pane>
<!-- <el-tab-pane label="操作记录" name="record" v-hasPermi="['crm:order:record']">
<div class="order-record" v-if="activeTab === 'record'">
<OrderRecord :orderId="currentOrder.orderId" />
</div>
</el-tab-pane> -->
</el-tabs>
<!-- 批量转单 - 选择目标合同弹窗 -->
@@ -93,7 +86,6 @@
</template>
<script>
// import OrderPage from "@/views/crm/order/index.vue";
import CoilTable from "../../components/CoilTable.vue";
import ContractSelect from "@/components/KLPService/ContractSelect";
import { batchUpdateContractCoil } from "@/api/wms/coil";
@@ -107,6 +99,8 @@ 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: {
@@ -125,14 +119,6 @@ export default {
type: [Number, String],
default: null
},
coilList: {
type: Array,
default: () => []
},
productList: {
type: Array,
default: () => []
},
loading: {
type: Boolean,
default: false
@@ -172,9 +158,50 @@ export default {
batchTransferDialogVisible: false,
batchTargetContractId: '',
batchTransferCoilIds: [],
batchTransferLoading: false
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;
@@ -200,7 +227,8 @@ export default {
this.$message.success(`成功将 ${coilIds.length} 卷钢卷转入目标合同`);
this.batchTransferDialogVisible = false;
// 刷新生产成果列表
this.$emit('refresh-product');
this.fetchProductCoilStatistics();
this.fetchProductCoils();
// 清除选中
if (this.$refs.productCoilTable) {
this.$refs.productCoilTable.clearSelection();
@@ -230,6 +258,50 @@ export default {
.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>

View File

@@ -26,9 +26,9 @@
<!-- 右侧下方Tab标签页 -->
<div class="tab-panel" ref="tabPanel" style="flex: 1; overflow-y: auto;">
<ContractTabs :orderId="form.orderId" :deliveryWaybillList="wmsDeliveryWaybills" :coilList="coilList" :contract-attachment="form.businessAnnex" :technical-agreement="form.techAnnex"
:other-attachment="form.productionSchedule" :currentOrder="form" :productList="form.coilList"
@refresh-product="handleRefreshProduct" />
<ContractTabs :orderId="form.orderId" :deliveryWaybillList="wmsDeliveryWaybills"
:contract-attachment="form.businessAnnex" :technical-agreement="form.techAnnex"
:other-attachment="form.productionSchedule" :currentOrder="form" />
</div>
</div>
<div v-else style="flex: 1; display: flex; flex-direction: column;">
@@ -229,7 +229,7 @@
</template>
<script>
import { delOrder, listOrderPackaging, updateOrder, getOrder, addOrder } from "@/api/crm/order";
import { delOrder, updateOrder, getOrder, addOrder } from "@/api/crm/order";
import { addCustomer } from "@/api/crm/customer";
import { listDeliveryWaybill } from "@/api/wms/deliveryWaybill";
import dayjs from "dayjs";
@@ -261,7 +261,6 @@ export default {
financeList: [],
objectionList: [],
wmsDeliveryWaybills: [],
coilList: [],
loading: false,
tabLoading: false,
// 按钮loading
@@ -497,28 +496,10 @@ export default {
/** 行点击事件 */
handleRowClick(row) {
this.form = row;
this.getCoilList();
listDeliveryWaybill({ orderId: row.orderId, pageNum: 1, pageSize: 50 }).then(res => {
this.wmsDeliveryWaybills = res.rows || [];
})
},
/** 查询合同配卷列表 */
getCoilList() {
listOrderPackaging(this.form.orderId, { pageNum: 1, pageSize: 10000 }).then(response => {
this.coilList = response.rows || [];
})
},
/** 刷新生产成果列表(批量转单后调用) */
handleRefreshProduct() {
if (!this.form.orderId) return;
getOrder(this.form.orderId).then(res => {
if (res.data) {
this.form.coilList = res.data.coilList || [];
}
}).catch(err => {
console.error('刷新生产成果失败:', err);
});
},
/** 表单重置 */
reset() {
this.form = {

View File

@@ -76,10 +76,18 @@
<DeliveryTable :data="waybillList" table-height="calc(100vh - 240px)" />
</el-tab-pane>
<el-tab-pane label="生产成果" name="production">
<CoilTable :data="productList || []" table-height="calc(100vh - 240px)" />
<div v-loading="productCoilLoading">
<CoilTable :data="productList || []" table-height="calc(100vh - 240px)"
:pagination="productPagination" :total-statistics="productTotalStatistics"
@page-change="handleProductPageChange" />
</div>
</el-tab-pane>
<el-tab-pane label="计划发货" name="planDelivery">
<CoilTable :data="deliveryList || []" table-height="calc(100vh - 240px)" />
<div v-loading="deliveryCoilLoading">
<CoilTable :data="deliveryList || []" table-height="calc(100vh - 240px)"
:pagination="deliveryPagination" :total-statistics="deliveryTotalStatistics"
@page-change="handleDeliveryPageChange" />
</div>
</el-tab-pane>
</el-tabs>
</div>
@@ -123,7 +131,7 @@ import DeliveryTable from "../components/DeliveryTable.vue";
import { listDeliveryWaybill } from "@/api/wms/deliveryWaybill";
import { listCustomer } from "@/api/crm/customer";
import { listOrder } from "@/api/crm/order";
import { listDeliveryWaybillDetailBySaleman } from "@/api/wms/deliveryWaybillDetail";
import { listProductCoilsBySalesman, getProductCoilsStatisticsBySalesman, listDeliveryCoilsByPrincipal, getDeliveryCoilsStatisticsByPrincipal } from "@/api/crm/coil";
export default {
name: "Data",
@@ -169,8 +177,20 @@ export default {
activeTab: 'customer',
// 生产成果列表
productList: [],
// 生产成果分页
productPagination: { total: 0, currentPage: 1, pageSize: 20 },
// 生产成果统计
productCoilStatistics: {},
// 生产成果加载
productCoilLoading: false,
// 计划发货列表
deliveryList: [],
// 计划发货分页
deliveryPagination: { total: 0, currentPage: 1, pageSize: 20 },
// 计划发货统计
deliveryCoilStatistics: {},
// 计划发货加载
deliveryCoilLoading: false,
// 发货单据列表
waybillList: [],
// 跟进客户列表
@@ -196,6 +216,24 @@ export default {
rightLoading: 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
};
}
},
created() {
// const dictId = this.$route.params && this.$route.params.dictId;
const dictId = '2036334758127824897'
@@ -237,6 +275,11 @@ export default {
this.rightLoading = true;
this.selectedItem = row;
this.loadedTabs = {};
// 重置分页
this.productPagination.currentPage = 1;
this.deliveryPagination.currentPage = 1;
this.productCoilStatistics = {};
this.deliveryCoilStatistics = {};
// 只加载当前激活tab的数据
this.loadTabData(this.activeTab, row.dictValue)
},
@@ -289,64 +332,73 @@ export default {
this.rightLoading = false;
});
case 'production':
// 生产成果和contract使用同一个api获取数据
if (this.loadedTabs.contract) {
// 合同数据已加载,直接处理
this.processProductionData();
return Promise.resolve();
} else {
// 合同数据未加载,先请求合同数据
return listOrder({
salesman: dictValue,
pageNum: 1,
pageSize: 10000
}).then(response => {
this.rawOrderList = response.rows;
this.orderList = response.rows;
this.loadedTabs.contract = true;
this.processProductionData();
}).catch(() => {
this.rightLoading = false;
});
}
// 使用独立的分页接口
return this.fetchProductCoils(dictValue).then(() => {
this.fetchProductCoilStatistics(dictValue);
});
case 'planDelivery':
return listDeliveryWaybillDetailBySaleman({ principal: dictValue, pageNum: 1, pageSize: 10000 }).then(response => {
this.deliveryList = response.rows || [];
this.loadedTabs.planDelivery = true;
this.rightLoading = false;
}).catch(() => {
this.rightLoading = false;
// 使用独立的分页接口
return this.fetchDeliveryCoils(dictValue).then(() => {
this.fetchDeliveryCoilStatistics(dictValue);
});
default:
this.rightLoading = false;
return Promise.resolve();
}
},
processProductionData() {
// 扁平化处理所有合同下的生产成果(coilList)
const allCoils = [];
this.rawOrderList.forEach(order => {
if (order.coilList && Array.isArray(order.coilList)) {
allCoils.push(...order.coilList);
}
fetchProductCoils(salesman) {
this.productCoilLoading = true;
return listProductCoilsBySalesman({
salesman: salesman,
pageNum: this.productPagination.currentPage,
pageSize: this.productPagination.pageSize
}).then(res => {
this.productList = res.rows || [];
this.productPagination.total = res.total || 0;
this.loadedTabs.production = true;
this.rightLoading = false;
}).catch(() => {
this.rightLoading = false;
}).finally(() => {
this.productCoilLoading = false;
});
this.productList = allCoils;
this.loadedTabs.production = true;
this.rightLoading = false;
},
processPlanDeliveryData() {
// 扁平化处理所有合同下的计划发货数据
const allDeliveryCoils = [];
this.rawOrderList.forEach(order => {
if (order.coilList && Array.isArray(order.coilList)) {
// 可以根据需要过滤未发货的钢卷
const deliveryCoils = order.coilList.filter(coil => coil.status !== 1);
allDeliveryCoils.push(...deliveryCoils);
}
fetchProductCoilStatistics(salesman) {
getProductCoilsStatisticsBySalesman(salesman).then(res => {
this.productCoilStatistics = res.data || {};
});
this.deliveryList = allDeliveryCoils;
this.loadedTabs.planDelivery = true;
this.rightLoading = false;
},
fetchDeliveryCoils(principal) {
this.deliveryCoilLoading = true;
return listDeliveryCoilsByPrincipal({
principal: principal,
pageNum: this.deliveryPagination.currentPage,
pageSize: this.deliveryPagination.pageSize
}).then(res => {
this.deliveryList = res.rows || [];
this.deliveryPagination.total = res.total || 0;
this.loadedTabs.planDelivery = true;
this.rightLoading = false;
}).catch(() => {
this.rightLoading = false;
}).finally(() => {
this.deliveryCoilLoading = false;
});
},
fetchDeliveryCoilStatistics(principal) {
getDeliveryCoilsStatisticsByPrincipal(principal).then(res => {
this.deliveryCoilStatistics = res.data || {};
});
},
handleProductPageChange({ currentPage, pageSize }) {
this.productPagination.currentPage = currentPage;
this.productPagination.pageSize = pageSize;
this.fetchProductCoils(this.selectedItem.dictValue);
},
handleDeliveryPageChange({ currentPage, pageSize }) {
this.deliveryPagination.currentPage = currentPage;
this.deliveryPagination.pageSize = pageSize;
this.fetchDeliveryCoils(this.selectedItem.dictValue);
},
// 取消按钮
cancel() {