forked from gxwebsoft/mp-10550
Compare commits
2 Commits
00d3ffaeee
...
d6eb6d5e05
| Author | SHA1 | Date | |
|---|---|---|---|
| d6eb6d5e05 | |||
| 694b5e1e7a |
@@ -3,7 +3,7 @@ import {Cell, CellGroup, Image, Space, Button, Dialog} from '@nutui/nutui-react-
|
||||
import Taro from '@tarojs/taro'
|
||||
import {View} from '@tarojs/components'
|
||||
import {ShopOrder} from "@/api/shop/shopOrder/model";
|
||||
import {getShopOrder, updateShopOrder} from "@/api/shop/shopOrder";
|
||||
import {getShopOrder, updateShopOrder, refundShopOrder} from "@/api/shop/shopOrder";
|
||||
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
|
||||
import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model";
|
||||
import dayjs from "dayjs";
|
||||
@@ -58,25 +58,39 @@ const OrderDetail = () => {
|
||||
const handleApplyRefund = async () => {
|
||||
if (order) {
|
||||
try {
|
||||
// 更新订单状态为"退款申请中"
|
||||
await updateShopOrder({
|
||||
const confirm = await Taro.showModal({
|
||||
title: '申请退款',
|
||||
content: '确认要申请退款吗?',
|
||||
confirmText: '确认',
|
||||
cancelText: '取消'
|
||||
})
|
||||
if (!confirm?.confirm) return
|
||||
|
||||
Taro.showLoading({ title: '提交中...' })
|
||||
|
||||
// 退款相关操作使用退款接口:PUT /api/shop/shop-order/refund
|
||||
await refundShopOrder({
|
||||
orderId: order.orderId,
|
||||
orderStatus: 4 // 退款申请中
|
||||
});
|
||||
refundMoney: order.payPrice || order.totalPrice,
|
||||
orderStatus: 7
|
||||
})
|
||||
|
||||
// 更新本地状态
|
||||
setOrder(prev => prev ? {...prev, orderStatus: 4} : null);
|
||||
// 乐观更新本地状态
|
||||
setOrder(prev => prev ? { ...prev, orderStatus: 7 } : null)
|
||||
|
||||
// 跳转到退款申请页面
|
||||
Taro.navigateTo({
|
||||
url: `/user/order/refund/index?orderId=${order.orderId}&orderNo=${order.orderNo}`
|
||||
});
|
||||
Taro.showToast({ title: '退款申请已提交', icon: 'success' })
|
||||
} catch (error) {
|
||||
console.error('更新订单状态失败:', error);
|
||||
console.error('申请退款失败:', error);
|
||||
Taro.showToast({
|
||||
title: '操作失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
try {
|
||||
Taro.hideLoading()
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import {ShopUserAddress} from "@/api/shop/shopUserAddress/model";
|
||||
import {getShopUserAddress, listShopUserAddress, updateShopUserAddress, addShopUserAddress} from "@/api/shop/shopUserAddress";
|
||||
import RegionData from '@/api/json/regions-data.json';
|
||||
import FixedButton from "@/components/FixedButton";
|
||||
import { parseLngLatFromText } from "@/utils/geofence";
|
||||
|
||||
type SelectedLocation = { lng: string; lat: string; name?: string; address?: string }
|
||||
|
||||
@@ -28,6 +29,15 @@ const isUserCancel = (e: any) => {
|
||||
return msg.includes('cancel')
|
||||
}
|
||||
|
||||
const hasValidLngLat = (addr?: Partial<ShopUserAddress> | null) => {
|
||||
if (!addr) return false
|
||||
const p = parseLngLatFromText(`${(addr as any)?.lng ?? ''},${(addr as any)?.lat ?? ''}`)
|
||||
if (!p) return false
|
||||
// Treat "0,0" as missing in this app (typically used as placeholder by backends).
|
||||
if (p.lng === 0 && p.lat === 0) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const AddUserAddress = () => {
|
||||
const {params} = useRouter();
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
@@ -47,6 +57,31 @@ const AddUserAddress = () => {
|
||||
// 整理地区数据
|
||||
setRegionData()
|
||||
|
||||
// 新增模式:若存在“默认地址”但缺少经纬度,则强制引导到编辑页完善定位
|
||||
// 目的:确保业务依赖默认地址坐标(选店/配送范围)时不会因为缺失坐标而失败
|
||||
if (!isEditMode) {
|
||||
try {
|
||||
const defaultList = await listShopUserAddress({ isDefault: true })
|
||||
const defaultAddr = defaultList?.[0]
|
||||
if (defaultAddr && !hasValidLngLat(defaultAddr)) {
|
||||
await Taro.showModal({
|
||||
title: '需要完善定位',
|
||||
content: '默认收货地址缺少定位信息,请先进入编辑页面选择定位并保存后再继续。',
|
||||
confirmText: '去完善',
|
||||
showCancel: false
|
||||
})
|
||||
if (defaultAddr.id) {
|
||||
Taro.navigateTo({ url: `/user/address/add?id=${defaultAddr.id}` })
|
||||
} else {
|
||||
Taro.navigateTo({ url: '/user/address/index' })
|
||||
}
|
||||
return
|
||||
}
|
||||
} catch (_e) {
|
||||
// ignore: 新增页不阻塞渲染
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是编辑模式,加载地址数据
|
||||
if (isEditMode && addressId) {
|
||||
try {
|
||||
@@ -55,9 +90,7 @@ const AddUserAddress = () => {
|
||||
// 设置所在地区
|
||||
setText(`${address.province} ${address.city} ${address.region}`)
|
||||
// 回显已保存的经纬度(编辑模式)
|
||||
if (address?.lng && address?.lat) {
|
||||
setSelectedLocation({ lng: String(address.lng), lat: String(address.lat) })
|
||||
}
|
||||
if (hasValidLngLat(address)) setSelectedLocation({ lng: String(address.lng), lat: String(address.lat) })
|
||||
} catch (error) {
|
||||
console.error('加载地址失败:', error)
|
||||
Taro.showToast({
|
||||
@@ -348,7 +381,7 @@ const AddUserAddress = () => {
|
||||
const submitSucceed = async (values: any) => {
|
||||
const loc =
|
||||
selectedLocation ||
|
||||
(FormData?.lng && FormData?.lat ? { lng: String(FormData.lng), lat: String(FormData.lat) } : null)
|
||||
(hasValidLngLat(FormData) ? { lng: String(FormData.lng), lat: String(FormData.lat) } : null)
|
||||
if (!loc) {
|
||||
Taro.showToast({ title: '请选择定位', icon: 'none' })
|
||||
return
|
||||
|
||||
@@ -13,6 +13,19 @@ const Address = () => {
|
||||
const [list, setList] = useState<ShopUserAddress[]>([])
|
||||
const [address, setAddress] = useState<ShopUserAddress>()
|
||||
|
||||
const safeNavigateBack = async () => {
|
||||
try {
|
||||
const pages = (Taro as any).getCurrentPages?.() || []
|
||||
if (Array.isArray(pages) && pages.length > 1) {
|
||||
await Taro.navigateBack()
|
||||
return true
|
||||
}
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const parseTime = (raw?: unknown) => {
|
||||
if (raw === undefined || raw === null || raw === '') return null;
|
||||
// 兼容秒/毫秒时间戳
|
||||
@@ -78,7 +91,11 @@ const Address = () => {
|
||||
title: '设置成功',
|
||||
icon: 'success'
|
||||
});
|
||||
reload();
|
||||
// 设置默认地址通常是“选择地址”的动作:成功后返回上一页,体验更顺滑
|
||||
setTimeout(async () => {
|
||||
const backed = await safeNavigateBack()
|
||||
if (!backed) reload()
|
||||
}, 400)
|
||||
}
|
||||
|
||||
const onDel = async (id?: number) => {
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
updateShopOrder,
|
||||
createOrder,
|
||||
getShopOrder,
|
||||
prepayShopOrder
|
||||
prepayShopOrder,
|
||||
refundShopOrder
|
||||
} from "@/api/shop/shopOrder";
|
||||
import {OrderCreateRequest, ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model";
|
||||
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
|
||||
@@ -330,28 +331,53 @@ function OrderList(props: OrderListProps) {
|
||||
|
||||
// 申请退款 (待发货状态)
|
||||
const applyRefund = async (order: ShopOrder) => {
|
||||
if (!order?.orderId) {
|
||||
Taro.showToast({ title: '订单信息错误', icon: 'none' })
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 更新订单状态为"退款申请中"
|
||||
await updateShopOrder({
|
||||
const confirm = await Taro.showModal({
|
||||
title: '申请退款',
|
||||
content: '确认要申请退款吗?',
|
||||
confirmText: '确认',
|
||||
cancelText: '取消'
|
||||
})
|
||||
if (!confirm?.confirm) return
|
||||
|
||||
Taro.showLoading({ title: '提交中...' })
|
||||
|
||||
// 退款相关操作使用退款接口:PUT /api/shop/shop-order/refund
|
||||
await refundShopOrder({
|
||||
orderId: order.orderId,
|
||||
orderStatus: 4 // 退款申请中
|
||||
// Best-effort: pass refund amount when available; backend may ignore and calculate by orderId.
|
||||
refundMoney: order.payPrice || order.totalPrice,
|
||||
// Backend requires orderStatus to determine refund flow (client applying).
|
||||
orderStatus: 7
|
||||
});
|
||||
|
||||
// 更新本地状态
|
||||
Taro.showToast({ title: '退款申请已提交', icon: 'success' })
|
||||
|
||||
// 乐观更新本地状态,提升反馈速度
|
||||
setList(prev => prev.map(item =>
|
||||
item.orderId === order.orderId ? {...item, orderStatus: 4} : item
|
||||
));
|
||||
item.orderId === order.orderId ? { ...item, orderStatus: 7 } : item
|
||||
))
|
||||
|
||||
// 刷新列表以服务端为准
|
||||
void reload(true)
|
||||
props.onReload?.()
|
||||
|
||||
// 跳转到退款申请页面
|
||||
Taro.navigateTo({
|
||||
url: `/user/order/refund/index?orderId=${order.orderId}&orderNo=${order.orderNo}`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新订单状态失败:', error);
|
||||
console.error('申请退款失败:', error);
|
||||
Taro.showToast({
|
||||
title: '操作失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
try {
|
||||
Taro.hideLoading()
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -839,7 +865,7 @@ function OrderList(props: OrderListProps) {
|
||||
)}
|
||||
|
||||
{/* 待发货状态:显示申请退款 */}
|
||||
{item.payStatus && isWithinRefundWindow(item.payTime, 60) && item.deliveryStatus === 10 && item.orderStatus !== 2 && item.orderStatus !== 4 && !isOrderCompleted(item) && (
|
||||
{item.payStatus && isWithinRefundWindow(item.payTime, 60) && item.deliveryStatus === 10 && item.orderStatus !== 2 && item.orderStatus !== 4 && item.orderStatus !== 7 && !isOrderCompleted(item) && (
|
||||
<Button size={'small'} onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
applyRefund(item);
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
Empty,
|
||||
InputNumber
|
||||
} from '@nutui/nutui-react-taro'
|
||||
import { applyAfterSale } from '@/api/afterSale'
|
||||
import { updateShopOrder } from '@/api/shop/shopOrder'
|
||||
import { getShopOrder, refundShopOrder } from '@/api/shop/shopOrder'
|
||||
import { listShopOrderGoods } from '@/api/shop/shopOrderGoods'
|
||||
import './index.scss'
|
||||
|
||||
// 订单商品信息
|
||||
@@ -84,42 +84,48 @@ const RefundPage: React.FC = () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
|
||||
// 模拟API调用
|
||||
const mockOrderGoods: OrderGoods[] = [
|
||||
{
|
||||
goodsId: '1',
|
||||
goodsName: 'iPhone 15 Pro Max 256GB 深空黑色',
|
||||
goodsImage: 'https://via.placeholder.com/100x100',
|
||||
goodsPrice: 9999,
|
||||
goodsNum: 1,
|
||||
canRefundNum: 1,
|
||||
skuInfo: '颜色:深空黑色,容量:256GB'
|
||||
},
|
||||
{
|
||||
goodsId: '2',
|
||||
goodsName: 'AirPods Pro 第三代',
|
||||
goodsImage: 'https://via.placeholder.com/100x100',
|
||||
goodsPrice: 1999,
|
||||
goodsNum: 2,
|
||||
canRefundNum: 2,
|
||||
skuInfo: '颜色:白色'
|
||||
const numericOrderId = Number(orderId)
|
||||
if (!Number.isFinite(numericOrderId) || numericOrderId <= 0) {
|
||||
setOrderGoods([])
|
||||
setOrderAmount(0)
|
||||
return
|
||||
}
|
||||
|
||||
const order = await getShopOrder(numericOrderId)
|
||||
|
||||
// 优先使用订单详情自带的 orderGoods;缺失时补拉一次商品明细
|
||||
const rawGoods = (order?.orderGoods && order.orderGoods.length)
|
||||
? order.orderGoods
|
||||
: (await listShopOrderGoods({ orderId: numericOrderId })) || []
|
||||
|
||||
const goods: OrderGoods[] = (rawGoods || []).map((g: any) => {
|
||||
const qty = Number(g?.totalNum ?? g?.quantity ?? 1)
|
||||
const price = Number(g?.price ?? 0)
|
||||
return {
|
||||
goodsId: String(g?.goodsId ?? g?.itemId ?? ''),
|
||||
goodsName: String(g?.goodsName || g?.goodsTitle || g?.title || ''),
|
||||
goodsImage: String(g?.image || ''),
|
||||
goodsPrice: Number.isFinite(price) ? price : 0,
|
||||
goodsNum: Number.isFinite(qty) && qty > 0 ? qty : 1,
|
||||
canRefundNum: Number.isFinite(qty) && qty > 0 ? qty : 1,
|
||||
skuInfo: g?.spec ? String(g.spec) : (g?.specInfo ? String(g.specInfo) : undefined)
|
||||
}
|
||||
]
|
||||
}).filter(g => !!g.goodsId)
|
||||
|
||||
const totalAmount = mockOrderGoods.reduce((sum, goods) =>
|
||||
sum + goods.goodsPrice * goods.goodsNum, 0
|
||||
)
|
||||
// 优先使用订单的实付金额;兜底按商品合计
|
||||
const amountFromOrder = Number(order?.payPrice ?? order?.totalPrice ?? 0)
|
||||
const totalAmount = (Number.isFinite(amountFromOrder) && amountFromOrder > 0)
|
||||
? amountFromOrder
|
||||
: goods.reduce((sum, g) => sum + g.goodsPrice * g.goodsNum, 0)
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
setOrderGoods(mockOrderGoods)
|
||||
setOrderGoods(goods)
|
||||
setOrderAmount(totalAmount)
|
||||
|
||||
// 初始化退款申请信息
|
||||
setRefundApp(prev => ({
|
||||
...prev,
|
||||
refundAmount: totalAmount,
|
||||
refundGoods: mockOrderGoods.map(goods => ({
|
||||
refundGoods: goods.map(goods => ({
|
||||
goodsId: goods.goodsId,
|
||||
refundNum: goods.goodsNum
|
||||
}))
|
||||
@@ -234,50 +240,20 @@ const RefundPage: React.FC = () => {
|
||||
|
||||
setSubmitting(true)
|
||||
|
||||
// 构造请求参数
|
||||
const params = {
|
||||
orderId: orderId || '',
|
||||
type: 'refund' as const,
|
||||
reason: refundApp.refundReason,
|
||||
description: refundApp.refundDescription,
|
||||
amount: refundApp.refundAmount,
|
||||
contactPhone: refundApp.contactPhone,
|
||||
evidenceImages: refundApp.evidenceImages,
|
||||
goodsItems: refundApp.refundGoods.filter(item => item.refundNum > 0).map(item => ({
|
||||
goodsId: item.goodsId,
|
||||
quantity: item.refundNum
|
||||
}))
|
||||
const numericOrderId = Number(orderId)
|
||||
if (!Number.isFinite(numericOrderId) || numericOrderId <= 0) {
|
||||
throw new Error('订单信息错误')
|
||||
}
|
||||
|
||||
// 调用API提交退款申请
|
||||
const result = await applyAfterSale(params)
|
||||
|
||||
if (result.success) {
|
||||
// 更新订单状态为"退款申请中"
|
||||
if (orderId) {
|
||||
try {
|
||||
await updateShopOrder({
|
||||
orderId: parseInt(orderId),
|
||||
orderStatus: 4 // 退款申请中
|
||||
})
|
||||
} catch (updateError) {
|
||||
console.error('更新订单状态失败:', updateError)
|
||||
// 即使更新订单状态失败,也继续执行后续操作
|
||||
}
|
||||
}
|
||||
// 退款相关操作使用退款接口:PUT /api/shop/shop-order/refund
|
||||
await refundShopOrder({
|
||||
orderId: numericOrderId,
|
||||
refundMoney: String(refundApp.refundAmount),
|
||||
orderStatus: 7
|
||||
})
|
||||
|
||||
Taro.showToast({
|
||||
title: '退款申请提交成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 延迟返回上一页
|
||||
setTimeout(() => {
|
||||
Taro.navigateBack()
|
||||
}, 1500)
|
||||
} else {
|
||||
throw new Error(result.message || '提交失败')
|
||||
}
|
||||
Taro.showToast({ title: '退款申请提交成功', icon: 'success' })
|
||||
setTimeout(() => Taro.navigateBack(), 1200)
|
||||
|
||||
} catch (error) {
|
||||
console.error('提交退款申请失败:', error)
|
||||
@@ -316,6 +292,9 @@ const RefundPage: React.FC = () => {
|
||||
)
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
return (
|
||||
<View className="refund-page">
|
||||
{/* 订单信息 */}
|
||||
@@ -369,7 +348,7 @@ const RefundPage: React.FC = () => {
|
||||
value={refundNum}
|
||||
min={0}
|
||||
max={goods.canRefundNum}
|
||||
onChange={(value) => updateGoodsRefundNum(goods.goodsId, value)}
|
||||
onChange={(value) => updateGoodsRefundNum(goods.goodsId, Number(value))}
|
||||
/>
|
||||
<Text className="max-num">最多{goods.canRefundNum}件</Text>
|
||||
</View>
|
||||
@@ -418,6 +397,7 @@ const RefundPage: React.FC = () => {
|
||||
<View className="evidence-section">
|
||||
<View className="section-title">上传凭证(可选)</View>
|
||||
<Uploader
|
||||
// @ts-ignore
|
||||
value={refundApp.evidenceImages.map(url => ({ url }))}
|
||||
onChange={handleImageUpload}
|
||||
multiple
|
||||
@@ -454,4 +434,4 @@ const RefundPage: React.FC = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export default RefundPage
|
||||
export default RefundPage
|
||||
|
||||
Reference in New Issue
Block a user