feat: 已实现订单基本逻辑,到完成,却订单售后
This commit is contained in:
@@ -14,6 +14,7 @@ import JoyfulSelectionAddressList from '@/view/joyful-selection/address/index.vu
|
||||
import JoyfulSelectionAddressEdit from '@/view/joyful-selection/address/edit/index.vue'
|
||||
import JoyfulSelectionOrders from '@/view/joyful-selection/profile/orders/index.vue'
|
||||
import JoyfulSelectionOrderDetail from '@/view/joyful-selection/profile/order-detail/index.vue'
|
||||
import JoyfulSelectionAfterSale from '@/view/joyful-selection/profile/after-sale/index.vue'
|
||||
import JoyfulSelectionProfile from '@/view/joyful-selection/profile/index.vue'
|
||||
import JoyfulSelectionPoints from '@/view/joyful-selection/profile/points/index.vue'
|
||||
import HomeLiving from '@/view/home-living/index.vue'
|
||||
@@ -43,6 +44,7 @@ export default new Router({
|
||||
{ path: 'address/edit', component: JoyfulSelectionAddressEdit },
|
||||
{ path: 'orders', component: JoyfulSelectionOrders },
|
||||
{ path: 'order-detail', component: JoyfulSelectionOrderDetail },
|
||||
{ path: 'after-sale', component: JoyfulSelectionAfterSale },
|
||||
{ path: 'profile', component: JoyfulSelectionProfile },
|
||||
{ path: 'points', component: JoyfulSelectionPoints }
|
||||
]
|
||||
|
||||
@@ -1,36 +1,7 @@
|
||||
const ADDRESS_KEY = 'joyful-selection-addresses'
|
||||
const CHOSEN_ADDRESS_KEY = 'joyful-selection-chosen-address-id'
|
||||
|
||||
const defaultAddresses = [
|
||||
{
|
||||
id: 'addr-1',
|
||||
name: '张三',
|
||||
tel: '13888888888',
|
||||
province: '浙江省',
|
||||
city: '杭州市',
|
||||
county: '余杭区',
|
||||
addressDetail: '文一西路 888 号未来科技城海创园5幢1203室',
|
||||
areaCode: '330110',
|
||||
postalCode: '310000',
|
||||
isDefault: true
|
||||
}
|
||||
]
|
||||
|
||||
export const readAddresses = () => {
|
||||
try {
|
||||
const addresses = JSON.parse(localStorage.getItem(ADDRESS_KEY) || '[]')
|
||||
return addresses.length ? addresses : defaultAddresses
|
||||
} catch (error) {
|
||||
return defaultAddresses
|
||||
}
|
||||
}
|
||||
|
||||
export const saveAddresses = addresses => {
|
||||
localStorage.setItem(ADDRESS_KEY, JSON.stringify(addresses))
|
||||
}
|
||||
|
||||
export const readChosenAddressId = () => {
|
||||
return localStorage.getItem(CHOSEN_ADDRESS_KEY) || readAddresses().find(item => item.isDefault)?.id || readAddresses()[0]?.id || ''
|
||||
return localStorage.getItem(CHOSEN_ADDRESS_KEY) || ''
|
||||
}
|
||||
|
||||
export const saveChosenAddressId = id => {
|
||||
@@ -42,7 +13,7 @@ export const formatAddress = address => {
|
||||
return ''
|
||||
}
|
||||
|
||||
return `${address.province}${address.city}${address.county}${address.addressDetail}`
|
||||
return `${address.province || ''}${address.city || ''}${address.county || ''}${address.addressDetail || ''}`
|
||||
}
|
||||
|
||||
export const createAddressId = () => `addr-${Date.now()}`
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
show-delete
|
||||
show-set-default
|
||||
show-search-result
|
||||
:tel-maxlength="11"
|
||||
:search-result="searchResult"
|
||||
:area-columns-placeholder="['请选择', '请选择', '请选择']"
|
||||
@save="onSave"
|
||||
@@ -21,9 +22,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapState } from 'pinia'
|
||||
import { Toast } from 'vant'
|
||||
import { areaList } from '@vant/area-data'
|
||||
import { createAddressId, readAddresses, saveAddresses, saveChosenAddressId } from '../address-data'
|
||||
import { postFunction } from '@/api/supplied-service'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { createAddressId, saveChosenAddressId } from '../address-data'
|
||||
import JoyfulSelectionAddressEditSkeleton from '@/skeleton-view/joyful-selection/address/edit/index.vue'
|
||||
|
||||
export default {
|
||||
@@ -38,6 +42,7 @@ export default {
|
||||
addressInfo: {}
|
||||
}),
|
||||
computed: {
|
||||
...mapState(useUserStore, ['supplierId', 'userId']),
|
||||
editId() {
|
||||
return this.$route.query.id
|
||||
},
|
||||
@@ -46,51 +51,64 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const address = readAddresses().find(item => item.id === this.editId)
|
||||
|
||||
if (address) {
|
||||
this.addressInfo = { ...address }
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.loading = false
|
||||
})
|
||||
this.loadAddress()
|
||||
},
|
||||
methods: {
|
||||
onSave(content) {
|
||||
const addresses = readAddresses()
|
||||
...mapActions(useUserStore, ['fetchBridgeToken']),
|
||||
async loadAddress() {
|
||||
this.loading = true
|
||||
try {
|
||||
await this.fetchBridgeToken()
|
||||
|
||||
if (!this.editId || !this.supplierId || !this.userId) {
|
||||
return
|
||||
}
|
||||
|
||||
const data = await postFunction('address-list', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId
|
||||
})
|
||||
const address = (data?.addresses || []).find(item => item.id === this.editId)
|
||||
|
||||
if (address) {
|
||||
this.addressInfo = { ...address }
|
||||
}
|
||||
} catch (error) {
|
||||
Toast(error.message || '查询地址失败,请稍后再试')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async onSave(content) {
|
||||
const nextAddress = {
|
||||
...content,
|
||||
id: this.editId || content.id || createAddressId()
|
||||
}
|
||||
const nextAddresses = addresses.filter(item => item.id !== nextAddress.id)
|
||||
|
||||
if (nextAddress.isDefault) {
|
||||
nextAddresses.forEach(item => { item.isDefault = false })
|
||||
}
|
||||
|
||||
if (!nextAddresses.length && !nextAddress.isDefault) {
|
||||
nextAddress.isDefault = true
|
||||
}
|
||||
|
||||
saveAddresses([nextAddress, ...nextAddresses])
|
||||
await this.fetchBridgeToken()
|
||||
await postFunction('address-save', {
|
||||
...nextAddress,
|
||||
addressId: nextAddress.id,
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId
|
||||
})
|
||||
saveChosenAddressId(nextAddress.id)
|
||||
Toast('保存成功')
|
||||
this.$router.back()
|
||||
},
|
||||
onDelete() {
|
||||
async onDelete() {
|
||||
if (!this.editId) {
|
||||
this.$router.back()
|
||||
return
|
||||
}
|
||||
|
||||
const addresses = readAddresses().filter(item => item.id !== this.editId)
|
||||
|
||||
if (addresses.length && !addresses.some(item => item.isDefault)) {
|
||||
addresses[0].isDefault = true
|
||||
}
|
||||
|
||||
saveAddresses(addresses)
|
||||
saveChosenAddressId(addresses[0]?.id || '')
|
||||
await this.fetchBridgeToken()
|
||||
await postFunction('address-delete', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId,
|
||||
addressId: this.editId
|
||||
})
|
||||
saveChosenAddressId('')
|
||||
Toast('删除成功')
|
||||
this.$router.back()
|
||||
},
|
||||
|
||||
@@ -18,7 +18,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatAddress, readAddresses, readChosenAddressId, saveChosenAddressId } from './address-data'
|
||||
import { mapActions, mapState } from 'pinia'
|
||||
import { Toast } from 'vant'
|
||||
import { postFunction } from '@/api/supplied-service'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { formatAddress, readChosenAddressId, saveChosenAddressId } from './address-data'
|
||||
import JoyfulSelectionAddressListSkeleton from '@/skeleton-view/joyful-selection/address/index.vue'
|
||||
|
||||
export default {
|
||||
@@ -33,6 +37,7 @@ export default {
|
||||
disabledList: []
|
||||
}),
|
||||
computed: {
|
||||
...mapState(useUserStore, ['supplierId', 'userId']),
|
||||
addressList() {
|
||||
return this.addresses.map(item => ({
|
||||
id: item.id,
|
||||
@@ -44,18 +49,54 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.addresses = readAddresses()
|
||||
this.chosenAddressId = readChosenAddressId()
|
||||
this.$nextTick(() => {
|
||||
this.loading = false
|
||||
})
|
||||
this.loadAddresses()
|
||||
},
|
||||
methods: {
|
||||
onSelect(item) {
|
||||
...mapActions(useUserStore, ['fetchBridgeToken']),
|
||||
async loadAddresses() {
|
||||
this.loading = true
|
||||
try {
|
||||
await this.fetchBridgeToken()
|
||||
|
||||
if (!this.supplierId || !this.userId) {
|
||||
Toast('用户信息不完整,请重新进入商城')
|
||||
this.addresses = []
|
||||
return
|
||||
}
|
||||
|
||||
const data = await postFunction('address-list', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId
|
||||
})
|
||||
this.addresses = data?.addresses || []
|
||||
this.chosenAddressId = readChosenAddressId() || this.addresses.find(item => item.isDefault)?.id || this.addresses[0]?.id || ''
|
||||
} catch (error) {
|
||||
Toast(error.message || '查询地址失败,请稍后再试')
|
||||
this.addresses = []
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async onSelect(item) {
|
||||
this.chosenAddressId = item.id
|
||||
saveChosenAddressId(item.id)
|
||||
|
||||
if (this.$route.query.from === 'checkout') {
|
||||
const address = this.addresses.find(addressItem => addressItem.id === item.id)
|
||||
if (address) {
|
||||
try {
|
||||
await postFunction('address-save', {
|
||||
...address,
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId,
|
||||
addressId: address.id,
|
||||
isDefault: true
|
||||
})
|
||||
} catch (error) {
|
||||
Toast(error.message || '璁剧疆榛樿鍦板潃澶辫触锛岃绋嶅悗鍐嶈瘯')
|
||||
return
|
||||
}
|
||||
}
|
||||
this.$router.back()
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,14 +4,22 @@
|
||||
<main v-else class="checkout-page">
|
||||
<van-nav-bar title="确认订单" left-arrow @click-left="$router.back()" />
|
||||
|
||||
<section class="address-card" @click="$router.push({ path: '/joyful-selection/address', query: { from: 'checkout' } })">
|
||||
<section class="address-card" :class="{ 'address-card--empty': !chosenAddress.id }" @click="goAddress">
|
||||
<van-icon class="address-icon" name="location" />
|
||||
<div class="address-info">
|
||||
<div v-if="chosenAddress.id" class="address-info">
|
||||
<h3>收货人:{{ chosenAddress.name }} <span>{{ maskTel(chosenAddress.tel) }}</span></h3>
|
||||
<p>{{ chosenAddress.province }}{{ chosenAddress.city }}{{ chosenAddress.county }}</p>
|
||||
<p>{{ chosenAddress.addressDetail }}</p>
|
||||
</div>
|
||||
<van-icon class="arrow-icon" name="arrow" />
|
||||
<div v-else class="address-info address-empty">
|
||||
<h3>请先新增收货地址</h3>
|
||||
<p>用于商品配送,请确保信息准确</p>
|
||||
</div>
|
||||
<div v-if="!chosenAddress.id" class="address-action">
|
||||
<span>去新增</span>
|
||||
<van-icon name="arrow" />
|
||||
</div>
|
||||
<van-icon v-else class="arrow-icon" name="arrow" />
|
||||
</section>
|
||||
|
||||
<section class="delivery-card">
|
||||
@@ -30,7 +38,8 @@
|
||||
<van-image class="order-thumb" :src="item.image" fit="cover" />
|
||||
<div class="order-info">
|
||||
<h3>{{ item.name }}</h3>
|
||||
<p>¥{{ item.price }}</p>
|
||||
<p v-if="productSpecText(item)" class="order-desc">{{ productSpecText(item) }}</p>
|
||||
<b>¥{{ item.price }}</b>
|
||||
</div>
|
||||
<span class="order-count">x{{ item.count || 1 }}</span>
|
||||
</article>
|
||||
@@ -48,7 +57,7 @@
|
||||
<section class="amount-card">
|
||||
<p><span>商品金额</span><b>¥{{ formatYuan(goodsAmount) }}</b></p>
|
||||
<p><span>运费</span><b>¥0.00</b></p>
|
||||
<p><span>优惠券</span><b class="muted">选择优惠券</b></p>
|
||||
<p class="coupon-row" @click="showCouponTip"><span>优惠券</span><b class="muted">选择优惠券<van-icon name="arrow" /></b></p>
|
||||
</section>
|
||||
|
||||
<van-submit-bar :price="goodsAmount" button-text="提交订单" @submit="onSubmit" />
|
||||
@@ -61,7 +70,6 @@ import { mapActions, mapState } from 'pinia'
|
||||
import { postFunction } from '@/api/supplied-service'
|
||||
import { useCartStore } from '@/store/cart'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { readAddresses, readChosenAddressId } from '../../address/address-data'
|
||||
import { createOrderNo, savePaymentInfo } from '../payment/payment-data'
|
||||
import JoyfulSelectionCheckoutSkeleton from '@/skeleton-view/joyful-selection/order/checkout/index.vue'
|
||||
|
||||
@@ -96,13 +104,16 @@ export default {
|
||||
created() {
|
||||
this.loadCart()
|
||||
this.goods = this.getCheckoutGoods()
|
||||
this.loadAddress()
|
||||
this.$nextTick(() => {
|
||||
this.loadAddress().catch(error => {
|
||||
this.$toast(error.message || '查询地址失败,请稍后再试')
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
activated() {
|
||||
this.loadAddress()
|
||||
this.loadAddress().catch(error => {
|
||||
this.$toast(error.message || '查询地址失败,请稍后再试')
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useCartStore, ['loadCart', 'removeItems']),
|
||||
@@ -130,14 +141,37 @@ export default {
|
||||
formatYuan(price) {
|
||||
return (Number(price || 0) / 100).toFixed(2)
|
||||
},
|
||||
loadAddress() {
|
||||
const addresses = readAddresses()
|
||||
const chosenId = readChosenAddressId()
|
||||
this.chosenAddress = addresses.find(item => item.id === chosenId) || addresses[0] || {}
|
||||
productSpecText(item = {}) {
|
||||
return String(item.skuText || item.desc || '').replace(/\s*\/\s*/g, ' | ')
|
||||
},
|
||||
async loadAddress() {
|
||||
await this.fetchBridgeToken()
|
||||
|
||||
if (!this.supplierId || !this.userId) {
|
||||
this.chosenAddress = {}
|
||||
return
|
||||
}
|
||||
|
||||
const data = await postFunction('address-default', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId
|
||||
})
|
||||
this.chosenAddress = data?.address || {}
|
||||
},
|
||||
goAddress() {
|
||||
if (this.chosenAddress.id) {
|
||||
this.$router.push({ path: '/joyful-selection/address', query: { from: 'checkout' } })
|
||||
return
|
||||
}
|
||||
|
||||
this.$router.push({ path: '/joyful-selection/address/edit', query: { from: 'checkout' } })
|
||||
},
|
||||
maskTel(tel = '') {
|
||||
return tel.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
|
||||
},
|
||||
showCouponTip() {
|
||||
this.$toast('急速开发中,敬请期待')
|
||||
},
|
||||
async onSubmit() {
|
||||
if (this.submitting) {
|
||||
return
|
||||
@@ -162,6 +196,8 @@ export default {
|
||||
userId: this.userId,
|
||||
orderNo,
|
||||
amount: this.goodsAmount,
|
||||
addressId: this.chosenAddress.id || '',
|
||||
remark: this.remark,
|
||||
goods: this.goods
|
||||
})
|
||||
|
||||
@@ -233,6 +269,10 @@ export default {
|
||||
padding: 14px 12px;
|
||||
}
|
||||
|
||||
.address-card--empty {
|
||||
background: #fff6ea;
|
||||
}
|
||||
|
||||
.address-icon {
|
||||
flex: none;
|
||||
margin-right: 10px;
|
||||
@@ -267,6 +307,34 @@ export default {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.address-empty h3 {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.address-empty p {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.address-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex: none;
|
||||
height: 30px;
|
||||
margin-left: 8px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #ff6a2a;
|
||||
border-radius: 16px;
|
||||
color: #ff5b20;
|
||||
font-size: 12px;
|
||||
background: #fff6ea;
|
||||
}
|
||||
|
||||
.address-action .van-icon {
|
||||
margin-left: 4px;
|
||||
color: inherit;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
flex: none;
|
||||
color: #999;
|
||||
@@ -363,13 +431,26 @@ export default {
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.order-info p {
|
||||
.order-info b {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
color: #ff5b20;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.order-info .order-desc {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
margin-bottom: 4px;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 17px;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
|
||||
.order-count {
|
||||
flex: none;
|
||||
color: #222;
|
||||
@@ -425,10 +506,22 @@ export default {
|
||||
}
|
||||
|
||||
.amount-card .muted {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: #777;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.coupon-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.coupon-row .van-icon {
|
||||
margin-left: 4px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.checkout-page ::v-deep .van-submit-bar {
|
||||
bottom: 0;
|
||||
height: 58px;
|
||||
|
||||
@@ -121,6 +121,7 @@ export default {
|
||||
id: this.currentGoods.id,
|
||||
name: this.currentGoods.name,
|
||||
desc: skuDesc,
|
||||
productDesc: this.currentGoods.desc || this.currentGoods.detail || '',
|
||||
price: this.currentGoods.price,
|
||||
image: this.currentGoods.image,
|
||||
tag: this.currentGoods.tag,
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<main class="after-sale-page">
|
||||
<van-nav-bar title="退款/售后" left-arrow @click-left="$router.back()" />
|
||||
|
||||
<van-pull-refresh v-model="refreshing" class="after-sale-list" @refresh="onRefresh">
|
||||
<van-empty v-if="afterSaleItems.length === 0" description="暂无退款/售后" />
|
||||
|
||||
<template v-else>
|
||||
<article v-for="item in afterSaleItems" :key="item.serviceOrderId" class="after-sale-card">
|
||||
<header class="after-sale-card__header">
|
||||
<span>{{ item.serviceTypeText }}</span>
|
||||
<b>{{ item.serviceStateText }}</b>
|
||||
</header>
|
||||
|
||||
<div class="after-sale-card__body">
|
||||
<div
|
||||
v-for="product in previewProducts(item.products)"
|
||||
:key="product.id"
|
||||
class="after-sale-product"
|
||||
>
|
||||
<van-image class="after-sale-thumb" :src="product.image" fit="cover" />
|
||||
<div class="after-sale-product__info">
|
||||
<h3>{{ product.name || '-' }}</h3>
|
||||
<p>{{ product.sku || '-' }}</p>
|
||||
<b>¥{{ product.price || item.amount }}</b>
|
||||
</div>
|
||||
<span>x{{ product.number || 1 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="after-sale-meta">
|
||||
<p><span>售后单号</span><b>{{ item.serviceOrderId }}</b></p>
|
||||
<p><span>申请时间</span><b>{{ item.applyTime || '-' }}</b></p>
|
||||
<p><span>物流状态</span><b>{{ item.logisticsStateText }}</b></p>
|
||||
<p v-if="item.reason"><span>申请原因</span><b>{{ item.reason }}</b></p>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
</van-pull-refresh>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapState } from 'pinia'
|
||||
import { Toast } from 'vant'
|
||||
import { postFunction } from '@/api/supplied-service'
|
||||
import { useUserStore } from '@/store/user'
|
||||
|
||||
export default {
|
||||
name: 'JoyfulSelectionAfterSale',
|
||||
data: () => ({
|
||||
afterSaleItems: [],
|
||||
loading: false,
|
||||
refreshing: false
|
||||
}),
|
||||
computed: {
|
||||
...mapState(useUserStore, ['supplierId', 'userId'])
|
||||
},
|
||||
created() {
|
||||
this.loadAfterSales()
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useUserStore, ['fetchBridgeToken']),
|
||||
previewProducts(products = []) {
|
||||
return products.slice(0, 3)
|
||||
},
|
||||
async loadAfterSales(showLoading = true) {
|
||||
if (showLoading) {
|
||||
this.loading = true
|
||||
}
|
||||
|
||||
try {
|
||||
await this.fetchBridgeToken()
|
||||
|
||||
if (!this.supplierId || !this.userId) {
|
||||
Toast('用户信息不完整,请重新进入商城')
|
||||
this.afterSaleItems = []
|
||||
return
|
||||
}
|
||||
|
||||
const data = await postFunction('after-sale-list', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId
|
||||
})
|
||||
|
||||
this.afterSaleItems = data?.afterSaleItems || []
|
||||
} catch (error) {
|
||||
Toast(error.message || '查询退款/售后失败,请稍后再试')
|
||||
this.afterSaleItems = []
|
||||
} finally {
|
||||
if (showLoading) {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
async onRefresh() {
|
||||
try {
|
||||
await this.loadAfterSales(false)
|
||||
} finally {
|
||||
this.refreshing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.after-sale-page {
|
||||
min-height: 100vh;
|
||||
padding-top: 46px;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.after-sale-page ::v-deep .van-nav-bar {
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.after-sale-list {
|
||||
min-height: calc(100vh - 46px);
|
||||
box-sizing: border-box;
|
||||
padding: 12px 10px 18px;
|
||||
}
|
||||
|
||||
.after-sale-card {
|
||||
overflow: hidden;
|
||||
margin-bottom: 14px;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.after-sale-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 14px 8px;
|
||||
color: #222;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.after-sale-card__header b {
|
||||
color: #ff5b20;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.after-sale-card__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.after-sale-product {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.after-sale-thumb {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
flex: none;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.after-sale-product__info {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.after-sale-product__info h3 {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
margin: 0 0 6px;
|
||||
color: #222;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.after-sale-product__info p {
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
color: #777;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.after-sale-product__info b {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
color: #ff5b20;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.after-sale-product > span {
|
||||
flex: none;
|
||||
align-self: flex-end;
|
||||
color: #222;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.after-sale-meta {
|
||||
padding: 0 14px 14px;
|
||||
}
|
||||
|
||||
.after-sale-meta p {
|
||||
display: flex;
|
||||
margin: 8px 0 0;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.after-sale-meta span {
|
||||
flex: none;
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.after-sale-meta b {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow-wrap: anywhere;
|
||||
color: #555;
|
||||
font-weight: 400;
|
||||
}
|
||||
</style>
|
||||
@@ -49,12 +49,14 @@
|
||||
<b>我的订单</b>
|
||||
<span @click="$router.push('/joyful-selection/orders')">全部订单 <van-icon name="arrow" /></span>
|
||||
</div>
|
||||
<van-grid :column-num="5" :border="false">
|
||||
<van-grid :column-num="4" :border="false">
|
||||
<van-grid-item
|
||||
v-for="item in orderItems"
|
||||
:key="item.text"
|
||||
:icon="item.icon"
|
||||
:text="item.text"
|
||||
:badge="orderBadge(item)"
|
||||
@click="goOrders(item)"
|
||||
/>
|
||||
</van-grid>
|
||||
</section>
|
||||
@@ -78,6 +80,7 @@
|
||||
<script>
|
||||
import { mapActions, mapState } from 'pinia'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { postFunction } from '@/api/supplied-service'
|
||||
import JoyfulSelectionProfileSkeleton from '@/skeleton-view/joyful-selection/profile/index.vue'
|
||||
|
||||
export default {
|
||||
@@ -88,24 +91,31 @@ export default {
|
||||
data: () => ({
|
||||
loading: true,
|
||||
orderItems: [
|
||||
{ icon: 'balance-pay', text: '待付款' },
|
||||
{ icon: 'logistics', text: '待发货' },
|
||||
{ icon: 'send-gift-o', text: '待收货' },
|
||||
{ icon: 'todo-list-o', text: '已完成' },
|
||||
{ icon: 'after-sale', text: '售后' }
|
||||
]
|
||||
{ icon: 'balance-pay', text: '待付款', tab: 'pending-pay', badgeKey: 'pendingPay' },
|
||||
{ icon: 'logistics', text: '待发货', tab: 'pending-ship' },
|
||||
{ icon: 'send-gift-o', text: '待收货', tab: 'pending-receive', badgeKey: 'pendingReceive' },
|
||||
{ icon: 'after-sale', text: '退款/售后', path: '/joyful-selection/after-sale', badgeKey: 'afterSale' }
|
||||
],
|
||||
orderCounts: {
|
||||
pendingPay: 0,
|
||||
pendingReceive: 0,
|
||||
afterSale: 0
|
||||
}
|
||||
}),
|
||||
computed: {
|
||||
...mapState(useUserStore, ['pointsBalance']),
|
||||
...mapState(useUserStore, ['pointsBalance', 'supplierId', 'userId']),
|
||||
formattedPoints() {
|
||||
return Number(this.pointsBalance || 0).toLocaleString('zh-CN', {
|
||||
maximumFractionDigits: 2
|
||||
})
|
||||
},
|
||||
orderBadgeCounts() {
|
||||
return this.orderCounts
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loading = true
|
||||
this.fetchUserPoints().catch(error => {
|
||||
this.fetchProfileData().catch(error => {
|
||||
console.warn('查询用户积分失败', error)
|
||||
this.$toast(error.message || '查询用户积分失败')
|
||||
}).finally(() => {
|
||||
@@ -113,7 +123,46 @@ export default {
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useUserStore, ['fetchUserPoints'])
|
||||
...mapActions(useUserStore, ['fetchUserPoints']),
|
||||
async fetchProfileData() {
|
||||
await this.fetchUserPoints()
|
||||
|
||||
if (!this.supplierId || !this.userId) {
|
||||
this.orderCounts = {
|
||||
pendingPay: 0,
|
||||
pendingReceive: 0,
|
||||
afterSale: 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const data = await postFunction('order-count', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId
|
||||
})
|
||||
|
||||
this.orderCounts = {
|
||||
pendingPay: Number(data?.pendingPay || 0),
|
||||
pendingReceive: Number(data?.pendingReceive || 0),
|
||||
afterSale: Number(data?.afterSale || 0)
|
||||
}
|
||||
},
|
||||
orderBadge(item) {
|
||||
const count = item.badgeKey ? this.orderBadgeCounts[item.badgeKey] : 0
|
||||
|
||||
return count > 0 ? count : undefined
|
||||
},
|
||||
goOrders(item) {
|
||||
if (item.path) {
|
||||
this.$router.push(item.path)
|
||||
return
|
||||
}
|
||||
|
||||
this.$router.push({
|
||||
path: '/joyful-selection/orders',
|
||||
query: item.tab ? { tab: item.tab } : {}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -8,28 +8,88 @@
|
||||
|
||||
<template v-else>
|
||||
<section class="status-card">
|
||||
<h2>{{ currentSubOrder.status }}</h2>
|
||||
<p>主单号:{{ currentOrder.mainOrderId || '-' }}</p>
|
||||
<p>子单号:{{ currentSubOrder.orderId || '-' }}</p>
|
||||
<div class="status-card__title">
|
||||
<van-icon name="orders-o" />
|
||||
<div>
|
||||
<h2>{{ currentSubOrder.status }}</h2>
|
||||
<p>{{ statusDesc }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-progress">
|
||||
<div class="order-progress__line">
|
||||
<span :style="{ width: progressWidth }"></span>
|
||||
</div>
|
||||
<div
|
||||
v-for="(step, index) in progressSteps"
|
||||
:key="step.text"
|
||||
class="order-progress__step"
|
||||
:class="{ active: index <= activeProgressIndex, passed: index < activeProgressIndex, current: index === activeProgressIndex }"
|
||||
>
|
||||
<i></i>
|
||||
<b>{{ step.text }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="goods-card">
|
||||
<h3 class="card-title">订单信息</h3>
|
||||
<article v-for="item in currentSubOrder.products" :key="item.id" class="goods-item">
|
||||
<van-image class="goods-thumb" :src="item.image" fit="cover" />
|
||||
<div class="goods-info">
|
||||
<h3>{{ item.name }}</h3>
|
||||
<p>SKU:{{ item.sku || '-' }}</p>
|
||||
<p>{{ skuText(item) }}</p>
|
||||
<b>¥{{ item.price }}</b>
|
||||
</div>
|
||||
<span>x{{ item.number || 1 }}</span>
|
||||
</article>
|
||||
<div v-if="detailActions.length" class="goods-actions">
|
||||
<van-button
|
||||
v-for="action in detailActions"
|
||||
:key="action.key"
|
||||
size="small"
|
||||
:class="action.buttonClass"
|
||||
:loading="isOperating(action.key)"
|
||||
@click="submitDetailAction(action)"
|
||||
>
|
||||
{{ action.text }}
|
||||
</van-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="address-card">
|
||||
<h3 class="card-title">收货地址</h3>
|
||||
<div class="address-row">
|
||||
<div>
|
||||
<p>{{ receiverNameText }} <span>{{ receiverMobileText }}</span></p>
|
||||
<b><van-icon name="location-o" />{{ receiverAddressText }}</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class="remark-row">
|
||||
<span>备注</span>
|
||||
<b>{{ remarkText }}</b>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="info-card">
|
||||
<p><span>订单编号</span><b>{{ currentSubOrder.orderNumberId || '-' }}</b></p>
|
||||
<p><span>供应商</span><b>{{ currentSubOrder.supplierId || '-' }}</b></p>
|
||||
<p><span>商品数量</span><b>{{ currentSubOrder.count || 0 }}</b></p>
|
||||
<p><span>订单金额</span><b class="price">¥{{ currentSubOrder.amount }}</b></p>
|
||||
<p><span>订单编号</span><b>{{ currentSubOrder.orderNumberId || currentSubOrder.orderId || '-' }}</b></p>
|
||||
<p><span>下单时间</span><b>{{ currentSubOrder.createTime || currentOrder.createTime || '-' }}</b></p>
|
||||
<p><span>支付方式</span><b>{{ payMethodText }}</b></p>
|
||||
<p v-if="!isPendingPay"><span>支付时间</span><b>{{ currentOrder.payTime || '-' }}</b></p>
|
||||
<p><span>商品金额</span><b>¥{{ currentSubOrder.amount }}</b></p>
|
||||
<p><span>运费</span><b>¥{{ freightText }}</b></p>
|
||||
<p><span>实付款</span><b class="price">¥{{ paidAmountText }}</b></p>
|
||||
</section>
|
||||
|
||||
<section v-if="showLogisticsCard" class="logistics-card">
|
||||
<h3 class="card-title">物流信息</h3>
|
||||
<div class="logistics-row">
|
||||
<van-icon name="logistics" />
|
||||
<span>{{ logisticsName }}</span>
|
||||
<b>{{ logisticsNo }}</b>
|
||||
<em>{{ logisticsStatusText }}</em>
|
||||
</div>
|
||||
<p>{{ logisticsTimeText }}</p>
|
||||
<p>{{ logisticsDescText }}</p>
|
||||
</section>
|
||||
</template>
|
||||
</main>
|
||||
@@ -38,9 +98,10 @@
|
||||
|
||||
<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 { savePaymentInfo } from '../../order/payment/payment-data'
|
||||
import JoyfulSelectionOrderDetailSkeleton from '@/skeleton-view/joyful-selection/profile/order-detail/index.vue'
|
||||
|
||||
const ORDER_STATE_VIEW = {
|
||||
@@ -53,6 +114,22 @@ const ORDER_STATE_VIEW = {
|
||||
7: { text: '退货', statusClass: 'muted' }
|
||||
}
|
||||
|
||||
const ORDER_EVENT_TYPE = {
|
||||
RETURN_ORDER: 4,
|
||||
CONFIRM_RECEIVE: 6
|
||||
}
|
||||
|
||||
const SKU_COLOR_MAP = {
|
||||
cream: '奶油白',
|
||||
blue: '雾霾蓝',
|
||||
orange: '暖橙色'
|
||||
}
|
||||
|
||||
const SKU_SIZE_MAP = {
|
||||
standard: '标准款',
|
||||
plus: '升级款'
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'JoyfulSelectionOrderDetail',
|
||||
components: {
|
||||
@@ -60,11 +137,113 @@ export default {
|
||||
},
|
||||
data: () => ({
|
||||
loading: true,
|
||||
operationKey: '',
|
||||
currentOrder: null,
|
||||
currentSubOrder: null
|
||||
}),
|
||||
computed: {
|
||||
...mapState(useUserStore, ['supplierId', 'userId'])
|
||||
...mapState(useUserStore, ['supplierId', 'userId']),
|
||||
progressSteps() {
|
||||
return [
|
||||
{ text: '待付款', state: 0 },
|
||||
{ text: '待发货', state: 1 },
|
||||
{ text: '待收货', state: 2 },
|
||||
{ text: '已完成', state: 5 }
|
||||
]
|
||||
},
|
||||
activeProgressIndex() {
|
||||
const state = Number(this.currentSubOrder?.orderState)
|
||||
if (state === 0) return 0
|
||||
if (state === 1) return 1
|
||||
if (state === 2) return 2
|
||||
if (state === 5) return 3
|
||||
return 0
|
||||
},
|
||||
progressWidth() {
|
||||
return `${(this.activeProgressIndex / (this.progressSteps.length - 1)) * 100}%`
|
||||
},
|
||||
statusDesc() {
|
||||
const state = Number(this.currentSubOrder?.orderState)
|
||||
if (state === 1) return '商家已发货,等待快递揽收'
|
||||
if (state === 2) return '商品运输中,请留意收货'
|
||||
if (state === 5) return '订单已完成'
|
||||
if (state === 0) return '订单待支付,请及时付款'
|
||||
return '订单状态已更新'
|
||||
},
|
||||
payMethodText() {
|
||||
const payType = Number(this.currentOrder?.wPayOrAPay ?? this.currentSubOrder?.wPayOrAPay ?? 0)
|
||||
return payType === 1 ? '支付宝支付' : '微信支付'
|
||||
},
|
||||
freightText() {
|
||||
return Number(this.currentSubOrder?.freightCost || 0).toFixed(2)
|
||||
},
|
||||
paidAmountText() {
|
||||
return Number(this.currentSubOrder?.amount || 0).toFixed(2)
|
||||
},
|
||||
receiverNameText() {
|
||||
return this.currentSubOrder?.receiverName || this.currentOrder?.receiverName || '-'
|
||||
},
|
||||
receiverMobileText() {
|
||||
return this.currentSubOrder?.receiverMobile || this.currentOrder?.receiverMobile || '-'
|
||||
},
|
||||
receiverAddressText() {
|
||||
return this.currentSubOrder?.receiverAddress || this.currentOrder?.receiverAddress || '-'
|
||||
},
|
||||
remarkText() {
|
||||
return this.currentSubOrder?.remark || this.currentOrder?.remark || '无'
|
||||
},
|
||||
detailActions() {
|
||||
const state = Number(this.currentSubOrder?.orderState)
|
||||
|
||||
if (state === 0) {
|
||||
return [
|
||||
{ key: 'cancel-main', text: '取消订单', buttonClass: 'dark-action' },
|
||||
{ key: 'pay-main', text: '去支付', buttonClass: 'primary-action' }
|
||||
]
|
||||
}
|
||||
|
||||
if (state === 1 || state === 5) {
|
||||
return [{ key: 'after-sale', orderType: ORDER_EVENT_TYPE.RETURN_ORDER, text: '申请售后', buttonClass: 'dark-action' }]
|
||||
}
|
||||
|
||||
if (state === 2) {
|
||||
return [
|
||||
{ key: 'after-sale', orderType: ORDER_EVENT_TYPE.RETURN_ORDER, text: '申请售后', buttonClass: 'dark-action' },
|
||||
{ key: 'confirm-receive', orderType: ORDER_EVENT_TYPE.CONFIRM_RECEIVE, text: '确认收货', buttonClass: 'primary-action' }
|
||||
]
|
||||
}
|
||||
|
||||
return []
|
||||
},
|
||||
isPendingPay() {
|
||||
return Number(this.currentSubOrder?.orderState) === 0
|
||||
},
|
||||
showLogisticsCard() {
|
||||
return Number(this.currentSubOrder?.orderState) > 0
|
||||
},
|
||||
logisticsName() {
|
||||
return this.currentSubOrder?.crrgBsnNm || this.currentOrder?.crrgBsnNm || '申通快递'
|
||||
},
|
||||
logisticsNo() {
|
||||
return this.currentSubOrder?.logisticsOrderId || this.currentOrder?.logisticsOrderId || '-'
|
||||
},
|
||||
logisticsStatusText() {
|
||||
const state = Number(this.currentSubOrder?.orderState)
|
||||
if (state === 1) return '待揽收'
|
||||
if (state === 2) return '运输中'
|
||||
if (state === 5) return '已签收'
|
||||
return '-'
|
||||
},
|
||||
logisticsTimeText() {
|
||||
return this.currentSubOrder?.deliveryTime || this.currentOrder?.deliveryTime || this.currentSubOrder?.createTime || '-'
|
||||
},
|
||||
logisticsDescText() {
|
||||
const state = Number(this.currentSubOrder?.orderState)
|
||||
if (state === 1) return '待商家发货'
|
||||
if (state === 2) return '商家已发货,等待快递揽收'
|
||||
if (state === 5) return '商品已签收,订单已完成'
|
||||
return '物流状态已更新'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadOrderDetail()
|
||||
@@ -80,6 +259,169 @@ export default {
|
||||
statusClass: view.statusClass
|
||||
}
|
||||
},
|
||||
skuText(item = {}) {
|
||||
const sku = String(item.sku || item.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(' | ') : '-'
|
||||
},
|
||||
isOperating(key) {
|
||||
return this.operationKey === key
|
||||
},
|
||||
submitDetailAction(action) {
|
||||
if (action.key === 'cancel-main') {
|
||||
this.cancelMainOrder()
|
||||
return
|
||||
}
|
||||
|
||||
if (action.key === 'pay-main') {
|
||||
this.goPay()
|
||||
return
|
||||
}
|
||||
|
||||
if (action.key === 'after-sale') {
|
||||
this.submitAfterSale(action)
|
||||
return
|
||||
}
|
||||
|
||||
if (action.key === 'confirm-receive') {
|
||||
this.confirmReceive(action)
|
||||
}
|
||||
},
|
||||
goPay() {
|
||||
const products = this.currentSubOrder?.products || []
|
||||
|
||||
savePaymentInfo({
|
||||
orderNo: this.currentOrder?.mainOrderId || this.currentOrder?.orderId,
|
||||
orderNumberId: this.currentOrder?.orderNumberId || '',
|
||||
amount: Math.round(Number(this.currentOrder?.amount || this.currentSubOrder?.amount || 0) * 100),
|
||||
receiverName: this.receiverNameText === '-' ? '' : this.receiverNameText,
|
||||
receiverTel: this.receiverMobileText === '-' ? '' : this.receiverMobileText,
|
||||
goods: products
|
||||
})
|
||||
this.$router.push('/joyful-selection/payment')
|
||||
},
|
||||
async cancelMainOrder() {
|
||||
try {
|
||||
await Dialog.confirm({
|
||||
title: '取消订单',
|
||||
message: '确定取消该未支付订单吗?'
|
||||
})
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
|
||||
this.operationKey = 'cancel-main'
|
||||
|
||||
try {
|
||||
await this.fetchBridgeToken()
|
||||
|
||||
const mainOrderId = this.currentOrder?.mainOrderId || this.currentOrder?.orderId
|
||||
if (!this.supplierId || !this.userId || !mainOrderId) {
|
||||
Toast('订单信息不完整,请稍后再试')
|
||||
return
|
||||
}
|
||||
|
||||
await postFunction('order-cancel-main', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId,
|
||||
orderId: mainOrderId,
|
||||
mainOrderId
|
||||
})
|
||||
|
||||
Toast('订单已取消')
|
||||
await this.loadOrderDetail()
|
||||
} catch (error) {
|
||||
Toast(error.message || '取消订单失败,请稍后再试')
|
||||
} finally {
|
||||
this.operationKey = ''
|
||||
}
|
||||
},
|
||||
async submitAfterSale(action) {
|
||||
try {
|
||||
await Dialog.confirm({
|
||||
title: action.text,
|
||||
message: `确定${action.text}吗?提交后将进入商家待审核。`
|
||||
})
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
|
||||
this.operationKey = action.key
|
||||
|
||||
try {
|
||||
await this.fetchBridgeToken()
|
||||
|
||||
const mainOrderId = this.currentOrder?.mainOrderId || this.currentOrder?.orderId
|
||||
const orderId = this.currentSubOrder?.orderId
|
||||
if (!this.supplierId || !this.userId || !mainOrderId || !orderId) {
|
||||
Toast('订单信息不完整,请稍后再试')
|
||||
return
|
||||
}
|
||||
|
||||
await postFunction('after-sale-apply', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId,
|
||||
serviceType: action.orderType,
|
||||
orderId,
|
||||
subOrderId: orderId,
|
||||
mainOrderId,
|
||||
amount: Number(this.currentSubOrder?.amount || 0),
|
||||
reason: action.text
|
||||
})
|
||||
|
||||
Toast(`${action.text}已提交,等待商家审核`)
|
||||
await this.loadOrderDetail()
|
||||
} catch (error) {
|
||||
Toast(error.message || `${action.text}失败,请稍后再试`)
|
||||
} finally {
|
||||
this.operationKey = ''
|
||||
}
|
||||
},
|
||||
async confirmReceive(action) {
|
||||
try {
|
||||
await Dialog.confirm({
|
||||
title: action.text,
|
||||
message: '确定确认收货吗?'
|
||||
})
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
|
||||
this.operationKey = action.key
|
||||
|
||||
try {
|
||||
await this.fetchBridgeToken()
|
||||
|
||||
const mainOrderId = this.currentOrder?.mainOrderId || this.currentOrder?.orderId
|
||||
const orderId = this.currentSubOrder?.orderId
|
||||
if (!this.supplierId || !this.userId || !mainOrderId || !orderId) {
|
||||
Toast('订单信息不完整,请稍后再试')
|
||||
return
|
||||
}
|
||||
|
||||
await postFunction('order-push-event', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId,
|
||||
orderType: action.orderType,
|
||||
orderId,
|
||||
mainOrderId,
|
||||
subOrderId: orderId,
|
||||
amount: Number(this.currentSubOrder?.amount || 0),
|
||||
reason: action.text,
|
||||
time: Date.now()
|
||||
})
|
||||
|
||||
Toast(`${action.text}成功`)
|
||||
await this.loadOrderDetail()
|
||||
} catch (error) {
|
||||
Toast(error.message || `${action.text}失败,请稍后再试`)
|
||||
} finally {
|
||||
this.operationKey = ''
|
||||
}
|
||||
},
|
||||
async loadOrderDetail() {
|
||||
this.loading = true
|
||||
try {
|
||||
@@ -90,15 +432,16 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
const data = await postFunction('order-list', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId
|
||||
})
|
||||
|
||||
const mainOrderId = String(this.$route.query.mainOrderId || '')
|
||||
const orderId = String(this.$route.query.orderId || '')
|
||||
const order = (data?.orders || []).find(item => item.mainOrderId === mainOrderId)
|
||||
const subOrder = order?.subOrders?.find(item => item.orderId === orderId)
|
||||
const data = await postFunction('order-detail', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId,
|
||||
mainOrderId,
|
||||
orderId
|
||||
})
|
||||
const order = data?.order || null
|
||||
const subOrder = data?.subOrder || null
|
||||
|
||||
this.currentOrder = order || null
|
||||
this.currentSubOrder = order && subOrder ? this.normalizeSubOrder(order, subOrder) : null
|
||||
@@ -115,8 +458,8 @@ export default {
|
||||
<style scoped lang="less">
|
||||
.detail-page {
|
||||
min-height: 100vh;
|
||||
padding: 46px 0 16px;
|
||||
background: #f7f7f7;
|
||||
padding: 46px 9px 16px;
|
||||
background: #f7f8fa;
|
||||
}
|
||||
|
||||
.detail-page ::v-deep .van-nav-bar {
|
||||
@@ -130,66 +473,177 @@ export default {
|
||||
|
||||
.status-card,
|
||||
.goods-card,
|
||||
.info-card {
|
||||
margin: 10px 12px 0;
|
||||
.address-card,
|
||||
.info-card,
|
||||
.logistics-card {
|
||||
margin: 10px 0 0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.status-card {
|
||||
padding: 16px 14px;
|
||||
padding: 14px 14px 16px;
|
||||
border: 1px solid #ffe4d6;
|
||||
background: linear-gradient(135deg, #fff1e6 0%, #fff8f4 100%);
|
||||
}
|
||||
|
||||
.status-card__title {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.status-card__title > .van-icon {
|
||||
display: flex;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 2px;
|
||||
border-radius: 5px;
|
||||
background: #ff6b2c;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.status-card h2 {
|
||||
margin: 0 0 10px;
|
||||
margin: 0;
|
||||
color: #ff5b20;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.status-card p {
|
||||
margin: 4px 0 0;
|
||||
color: #666;
|
||||
margin: 5px 0 0;
|
||||
color: #b69b8b;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
.order-progress {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
margin-top: 17px;
|
||||
}
|
||||
|
||||
.order-progress__line {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 38px;
|
||||
left: 38px;
|
||||
height: 2px;
|
||||
overflow: hidden;
|
||||
border-radius: 2px;
|
||||
background: #e9edf0;
|
||||
}
|
||||
|
||||
.order-progress__line span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
background: #ffb99c;
|
||||
}
|
||||
|
||||
.order-progress__step {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #b7b9be;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.order-progress__step i {
|
||||
position: relative;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
background: #e7ebee;
|
||||
}
|
||||
|
||||
.order-progress__step.active {
|
||||
color: #ff5b20;
|
||||
}
|
||||
|
||||
.order-progress__step.passed i {
|
||||
background: #ff5b20;
|
||||
}
|
||||
|
||||
.order-progress__step.current {
|
||||
color: #e84b18;
|
||||
}
|
||||
|
||||
.order-progress__step.current i {
|
||||
background: #e84b18;
|
||||
}
|
||||
|
||||
.order-progress__step i::after {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.goods-card {
|
||||
padding: 2px 12px;
|
||||
padding: 15px 15px 14px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin: 0 0 14px;
|
||||
color: #222;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 86px;
|
||||
padding: 12px 0;
|
||||
align-items: stretch;
|
||||
min-height: 78px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.goods-item + .goods-item {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f4f4f4;
|
||||
}
|
||||
|
||||
.goods-thumb {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
width: 78px;
|
||||
height: 78px;
|
||||
flex: none;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
padding: 0 10px;
|
||||
padding: 2px 10px 0 12px;
|
||||
}
|
||||
|
||||
.goods-info h3 {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
margin: 0 0 6px;
|
||||
margin: 0 0 8px;
|
||||
color: #222;
|
||||
font-size: 13px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 18px;
|
||||
line-height: 20px;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
@@ -198,8 +652,8 @@ export default {
|
||||
.goods-info p {
|
||||
overflow: hidden;
|
||||
margin: 0 0 6px;
|
||||
color: #999;
|
||||
font-size: 11px;
|
||||
color: #8f8f8f;
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -207,15 +661,112 @@ export default {
|
||||
.goods-info b,
|
||||
.price {
|
||||
color: #ff5b20;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.goods-item > span {
|
||||
align-self: flex-end;
|
||||
padding-bottom: 2px;
|
||||
color: #111;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.goods-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.goods-actions .van-button {
|
||||
min-width: 88px;
|
||||
height: 34px;
|
||||
padding: 0 16px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.dark-action {
|
||||
color: #333;
|
||||
background: #f6f7f9;
|
||||
}
|
||||
|
||||
.primary-action {
|
||||
color: #ff5b20;
|
||||
background: #fff1e8;
|
||||
}
|
||||
|
||||
.address-card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.address-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.address-row > div {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.address-row p {
|
||||
margin: 0 0 7px;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.address-row p span {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.address-row b {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
color: #333;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.address-row b .van-icon {
|
||||
flex: none;
|
||||
margin: 2px 6px 0 0;
|
||||
color: #d58b73;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.remark-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 15px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f4f4f4;
|
||||
color: #333;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.remark-row span {
|
||||
flex: none;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.remark-row b {
|
||||
flex: 1;
|
||||
margin-left: 12px;
|
||||
color: #999;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
padding: 12px 14px;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
.info-card p {
|
||||
@@ -225,7 +776,7 @@ export default {
|
||||
margin: 0;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
line-height: 30px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.info-card b {
|
||||
@@ -237,4 +788,54 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.logistics-card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.logistics-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #333;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.logistics-row .van-icon {
|
||||
flex: none;
|
||||
margin-right: 6px;
|
||||
color: #999;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.logistics-row span {
|
||||
flex: none;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.logistics-row b {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-left: 8px;
|
||||
overflow: hidden;
|
||||
color: #333;
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.logistics-row em {
|
||||
flex: none;
|
||||
margin-left: 8px;
|
||||
color: #ff5b20;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.logistics-card p {
|
||||
margin: 5px 0 0 21px;
|
||||
color: #777;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
color="#ff5b20"
|
||||
title-active-color="#ff5b20"
|
||||
>
|
||||
<van-tab v-for="tab in tabs" :key="tab.label" :title="tab.label" />
|
||||
<van-tab v-for="tab in tabs" :key="tab.label" :title="tab.label" :badge="tabBadge(tab)" />
|
||||
</van-tabs>
|
||||
|
||||
<van-pull-refresh v-model="refreshing" class="order-list" @refresh="onRefresh">
|
||||
@@ -89,6 +89,7 @@ import { mapActions, mapState } from 'pinia'
|
||||
import { Dialog, Toast } from 'vant'
|
||||
import { postFunction } from '@/api/supplied-service'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { savePaymentInfo } from '../../order/payment/payment-data'
|
||||
import JoyfulSelectionOrdersSkeleton from '@/skeleton-view/joyful-selection/profile/orders/index.vue'
|
||||
|
||||
const ORDER_STATE = {
|
||||
@@ -142,8 +143,7 @@ export default {
|
||||
{ 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' }
|
||||
{ label: '已完成', type: 'sub-state', state: ORDER_STATE.COMPLETED }
|
||||
],
|
||||
orderItems: [],
|
||||
loading: false,
|
||||
@@ -184,10 +184,24 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.applyRouteTab()
|
||||
this.loadOrders()
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useUserStore, ['fetchBridgeToken']),
|
||||
applyRouteTab() {
|
||||
const tabIndexMap = {
|
||||
'pending-pay': 1,
|
||||
'pending-ship': 2,
|
||||
'pending-receive': 3,
|
||||
completed: 5
|
||||
}
|
||||
const nextActive = tabIndexMap[this.$route.query.tab]
|
||||
|
||||
if (nextActive !== undefined) {
|
||||
this.active = nextActive
|
||||
}
|
||||
},
|
||||
previewProducts(products = []) {
|
||||
return products.slice(0, 3)
|
||||
},
|
||||
@@ -212,7 +226,7 @@ export default {
|
||||
return color || size ? [color, size].filter(Boolean).join(' | ') : '-'
|
||||
},
|
||||
showLogistics(order) {
|
||||
return ![ORDER_STATE.CANCELLED, ORDER_STATE.COMPLETED].includes(Number(order.orderState))
|
||||
return ![ORDER_STATE.PENDING_PAY, ORDER_STATE.CANCELLED, ORDER_STATE.COMPLETED].includes(Number(order.orderState))
|
||||
},
|
||||
actionKey(type, orderId) {
|
||||
return `${type}-${orderId}`
|
||||
@@ -220,13 +234,23 @@ export default {
|
||||
isOperating(type, orderId) {
|
||||
return this.operationKey === this.actionKey(type, orderId)
|
||||
},
|
||||
tabBadge(tab) {
|
||||
if (tab.type !== 'pending-main') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const count = this.orderItems.filter(order => {
|
||||
return order.displayType === 'main' && Number(order.orderState) === ORDER_STATE.PENDING_PAY
|
||||
}).length
|
||||
|
||||
return count > 0 ? count : undefined
|
||||
},
|
||||
subActions(subOrder) {
|
||||
const state = Number(subOrder.orderState)
|
||||
const actions = []
|
||||
|
||||
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.FINISH_PAY, ORDER_STATE.IN_PROGRESS, ORDER_STATE.COMPLETED].includes(state)) {
|
||||
actions.push({ orderType: ORDER_EVENT_TYPE.RETURN_ORDER, text: '申请售后', buttonClass: 'dark-action' })
|
||||
}
|
||||
|
||||
if (state === ORDER_STATE.IN_PROGRESS) {
|
||||
@@ -289,13 +313,16 @@ export default {
|
||||
orderActions(order) {
|
||||
if (order.displayType === 'main') {
|
||||
return Number(order.orderState) === ORDER_STATE.PENDING_PAY
|
||||
? [{ key: 'cancel-main', orderId: order.mainOrderId, text: '取消订单', buttonClass: 'dark-action' }]
|
||||
? [
|
||||
{ key: 'cancel-main', orderId: order.mainOrderId, text: '取消订单', buttonClass: 'dark-action' },
|
||||
{ key: 'pay-main', orderId: order.mainOrderId, text: '去支付', buttonClass: 'primary-action' }
|
||||
]
|
||||
: []
|
||||
}
|
||||
|
||||
return this.subActions(order).map(action => ({
|
||||
...action,
|
||||
key: action.orderType,
|
||||
key: action.key || action.orderType,
|
||||
orderId: order.orderId
|
||||
}))
|
||||
},
|
||||
@@ -305,12 +332,40 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
if (action.key === 'ship-order') {
|
||||
this.shipOrder(order)
|
||||
return
|
||||
}
|
||||
|
||||
if (action.key === 'pay-main') {
|
||||
this.goPay(order)
|
||||
return
|
||||
}
|
||||
|
||||
if ([ORDER_EVENT_TYPE.RETURN_ORDER, ORDER_EVENT_TYPE.EXCHANGE_ORDER].includes(action.orderType)) {
|
||||
this.submitAfterSale(order, action)
|
||||
return
|
||||
}
|
||||
|
||||
this.submitSubOrderEvent(
|
||||
{ mainOrderId: order.mainOrderId },
|
||||
order,
|
||||
action
|
||||
)
|
||||
},
|
||||
goPay(order) {
|
||||
const firstProduct = this.firstProduct(order)
|
||||
|
||||
savePaymentInfo({
|
||||
orderNo: order.mainOrderId || order.orderId,
|
||||
orderNumberId: order.orderNumberId || '',
|
||||
amount: Math.round(Number(order.amount || 0) * 100),
|
||||
receiverName: order.receiverName || '',
|
||||
receiverTel: order.receiverMobile || '',
|
||||
goods: order.products || (firstProduct.id ? [firstProduct] : [])
|
||||
})
|
||||
this.$router.push('/joyful-selection/payment')
|
||||
},
|
||||
async cancelMainOrder(order) {
|
||||
try {
|
||||
await Dialog.confirm({
|
||||
@@ -346,6 +401,81 @@ export default {
|
||||
this.operationKey = ''
|
||||
}
|
||||
},
|
||||
async shipOrder(order) {
|
||||
try {
|
||||
await Dialog.confirm({
|
||||
title: '发货',
|
||||
message: '确定将该订单更新为已发货吗?'
|
||||
})
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
|
||||
this.operationKey = this.actionKey('ship-order', order.orderId)
|
||||
|
||||
try {
|
||||
await this.fetchBridgeToken()
|
||||
|
||||
if (!this.supplierId || !this.userId || !order.orderId) {
|
||||
Toast('订单信息不完整,请稍后再试')
|
||||
return
|
||||
}
|
||||
|
||||
await postFunction('order-ship', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId,
|
||||
orderId: order.orderId,
|
||||
mainOrderId: order.mainOrderId,
|
||||
deliveryStatus: 1
|
||||
})
|
||||
|
||||
Toast('发货成功')
|
||||
await this.loadOrders()
|
||||
} catch (error) {
|
||||
Toast(error.message || '发货失败,请稍后再试')
|
||||
} finally {
|
||||
this.operationKey = ''
|
||||
}
|
||||
},
|
||||
async submitAfterSale(order, action) {
|
||||
try {
|
||||
await Dialog.confirm({
|
||||
title: action.text,
|
||||
message: `确定申请${action.text}吗?提交后将进入商家待审核。`
|
||||
})
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
|
||||
this.operationKey = this.actionKey(action.orderType, order.orderId)
|
||||
|
||||
try {
|
||||
await this.fetchBridgeToken()
|
||||
|
||||
if (!this.supplierId || !this.userId || !order.mainOrderId || !order.orderId) {
|
||||
Toast('订单信息不完整,请稍后再试')
|
||||
return
|
||||
}
|
||||
|
||||
await postFunction('after-sale-apply', {
|
||||
supplierId: this.supplierId,
|
||||
userId: this.userId,
|
||||
serviceType: action.orderType,
|
||||
orderId: order.orderId,
|
||||
subOrderId: order.orderId,
|
||||
mainOrderId: order.mainOrderId,
|
||||
amount: Number(order.amount || 0),
|
||||
reason: action.text
|
||||
})
|
||||
|
||||
Toast(`${action.text}申请已提交,等待商家审核`)
|
||||
await this.loadOrders()
|
||||
} catch (error) {
|
||||
Toast(error.message || `${action.text}申请失败,请稍后再试`)
|
||||
} finally {
|
||||
this.operationKey = ''
|
||||
}
|
||||
},
|
||||
async submitSubOrderEvent(order, subOrder, action) {
|
||||
try {
|
||||
await Dialog.confirm({
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Reference in New Issue
Block a user