feat: 基本实现拆单收货--2

This commit is contained in:
2026-07-29 18:33:34 +08:00
parent 63ff3edb11
commit fb178d304f
2 changed files with 153 additions and 169 deletions
@@ -20,30 +20,48 @@
<template v-else> <template v-else>
<article v-for="order in visibleOrders" :key="order.viewId" class="order-card"> <article v-for="order in visibleOrders" :key="order.viewId" class="order-card">
<header class="order-card__header"> <header class="order-card__header">
<span>订单号:{{ order.orderNumberId || order.orderId || '-' }}</span> <div class="order-card__no">
<van-icon name="orders-o" />
<span>订单号:{{ order.orderNumberId || order.orderId || '-' }}</span>
</div>
<b :class="order.statusClass">{{ order.status }}</b> <b :class="order.statusClass">{{ order.status }}</b>
</header> </header>
<div class="order-card__body" @click="goOrderDetail(order)"> <div class="order-card__body" @click="goOrderDetail(order)">
<div class="order-goods"> <div class="order-goods-list">
<van-image <div
v-if="firstProduct(order)" v-for="item in previewProducts(order.products)"
class="order-thumb" :key="item.id"
:src="firstProduct(order).image" class="order-goods-row"
fit="cover" >
/> <van-image
<div class="order-goods__info"> class="order-thumb"
<h3>{{ firstProduct(order).name || '-' }}</h3> :src="item.image"
<p>{{ skuText(firstProduct(order)) }}</p> fit="cover"
<p>创建时间:{{ order.createTime || '-' }}</p> />
<div class="order-goods__info">
<h3>{{ item.name || '-' }}</h3>
<p>{{ skuText(item) }}</p>
<b>¥{{ item.price || order.amount }}</b>
</div>
<span class="order-goods-count">x{{ productCount(item) }}</span>
</div> </div>
</div> </div>
<div class="order-summary">
<p>共{{ order.count }}件商品</p>
<p>合计:<b>¥{{ order.amount }}</b></p>
</div>
</div> </div>
<div v-if="showLogistics(order)" class="logistics-row">
<van-icon name="logistics" />
<span>物流状态:包裹已到达目的地城市</span>
<van-icon name="arrow" />
</div>
<div class="order-card__summary">
<span>共{{ orderProductCount(order) }}件商品</span>
<span>合计:<b>¥{{ order.amount }}</b></span>
</div>
<p class="order-create-time">下单时间:{{ order.createTime || '-' }}</p>
<footer v-if="orderActions(order).length"> <footer v-if="orderActions(order).length">
<div class="order-actions"> <div class="order-actions">
<van-button <van-button
@@ -83,11 +101,6 @@ const ORDER_STATE = {
REFUNDED: 7 REFUNDED: 7
} }
const ORDER_SPLIT_STATE = {
NOT_SPLIT: 0,
SPLIT: 1
}
const ORDER_EVENT_TYPE = { const ORDER_EVENT_TYPE = {
RETURN_ORDER: 4, RETURN_ORDER: 4,
EXCHANGE_ORDER: 5, EXCHANGE_ORDER: 5,
@@ -132,7 +145,6 @@ export default {
{ label: '已完成', type: 'sub-state', state: ORDER_STATE.COMPLETED }, { label: '已完成', type: 'sub-state', state: ORDER_STATE.COMPLETED },
{ label: '售后', type: 'after-sale' } { label: '售后', type: 'after-sale' }
], ],
orders: [],
orderItems: [], orderItems: [],
loading: false, loading: false,
refreshing: false, refreshing: false,
@@ -146,26 +158,24 @@ export default {
visibleOrders() { visibleOrders() {
const tab = this.activeTab const tab = this.activeTab
const orderItems = this.orderItems.length ? this.orderItems : this.buildOrderItems()
if (tab.type === 'all') { if (tab.type === 'all') {
return orderItems return this.orderItems
} }
if (tab.type === 'pending-main') { if (tab.type === 'pending-main') {
return orderItems.filter(order => order.displayType === 'main' && Number(order.orderState) === ORDER_STATE.PENDING_PAY) return this.orderItems.filter(order => order.displayType === 'main' && Number(order.orderState) === ORDER_STATE.PENDING_PAY)
} }
if (tab.type === 'sub-state') { if (tab.type === 'sub-state') {
return orderItems.filter(order => order.displayType === 'sub' && Number(order.orderState) === tab.state) return this.orderItems.filter(order => order.displayType === 'sub' && Number(order.orderState) === tab.state)
} }
if (tab.type === 'cancelled') { if (tab.type === 'cancelled') {
return orderItems.filter(order => Number(order.orderState) === ORDER_STATE.CANCELLED) return this.orderItems.filter(order => Number(order.orderState) === ORDER_STATE.CANCELLED)
} }
if (tab.type === 'after-sale') { if (tab.type === 'after-sale') {
return orderItems.filter(order => { return this.orderItems.filter(order => {
return order.displayType === 'sub' && [ORDER_STATE.EXCHANGE, ORDER_STATE.REFUNDED].includes(Number(order.orderState)) return order.displayType === 'sub' && [ORDER_STATE.EXCHANGE, ORDER_STATE.REFUNDED].includes(Number(order.orderState))
}) })
} }
@@ -178,92 +188,21 @@ export default {
}, },
methods: { methods: {
...mapActions(useUserStore, ['fetchBridgeToken']), ...mapActions(useUserStore, ['fetchBridgeToken']),
normalizeOrder(order, subOrders = []) {
const sourceSubOrders = subOrders.length ? subOrders : order.subOrders
const normalizedSubOrders = (sourceSubOrders || []).map(subOrder => this.normalizeSubOrder(order, subOrder))
return {
...order,
viewId: `${order.mainOrderId || order.id}-${normalizedSubOrders.map(item => item.orderId).join('-') || 'main'}`,
canCancel: Number(order.orderState) === ORDER_STATE.PENDING_PAY,
subOrders: normalizedSubOrders
}
},
normalizeSubOrder(order, subOrder) {
const view = ORDER_STATE_VIEW[Number(subOrder.orderState)] || ORDER_STATE_VIEW[Number(order.orderState)] || ORDER_STATE_VIEW[ORDER_STATE.IN_PROGRESS]
return {
...subOrder,
status: view.text,
statusClass: view.statusClass,
count: Number(subOrder.count || 0)
}
},
normalizedSubOrders(order) {
return (order.subOrders || []).map(subOrder => this.normalizeSubOrder(order, subOrder))
},
buildOrderItems() {
return this.orders.flatMap(order => {
const subOrders = this.normalizedSubOrders(order)
const products = subOrders.flatMap(subOrder => subOrder.products || [])
const isMainOrderItem = Number(order.orderState) === ORDER_STATE.PENDING_PAY ||
(Number(order.orderState) === ORDER_STATE.CANCELLED && Number(order.isSplit) !== ORDER_SPLIT_STATE.SPLIT)
if (isMainOrderItem) {
return [{
...order,
viewId: `main-${order.mainOrderId || order.id}`,
displayType: 'main',
orderId: order.mainOrderId || order.orderId,
orderNumberId: order.mainOrderNumberId || order.orderNumberId,
status: ORDER_STATE_VIEW[Number(order.orderState)]?.text || order.status,
statusClass: ORDER_STATE_VIEW[Number(order.orderState)]?.statusClass || order.statusClass,
amount: order.amount || subOrders.reduce((total, subOrder) => total + Number(subOrder.amount || 0), 0).toFixed(2),
count: subOrders.reduce((total, subOrder) => total + Number(subOrder.count || 0), 0),
products,
subOrders
}]
}
return subOrders.map(subOrder => ({
...subOrder,
viewId: `sub-${subOrder.orderId}`,
displayType: 'sub',
mainOrderId: order.mainOrderId,
mainOrderNumberId: order.mainOrderNumberId
}))
})
},
splitSubOrdersByState(state) {
return this.orders.flatMap(order => {
if (Number(order.isSplit) !== ORDER_SPLIT_STATE.SPLIT) {
return []
}
return this.normalizedSubOrders(order)
.filter(subOrder => Number(subOrder.orderState) === state)
.map(subOrder => this.normalizeOrder(order, [subOrder]))
})
},
cancelledOrders() {
return this.orders.flatMap(order => {
const mainCancelled = Number(order.orderState) === ORDER_STATE.CANCELLED
if (mainCancelled && Number(order.isSplit) !== ORDER_SPLIT_STATE.SPLIT) {
return [this.normalizeOrder(order, order.subOrders)]
}
return this.normalizedSubOrders(order)
.filter(subOrder => Number(subOrder.orderState) === ORDER_STATE.CANCELLED)
.map(subOrder => this.normalizeOrder(order, [subOrder]))
})
},
previewProducts(products = []) { previewProducts(products = []) {
return products.slice(0, 3) return products.slice(0, 3)
}, },
firstProduct(order) { firstProduct(order) {
return order.products?.[0] || {} return order.products?.[0] || {}
}, },
productCount(product = {}) {
return Number(product.number || product.count || 1)
},
orderProductCount(order) {
const products = Array.isArray(order.products) ? order.products : []
const count = products.reduce((total, product) => total + this.productCount(product), 0)
return count || Number(order.count || 0)
},
skuText(product = {}) { skuText(product = {}) {
const sku = String(product.sku || product.skuText || '').trim() const sku = String(product.sku || product.skuText || '').trim()
const parts = sku.split('-').slice(-2).filter(Boolean) const parts = sku.split('-').slice(-2).filter(Boolean)
@@ -272,6 +211,9 @@ export default {
return color || size ? [color, size].filter(Boolean).join(' | ') : '-' return color || size ? [color, size].filter(Boolean).join(' | ') : '-'
}, },
showLogistics(order) {
return ![ORDER_STATE.CANCELLED, ORDER_STATE.COMPLETED].includes(Number(order.orderState))
},
actionKey(type, orderId) { actionKey(type, orderId) {
return `${type}-${orderId}` return `${type}-${orderId}`
}, },
@@ -282,15 +224,15 @@ export default {
const state = Number(subOrder.orderState) const state = Number(subOrder.orderState)
const actions = [] const actions = []
if (state === ORDER_STATE.IN_PROGRESS) {
actions.push({ orderType: ORDER_EVENT_TYPE.CONFIRM_RECEIVE, text: '确认收货', buttonClass: 'primary-action' })
}
if ([ORDER_STATE.IN_PROGRESS, ORDER_STATE.COMPLETED].includes(state)) { if ([ORDER_STATE.IN_PROGRESS, ORDER_STATE.COMPLETED].includes(state)) {
actions.push({ orderType: ORDER_EVENT_TYPE.RETURN_ORDER, text: '退货', buttonClass: 'dark-action' }) actions.push({ orderType: ORDER_EVENT_TYPE.RETURN_ORDER, text: '退货', buttonClass: 'dark-action' })
actions.push({ orderType: ORDER_EVENT_TYPE.EXCHANGE_ORDER, text: '换货', buttonClass: 'dark-action' }) actions.push({ orderType: ORDER_EVENT_TYPE.EXCHANGE_ORDER, text: '换货', buttonClass: 'dark-action' })
} }
if (state === ORDER_STATE.IN_PROGRESS) {
actions.push({ orderType: ORDER_EVENT_TYPE.CONFIRM_RECEIVE, text: '确认收货', buttonClass: 'primary-action' })
}
return actions return actions
}, },
goOrderDetail(order) { goOrderDetail(order) {
@@ -318,7 +260,6 @@ export default {
if (!this.supplierId || !this.userId) { if (!this.supplierId || !this.userId) {
Toast('用户信息不完整,请重新进入商城') Toast('用户信息不完整,请重新进入商城')
this.orders = []
this.orderItems = [] this.orderItems = []
return return
} }
@@ -328,11 +269,9 @@ export default {
userId: this.userId userId: this.userId
}) })
this.orders = data?.orders || []
this.orderItems = data?.orderItems || [] this.orderItems = data?.orderItems || []
} catch (error) { } catch (error) {
Toast(error.message || '查询订单失败,请稍后再试') Toast(error.message || '查询订单失败,请稍后再试')
this.orders = []
this.orderItems = [] this.orderItems = []
} finally { } finally {
if (showLoading) { if (showLoading) {
@@ -497,7 +436,7 @@ export default {
.order-card { .order-card {
overflow: hidden; overflow: hidden;
margin-bottom: 14px; margin-bottom: 14px;
border-radius: 8px; border-radius: 10px;
background: #fff; background: #fff;
} }
@@ -505,13 +444,26 @@ export default {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 14px 14px 6px; padding: 16px 14px 8px;
} }
.order-card__header span { .order-card__no {
display: flex;
min-width: 0;
align-items: center;
}
.order-card__no .van-icon {
flex: none;
margin-right: 8px;
color: #ff5b20;
font-size: 18px;
}
.order-card__no span {
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
color: #555; color: #222;
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -521,7 +473,7 @@ export default {
.order-card__header b { .order-card__header b {
flex: none; flex: none;
margin-left: 10px; margin-left: 10px;
font-size: 14px; font-size: 15px;
font-weight: 700; font-weight: 700;
} }
@@ -538,43 +490,47 @@ export default {
} }
.order-card__body { .order-card__body {
display: flex; padding: 12px 14px 14px;
align-items: flex-end;
justify-content: space-between;
padding: 12px 14px 10px;
} }
.order-goods { .order-goods-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.order-goods-row {
display: flex; display: flex;
min-width: 0; min-width: 0;
flex: 1; align-items: flex-start;
align-items: center;
} }
.order-thumb { .order-thumb {
width: 70px; width: 82px;
height: 70px; height: 82px;
flex: none; flex: none;
overflow: hidden; overflow: hidden;
border-radius: 4px; border-radius: 8px;
background: #f5f5f5; background: #f5f5f5;
} }
.order-goods__info { .order-goods__info {
min-width: 0; min-width: 0;
flex: 1; flex: 1;
padding-left: 10px; padding: 2px 10px 0 12px;
} }
.order-goods__info h3 { .order-goods__info h3 {
display: -webkit-box;
overflow: hidden; overflow: hidden;
margin: 0 0 6px; margin: 0 0 8px;
color: #222; color: #222;
font-size: 14px; font-size: 15px;
font-weight: 700; font-weight: 700;
line-height: 20px; line-height: 21px;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; -webkit-box-orient: vertical;
-webkit-line-clamp: 2;
} }
.order-goods__info p { .order-goods__info p {
@@ -587,30 +543,77 @@ export default {
white-space: nowrap; white-space: nowrap;
} }
.order-summary { .order-goods__info b {
display: block;
margin-top: 14px;
color: #ff5b20;
font-size: 16px;
font-weight: 700;
}
.order-goods-count {
flex: none; flex: none;
margin-left: 12px; align-self: flex-end;
padding-bottom: 4px;
color: #222;
font-size: 14px;
}
.logistics-row {
display: flex;
align-items: center;
margin: 0 14px;
padding: 13px 0;
border-top: 1px dashed #eee;
border-bottom: 1px solid #f2f2f2;
color: #777; color: #777;
font-size: 13px; font-size: 13px;
text-align: right; line-height: 20px;
}
.logistics-row .van-icon {
flex: none;
color: #777;
font-size: 17px;
}
.logistics-row span {
min-width: 0;
flex: 1;
margin: 0 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.order-summary p { .order-card__summary {
margin: 0; display: flex;
line-height: 25px; align-items: center;
justify-content: space-between;
padding: 12px 14px 0;
color: #555;
font-size: 13px;
line-height: 22px;
} }
.order-summary b { .order-card__summary b {
color: #222; color: #ff5b20;
font-size: 14px; font-size: 16px;
font-weight: 700;
}
.order-create-time {
margin: 6px 14px 14px;
color: #aaa;
font-size: 13px;
line-height: 20px;
} }
.order-card footer { .order-card footer {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: flex-end;
padding: 0 14px 14px; padding: 0 14px 16px;
} }
.order-actions { .order-actions {
@@ -58,7 +58,7 @@ export const orderListService = {
} }
const mainOrders = await orderDao.findMainOrdersWithSubs(supplierId, userId) const mainOrders = await orderDao.findMainOrdersWithSubs(supplierId, userId)
const orders = mainOrders.map(mainOrder => { const orderItems = mainOrders.flatMap(mainOrder => {
const mainStatus = statusMap[mainOrder.orderState] || statusMap[ORDER_STATE.FINISH_PAY] const mainStatus = statusMap[mainOrder.orderState] || statusMap[ORDER_STATE.FINISH_PAY]
const subOrders = mainOrder.subOrders.map(subOrder => { const subOrders = mainOrder.subOrders.map(subOrder => {
const status = statusMap[subOrder.orderState] || mainStatus const status = statusMap[subOrder.orderState] || mainStatus
@@ -91,7 +91,7 @@ export const orderListService = {
const products = subOrders.flatMap(subOrder => subOrder.products) const products = subOrders.flatMap(subOrder => subOrder.products)
const isMainOrderItem = mainOrder.orderState === ORDER_STATE.PENDING_PAY || const isMainOrderItem = mainOrder.orderState === ORDER_STATE.PENDING_PAY ||
(mainOrder.orderState === ORDER_STATE.CANCELLED && mainOrder.isSplit !== ORDER_SPLIT_STATE.SPLIT) (mainOrder.orderState === ORDER_STATE.CANCELLED && mainOrder.isSplit !== ORDER_SPLIT_STATE.SPLIT)
const orderItems = isMainOrderItem return isMainOrderItem
? [{ ? [{
id: mainOrder.orderId, id: mainOrder.orderId,
viewId: `main-${mainOrder.orderId}`, viewId: `main-${mainOrder.orderId}`,
@@ -118,28 +118,9 @@ export const orderListService = {
displayType: 'sub', displayType: 'sub',
mainOrderNumberId: mainOrder.orderNumberId || '' mainOrderNumberId: mainOrder.orderNumberId || ''
})) }))
return {
id: mainOrder.orderId,
mainOrderId: mainOrder.orderId,
mainOrderNumberId: mainOrder.orderNumberId || '',
supplierId: mainOrder.supplierId,
createTime: toDateTimeText(mainOrder.createTime || mainOrder.createAt),
status: mainStatus.text,
statusClass: mainStatus.statusClass,
orderState: mainOrder.orderState,
isSplit: mainOrder.isSplit,
splitTime: mainOrder.splitTime,
canCancel: mainOrder.orderState === ORDER_STATE.PENDING_PAY,
products,
amount: toYuanText(mainOrder.totalPrice),
count: sumProductsCount(subOrders),
orderItems,
subOrders
}
}) })
logger.info('order list result', { supplierId, userId, count: orders.length }) logger.info('order list result', { supplierId, userId, count: orderItems.length })
return serviceOk({ orders, orderItems: orders.flatMap(order => order.orderItems) }) return serviceOk({ orderItems })
} }
} }