mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-09-20 20:02:05 +08:00
feat(orderFulfill): 增强订单发货逻辑与界面
- 新增无需配送场景及发货模式相关工具函数 - 更新订单详情与订单管理页,支持整单发货、分包裹发货、无需配送 - 优化物流信息与订单日志展示 - 重构订单发货 API 以适配新逻辑 - 增强订单列表与详情页的发货选项与操作展示
This commit is contained in:
@@ -72,23 +72,29 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="order-card" v-if="!order.order.verificationCode && !isNonPhysicalOrder">
|
<div class="order-card" v-if="!order.order.verificationCode && !isNonPhysicalOrder">
|
||||||
<h3>配送信息</h3>
|
<h3>配送信息</h3>
|
||||||
<p>配送方式:{{ order.deliveryMethodValue }}</p>
|
<p>配送方式:{{ displayDeliveryMethodValue }}</p>
|
||||||
<p v-if="order.order.deliveryMethod === 'LOGISTICS'">配送状态:{{ order.deliverStatusValue }}</p>
|
<p v-if="order.order.deliveryMethod === 'LOGISTICS' && !isNoDeliveryOrder">配送状态:{{ order.deliverStatusValue }}</p>
|
||||||
<p v-if="logistics">
|
<template v-if="isNoDeliveryOrder">
|
||||||
物流信息:{{ logistics.shipper || "暂无物流信息" }}
|
<p v-if="noDeliveryShipTime">发货时间:{{ noDeliveryShipTime }}</p>
|
||||||
</p>
|
<p v-if="noDeliveryRemark">配送说明:{{ noDeliveryRemark }}</p>
|
||||||
<p v-if="logistics">
|
</template>
|
||||||
物流单号:{{ logistics.logisticCode || "暂无物流单号" }}
|
<template v-else>
|
||||||
</p>
|
<p v-if="logistics">
|
||||||
<div class="div-express-log" v-if="logistics">
|
物流信息:{{ logistics.shipper || "暂无物流信息" }}
|
||||||
<div class="express-log">
|
</p>
|
||||||
<p>订单日志:</p>
|
<p v-if="logistics">
|
||||||
<div v-for="(item, index) in logistics.traces" :key="index">
|
物流单号:{{ logistics.logisticCode || "暂无物流单号" }}
|
||||||
<span class="time">{{ item.AcceptTime }}</span>
|
</p>
|
||||||
<span class="detail">{{ item.AcceptStation }}</span>
|
<div class="div-express-log" v-if="logistics">
|
||||||
|
<div class="express-log">
|
||||||
|
<p>订单日志:</p>
|
||||||
|
<div v-for="(item, index) in logistics.traces" :key="index">
|
||||||
|
<span class="time">{{ item.AcceptTime }}</span>
|
||||||
|
<span class="detail">{{ item.AcceptStation }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="order-card" v-if="order.order.payStatus === 'PAID'">
|
<div class="order-card" v-if="order.order.payStatus === 'PAID'">
|
||||||
<h3>发票信息</h3>
|
<h3>发票信息</h3>
|
||||||
@@ -313,12 +319,28 @@
|
|||||||
<template v-if="orderPackage.length > 0">
|
<template v-if="orderPackage.length > 0">
|
||||||
<div v-for="(packageItem, packageIndex) in orderPackage" :key="packageIndex">
|
<div v-for="(packageItem, packageIndex) in orderPackage" :key="packageIndex">
|
||||||
<div class="layui-layer-wrap">
|
<div class="layui-layer-wrap">
|
||||||
|
<dl><dt>发货方式:</dt>
|
||||||
|
<dd><div class="text-box">{{ getFulfillmentModeLabel(packageItem.fulfillmentMode) }}</div></dd>
|
||||||
|
</dl>
|
||||||
|
<template v-if="isNoDeliveryPackage(packageItem)">
|
||||||
|
<dl><dt>配送方式:</dt>
|
||||||
|
<dd><div class="text-box">无需配送</div></dd>
|
||||||
|
</dl>
|
||||||
|
<dl v-if="packageItem.logisticsTime"><dt>发货时间:</dt>
|
||||||
|
<dd><div class="text-box">{{ packageItem.logisticsTime }}</div></dd>
|
||||||
|
</dl>
|
||||||
|
<dl v-if="packageItem.deliveryRemark"><dt>配送说明:</dt>
|
||||||
|
<dd><div class="text-box">{{ packageItem.deliveryRemark }}</div></dd>
|
||||||
|
</dl>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<dl><dt>物流公司:</dt>
|
<dl><dt>物流公司:</dt>
|
||||||
<dd><div class="text-box">{{ packageItem.logisticsName }}</div></dd>
|
<dd><div class="text-box">{{ packageItem.logisticsName }}</div></dd>
|
||||||
</dl>
|
</dl>
|
||||||
<dl><dt>快递单号:</dt>
|
<dl><dt>快递单号:</dt>
|
||||||
<dd><div nctype="ordersSn" class="text-box">{{ packageItem.logisticsNo }}</div></dd>
|
<dd><div nctype="ordersSn" class="text-box">{{ packageItem.logisticsNo }}</div></dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
</template>
|
||||||
<div class="div-express-log">
|
<div class="div-express-log">
|
||||||
<ul class="express-log express-log-name">
|
<ul class="express-log express-log-name">
|
||||||
<li v-for="(item, index) in packageItem.orderPackageItemList" :key="index">
|
<li v-for="(item, index) in packageItem.orderPackageItemList" :key="index">
|
||||||
@@ -327,7 +349,7 @@
|
|||||||
<span class="time" style="width: 20%;"><span>发货数量:</span><span>{{ item.deliverNumber }}</span></span>
|
<span class="time" style="width: 20%;"><span>发货数量:</span><span>{{ item.deliverNumber }}</span></span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="div-express-log" style="overflow: hidden;">
|
<div v-if="!isNoDeliveryPackage(packageItem)" class="div-express-log" style="overflow: hidden;">
|
||||||
<ul class="express-log" v-if="packageItem.traces && packageItem.traces.traces">
|
<ul class="express-log" v-if="packageItem.traces && packageItem.traces.traces">
|
||||||
<li v-for="(item, index) in packageItem.traces.traces" :key="index">
|
<li v-for="(item, index) in packageItem.traces.traces" :key="index">
|
||||||
<span class="time">{{ item.AcceptTime || item.acceptTime }}</span>
|
<span class="time">{{ item.AcceptTime || item.acceptTime }}</span>
|
||||||
@@ -400,6 +422,11 @@ import {
|
|||||||
cardKeyFulfillAlertType,
|
cardKeyFulfillAlertType,
|
||||||
resolveCardKeyFulfillMessage,
|
resolveCardKeyFulfillMessage,
|
||||||
} from "@/constants/goodsType";
|
} from "@/constants/goodsType";
|
||||||
|
import {
|
||||||
|
isNoDeliveryPackage,
|
||||||
|
isNoDeliveryOrder as checkNoDeliveryOrder,
|
||||||
|
getFulfillmentModeLabel,
|
||||||
|
} from "@/utils/orderFulfill";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单详情:E_COUPON 展示 orderItems[].cardKeys,屏蔽售后/物流(FR-B-03 / FR-B-04)。
|
* 订单详情:E_COUPON 展示 orderItems[].cardKeys,屏蔽售后/物流(FR-B-03 / FR-B-04)。
|
||||||
@@ -449,8 +476,29 @@ export default {
|
|||||||
ecouponCardKeyRows() {
|
ecouponCardKeyRows() {
|
||||||
return flattenOrderCardKeys(this.order.orderItems, false);
|
return flattenOrderCardKeys(this.order.orderItems, false);
|
||||||
},
|
},
|
||||||
|
/** 商家以无需配送方式发货(包裹 fulfillmentMode=NO_DELIVERY) */
|
||||||
|
isNoDeliveryOrder() {
|
||||||
|
return checkNoDeliveryOrder(this.orderPackage);
|
||||||
|
},
|
||||||
|
displayDeliveryMethodValue() {
|
||||||
|
if (this.isNoDeliveryOrder) return "无需配送";
|
||||||
|
return this.order?.deliveryMethodValue || "-";
|
||||||
|
},
|
||||||
|
noDeliveryPackages() {
|
||||||
|
return (this.orderPackage || []).filter(isNoDeliveryPackage);
|
||||||
|
},
|
||||||
|
noDeliveryRemark() {
|
||||||
|
const pkg = this.noDeliveryPackages.find((p) => p.deliveryRemark);
|
||||||
|
return pkg?.deliveryRemark || "";
|
||||||
|
},
|
||||||
|
noDeliveryShipTime() {
|
||||||
|
const pkg = this.noDeliveryPackages[0];
|
||||||
|
return pkg?.logisticsTime || "";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isNoDeliveryPackage,
|
||||||
|
getFulfillmentModeLabel,
|
||||||
cardKeyFulfillAlertType,
|
cardKeyFulfillAlertType,
|
||||||
resolveCardKeyFulfillMessage,
|
resolveCardKeyFulfillMessage,
|
||||||
lineCardKeyRows(line) {
|
lineCardKeyRows(line) {
|
||||||
@@ -576,7 +624,6 @@ export default {
|
|||||||
this.progressList = res.result.orderLogs;
|
this.progressList = res.result.orderLogs;
|
||||||
if (this.order.order.deliveryMethod === 'LOGISTICS') {
|
if (this.order.order.deliveryMethod === 'LOGISTICS') {
|
||||||
this.getOrderPackage(this.order.order.sn);
|
this.getOrderPackage(this.order.order.sn);
|
||||||
this.traces();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -584,9 +631,16 @@ export default {
|
|||||||
getOrderPackage(sn) {
|
getOrderPackage(sn) {
|
||||||
getPackage(sn).then(res => {
|
getPackage(sn).then(res => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
this.orderPackage = res.result
|
this.orderPackage = Array.isArray(res.result) ? res.result : [];
|
||||||
|
if (!this.orderPackage.length) {
|
||||||
|
this.traces();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.traces();
|
||||||
}
|
}
|
||||||
})
|
}).catch(() => {
|
||||||
|
this.traces();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
traces() {
|
traces() {
|
||||||
// 物流信息
|
// 物流信息
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="more-addr" @click="storeMoreAddr = !storeMoreAddr" v-if="addressList.length > 3">
|
<div class="more-addr" @click="storeMoreAddr = !storeMoreAddr" v-if="storeAddressList.length > 3">
|
||||||
{{ storeMoreAddr ? "收起地址" : "更多地址" }}
|
{{ storeMoreAddr ? "收起地址" : "更多地址" }}
|
||||||
<el-icon :v-show="!storeMoreAddr"><ArrowDown /></el-icon>
|
<el-icon :v-show="!storeMoreAddr"><ArrowDown /></el-icon>
|
||||||
<el-icon :v-show="storeMoreAddr"><ArrowUp /></el-icon>
|
<el-icon :v-show="storeMoreAddr"><ArrowUp /></el-icon>
|
||||||
|
|||||||
18
buyer/src/utils/orderFulfill.js
Normal file
18
buyer/src/utils/orderFulfill.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export function isNoDeliveryPackage(pkg) {
|
||||||
|
if (!pkg) return false;
|
||||||
|
return pkg.fulfillmentMode === "NO_DELIVERY" || pkg.logisticsRequired === false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNoDeliveryOrder(packages) {
|
||||||
|
if (!Array.isArray(packages) || !packages.length) return false;
|
||||||
|
return packages.every(isNoDeliveryPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFulfillmentModeLabel(mode) {
|
||||||
|
const map = {
|
||||||
|
WHOLE: "整单发货",
|
||||||
|
SPLIT: "拆单发货",
|
||||||
|
NO_DELIVERY: "无需配送",
|
||||||
|
};
|
||||||
|
return map[mode] || mode || "-";
|
||||||
|
}
|
||||||
@@ -104,9 +104,11 @@ export const getOrderLog = (sn, params) => {
|
|||||||
return getRequest(`/orderLog/${sn}`, params);
|
return getRequest(`/orderLog/${sn}`, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 订单发货
|
// 订单发货(整单 / 拆单 / 无需配送)
|
||||||
export const orderDelivery = (sn, params) => {
|
export const orderFulfill = (orderSn, params) => {
|
||||||
return postRequest(`/order/order/${sn}/delivery`, params);
|
return postRequest(`/order/order/${orderSn}/fulfill`, params, {
|
||||||
|
"Content-type": "application/json",
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 订单发货
|
// 订单发货
|
||||||
@@ -212,13 +214,6 @@ export const getPackage = (orderSn) => {
|
|||||||
return getRequest(`/order/order/getPackage/${orderSn}`);
|
return getRequest(`/order/order/getPackage/${orderSn}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
//分包裹发货
|
|
||||||
export const partDelivery = (orderSn,params) => {
|
|
||||||
return postRequest(`/order/order/${orderSn}/partDelivery`,params,{
|
|
||||||
"Content-type": "application/json"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//查询物流
|
//查询物流
|
||||||
export const getTracesList = (sn) => {
|
export const getTracesList = (sn) => {
|
||||||
return getRequest(`/order/order/getTracesList/${sn}`);
|
return getRequest(`/order/order/getTracesList/${sn}`);
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export const logisticsUnChecked = (id, params) => {
|
|||||||
}
|
}
|
||||||
// 获取商家自提点
|
// 获取商家自提点
|
||||||
export const getShopAddress = (id, params) => {
|
export const getShopAddress = (id, params) => {
|
||||||
return getRequest(`/member/storeAddress/`, params)
|
return getRequest(`/member/storeAddress`, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改商家自提点
|
// 修改商家自提点
|
||||||
@@ -81,7 +81,7 @@ export const editShopAddress = (id, params) => {
|
|||||||
|
|
||||||
// 添加商品自提点
|
// 添加商品自提点
|
||||||
export const addShopAddress = (params) => {
|
export const addShopAddress = (params) => {
|
||||||
return postRequest(`/member/storeAddress/`, params)
|
return postRequest(`/member/storeAddress`, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加商品自提点
|
// 添加商品自提点
|
||||||
|
|||||||
72
seller/src/utils/orderFulfill.js
Normal file
72
seller/src/utils/orderFulfill.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/** @typedef {'WHOLE' | 'SPLIT' | 'NO_DELIVERY'} FulfillmentMode */
|
||||||
|
|
||||||
|
const NON_SHIPPABLE_ORDER_TYPES = ["E_COUPON", "VIRTUAL"];
|
||||||
|
|
||||||
|
/** 电子卡券 / 虚拟 / 自提订单不适用商家发货 */
|
||||||
|
export function isNonShippableOrder(order = {}) {
|
||||||
|
if (!order) return false;
|
||||||
|
if (NON_SHIPPABLE_ORDER_TYPES.includes(order.orderType)) return true;
|
||||||
|
return order.deliveryMethod === "SELF_PICK_UP";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canShowShipButton(allowOperation = {}, order = {}) {
|
||||||
|
return !!allowOperation.ship && !isNonShippableOrder(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FULFILLMENT_MODE_LABEL = {
|
||||||
|
WHOLE: "整单发货",
|
||||||
|
SPLIT: "拆单发货",
|
||||||
|
NO_DELIVERY: "无需配送",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FulfillmentMode} mode
|
||||||
|
* @param {object} form
|
||||||
|
* @returns {string | null}
|
||||||
|
*/
|
||||||
|
export function validateFulfillForm(mode, form) {
|
||||||
|
if (mode === "WHOLE" || mode === "SPLIT") {
|
||||||
|
if (!form.logisticsId) return "请选择物流公司";
|
||||||
|
if (!form.logisticsNo || !String(form.logisticsNo).trim()) return "请填写运单号";
|
||||||
|
}
|
||||||
|
if (mode === "NO_DELIVERY") {
|
||||||
|
if (form.logisticsId || form.logisticsNo) return "无需配送不能填写物流信息";
|
||||||
|
if (form.deliveryRemark && form.deliveryRemark.length > 500) {
|
||||||
|
return "备注不能超过500字";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mode === "SPLIT") {
|
||||||
|
if (!form.items || !form.items.length) return "请至少选择一个商品";
|
||||||
|
for (const item of form.items) {
|
||||||
|
if (item.deliveryNum != null && item.deliveryNum <= 0) {
|
||||||
|
return "发货数量必须大于0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {object} pkg */
|
||||||
|
export function isNoDeliveryPackage(pkg) {
|
||||||
|
if (!pkg) return false;
|
||||||
|
return pkg.fulfillmentMode === "NO_DELIVERY" || pkg.logisticsRequired === false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} mode */
|
||||||
|
export function getFulfillmentModeLabel(mode) {
|
||||||
|
return FULFILLMENT_MODE_LABEL[mode] || mode || "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} allowOperation
|
||||||
|
* @returns {FulfillmentMode | null}
|
||||||
|
*/
|
||||||
|
export function resolveDefaultFulfillmentMode(allowOperation = {}, orderStatus) {
|
||||||
|
if (orderStatus === "PARTS_DELIVERED") {
|
||||||
|
return allowOperation.fulfillSplit ? "SPLIT" : null;
|
||||||
|
}
|
||||||
|
if (allowOperation.fulfillWhole) return "WHOLE";
|
||||||
|
if (allowOperation.fulfillSplit) return "SPLIT";
|
||||||
|
if (allowOperation.fulfillNoDelivery) return "NO_DELIVERY";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
>查看物流</el-button>
|
>查看物流</el-button>
|
||||||
<el-button type="primary" @click="orderLog">订单日志</el-button>
|
<el-button type="primary" @click="orderLog">订单日志</el-button>
|
||||||
<el-button v-if="canOrderTake" type="primary" @click="orderTake">订单核销</el-button>
|
<el-button v-if="canOrderTake" type="primary" @click="orderTake">订单核销</el-button>
|
||||||
<el-button v-if="canPartDelivery" type="primary" @click="openPartDelivery">分包裹发货</el-button>
|
<el-button v-if="canShip" type="primary" @click="openFulfill()">发货</el-button>
|
||||||
<el-button type="primary" @click="modifyRemark">添加备注</el-button>
|
<el-button type="primary" @click="modifyRemark">添加备注</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="allowOperation.showLogistics && logisticsType === 'SHUNFENG'"
|
v-if="allowOperation.showLogistics && logisticsType === 'SHUNFENG'"
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
@click="sfPrint"
|
@click="sfPrint"
|
||||||
>下载面单</el-button>
|
>下载面单</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="allowOperation.ship && logisticsType !== 'SHUNFENG'"
|
v-if="canShip && logisticsType !== 'SHUNFENG'"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="toPrint"
|
@click="toPrint"
|
||||||
@@ -384,6 +384,34 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
<el-card v-if="orderPackages.length" class="mt_10">
|
||||||
|
<el-divider content-position="left">发货记录</el-divider>
|
||||||
|
<el-table :data="orderPackages" border style="width: 100%">
|
||||||
|
<el-table-column label="发货方式" width="120">
|
||||||
|
<template #default="{ row }">{{ getFulfillmentModeLabel(row.fulfillmentMode) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="物流信息" min-width="220">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<template v-if="isNoDeliveryPackage(row)">
|
||||||
|
<span>无需配送</span>
|
||||||
|
<span v-if="row.deliveryRemark" class="delivery-remark-text">({{ row.deliveryRemark }})</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ row.logisticsName || "-" }} / {{ row.logisticsNo || "-" }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="logisticsTime" label="发货时间" width="170" />
|
||||||
|
<el-table-column label="商品明细" min-width="260">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div v-for="(item, idx) in row.orderPackageItemList || []" :key="idx" class="package-item-line">
|
||||||
|
{{ item.goodsName }} × {{ item.deliverNumber }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-dialog v-model="modal" title="修改金额" width="530px">
|
<el-dialog v-model="modal" title="修改金额" width="530px">
|
||||||
@@ -446,92 +474,163 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog v-model="partDeliveryModal" title="分包裹发货" width="1200px">
|
<el-dialog v-model="fulfillModal" title="订单发货" width="1200px">
|
||||||
<el-form ref="partDeliveryForm" :model="partDeliveryForm" label-width="100px" :rules="partDeliveryRules">
|
<div v-if="showFulfillModeSelector" class="fulfill-mode-selector">
|
||||||
<el-form-item label="物流公司" prop="logisticsId">
|
<el-radio-group v-model="fulfillForm.fulfillmentMode">
|
||||||
<el-select v-model="partDeliveryForm.logisticsId" placeholder="请选择物流公司" filterable style="width: 260px">
|
<el-radio v-if="allowOperation.fulfillWhole" label="WHOLE">整单发货</el-radio>
|
||||||
<el-option
|
<el-radio v-if="allowOperation.fulfillSplit" label="SPLIT">拆单发货</el-radio>
|
||||||
v-for="item in checkedLogistics"
|
<el-radio v-if="allowOperation.fulfillNoDelivery" label="NO_DELIVERY">无需配送</el-radio>
|
||||||
:key="getLogisticsId(item)"
|
</el-radio-group>
|
||||||
:label="getLogisticsName(item)"
|
</div>
|
||||||
:value="getLogisticsId(item)"
|
<div v-else-if="fulfillForm.fulfillmentMode === 'SPLIT'" class="fulfill-mode-tip">继续拆单发货</div>
|
||||||
/>
|
|
||||||
</el-select>
|
<el-form ref="fulfillFormRef" :model="fulfillForm" label-width="100px" :rules="fulfillRules">
|
||||||
|
<template v-if="fulfillForm.fulfillmentMode !== 'NO_DELIVERY'">
|
||||||
|
<el-form-item label="物流公司" prop="logisticsId">
|
||||||
|
<el-select v-model="fulfillForm.logisticsId" placeholder="请选择物流公司" filterable style="width: 260px">
|
||||||
|
<el-option
|
||||||
|
v-for="item in checkedLogistics"
|
||||||
|
:key="getLogisticsId(item)"
|
||||||
|
:label="getLogisticsName(item)"
|
||||||
|
:value="getLogisticsId(item)"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="物流单号" prop="logisticsNo">
|
||||||
|
<el-input v-model="fulfillForm.logisticsNo" placeholder="请输入物流单号" style="width: 260px" />
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
v-if="fulfillForm.fulfillmentMode === 'NO_DELIVERY'"
|
||||||
|
label="配送说明"
|
||||||
|
prop="deliveryRemark"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="fulfillForm.deliveryRemark"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
maxlength="500"
|
||||||
|
show-word-limit
|
||||||
|
placeholder="选填,如:商家自行配送,预计今日送达"
|
||||||
|
style="width: 480px"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="物流单号" prop="logisticsNo">
|
|
||||||
<el-input v-model="partDeliveryForm.logisticsNo" placeholder="请输入物流单号" style="width: 260px" />
|
<el-form-item label-width="0" class="part-delivery-goods-item">
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label-width="0" prop="partDeliveryDTOList" class="part-delivery-goods-item">
|
|
||||||
<div v-if="deliverableOrderItems.length" class="part-delivery-table-wrap">
|
<div v-if="deliverableOrderItems.length" class="part-delivery-table-wrap">
|
||||||
<el-table
|
<el-table
|
||||||
ref="partDeliveryTable"
|
v-if="fulfillForm.fulfillmentMode === 'SPLIT'"
|
||||||
|
ref="fulfillTable"
|
||||||
border
|
border
|
||||||
:data="deliverableOrderItems"
|
:data="deliverableOrderItems"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@selection-change="handlePartDeliverySelectionChange"
|
@selection-change="handleFulfillSelectionChange"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" :selectable="isPartDeliveryRowSelectable" />
|
<el-table-column type="selection" width="55" :selectable="isPartDeliveryRowSelectable" />
|
||||||
<el-table-column label="商品" min-width="420">
|
<el-table-column label="商品" min-width="420">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="part-delivery-goods">
|
<div class="part-delivery-goods">
|
||||||
<img :src="row.image" class="part-delivery-goods-img" alt="" />
|
<img :src="row.image" class="part-delivery-goods-img" alt="" />
|
||||||
<div class="part-delivery-goods-info">
|
<div class="part-delivery-goods-info">
|
||||||
<div class="part-delivery-goods-name">{{ row.goodsName }}</div>
|
<div class="part-delivery-goods-name">{{ row.goodsName }}</div>
|
||||||
<div
|
<div
|
||||||
v-for="(specValue, specKey) in parseOrderItemSpecs(row.specs)"
|
v-for="(specValue, specKey) in parseOrderItemSpecs(row.specs)"
|
||||||
:key="specKey"
|
:key="specKey"
|
||||||
class="part-delivery-goods-spec"
|
class="part-delivery-goods-spec"
|
||||||
>
|
>
|
||||||
<span v-if="specKey !== 'images'">{{ specKey }}:{{ specValue }}</span>
|
<span v-if="specKey !== 'images'">{{ specKey }}:{{ specValue }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</el-table-column>
|
||||||
</el-table-column>
|
<el-table-column label="单价" min-width="110" align="center">
|
||||||
<el-table-column label="单价" min-width="110" align="center">
|
<template #default="{ row }">
|
||||||
<template #default="{ row }">
|
{{ $filters.unitPrice(row.unitPrice || 0, "¥") }}
|
||||||
{{ $filters.unitPrice(row.unitPrice || 0, "¥") }}
|
</template>
|
||||||
</template>
|
</el-table-column>
|
||||||
</el-table-column>
|
<el-table-column label="待发/购买" min-width="120" align="center">
|
||||||
<el-table-column label="数量" min-width="140" align="center">
|
<template #default="{ row }">
|
||||||
<template #default="{ row }">
|
{{ getRemainingDeliveryNum(row) }} / {{ row.num }}
|
||||||
<el-input-number
|
</template>
|
||||||
v-model="partDeliveryForm.deliveryNumMap[getOrderItemId(row)]"
|
</el-table-column>
|
||||||
:min="1"
|
<el-table-column label="本次发货" min-width="140" align="center">
|
||||||
:max="getRemainingDeliveryNum(row)"
|
<template #default="{ row }">
|
||||||
:precision="0"
|
<el-input-number
|
||||||
controls-position="right"
|
v-model="fulfillForm.deliveryNumMap[getOrderItemId(row)]"
|
||||||
size="default"
|
:min="1"
|
||||||
class="part-delivery-num-input"
|
:max="getRemainingDeliveryNum(row)"
|
||||||
@change="(val) => handlePartDeliveryNumChange(row, val)"
|
:precision="0"
|
||||||
/>
|
controls-position="right"
|
||||||
</template>
|
size="default"
|
||||||
</el-table-column>
|
class="part-delivery-num-input"
|
||||||
<el-table-column label="已发包裹" min-width="110" align="center">
|
@change="(val) => handleFulfillNumChange(row, val)"
|
||||||
<template #default="{ row }">
|
/>
|
||||||
{{ getShippedPackageNum(row) }}
|
</template>
|
||||||
</template>
|
</el-table-column>
|
||||||
</el-table-column>
|
<el-table-column label="已发" min-width="80" align="center">
|
||||||
<el-table-column label="小计" min-width="110" align="center">
|
<template #default="{ row }">
|
||||||
<template #default="{ row }">
|
{{ getShippedPackageNum(row) }}
|
||||||
{{ $filters.unitPrice(getPartDeliverySubtotal(row), "¥") }}
|
</template>
|
||||||
</template>
|
</el-table-column>
|
||||||
</el-table-column>
|
</el-table>
|
||||||
</el-table>
|
|
||||||
|
<el-table v-else border :data="deliverableOrderItems" style="width: 100%">
|
||||||
|
<el-table-column label="商品" min-width="420">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="part-delivery-goods">
|
||||||
|
<img :src="row.image" class="part-delivery-goods-img" alt="" />
|
||||||
|
<div class="part-delivery-goods-info">
|
||||||
|
<div class="part-delivery-goods-name">{{ row.goodsName }}</div>
|
||||||
|
<div
|
||||||
|
v-for="(specValue, specKey) in parseOrderItemSpecs(row.specs)"
|
||||||
|
:key="specKey"
|
||||||
|
class="part-delivery-goods-spec"
|
||||||
|
>
|
||||||
|
<span v-if="specKey !== 'images'">{{ specKey }}:{{ specValue }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="单价" min-width="110" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ $filters.unitPrice(row.unitPrice || 0, "¥") }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="待发数量" min-width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ getRemainingDeliveryNum(row) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="购买数量" min-width="100" align="center" prop="num" />
|
||||||
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="delivery-empty">暂无可发货商品</div>
|
<div v-else class="delivery-empty">暂无可发货商品</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<div v-if="orderPackages.length" class="package-list">
|
<div v-if="orderPackages.length" class="package-list">
|
||||||
<div class="package-title">已有包裹</div>
|
<div class="package-title">已有包裹</div>
|
||||||
<el-table :data="orderPackages" border size="small">
|
<el-table :data="orderPackages" border size="small">
|
||||||
<el-table-column prop="logisticsName" label="物流公司" min-width="120" />
|
<el-table-column label="方式" width="100">
|
||||||
<el-table-column prop="logisticsNo" label="物流单号" min-width="160" />
|
<template #default="{ row }">{{ getFulfillmentModeLabel(row.fulfillmentMode) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="物流/说明" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<template v-if="isNoDeliveryPackage(row)">
|
||||||
|
无需配送<span v-if="row.deliveryRemark">({{ row.deliveryRemark }})</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>{{ row.logisticsName }} / {{ row.logisticsNo }}</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="logisticsTime" label="发货时间" width="170" />
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="partDeliveryModal = false">关闭</el-button>
|
<el-button @click="fulfillModal = false">关闭</el-button>
|
||||||
<el-button type="primary" :loading="partDeliverySubmitting" @click="submitPartDelivery">确认发货</el-button>
|
<el-button type="primary" :loading="fulfillSubmitting" @click="submitFulfill">确认发货</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@@ -611,6 +710,21 @@
|
|||||||
:key="packageIndex"
|
:key="packageIndex"
|
||||||
>
|
>
|
||||||
<div class="layui-layer-wrap">
|
<div class="layui-layer-wrap">
|
||||||
|
<template v-if="isNoDeliveryPackage(packageItem)">
|
||||||
|
<dl>
|
||||||
|
<dt>配送方式:</dt>
|
||||||
|
<dd><div class="text-box">无需配送</div></dd>
|
||||||
|
</dl>
|
||||||
|
<dl v-if="packageItem.logisticsTime">
|
||||||
|
<dt>发货时间:</dt>
|
||||||
|
<dd><div class="text-box">{{ packageItem.logisticsTime }}</div></dd>
|
||||||
|
</dl>
|
||||||
|
<dl v-if="packageItem.deliveryRemark">
|
||||||
|
<dt>配送说明:</dt>
|
||||||
|
<dd><div class="text-box">{{ packageItem.deliveryRemark }}</div></dd>
|
||||||
|
</dl>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>物流公司:</dt>
|
<dt>物流公司:</dt>
|
||||||
<dd><div class="text-box">{{ packageItem.logisticsName }}</div></dd>
|
<dd><div class="text-box">{{ packageItem.logisticsName }}</div></dd>
|
||||||
@@ -619,6 +733,7 @@
|
|||||||
<dt>快递单号:</dt>
|
<dt>快递单号:</dt>
|
||||||
<dd><div class="text-box">{{ packageItem.logisticsNo }}</div></dd>
|
<dd><div class="text-box">{{ packageItem.logisticsNo }}</div></dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
</template>
|
||||||
<div class="div-express-log">
|
<div class="div-express-log">
|
||||||
<ul class="express-log express-log-name">
|
<ul class="express-log express-log-name">
|
||||||
<li v-for="(item, index) in packageItem.orderPackageItemList" :key="index">
|
<li v-for="(item, index) in packageItem.orderPackageItemList" :key="index">
|
||||||
@@ -628,7 +743,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="div-express-log">
|
<div v-if="!isNoDeliveryPackage(packageItem)" class="div-express-log">
|
||||||
<ul v-if="packageItem.traces && packageItem.traces.traces" class="express-log">
|
<ul v-if="packageItem.traces && packageItem.traces.traces" class="express-log">
|
||||||
<li v-for="(item, index) in packageItem.traces.traces" :key="index">
|
<li v-for="(item, index) in packageItem.traces.traces" :key="index">
|
||||||
<span class="time">{{ item.AcceptTime || item.acceptTime }}</span>
|
<span class="time">{{ item.AcceptTime || item.acceptTime }}</span>
|
||||||
@@ -666,9 +781,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog v-model="orderDeliverModal" title="订单发货" width="500px">
|
<el-dialog v-model="faceSheetModal" title="打印电子面单" width="500px">
|
||||||
<el-form
|
<el-form
|
||||||
v-if="facesheetFlag"
|
|
||||||
ref="faceSheetForm"
|
ref="faceSheetForm"
|
||||||
:model="faceSheetForm"
|
:model="faceSheetForm"
|
||||||
:rules="faceSheetFormValidate"
|
:rules="faceSheetFormValidate"
|
||||||
@@ -685,30 +799,9 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<el-form
|
|
||||||
v-else
|
|
||||||
ref="orderDeliveryForm"
|
|
||||||
:model="orderDeliveryForm"
|
|
||||||
:rules="orderDeliverFormValidate"
|
|
||||||
label-width="90px"
|
|
||||||
>
|
|
||||||
<el-form-item label="物流公司" prop="logisticsId">
|
|
||||||
<el-select v-model="orderDeliveryForm.logisticsId" placeholder="请选择" style="width: 250px">
|
|
||||||
<el-option
|
|
||||||
v-for="(item, i) in checkedLogistics"
|
|
||||||
:key="i"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.logisticsId"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="物流单号" prop="logisticsNo">
|
|
||||||
<el-input v-model="orderDeliveryForm.logisticsNo" style="width: 250px" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="orderDeliverModal = false">关闭</el-button>
|
<el-button @click="faceSheetModal = false">关闭</el-button>
|
||||||
<el-button type="primary" @click="orderDeliverySubmit">发货</el-button>
|
<el-button type="primary" @click="submitFaceSheet">打印</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@@ -744,6 +837,13 @@ import {
|
|||||||
cardKeyFulfillAlertType,
|
cardKeyFulfillAlertType,
|
||||||
resolveCardKeyFulfillMessage,
|
resolveCardKeyFulfillMessage,
|
||||||
} from "@/constants/goodsType";
|
} from "@/constants/goodsType";
|
||||||
|
import {
|
||||||
|
validateFulfillForm,
|
||||||
|
isNoDeliveryPackage,
|
||||||
|
getFulfillmentModeLabel,
|
||||||
|
resolveDefaultFulfillmentMode,
|
||||||
|
canShowShipButton,
|
||||||
|
} from "@/utils/orderFulfill";
|
||||||
export default {
|
export default {
|
||||||
name: "orderDetail",
|
name: "orderDetail",
|
||||||
components: {
|
components: {
|
||||||
@@ -764,9 +864,8 @@ export default {
|
|||||||
faceSheetFormValidate: {
|
faceSheetFormValidate: {
|
||||||
logisticsId: [{ required: true, message: "请选择物流公司" }],
|
logisticsId: [{ required: true, message: "请选择物流公司" }],
|
||||||
},
|
},
|
||||||
facesheetFlag: false,
|
|
||||||
logisticsModal: false,
|
logisticsModal: false,
|
||||||
orderDeliverModal: false,
|
faceSheetModal: false,
|
||||||
logisticsInfo: {
|
logisticsInfo: {
|
||||||
shipper: "",
|
shipper: "",
|
||||||
},
|
},
|
||||||
@@ -792,16 +891,14 @@ export default {
|
|||||||
modifyRemarkForm: {
|
modifyRemarkForm: {
|
||||||
sellerRemark: "",
|
sellerRemark: "",
|
||||||
},
|
},
|
||||||
//订单发货
|
fulfillModal: false,
|
||||||
orderDeliveryForm: {
|
fulfillSubmitting: false,
|
||||||
logisticsNo: "", //发货单号
|
autoOpenFulfill: true,
|
||||||
logisticsId: "", //物流公司
|
fulfillForm: {
|
||||||
},
|
fulfillmentMode: "WHOLE",
|
||||||
partDeliveryModal: false,
|
|
||||||
partDeliverySubmitting: false,
|
|
||||||
partDeliveryForm: {
|
|
||||||
logisticsNo: "",
|
logisticsNo: "",
|
||||||
logisticsId: "",
|
logisticsId: "",
|
||||||
|
deliveryRemark: "",
|
||||||
selectedOrderItemIds: [],
|
selectedOrderItemIds: [],
|
||||||
deliveryNumMap: {},
|
deliveryNumMap: {},
|
||||||
},
|
},
|
||||||
@@ -835,11 +932,7 @@ export default {
|
|||||||
consigneeAddressPath: "",
|
consigneeAddressPath: "",
|
||||||
consigneeAddressIdPath: "",
|
consigneeAddressIdPath: "",
|
||||||
},
|
},
|
||||||
orderDeliverFormValidate: {
|
fulfillRules: {
|
||||||
logisticsNo: [{ required: true, message: "发货单号不能为空", trigger: "change" }],
|
|
||||||
logisticsId: [{ required: true, message: "请选择物流公司", trigger: "blur" }],
|
|
||||||
},
|
|
||||||
partDeliveryRules: {
|
|
||||||
logisticsNo: [{ required: true, message: "发货单号不能为空", trigger: "change" }],
|
logisticsNo: [{ required: true, message: "发货单号不能为空", trigger: "change" }],
|
||||||
logisticsId: [{ required: true, message: "请选择物流公司", trigger: "change" }],
|
logisticsId: [{ required: true, message: "请选择物流公司", trigger: "change" }],
|
||||||
},
|
},
|
||||||
@@ -888,14 +981,14 @@ export default {
|
|||||||
ecouponCardKeyRows() {
|
ecouponCardKeyRows() {
|
||||||
return flattenOrderCardKeys(this.data, true);
|
return flattenOrderCardKeys(this.data, true);
|
||||||
},
|
},
|
||||||
canPartDelivery() {
|
canShip() {
|
||||||
if (!this.allowOperation.ship) return false;
|
return canShowShipButton(this.allowOperation, this.orderInfo?.order);
|
||||||
const status = this.orderInfo.order && this.orderInfo.order.orderStatus;
|
},
|
||||||
return (
|
showFulfillModeSelector() {
|
||||||
!this.isNonPhysicalOrder &&
|
const status = this.orderInfo?.order?.orderStatus;
|
||||||
["UNDELIVERED", "PARTS_DELIVERED"].includes(status) &&
|
if (status === "PARTS_DELIVERED") return false;
|
||||||
this.deliverableOrderItems.length > 0
|
const op = this.allowOperation || {};
|
||||||
);
|
return [op.fulfillWhole, op.fulfillSplit, op.fulfillNoDelivery].filter(Boolean).length > 1;
|
||||||
},
|
},
|
||||||
deliverableOrderItems() {
|
deliverableOrderItems() {
|
||||||
return (this.data || []).filter((item) => {
|
return (this.data || []).filter((item) => {
|
||||||
@@ -1011,16 +1104,16 @@ export default {
|
|||||||
return pkgKeys.some((key) => orderKeys.includes(key));
|
return pkgKeys.some((key) => orderKeys.includes(key));
|
||||||
},
|
},
|
||||||
getRemainingDeliveryNum(item) {
|
getRemainingDeliveryNum(item) {
|
||||||
|
if (item != null && item.remainDeliverNumber != null && item.remainDeliverNumber !== "") {
|
||||||
|
const remain = Number(item.remainDeliverNumber);
|
||||||
|
if (Number.isFinite(remain)) return Math.max(remain, 0);
|
||||||
|
}
|
||||||
const total = Number(item && item.num) || 0;
|
const total = Number(item && item.num) || 0;
|
||||||
return Math.max(total - this.getShippedPackageNum(item), 0);
|
return Math.max(total - this.getShippedPackageNum(item), 0);
|
||||||
},
|
},
|
||||||
getPartDeliverySubtotal(row) {
|
isNoDeliveryPackage,
|
||||||
const id = this.getOrderItemId(row);
|
getFulfillmentModeLabel,
|
||||||
const qty = Number(this.partDeliveryForm.deliveryNumMap[id]) || 0;
|
initFulfillNumMap() {
|
||||||
const unitPrice = Number(row.unitPrice) || 0;
|
|
||||||
return unitPrice * qty;
|
|
||||||
},
|
|
||||||
initPartDeliveryNumMap() {
|
|
||||||
const map = {};
|
const map = {};
|
||||||
this.deliverableOrderItems.forEach((item) => {
|
this.deliverableOrderItems.forEach((item) => {
|
||||||
const id = this.getOrderItemId(item);
|
const id = this.getOrderItemId(item);
|
||||||
@@ -1028,10 +1121,10 @@ export default {
|
|||||||
map[id] = this.getRemainingDeliveryNum(item);
|
map[id] = this.getRemainingDeliveryNum(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.partDeliveryForm.deliveryNumMap = map;
|
this.fulfillForm.deliveryNumMap = map;
|
||||||
},
|
},
|
||||||
refreshPartDeliveryAfterSubmit() {
|
refreshFulfillAfterSubmit() {
|
||||||
this.partDeliveryModal = false;
|
this.fulfillModal = false;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
API_Order.getOrderDetail(this.sn).then((res) => {
|
API_Order.getOrderDetail(this.sn).then((res) => {
|
||||||
@@ -1049,80 +1142,132 @@ export default {
|
|||||||
this.getOrderPackages(),
|
this.getOrderPackages(),
|
||||||
]).finally(() => {
|
]).finally(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.partDeliveryForm.logisticsNo = "";
|
this.resetFulfillForm(false);
|
||||||
this.partDeliveryForm.logisticsId = "";
|
this.initFulfillNumMap();
|
||||||
this.partDeliveryForm.selectedOrderItemIds = [];
|
|
||||||
this.initPartDeliveryNumMap();
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.partDeliveryTable?.clearSelection();
|
this.$refs.fulfillTable?.clearSelection();
|
||||||
this.$refs.partDeliveryForm?.clearValidate();
|
this.$refs.fulfillFormRef?.clearValidate();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handlePartDeliveryNumChange(row, value) {
|
handleFulfillNumChange(row, value) {
|
||||||
const id = this.getOrderItemId(row);
|
const id = this.getOrderItemId(row);
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
const num = Number(value);
|
const num = Number(value);
|
||||||
if (!Number.isFinite(num) || num < 1) return;
|
if (!Number.isFinite(num) || num < 1) return;
|
||||||
if (!this.partDeliveryForm.selectedOrderItemIds.includes(id)) {
|
if (!this.fulfillForm.selectedOrderItemIds.includes(id)) {
|
||||||
this.partDeliveryForm.selectedOrderItemIds = [
|
this.fulfillForm.selectedOrderItemIds = [...this.fulfillForm.selectedOrderItemIds, id];
|
||||||
...this.partDeliveryForm.selectedOrderItemIds,
|
|
||||||
id,
|
|
||||||
];
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.partDeliveryTable?.toggleRowSelection(row, true);
|
this.$refs.fulfillTable?.toggleRowSelection(row, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isPartDeliveryRowSelectable(row) {
|
isPartDeliveryRowSelectable(row) {
|
||||||
return this.getRemainingDeliveryNum(row) > 0;
|
return this.getRemainingDeliveryNum(row) > 0;
|
||||||
},
|
},
|
||||||
handlePartDeliverySelectionChange(selection) {
|
handleFulfillSelectionChange(selection) {
|
||||||
const selectedIds = [];
|
const selectedIds = [];
|
||||||
selection.forEach((item) => {
|
selection.forEach((item) => {
|
||||||
const id = this.getOrderItemId(item);
|
const id = this.getOrderItemId(item);
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
selectedIds.push(id);
|
selectedIds.push(id);
|
||||||
if (this.partDeliveryForm.deliveryNumMap[id] == null) {
|
if (this.fulfillForm.deliveryNumMap[id] == null) {
|
||||||
this.partDeliveryForm.deliveryNumMap[id] = this.getRemainingDeliveryNum(item);
|
this.fulfillForm.deliveryNumMap[id] = this.getRemainingDeliveryNum(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.partDeliveryForm.selectedOrderItemIds = selectedIds;
|
this.fulfillForm.selectedOrderItemIds = selectedIds;
|
||||||
},
|
},
|
||||||
isPartDeliveryItemSelected(item) {
|
buildFulfillItems() {
|
||||||
const id = this.getOrderItemId(item);
|
return this.fulfillForm.selectedOrderItemIds
|
||||||
return !!id && this.partDeliveryForm.selectedOrderItemIds.includes(id);
|
|
||||||
},
|
|
||||||
buildPartDeliveryDTOList() {
|
|
||||||
return this.partDeliveryForm.selectedOrderItemIds
|
|
||||||
.map((orderItemId) => ({
|
.map((orderItemId) => ({
|
||||||
orderItemId,
|
orderItemId,
|
||||||
deliveryNum: Number(this.partDeliveryForm.deliveryNumMap[orderItemId]),
|
deliveryNum: Number(this.fulfillForm.deliveryNumMap[orderItemId]),
|
||||||
}))
|
}))
|
||||||
.filter((item) => item.orderItemId && item.deliveryNum > 0);
|
.filter((item) => item.orderItemId && item.deliveryNum > 0);
|
||||||
},
|
},
|
||||||
resetPartDeliveryForm() {
|
resetFulfillForm(clearMode = true) {
|
||||||
this.partDeliveryForm = {
|
const mode = clearMode
|
||||||
|
? resolveDefaultFulfillmentMode(
|
||||||
|
this.allowOperation,
|
||||||
|
this.orderInfo?.order?.orderStatus
|
||||||
|
) || "WHOLE"
|
||||||
|
: this.fulfillForm.fulfillmentMode;
|
||||||
|
this.fulfillForm = {
|
||||||
|
fulfillmentMode: mode,
|
||||||
logisticsNo: "",
|
logisticsNo: "",
|
||||||
logisticsId: "",
|
logisticsId: "",
|
||||||
|
deliveryRemark: "",
|
||||||
selectedOrderItemIds: [],
|
selectedOrderItemIds: [],
|
||||||
deliveryNumMap: {},
|
deliveryNumMap: {},
|
||||||
};
|
};
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.partDeliveryForm) {
|
this.$refs.fulfillFormRef?.clearValidate();
|
||||||
this.$refs.partDeliveryForm.clearValidate();
|
this.$refs.fulfillTable?.clearSelection();
|
||||||
}
|
|
||||||
if (this.$refs.partDeliveryTable) {
|
|
||||||
this.$refs.partDeliveryTable.clearSelection();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
openPartDelivery() {
|
openFulfill(presetMode) {
|
||||||
this.resetPartDeliveryForm();
|
this.resetFulfillForm(true);
|
||||||
this.partDeliveryModal = true;
|
if (presetMode === "SPLIT" || this.$route.query.fulfillMode === "SPLIT") {
|
||||||
|
this.fulfillForm.fulfillmentMode = "SPLIT";
|
||||||
|
} else {
|
||||||
|
const mode = resolveDefaultFulfillmentMode(
|
||||||
|
this.allowOperation,
|
||||||
|
this.orderInfo?.order?.orderStatus
|
||||||
|
);
|
||||||
|
if (mode) this.fulfillForm.fulfillmentMode = mode;
|
||||||
|
}
|
||||||
|
this.fulfillModal = true;
|
||||||
this.getCheckedLogistics();
|
this.getCheckedLogistics();
|
||||||
this.getOrderPackages();
|
this.getOrderPackages();
|
||||||
},
|
},
|
||||||
|
buildFulfillPayload() {
|
||||||
|
const mode = this.fulfillForm.fulfillmentMode;
|
||||||
|
const payload = { fulfillmentMode: mode };
|
||||||
|
if (mode === "WHOLE" || mode === "SPLIT") {
|
||||||
|
payload.logisticsId = this.fulfillForm.logisticsId;
|
||||||
|
payload.logisticsNo = this.fulfillForm.logisticsNo;
|
||||||
|
}
|
||||||
|
if (mode === "NO_DELIVERY") {
|
||||||
|
const remark = (this.fulfillForm.deliveryRemark || "").trim();
|
||||||
|
if (remark) payload.deliveryRemark = remark;
|
||||||
|
}
|
||||||
|
if (mode === "SPLIT") {
|
||||||
|
payload.items = this.buildFulfillItems();
|
||||||
|
}
|
||||||
|
return payload;
|
||||||
|
},
|
||||||
|
submitFulfill() {
|
||||||
|
const mode = this.fulfillForm.fulfillmentMode;
|
||||||
|
const payload = this.buildFulfillPayload();
|
||||||
|
const validateMsg = validateFulfillForm(mode, payload);
|
||||||
|
if (validateMsg) {
|
||||||
|
this.$Message.error(validateMsg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mode !== "NO_DELIVERY") {
|
||||||
|
this.$refs.fulfillFormRef.validate((valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
this.doSubmitFulfill(payload);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.doSubmitFulfill(payload);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doSubmitFulfill(payload) {
|
||||||
|
this.fulfillSubmitting = true;
|
||||||
|
API_Order.orderFulfill(this.sn, payload)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
this.$Message.success("发货成功");
|
||||||
|
this.refreshFulfillAfterSubmit();
|
||||||
|
} else if (res.code === 1003) {
|
||||||
|
this.$Message.warning(res.message || "访问过于频繁,请稍后再试");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.fulfillSubmitting = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
getCheckedLogistics() {
|
getCheckedLogistics() {
|
||||||
API_Order.getLogisticsChecked().then((res) => {
|
API_Order.getLogisticsChecked().then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
@@ -1135,33 +1280,7 @@ export default {
|
|||||||
if (res.success) {
|
if (res.success) {
|
||||||
this.orderPackages = Array.isArray(res.result) ? res.result : [];
|
this.orderPackages = Array.isArray(res.result) ? res.result : [];
|
||||||
}
|
}
|
||||||
this.initPartDeliveryNumMap();
|
this.initFulfillNumMap();
|
||||||
});
|
|
||||||
},
|
|
||||||
submitPartDelivery() {
|
|
||||||
this.$refs.partDeliveryForm.validate((valid) => {
|
|
||||||
if (!valid) return;
|
|
||||||
const partDeliveryDTOList = this.buildPartDeliveryDTOList();
|
|
||||||
if (!partDeliveryDTOList.length) {
|
|
||||||
this.$Message.error("请选择本次发货商品并填写发货数量");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.partDeliverySubmitting = true;
|
|
||||||
API_Order.partDelivery(this.sn, {
|
|
||||||
orderSn: this.sn,
|
|
||||||
logisticsNo: this.partDeliveryForm.logisticsNo,
|
|
||||||
logisticsId: this.partDeliveryForm.logisticsId,
|
|
||||||
partDeliveryDTOList,
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (res.success) {
|
|
||||||
this.$Message.success("发货成功");
|
|
||||||
this.refreshPartDeliveryAfterSubmit();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.partDeliverySubmitting = false;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1180,6 +1299,14 @@ export default {
|
|||||||
: [];
|
: [];
|
||||||
this.getContentPrice();
|
this.getContentPrice();
|
||||||
this.getOrderPrice();
|
this.getOrderPrice();
|
||||||
|
if (
|
||||||
|
this.autoOpenFulfill &&
|
||||||
|
this.canShip &&
|
||||||
|
(this.$route.query.openFulfill || this.$route.query.fulfillMode)
|
||||||
|
) {
|
||||||
|
this.autoOpenFulfill = false;
|
||||||
|
this.$nextTick(() => this.openFulfill(this.$route.query.fulfillMode));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -1240,11 +1367,10 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
toPrint () {
|
toPrint () {
|
||||||
this.facesheetFlag = true;
|
|
||||||
API_Logistics.getCheckedOn().then((res) => {
|
API_Logistics.getCheckedOn().then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
this.checkedLogistics = res.result;
|
this.checkedLogistics = res.result;
|
||||||
this.orderDeliverModal = true;
|
this.faceSheetModal = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -1275,7 +1401,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
toPrints (printWindow) {
|
toPrints (printWindow) {
|
||||||
this.orderDeliverModal = false;
|
this.faceSheetModal = false;
|
||||||
if (!this.someJSONdata) {
|
if (!this.someJSONdata) {
|
||||||
printWindow && printWindow.close();
|
printWindow && printWindow.close();
|
||||||
return;
|
return;
|
||||||
@@ -1290,39 +1416,26 @@ export default {
|
|||||||
win.focus();
|
win.focus();
|
||||||
this.$Message.success("电子面单发货成功");
|
this.$Message.success("电子面单发货成功");
|
||||||
this.getDataDetail();
|
this.getDataDetail();
|
||||||
|
this.getOrderPackages();
|
||||||
},
|
},
|
||||||
orderDeliverySubmit () {
|
submitFaceSheet () {
|
||||||
if (this.facesheetFlag) {
|
this.$refs.faceSheetForm.validate((valid) => {
|
||||||
this.$refs.faceSheetForm.validate((valid) => {
|
if (valid) {
|
||||||
if (valid) {
|
const printWindow = window.open("", "_blank");
|
||||||
const printWindow = window.open("", "_blank");
|
API_Order.getOrderFaceSheet(this.sn, this.faceSheetForm).then((res) => {
|
||||||
API_Order.getOrderFaceSheet(this.sn, this.faceSheetForm).then((res) => {
|
if (res.success && res.result.printTemplate) {
|
||||||
if (res.success && res.result.printTemplate) {
|
this.someJSONdata = res.result.printTemplate;
|
||||||
this.someJSONdata = res.result.printTemplate;
|
this.toPrints(printWindow);
|
||||||
this.toPrints(printWindow);
|
} else {
|
||||||
} else {
|
|
||||||
printWindow && printWindow.close();
|
|
||||||
this.$Message.error("电子面单发货失败!");
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
printWindow && printWindow.close();
|
printWindow && printWindow.close();
|
||||||
this.$Message.error("电子面单发货失败!");
|
this.$Message.error("电子面单发货失败!");
|
||||||
});
|
}
|
||||||
}
|
}).catch(() => {
|
||||||
});
|
printWindow && printWindow.close();
|
||||||
} else {
|
this.$Message.error("电子面单发货失败!");
|
||||||
this.$refs.orderDeliveryForm.validate((valid) => {
|
});
|
||||||
if (valid) {
|
}
|
||||||
API_Order.orderDelivery(this.sn, this.orderDeliveryForm).then((res) => {
|
});
|
||||||
if (res.success) {
|
|
||||||
this.$Message.success("订单发货成功");
|
|
||||||
this.orderDeliverModal = false;
|
|
||||||
this.getDataDetail();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
getLogisticsSetting () {
|
getLogisticsSetting () {
|
||||||
API_Logistics.getLogisticsSetting().then((res) => {
|
API_Logistics.getLogisticsSetting().then((res) => {
|
||||||
@@ -1605,6 +1718,24 @@ export default {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fulfill-mode-selector {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fulfill-mode-tip {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-item-line {
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-remark-text {
|
||||||
|
color: #909399;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-card {
|
.flex-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 600px;
|
height: 600px;
|
||||||
|
|||||||
@@ -108,9 +108,14 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="createTime" label="下单时间" width="170" />
|
<el-table-column prop="createTime" label="下单时间" width="170" />
|
||||||
<el-table-column label="操作" width="100" align="center" fixed="right">
|
<el-table-column label="操作" width="160" align="center" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<a class="link-text" @click="detail(row)">查看</a>
|
<a class="link-text" @click="detail(row)">查看</a>
|
||||||
|
<a
|
||||||
|
v-if="canShip(row)"
|
||||||
|
class="link-text continue-ship-link"
|
||||||
|
@click="detail(row, true)"
|
||||||
|
>{{ row.orderStatus === "PARTS_DELIVERED" ? "继续发货" : "发货" }}</a>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -133,6 +138,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as API_Order from "@/api/order";
|
import * as API_Order from "@/api/order";
|
||||||
|
import { canShowShipButton, isNonShippableOrder } from "@/utils/orderFulfill";
|
||||||
export default {
|
export default {
|
||||||
name: "orderList",
|
name: "orderList",
|
||||||
data() {
|
data() {
|
||||||
@@ -335,12 +341,24 @@ export default {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
detail(v) {
|
detail(v, openFulfill = false) {
|
||||||
this.$filters.customRouterPush({
|
this.$filters.customRouterPush({
|
||||||
name: "order-detail",
|
name: "order-detail",
|
||||||
query: { sn: v.sn },
|
query: {
|
||||||
|
sn: v.sn,
|
||||||
|
...(openFulfill ? { openFulfill: "1" } : {}),
|
||||||
|
...(openFulfill && v.orderStatus === "PARTS_DELIVERED" ? { fulfillMode: "SPLIT" } : {}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
canShip(row) {
|
||||||
|
if (!row) return false;
|
||||||
|
if (row.allowOperationVO) {
|
||||||
|
return canShowShipButton(row.allowOperationVO, row);
|
||||||
|
}
|
||||||
|
if (isNonShippableOrder(row)) return false;
|
||||||
|
return row.orderStatus === "UNDELIVERED" || row.orderStatus === "PARTS_DELIVERED";
|
||||||
|
},
|
||||||
async exportOrder() {
|
async exportOrder() {
|
||||||
if (!this.searchForm.startDate || !this.searchForm.endDate) {
|
if (!this.searchForm.startDate || !this.searchForm.endDate) {
|
||||||
this.$Message.error("必须选择时间范围,搜索后进行导出!");
|
this.$Message.error("必须选择时间范围,搜索后进行导出!");
|
||||||
@@ -417,4 +435,7 @@ export default {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
.continue-ship-link {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user