merge: resolve conflict from origin/zuncle - keep async onShow with phone bind check
This commit is contained in:
commit
5c88d91382
@ -148,8 +148,10 @@ export function redeemInventory(user_id, ids) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/inventory/redeem`, method: 'POST', data: { inventory_ids: ids } })
|
||||
}
|
||||
|
||||
export function requestShipping(user_id, ids) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/inventory/request-shipping`, method: 'POST', data: { inventory_ids: ids } })
|
||||
export function requestShipping(user_id, ids, address_id) {
|
||||
const data = { inventory_ids: ids }
|
||||
if (address_id) data.address_id = address_id
|
||||
return authRequest({ url: `/api/app/users/${user_id}/inventory/request-shipping`, method: 'POST', data })
|
||||
}
|
||||
|
||||
export function createShippingFeeOrder(user_id, ids) {
|
||||
|
||||
@ -64,7 +64,7 @@
|
||||
<view v-if="!hasMore && aggregatedList.length > 0" class="no-more">没有更多了</view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="bottom-bar" v-if="hasSelected">
|
||||
<view class="bottom-bar" v-if="hasSelected && !showAddressPicker">
|
||||
<view class="selected-info">
|
||||
<text>已选 {{ totalSelectedCount }} 件</text>
|
||||
</view>
|
||||
@ -244,6 +244,41 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 地址选择弹窗 -->
|
||||
<view class="address-picker-mask" v-if="showAddressPicker" @tap="showAddressPicker = false" @touchmove.stop></view>
|
||||
<view class="address-picker-popup" :class="{ 'show': showAddressPicker }">
|
||||
<view class="share-header">
|
||||
<text class="share-title">选择收货地址</text>
|
||||
<text class="share-close" @tap="showAddressPicker = false">×</text>
|
||||
</view>
|
||||
<scroll-view scroll-y class="address-scroll">
|
||||
<view v-if="addressList.length === 0" class="empty-address">
|
||||
<text>暂无收货地址</text>
|
||||
</view>
|
||||
<view
|
||||
v-for="addr in addressList"
|
||||
:key="addr.id"
|
||||
class="address-option"
|
||||
:class="{ selected: selectedAddressId === addr.id }"
|
||||
@tap="selectedAddressId = addr.id"
|
||||
>
|
||||
<view class="addr-radio" :class="{ checked: selectedAddressId === addr.id }"></view>
|
||||
<view class="addr-info">
|
||||
<view class="addr-top">
|
||||
<text class="addr-name">{{ addr.name || addr.realname }}</text>
|
||||
<text class="addr-phone">{{ addr.phone || addr.mobile }}</text>
|
||||
<view v-if="addr.is_default" class="addr-default-tag">默认</view>
|
||||
</view>
|
||||
<text class="addr-detail">{{ addr.province }} {{ addr.city }} {{ addr.district }} {{ addr.address || addr.detail }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="address-picker-footer">
|
||||
<text class="add-address-link" @tap="toAddAddress">+ 新增收货地址</text>
|
||||
<button class="confirm-ship-btn" :disabled="!selectedAddressId" @tap="confirmShipWithAddress">确认发货</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -272,6 +307,12 @@ const pageSize = ref(100)
|
||||
const hasMore = ref(true)
|
||||
const productMetaCache = new Map()
|
||||
|
||||
// 地址选择弹窗状态
|
||||
const showAddressPicker = ref(false)
|
||||
const addressList = ref([])
|
||||
const selectedAddressId = ref(null)
|
||||
const pendingShipIds = ref([])
|
||||
|
||||
// Synthesis tab state
|
||||
const recipes = ref([])
|
||||
const synthLoading = ref(false)
|
||||
@ -311,7 +352,10 @@ async function fetchProductMeta(productId) {
|
||||
return meta
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
onShow(async () => {
|
||||
// 检查手机号绑定状态(快速检查本地缓存)
|
||||
if (!checkPhoneBoundSync()) return
|
||||
|
||||
// Check for external tab switch request
|
||||
try {
|
||||
const targetTab = uni.getStorageSync('cabinet_target_tab')
|
||||
@ -338,18 +382,36 @@ onShow(() => {
|
||||
return
|
||||
}
|
||||
|
||||
const uid = uni.getStorageSync("user_id")
|
||||
|
||||
// 从新增地址页返回:刷新地址列表并重新打开弹窗
|
||||
if (pendingShipIds.value.length > 0) {
|
||||
try {
|
||||
const addresses = await listAddresses(uid)
|
||||
const list = addresses.list || addresses.data || addresses || []
|
||||
addressList.value = Array.isArray(list) ? list : []
|
||||
if (addressList.value.length > 0) {
|
||||
if (!selectedAddressId.value) {
|
||||
const defaultAddr = addressList.value.find(a => a.is_default)
|
||||
selectedAddressId.value = defaultAddr ? defaultAddr.id : addressList.value[0].id
|
||||
}
|
||||
showAddressPicker.value = true
|
||||
}
|
||||
} catch (e) {}
|
||||
return
|
||||
}
|
||||
|
||||
// 重置并加载第一页
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
aggregatedList.value = []
|
||||
shippedList.value = []
|
||||
const uid = uni.getStorageSync("user_id")
|
||||
if (currentTab.value === 1) {
|
||||
loadShipments(uid)
|
||||
} else if (currentTab.value === 2) {
|
||||
loadRecipes(uid)
|
||||
} else {
|
||||
loadInventory(uid) // 改为只加载第一页,后续由 onReachBottom 触发
|
||||
loadInventory(uid)
|
||||
}
|
||||
})
|
||||
|
||||
@ -772,7 +834,6 @@ async function onShip() {
|
||||
const selectedItems = aggregatedList.value.filter(item => item.selected)
|
||||
if (selectedItems.length === 0) return
|
||||
|
||||
// 收集所有需要发货的 inventory id
|
||||
let allIds = []
|
||||
selectedItems.forEach(item => {
|
||||
if (item.original_ids && item.original_ids.length >= item.selectedCount) {
|
||||
@ -786,42 +847,49 @@ async function onShip() {
|
||||
return
|
||||
}
|
||||
|
||||
// 1. 先检查是否有默认地址
|
||||
// 获取地址列表并弹窗选择
|
||||
try {
|
||||
const addresses = await listAddresses(user_id)
|
||||
const addressList = addresses.list || addresses.data || addresses || []
|
||||
const list = addresses.list || addresses.data || addresses || []
|
||||
addressList.value = Array.isArray(list) ? list : []
|
||||
|
||||
if (!addressList || addressList.length === 0) {
|
||||
// 没有默认地址,提示用户跳转到新建地址页面
|
||||
if (addressList.value.length === 0) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '申请发货需要设置默认地址,是否前往新建地址?',
|
||||
content: '申请发货需要设置收货地址,是否前往新建地址?',
|
||||
confirmText: '前往',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateTo({ url: '/pages-user/address/edit' })
|
||||
}
|
||||
if (res.confirm) uni.navigateTo({ url: '/pages-user/address/edit' })
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
pendingShipIds.value = allIds
|
||||
const defaultAddr = addressList.value.find(a => a.is_default)
|
||||
selectedAddressId.value = defaultAddr ? defaultAddr.id : addressList.value[0].id
|
||||
showAddressPicker.value = true
|
||||
} catch (e) {
|
||||
console.error('获取地址列表失败:', e)
|
||||
uni.showToast({ title: '获取地址失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmShipWithAddress() {
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
const allIds = pendingShipIds.value
|
||||
const addressId = selectedAddressId.value
|
||||
showAddressPicker.value = false
|
||||
|
||||
// 2. 有默认地址,判断是否需要支付运费
|
||||
const FREIGHT_THRESHOLD = 5
|
||||
const FREIGHT_FEE = 10
|
||||
|
||||
if (allIds.length > FREIGHT_THRESHOLD) {
|
||||
// 超过 5 件,需支付 10 元运费
|
||||
if (allIds.length < FREIGHT_THRESHOLD) {
|
||||
const confirmed = await new Promise((resolve) => {
|
||||
uni.showModal({
|
||||
title: '需支付运费',
|
||||
content: `共 ${allIds.length} 件商品,超过 ${FREIGHT_THRESHOLD} 件需支付 ¥${FREIGHT_FEE}.00 运费,确认继续?`,
|
||||
content: `共 ${allIds.length} 件商品,不满 ${FREIGHT_THRESHOLD} 件需支付 ¥${FREIGHT_FEE}.00 运费,确认继续?`,
|
||||
confirmText: '去支付',
|
||||
cancelText: '取消',
|
||||
success: (res) => resolve(res.confirm)
|
||||
@ -843,11 +911,11 @@ async function onShip() {
|
||||
}
|
||||
uni.hideLoading()
|
||||
|
||||
// 支付成功后发货
|
||||
uni.showLoading({ title: '提交中...' })
|
||||
try {
|
||||
await requestShipping(user_id, allIds)
|
||||
await requestShipping(user_id, allIds, addressId)
|
||||
uni.showToast({ title: '申请成功', icon: 'success' })
|
||||
pendingShipIds.value = []
|
||||
aggregatedList.value = []
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
@ -860,30 +928,26 @@ async function onShip() {
|
||||
return
|
||||
}
|
||||
|
||||
// 不超过 5 件,原有确认发货流程
|
||||
uni.showModal({
|
||||
title: '确认发货',
|
||||
content: `共 ${allIds.length} 件物品,确认申请发货?`,
|
||||
confirmText: '确认发货',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '提交中...' })
|
||||
try {
|
||||
await requestShipping(user_id, allIds)
|
||||
uni.showToast({ title: '申请成功', icon: 'success' })
|
||||
// 刷新列表
|
||||
aggregatedList.value = []
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
loadInventory(user_id)
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e.message || '申请失败', icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// 满5件包邮直接发货
|
||||
uni.showLoading({ title: '提交中...' })
|
||||
try {
|
||||
await requestShipping(user_id, allIds, addressId)
|
||||
uni.showToast({ title: '申请成功', icon: 'success' })
|
||||
pendingShipIds.value = []
|
||||
aggregatedList.value = []
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
loadInventory(user_id)
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e.message || '申请失败', icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
function toAddAddress() {
|
||||
showAddressPicker.value = false
|
||||
uni.navigateTo({ url: '/pages-user/address/edit' })
|
||||
}
|
||||
|
||||
// 检查是否可以撤销发货(48小时内)
|
||||
@ -1920,4 +1984,164 @@ function onCopyShareLink() {
|
||||
0% { left: -100%; }
|
||||
20%, 100% { left: 200%; }
|
||||
}
|
||||
|
||||
/* ── 地址选择弹窗 ── */
|
||||
.address-picker-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 1009;
|
||||
backdrop-filter: blur(4rpx);
|
||||
}
|
||||
|
||||
.address-picker-popup {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
border-radius: 40rpx 40rpx 0 0;
|
||||
z-index: 1010;
|
||||
padding: 40rpx 40rpx 0;
|
||||
transform: translateY(100%);
|
||||
transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
max-height: 70vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.address-scroll {
|
||||
flex: 1;
|
||||
max-height: 50vh;
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
|
||||
.address-option {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 24rpx;
|
||||
border-radius: $radius-md;
|
||||
margin-bottom: 16rpx;
|
||||
border: 2rpx solid transparent;
|
||||
background: $bg-page;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.selected {
|
||||
border-color: $brand-primary;
|
||||
background: rgba($brand-primary, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.addr-radio {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid $text-tertiary;
|
||||
margin-right: 20rpx;
|
||||
margin-top: 6rpx;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.checked {
|
||||
border-color: $brand-primary;
|
||||
background: $brand-primary;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 16rpx;
|
||||
height: 8rpx;
|
||||
border-left: 3rpx solid #fff;
|
||||
border-bottom: 3rpx solid #fff;
|
||||
transform: translate(-50%, -65%) rotate(-45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.addr-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.addr-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.addr-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: $text-main;
|
||||
}
|
||||
|
||||
.addr-phone {
|
||||
font-size: 26rpx;
|
||||
color: $text-sub;
|
||||
}
|
||||
|
||||
.addr-default-tag {
|
||||
font-size: 18rpx;
|
||||
color: #fff;
|
||||
background: $brand-primary;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 6rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.addr-detail {
|
||||
font-size: 24rpx;
|
||||
color: $text-sub;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.empty-address {
|
||||
text-align: center;
|
||||
padding: 60rpx 0;
|
||||
color: $text-tertiary;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.address-picker-footer {
|
||||
padding: 20rpx 0 calc(20rpx + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-top: 1rpx solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.add-address-link {
|
||||
font-size: 26rpx;
|
||||
color: $brand-primary;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.confirm-ship-btn {
|
||||
background: $gradient-brand;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: $radius-round;
|
||||
height: 80rpx;
|
||||
padding: 0 60rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: $shadow-warm;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&::after { border: none; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user