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 JoyfulSelectionAddressEdit from '@/view/joyful-selection/address/edit/index.vue'
|
||||||
import JoyfulSelectionOrders from '@/view/joyful-selection/profile/orders/index.vue'
|
import JoyfulSelectionOrders from '@/view/joyful-selection/profile/orders/index.vue'
|
||||||
import JoyfulSelectionOrderDetail from '@/view/joyful-selection/profile/order-detail/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 JoyfulSelectionProfile from '@/view/joyful-selection/profile/index.vue'
|
||||||
import JoyfulSelectionPoints from '@/view/joyful-selection/profile/points/index.vue'
|
import JoyfulSelectionPoints from '@/view/joyful-selection/profile/points/index.vue'
|
||||||
import HomeLiving from '@/view/home-living/index.vue'
|
import HomeLiving from '@/view/home-living/index.vue'
|
||||||
@@ -43,6 +44,7 @@ export default new Router({
|
|||||||
{ path: 'address/edit', component: JoyfulSelectionAddressEdit },
|
{ path: 'address/edit', component: JoyfulSelectionAddressEdit },
|
||||||
{ path: 'orders', component: JoyfulSelectionOrders },
|
{ path: 'orders', component: JoyfulSelectionOrders },
|
||||||
{ path: 'order-detail', component: JoyfulSelectionOrderDetail },
|
{ path: 'order-detail', component: JoyfulSelectionOrderDetail },
|
||||||
|
{ path: 'after-sale', component: JoyfulSelectionAfterSale },
|
||||||
{ path: 'profile', component: JoyfulSelectionProfile },
|
{ path: 'profile', component: JoyfulSelectionProfile },
|
||||||
{ path: 'points', component: JoyfulSelectionPoints }
|
{ path: 'points', component: JoyfulSelectionPoints }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,36 +1,7 @@
|
|||||||
const ADDRESS_KEY = 'joyful-selection-addresses'
|
|
||||||
const CHOSEN_ADDRESS_KEY = 'joyful-selection-chosen-address-id'
|
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 = () => {
|
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 => {
|
export const saveChosenAddressId = id => {
|
||||||
@@ -42,7 +13,7 @@ export const formatAddress = address => {
|
|||||||
return ''
|
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()}`
|
export const createAddressId = () => `addr-${Date.now()}`
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
show-delete
|
show-delete
|
||||||
show-set-default
|
show-set-default
|
||||||
show-search-result
|
show-search-result
|
||||||
|
:tel-maxlength="11"
|
||||||
:search-result="searchResult"
|
:search-result="searchResult"
|
||||||
:area-columns-placeholder="['请选择', '请选择', '请选择']"
|
:area-columns-placeholder="['请选择', '请选择', '请选择']"
|
||||||
@save="onSave"
|
@save="onSave"
|
||||||
@@ -21,9 +22,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapActions, mapState } from 'pinia'
|
||||||
import { Toast } from 'vant'
|
import { Toast } from 'vant'
|
||||||
import { areaList } from '@vant/area-data'
|
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'
|
import JoyfulSelectionAddressEditSkeleton from '@/skeleton-view/joyful-selection/address/edit/index.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -38,6 +42,7 @@ export default {
|
|||||||
addressInfo: {}
|
addressInfo: {}
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapState(useUserStore, ['supplierId', 'userId']),
|
||||||
editId() {
|
editId() {
|
||||||
return this.$route.query.id
|
return this.$route.query.id
|
||||||
},
|
},
|
||||||
@@ -46,51 +51,64 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
const address = readAddresses().find(item => item.id === this.editId)
|
this.loadAddress()
|
||||||
|
|
||||||
if (address) {
|
|
||||||
this.addressInfo = { ...address }
|
|
||||||
}
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onSave(content) {
|
...mapActions(useUserStore, ['fetchBridgeToken']),
|
||||||
const addresses = readAddresses()
|
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 = {
|
const nextAddress = {
|
||||||
...content,
|
...content,
|
||||||
id: this.editId || content.id || createAddressId()
|
id: this.editId || content.id || createAddressId()
|
||||||
}
|
}
|
||||||
const nextAddresses = addresses.filter(item => item.id !== nextAddress.id)
|
|
||||||
|
|
||||||
if (nextAddress.isDefault) {
|
await this.fetchBridgeToken()
|
||||||
nextAddresses.forEach(item => { item.isDefault = false })
|
await postFunction('address-save', {
|
||||||
}
|
...nextAddress,
|
||||||
|
addressId: nextAddress.id,
|
||||||
if (!nextAddresses.length && !nextAddress.isDefault) {
|
supplierId: this.supplierId,
|
||||||
nextAddress.isDefault = true
|
userId: this.userId
|
||||||
}
|
})
|
||||||
|
|
||||||
saveAddresses([nextAddress, ...nextAddresses])
|
|
||||||
saveChosenAddressId(nextAddress.id)
|
saveChosenAddressId(nextAddress.id)
|
||||||
Toast('保存成功')
|
Toast('保存成功')
|
||||||
this.$router.back()
|
this.$router.back()
|
||||||
},
|
},
|
||||||
onDelete() {
|
async onDelete() {
|
||||||
if (!this.editId) {
|
if (!this.editId) {
|
||||||
this.$router.back()
|
this.$router.back()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const addresses = readAddresses().filter(item => item.id !== this.editId)
|
await this.fetchBridgeToken()
|
||||||
|
await postFunction('address-delete', {
|
||||||
if (addresses.length && !addresses.some(item => item.isDefault)) {
|
supplierId: this.supplierId,
|
||||||
addresses[0].isDefault = true
|
userId: this.userId,
|
||||||
}
|
addressId: this.editId
|
||||||
|
})
|
||||||
saveAddresses(addresses)
|
saveChosenAddressId('')
|
||||||
saveChosenAddressId(addresses[0]?.id || '')
|
|
||||||
Toast('删除成功')
|
Toast('删除成功')
|
||||||
this.$router.back()
|
this.$router.back()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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'
|
import JoyfulSelectionAddressListSkeleton from '@/skeleton-view/joyful-selection/address/index.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -33,6 +37,7 @@ export default {
|
|||||||
disabledList: []
|
disabledList: []
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapState(useUserStore, ['supplierId', 'userId']),
|
||||||
addressList() {
|
addressList() {
|
||||||
return this.addresses.map(item => ({
|
return this.addresses.map(item => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
@@ -44,18 +49,54 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.addresses = readAddresses()
|
this.loadAddresses()
|
||||||
this.chosenAddressId = readChosenAddressId()
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
methods: {
|
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
|
this.chosenAddressId = item.id
|
||||||
saveChosenAddressId(item.id)
|
saveChosenAddressId(item.id)
|
||||||
|
|
||||||
if (this.$route.query.from === 'checkout') {
|
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()
|
this.$router.back()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,14 +4,22 @@
|
|||||||
<main v-else class="checkout-page">
|
<main v-else class="checkout-page">
|
||||||
<van-nav-bar title="确认订单" left-arrow @click-left="$router.back()" />
|
<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" />
|
<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>
|
<h3>收货人:{{ chosenAddress.name }} <span>{{ maskTel(chosenAddress.tel) }}</span></h3>
|
||||||
<p>{{ chosenAddress.province }}{{ chosenAddress.city }}{{ chosenAddress.county }}</p>
|
<p>{{ chosenAddress.province }}{{ chosenAddress.city }}{{ chosenAddress.county }}</p>
|
||||||
<p>{{ chosenAddress.addressDetail }}</p>
|
<p>{{ chosenAddress.addressDetail }}</p>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<section class="delivery-card">
|
<section class="delivery-card">
|
||||||
@@ -30,7 +38,8 @@
|
|||||||
<van-image class="order-thumb" :src="item.image" fit="cover" />
|
<van-image class="order-thumb" :src="item.image" fit="cover" />
|
||||||
<div class="order-info">
|
<div class="order-info">
|
||||||
<h3>{{ item.name }}</h3>
|
<h3>{{ item.name }}</h3>
|
||||||
<p>¥{{ item.price }}</p>
|
<p v-if="productSpecText(item)" class="order-desc">{{ productSpecText(item) }}</p>
|
||||||
|
<b>¥{{ item.price }}</b>
|
||||||
</div>
|
</div>
|
||||||
<span class="order-count">x{{ item.count || 1 }}</span>
|
<span class="order-count">x{{ item.count || 1 }}</span>
|
||||||
</article>
|
</article>
|
||||||
@@ -48,7 +57,7 @@
|
|||||||
<section class="amount-card">
|
<section class="amount-card">
|
||||||
<p><span>商品金额</span><b>¥{{ formatYuan(goodsAmount) }}</b></p>
|
<p><span>商品金额</span><b>¥{{ formatYuan(goodsAmount) }}</b></p>
|
||||||
<p><span>运费</span><b>¥0.00</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>
|
</section>
|
||||||
|
|
||||||
<van-submit-bar :price="goodsAmount" button-text="提交订单" @submit="onSubmit" />
|
<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 { postFunction } from '@/api/supplied-service'
|
||||||
import { useCartStore } from '@/store/cart'
|
import { useCartStore } from '@/store/cart'
|
||||||
import { useUserStore } from '@/store/user'
|
import { useUserStore } from '@/store/user'
|
||||||
import { readAddresses, readChosenAddressId } from '../../address/address-data'
|
|
||||||
import { createOrderNo, savePaymentInfo } from '../payment/payment-data'
|
import { createOrderNo, savePaymentInfo } from '../payment/payment-data'
|
||||||
import JoyfulSelectionCheckoutSkeleton from '@/skeleton-view/joyful-selection/order/checkout/index.vue'
|
import JoyfulSelectionCheckoutSkeleton from '@/skeleton-view/joyful-selection/order/checkout/index.vue'
|
||||||
|
|
||||||
@@ -96,13 +104,16 @@ export default {
|
|||||||
created() {
|
created() {
|
||||||
this.loadCart()
|
this.loadCart()
|
||||||
this.goods = this.getCheckoutGoods()
|
this.goods = this.getCheckoutGoods()
|
||||||
this.loadAddress()
|
this.loadAddress().catch(error => {
|
||||||
this.$nextTick(() => {
|
this.$toast(error.message || '查询地址失败,请稍后再试')
|
||||||
|
}).finally(() => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
activated() {
|
activated() {
|
||||||
this.loadAddress()
|
this.loadAddress().catch(error => {
|
||||||
|
this.$toast(error.message || '查询地址失败,请稍后再试')
|
||||||
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useCartStore, ['loadCart', 'removeItems']),
|
...mapActions(useCartStore, ['loadCart', 'removeItems']),
|
||||||
@@ -130,14 +141,37 @@ export default {
|
|||||||
formatYuan(price) {
|
formatYuan(price) {
|
||||||
return (Number(price || 0) / 100).toFixed(2)
|
return (Number(price || 0) / 100).toFixed(2)
|
||||||
},
|
},
|
||||||
loadAddress() {
|
productSpecText(item = {}) {
|
||||||
const addresses = readAddresses()
|
return String(item.skuText || item.desc || '').replace(/\s*\/\s*/g, ' | ')
|
||||||
const chosenId = readChosenAddressId()
|
},
|
||||||
this.chosenAddress = addresses.find(item => item.id === chosenId) || addresses[0] || {}
|
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 = '') {
|
maskTel(tel = '') {
|
||||||
return tel.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
|
return tel.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
|
||||||
},
|
},
|
||||||
|
showCouponTip() {
|
||||||
|
this.$toast('急速开发中,敬请期待')
|
||||||
|
},
|
||||||
async onSubmit() {
|
async onSubmit() {
|
||||||
if (this.submitting) {
|
if (this.submitting) {
|
||||||
return
|
return
|
||||||
@@ -162,6 +196,8 @@ export default {
|
|||||||
userId: this.userId,
|
userId: this.userId,
|
||||||
orderNo,
|
orderNo,
|
||||||
amount: this.goodsAmount,
|
amount: this.goodsAmount,
|
||||||
|
addressId: this.chosenAddress.id || '',
|
||||||
|
remark: this.remark,
|
||||||
goods: this.goods
|
goods: this.goods
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -233,6 +269,10 @@ export default {
|
|||||||
padding: 14px 12px;
|
padding: 14px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.address-card--empty {
|
||||||
|
background: #fff6ea;
|
||||||
|
}
|
||||||
|
|
||||||
.address-icon {
|
.address-icon {
|
||||||
flex: none;
|
flex: none;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
@@ -267,6 +307,34 @@ export default {
|
|||||||
white-space: nowrap;
|
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 {
|
.arrow-icon {
|
||||||
flex: none;
|
flex: none;
|
||||||
color: #999;
|
color: #999;
|
||||||
@@ -363,13 +431,26 @@ export default {
|
|||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-info p {
|
.order-info b {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #333;
|
color: #ff5b20;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 700;
|
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 {
|
.order-count {
|
||||||
flex: none;
|
flex: none;
|
||||||
color: #222;
|
color: #222;
|
||||||
@@ -425,10 +506,22 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.amount-card .muted {
|
.amount-card .muted {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
color: #777;
|
color: #777;
|
||||||
font-weight: 400;
|
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 {
|
.checkout-page ::v-deep .van-submit-bar {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
height: 58px;
|
height: 58px;
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ export default {
|
|||||||
id: this.currentGoods.id,
|
id: this.currentGoods.id,
|
||||||
name: this.currentGoods.name,
|
name: this.currentGoods.name,
|
||||||
desc: skuDesc,
|
desc: skuDesc,
|
||||||
|
productDesc: this.currentGoods.desc || this.currentGoods.detail || '',
|
||||||
price: this.currentGoods.price,
|
price: this.currentGoods.price,
|
||||||
image: this.currentGoods.image,
|
image: this.currentGoods.image,
|
||||||
tag: this.currentGoods.tag,
|
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>
|
<b>我的订单</b>
|
||||||
<span @click="$router.push('/joyful-selection/orders')">全部订单 <van-icon name="arrow" /></span>
|
<span @click="$router.push('/joyful-selection/orders')">全部订单 <van-icon name="arrow" /></span>
|
||||||
</div>
|
</div>
|
||||||
<van-grid :column-num="5" :border="false">
|
<van-grid :column-num="4" :border="false">
|
||||||
<van-grid-item
|
<van-grid-item
|
||||||
v-for="item in orderItems"
|
v-for="item in orderItems"
|
||||||
:key="item.text"
|
:key="item.text"
|
||||||
:icon="item.icon"
|
:icon="item.icon"
|
||||||
:text="item.text"
|
:text="item.text"
|
||||||
|
:badge="orderBadge(item)"
|
||||||
|
@click="goOrders(item)"
|
||||||
/>
|
/>
|
||||||
</van-grid>
|
</van-grid>
|
||||||
</section>
|
</section>
|
||||||
@@ -78,6 +80,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapActions, mapState } from 'pinia'
|
import { mapActions, mapState } from 'pinia'
|
||||||
import { useUserStore } from '@/store/user'
|
import { useUserStore } from '@/store/user'
|
||||||
|
import { postFunction } from '@/api/supplied-service'
|
||||||
import JoyfulSelectionProfileSkeleton from '@/skeleton-view/joyful-selection/profile/index.vue'
|
import JoyfulSelectionProfileSkeleton from '@/skeleton-view/joyful-selection/profile/index.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -88,24 +91,31 @@ export default {
|
|||||||
data: () => ({
|
data: () => ({
|
||||||
loading: true,
|
loading: true,
|
||||||
orderItems: [
|
orderItems: [
|
||||||
{ icon: 'balance-pay', text: '待付款' },
|
{ icon: 'balance-pay', text: '待付款', tab: 'pending-pay', badgeKey: 'pendingPay' },
|
||||||
{ icon: 'logistics', text: '待发货' },
|
{ icon: 'logistics', text: '待发货', tab: 'pending-ship' },
|
||||||
{ icon: 'send-gift-o', text: '待收货' },
|
{ icon: 'send-gift-o', text: '待收货', tab: 'pending-receive', badgeKey: 'pendingReceive' },
|
||||||
{ icon: 'todo-list-o', text: '已完成' },
|
{ icon: 'after-sale', text: '退款/售后', path: '/joyful-selection/after-sale', badgeKey: 'afterSale' }
|
||||||
{ icon: 'after-sale', text: '售后' }
|
],
|
||||||
]
|
orderCounts: {
|
||||||
|
pendingPay: 0,
|
||||||
|
pendingReceive: 0,
|
||||||
|
afterSale: 0
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useUserStore, ['pointsBalance']),
|
...mapState(useUserStore, ['pointsBalance', 'supplierId', 'userId']),
|
||||||
formattedPoints() {
|
formattedPoints() {
|
||||||
return Number(this.pointsBalance || 0).toLocaleString('zh-CN', {
|
return Number(this.pointsBalance || 0).toLocaleString('zh-CN', {
|
||||||
maximumFractionDigits: 2
|
maximumFractionDigits: 2
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
orderBadgeCounts() {
|
||||||
|
return this.orderCounts
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.fetchUserPoints().catch(error => {
|
this.fetchProfileData().catch(error => {
|
||||||
console.warn('查询用户积分失败', error)
|
console.warn('查询用户积分失败', error)
|
||||||
this.$toast(error.message || '查询用户积分失败')
|
this.$toast(error.message || '查询用户积分失败')
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
@@ -113,7 +123,46 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
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>
|
</script>
|
||||||
|
|||||||
@@ -8,28 +8,88 @@
|
|||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<section class="status-card">
|
<section class="status-card">
|
||||||
<h2>{{ currentSubOrder.status }}</h2>
|
<div class="status-card__title">
|
||||||
<p>主单号:{{ currentOrder.mainOrderId || '-' }}</p>
|
<van-icon name="orders-o" />
|
||||||
<p>子单号:{{ currentSubOrder.orderId || '-' }}</p>
|
<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>
|
||||||
|
|
||||||
<section class="goods-card">
|
<section class="goods-card">
|
||||||
|
<h3 class="card-title">订单信息</h3>
|
||||||
<article v-for="item in currentSubOrder.products" :key="item.id" class="goods-item">
|
<article v-for="item in currentSubOrder.products" :key="item.id" class="goods-item">
|
||||||
<van-image class="goods-thumb" :src="item.image" fit="cover" />
|
<van-image class="goods-thumb" :src="item.image" fit="cover" />
|
||||||
<div class="goods-info">
|
<div class="goods-info">
|
||||||
<h3>{{ item.name }}</h3>
|
<h3>{{ item.name }}</h3>
|
||||||
<p>SKU:{{ item.sku || '-' }}</p>
|
<p>{{ skuText(item) }}</p>
|
||||||
<b>¥{{ item.price }}</b>
|
<b>¥{{ item.price }}</b>
|
||||||
</div>
|
</div>
|
||||||
<span>x{{ item.number || 1 }}</span>
|
<span>x{{ item.number || 1 }}</span>
|
||||||
</article>
|
</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>
|
||||||
|
|
||||||
<section class="info-card">
|
<section class="info-card">
|
||||||
<p><span>订单编号</span><b>{{ currentSubOrder.orderNumberId || '-' }}</b></p>
|
<p><span>订单编号</span><b>{{ currentSubOrder.orderNumberId || currentSubOrder.orderId || '-' }}</b></p>
|
||||||
<p><span>供应商</span><b>{{ currentSubOrder.supplierId || '-' }}</b></p>
|
<p><span>下单时间</span><b>{{ currentSubOrder.createTime || currentOrder.createTime || '-' }}</b></p>
|
||||||
<p><span>商品数量</span><b>{{ currentSubOrder.count || 0 }}</b></p>
|
<p><span>支付方式</span><b>{{ payMethodText }}</b></p>
|
||||||
<p><span>订单金额</span><b class="price">¥{{ currentSubOrder.amount }}</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>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
@@ -38,9 +98,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapState } from 'pinia'
|
import { mapActions, mapState } from 'pinia'
|
||||||
import { Toast } from 'vant'
|
import { Dialog, Toast } from 'vant'
|
||||||
import { postFunction } from '@/api/supplied-service'
|
import { postFunction } from '@/api/supplied-service'
|
||||||
import { useUserStore } from '@/store/user'
|
import { useUserStore } from '@/store/user'
|
||||||
|
import { savePaymentInfo } from '../../order/payment/payment-data'
|
||||||
import JoyfulSelectionOrderDetailSkeleton from '@/skeleton-view/joyful-selection/profile/order-detail/index.vue'
|
import JoyfulSelectionOrderDetailSkeleton from '@/skeleton-view/joyful-selection/profile/order-detail/index.vue'
|
||||||
|
|
||||||
const ORDER_STATE_VIEW = {
|
const ORDER_STATE_VIEW = {
|
||||||
@@ -53,6 +114,22 @@ const ORDER_STATE_VIEW = {
|
|||||||
7: { text: '退货', statusClass: 'muted' }
|
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 {
|
export default {
|
||||||
name: 'JoyfulSelectionOrderDetail',
|
name: 'JoyfulSelectionOrderDetail',
|
||||||
components: {
|
components: {
|
||||||
@@ -60,11 +137,113 @@ export default {
|
|||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
loading: true,
|
loading: true,
|
||||||
|
operationKey: '',
|
||||||
currentOrder: null,
|
currentOrder: null,
|
||||||
currentSubOrder: null
|
currentSubOrder: null
|
||||||
}),
|
}),
|
||||||
computed: {
|
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() {
|
created() {
|
||||||
this.loadOrderDetail()
|
this.loadOrderDetail()
|
||||||
@@ -80,6 +259,169 @@ export default {
|
|||||||
statusClass: view.statusClass
|
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() {
|
async loadOrderDetail() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
@@ -90,15 +432,16 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await postFunction('order-list', {
|
|
||||||
supplierId: this.supplierId,
|
|
||||||
userId: this.userId
|
|
||||||
})
|
|
||||||
|
|
||||||
const mainOrderId = String(this.$route.query.mainOrderId || '')
|
const mainOrderId = String(this.$route.query.mainOrderId || '')
|
||||||
const orderId = String(this.$route.query.orderId || '')
|
const orderId = String(this.$route.query.orderId || '')
|
||||||
const order = (data?.orders || []).find(item => item.mainOrderId === mainOrderId)
|
const data = await postFunction('order-detail', {
|
||||||
const subOrder = order?.subOrders?.find(item => item.orderId === orderId)
|
supplierId: this.supplierId,
|
||||||
|
userId: this.userId,
|
||||||
|
mainOrderId,
|
||||||
|
orderId
|
||||||
|
})
|
||||||
|
const order = data?.order || null
|
||||||
|
const subOrder = data?.subOrder || null
|
||||||
|
|
||||||
this.currentOrder = order || null
|
this.currentOrder = order || null
|
||||||
this.currentSubOrder = order && subOrder ? this.normalizeSubOrder(order, subOrder) : null
|
this.currentSubOrder = order && subOrder ? this.normalizeSubOrder(order, subOrder) : null
|
||||||
@@ -115,8 +458,8 @@ export default {
|
|||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.detail-page {
|
.detail-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 46px 0 16px;
|
padding: 46px 9px 16px;
|
||||||
background: #f7f7f7;
|
background: #f7f8fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-page ::v-deep .van-nav-bar {
|
.detail-page ::v-deep .van-nav-bar {
|
||||||
@@ -130,66 +473,177 @@ export default {
|
|||||||
|
|
||||||
.status-card,
|
.status-card,
|
||||||
.goods-card,
|
.goods-card,
|
||||||
.info-card {
|
.address-card,
|
||||||
margin: 10px 12px 0;
|
.info-card,
|
||||||
|
.logistics-card {
|
||||||
|
margin: 10px 0 0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-card {
|
.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 {
|
.status-card h2 {
|
||||||
margin: 0 0 10px;
|
margin: 0;
|
||||||
color: #ff5b20;
|
color: #ff5b20;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-card p {
|
.status-card p {
|
||||||
margin: 4px 0 0;
|
margin: 5px 0 0;
|
||||||
color: #666;
|
color: #b69b8b;
|
||||||
font-size: 12px;
|
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 {
|
.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 {
|
.goods-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
min-height: 86px;
|
min-height: 78px;
|
||||||
padding: 12px 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods-item + .goods-item {
|
.goods-item + .goods-item {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-top: 12px;
|
||||||
border-top: 1px solid #f4f4f4;
|
border-top: 1px solid #f4f4f4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods-thumb {
|
.goods-thumb {
|
||||||
width: 64px;
|
width: 78px;
|
||||||
height: 64px;
|
height: 78px;
|
||||||
flex: none;
|
flex: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods-info {
|
.goods-info {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0 10px;
|
padding: 2px 10px 0 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods-info h3 {
|
.goods-info h3 {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0 0 6px;
|
margin: 0 0 8px;
|
||||||
color: #222;
|
color: #222;
|
||||||
font-size: 13px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 18px;
|
line-height: 20px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
@@ -198,8 +652,8 @@ export default {
|
|||||||
.goods-info p {
|
.goods-info p {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0 0 6px;
|
margin: 0 0 6px;
|
||||||
color: #999;
|
color: #8f8f8f;
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -207,15 +661,112 @@ export default {
|
|||||||
.goods-info b,
|
.goods-info b,
|
||||||
.price {
|
.price {
|
||||||
color: #ff5b20;
|
color: #ff5b20;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods-item > span {
|
.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;
|
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 {
|
.info-card {
|
||||||
padding: 12px 14px;
|
padding: 12px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-card p {
|
.info-card p {
|
||||||
@@ -225,7 +776,7 @@ export default {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
color: #777;
|
color: #777;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 30px;
|
line-height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-card b {
|
.info-card b {
|
||||||
@@ -237,4 +788,54 @@ export default {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
color="#ff5b20"
|
color="#ff5b20"
|
||||||
title-active-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-tabs>
|
||||||
|
|
||||||
<van-pull-refresh v-model="refreshing" class="order-list" @refresh="onRefresh">
|
<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 { Dialog, Toast } from 'vant'
|
||||||
import { postFunction } from '@/api/supplied-service'
|
import { postFunction } from '@/api/supplied-service'
|
||||||
import { useUserStore } from '@/store/user'
|
import { useUserStore } from '@/store/user'
|
||||||
|
import { savePaymentInfo } from '../../order/payment/payment-data'
|
||||||
import JoyfulSelectionOrdersSkeleton from '@/skeleton-view/joyful-selection/profile/orders/index.vue'
|
import JoyfulSelectionOrdersSkeleton from '@/skeleton-view/joyful-selection/profile/orders/index.vue'
|
||||||
|
|
||||||
const ORDER_STATE = {
|
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.FINISH_PAY },
|
||||||
{ label: '待收货', type: 'sub-state', state: ORDER_STATE.IN_PROGRESS },
|
{ label: '待收货', type: 'sub-state', state: ORDER_STATE.IN_PROGRESS },
|
||||||
{ label: '已取消', type: 'cancelled' },
|
{ label: '已取消', type: 'cancelled' },
|
||||||
{ label: '已完成', type: 'sub-state', state: ORDER_STATE.COMPLETED },
|
{ label: '已完成', type: 'sub-state', state: ORDER_STATE.COMPLETED }
|
||||||
{ label: '售后', type: 'after-sale' }
|
|
||||||
],
|
],
|
||||||
orderItems: [],
|
orderItems: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -184,10 +184,24 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
this.applyRouteTab()
|
||||||
this.loadOrders()
|
this.loadOrders()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useUserStore, ['fetchBridgeToken']),
|
...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 = []) {
|
previewProducts(products = []) {
|
||||||
return products.slice(0, 3)
|
return products.slice(0, 3)
|
||||||
},
|
},
|
||||||
@@ -212,7 +226,7 @@ export default {
|
|||||||
return color || size ? [color, size].filter(Boolean).join(' | ') : '-'
|
return color || size ? [color, size].filter(Boolean).join(' | ') : '-'
|
||||||
},
|
},
|
||||||
showLogistics(order) {
|
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) {
|
actionKey(type, orderId) {
|
||||||
return `${type}-${orderId}`
|
return `${type}-${orderId}`
|
||||||
@@ -220,13 +234,23 @@ export default {
|
|||||||
isOperating(type, orderId) {
|
isOperating(type, orderId) {
|
||||||
return this.operationKey === this.actionKey(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) {
|
subActions(subOrder) {
|
||||||
const state = Number(subOrder.orderState)
|
const state = Number(subOrder.orderState)
|
||||||
const actions = []
|
const actions = []
|
||||||
|
|
||||||
if ([ORDER_STATE.IN_PROGRESS, ORDER_STATE.COMPLETED].includes(state)) {
|
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' })
|
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 (state === ORDER_STATE.IN_PROGRESS) {
|
if (state === ORDER_STATE.IN_PROGRESS) {
|
||||||
@@ -289,13 +313,16 @@ export default {
|
|||||||
orderActions(order) {
|
orderActions(order) {
|
||||||
if (order.displayType === 'main') {
|
if (order.displayType === 'main') {
|
||||||
return Number(order.orderState) === ORDER_STATE.PENDING_PAY
|
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 => ({
|
return this.subActions(order).map(action => ({
|
||||||
...action,
|
...action,
|
||||||
key: action.orderType,
|
key: action.key || action.orderType,
|
||||||
orderId: order.orderId
|
orderId: order.orderId
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
@@ -305,12 +332,40 @@ export default {
|
|||||||
return
|
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(
|
this.submitSubOrderEvent(
|
||||||
{ mainOrderId: order.mainOrderId },
|
{ mainOrderId: order.mainOrderId },
|
||||||
order,
|
order,
|
||||||
action
|
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) {
|
async cancelMainOrder(order) {
|
||||||
try {
|
try {
|
||||||
await Dialog.confirm({
|
await Dialog.confirm({
|
||||||
@@ -346,6 +401,81 @@ export default {
|
|||||||
this.operationKey = ''
|
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) {
|
async submitSubOrderEvent(order, subOrder, action) {
|
||||||
try {
|
try {
|
||||||
await Dialog.confirm({
|
await Dialog.confirm({
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -88,6 +88,8 @@ model SupplierOrderMain {
|
|||||||
supplierId String
|
supplierId String
|
||||||
userId String?
|
userId String?
|
||||||
aplName String?
|
aplName String?
|
||||||
|
addressId String?
|
||||||
|
remark String? @db.Text
|
||||||
createTime DateTime?
|
createTime DateTime?
|
||||||
updateTime DateTime?
|
updateTime DateTime?
|
||||||
deliveryTime DateTime?
|
deliveryTime DateTime?
|
||||||
@@ -125,6 +127,8 @@ model SupplierOrderSub {
|
|||||||
token String?
|
token String?
|
||||||
userId String?
|
userId String?
|
||||||
aplName String?
|
aplName String?
|
||||||
|
addressId String?
|
||||||
|
remark String? @db.Text
|
||||||
createTime DateTime?
|
createTime DateTime?
|
||||||
updateTime DateTime?
|
updateTime DateTime?
|
||||||
deliveryTime DateTime?
|
deliveryTime DateTime?
|
||||||
@@ -175,6 +179,29 @@ model SupplierOrderProduct {
|
|||||||
@@map("supplier_order_product")
|
@@map("supplier_order_product")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model SupplierUserAddress {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
addressId String @unique
|
||||||
|
supplierId String
|
||||||
|
userId String
|
||||||
|
name String
|
||||||
|
tel String
|
||||||
|
province String?
|
||||||
|
city String?
|
||||||
|
county String?
|
||||||
|
addressDetail String? @db.Text
|
||||||
|
areaCode String?
|
||||||
|
postalCode String?
|
||||||
|
isDefault Boolean @default(false)
|
||||||
|
createTime DateTime @default(now())
|
||||||
|
updateTime DateTime @updatedAt
|
||||||
|
deleted Int @default(0)
|
||||||
|
|
||||||
|
@@index([supplierId, userId])
|
||||||
|
@@index([supplierId, userId, isDefault])
|
||||||
|
@@map("supplier_user_address")
|
||||||
|
}
|
||||||
|
|
||||||
model SupplierPointsLedger {
|
model SupplierPointsLedger {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
ledgerNo String @unique
|
ledgerNo String @unique
|
||||||
@@ -269,6 +296,43 @@ model SupplierRefundExchange {
|
|||||||
@@map("supplier_refund_exchange")
|
@@map("supplier_refund_exchange")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model SupplierAfterSale {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
serviceOrderId String @unique
|
||||||
|
supplierId String
|
||||||
|
userId String?
|
||||||
|
mainOrderId String?
|
||||||
|
subOrderId String?
|
||||||
|
serviceType Int
|
||||||
|
serviceState Int @default(0)
|
||||||
|
auditStatus Int @default(0)
|
||||||
|
logisticsState Int @default(0)
|
||||||
|
amount Decimal @default(0) @db.Decimal(10, 2)
|
||||||
|
reason String? @db.Text
|
||||||
|
auditRemark String? @db.Text
|
||||||
|
userLogisticsNo String?
|
||||||
|
userLogisticsCompany String?
|
||||||
|
merchantLogisticsNo String?
|
||||||
|
merchantLogisticsCompany String?
|
||||||
|
applyTime DateTime @default(now())
|
||||||
|
auditTime DateTime?
|
||||||
|
userShipTime DateTime?
|
||||||
|
merchantReceiveTime DateTime?
|
||||||
|
merchantShipTime DateTime?
|
||||||
|
finishTime DateTime?
|
||||||
|
requestBody Json?
|
||||||
|
createTime DateTime @default(now())
|
||||||
|
updateTime DateTime @updatedAt
|
||||||
|
deleted Int @default(0)
|
||||||
|
|
||||||
|
@@index([supplierId, mainOrderId])
|
||||||
|
@@index([supplierId, userId])
|
||||||
|
@@index([subOrderId])
|
||||||
|
@@index([serviceType, serviceState])
|
||||||
|
@@index([auditStatus, logisticsState])
|
||||||
|
@@map("supplier_after_sale")
|
||||||
|
}
|
||||||
|
|
||||||
model SupplierInvoiceInfo {
|
model SupplierInvoiceInfo {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
invoiceBizId String @unique
|
invoiceBizId String @unique
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
ALTER TABLE `supplier_order_main`
|
||||||
|
ADD COLUMN `addressId` VARCHAR(191) NULL COMMENT '收货地址ID' AFTER `aplName`,
|
||||||
|
ADD COLUMN `remark` TEXT NULL COMMENT '订单备注' AFTER `addressId`;
|
||||||
|
|
||||||
|
ALTER TABLE `supplier_order_sub`
|
||||||
|
ADD COLUMN `addressId` VARCHAR(191) NULL COMMENT '收货地址ID' AFTER `aplName`,
|
||||||
|
ADD COLUMN `remark` TEXT NULL COMMENT '订单备注' AFTER `addressId`;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `supplier_after_sale` (
|
||||||
|
`id` INT NOT NULL AUTO_INCREMENT COMMENT '供应商售后单主键ID',
|
||||||
|
`serviceOrderId` VARCHAR(191) NOT NULL COMMENT '售后业务单号',
|
||||||
|
`supplierId` VARCHAR(191) NOT NULL COMMENT '供应商ID',
|
||||||
|
`userId` VARCHAR(191) NULL COMMENT '用户ID',
|
||||||
|
`mainOrderId` VARCHAR(191) NULL COMMENT '关联主单ID',
|
||||||
|
`subOrderId` VARCHAR(191) NULL COMMENT '关联子单ID',
|
||||||
|
`serviceType` INT NOT NULL COMMENT '售后类型:4退货,5换货',
|
||||||
|
`serviceState` INT NOT NULL DEFAULT 0 COMMENT '售后状态:0商家待审核,1审核通过,2审核拒绝,3用户已寄回,4商家已收货,5已完成,6已取消',
|
||||||
|
`auditStatus` INT NOT NULL DEFAULT 0 COMMENT '审核状态:0待审核,1审核通过,2审核拒绝',
|
||||||
|
`logisticsState` INT NOT NULL DEFAULT 0 COMMENT '物流状态:0无物流,1待用户寄回,2用户已寄回,3商家已收货,4商家已发货,5用户已收货',
|
||||||
|
`amount` DECIMAL(10, 2) NOT NULL DEFAULT 0 COMMENT '售后金额',
|
||||||
|
`reason` TEXT NULL COMMENT '售后原因',
|
||||||
|
`auditRemark` TEXT NULL COMMENT '审核备注',
|
||||||
|
`userLogisticsNo` VARCHAR(191) NULL COMMENT '用户寄回物流单号',
|
||||||
|
`userLogisticsCompany` VARCHAR(191) NULL COMMENT '用户寄回物流公司',
|
||||||
|
`merchantLogisticsNo` VARCHAR(191) NULL COMMENT '商家换货物流单号',
|
||||||
|
`merchantLogisticsCompany` VARCHAR(191) NULL COMMENT '商家换货物流公司',
|
||||||
|
`applyTime` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '申请时间',
|
||||||
|
`auditTime` DATETIME(3) NULL COMMENT '审核时间',
|
||||||
|
`userShipTime` DATETIME(3) NULL COMMENT '用户寄回时间',
|
||||||
|
`merchantReceiveTime` DATETIME(3) NULL COMMENT '商家收货时间',
|
||||||
|
`merchantShipTime` DATETIME(3) NULL COMMENT '商家发货时间',
|
||||||
|
`finishTime` DATETIME(3) NULL COMMENT '完成时间',
|
||||||
|
`requestBody` JSON NULL COMMENT '原始业务请求体',
|
||||||
|
`createTime` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||||
|
`updateTime` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
|
||||||
|
`deleted` INT NOT NULL DEFAULT 0 COMMENT '删除状态:0未删除,1已删除',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `supplier_after_sale_serviceOrderId_key` (`serviceOrderId`),
|
||||||
|
KEY `supplier_after_sale_supplierId_mainOrderId_idx` (`supplierId`, `mainOrderId`),
|
||||||
|
KEY `supplier_after_sale_supplierId_userId_idx` (`supplierId`, `userId`),
|
||||||
|
KEY `supplier_after_sale_subOrderId_idx` (`subOrderId`),
|
||||||
|
KEY `supplier_after_sale_serviceType_serviceState_idx` (`serviceType`, `serviceState`),
|
||||||
|
KEY `supplier_after_sale_auditStatus_logisticsState_idx` (`auditStatus`, `logisticsState`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='供应商退款售后流程表';
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `supplier_user_address` (
|
||||||
|
`id` INT NOT NULL AUTO_INCREMENT COMMENT '地址主键ID',
|
||||||
|
`addressId` VARCHAR(191) NOT NULL COMMENT '地址业务ID',
|
||||||
|
`supplierId` VARCHAR(191) NOT NULL COMMENT '供应商ID',
|
||||||
|
`userId` VARCHAR(191) NOT NULL COMMENT '用户ID',
|
||||||
|
`name` VARCHAR(191) NOT NULL COMMENT '收货人姓名',
|
||||||
|
`tel` VARCHAR(191) NOT NULL COMMENT '收货人手机号',
|
||||||
|
`province` VARCHAR(191) NULL COMMENT '省',
|
||||||
|
`city` VARCHAR(191) NULL COMMENT '市',
|
||||||
|
`county` VARCHAR(191) NULL COMMENT '区县',
|
||||||
|
`addressDetail` TEXT NULL COMMENT '详细地址',
|
||||||
|
`areaCode` VARCHAR(191) NULL COMMENT '地区编码',
|
||||||
|
`postalCode` VARCHAR(191) NULL COMMENT '邮政编码',
|
||||||
|
`isDefault` BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否默认地址',
|
||||||
|
`createTime` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||||
|
`updateTime` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
|
||||||
|
`deleted` INT NOT NULL DEFAULT 0 COMMENT '删除状态:0未删除,1已删除',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `supplier_user_address_addressId_key` (`addressId`),
|
||||||
|
KEY `supplier_user_address_supplierId_userId_idx` (`supplierId`, `userId`),
|
||||||
|
KEY `supplier_user_address_supplierId_userId_isDefault_idx` (`supplierId`, `userId`, `isDefault`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='供应商用户收货地址表';
|
||||||
@@ -23,7 +23,8 @@ export const ORDER_EVENT_TYPE = {
|
|||||||
EXCHANGE_ORDER: 5,
|
EXCHANGE_ORDER: 5,
|
||||||
CONFIRM_RECEIVE: 6,
|
CONFIRM_RECEIVE: 6,
|
||||||
CANCEL_PAID_NOT_SPLIT_MAIN: 7,
|
CANCEL_PAID_NOT_SPLIT_MAIN: 7,
|
||||||
INVOICE_INFO: 11
|
INVOICE_INFO: 11,
|
||||||
|
SHIP_ORDER: 99
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const POINTS_LEDGER_BIZ_TYPE = {
|
export const POINTS_LEDGER_BIZ_TYPE = {
|
||||||
@@ -44,6 +45,25 @@ export const ORDER_PUSH_STATUS = {
|
|||||||
FAILED: 2
|
FAILED: 2
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
export const AFTER_SALE_SERVICE_STATE = {
|
||||||
|
PENDING_AUDIT: 0,
|
||||||
|
APPROVED: 1,
|
||||||
|
REJECTED: 2,
|
||||||
|
USER_SHIPPED: 3,
|
||||||
|
RECEIVED: 4,
|
||||||
|
FINISHED: 5,
|
||||||
|
CANCELLED: 6
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const AFTER_SALE_LOGISTICS_STATE = {
|
||||||
|
NONE: 0,
|
||||||
|
WAIT_USER_SHIP: 1,
|
||||||
|
USER_SHIPPED: 2,
|
||||||
|
MERCHANT_RECEIVED: 3,
|
||||||
|
MERCHANT_SHIPPED: 4,
|
||||||
|
USER_RECEIVED: 5
|
||||||
|
} as const
|
||||||
|
|
||||||
export const ORDER_OPERATE_TYPE = {
|
export const ORDER_OPERATE_TYPE = {
|
||||||
CREATE_PENDING: 1,
|
CREATE_PENDING: 1,
|
||||||
PAY_SUCCESS: 2,
|
PAY_SUCCESS: 2,
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import type { Context } from '../utils/handler.js'
|
||||||
|
import { guardMethod } from '../utils/method.js'
|
||||||
|
import { readJson } from '../utils/request.js'
|
||||||
|
import { withErrorBoundary } from '../utils/handler.js'
|
||||||
|
import {
|
||||||
|
addressService,
|
||||||
|
type AddressDefaultInput,
|
||||||
|
type AddressDeleteInput,
|
||||||
|
type AddressListInput,
|
||||||
|
type AddressSaveInput
|
||||||
|
} from '../services/address.service.js'
|
||||||
|
import { toHttpResponse } from './controller-response.js'
|
||||||
|
|
||||||
|
export const addressListController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
return toHttpResponse(await addressService.list(await readJson<AddressListInput>(request)))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const addressDefaultController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
return toHttpResponse(await addressService.default(await readJson<AddressDefaultInput>(request)))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const addressSaveController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
return toHttpResponse(await addressService.save(await readJson<AddressSaveInput>(request)))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const addressDeleteController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
return toHttpResponse(await addressService.delete(await readJson<AddressDeleteInput>(request)))
|
||||||
|
})
|
||||||
@@ -2,6 +2,13 @@ import type { Context } from '../utils/handler.js'
|
|||||||
import { guardMethod } from '../utils/method.js'
|
import { guardMethod } from '../utils/method.js'
|
||||||
import { readJson } from '../utils/request.js'
|
import { readJson } from '../utils/request.js'
|
||||||
import { withErrorBoundary } from '../utils/handler.js'
|
import { withErrorBoundary } from '../utils/handler.js'
|
||||||
|
import {
|
||||||
|
afterSaleService,
|
||||||
|
type AfterSaleApplyInput,
|
||||||
|
type AfterSaleAuditInput,
|
||||||
|
type AfterSaleListInput,
|
||||||
|
type AfterSaleLogisticsInput
|
||||||
|
} from '../services/after-sale.service.js'
|
||||||
import { orderActionService } from '../services/order-action.service.js'
|
import { orderActionService } from '../services/order-action.service.js'
|
||||||
import { orderCreateService, type CheckoutGoods } from '../services/order-create.service.js'
|
import { orderCreateService, type CheckoutGoods } from '../services/order-create.service.js'
|
||||||
import { orderListService } from '../services/order-list.service.js'
|
import { orderListService } from '../services/order-list.service.js'
|
||||||
@@ -12,6 +19,8 @@ import { toHttpResponse } from './controller-response.js'
|
|||||||
type OrderListRequest = {
|
type OrderListRequest = {
|
||||||
supplierId?: string
|
supplierId?: string
|
||||||
userId?: string
|
userId?: string
|
||||||
|
orderId?: string
|
||||||
|
mainOrderId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderActionRequest = {
|
type OrderActionRequest = {
|
||||||
@@ -19,6 +28,9 @@ type OrderActionRequest = {
|
|||||||
userId?: string
|
userId?: string
|
||||||
orderId?: string
|
orderId?: string
|
||||||
mainOrderId?: string
|
mainOrderId?: string
|
||||||
|
deliveryStatus?: number
|
||||||
|
logisticsOrderId?: string
|
||||||
|
crrgBsnNm?: string
|
||||||
amount?: number
|
amount?: number
|
||||||
totalPrice?: number
|
totalPrice?: number
|
||||||
wPayOrAPay?: number
|
wPayOrAPay?: number
|
||||||
@@ -31,6 +43,8 @@ type OrderCreateRequest = {
|
|||||||
orderNo?: string
|
orderNo?: string
|
||||||
orderNumberId?: string
|
orderNumberId?: string
|
||||||
amount?: number
|
amount?: number
|
||||||
|
addressId?: string
|
||||||
|
remark?: string
|
||||||
goods?: CheckoutGoods[]
|
goods?: CheckoutGoods[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +61,58 @@ export const orderListController = withErrorBoundary(async (request: Request, _c
|
|||||||
return toHttpResponse(await orderListService.listOrders(supplierId, userId))
|
return toHttpResponse(await orderListService.listOrders(supplierId, userId))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const orderCountController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const body = await readJson<OrderListRequest>(request)
|
||||||
|
const supplierId = String(body.supplierId || '').trim()
|
||||||
|
const userId = String(body.userId || '').trim()
|
||||||
|
|
||||||
|
return toHttpResponse(await orderListService.countOrders(supplierId, userId))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const orderDetailController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const body = await readJson<OrderListRequest>(request)
|
||||||
|
const supplierId = String(body.supplierId || '').trim()
|
||||||
|
const userId = String(body.userId || '').trim()
|
||||||
|
const orderId = String(body.orderId || '').trim()
|
||||||
|
const mainOrderId = String(body.mainOrderId || '').trim()
|
||||||
|
|
||||||
|
return toHttpResponse(await orderListService.detailOrder(supplierId, userId, orderId, mainOrderId))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const afterSaleApplyController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
return toHttpResponse(await afterSaleService.apply(await readJson<AfterSaleApplyInput>(request)))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const afterSaleListController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
return toHttpResponse(await afterSaleService.list(await readJson<AfterSaleListInput>(request)))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const afterSaleAuditController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
return toHttpResponse(await afterSaleService.audit(await readJson<AfterSaleAuditInput>(request)))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const afterSaleLogisticsController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
return toHttpResponse(await afterSaleService.updateLogistics(await readJson<AfterSaleLogisticsInput>(request)))
|
||||||
|
})
|
||||||
|
|
||||||
export const orderCreateController = withErrorBoundary(async (request: Request, _context: Context) => {
|
export const orderCreateController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
if (methodResponse) return methodResponse
|
if (methodResponse) return methodResponse
|
||||||
@@ -59,6 +125,8 @@ export const orderCreateController = withErrorBoundary(async (request: Request,
|
|||||||
orderNo: String(body.orderNo || '').trim(),
|
orderNo: String(body.orderNo || '').trim(),
|
||||||
orderNumberId: body.orderNumberId,
|
orderNumberId: body.orderNumberId,
|
||||||
amount: body.amount,
|
amount: body.amount,
|
||||||
|
addressId: String(body.addressId || '').trim(),
|
||||||
|
remark: String(body.remark || '').trim(),
|
||||||
goods: Array.isArray(body.goods) ? body.goods : []
|
goods: Array.isArray(body.goods) ? body.goods : []
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
@@ -121,6 +189,22 @@ export const orderPayController = withErrorBoundary(async (request: Request, _co
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const orderShipController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const body = await readJson<OrderActionRequest>(request)
|
||||||
|
|
||||||
|
return toHttpResponse(await orderActionService.shipOrder({
|
||||||
|
supplierId: String(body.supplierId || '').trim(),
|
||||||
|
userId: String(body.userId || '').trim(),
|
||||||
|
orderId: String(body.orderId || body.mainOrderId || '').trim(),
|
||||||
|
deliveryStatus: Number(body.deliveryStatus || 1),
|
||||||
|
logisticsOrderId: String(body.logisticsOrderId || '').trim(),
|
||||||
|
crrgBsnNm: String(body.crrgBsnNm || '').trim()
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
export const orderPushEventController = withErrorBoundary(async (request: Request, _context: Context) => {
|
export const orderPushEventController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
if (methodResponse) return methodResponse
|
if (methodResponse) return methodResponse
|
||||||
|
|||||||
@@ -37,6 +37,32 @@ export const orderDao = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
findMainOrderWithSubs(supplierId: string, userId: string, mainOrderId: string) {
|
||||||
|
return prisma.supplierOrderMain.findFirst({
|
||||||
|
where: {
|
||||||
|
orderId: mainOrderId,
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
deleted: 0
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
subOrders: {
|
||||||
|
where: { deleted: 0 },
|
||||||
|
include: {
|
||||||
|
products: {
|
||||||
|
where: { deleted: 0 },
|
||||||
|
orderBy: { createAt: 'asc' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
orderBy: [
|
||||||
|
{ createTime: 'desc' },
|
||||||
|
{ createAt: 'desc' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
findMainOrder(supplierId: string, userId: string, mainOrderId: string) {
|
findMainOrder(supplierId: string, userId: string, mainOrderId: string) {
|
||||||
return prisma.supplierOrderMain.findFirst({
|
return prisma.supplierOrderMain.findFirst({
|
||||||
where: {
|
where: {
|
||||||
@@ -59,6 +85,23 @@ export const orderDao = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
findSubOrderWithProducts(supplierId: string, userId: string, orderId: string) {
|
||||||
|
return prisma.supplierOrderSub.findFirst({
|
||||||
|
where: {
|
||||||
|
orderId,
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
deleted: 0
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
products: {
|
||||||
|
where: { deleted: 0 },
|
||||||
|
orderBy: { createAt: 'asc' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
findPayingMainOrder(orderId: string) {
|
findPayingMainOrder(orderId: string) {
|
||||||
return prisma.supplierOrderMain.findUnique({
|
return prisma.supplierOrderMain.findUnique({
|
||||||
where: { orderId }
|
where: { orderId }
|
||||||
@@ -94,6 +137,71 @@ export const orderDao = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
findUserAddresses(supplierId: string, userId: string) {
|
||||||
|
return prisma.supplierUserAddress.findMany({
|
||||||
|
where: { supplierId, userId, deleted: 0 },
|
||||||
|
orderBy: [
|
||||||
|
{ isDefault: 'desc' },
|
||||||
|
{ updateTime: 'desc' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async findDefaultUserAddress(supplierId: string, userId: string) {
|
||||||
|
const addresses = await prisma.supplierUserAddress.findMany({
|
||||||
|
where: { supplierId, userId, deleted: 0 },
|
||||||
|
orderBy: [
|
||||||
|
{ isDefault: 'desc' },
|
||||||
|
{ updateTime: 'desc' }
|
||||||
|
],
|
||||||
|
take: 2
|
||||||
|
})
|
||||||
|
|
||||||
|
return addresses[0] || null
|
||||||
|
},
|
||||||
|
|
||||||
|
findUserAddress(supplierId: string, userId: string, addressId: string) {
|
||||||
|
return prisma.supplierUserAddress.findFirst({
|
||||||
|
where: { supplierId, userId, addressId, deleted: 0 }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
findUserAddressById(addressId: string) {
|
||||||
|
return prisma.supplierUserAddress.findFirst({
|
||||||
|
where: { addressId, deleted: 0 }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
countMainOrders(where: Prisma.SupplierOrderMainWhereInput) {
|
||||||
|
return prisma.supplierOrderMain.count({ where })
|
||||||
|
},
|
||||||
|
|
||||||
|
groupSubOrdersByState(where: Prisma.SupplierOrderSubWhereInput) {
|
||||||
|
return prisma.supplierOrderSub.groupBy({
|
||||||
|
by: ['orderState'],
|
||||||
|
where,
|
||||||
|
_count: { _all: true }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
findAfterSale(where: Prisma.SupplierAfterSaleWhereInput) {
|
||||||
|
return prisma.supplierAfterSale.findFirst({ where })
|
||||||
|
},
|
||||||
|
|
||||||
|
findAfterSales(where: Prisma.SupplierAfterSaleWhereInput) {
|
||||||
|
return prisma.supplierAfterSale.findMany({
|
||||||
|
where,
|
||||||
|
orderBy: [
|
||||||
|
{ createTime: 'desc' },
|
||||||
|
{ id: 'desc' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
countAfterSales(where: Prisma.SupplierAfterSaleWhereInput) {
|
||||||
|
return prisma.supplierAfterSale.count({ where })
|
||||||
|
},
|
||||||
|
|
||||||
updatePushLog(id: number, data: Parameters<typeof prisma.supplierOrderPushLog.update>[0]['data']) {
|
updatePushLog(id: number, data: Parameters<typeof prisma.supplierOrderPushLog.update>[0]['data']) {
|
||||||
return prisma.supplierOrderPushLog.update({
|
return prisma.supplierOrderPushLog.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { addressDefaultController as default } from '../controllers/address.controller.js'
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { addressDeleteController as default } from '../controllers/address.controller.js'
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { addressListController as default } from '../controllers/address.controller.js'
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { addressSaveController as default } from '../controllers/address.controller.js'
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { afterSaleApplyController as default } from '../controllers/order.controller.js'
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { afterSaleAuditController as default } from '../controllers/order.controller.js'
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { afterSaleListController as default } from '../controllers/order.controller.js'
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { afterSaleLogisticsController as default } from '../controllers/order.controller.js'
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { orderDetailController as default } from '../controllers/order.controller.js'
|
||||||
@@ -4,6 +4,12 @@ import Router from '@koa/router'
|
|||||||
import bodyParser from 'koa-bodyparser'
|
import bodyParser from 'koa-bodyparser'
|
||||||
import type { FunctionHandler } from './utils/handler.js'
|
import type { FunctionHandler } from './utils/handler.js'
|
||||||
import { logger } from './utils/logger.js'
|
import { logger } from './utils/logger.js'
|
||||||
|
import {
|
||||||
|
addressDefaultController,
|
||||||
|
addressDeleteController,
|
||||||
|
addressListController,
|
||||||
|
addressSaveController
|
||||||
|
} from './controllers/address.controller.js'
|
||||||
import {
|
import {
|
||||||
cartAddController,
|
cartAddController,
|
||||||
loginController,
|
loginController,
|
||||||
@@ -11,13 +17,20 @@ import {
|
|||||||
userInfoController
|
userInfoController
|
||||||
} from './controllers/basic.controller.js'
|
} from './controllers/basic.controller.js'
|
||||||
import {
|
import {
|
||||||
|
afterSaleApplyController,
|
||||||
|
afterSaleAuditController,
|
||||||
|
afterSaleListController,
|
||||||
|
afterSaleLogisticsController,
|
||||||
orderCancelMainController,
|
orderCancelMainController,
|
||||||
orderCancelPaidMainController,
|
orderCancelPaidMainController,
|
||||||
orderCancelSubController,
|
orderCancelSubController,
|
||||||
|
orderCountController,
|
||||||
orderCreateController,
|
orderCreateController,
|
||||||
|
orderDetailController,
|
||||||
orderListController,
|
orderListController,
|
||||||
orderPayController,
|
orderPayController,
|
||||||
orderPushEventController,
|
orderPushEventController,
|
||||||
|
orderShipController,
|
||||||
supplierOrderInfoController
|
supplierOrderInfoController
|
||||||
} from './controllers/order.controller.js'
|
} from './controllers/order.controller.js'
|
||||||
import { productDetailController, productListController } from './controllers/product.controller.js'
|
import { productDetailController, productListController } from './controllers/product.controller.js'
|
||||||
@@ -33,15 +46,26 @@ const router = new Router()
|
|||||||
const port = Number(process.env.PORT || 8888)
|
const port = Number(process.env.PORT || 8888)
|
||||||
|
|
||||||
const handlers: Record<string, FunctionHandler> = {
|
const handlers: Record<string, FunctionHandler> = {
|
||||||
|
'address-default': addressDefaultController,
|
||||||
|
'address-delete': addressDeleteController,
|
||||||
|
'address-list': addressListController,
|
||||||
|
'address-save': addressSaveController,
|
||||||
|
'after-sale-apply': afterSaleApplyController,
|
||||||
|
'after-sale-audit': afterSaleAuditController,
|
||||||
|
'after-sale-list': afterSaleListController,
|
||||||
|
'after-sale-logistics': afterSaleLogisticsController,
|
||||||
'cart-add': cartAddController,
|
'cart-add': cartAddController,
|
||||||
login: loginController,
|
login: loginController,
|
||||||
'order-cancel-main': orderCancelMainController,
|
'order-cancel-main': orderCancelMainController,
|
||||||
'order-cancel-paid-main': orderCancelPaidMainController,
|
'order-cancel-paid-main': orderCancelPaidMainController,
|
||||||
'order-cancel-sub': orderCancelSubController,
|
'order-cancel-sub': orderCancelSubController,
|
||||||
|
'order-count': orderCountController,
|
||||||
'order-create': orderCreateController,
|
'order-create': orderCreateController,
|
||||||
|
'order-detail': orderDetailController,
|
||||||
'order-list': orderListController,
|
'order-list': orderListController,
|
||||||
'order-pay': orderPayController,
|
'order-pay': orderPayController,
|
||||||
'order-push-event': orderPushEventController,
|
'order-push-event': orderPushEventController,
|
||||||
|
'order-ship': orderShipController,
|
||||||
'product-detail': productDetailController,
|
'product-detail': productDetailController,
|
||||||
'product-list': productListController,
|
'product-list': productListController,
|
||||||
'supplier-debug-tokens': supplierDebugTokensController,
|
'supplier-debug-tokens': supplierDebugTokensController,
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
import { orderDao } from '../dao/order.dao.js'
|
||||||
|
import { serviceFail, serviceOk } from '../types/service-result.js'
|
||||||
|
|
||||||
|
export type AddressListInput = {
|
||||||
|
supplierId?: string
|
||||||
|
userId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AddressDefaultInput = AddressListInput
|
||||||
|
|
||||||
|
export type AddressSaveInput = AddressListInput & {
|
||||||
|
id?: string
|
||||||
|
addressId?: string
|
||||||
|
name?: string
|
||||||
|
tel?: string
|
||||||
|
province?: string
|
||||||
|
city?: string
|
||||||
|
county?: string
|
||||||
|
addressDetail?: string
|
||||||
|
areaCode?: string
|
||||||
|
postalCode?: string
|
||||||
|
isDefault?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AddressDeleteInput = AddressListInput & {
|
||||||
|
id?: string
|
||||||
|
addressId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const createAddressId = () => `addr-${Date.now()}`
|
||||||
|
|
||||||
|
const toAddressView = (address: {
|
||||||
|
addressId: string
|
||||||
|
name: string
|
||||||
|
tel: string
|
||||||
|
province: string | null
|
||||||
|
city: string | null
|
||||||
|
county: string | null
|
||||||
|
addressDetail: string | null
|
||||||
|
areaCode: string | null
|
||||||
|
postalCode: string | null
|
||||||
|
isDefault: boolean
|
||||||
|
}) => ({
|
||||||
|
id: address.addressId,
|
||||||
|
addressId: address.addressId,
|
||||||
|
name: address.name,
|
||||||
|
tel: address.tel,
|
||||||
|
province: address.province || '',
|
||||||
|
city: address.city || '',
|
||||||
|
county: address.county || '',
|
||||||
|
addressDetail: address.addressDetail || '',
|
||||||
|
areaCode: address.areaCode || '',
|
||||||
|
postalCode: address.postalCode || '',
|
||||||
|
isDefault: address.isDefault
|
||||||
|
})
|
||||||
|
|
||||||
|
export const addressService = {
|
||||||
|
async list(input: AddressListInput) {
|
||||||
|
const supplierId = String(input.supplierId || '').trim()
|
||||||
|
const userId = String(input.userId || '').trim()
|
||||||
|
|
||||||
|
if (!supplierId || !userId) {
|
||||||
|
return serviceFail('供应商或用户信息不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const addresses = await orderDao.findUserAddresses(supplierId, userId)
|
||||||
|
return serviceOk({ addresses: addresses.map(toAddressView) })
|
||||||
|
},
|
||||||
|
|
||||||
|
async default(input: AddressDefaultInput) {
|
||||||
|
const supplierId = String(input.supplierId || '').trim()
|
||||||
|
const userId = String(input.userId || '').trim()
|
||||||
|
|
||||||
|
if (!supplierId || !userId) {
|
||||||
|
return serviceFail('渚涘簲鍟嗘垨鐢ㄦ埛淇℃伅涓嶈兘涓虹┖')
|
||||||
|
}
|
||||||
|
|
||||||
|
const address = await orderDao.findDefaultUserAddress(supplierId, userId)
|
||||||
|
return serviceOk({ address: address ? toAddressView(address) : null })
|
||||||
|
},
|
||||||
|
|
||||||
|
async save(input: AddressSaveInput) {
|
||||||
|
const supplierId = String(input.supplierId || '').trim()
|
||||||
|
const userId = String(input.userId || '').trim()
|
||||||
|
const addressId = String(input.addressId || input.id || createAddressId()).trim()
|
||||||
|
const name = String(input.name || '').trim()
|
||||||
|
const tel = String(input.tel || '').trim()
|
||||||
|
const isDefault = Boolean(input.isDefault)
|
||||||
|
|
||||||
|
if (!supplierId || !userId || !addressId || !name || !tel) {
|
||||||
|
return serviceFail('供应商、用户、收货人或手机号不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingAddress = await orderDao.findUserAddressById(addressId)
|
||||||
|
if (existingAddress && (existingAddress.supplierId !== supplierId || existingAddress.userId !== userId)) {
|
||||||
|
return serviceFail('地址不存在或无权操作', 404, 'ADDRESS_NOT_FOUND')
|
||||||
|
}
|
||||||
|
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
const count = await tx.supplierUserAddress.count({
|
||||||
|
where: { supplierId, userId, deleted: 0 }
|
||||||
|
})
|
||||||
|
const nextIsDefault = isDefault || count === 0
|
||||||
|
|
||||||
|
if (nextIsDefault) {
|
||||||
|
await tx.supplierUserAddress.updateMany({
|
||||||
|
where: { supplierId, userId, deleted: 0, addressId: { not: addressId } },
|
||||||
|
data: { isDefault: false }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx.supplierUserAddress.upsert({
|
||||||
|
where: { addressId },
|
||||||
|
update: {
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
name,
|
||||||
|
tel,
|
||||||
|
province: input.province || null,
|
||||||
|
city: input.city || null,
|
||||||
|
county: input.county || null,
|
||||||
|
addressDetail: input.addressDetail || null,
|
||||||
|
areaCode: input.areaCode || null,
|
||||||
|
postalCode: input.postalCode || null,
|
||||||
|
isDefault: nextIsDefault,
|
||||||
|
deleted: 0
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
addressId,
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
name,
|
||||||
|
tel,
|
||||||
|
province: input.province || null,
|
||||||
|
city: input.city || null,
|
||||||
|
county: input.county || null,
|
||||||
|
addressDetail: input.addressDetail || null,
|
||||||
|
areaCode: input.areaCode || null,
|
||||||
|
postalCode: input.postalCode || null,
|
||||||
|
isDefault: nextIsDefault
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const address = await orderDao.findUserAddress(supplierId, userId, addressId)
|
||||||
|
return serviceOk({ address: address ? toAddressView(address) : null })
|
||||||
|
},
|
||||||
|
|
||||||
|
async delete(input: AddressDeleteInput) {
|
||||||
|
const supplierId = String(input.supplierId || '').trim()
|
||||||
|
const userId = String(input.userId || '').trim()
|
||||||
|
const addressId = String(input.addressId || input.id || '').trim()
|
||||||
|
|
||||||
|
if (!supplierId || !userId || !addressId) {
|
||||||
|
return serviceFail('供应商、用户或地址不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
await tx.supplierUserAddress.updateMany({
|
||||||
|
where: { supplierId, userId, addressId, deleted: 0 },
|
||||||
|
data: { deleted: 1, isDefault: false }
|
||||||
|
})
|
||||||
|
|
||||||
|
const defaultCount = await tx.supplierUserAddress.count({
|
||||||
|
where: { supplierId, userId, deleted: 0, isDefault: true }
|
||||||
|
})
|
||||||
|
if (defaultCount === 0) {
|
||||||
|
const firstAddress = await tx.supplierUserAddress.findFirst({
|
||||||
|
where: { supplierId, userId, deleted: 0 },
|
||||||
|
orderBy: { updateTime: 'desc' }
|
||||||
|
})
|
||||||
|
if (firstAddress) {
|
||||||
|
await tx.supplierUserAddress.update({
|
||||||
|
where: { addressId: firstAddress.addressId },
|
||||||
|
data: { isDefault: true }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return serviceOk({ addressId })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
import {
|
||||||
|
AFTER_SALE_LOGISTICS_STATE,
|
||||||
|
AFTER_SALE_SERVICE_STATE,
|
||||||
|
ORDER_EVENT_TYPE,
|
||||||
|
ORDER_STATE
|
||||||
|
} from '../constants/order.js'
|
||||||
|
import { orderDao } from '../dao/order.dao.js'
|
||||||
|
import { logger } from '../utils/logger.js'
|
||||||
|
import { serviceFail, serviceOk } from '../types/service-result.js'
|
||||||
|
|
||||||
|
export type AfterSaleApplyInput = {
|
||||||
|
supplierId?: string
|
||||||
|
userId?: string
|
||||||
|
mainOrderId?: string
|
||||||
|
subOrderId?: string
|
||||||
|
orderId?: string
|
||||||
|
serviceType?: number
|
||||||
|
amount?: number
|
||||||
|
reason?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AfterSaleListInput = {
|
||||||
|
supplierId?: string
|
||||||
|
userId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AfterSaleAuditInput = {
|
||||||
|
supplierId?: string
|
||||||
|
serviceOrderId?: string
|
||||||
|
auditStatus?: number
|
||||||
|
auditRemark?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AfterSaleLogisticsInput = {
|
||||||
|
supplierId?: string
|
||||||
|
serviceOrderId?: string
|
||||||
|
logisticsState?: number
|
||||||
|
userLogisticsNo?: string
|
||||||
|
userLogisticsCompany?: string
|
||||||
|
merchantLogisticsNo?: string
|
||||||
|
merchantLogisticsCompany?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeAfterSaleStates = [
|
||||||
|
AFTER_SALE_SERVICE_STATE.PENDING_AUDIT,
|
||||||
|
AFTER_SALE_SERVICE_STATE.APPROVED,
|
||||||
|
AFTER_SALE_SERVICE_STATE.USER_SHIPPED,
|
||||||
|
AFTER_SALE_SERVICE_STATE.RECEIVED
|
||||||
|
]
|
||||||
|
const supportedServiceTypes = new Set<number>([
|
||||||
|
ORDER_EVENT_TYPE.RETURN_ORDER,
|
||||||
|
ORDER_EVENT_TYPE.EXCHANGE_ORDER
|
||||||
|
])
|
||||||
|
|
||||||
|
const afterSaleOrderStates = new Set<number>([
|
||||||
|
ORDER_STATE.FINISH_PAY,
|
||||||
|
ORDER_STATE.IN_PROGRESS,
|
||||||
|
ORDER_STATE.COMPLETED
|
||||||
|
])
|
||||||
|
|
||||||
|
const serviceTypeText = (serviceType: number) => {
|
||||||
|
if (serviceType === ORDER_EVENT_TYPE.RETURN_ORDER) return '退货'
|
||||||
|
if (serviceType === ORDER_EVENT_TYPE.EXCHANGE_ORDER) return '换货'
|
||||||
|
return '售后'
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceStateText = (serviceState: number) => {
|
||||||
|
if (serviceState === AFTER_SALE_SERVICE_STATE.PENDING_AUDIT) return '商家待审核'
|
||||||
|
if (serviceState === AFTER_SALE_SERVICE_STATE.APPROVED) return '审核通过'
|
||||||
|
if (serviceState === AFTER_SALE_SERVICE_STATE.REJECTED) return '审核拒绝'
|
||||||
|
if (serviceState === AFTER_SALE_SERVICE_STATE.USER_SHIPPED) return '用户已寄回'
|
||||||
|
if (serviceState === AFTER_SALE_SERVICE_STATE.RECEIVED) return '商家已收货'
|
||||||
|
if (serviceState === AFTER_SALE_SERVICE_STATE.FINISHED) return '已完成'
|
||||||
|
if (serviceState === AFTER_SALE_SERVICE_STATE.CANCELLED) return '已取消'
|
||||||
|
return '售后处理中'
|
||||||
|
}
|
||||||
|
|
||||||
|
const logisticsStateText = (logisticsState: number) => {
|
||||||
|
if (logisticsState === AFTER_SALE_LOGISTICS_STATE.WAIT_USER_SHIP) return '待用户寄回'
|
||||||
|
if (logisticsState === AFTER_SALE_LOGISTICS_STATE.USER_SHIPPED) return '用户已寄回'
|
||||||
|
if (logisticsState === AFTER_SALE_LOGISTICS_STATE.MERCHANT_RECEIVED) return '商家已收货'
|
||||||
|
if (logisticsState === AFTER_SALE_LOGISTICS_STATE.MERCHANT_SHIPPED) return '商家已发货'
|
||||||
|
if (logisticsState === AFTER_SALE_LOGISTICS_STATE.USER_RECEIVED) return '用户已收货'
|
||||||
|
return '暂无物流'
|
||||||
|
}
|
||||||
|
|
||||||
|
const toYuanText = (value: { toFixed: (digits: number) => string } | number | string | null | undefined) => {
|
||||||
|
if (value && typeof value === 'object' && 'toFixed' in value) {
|
||||||
|
return value.toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(value || 0).toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstImage = (picInfo: unknown) => {
|
||||||
|
if (Array.isArray(picInfo)) {
|
||||||
|
return String(picInfo[0] || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const toDateTimeText = (date?: Date | null) => {
|
||||||
|
if (!date) return ''
|
||||||
|
const pad = (value: number) => String(value).padStart(2, '0')
|
||||||
|
|
||||||
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const createServiceOrderId = (subOrderId: string, serviceType: number) => {
|
||||||
|
return `AS${serviceType}_${subOrderId}_${Date.now()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const afterSaleService = {
|
||||||
|
async apply(input: AfterSaleApplyInput) {
|
||||||
|
const supplierId = String(input.supplierId || '').trim()
|
||||||
|
const userId = String(input.userId || '').trim()
|
||||||
|
const subOrderId = String(input.subOrderId || input.orderId || '').trim()
|
||||||
|
const serviceType = Number(input.serviceType)
|
||||||
|
const reason = String(input.reason || '').trim()
|
||||||
|
logger.info('after sale apply', { supplierId, userId, subOrderId, serviceType })
|
||||||
|
|
||||||
|
if (!supplierId || !userId || !subOrderId || !serviceType) {
|
||||||
|
return serviceFail('供应商、用户、订单或售后类型不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supportedServiceTypes.has(serviceType)) {
|
||||||
|
return serviceFail('售后类型只支持退货或换货', 400, 'AFTER_SALE_TYPE_NOT_SUPPORTED')
|
||||||
|
}
|
||||||
|
|
||||||
|
const subOrder = await orderDao.findSubOrderWithProducts(supplierId, userId, subOrderId)
|
||||||
|
if (!subOrder) {
|
||||||
|
return serviceFail('子订单不存在或已取消', 404, 'SUB_ORDER_NOT_FOUND')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!afterSaleOrderStates.has(subOrder.orderState)) {
|
||||||
|
return serviceFail('当前订单状态不能申请退款/售后', 400, 'ORDER_NOT_ALLOW_AFTER_SALE')
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeAfterSale = await orderDao.findAfterSale({
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
subOrderId,
|
||||||
|
deleted: 0,
|
||||||
|
serviceState: { in: activeAfterSaleStates }
|
||||||
|
})
|
||||||
|
if (activeAfterSale) {
|
||||||
|
return serviceFail('该订单已有处理中的退款/售后申请', 400, 'AFTER_SALE_ALREADY_EXISTS')
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const amount = Number(input.amount || subOrder.totalPrice || 0)
|
||||||
|
const serviceOrderId = createServiceOrderId(subOrderId, serviceType)
|
||||||
|
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
await tx.supplierAfterSale.create({
|
||||||
|
data: {
|
||||||
|
serviceOrderId,
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
mainOrderId: input.mainOrderId || subOrder.mainOrderId,
|
||||||
|
subOrderId,
|
||||||
|
serviceType,
|
||||||
|
serviceState: AFTER_SALE_SERVICE_STATE.PENDING_AUDIT,
|
||||||
|
auditStatus: 0,
|
||||||
|
logisticsState: AFTER_SALE_LOGISTICS_STATE.NONE,
|
||||||
|
amount,
|
||||||
|
reason: reason || null,
|
||||||
|
applyTime: now,
|
||||||
|
requestBody: input
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await tx.supplierOrderOperateLog.create({
|
||||||
|
data: {
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
mainOrderId: subOrder.mainOrderId,
|
||||||
|
subOrderId,
|
||||||
|
operateType: serviceType,
|
||||||
|
beforeState: subOrder.orderState,
|
||||||
|
afterState: subOrder.orderState,
|
||||||
|
remark: reason || serviceTypeText(serviceType),
|
||||||
|
operator: 'user'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return serviceOk({
|
||||||
|
serviceOrderId,
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
mainOrderId: input.mainOrderId || subOrder.mainOrderId,
|
||||||
|
subOrderId,
|
||||||
|
serviceType,
|
||||||
|
serviceTypeText: serviceTypeText(serviceType),
|
||||||
|
serviceState: AFTER_SALE_SERVICE_STATE.PENDING_AUDIT,
|
||||||
|
serviceStateText: serviceStateText(AFTER_SALE_SERVICE_STATE.PENDING_AUDIT),
|
||||||
|
logisticsState: AFTER_SALE_LOGISTICS_STATE.NONE,
|
||||||
|
logisticsStateText: logisticsStateText(AFTER_SALE_LOGISTICS_STATE.NONE)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async list(input: AfterSaleListInput) {
|
||||||
|
const supplierId = String(input.supplierId || '').trim()
|
||||||
|
const userId = String(input.userId || '').trim()
|
||||||
|
logger.info('after sale list query', { supplierId, userId })
|
||||||
|
|
||||||
|
if (!supplierId || !userId) {
|
||||||
|
return serviceFail('供应商或用户信息不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const records = await orderDao.findAfterSales({
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
deleted: 0,
|
||||||
|
serviceType: { in: [ORDER_EVENT_TYPE.RETURN_ORDER, ORDER_EVENT_TYPE.EXCHANGE_ORDER] }
|
||||||
|
})
|
||||||
|
const subOrderIds = records.map(item => item.subOrderId).filter(Boolean) as string[]
|
||||||
|
const subOrders = subOrderIds.length
|
||||||
|
? await orderDao.findOpenApiSubOrders(supplierId, subOrderIds)
|
||||||
|
: []
|
||||||
|
const subOrderMap = new Map(subOrders.map(item => [item.orderId, item]))
|
||||||
|
const afterSaleItems = records.map(record => {
|
||||||
|
const subOrder = record.subOrderId ? subOrderMap.get(record.subOrderId) : null
|
||||||
|
const products = subOrder?.products.map(product => ({
|
||||||
|
id: product.id,
|
||||||
|
sku: product.sku,
|
||||||
|
name: product.name,
|
||||||
|
number: product.number,
|
||||||
|
price: toYuanText(product.price),
|
||||||
|
image: product.productImgUrl || firstImage(product.picInfo)
|
||||||
|
})) || []
|
||||||
|
|
||||||
|
return {
|
||||||
|
serviceOrderId: record.serviceOrderId,
|
||||||
|
supplierId: record.supplierId,
|
||||||
|
userId: record.userId,
|
||||||
|
mainOrderId: record.mainOrderId,
|
||||||
|
subOrderId: record.subOrderId,
|
||||||
|
orderId: record.subOrderId,
|
||||||
|
orderNumberId: subOrder?.orderNumberId || '',
|
||||||
|
serviceType: record.serviceType,
|
||||||
|
serviceTypeText: serviceTypeText(record.serviceType),
|
||||||
|
serviceState: record.serviceState,
|
||||||
|
serviceStateText: serviceStateText(record.serviceState),
|
||||||
|
auditStatus: record.auditStatus,
|
||||||
|
logisticsState: record.logisticsState,
|
||||||
|
logisticsStateText: logisticsStateText(record.logisticsState),
|
||||||
|
amount: toYuanText(record.amount),
|
||||||
|
reason: record.reason || '',
|
||||||
|
auditRemark: record.auditRemark || '',
|
||||||
|
userLogisticsNo: record.userLogisticsNo || '',
|
||||||
|
userLogisticsCompany: record.userLogisticsCompany || '',
|
||||||
|
merchantLogisticsNo: record.merchantLogisticsNo || '',
|
||||||
|
merchantLogisticsCompany: record.merchantLogisticsCompany || '',
|
||||||
|
applyTime: toDateTimeText(record.applyTime || record.createTime),
|
||||||
|
auditTime: toDateTimeText(record.auditTime),
|
||||||
|
userShipTime: toDateTimeText(record.userShipTime),
|
||||||
|
merchantReceiveTime: toDateTimeText(record.merchantReceiveTime),
|
||||||
|
merchantShipTime: toDateTimeText(record.merchantShipTime),
|
||||||
|
finishTime: toDateTimeText(record.finishTime),
|
||||||
|
products
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return serviceOk({ afterSaleItems })
|
||||||
|
},
|
||||||
|
|
||||||
|
async audit(input: AfterSaleAuditInput) {
|
||||||
|
const supplierId = String(input.supplierId || '').trim()
|
||||||
|
const serviceOrderId = String(input.serviceOrderId || '').trim()
|
||||||
|
const auditStatus = Number(input.auditStatus)
|
||||||
|
const auditRemark = String(input.auditRemark || '').trim()
|
||||||
|
|
||||||
|
if (!supplierId || !serviceOrderId || ![1, 2].includes(auditStatus)) {
|
||||||
|
return serviceFail('供应商、售后单或审核状态不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await orderDao.findAfterSale({ supplierId, serviceOrderId, deleted: 0 })
|
||||||
|
if (!record) {
|
||||||
|
return serviceFail('退款/售后申请不存在', 404, 'AFTER_SALE_NOT_FOUND')
|
||||||
|
}
|
||||||
|
|
||||||
|
const approved = auditStatus === 1
|
||||||
|
const now = new Date()
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
await tx.supplierAfterSale.update({
|
||||||
|
where: { serviceOrderId },
|
||||||
|
data: {
|
||||||
|
auditStatus,
|
||||||
|
auditRemark: auditRemark || null,
|
||||||
|
auditTime: now,
|
||||||
|
serviceState: approved ? AFTER_SALE_SERVICE_STATE.APPROVED : AFTER_SALE_SERVICE_STATE.REJECTED,
|
||||||
|
logisticsState: approved ? AFTER_SALE_LOGISTICS_STATE.WAIT_USER_SHIP : AFTER_SALE_LOGISTICS_STATE.NONE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return serviceOk({ serviceOrderId, auditStatus, serviceState: approved ? AFTER_SALE_SERVICE_STATE.APPROVED : AFTER_SALE_SERVICE_STATE.REJECTED })
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateLogistics(input: AfterSaleLogisticsInput) {
|
||||||
|
const supplierId = String(input.supplierId || '').trim()
|
||||||
|
const serviceOrderId = String(input.serviceOrderId || '').trim()
|
||||||
|
const logisticsState = Number(input.logisticsState)
|
||||||
|
|
||||||
|
if (!supplierId || !serviceOrderId || !logisticsState) {
|
||||||
|
return serviceFail('供应商、售后单或物流状态不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await orderDao.findAfterSale({ supplierId, serviceOrderId, deleted: 0 })
|
||||||
|
if (!record) {
|
||||||
|
return serviceFail('退款/售后申请不存在', 404, 'AFTER_SALE_NOT_FOUND')
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const finish = logisticsState === AFTER_SALE_LOGISTICS_STATE.USER_RECEIVED ||
|
||||||
|
(record.serviceType === ORDER_EVENT_TYPE.RETURN_ORDER && logisticsState === AFTER_SALE_LOGISTICS_STATE.MERCHANT_RECEIVED)
|
||||||
|
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
await tx.supplierAfterSale.update({
|
||||||
|
where: { serviceOrderId },
|
||||||
|
data: {
|
||||||
|
logisticsState,
|
||||||
|
serviceState: finish ? AFTER_SALE_SERVICE_STATE.FINISHED : logisticsState === AFTER_SALE_LOGISTICS_STATE.USER_SHIPPED
|
||||||
|
? AFTER_SALE_SERVICE_STATE.USER_SHIPPED
|
||||||
|
: logisticsState === AFTER_SALE_LOGISTICS_STATE.MERCHANT_RECEIVED
|
||||||
|
? AFTER_SALE_SERVICE_STATE.RECEIVED
|
||||||
|
: record.serviceState,
|
||||||
|
userLogisticsNo: input.userLogisticsNo || undefined,
|
||||||
|
userLogisticsCompany: input.userLogisticsCompany || undefined,
|
||||||
|
merchantLogisticsNo: input.merchantLogisticsNo || undefined,
|
||||||
|
merchantLogisticsCompany: input.merchantLogisticsCompany || undefined,
|
||||||
|
userShipTime: logisticsState === AFTER_SALE_LOGISTICS_STATE.USER_SHIPPED ? now : undefined,
|
||||||
|
merchantReceiveTime: logisticsState === AFTER_SALE_LOGISTICS_STATE.MERCHANT_RECEIVED ? now : undefined,
|
||||||
|
merchantShipTime: logisticsState === AFTER_SALE_LOGISTICS_STATE.MERCHANT_SHIPPED ? now : undefined,
|
||||||
|
finishTime: finish ? now : undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return serviceOk({ serviceOrderId, logisticsState, logisticsStateText: logisticsStateText(logisticsState) })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ORDER_EVENT_TYPE, ORDER_SPLIT_STATE, ORDER_STATE } from '../constants/order.js'
|
import { ORDER_EVENT_TYPE, ORDER_OPERATE_TYPE, ORDER_SPLIT_STATE, ORDER_STATE } from '../constants/order.js'
|
||||||
import { orderDao } from '../dao/order.dao.js'
|
import { orderDao } from '../dao/order.dao.js'
|
||||||
import { bridgeFetchWithAuth } from '../utils/bridge-auth.js'
|
import { bridgeFetchWithAuth } from '../utils/bridge-auth.js'
|
||||||
import { logger } from '../utils/logger.js'
|
import { logger } from '../utils/logger.js'
|
||||||
@@ -31,6 +31,20 @@ type PayOrderInput = {
|
|||||||
pointsPrice: number
|
pointsPrice: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ShipOrderInput = {
|
||||||
|
supplierId: string
|
||||||
|
userId: string
|
||||||
|
orderId: string
|
||||||
|
deliveryStatus: number
|
||||||
|
logisticsOrderId: string
|
||||||
|
crrgBsnNm: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const toDateTimeText = (date: Date) => {
|
||||||
|
const pad = (value: number) => String(value).padStart(2, '0')
|
||||||
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
||||||
|
}
|
||||||
|
|
||||||
export const orderActionService = {
|
export const orderActionService = {
|
||||||
async cancelPendingMain(input: CancelMainInput) {
|
async cancelPendingMain(input: CancelMainInput) {
|
||||||
const { supplierId, userId, mainOrderId } = input
|
const { supplierId, userId, mainOrderId } = input
|
||||||
@@ -234,7 +248,7 @@ export const orderActionService = {
|
|||||||
})
|
})
|
||||||
await tx.supplierOrderSub.updateMany({
|
await tx.supplierOrderSub.updateMany({
|
||||||
where: { mainOrderId: orderId, deleted: 0 },
|
where: { mainOrderId: orderId, deleted: 0 },
|
||||||
data: { orderState: ORDER_STATE.IN_PROGRESS, updateTime: new Date() }
|
data: { orderState: ORDER_STATE.FINISH_PAY, updateTime: new Date() }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -251,5 +265,129 @@ export const orderActionService = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return serviceOk({ orderId, subOrderIds, totalPrice, wPayOrAPay, pointsPrice })
|
return serviceOk({ orderId, subOrderIds, totalPrice, wPayOrAPay, pointsPrice })
|
||||||
|
},
|
||||||
|
|
||||||
|
async shipOrder(input: ShipOrderInput) {
|
||||||
|
const { supplierId, userId, orderId, logisticsOrderId, crrgBsnNm } = input
|
||||||
|
const deliveryStatus = 1
|
||||||
|
logger.info('ship order', { supplierId, userId, orderId })
|
||||||
|
|
||||||
|
if (!supplierId || !userId || !orderId) {
|
||||||
|
return serviceFail('供应商、用户或订单信息不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
let affectedSubOrderIds: string[] = []
|
||||||
|
let mainOrderId = ''
|
||||||
|
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
const mainOrder = await tx.supplierOrderMain.findFirst({
|
||||||
|
where: { supplierId, userId, orderId, deleted: 0 }
|
||||||
|
})
|
||||||
|
|
||||||
|
const targetSubOrders = mainOrder
|
||||||
|
? await tx.supplierOrderSub.findMany({
|
||||||
|
where: { supplierId, userId, mainOrderId: orderId, deleted: 0 },
|
||||||
|
orderBy: { createAt: 'asc' }
|
||||||
|
})
|
||||||
|
: await tx.supplierOrderSub.findMany({
|
||||||
|
where: { supplierId, userId, orderId, deleted: 0 },
|
||||||
|
orderBy: { createAt: 'asc' }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!targetSubOrders.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mainOrderId = mainOrder?.orderId || targetSubOrders[0].mainOrderId
|
||||||
|
const pendingShipSubOrders = targetSubOrders.filter(subOrder => subOrder.orderState === ORDER_STATE.FINISH_PAY)
|
||||||
|
affectedSubOrderIds = pendingShipSubOrders.map(subOrder => subOrder.orderId)
|
||||||
|
|
||||||
|
if (!affectedSubOrderIds.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx.supplierOrderSub.updateMany({
|
||||||
|
where: { supplierId, userId, orderId: { in: affectedSubOrderIds }, deleted: 0 },
|
||||||
|
data: {
|
||||||
|
orderState: ORDER_STATE.IN_PROGRESS,
|
||||||
|
deliveryStatus: String(deliveryStatus),
|
||||||
|
logisticsOrderId: logisticsOrderId || undefined,
|
||||||
|
crrgBsnNm: crrgBsnNm || undefined,
|
||||||
|
deliveryTime: now,
|
||||||
|
updateTime: now
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await tx.supplierOrderOperateLog.createMany({
|
||||||
|
data: pendingShipSubOrders.map(subOrder => ({
|
||||||
|
supplierId,
|
||||||
|
userId: subOrder.userId,
|
||||||
|
mainOrderId: subOrder.mainOrderId,
|
||||||
|
subOrderId: subOrder.orderId,
|
||||||
|
operateType: ORDER_OPERATE_TYPE.RECEIVE,
|
||||||
|
beforeState: subOrder.orderState,
|
||||||
|
afterState: ORDER_STATE.IN_PROGRESS,
|
||||||
|
operator: 'api'
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const unshippedCount = await tx.supplierOrderSub.count({
|
||||||
|
where: {
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
mainOrderId,
|
||||||
|
deleted: 0,
|
||||||
|
orderState: ORDER_STATE.FINISH_PAY
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (unshippedCount === 0) {
|
||||||
|
await tx.supplierOrderMain.update({
|
||||||
|
where: { orderId: mainOrderId },
|
||||||
|
data: {
|
||||||
|
orderState: ORDER_STATE.IN_PROGRESS,
|
||||||
|
deliveryStatus: String(deliveryStatus),
|
||||||
|
logisticsOrderId: logisticsOrderId || undefined,
|
||||||
|
crrgBsnNm: crrgBsnNm || undefined,
|
||||||
|
deliveryTime: now,
|
||||||
|
updateTime: now
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!mainOrderId) {
|
||||||
|
return serviceFail('订单不存在或已取消', 404, 'ORDER_NOT_FOUND')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!affectedSubOrderIds.length) {
|
||||||
|
return serviceFail('当前订单不是待发货状态', 400, 'ORDER_NOT_PENDING_SHIP')
|
||||||
|
}
|
||||||
|
|
||||||
|
const ship = await bridgeFetchWithAuth<BridgeOrderResponse>(supplierId, '/pointOrder/pushOrderInfoMsg', {
|
||||||
|
orderType: ORDER_EVENT_TYPE.SHIP_ORDER,
|
||||||
|
orderId: affectedSubOrderIds.join(','),
|
||||||
|
mainOrderId,
|
||||||
|
userId,
|
||||||
|
deliveryStatus,
|
||||||
|
deliveryTime: toDateTimeText(now),
|
||||||
|
logisticsOrderId,
|
||||||
|
crrgBsnNm,
|
||||||
|
time: Date.now()
|
||||||
|
})
|
||||||
|
if (!ship.response.ok || ship.payload.resultCode !== 0) {
|
||||||
|
return serviceFail(ship.payload.message || '发货状态推送失败', 502, 'BRIDGE_SHIP_ORDER_FAILED')
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceOk({
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
orderId,
|
||||||
|
mainOrderId,
|
||||||
|
orderState: ORDER_STATE.IN_PROGRESS,
|
||||||
|
deliveryStatus,
|
||||||
|
affectedSubOrderIds
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ type OrderCreateInput = {
|
|||||||
orderNo: string
|
orderNo: string
|
||||||
orderNumberId?: string
|
orderNumberId?: string
|
||||||
amount?: number
|
amount?: number
|
||||||
|
addressId?: string
|
||||||
|
remark?: string
|
||||||
goods: CheckoutGoods[]
|
goods: CheckoutGoods[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ type BridgeOrderResponse = {
|
|||||||
type OrderProductSnapshot = {
|
type OrderProductSnapshot = {
|
||||||
sku: string
|
sku: string
|
||||||
number: number
|
number: number
|
||||||
price: number
|
price: string
|
||||||
name: string
|
name: string
|
||||||
taxRate: number
|
taxRate: number
|
||||||
picInfo: string[]
|
picInfo: string[]
|
||||||
@@ -58,6 +60,7 @@ const createOrderNumberId = () => {
|
|||||||
|
|
||||||
const toYuan = (centAmount?: number) => Number((Number(centAmount || 0) / 100).toFixed(2))
|
const toYuan = (centAmount?: number) => Number((Number(centAmount || 0) / 100).toFixed(2))
|
||||||
const toMoney = (value: number) => Number(value.toFixed(2))
|
const toMoney = (value: number) => Number(value.toFixed(2))
|
||||||
|
const toMoneyText = (value: number) => Number(value || 0).toFixed(2)
|
||||||
const createSubOrderId = (orderId: string, index: number) => `${orderId}_${String(index + 1).padStart(3, '0')}`
|
const createSubOrderId = (orderId: string, index: number) => `${orderId}_${String(index + 1).padStart(3, '0')}`
|
||||||
const createSubOrderNumberId = (orderNumberId: string, index: number) => `${orderNumberId}_${String(index + 1).padStart(3, '0')}`
|
const createSubOrderNumberId = (orderNumberId: string, index: number) => `${orderNumberId}_${String(index + 1).padStart(3, '0')}`
|
||||||
|
|
||||||
@@ -70,7 +73,7 @@ const toOrderProducts = (goods: CheckoutGoods[]) => goods.map((item, index) => {
|
|||||||
return {
|
return {
|
||||||
sku: String(sku || item.id || `SKU_${index + 1}`),
|
sku: String(sku || item.id || `SKU_${index + 1}`),
|
||||||
number: Number(item.count || item.number || 1),
|
number: Number(item.count || item.number || 1),
|
||||||
price: Number(item.price || 0),
|
price: toMoneyText(Number(item.price || 0)),
|
||||||
name: String(item.name || ''),
|
name: String(item.name || ''),
|
||||||
taxRate: Number(item.taxRate || 0.13),
|
taxRate: Number(item.taxRate || 0.13),
|
||||||
picInfo: item.picInfo?.length ? item.picInfo : image ? [image] : [],
|
picInfo: item.picInfo?.length ? item.picInfo : image ? [image] : [],
|
||||||
@@ -88,6 +91,8 @@ export const orderCreateService = {
|
|||||||
const orderId = input.orderNo
|
const orderId = input.orderNo
|
||||||
const amount = toYuan(input.amount)
|
const amount = toYuan(input.amount)
|
||||||
const goods = Array.isArray(input.goods) ? input.goods : []
|
const goods = Array.isArray(input.goods) ? input.goods : []
|
||||||
|
const addressId = String(input.addressId || '').trim()
|
||||||
|
const remark = String(input.remark || '').trim()
|
||||||
const orderProInfos = toOrderProducts(goods)
|
const orderProInfos = toOrderProducts(goods)
|
||||||
logger.info('create pending order', { supplierId, userId, orderId, goodsCount: goods.length, amount })
|
logger.info('create pending order', { supplierId, userId, orderId, goodsCount: goods.length, amount })
|
||||||
|
|
||||||
@@ -103,6 +108,11 @@ export const orderCreateService = {
|
|||||||
return serviceFail('订单商品不能为空')
|
return serviceFail('订单商品不能为空')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const address = addressId ? await orderDao.findUserAddress(supplierId, userId, addressId) : null
|
||||||
|
if (!address) {
|
||||||
|
return serviceFail('请选择收货地址', 400, 'ADDRESS_NOT_FOUND')
|
||||||
|
}
|
||||||
|
|
||||||
const orderNumberId = String(input.orderNumberId || createOrderNumberId())
|
const orderNumberId = String(input.orderNumberId || createOrderNumberId())
|
||||||
const orderTime = nowText()
|
const orderTime = nowText()
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
@@ -122,6 +132,8 @@ export const orderCreateService = {
|
|||||||
orderNumberId,
|
orderNumberId,
|
||||||
supplierId,
|
supplierId,
|
||||||
aplName: '积分商城',
|
aplName: '积分商城',
|
||||||
|
addressId,
|
||||||
|
remark,
|
||||||
createTime: orderTime,
|
createTime: orderTime,
|
||||||
updateTime: orderTime,
|
updateTime: orderTime,
|
||||||
deliveryTime: null,
|
deliveryTime: null,
|
||||||
@@ -161,6 +173,8 @@ export const orderCreateService = {
|
|||||||
supplierId,
|
supplierId,
|
||||||
userId,
|
userId,
|
||||||
aplName: orderInfo.aplName,
|
aplName: orderInfo.aplName,
|
||||||
|
addressId: orderInfo.addressId,
|
||||||
|
remark: orderInfo.remark,
|
||||||
updateTime: now,
|
updateTime: now,
|
||||||
deliveryStatus: orderInfo.deliveryStatus,
|
deliveryStatus: orderInfo.deliveryStatus,
|
||||||
freightCost: orderInfo.freightCost,
|
freightCost: orderInfo.freightCost,
|
||||||
@@ -182,6 +196,8 @@ export const orderCreateService = {
|
|||||||
supplierId,
|
supplierId,
|
||||||
userId,
|
userId,
|
||||||
aplName: orderInfo.aplName,
|
aplName: orderInfo.aplName,
|
||||||
|
addressId: orderInfo.addressId,
|
||||||
|
remark: orderInfo.remark,
|
||||||
createTime: now,
|
createTime: now,
|
||||||
updateTime: now,
|
updateTime: now,
|
||||||
deliveryStatus: orderInfo.deliveryStatus,
|
deliveryStatus: orderInfo.deliveryStatus,
|
||||||
@@ -206,6 +222,8 @@ export const orderCreateService = {
|
|||||||
supplierId,
|
supplierId,
|
||||||
userId,
|
userId,
|
||||||
aplName: orderInfo.aplName,
|
aplName: orderInfo.aplName,
|
||||||
|
addressId: orderInfo.addressId,
|
||||||
|
remark: orderInfo.remark,
|
||||||
createTime: now,
|
createTime: now,
|
||||||
updateTime: now,
|
updateTime: now,
|
||||||
deliveryStatus: orderInfo.deliveryStatus,
|
deliveryStatus: orderInfo.deliveryStatus,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ORDER_SPLIT_STATE, ORDER_STATE } from '../constants/order.js'
|
import { ORDER_EVENT_TYPE, ORDER_SPLIT_STATE, ORDER_STATE } from '../constants/order.js'
|
||||||
import { orderDao } from '../dao/order.dao.js'
|
import { orderDao } from '../dao/order.dao.js'
|
||||||
import { logger } from '../utils/logger.js'
|
import { logger } from '../utils/logger.js'
|
||||||
import { serviceFail, serviceOk } from '../types/service-result.js'
|
import { serviceFail, serviceOk } from '../types/service-result.js'
|
||||||
@@ -49,7 +49,152 @@ const sumProductsCount = (subOrders: Array<{ products: Array<{ number: number |
|
|||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatAddress = (address: {
|
||||||
|
name: string
|
||||||
|
tel: string
|
||||||
|
province: string | null
|
||||||
|
city: string | null
|
||||||
|
county: string | null
|
||||||
|
addressDetail: string | null
|
||||||
|
} | null) => {
|
||||||
|
if (!address) return null
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: address.name,
|
||||||
|
tel: address.tel,
|
||||||
|
address: `${address.province || ''}${address.city || ''}${address.county || ''}${address.addressDetail || ''}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const orderListService = {
|
export const orderListService = {
|
||||||
|
async detailOrder(supplierId: string, userId: string, orderId: string, mainOrderId: string) {
|
||||||
|
logger.info('order detail query', { supplierId, userId, orderId, mainOrderId })
|
||||||
|
|
||||||
|
if (!supplierId || !userId || !orderId) {
|
||||||
|
return serviceFail('供应商、用户或订单信息不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetSubOrder = await orderDao.findSubOrderWithProducts(supplierId, userId, orderId)
|
||||||
|
const targetMainOrderId = mainOrderId || targetSubOrder?.mainOrderId || orderId
|
||||||
|
const mainOrder = await orderDao.findMainOrderWithSubs(supplierId, userId, targetMainOrderId)
|
||||||
|
|
||||||
|
if (!mainOrder) {
|
||||||
|
return serviceFail('订单不存在或已取消', 404, 'ORDER_NOT_FOUND')
|
||||||
|
}
|
||||||
|
|
||||||
|
const addressId = targetSubOrder?.addressId || mainOrder.addressId || ''
|
||||||
|
const address = addressId ? await orderDao.findUserAddressById(addressId) : null
|
||||||
|
const addressView = formatAddress(address)
|
||||||
|
const mainStatus = statusMap[mainOrder.orderState] || statusMap[ORDER_STATE.FINISH_PAY]
|
||||||
|
const subOrders = mainOrder.subOrders.map(subOrder => {
|
||||||
|
const status = statusMap[subOrder.orderState] || mainStatus
|
||||||
|
const products = subOrder.products.map(product => ({
|
||||||
|
id: product.id,
|
||||||
|
sku: product.sku,
|
||||||
|
name: product.name,
|
||||||
|
number: product.number,
|
||||||
|
price: toYuanText(product.price),
|
||||||
|
image: product.productImgUrl || firstImage(product.picInfo)
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: subOrder.orderId,
|
||||||
|
orderId: subOrder.orderId,
|
||||||
|
orderNumberId: subOrder.orderNumberId || '',
|
||||||
|
mainOrderId: subOrder.mainOrderId,
|
||||||
|
supplierId: subOrder.supplierId,
|
||||||
|
addressId: subOrder.addressId || mainOrder.addressId || '',
|
||||||
|
receiverName: addressView?.name || '',
|
||||||
|
receiverMobile: addressView?.tel || '',
|
||||||
|
receiverAddress: addressView?.address || '',
|
||||||
|
remark: subOrder.remark || mainOrder.remark || '',
|
||||||
|
createTime: toDateTimeText(subOrder.createTime || subOrder.createAt),
|
||||||
|
status: status.text,
|
||||||
|
statusClass: status.statusClass,
|
||||||
|
orderState: subOrder.orderState,
|
||||||
|
amount: toYuanText(subOrder.totalPrice),
|
||||||
|
freightCost: toYuanText(subOrder.freightCost),
|
||||||
|
logisticsOrderId: subOrder.logisticsOrderId || mainOrder.logisticsOrderId || '',
|
||||||
|
crrgBsnNm: subOrder.crrgBsnNm || mainOrder.crrgBsnNm || '',
|
||||||
|
deliveryTime: toDateTimeText(subOrder.deliveryTime || mainOrder.deliveryTime),
|
||||||
|
deliveryStatus: subOrder.deliveryStatus || mainOrder.deliveryStatus || '',
|
||||||
|
wPayOrAPay: subOrder.wPayOrAPay,
|
||||||
|
count: products.reduce((total, item) => total + Number(item.number || 0), 0),
|
||||||
|
products
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const subOrder = subOrders.find(item => item.orderId === orderId) || subOrders[0] || null
|
||||||
|
|
||||||
|
return serviceOk({
|
||||||
|
order: {
|
||||||
|
id: mainOrder.orderId,
|
||||||
|
orderId: mainOrder.orderId,
|
||||||
|
mainOrderId: mainOrder.orderId,
|
||||||
|
orderNumberId: mainOrder.orderNumberId || '',
|
||||||
|
supplierId: mainOrder.supplierId,
|
||||||
|
addressId: mainOrder.addressId || '',
|
||||||
|
receiverName: addressView?.name || '',
|
||||||
|
receiverMobile: addressView?.tel || '',
|
||||||
|
receiverAddress: addressView?.address || '',
|
||||||
|
remark: mainOrder.remark || '',
|
||||||
|
createTime: toDateTimeText(mainOrder.createTime || mainOrder.createAt),
|
||||||
|
status: mainStatus.text,
|
||||||
|
statusClass: mainStatus.statusClass,
|
||||||
|
orderState: mainOrder.orderState,
|
||||||
|
amount: toYuanText(mainOrder.totalPrice),
|
||||||
|
freightCost: toYuanText(mainOrder.freightCost),
|
||||||
|
logisticsOrderId: mainOrder.logisticsOrderId || '',
|
||||||
|
crrgBsnNm: mainOrder.crrgBsnNm || '',
|
||||||
|
deliveryTime: toDateTimeText(mainOrder.deliveryTime),
|
||||||
|
deliveryStatus: mainOrder.deliveryStatus || '',
|
||||||
|
wPayOrAPay: mainOrder.wPayOrAPay,
|
||||||
|
count: sumProductsCount(subOrders),
|
||||||
|
subOrders
|
||||||
|
},
|
||||||
|
subOrder
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async countOrders(supplierId: string, userId: string) {
|
||||||
|
logger.info('order count query', { supplierId, userId })
|
||||||
|
|
||||||
|
if (!supplierId || !userId) {
|
||||||
|
return serviceFail('供应商或用户信息不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const [pendingPay, subStateCounts, afterSale] = await Promise.all([
|
||||||
|
orderDao.countMainOrders({
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
deleted: 0,
|
||||||
|
orderState: ORDER_STATE.PENDING_PAY
|
||||||
|
}),
|
||||||
|
orderDao.groupSubOrdersByState({
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
deleted: 0,
|
||||||
|
orderState: {
|
||||||
|
in: [ORDER_STATE.IN_PROGRESS]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
orderDao.countAfterSales({
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
deleted: 0,
|
||||||
|
serviceType: {
|
||||||
|
in: [ORDER_EVENT_TYPE.RETURN_ORDER, ORDER_EVENT_TYPE.EXCHANGE_ORDER]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
])
|
||||||
|
const countByState = subStateCounts.reduce<Record<number, number>>((result, item) => {
|
||||||
|
result[item.orderState] = item._count._all
|
||||||
|
return result
|
||||||
|
}, {})
|
||||||
|
const pendingReceive = countByState[ORDER_STATE.IN_PROGRESS] || 0
|
||||||
|
|
||||||
|
return serviceOk({ pendingPay, pendingReceive, afterSale })
|
||||||
|
},
|
||||||
|
|
||||||
async listOrders(supplierId: string, userId: string) {
|
async listOrders(supplierId: string, userId: string) {
|
||||||
logger.info('order list query', { supplierId, userId })
|
logger.info('order list query', { supplierId, userId })
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user