feat: 基本实现拆单收货
This commit is contained in:
@@ -1,74 +1,184 @@
|
||||
<template>
|
||||
<main class="orders-page">
|
||||
<van-nav-bar title="我的订单" left-arrow @click-left="$router.back()" />
|
||||
<van-tabs
|
||||
v-model="active"
|
||||
class="order-tabs"
|
||||
line-width="18"
|
||||
line-height="2"
|
||||
color="#ff5b20"
|
||||
title-active-color="#ff5b20"
|
||||
>
|
||||
<van-tab v-for="tab in tabs" :key="tab" :title="tab" />
|
||||
</van-tabs>
|
||||
<div>
|
||||
<joyful-selection-orders-skeleton v-if="loading" />
|
||||
<main v-else class="orders-page">
|
||||
<van-nav-bar title="我的订单" left-arrow @click-left="$router.back()" />
|
||||
<van-tabs
|
||||
v-model="active"
|
||||
class="order-tabs"
|
||||
line-width="18"
|
||||
line-height="2"
|
||||
color="#ff5b20"
|
||||
title-active-color="#ff5b20"
|
||||
>
|
||||
<van-tab v-for="tab in tabs" :key="tab.label" :title="tab.label" />
|
||||
</van-tabs>
|
||||
|
||||
<section class="order-list">
|
||||
<van-loading v-if="loading" class="order-loading" color="#ff5b20" size="24px">加载中...</van-loading>
|
||||
<van-empty v-else-if="visibleOrders.length === 0" description="暂无订单" />
|
||||
<section class="order-list">
|
||||
<van-empty v-if="visibleOrders.length === 0" description="暂无订单" />
|
||||
|
||||
<template v-else>
|
||||
<article v-for="order in visibleOrders" :key="order.id" class="order-card">
|
||||
<header>
|
||||
<div class="order-nos">
|
||||
<template v-else>
|
||||
<article v-for="order in visibleOrders" :key="order.viewId" class="order-card">
|
||||
<header class="main-order-header">
|
||||
<span>主单号:{{ order.mainOrderId || '-' }}</span>
|
||||
<span>子单号:{{ order.orderId || '-' }}</span>
|
||||
<van-button
|
||||
v-if="order.canCancel"
|
||||
class="cancel-main-btn"
|
||||
size="mini"
|
||||
round
|
||||
plain
|
||||
:loading="isOperating('cancel-main', order.mainOrderId)"
|
||||
@click.stop="cancelMainOrder(order)"
|
||||
>
|
||||
取消订单
|
||||
</van-button>
|
||||
</header>
|
||||
|
||||
<div class="sub-order-list">
|
||||
<section
|
||||
v-for="subOrder in order.subOrders"
|
||||
:key="subOrder.id"
|
||||
class="sub-order-row"
|
||||
@click="goOrderDetail(order, subOrder)"
|
||||
>
|
||||
<div class="sub-order-title">
|
||||
<span>子单号:{{ subOrder.orderId || '-' }}</span>
|
||||
<b :class="subOrder.statusClass">{{ subOrder.status }}</b>
|
||||
</div>
|
||||
<div class="order-body">
|
||||
<div class="thumb-row">
|
||||
<van-image
|
||||
v-for="item in previewProducts(subOrder.products)"
|
||||
:key="item.id"
|
||||
class="order-thumb"
|
||||
:src="item.image"
|
||||
fit="cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="order-summary">
|
||||
<p>共{{ subOrder.count }}件商品</p>
|
||||
<p>合计:<b>¥{{ subOrder.amount }}</b></p>
|
||||
</div>
|
||||
</div>
|
||||
<footer v-if="subActions(subOrder).length">
|
||||
<van-button
|
||||
v-for="action in subActions(subOrder)"
|
||||
:key="action.orderType"
|
||||
size="small"
|
||||
round
|
||||
plain
|
||||
:class="action.buttonClass"
|
||||
:loading="isOperating(action.orderType, subOrder.orderId)"
|
||||
@click.stop="submitSubOrderEvent(order, subOrder, action)"
|
||||
>
|
||||
{{ action.text }}
|
||||
</van-button>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
<b :class="order.statusClass">{{ order.status }}</b>
|
||||
</header>
|
||||
<div class="order-body">
|
||||
<div class="thumb-row">
|
||||
<van-image
|
||||
v-for="item in previewProducts(order.products)"
|
||||
:key="item.id"
|
||||
class="order-thumb"
|
||||
:src="item.image"
|
||||
fit="cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="order-summary">
|
||||
<p>共{{ order.count }}件商品</p>
|
||||
<p>合计:<b>¥{{ order.amount }}</b></p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<van-button size="small" round plain :class="order.buttonClass">{{ order.actionText }}</van-button>
|
||||
</footer>
|
||||
</article>
|
||||
</template>
|
||||
</section>
|
||||
</main>
|
||||
</article>
|
||||
</template>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapState } from 'pinia'
|
||||
import { Toast } from 'vant'
|
||||
import { Dialog, Toast } from 'vant'
|
||||
import { postFunction } from '@/api/supplied-service'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import JoyfulSelectionOrdersSkeleton from '@/skeleton-view/joyful-selection/profile/orders/index.vue'
|
||||
|
||||
const ORDER_STATE = {
|
||||
PENDING_PAY: 0,
|
||||
FINISH_PAY: 1,
|
||||
IN_PROGRESS: 2,
|
||||
CANCELLED: 3,
|
||||
COMPLETED: 5,
|
||||
EXCHANGE: 6,
|
||||
REFUNDED: 7
|
||||
}
|
||||
|
||||
const ORDER_SPLIT_STATE = {
|
||||
NOT_SPLIT: 0,
|
||||
SPLIT: 1
|
||||
}
|
||||
|
||||
const ORDER_EVENT_TYPE = {
|
||||
RETURN_ORDER: 4,
|
||||
EXCHANGE_ORDER: 5,
|
||||
CONFIRM_RECEIVE: 6,
|
||||
INVOICE_INFO: 11
|
||||
}
|
||||
|
||||
const ORDER_STATE_VIEW = {
|
||||
[ORDER_STATE.PENDING_PAY]: { text: '待付款', statusClass: 'pending' },
|
||||
[ORDER_STATE.FINISH_PAY]: { text: '待发货', statusClass: 'pending' },
|
||||
[ORDER_STATE.IN_PROGRESS]: { text: '待收货', statusClass: 'pending' },
|
||||
[ORDER_STATE.CANCELLED]: { text: '已取消', statusClass: 'muted' },
|
||||
[ORDER_STATE.COMPLETED]: { text: '已完成', statusClass: 'done' },
|
||||
[ORDER_STATE.EXCHANGE]: { text: '换货', statusClass: 'muted' },
|
||||
[ORDER_STATE.REFUNDED]: { text: '退货', statusClass: 'muted' }
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'JoyfulSelectionOrders',
|
||||
components: {
|
||||
JoyfulSelectionOrdersSkeleton
|
||||
},
|
||||
data: () => ({
|
||||
active: 0,
|
||||
tabs: ['全部', '待付款', '待发货', '待收货', '已完成', '售后'],
|
||||
tabs: [
|
||||
{ label: '全部', type: 'all' },
|
||||
{ label: '待付款', type: 'pending-main' },
|
||||
{ label: '待发货', type: 'sub-state', state: ORDER_STATE.FINISH_PAY },
|
||||
{ label: '待收货', type: 'sub-state', state: ORDER_STATE.IN_PROGRESS },
|
||||
{ label: '已取消', type: 'cancelled' },
|
||||
{ label: '已完成', type: 'sub-state', state: ORDER_STATE.COMPLETED },
|
||||
{ label: '售后', type: 'after-sale' }
|
||||
],
|
||||
orders: [],
|
||||
loading: false
|
||||
loading: false,
|
||||
operationKey: ''
|
||||
}),
|
||||
computed: {
|
||||
...mapState(useUserStore, ['supplierId', 'userId']),
|
||||
activeTab() {
|
||||
return this.tabs[this.active] || this.tabs[0]
|
||||
},
|
||||
visibleOrders() {
|
||||
const tab = this.tabs[this.active]
|
||||
const tab = this.activeTab
|
||||
|
||||
return tab === '全部' ? this.orders : this.orders.filter(order => order.status === tab)
|
||||
if (tab.type === 'all') {
|
||||
return this.orders.map(order => this.normalizeOrder(order, order.subOrders))
|
||||
}
|
||||
|
||||
if (tab.type === 'pending-main') {
|
||||
return this.orders
|
||||
.filter(order => Number(order.orderState) === ORDER_STATE.PENDING_PAY)
|
||||
.map(order => this.normalizeOrder(order, order.subOrders))
|
||||
}
|
||||
|
||||
if (tab.type === 'sub-state') {
|
||||
return this.splitSubOrdersByState(tab.state)
|
||||
}
|
||||
|
||||
if (tab.type === 'cancelled') {
|
||||
return this.cancelledOrders()
|
||||
}
|
||||
|
||||
if (tab.type === 'after-sale') {
|
||||
return this.orders.flatMap(order => {
|
||||
const subOrders = this.normalizedSubOrders(order).filter(subOrder => {
|
||||
return [ORDER_STATE.EXCHANGE, ORDER_STATE.REFUNDED].includes(Number(subOrder.orderState))
|
||||
})
|
||||
|
||||
return subOrders.map(subOrder => this.normalizeOrder(order, [subOrder]))
|
||||
})
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -76,9 +186,91 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...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))
|
||||
},
|
||||
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 = []) {
|
||||
return products.slice(0, 3)
|
||||
},
|
||||
actionKey(type, orderId) {
|
||||
return `${type}-${orderId}`
|
||||
},
|
||||
isOperating(type, orderId) {
|
||||
return this.operationKey === this.actionKey(type, orderId)
|
||||
},
|
||||
subActions(subOrder) {
|
||||
const state = Number(subOrder.orderState)
|
||||
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)) {
|
||||
actions.push({ orderType: ORDER_EVENT_TYPE.RETURN_ORDER, text: '退货', buttonClass: 'dark-action' })
|
||||
actions.push({ orderType: ORDER_EVENT_TYPE.EXCHANGE_ORDER, text: '换货', buttonClass: 'dark-action' })
|
||||
}
|
||||
|
||||
if (![ORDER_STATE.PENDING_PAY, ORDER_STATE.CANCELLED].includes(state)) {
|
||||
actions.push({ orderType: ORDER_EVENT_TYPE.INVOICE_INFO, text: '发票信息', buttonClass: 'dark-action' })
|
||||
}
|
||||
|
||||
return actions
|
||||
},
|
||||
goOrderDetail(order, subOrder) {
|
||||
this.$router.push({
|
||||
path: '/joyful-selection/order-detail',
|
||||
query: {
|
||||
mainOrderId: order.mainOrderId,
|
||||
orderId: subOrder.orderId
|
||||
}
|
||||
})
|
||||
},
|
||||
async loadOrders() {
|
||||
this.loading = true
|
||||
|
||||
@@ -103,6 +295,84 @@ export default {
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async cancelMainOrder(order) {
|
||||
try {
|
||||
await Dialog.confirm({
|
||||
title: '取消订单',
|
||||
message: '确定取消该未支付订单吗?'
|
||||
})
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
|
||||
this.operationKey = this.actionKey('cancel-main', order.mainOrderId)
|
||||
|
||||
try {
|
||||
await this.fetchBridgeToken()
|
||||
|
||||
if (!this.supplierId || !this.userId || !order.mainOrderId) {
|
||||
Toast('订单信息不完整,请稍后再试')
|
||||
return
|
||||
}
|
||||
|
||||
await postFunction('order-cancel-main', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId,
|
||||
orderId: order.mainOrderId,
|
||||
mainOrderId: order.mainOrderId
|
||||
})
|
||||
|
||||
Toast('订单已取消')
|
||||
await this.loadOrders()
|
||||
} catch (error) {
|
||||
Toast(error.message || '取消订单失败,请稍后再试')
|
||||
} finally {
|
||||
this.operationKey = ''
|
||||
}
|
||||
},
|
||||
async submitSubOrderEvent(order, subOrder, action) {
|
||||
try {
|
||||
await Dialog.confirm({
|
||||
title: action.text,
|
||||
message: `确定对该子单进行“${action.text}”操作吗?`
|
||||
})
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
|
||||
this.operationKey = this.actionKey(action.orderType, subOrder.orderId)
|
||||
|
||||
try {
|
||||
await this.fetchBridgeToken()
|
||||
|
||||
if (!this.supplierId || !this.userId || !order.mainOrderId || !subOrder.orderId) {
|
||||
Toast('订单信息不完整,请稍后再试')
|
||||
return
|
||||
}
|
||||
|
||||
const data = await postFunction('order-push-event', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId,
|
||||
orderType: action.orderType,
|
||||
orderId: subOrder.orderId,
|
||||
mainOrderId: order.mainOrderId,
|
||||
subOrderId: subOrder.orderId,
|
||||
amount: Number(subOrder.amount || 0),
|
||||
reason: action.text,
|
||||
invoiceBizId: action.orderType === ORDER_EVENT_TYPE.INVOICE_INFO ? `INV_${subOrder.orderId}` : undefined,
|
||||
invoiceIds: action.orderType === ORDER_EVENT_TYPE.INVOICE_INFO ? `INV_${subOrder.orderId}` : undefined,
|
||||
orderIds: action.orderType === ORDER_EVENT_TYPE.INVOICE_INFO ? subOrder.orderId : undefined,
|
||||
time: Date.now()
|
||||
})
|
||||
|
||||
Toast(data?.pushSuccess === false ? '本地已保存,远程推送失败' : `${action.text}成功`)
|
||||
await this.loadOrders()
|
||||
} catch (error) {
|
||||
Toast(error.message || `${action.text}失败,请稍后再试`)
|
||||
} finally {
|
||||
this.operationKey = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,12 +416,6 @@ export default {
|
||||
padding: 8px 8px 14px;
|
||||
}
|
||||
|
||||
.order-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 42px 0;
|
||||
}
|
||||
|
||||
.order-card {
|
||||
overflow: hidden;
|
||||
margin-bottom: 10px;
|
||||
@@ -159,48 +423,78 @@ export default {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.order-card header {
|
||||
.main-order-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 12px 8px;
|
||||
padding: 12px 12px 10px;
|
||||
border-bottom: 1px solid #f4f4f4;
|
||||
}
|
||||
|
||||
.order-nos {
|
||||
.main-order-header span {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.order-nos span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: #777;
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
color: #555;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.order-card header b {
|
||||
.cancel-main-btn {
|
||||
flex: none;
|
||||
margin-left: 10px;
|
||||
color: #ff5b20;
|
||||
border-color: #ff5b20;
|
||||
}
|
||||
|
||||
.sub-order-row {
|
||||
padding: 10px 12px 12px;
|
||||
}
|
||||
|
||||
.sub-order-row + .sub-order-row {
|
||||
border-top: 1px solid #f4f4f4;
|
||||
}
|
||||
|
||||
.sub-order-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.sub-order-title span {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: #777;
|
||||
font-size: 11px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sub-order-title b {
|
||||
flex: none;
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.order-card header .pending {
|
||||
.sub-order-title .pending {
|
||||
color: #ff5b20;
|
||||
}
|
||||
|
||||
.order-card header .done {
|
||||
.sub-order-title .done {
|
||||
color: #21b36b;
|
||||
}
|
||||
|
||||
.sub-order-title .muted {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.order-body {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px 10px;
|
||||
}
|
||||
|
||||
.thumb-row {
|
||||
@@ -238,16 +532,18 @@ export default {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.order-card footer {
|
||||
.sub-order-row footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
padding: 0 12px 12px;
|
||||
gap: 8px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.order-card footer .van-button {
|
||||
min-width: 78px;
|
||||
.sub-order-row footer .van-button {
|
||||
min-width: 72px;
|
||||
height: 28px;
|
||||
padding: 0 14px;
|
||||
padding: 0 12px;
|
||||
border-radius: 15px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user