feat: 基本实现拆单收货--
This commit is contained in:
@@ -2,7 +2,15 @@
|
||||
<section class="cart-panel" :class="{ 'cart-panel--page': pageMode }">
|
||||
<van-notice-bar v-if="showNotice" text="满 199 元包邮,快去凑单吧~" left-icon="info-o" />
|
||||
<section class="cart-list">
|
||||
<van-empty v-if="!items.length" description="购物车还是空的" />
|
||||
<div v-if="!items.length" class="cart-empty">
|
||||
<van-empty description="购物车还是空的" />
|
||||
<van-button
|
||||
class="cart-empty__button"
|
||||
round
|
||||
type="danger"
|
||||
@click="$router.push('/joyful-selection/products')"
|
||||
>去购物</van-button>
|
||||
</div>
|
||||
<template v-else>
|
||||
<article
|
||||
v-for="item in items"
|
||||
@@ -37,10 +45,11 @@
|
||||
</template>
|
||||
</section>
|
||||
<van-submit-bar
|
||||
v-if="items.length"
|
||||
:fixed="fixedSubmit"
|
||||
:price="selectedTotalPrice"
|
||||
:button-text="submitText"
|
||||
@submit="$emit('checkout', selectedItems)"
|
||||
@submit="handleCheckout"
|
||||
>
|
||||
<van-checkbox
|
||||
:value="isAllChecked"
|
||||
@@ -139,6 +148,14 @@ export default {
|
||||
}
|
||||
|
||||
this.persistCart()
|
||||
},
|
||||
handleCheckout() {
|
||||
if (!this.items.length || !this.selectedItems.length) {
|
||||
this.$toast('请选择要结算的商品')
|
||||
return
|
||||
}
|
||||
|
||||
this.$emit('checkout', this.selectedItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,6 +190,19 @@ export default {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.cart-empty {
|
||||
padding-top: 48px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cart-empty__button {
|
||||
width: 120px;
|
||||
height: 38px;
|
||||
background: #ff5b20;
|
||||
border-color: #ff5b20;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cart-item {
|
||||
display: flex;
|
||||
min-height: 128px;
|
||||
|
||||
@@ -28,6 +28,11 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
goCheckout(items) {
|
||||
if (!items || !items.length) {
|
||||
this.$toast('请选择要结算的商品')
|
||||
return
|
||||
}
|
||||
|
||||
sessionStorage.setItem('joyful-selection-checkout-items', JSON.stringify(items))
|
||||
this.$router.push({ path: '/joyful-selection/checkout', query: { source: 'cart' } })
|
||||
}
|
||||
|
||||
@@ -143,6 +143,11 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.goods.length) {
|
||||
this.$toast('暂无可提交的商品')
|
||||
return
|
||||
}
|
||||
|
||||
this.submitting = true
|
||||
try {
|
||||
await this.fetchBridgeToken()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<van-sku
|
||||
:key="skuKey"
|
||||
v-model="show"
|
||||
:class="`sku-popup--${mode}`"
|
||||
:sku="sku"
|
||||
@@ -46,6 +47,7 @@ export default {
|
||||
data: () => ({
|
||||
show: false,
|
||||
mode: 'cart',
|
||||
skuKey: 0,
|
||||
currentGoods: emptyGoods
|
||||
}),
|
||||
computed: {
|
||||
@@ -103,6 +105,7 @@ export default {
|
||||
open(goods, mode = 'cart') {
|
||||
this.currentGoods = goods
|
||||
this.mode = mode
|
||||
this.skuKey += 1
|
||||
this.show = true
|
||||
},
|
||||
getCartPayload(data) {
|
||||
|
||||
@@ -14,71 +14,54 @@
|
||||
<van-tab v-for="tab in tabs" :key="tab.label" :title="tab.label" />
|
||||
</van-tabs>
|
||||
|
||||
<section class="order-list">
|
||||
<van-pull-refresh v-model="refreshing" class="order-list" @refresh="onRefresh">
|
||||
<van-empty v-if="visibleOrders.length === 0" description="暂无订单" />
|
||||
|
||||
<template v-else>
|
||||
<article v-for="order in visibleOrders" :key="order.viewId" class="order-card">
|
||||
<header class="main-order-header">
|
||||
<span>主单号:{{ order.mainOrderId || '-' }}</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 class="order-card__header">
|
||||
<span>订单号:{{ order.orderNumberId || order.orderId || '-' }}</span>
|
||||
<b :class="order.statusClass">{{ order.status }}</b>
|
||||
</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 class="order-card__body" @click="goOrderDetail(order)">
|
||||
<div class="order-goods">
|
||||
<van-image
|
||||
v-if="firstProduct(order)"
|
||||
class="order-thumb"
|
||||
:src="firstProduct(order).image"
|
||||
fit="cover"
|
||||
/>
|
||||
<div class="order-goods__info">
|
||||
<h3>{{ firstProduct(order).name || '-' }}</h3>
|
||||
<p>{{ skuText(firstProduct(order)) }}</p>
|
||||
<p>创建时间:{{ order.createTime || '-' }}</p>
|
||||
</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>
|
||||
<div class="order-summary">
|
||||
<p>共{{ order.count }}件商品</p>
|
||||
<p>合计:<b>¥{{ order.amount }}</b></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer v-if="orderActions(order).length">
|
||||
<div class="order-actions">
|
||||
<van-button
|
||||
v-for="action in orderActions(order)"
|
||||
:key="action.key"
|
||||
size="small"
|
||||
round
|
||||
:class="action.buttonClass"
|
||||
:loading="isOperating(action.key, action.orderId)"
|
||||
@click.stop="submitOrderAction(order, action)"
|
||||
>
|
||||
{{ action.text }}
|
||||
</van-button>
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
</template>
|
||||
</section>
|
||||
</van-pull-refresh>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
@@ -122,6 +105,17 @@ const ORDER_STATE_VIEW = {
|
||||
[ORDER_STATE.REFUNDED]: { text: '退货', statusClass: 'muted' }
|
||||
}
|
||||
|
||||
const SKU_COLOR_MAP = {
|
||||
cream: '奶油白',
|
||||
blue: '雾霾蓝',
|
||||
orange: '暖橙色'
|
||||
}
|
||||
|
||||
const SKU_SIZE_MAP = {
|
||||
standard: '标准款',
|
||||
plus: '升级款'
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'JoyfulSelectionOrders',
|
||||
components: {
|
||||
@@ -139,7 +133,9 @@ export default {
|
||||
{ label: '售后', type: 'after-sale' }
|
||||
],
|
||||
orders: [],
|
||||
orderItems: [],
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
operationKey: ''
|
||||
}),
|
||||
computed: {
|
||||
@@ -150,31 +146,27 @@ export default {
|
||||
visibleOrders() {
|
||||
const tab = this.activeTab
|
||||
|
||||
const orderItems = this.orderItems.length ? this.orderItems : this.buildOrderItems()
|
||||
|
||||
if (tab.type === 'all') {
|
||||
return this.orders.map(order => this.normalizeOrder(order, order.subOrders))
|
||||
return orderItems
|
||||
}
|
||||
|
||||
if (tab.type === 'pending-main') {
|
||||
return this.orders
|
||||
.filter(order => Number(order.orderState) === ORDER_STATE.PENDING_PAY)
|
||||
.map(order => this.normalizeOrder(order, order.subOrders))
|
||||
return orderItems.filter(order => order.displayType === 'main' && Number(order.orderState) === ORDER_STATE.PENDING_PAY)
|
||||
}
|
||||
|
||||
if (tab.type === 'sub-state') {
|
||||
return this.splitSubOrdersByState(tab.state)
|
||||
return orderItems.filter(order => order.displayType === 'sub' && Number(order.orderState) === tab.state)
|
||||
}
|
||||
|
||||
if (tab.type === 'cancelled') {
|
||||
return this.cancelledOrders()
|
||||
return orderItems.filter(order => Number(order.orderState) === ORDER_STATE.CANCELLED)
|
||||
}
|
||||
|
||||
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 orderItems.filter(order => {
|
||||
return order.displayType === 'sub' && [ORDER_STATE.EXCHANGE, ORDER_STATE.REFUNDED].includes(Number(order.orderState))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -210,6 +202,38 @@ export default {
|
||||
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) {
|
||||
@@ -237,6 +261,17 @@ export default {
|
||||
previewProducts(products = []) {
|
||||
return products.slice(0, 3)
|
||||
},
|
||||
firstProduct(order) {
|
||||
return order.products?.[0] || {}
|
||||
},
|
||||
skuText(product = {}) {
|
||||
const sku = String(product.sku || product.skuText || '').trim()
|
||||
const parts = sku.split('-').slice(-2).filter(Boolean)
|
||||
const color = SKU_COLOR_MAP[parts[0]] || parts[0]
|
||||
const size = SKU_SIZE_MAP[parts[1]] || parts[1]
|
||||
|
||||
return color || size ? [color, size].filter(Boolean).join(' | ') : '-'
|
||||
},
|
||||
actionKey(type, orderId) {
|
||||
return `${type}-${orderId}`
|
||||
},
|
||||
@@ -256,13 +291,15 @@ export default {
|
||||
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) {
|
||||
goOrderDetail(order) {
|
||||
const subOrder = order.displayType === 'main' ? order.subOrders?.[0] : order
|
||||
|
||||
if (!subOrder?.orderId) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$router.push({
|
||||
path: '/joyful-selection/order-detail',
|
||||
query: {
|
||||
@@ -271,8 +308,10 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
async loadOrders() {
|
||||
this.loading = true
|
||||
async loadOrders(showLoading = true) {
|
||||
if (showLoading) {
|
||||
this.loading = true
|
||||
}
|
||||
|
||||
try {
|
||||
await this.fetchBridgeToken()
|
||||
@@ -280,6 +319,7 @@ export default {
|
||||
if (!this.supplierId || !this.userId) {
|
||||
Toast('用户信息不完整,请重新进入商城')
|
||||
this.orders = []
|
||||
this.orderItems = []
|
||||
return
|
||||
}
|
||||
|
||||
@@ -289,13 +329,49 @@ export default {
|
||||
})
|
||||
|
||||
this.orders = data?.orders || []
|
||||
this.orderItems = data?.orderItems || []
|
||||
} catch (error) {
|
||||
Toast(error.message || '查询订单失败,请稍后再试')
|
||||
this.orders = []
|
||||
this.orderItems = []
|
||||
} finally {
|
||||
this.loading = false
|
||||
if (showLoading) {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
async onRefresh() {
|
||||
try {
|
||||
await this.loadOrders(false)
|
||||
} finally {
|
||||
this.refreshing = false
|
||||
}
|
||||
},
|
||||
orderActions(order) {
|
||||
if (order.displayType === 'main') {
|
||||
return Number(order.orderState) === ORDER_STATE.PENDING_PAY
|
||||
? [{ key: 'cancel-main', orderId: order.mainOrderId, text: '取消订单', buttonClass: 'dark-action' }]
|
||||
: []
|
||||
}
|
||||
|
||||
return this.subActions(order).map(action => ({
|
||||
...action,
|
||||
key: action.orderType,
|
||||
orderId: order.orderId
|
||||
}))
|
||||
},
|
||||
submitOrderAction(order, action) {
|
||||
if (action.key === 'cancel-main') {
|
||||
this.cancelMainOrder(order)
|
||||
return
|
||||
}
|
||||
|
||||
this.submitSubOrderEvent(
|
||||
{ mainOrderId: order.mainOrderId },
|
||||
order,
|
||||
action
|
||||
)
|
||||
},
|
||||
async cancelMainOrder(order) {
|
||||
try {
|
||||
await Dialog.confirm({
|
||||
@@ -409,152 +485,159 @@ export default {
|
||||
|
||||
.orders-page ::v-deep .van-tab {
|
||||
color: #333;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.order-list {
|
||||
padding: 8px 8px 14px;
|
||||
min-height: calc(100vh - 90px);
|
||||
box-sizing: border-box;
|
||||
padding: 12px 10px 18px;
|
||||
}
|
||||
|
||||
.order-card {
|
||||
overflow: hidden;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 14px;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.main-order-header {
|
||||
.order-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 12px 10px;
|
||||
border-bottom: 1px solid #f4f4f4;
|
||||
padding: 14px 14px 6px;
|
||||
}
|
||||
|
||||
.main-order-header span {
|
||||
.order-card__header span {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: #555;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cancel-main-btn {
|
||||
.order-card__header b {
|
||||
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-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sub-order-title .pending {
|
||||
.order-card__header .pending {
|
||||
color: #ff5b20;
|
||||
}
|
||||
|
||||
.sub-order-title .done {
|
||||
.order-card__header .done {
|
||||
color: #21b36b;
|
||||
}
|
||||
|
||||
.sub-order-title .muted {
|
||||
.order-card__header .muted {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.order-body {
|
||||
.order-card__body {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
padding: 12px 14px 10px;
|
||||
}
|
||||
|
||||
.thumb-row {
|
||||
.order-goods {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.order-thumb {
|
||||
width: 62px;
|
||||
height: 62px;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
flex: none;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.order-goods__info {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.order-goods__info h3 {
|
||||
overflow: hidden;
|
||||
margin: 0 0 6px;
|
||||
color: #222;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.order-goods__info p {
|
||||
overflow: hidden;
|
||||
margin: 4px 0 0;
|
||||
color: #777;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.order-summary {
|
||||
flex: none;
|
||||
margin-left: 10px;
|
||||
margin-left: 12px;
|
||||
color: #777;
|
||||
font-size: 11px;
|
||||
font-size: 13px;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.order-summary p {
|
||||
margin: 0;
|
||||
line-height: 22px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.order-summary b {
|
||||
color: #222;
|
||||
font-size: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sub-order-row footer {
|
||||
.order-card footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding-top: 10px;
|
||||
padding: 0 14px 14px;
|
||||
}
|
||||
|
||||
.sub-order-row footer .van-button {
|
||||
min-width: 72px;
|
||||
height: 28px;
|
||||
padding: 0 12px;
|
||||
border-radius: 15px;
|
||||
font-size: 11px;
|
||||
.order-actions {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.order-actions .van-button {
|
||||
min-width: 88px;
|
||||
height: 34px;
|
||||
padding: 0 16px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.primary-action {
|
||||
color: #ff5b20;
|
||||
border-color: #ff5b20;
|
||||
background: #fff1e8;
|
||||
}
|
||||
|
||||
.dark-action {
|
||||
color: #333;
|
||||
border-color: #ddd;
|
||||
background: #f6f7f9;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user