销售发货
This commit is contained in:
@@ -18,6 +18,8 @@
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "2.3.1",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@vue-office/docx": "^1.6.3",
|
||||
"@vue-office/excel": "^1.7.14",
|
||||
"@vueup/vue-quill": "1.2.0",
|
||||
"@vueuse/core": "13.3.0",
|
||||
"axios": "1.9.0",
|
||||
@@ -27,6 +29,7 @@
|
||||
"element-plus": "2.9.9",
|
||||
"file-saver": "2.0.5",
|
||||
"fuse.js": "6.6.2",
|
||||
"html2canvas": "^1.4.1",
|
||||
"i": "^0.3.7",
|
||||
"js-beautify": "1.14.11",
|
||||
"js-cookie": "3.0.5",
|
||||
|
||||
@@ -69,3 +69,15 @@ export function importProductData(data, updateSupport) {
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 查询产品附加属性(按产品ID集合返回 Map<productId, attrs>)
|
||||
export function listProductAdditionByProductIds(productIds) {
|
||||
return request({
|
||||
url: '/api/mat/productAddition/listByProductIds',
|
||||
method: 'post',
|
||||
data: productIds,
|
||||
headers: {
|
||||
repeatSubmit: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ export function listProductAdditionByProductIds(productIds) {
|
||||
return request({
|
||||
url: '/api/mat/productAddition/listByProductIds',
|
||||
method: 'post',
|
||||
data: productIds
|
||||
data: productIds,
|
||||
headers: {
|
||||
repeatSubmit: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
27
gear-ui3/src/api/oms/orderProduction.js
Normal file
27
gear-ui3/src/api/oms/orderProduction.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 订单生产记录(gear_order_production)
|
||||
|
||||
export function listOrderProduction(query) {
|
||||
return request({
|
||||
url: '/oa/orderProduction/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function initOrderProduction(orderId) {
|
||||
return request({
|
||||
url: '/oa/orderProduction/init/' + orderId,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
export function updateOrderProduction(data) {
|
||||
return request({
|
||||
url: '/oa/orderProduction',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
54
gear-ui3/src/api/oms/salesman.js
Normal file
54
gear-ui3/src/api/oms/salesman.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询销售员列表
|
||||
export function listSalesman(query) {
|
||||
return request({
|
||||
url: '/oa/salesman/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询销售员详情
|
||||
export function getSalesman(salesmanId) {
|
||||
return request({
|
||||
url: '/oa/salesman/' + salesmanId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增销售员
|
||||
export function addSalesman(data) {
|
||||
return request({
|
||||
url: '/oa/salesman',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改销售员
|
||||
export function updateSalesman(data) {
|
||||
return request({
|
||||
url: '/oa/salesman',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除销售员
|
||||
export function delSalesman(salesmanIds) {
|
||||
return request({
|
||||
url: '/oa/salesman/' + salesmanIds,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 查询销售员跟进客户
|
||||
export function listSalesmanCustomers(salesmanId, query) {
|
||||
return request({
|
||||
url: '/oa/salesman/' + salesmanId + '/customers',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
111
gear-ui3/src/api/oms/shippingOrder.js
Normal file
111
gear-ui3/src/api/oms/shippingOrder.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 发货单据(独立表 gear_shipping_order)
|
||||
// 说明:用于订单发货/物流信息记录,不与出入库/WMS 单据绑定
|
||||
|
||||
// 查询发货单据列表
|
||||
export function listShippingOrder(query) {
|
||||
return request({
|
||||
url: '/oa/shippingOrder/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 根据订单ID查询发货单据列表
|
||||
export function listShippingOrderByOrderId(orderId) {
|
||||
return request({
|
||||
url: '/oa/shippingOrder/listByOrderId/' + orderId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 查询发货单据详细
|
||||
export function getShippingOrder(shippingId) {
|
||||
return request({
|
||||
url: '/oa/shippingOrder/' + shippingId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增发货单据
|
||||
export function addShippingOrder(data) {
|
||||
return request({
|
||||
url: '/oa/shippingOrder',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改发货单据
|
||||
export function updateShippingOrder(data) {
|
||||
return request({
|
||||
url: '/oa/shippingOrder',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除发货单据
|
||||
export function delShippingOrder(shippingId) {
|
||||
return request({
|
||||
url: '/oa/shippingOrder/' + shippingId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// ================================
|
||||
// 发货计划(独立表 gear_shipping_plan)
|
||||
// ================================
|
||||
|
||||
// 查询发货计划列表(分页)
|
||||
export function listShippingPlan(query) {
|
||||
return request({
|
||||
url: '/oa/shippingPlan/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询发货计划列表(带单据数,左侧列表使用)
|
||||
export function listShippingPlanWithCount(query) {
|
||||
return request({
|
||||
url: '/oa/shippingPlan/listWithCount',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询发货计划详细
|
||||
export function getShippingPlan(planId) {
|
||||
return request({
|
||||
url: '/oa/shippingPlan/' + planId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增发货计划
|
||||
export function addShippingPlan(data) {
|
||||
return request({
|
||||
url: '/oa/shippingPlan',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改发货计划
|
||||
export function updateShippingPlan(data) {
|
||||
return request({
|
||||
url: '/oa/shippingPlan',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除发货计划
|
||||
export function delShippingPlan(planId) {
|
||||
return request({
|
||||
url: '/oa/shippingPlan/' + planId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@@ -53,3 +53,17 @@ export function getStockTrace(batchNo) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function sumStockQuantityByItemIds(itemType, itemIds) {
|
||||
return request({
|
||||
url: '/gear/stock/sumQuantityByItemIds',
|
||||
method: 'post',
|
||||
params: {
|
||||
itemType: itemType || 'product'
|
||||
},
|
||||
data: itemIds || [],
|
||||
headers: {
|
||||
repeatSubmit: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -88,13 +88,13 @@ const renderChart = () => {
|
||||
if (!productStats[productName]) {
|
||||
productStats[productName] = {
|
||||
quantity: 0, // 销量
|
||||
amount: 0 // 销售额(销量*含税单价)
|
||||
amount: 0 // 销售额(销量*单价)
|
||||
};
|
||||
}
|
||||
// 累加销量
|
||||
productStats[productName].quantity += Number(detail.quantity || 0);
|
||||
// 累加销售额
|
||||
const unitPrice = Number(detail.taxPrice || 0);
|
||||
const unitPrice = Number(detail.unitPrice || 0);
|
||||
productStats[productName].amount += unitPrice * Number(detail.quantity || 0);
|
||||
});
|
||||
|
||||
@@ -200,4 +200,4 @@ const handleResize = () => {
|
||||
width: 100%;
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
626
gear-ui3/src/views/mat/sales/order/index.vue
Normal file
626
gear-ui3/src/views/mat/sales/order/index.vue
Normal file
@@ -0,0 +1,626 @@
|
||||
<template>
|
||||
<div class="app-container sales-order-page">
|
||||
<!-- 左右两栏:左侧合同列表 + 右侧详情 -->
|
||||
<el-row :gutter="16">
|
||||
<!-- 左侧:列表区 -->
|
||||
<el-col :span="6" class="left-pane">
|
||||
<!-- 顶部:搜索 + 操作 -->
|
||||
<div class="left-toolbar">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
clearable
|
||||
placeholder="请输入关键字"
|
||||
@clear="handleQuery"
|
||||
@keyup.enter="handleQuery"
|
||||
>
|
||||
<template #append>
|
||||
<el-button icon="Search" @click="handleQuery" />
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 列表:使用现有 GearList 组件实现“卡片式” -->
|
||||
<klp-list
|
||||
:list-data="filteredList"
|
||||
list-key="contractId"
|
||||
:loading="loading"
|
||||
info1-field="contractTitle"
|
||||
info1-max-percent="70"
|
||||
info5-field="contractCode"
|
||||
@item-click="handleSelect"
|
||||
>
|
||||
<!-- 主标题:合同名称 + 合同编号 -->
|
||||
<template #info1="{ item }">
|
||||
<span class="list-title">{{ item.contractTitle }}</span>
|
||||
<span class="list-code">{{ item.contractCode }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 副标题:甲/乙方 -->
|
||||
<template #info2="{ item }">
|
||||
<div class="list-sub">
|
||||
<span>供方:{{ item.sellerName }}</span>
|
||||
<span class="ml-8">需方:{{ item.buyerName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 状态 -->
|
||||
<template #info3="{ item }">
|
||||
<el-tag :type="item.status === '已生效' ? 'success' : 'info'" size="small">
|
||||
{{ item.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<!-- 时间信息 -->
|
||||
<template #info4="{ item }">
|
||||
<div class="list-sub">
|
||||
<span>签订时间:{{ item.signDate }}</span>
|
||||
<span class="ml-8">交货日期:{{ item.deliveryDate }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 右侧动作:置顶/导出/修改/删除(雏形,只做 UI) -->
|
||||
<template #actions="{ item }">
|
||||
<div class="list-actions">
|
||||
<el-button link type="primary" icon="Top" @click.stop="handlePin(item)">置顶</el-button>
|
||||
<el-button link type="primary" icon="Download" @click.stop="handleExport(item)">导出</el-button>
|
||||
<el-button link type="primary" icon="Edit" @click.stop="handleEdit(item)">修改</el-button>
|
||||
<el-button link type="danger" icon="Delete" @click.stop="handleDelete(item)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</klp-list>
|
||||
</el-col>
|
||||
|
||||
<!-- 右侧:详情区 -->
|
||||
<el-col :span="18" class="right-pane">
|
||||
<!-- 未选中时提示 -->
|
||||
<div v-if="!selected" class="empty-tip">
|
||||
<el-empty description="请选择左侧一个订单查看详情" />
|
||||
</div>
|
||||
|
||||
<!-- 选中后显示详情 -->
|
||||
<div v-else>
|
||||
<!-- 顶部:标题 + 状态选择(雏形) -->
|
||||
<div class="detail-header">
|
||||
<div class="detail-header__title">
|
||||
<span class="detail-title">产品销售合同</span>
|
||||
<span class="detail-code">{{ selected.contractCode }}</span>
|
||||
</div>
|
||||
<el-select v-model="selected.contractStatus" size="small" style="width: 140px">
|
||||
<el-option label="草稿" value="草稿" />
|
||||
<el-option label="已生效" value="已生效" />
|
||||
<el-option label="已作废" value="已作废" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 合同基础信息:用“预览Excel表”风格展示(对齐产品管理-说明书预览的观感:边框+圆角+表格单元格) -->
|
||||
<div class="contract-excel-preview">
|
||||
<div class="contract-excel-title">
|
||||
<span>合同基础信息</span>
|
||||
<div class="contract-excel-title__actions">
|
||||
<!-- 上传合同Excel:雏形直接保存ossId串到 selected.contractExcelOssIds,后续可落库到订单主表 -->
|
||||
<file-upload
|
||||
v-model="selected.contractExcelOssIds"
|
||||
:limit="1"
|
||||
:file-size="20"
|
||||
:file-type="['xls', 'xlsx']"
|
||||
:is-show-tip="false"
|
||||
@success="handleContractExcelUploaded"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contract-excel-body">
|
||||
<table class="excel-table">
|
||||
<colgroup>
|
||||
<col style="width: 14%" />
|
||||
<col style="width: 36%" />
|
||||
<col style="width: 14%" />
|
||||
<col style="width: 36%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="excel-cell excel-cell--label">合同编号</td>
|
||||
<td class="excel-cell">{{ selected.contractCode }}</td>
|
||||
<td class="excel-cell excel-cell--label">合同状态</td>
|
||||
<td class="excel-cell">{{ selected.contractStatus }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="excel-cell excel-cell--label">供方</td>
|
||||
<td class="excel-cell">{{ selected.sellerName }}</td>
|
||||
<td class="excel-cell excel-cell--label">需方</td>
|
||||
<td class="excel-cell">{{ selected.buyerName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="excel-cell excel-cell--label">签订时间</td>
|
||||
<td class="excel-cell">{{ selected.signDate }}</td>
|
||||
<td class="excel-cell excel-cell--label">交货日期</td>
|
||||
<td class="excel-cell">{{ selected.deliveryDate }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="excel-cell excel-cell--label">签订地点</td>
|
||||
<td class="excel-cell">{{ selected.signPlace }}</td>
|
||||
<td class="excel-cell excel-cell--label">备注</td>
|
||||
<td class="excel-cell">{{ selected.remark || '—' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 产品内容:产品名称 + 生产厂家 -->
|
||||
<div class="section">
|
||||
<div class="section-title">产品内容</div>
|
||||
<div class="section-body">
|
||||
<div class="product-top">
|
||||
<div class="product-top__left">
|
||||
<span class="label">产品名称:</span>
|
||||
<el-select v-model="selected.productName" filterable clearable style="width: 240px">
|
||||
<el-option v-for="name in productNameOptions" :key="name" :label="name" :value="name" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="product-top__right">
|
||||
<span class="label">生产厂家:</span>
|
||||
<span class="value">{{ selected.factoryName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 产品明细:雏形表格,支持行内编辑与汇总 -->
|
||||
<el-table
|
||||
:data="selected.items"
|
||||
border
|
||||
size="small"
|
||||
class="detail-table"
|
||||
show-summary
|
||||
:summary-method="summaryMethod"
|
||||
>
|
||||
<el-table-column type="index" width="60" label="序号" align="center" />
|
||||
<el-table-column label="规格(mm)" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.spec" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="材质" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.material" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量(吨)" width="140" align="right">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.qty" :controls="false" :min="0" size="small" style="width: 120px" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 业务 Tab:雏形占位 -->
|
||||
<el-tabs v-model="activeTab" type="border-card" class="mt-12">
|
||||
<el-tab-pane label="订单信息" name="orderInfo">
|
||||
<el-empty description="订单信息(待完善)" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="财务状态" name="finance">
|
||||
<div class="section">
|
||||
<div class="section-title">财务状态</div>
|
||||
<div class="finance-strip">
|
||||
<div class="finance-item">
|
||||
<div class="finance-item__label">订单金额</div>
|
||||
<div class="finance-item__value">{{ formatMoney(financeSummary.orderAmount) }}</div>
|
||||
</div>
|
||||
<div class="finance-item">
|
||||
<div class="finance-item__label">已收款金额</div>
|
||||
<div class="finance-item__value">{{ formatMoney(financeSummary.receivedAmount) }}</div>
|
||||
</div>
|
||||
<div class="finance-item">
|
||||
<div class="finance-item__label">未收款金额</div>
|
||||
<div class="finance-item__value">{{ formatMoney(financeSummary.unreceivedAmount) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-title mt-12">收款明细</div>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" size="small" @click="handleAddPayment">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" size="small" :disabled="true">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" size="small" :disabled="true">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" size="small" @click="handleExportPayments">导出</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-table :data="paymentList" border size="small">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="收款日期" prop="payDate" width="160" />
|
||||
<el-table-column label="收款金额" prop="amount" width="160" align="right">
|
||||
<template #default="{ row }">
|
||||
{{ formatMoney(row.amount) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" />
|
||||
<el-table-column label="操作" width="140" align="center">
|
||||
<template #default>
|
||||
<el-button link type="primary" size="small" icon="Edit">修改</el-button>
|
||||
<el-button link type="danger" size="small" icon="Delete">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="生产成本" name="cost">
|
||||
<el-empty description="生产成本(待完善)" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="发货流程" name="ship">
|
||||
<el-empty description="发货流程(待完善)" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="合同附件" name="files">
|
||||
<el-empty description="合同附件(待完善)" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="操作记录" name="logs">
|
||||
<el-empty description="操作记录(待完善)" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import klpList from "@/components/GearList/index.vue";
|
||||
import FileUpload from "@/components/FileUpload/index.vue";
|
||||
|
||||
export default {
|
||||
name: "MatSalesOrder",
|
||||
components: { klpList, FileUpload },
|
||||
data() {
|
||||
return {
|
||||
// 页面加载态:雏形直接用本地数据,保留 loading 方便后续接接口
|
||||
loading: false,
|
||||
// 左侧搜索关键字
|
||||
keyword: "",
|
||||
// 当前选中的合同/订单
|
||||
selected: null,
|
||||
// 右侧 Tab 当前激活项
|
||||
activeTab: "finance",
|
||||
// 产品名称下拉:雏形静态配置,后续可接产品字典/接口
|
||||
productNameOptions: ["冷轧钢卷", "热轧钢卷", "镀锌卷", "不锈钢卷"],
|
||||
// 左侧列表:雏形静态数据,后续替换为接口分页
|
||||
list: [
|
||||
{
|
||||
contractId: 1,
|
||||
contractTitle: "产品销售合同",
|
||||
contractCode: "KLPYX260420-14",
|
||||
sellerName: "嘉祥科伦管理有限公司",
|
||||
buyerName: "洛阳一石科技有限公司",
|
||||
signDate: "2026-05-12",
|
||||
deliveryDate: "2026-06-15",
|
||||
signPlace: "嘉祥",
|
||||
status: "草稿",
|
||||
contractStatus: "草稿",
|
||||
productName: "冷轧钢卷",
|
||||
factoryName: "嘉祥科伦管理有限公司",
|
||||
remark: "",
|
||||
// 合同Excel附件(ossId串,逗号分隔)
|
||||
contractExcelOssIds: "",
|
||||
items: [
|
||||
{ spec: "0.38*1200", material: "SPCC", qty: 10, remark: "净边料 角包 卷" }
|
||||
]
|
||||
},
|
||||
{
|
||||
contractId: 2,
|
||||
contractTitle: "产品销售合同",
|
||||
contractCode: "KLPYX260420-06",
|
||||
sellerName: "嘉祥科伦管理有限公司",
|
||||
buyerName: "湖北广化工贸有限公司",
|
||||
signDate: "2026-04-20",
|
||||
deliveryDate: "2026-05-25",
|
||||
signPlace: "嘉祥",
|
||||
status: "已生效",
|
||||
contractStatus: "已生效",
|
||||
productName: "热轧钢卷",
|
||||
factoryName: "嘉祥科伦管理有限公司",
|
||||
remark: "",
|
||||
// 合同Excel附件(ossId串,逗号分隔)
|
||||
contractExcelOssIds: "",
|
||||
items: [
|
||||
{ spec: "2.0*1250", material: "Q235B", qty: 5, remark: "" }
|
||||
]
|
||||
}
|
||||
],
|
||||
// 财务状态:雏形数据
|
||||
financeSummary: {
|
||||
orderAmount: 30000,
|
||||
receivedAmount: 0,
|
||||
unreceivedAmount: 30000
|
||||
},
|
||||
// 收款明细:雏形为空数据
|
||||
paymentList: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 左侧列表过滤:关键字匹配合同号/甲乙方
|
||||
filteredList() {
|
||||
const kw = (this.keyword || "").trim();
|
||||
if (!kw) return this.list;
|
||||
return this.list.filter((it) => {
|
||||
return (
|
||||
(it.contractCode || "").includes(kw) ||
|
||||
(it.sellerName || "").includes(kw) ||
|
||||
(it.buyerName || "").includes(kw)
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 搜索:雏形仅触发计算属性刷新,后续可接接口
|
||||
handleQuery() {},
|
||||
// 左侧选中:加载详情(雏形直接赋值)
|
||||
handleSelect(item) {
|
||||
this.selected = item || null;
|
||||
this.activeTab = "finance";
|
||||
this.syncFinanceSummary();
|
||||
},
|
||||
// 新增:雏形占位,后续可弹窗录入合同/订单信息
|
||||
handleAdd() {
|
||||
this.$modal && this.$modal.msgInfo && this.$modal.msgInfo("新增(雏形)");
|
||||
},
|
||||
// 置顶:雏形占位
|
||||
handlePin() {},
|
||||
// 导出:雏形占位
|
||||
handleExport() {},
|
||||
// 修改:雏形占位
|
||||
handleEdit() {},
|
||||
// 删除:雏形占位
|
||||
handleDelete() {},
|
||||
// 财务:新增收款明细(雏形占位)
|
||||
handleAddPayment() {
|
||||
this.$modal && this.$modal.msgInfo && this.$modal.msgInfo("新增收款明细(雏形)");
|
||||
},
|
||||
// 财务:导出收款明细(雏形占位)
|
||||
handleExportPayments() {},
|
||||
// 金额格式化:雏形使用 toFixed
|
||||
formatMoney(val) {
|
||||
const n = Number(val || 0);
|
||||
if (Number.isNaN(n)) return "0.00";
|
||||
return n.toFixed(2);
|
||||
},
|
||||
// 表格汇总:合计数量/金额(雏形)
|
||||
summaryMethod({ columns, data }) {
|
||||
const sums = [];
|
||||
const totalQty = data.reduce((acc, it) => acc + Number(it.qty || 0), 0);
|
||||
|
||||
columns.forEach((col, index) => {
|
||||
if (index === 0) {
|
||||
sums[index] = "合计";
|
||||
return;
|
||||
}
|
||||
if (col.label === "数量(吨)") {
|
||||
sums[index] = totalQty;
|
||||
return;
|
||||
}
|
||||
sums[index] = "";
|
||||
});
|
||||
|
||||
return sums;
|
||||
},
|
||||
// 根据明细同步财务汇总(雏形:用含税总额当订单金额)
|
||||
syncFinanceSummary() {
|
||||
if (!this.selected) return;
|
||||
const orderAmount = 0;
|
||||
const receivedAmount = (this.paymentList || []).reduce((acc, it) => acc + Number(it.amount || 0), 0);
|
||||
this.financeSummary.orderAmount = orderAmount;
|
||||
this.financeSummary.receivedAmount = receivedAmount;
|
||||
this.financeSummary.unreceivedAmount = orderAmount - receivedAmount;
|
||||
},
|
||||
// 合同Excel上传完成:雏形仅做提示,后续可在这里调用接口保存到数据库
|
||||
handleContractExcelUploaded() {
|
||||
this.$modal && this.$modal.msgSuccess && this.$modal.msgSuccess("合同Excel已上传");
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 默认选中第一条,方便看到右侧雏形效果
|
||||
if (this.list.length > 0) {
|
||||
this.handleSelect(this.list[0]);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sales-order-page {
|
||||
height: calc(100vh - 120px);
|
||||
}
|
||||
|
||||
.left-pane {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.left-toolbar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
font-weight: 600;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.list-code {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.list-sub {
|
||||
color: #606266;
|
||||
font-size: 12px;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ml-8 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.list-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.right-pane {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
height: 520px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.detail-header__title {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.detail-code {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.contract-excel-preview {
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.contract-excel-title {
|
||||
padding: 8px 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
background: var(--el-fill-color-lighter);
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.contract-excel-title__actions :deep(.el-button) {
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.contract-excel-title__actions :deep(.upload-file-list) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.contract-excel-body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.excel-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.excel-cell {
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
color: #303133;
|
||||
word-break: break-word;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.excel-cell--label {
|
||||
background: var(--el-fill-color-light);
|
||||
color: #606266;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.section-body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.product-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.detail-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mt-12 {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.finance-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.finance-item {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.finance-item__label {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.finance-item__value {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,17 +26,6 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<p class="text-gray-500 text-sm">总销售额</p>
|
||||
<h3 class="text-2xl font-bold">¥{{ totalSales.toFixed(2) }}</h3>
|
||||
</div>
|
||||
<el-icon class="text-warning text-xl"><Money /></el-icon>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card">
|
||||
<div class="flex justify-between items-center">
|
||||
@@ -74,7 +63,6 @@
|
||||
<span>产品销售排行</span>
|
||||
<el-select v-model="productRankType" style="width: 100px;" placeholder="统计类型" size="small" @change="renderProductRankChart">
|
||||
<el-option label="销量" value="quantity" />
|
||||
<el-option label="销售额" value="amount" />
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
@@ -119,12 +107,6 @@
|
||||
<el-table-column prop="orderCode" label="订单编号" />
|
||||
<el-table-column prop="customerName" label="客户名称" />
|
||||
<el-table-column prop="salesManager" label="销售经理" />
|
||||
<el-table-column
|
||||
prop="taxAmount"
|
||||
label="订单金额"
|
||||
align="right"
|
||||
:formatter="(row) => `¥${Number(row.taxAmount).toFixed(2)}`"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="orderStatus"
|
||||
label="订单状态"
|
||||
@@ -155,7 +137,7 @@
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import {
|
||||
UserFilled, ShoppingCart, Money, RefreshRight
|
||||
UserFilled, ShoppingCart, RefreshRight
|
||||
} from '@element-plus/icons-vue'
|
||||
import { listOrder } from '@/api/oms/order'
|
||||
import { listOrderDetail } from '@/api/oms/orderDetail'
|
||||
@@ -194,12 +176,6 @@ const followUpStatusLabel = {
|
||||
// 指标计算(computed确保响应式)
|
||||
const totalCustomers = computed(() => customers.value.length)
|
||||
const totalOrders = computed(() => orders.value.length)
|
||||
const totalSales = computed(() => {
|
||||
return orders.value.reduce(
|
||||
(sum, order) => sum + Number(order.taxAmount || 0),
|
||||
0
|
||||
)
|
||||
})
|
||||
const returnExchangeRate = computed(() => {
|
||||
if (totalOrders.value === 0) return 0
|
||||
return (returnExchanges.value.length / totalOrders.value) * 100
|
||||
@@ -250,7 +226,7 @@ onMounted(async () => {
|
||||
|
||||
// ---------- 图表渲染函数 ----------
|
||||
|
||||
// 订单趋势图(双轴:订单数+销售额)
|
||||
// 订单趋势图(订单数)
|
||||
const renderOrderTrendChart = () => {
|
||||
const chartDom = orderTrendChart.value
|
||||
if (!chartDom) return
|
||||
@@ -266,19 +242,16 @@ const renderOrderTrendChart = () => {
|
||||
dates.push(date.toLocaleDateString())
|
||||
}
|
||||
|
||||
// 按日期统计订单数和销售额
|
||||
// 按日期统计订单数
|
||||
const orderCountMap = {}
|
||||
const salesMap = {}
|
||||
dates.forEach(date => {
|
||||
orderCountMap[date] = 0
|
||||
salesMap[date] = 0
|
||||
})
|
||||
orders.value.forEach(order => {
|
||||
// 假设订单有createTime字段,需与dates格式匹配
|
||||
const orderDate = new Date(order.createTime).toLocaleDateString()
|
||||
if (orderCountMap[orderDate] !== undefined) {
|
||||
orderCountMap[orderDate]++
|
||||
salesMap[orderDate] += Number(order.taxAmount || 0)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -287,35 +260,19 @@ const renderOrderTrendChart = () => {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross' }
|
||||
},
|
||||
legend: { data: ['订单数', '销售额'] },
|
||||
legend: { data: ['订单数'] },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dates,
|
||||
axisLabel: { rotate: 30, fontSize: 12 }
|
||||
},
|
||||
yAxis: [
|
||||
{ type: 'value', name: '订单数', min: 0 },
|
||||
{
|
||||
type: 'value',
|
||||
name: '销售额',
|
||||
min: 0,
|
||||
axisLabel: { formatter: '¥{value}' }
|
||||
}
|
||||
],
|
||||
yAxis: { type: 'value', name: '订单数', min: 0 },
|
||||
series: [
|
||||
{
|
||||
name: '订单数',
|
||||
type: 'bar',
|
||||
data: dates.map(date => orderCountMap[date]),
|
||||
barWidth: '40%'
|
||||
},
|
||||
{
|
||||
name: '销售额',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: dates.map(date => salesMap[date]),
|
||||
smooth: true,
|
||||
lineStyle: { width: 2 }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -340,7 +297,7 @@ const renderProductRankChart = () => {
|
||||
}
|
||||
productStats[productName].quantity += Number(detail.quantity || 0)
|
||||
productStats[productName].amount +=
|
||||
Number(detail.quantity || 0) * Number(detail.taxPrice || 0)
|
||||
Number(detail.quantity || 0) * Number(detail.unitPrice || 0)
|
||||
})
|
||||
|
||||
// 转换为图表数据(取Top10)
|
||||
@@ -478,4 +435,4 @@ const viewOrderDetail = (row) => {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -16,26 +16,6 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" size="small" @click="handleExport">导出</el-button>
|
||||
</el-col>
|
||||
|
||||
<!-- 含税部分 -->
|
||||
<el-col :span="8">
|
||||
<span>含税金额:</span>
|
||||
<span style="margin-right: 10px;">{{ orderInfo?.taxAmount }}</span>
|
||||
<span style="margin-right: 10px;">-</span>
|
||||
<span style="margin-right: 10px;">{{ actualAmount }}</span>
|
||||
<span style="margin-right: 10px;">=</span>
|
||||
<span style="color: red; margin-right: 10px;">{{ amountDifference }}</span>
|
||||
</el-col>
|
||||
|
||||
<!-- 无税部分 -->
|
||||
<el-col :span="8">
|
||||
<span>无税金额:</span>
|
||||
<span style="margin-right: 10px;">{{ orderInfo?.noTaxAmount }}</span>
|
||||
<span style="margin-right: 10px;">-</span>
|
||||
<span style="margin-right: 10px;">{{ noTaxAmount }}</span>
|
||||
<span style="margin-right: 10px;">=</span>
|
||||
<span style="color: red; margin-right: 10px;">{{ noTaxAmountDifference }}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="orderDetailList">
|
||||
@@ -56,8 +36,6 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="产品数量" align="center" prop="quantity" />
|
||||
<el-table-column label="单位" align="center" prop="unit" />
|
||||
<el-table-column label="含税单价" align="center" prop="taxPrice" />
|
||||
<el-table-column label="无税单价" align="center" prop="noTaxPrice" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" width="200">
|
||||
<template #default="scope">
|
||||
@@ -81,13 +59,6 @@
|
||||
<el-form-item label="单位" prop="unit">
|
||||
<el-input v-model="form.unit" placeholder="单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="含税单价" prop="taxPrice">
|
||||
<el-input-number :controls=false controls-position="right" v-model="form.taxPrice" placeholder="请输入含税单价" />
|
||||
</el-form-item>
|
||||
<el-form-item label="无税单价" prop="noTaxPrice">
|
||||
<el-input-number :controls=false controls-position="right" v-model="form.noTaxPrice" placeholder="请输入无税单价"
|
||||
:min="0" :max="form.taxPrice" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
@@ -165,22 +136,6 @@ export default {
|
||||
// 是否可以编辑(订单状态为新建时才能编辑)
|
||||
canEdit() {
|
||||
return this.orderInfo && this.orderInfo.orderStatus === EOrderStatus.NEW;
|
||||
},
|
||||
actualAmount() {
|
||||
return this.orderDetailList?.reduce((total, item) => {
|
||||
return total + (item.taxPrice || 0) * (item.quantity || 0);
|
||||
}, 0) || 0;
|
||||
},
|
||||
amountDifference() {
|
||||
return ((this.orderInfo?.taxAmount || 0) - this.actualAmount);
|
||||
},
|
||||
noTaxAmount() {
|
||||
return this.orderDetailList?.reduce((total, item) => {
|
||||
return total + (item.noTaxPrice || 0) * (item.quantity || 0);
|
||||
}, 0) || 0;
|
||||
},
|
||||
noTaxAmountDifference() {
|
||||
return ((this.orderInfo?.noTaxAmount || 0) - this.noTaxAmount);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -224,8 +179,6 @@ export default {
|
||||
orderId: this.orderId,
|
||||
productId: undefined,
|
||||
quantity: undefined,
|
||||
taxPrice: undefined,
|
||||
noTaxPrice: undefined,
|
||||
unit: undefined,
|
||||
remark: undefined,
|
||||
delFlag: undefined,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -94,8 +94,9 @@
|
||||
<!-- 添加或修改应收款管理(宽松版)对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="客户ID" prop="customerId">
|
||||
<CustomerSelect v-model="form.customerId" />
|
||||
<el-form-item label="客户" prop="customerId">
|
||||
<el-input v-if="fixedCustomer" :model-value="fixedCustomerLabel" disabled />
|
||||
<CustomerSelect v-else v-model="form.customerId" />
|
||||
</el-form-item>
|
||||
<el-form-item label="到期日" prop="dueDate">
|
||||
<el-date-picker clearable
|
||||
@@ -141,6 +142,7 @@
|
||||
<script>
|
||||
import { listReceivable, getReceivable, delReceivable, addReceivable, updateReceivable, updatePaidAmount } from "@/api/finance/receivable";
|
||||
import CustomerSelect from '@/components/CustomerSelect/index.vue';
|
||||
import { getCustomer } from "@/api/oms/customer";
|
||||
|
||||
export default {
|
||||
name: "Receivable",
|
||||
@@ -151,6 +153,25 @@ export default {
|
||||
orderId: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
},
|
||||
customerId: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
customerName: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fixedCustomer() {
|
||||
return this.customerId != null && String(this.customerId) !== ""
|
||||
},
|
||||
fixedCustomerLabel() {
|
||||
const name = String(this.customerName || this.fixedCustomerName || "").trim()
|
||||
return name || "—"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -179,7 +200,7 @@ export default {
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
customerId: undefined,
|
||||
customerId: this.customerId != null && String(this.customerId) !== "" ? this.customerId : undefined,
|
||||
orderId: this.orderId,
|
||||
dueDate: undefined,
|
||||
amount: undefined,
|
||||
@@ -195,7 +216,8 @@ export default {
|
||||
// 收款表单参数
|
||||
receiveForm: {},
|
||||
// 是否显示收款弹出层
|
||||
receiveOpen: false
|
||||
receiveOpen: false,
|
||||
fixedCustomerName: ""
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -205,9 +227,35 @@ export default {
|
||||
orderId(newVal) {
|
||||
this.queryParams.orderId = newVal;
|
||||
this.getList();
|
||||
},
|
||||
customerId(newVal) {
|
||||
this.queryParams.customerId = newVal != null && String(newVal) !== "" ? newVal : undefined
|
||||
this.loadFixedCustomerName()
|
||||
},
|
||||
customerName() {
|
||||
this.loadFixedCustomerName()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadFixedCustomerName() {
|
||||
if (!this.fixedCustomer) {
|
||||
this.fixedCustomerName = ""
|
||||
return
|
||||
}
|
||||
const name = String(this.customerName || "").trim()
|
||||
if (name) {
|
||||
this.fixedCustomerName = name
|
||||
return
|
||||
}
|
||||
const id = this.customerId
|
||||
if (id == null || String(id) === "") return
|
||||
getCustomer(id).then(res => {
|
||||
const d = res && res.data ? res.data : null
|
||||
this.fixedCustomerName = (d && d.name) ? String(d.name) : ""
|
||||
}).catch(() => {
|
||||
this.fixedCustomerName = ""
|
||||
})
|
||||
},
|
||||
/** 查询应收款管理(宽松版)列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
@@ -226,7 +274,7 @@ export default {
|
||||
reset() {
|
||||
this.form = {
|
||||
receivableId: undefined,
|
||||
customerId: undefined,
|
||||
customerId: this.fixedCustomer ? this.customerId : undefined,
|
||||
orderId: this.orderId,
|
||||
dueDate: undefined,
|
||||
amount: undefined,
|
||||
@@ -263,6 +311,7 @@ export default {
|
||||
this.reset();
|
||||
this.open = true;
|
||||
this.title = "添加应收款管理(宽松版)";
|
||||
this.loadFixedCustomerName()
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
@@ -291,7 +340,11 @@ export default {
|
||||
this.buttonLoading = false;
|
||||
});
|
||||
} else {
|
||||
addReceivable(this.form).then(response => {
|
||||
const payload = Object.assign({}, this.form, {
|
||||
customerId: this.fixedCustomer ? this.customerId : this.form.customerId,
|
||||
orderId: this.orderId
|
||||
})
|
||||
addReceivable(payload).then(response => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
|
||||
761
gear-ui3/src/views/oms/salesman/index.vue
Normal file
761
gear-ui3/src/views/oms/salesman/index.vue
Normal file
@@ -0,0 +1,761 @@
|
||||
<template>
|
||||
<div class="app-container salesman-page">
|
||||
<el-row :gutter="16">
|
||||
<!-- 左侧:销售员列表(对齐示意图的“左列表 + 右明细”布局) -->
|
||||
<el-col :span="6" class="salesman-left">
|
||||
<el-form :model="queryParams" ref="queryRef" label-width="56px" size="small" class="left-filter">
|
||||
<el-form-item label="姓名">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
clearable
|
||||
placeholder="请输入销售员名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="queryParams.status" clearable placeholder="数据状态">
|
||||
<el-option label="正常" :value="0" />
|
||||
<el-option label="停用" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="left-actions">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
|
||||
<el-button plain icon="Refresh" @click="getSalesmanList">刷新</el-button>
|
||||
</div>
|
||||
|
||||
<div class="left-list" v-loading="leftLoading">
|
||||
<el-empty v-if="!salesmanList.length && !leftLoading" description="暂无销售员" />
|
||||
<el-scrollbar v-else max-height="calc(100vh - 290px)">
|
||||
<div
|
||||
v-for="item in salesmanList"
|
||||
:key="item.salesmanId"
|
||||
class="salesman-item"
|
||||
:class="{ active: item.salesmanId === selectedSalesmanId }"
|
||||
@click="handleSelect(item)"
|
||||
>
|
||||
<div class="salesman-item__left">
|
||||
<el-tag v-if="item.status === 0" size="small" type="success">正常</el-tag>
|
||||
<el-tag v-else size="small" type="danger">停用</el-tag>
|
||||
<span class="salesman-item__name">{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="salesman-item__actions">
|
||||
<el-button link type="primary" icon="Edit" @click.stop="handleUpdate(item)">修改</el-button>
|
||||
<el-button link type="danger" icon="Delete" @click.stop="handleDelete(item)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<!-- 右侧:跟进信息(Tab 结构对齐示意图) -->
|
||||
<el-col :span="18" class="salesman-right">
|
||||
<el-card shadow="never">
|
||||
<div class="right-title">
|
||||
<span>销售员:</span>
|
||||
<span class="right-title__name">{{ selectedSalesmanName || "未选择" }}</span>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab" class="right-tabs" @tab-change="handleTabChange">
|
||||
<el-tab-pane label="跟进客户" name="followCustomer" />
|
||||
<el-tab-pane label="跟进合同" name="followContract" />
|
||||
<el-tab-pane label="发货单据" name="shippingDocs" />
|
||||
<el-tab-pane label="生产成果" name="production" />
|
||||
<el-tab-pane label="计划发货" name="planShipping" />
|
||||
</el-tabs>
|
||||
|
||||
<div class="right-body">
|
||||
<el-empty v-if="!selectedSalesmanId" description="请先从左侧选择销售员" />
|
||||
|
||||
<template v-else>
|
||||
<div v-show="activeTab === 'followCustomer'">
|
||||
<!-- 跟进客户:从订单反查客户(后端接口 /oa/salesman/{id}/customers) -->
|
||||
<el-table v-loading="customerLoading" :data="customerList">
|
||||
<el-table-column label="公司名称" prop="name" min-width="220" />
|
||||
<el-table-column label="联系方式" min-width="160">
|
||||
<template #default="scope">
|
||||
{{ scope.row.mobile || scope.row.telephone || "-" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="税号" min-width="160">
|
||||
<template #default>
|
||||
-
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="行业" prop="industryId" width="120" />
|
||||
<el-table-column label="客户等级" prop="level" width="120" />
|
||||
<el-table-column label="地址" prop="detailAddress" min-width="260" show-overflow-tooltip />
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="customerTotal > 0"
|
||||
:total="customerTotal"
|
||||
v-model:page="customerQuery.pageNum"
|
||||
v-model:limit="customerQuery.pageSize"
|
||||
@pagination="loadCustomers"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'followContract'">
|
||||
<!-- 跟进合同:复用现有订单接口(/oa/order/list),以销售经理名称过滤 -->
|
||||
<el-table v-loading="orderLoading" :data="orderList">
|
||||
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
|
||||
<el-table-column label="客户" prop="customerName" min-width="200" />
|
||||
<el-table-column label="状态" prop="orderStatus" width="120" />
|
||||
<el-table-column label="创建时间" prop="createTime" width="180" />
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="orderTotal > 0"
|
||||
:total="orderTotal"
|
||||
v-model:page="orderQuery.pageNum"
|
||||
v-model:limit="orderQuery.pageSize"
|
||||
@pagination="loadOrders"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'shippingDocs'">
|
||||
<el-table v-loading="shippingLoading" :data="shippingList">
|
||||
<el-table-column label="发货单号" prop="shippingNo" min-width="180" />
|
||||
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
|
||||
<el-table-column label="收货单位" prop="receiverCompany" min-width="220" />
|
||||
<el-table-column label="发货时间" prop="shipTime" width="180" />
|
||||
<el-table-column label="物流公司" prop="logisticsCompany" min-width="140" />
|
||||
<el-table-column label="运单号" prop="logisticsNo" min-width="180" />
|
||||
<el-table-column label="状态" prop="status" width="120" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'production'">
|
||||
<div class="production-wrap">
|
||||
<div class="production-summary">
|
||||
<div class="production-summary__item">订单数:{{ productionSummary.orderCount }}</div>
|
||||
<div class="production-summary__item">计划总量:{{ formatQty(productionSummary.planQty) }}</div>
|
||||
<div class="production-summary__item">已完成总量:{{ formatQty(productionSummary.finishedQty) }}</div>
|
||||
<div class="production-summary__item">完成率:{{ productionSummary.rateText }}</div>
|
||||
</div>
|
||||
<el-table v-loading="productionLoading" :data="productionOrderSummaryList" size="small" border>
|
||||
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
|
||||
<el-table-column label="客户" prop="customerName" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="计划" prop="planQty" width="120" align="center" />
|
||||
<el-table-column label="完成" prop="finishedQty" width="120" align="center" />
|
||||
<el-table-column label="完成率" width="120" align="center">
|
||||
<template #default="scope">
|
||||
{{ formatRate(scope.row.finishedQty, scope.row.planQty) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最近更新" prop="lastUpdateTime" min-width="160" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'planShipping'">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="plan-shipping__header">
|
||||
计划发货
|
||||
<span class="plan-shipping__sub">产品明细(按该销售员全部订单汇总)</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="plan-shipping__summary">
|
||||
<div class="plan-shipping__summary-item">总条数:{{ planDetailSummary.totalLines }}</div>
|
||||
<div class="plan-shipping__summary-item">总数量:{{ planDetailSummary.totalQty }}</div>
|
||||
<div class="plan-shipping__summary-item">订单数:{{ planDetailSummary.totalOrders }}</div>
|
||||
<div class="plan-shipping__summary-item">已发货订单:{{ planDetailSummary.shippedOrders }}</div>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="planDetailLoading"
|
||||
:data="planOrderDetailList"
|
||||
size="small"
|
||||
border
|
||||
>
|
||||
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
|
||||
<el-table-column label="客户" prop="customerName" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="产品编号" min-width="140">
|
||||
<template #default="scope">
|
||||
<el-popover placement="right" trigger="hover" width="460">
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="订单编号">{{ scope.row.orderCode || "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户">{{ scope.row.customerName || "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="产品编号">{{ scope.row.productCode || "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="产品名称">{{ scope.row.productName || "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="规格">{{ scope.row.spec || "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="型号">{{ scope.row.model || "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="单位">{{ scope.row.unit || "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="数量">{{ scope.row.quantity ?? "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ scope.row.remark || "-" }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #reference>
|
||||
<el-link type="primary" :underline="false">{{ scope.row.productCode || "-" }}</el-link>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产品名称" prop="productName" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="规格" prop="spec" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="型号" prop="model" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="单位" prop="unit" width="90" />
|
||||
<el-table-column label="数量" prop="quantity" width="90" />
|
||||
<el-table-column label="备注" prop="remark" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="发货状态" width="110">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.shippedFlag" type="success" size="small">已发货</el-tag>
|
||||
<el-tag v-else type="info" size="small">未发货</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 新增/修改销售员对话框 -->
|
||||
<el-dialog :title="dialogTitle" v-model="dialogOpen" width="520px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入销售员姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机" prop="mobile">
|
||||
<el-input v-model="form.mobile" placeholder="请输入联系电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :value="0">正常</el-radio>
|
||||
<el-radio :value="1">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="submitLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogOpen = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listSalesman, addSalesman, updateSalesman, delSalesman, listSalesmanCustomers } from "@/api/oms/salesman";
|
||||
import { listOrder } from "@/api/oms/order";
|
||||
import { listShippingOrder } from "@/api/oms/shippingOrder";
|
||||
import { listOrderDetail } from "@/api/oms/orderDetail";
|
||||
import { listOrderProduction } from "@/api/oms/orderProduction";
|
||||
|
||||
export default {
|
||||
name: "Salesman",
|
||||
data() {
|
||||
return {
|
||||
leftLoading: false,
|
||||
salesmanList: [],
|
||||
selectedSalesmanId: undefined,
|
||||
selectedSalesmanName: "",
|
||||
activeTab: "followCustomer",
|
||||
|
||||
// 左侧查询条件(用于筛选销售员列表)
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 9999,
|
||||
name: undefined,
|
||||
status: undefined
|
||||
},
|
||||
|
||||
// 跟进客户
|
||||
customerLoading: false,
|
||||
customerList: [],
|
||||
customerTotal: 0,
|
||||
customerQuery: {
|
||||
pageNum: 1,
|
||||
pageSize: 20
|
||||
},
|
||||
|
||||
// 跟进合同(订单列表)
|
||||
orderLoading: false,
|
||||
orderList: [],
|
||||
orderTotal: 0,
|
||||
orderQuery: {
|
||||
pageNum: 1,
|
||||
pageSize: 20
|
||||
},
|
||||
|
||||
// 发货单据
|
||||
shippingLoading: false,
|
||||
shippingList: [],
|
||||
|
||||
// 生产成果:按订单汇总(只读)
|
||||
productionLoading: false,
|
||||
productionOrderSummaryList: [],
|
||||
|
||||
// 计划发货:产品明细(按销售员全部订单汇总)
|
||||
planDetailLoading: false,
|
||||
planOrderDetailList: [],
|
||||
|
||||
// 新增/修改弹窗
|
||||
dialogOpen: false,
|
||||
dialogTitle: "",
|
||||
submitLoading: false,
|
||||
form: {},
|
||||
rules: {
|
||||
name: [{ required: true, message: "请输入销售员姓名", trigger: "blur" }]
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
planDetailSummary() {
|
||||
const rows = this.planOrderDetailList || [];
|
||||
const totalLines = rows.length;
|
||||
const totalQty = rows.reduce((sum, r) => sum + Number(r && r.quantity != null ? r.quantity : 0), 0);
|
||||
const totalOrders = new Set(rows.map(r => r && r.orderId).filter(v => v != null)).size;
|
||||
const shippedOrders = new Set(rows.filter(r => r && r.shippedFlag).map(r => r.orderId).filter(v => v != null)).size;
|
||||
return {
|
||||
totalLines,
|
||||
totalQty,
|
||||
totalOrders,
|
||||
shippedOrders
|
||||
};
|
||||
},
|
||||
productionSummary() {
|
||||
const list = Array.isArray(this.productionOrderSummaryList) ? this.productionOrderSummaryList : [];
|
||||
const orderCount = list.length;
|
||||
const planQty = list.reduce((sum, r) => sum + Number(r && r.planQty != null ? r.planQty : 0), 0);
|
||||
const finishedQty = list.reduce((sum, r) => sum + Number(r && r.finishedQty != null ? r.finishedQty : 0), 0);
|
||||
const rateText = planQty > 0 ? `${((finishedQty / planQty) * 100).toFixed(1)}%` : "0%";
|
||||
return { orderCount, planQty, finishedQty, rateText };
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getSalesmanList();
|
||||
},
|
||||
methods: {
|
||||
// 查询销售员列表
|
||||
getSalesmanList() {
|
||||
this.leftLoading = true;
|
||||
listSalesman(this.queryParams)
|
||||
.then(res => {
|
||||
this.salesmanList = (res && res.rows) ? res.rows : [];
|
||||
// 列表刷新后:若未选中,则默认选中第一条,方便右侧直接看到内容
|
||||
if (!this.selectedSalesmanId && this.salesmanList.length) {
|
||||
this.handleSelect(this.salesmanList[0]);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.leftLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
// 搜索
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getSalesmanList();
|
||||
},
|
||||
|
||||
// 重置
|
||||
resetQuery() {
|
||||
this.queryParams.name = undefined;
|
||||
this.queryParams.status = undefined;
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
// 选中销售员:刷新右侧数据
|
||||
handleSelect(item) {
|
||||
if (!item || !item.salesmanId) return;
|
||||
this.selectedSalesmanId = item.salesmanId;
|
||||
this.selectedSalesmanName = item.name;
|
||||
this.customerQuery.pageNum = 1;
|
||||
this.orderQuery.pageNum = 1;
|
||||
this.loadCustomers();
|
||||
if (this.activeTab === "followContract") {
|
||||
this.loadOrders();
|
||||
}
|
||||
if (this.activeTab === "shippingDocs") {
|
||||
this.loadShippingOrders();
|
||||
}
|
||||
if (this.activeTab === "production") {
|
||||
this.loadProductionSummary();
|
||||
}
|
||||
if (this.activeTab === "planShipping") {
|
||||
this.loadPlanShipping();
|
||||
}
|
||||
},
|
||||
|
||||
// Tab切换:按需加载(避免每次都拉所有数据)
|
||||
handleTabChange() {
|
||||
if (!this.selectedSalesmanId) return;
|
||||
if (this.activeTab === "followCustomer") {
|
||||
this.loadCustomers();
|
||||
}
|
||||
if (this.activeTab === "followContract") {
|
||||
this.loadOrders();
|
||||
}
|
||||
if (this.activeTab === "shippingDocs") {
|
||||
this.loadShippingOrders();
|
||||
}
|
||||
if (this.activeTab === "production") {
|
||||
this.loadProductionSummary();
|
||||
}
|
||||
if (this.activeTab === "planShipping") {
|
||||
this.loadPlanShipping();
|
||||
}
|
||||
},
|
||||
|
||||
// 跟进客户:后端反查客户
|
||||
loadCustomers() {
|
||||
if (!this.selectedSalesmanId) return;
|
||||
this.customerLoading = true;
|
||||
listSalesmanCustomers(this.selectedSalesmanId, this.customerQuery)
|
||||
.then(res => {
|
||||
this.customerList = (res && res.rows) ? res.rows : [];
|
||||
this.customerTotal = res && typeof res.total === "number" ? res.total : 0;
|
||||
})
|
||||
.finally(() => {
|
||||
this.customerLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
// 跟进合同:按 salesManager(姓名)过滤订单
|
||||
loadOrders() {
|
||||
if (!this.selectedSalesmanId) return;
|
||||
this.orderLoading = true;
|
||||
listOrder({
|
||||
...this.orderQuery,
|
||||
salesmanId: this.selectedSalesmanId
|
||||
})
|
||||
.then(res => {
|
||||
this.orderList = (res && res.rows) ? res.rows : [];
|
||||
this.orderTotal = res && typeof res.total === "number" ? res.total : 0;
|
||||
})
|
||||
.finally(() => {
|
||||
this.orderLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
loadShippingOrders() {
|
||||
if (!this.selectedSalesmanId) return;
|
||||
this.shippingLoading = true;
|
||||
listShippingOrder({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 })
|
||||
.then(res => {
|
||||
this.shippingList = (res && res.rows) ? res.rows : [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.shippingLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
async loadProductionSummary() {
|
||||
if (!this.selectedSalesmanId) return;
|
||||
this.productionLoading = true;
|
||||
try {
|
||||
const orderRes = await listOrder({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 });
|
||||
const orders = (orderRes && orderRes.rows) ? orderRes.rows : [];
|
||||
const promises = orders
|
||||
.filter(o => o && o.orderId != null)
|
||||
.map(async (o) => {
|
||||
const prodRes = await listOrderProduction({ orderId: o.orderId, pageNum: 1, pageSize: 9999 });
|
||||
const rows = (prodRes && prodRes.rows) ? prodRes.rows : [];
|
||||
const planQty = rows.reduce((sum, r) => sum + Number(r && r.planQty != null ? r.planQty : 0), 0);
|
||||
const finishedQty = rows.reduce((sum, r) => sum + Number(r && r.finishedQty != null ? r.finishedQty : 0), 0);
|
||||
let lastUpdateTime = "";
|
||||
rows.forEach(r => {
|
||||
const t = r && r.updateTime ? String(r.updateTime) : "";
|
||||
if (t && (!lastUpdateTime || new Date(t).getTime() > new Date(lastUpdateTime).getTime())) {
|
||||
lastUpdateTime = t;
|
||||
}
|
||||
});
|
||||
return {
|
||||
orderId: o.orderId,
|
||||
orderCode: o.orderCode,
|
||||
customerName: o.customerName,
|
||||
planQty,
|
||||
finishedQty,
|
||||
lastUpdateTime
|
||||
};
|
||||
});
|
||||
|
||||
this.productionOrderSummaryList = await Promise.all(promises);
|
||||
} finally {
|
||||
this.productionLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
loadPlanShipping() {
|
||||
if (!this.selectedSalesmanId) return;
|
||||
this.planDetailLoading = true;
|
||||
Promise.all([
|
||||
listOrder({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 }),
|
||||
listShippingOrder({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 })
|
||||
])
|
||||
.then(async ([orderRes, shipRes]) => {
|
||||
const orders = (orderRes && orderRes.rows) ? orderRes.rows : [];
|
||||
const shipping = (shipRes && shipRes.rows) ? shipRes.rows : [];
|
||||
|
||||
const orderMap = new Map();
|
||||
orders.forEach(o => {
|
||||
if (o && o.orderId != null) orderMap.set(o.orderId, o);
|
||||
});
|
||||
|
||||
const shipStatusMap = new Map();
|
||||
shipping.forEach(s => {
|
||||
const orderId = s && s.orderId != null ? s.orderId : null;
|
||||
if (orderId == null) return;
|
||||
const n = Number(s && s.status != null ? s.status : 0);
|
||||
const prev = shipStatusMap.has(orderId) ? shipStatusMap.get(orderId) : 0;
|
||||
shipStatusMap.set(orderId, Number.isFinite(n) ? Math.max(prev, n) : prev);
|
||||
});
|
||||
|
||||
const detailPromises = orders
|
||||
.filter(o => o && o.orderId)
|
||||
.map(o =>
|
||||
listOrderDetail({ orderId: o.orderId, pageNum: 1, pageSize: 9999 })
|
||||
.then(res => ({
|
||||
order: o,
|
||||
rows: (res && res.rows) ? res.rows : []
|
||||
}))
|
||||
);
|
||||
|
||||
const detailResults = await Promise.all(detailPromises);
|
||||
const merged = [];
|
||||
detailResults.forEach(({ order, rows }) => {
|
||||
const maxStatus = shipStatusMap.has(order.orderId) ? shipStatusMap.get(order.orderId) : 0;
|
||||
const shippedFlag = Number(maxStatus) >= 2;
|
||||
rows
|
||||
.filter(r => String(r && r.productType ? r.productType : "").toLowerCase() === "product")
|
||||
.forEach(r => {
|
||||
merged.push({
|
||||
...r,
|
||||
orderId: order.orderId,
|
||||
orderCode: order.orderCode,
|
||||
customerName: order.customerName,
|
||||
shippedFlag
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.planOrderDetailList = merged;
|
||||
})
|
||||
.finally(() => {
|
||||
this.planDetailLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
formatQty(v) {
|
||||
const n = Number(v);
|
||||
const val = Number.isFinite(n) ? n : 0;
|
||||
return val.toFixed(2);
|
||||
},
|
||||
|
||||
formatRate(finished, plan) {
|
||||
const a = Number(finished);
|
||||
const b = Number(plan);
|
||||
const fa = Number.isFinite(a) ? a : 0;
|
||||
const fb = Number.isFinite(b) ? b : 0;
|
||||
if (fb <= 0) return "0%";
|
||||
return `${((fa / fb) * 100).toFixed(1)}%`;
|
||||
},
|
||||
|
||||
// 新增
|
||||
handleAdd() {
|
||||
this.dialogTitle = "新增销售员";
|
||||
this.form = {
|
||||
salesmanId: undefined,
|
||||
name: "",
|
||||
mobile: "",
|
||||
status: 0,
|
||||
remark: ""
|
||||
};
|
||||
this.dialogOpen = true;
|
||||
},
|
||||
|
||||
// 修改
|
||||
handleUpdate(row) {
|
||||
this.dialogTitle = "修改销售员";
|
||||
this.form = { ...row };
|
||||
if (this.form.status === undefined || this.form.status === null) {
|
||||
this.form.status = 0;
|
||||
}
|
||||
this.dialogOpen = true;
|
||||
},
|
||||
|
||||
// 删除
|
||||
handleDelete(row) {
|
||||
this.$modal
|
||||
.confirm(`是否确认删除销售员"${row.name}"?`)
|
||||
.then(() => delSalesman([row.salesmanId]))
|
||||
.then(() => {
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
// 删除后清空选中,避免右侧仍显示已删除人员的数据
|
||||
if (this.selectedSalesmanId === row.salesmanId) {
|
||||
this.selectedSalesmanId = undefined;
|
||||
this.selectedSalesmanName = "";
|
||||
}
|
||||
this.getSalesmanList();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
|
||||
// 提交新增/修改
|
||||
submitForm() {
|
||||
this.$refs["formRef"].validate(valid => {
|
||||
if (!valid) return;
|
||||
this.submitLoading = true;
|
||||
const req = this.form.salesmanId ? updateSalesman(this.form) : addSalesman(this.form);
|
||||
req
|
||||
.then(() => {
|
||||
this.$modal.msgSuccess("保存成功");
|
||||
this.dialogOpen = false;
|
||||
this.getSalesmanList();
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.salesman-left {
|
||||
height: calc(100vh - 110px);
|
||||
}
|
||||
|
||||
.left-filter {
|
||||
padding: 10px 10px 0 10px;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.left-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.left-list {
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.salesman-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.salesman-item.active {
|
||||
border-color: #409eff;
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
|
||||
.salesman-item__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.salesman-item__name {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.salesman-item__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.right-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.right-title__name {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.right-body {
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
:deep(.right-tabs .el-tabs__content) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.right-tabs .el-tabs__header) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.plan-shipping__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.plan-shipping__sub {
|
||||
font-weight: 400;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.plan-shipping__summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 10px;
|
||||
background: #f5f7fa;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 6px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.plan-shipping__summary-item {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.production-wrap {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.production-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 10px;
|
||||
background: #f5f7fa;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 6px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.production-summary__item {
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
1479
gear-ui3/src/views/shipping/order/index.vue
Normal file
1479
gear-ui3/src/views/shipping/order/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user