refactor(invite): 重构邀请二维码生成逻辑
- 优化了 generateMiniProgramCode 函数,直接返回完整的二维码 URL - 移除了未使用的 getInviteStats 函数调用 - 增加了二维码加载失败时的错误处理和重新生成逻辑 -调整了页面布局,隐藏了邀请统计数据部分
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
import type { ApiResult, PageResult } from '@/api';
|
import type { ApiResult, PageResult } from '@/api';
|
||||||
import { SERVER_API_URL } from '@/utils/server';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小程序码生成参数
|
* 小程序码生成参数
|
||||||
@@ -96,14 +95,13 @@ export interface InviteRecordParam {
|
|||||||
* 生成小程序码
|
* 生成小程序码
|
||||||
*/
|
*/
|
||||||
export async function generateMiniProgramCode(data: MiniProgramCodeParam) {
|
export async function generateMiniProgramCode(data: MiniProgramCodeParam) {
|
||||||
const res = await request.get<ApiResult<string>>(
|
try {
|
||||||
'/wx-login/getOrderQRCodeUnlimited/' + data.scene
|
const url = '/wx-login/getOrderQRCodeUnlimited/' + data.scene;
|
||||||
);
|
// 由于接口直接返回图片buffer,我们直接构建完整的URL
|
||||||
console.log(res,'res....')
|
return `${API_BASE_URL}${url}`;
|
||||||
if (res.code === 0) {
|
} catch (error: any) {
|
||||||
return res.data;
|
throw new Error(error.message || '生成小程序码失败');
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,7 +124,7 @@ export async function generateInviteCode(inviterId: number) {
|
|||||||
*/
|
*/
|
||||||
export async function createInviteRelation(data: InviteRelationParam) {
|
export async function createInviteRelation(data: InviteRelationParam) {
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
const res = await request.post<ApiResult<unknown>>(
|
||||||
SERVER_API_URL + '/invite/create-relation',
|
'/invite/create-relation',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -140,7 +138,7 @@ export async function createInviteRelation(data: InviteRelationParam) {
|
|||||||
*/
|
*/
|
||||||
export async function processInviteScene(scene: string, userId: number) {
|
export async function processInviteScene(scene: string, userId: number) {
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
const res = await request.post<ApiResult<unknown>>(
|
||||||
SERVER_API_URL + '/invite/process-scene',
|
'/invite/process-scene',
|
||||||
{ scene, userId }
|
{ scene, userId }
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -154,7 +152,7 @@ export async function processInviteScene(scene: string, userId: number) {
|
|||||||
*/
|
*/
|
||||||
export async function getInviteStats(inviterId: number) {
|
export async function getInviteStats(inviterId: number) {
|
||||||
const res = await request.get<ApiResult<InviteStats>>(
|
const res = await request.get<ApiResult<InviteStats>>(
|
||||||
SERVER_API_URL + `/invite/stats/${inviterId}`
|
`/invite/stats/${inviterId}`
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
return res.data;
|
return res.data;
|
||||||
@@ -167,7 +165,7 @@ export async function getInviteStats(inviterId: number) {
|
|||||||
*/
|
*/
|
||||||
export async function pageInviteRecords(params: InviteRecordParam) {
|
export async function pageInviteRecords(params: InviteRecordParam) {
|
||||||
const res = await request.get<ApiResult<PageResult<InviteRecord>>>(
|
const res = await request.get<ApiResult<PageResult<InviteRecord>>>(
|
||||||
SERVER_API_URL + '/invite/records/page',
|
'/invite/records/page',
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -181,7 +179,7 @@ export async function pageInviteRecords(params: InviteRecordParam) {
|
|||||||
*/
|
*/
|
||||||
export async function getMyInviteRecords(params: InviteRecordParam) {
|
export async function getMyInviteRecords(params: InviteRecordParam) {
|
||||||
const res = await request.get<ApiResult<PageResult<InviteRecord>>>(
|
const res = await request.get<ApiResult<PageResult<InviteRecord>>>(
|
||||||
SERVER_API_URL + '/invite/my-records',
|
'/invite/my-records',
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -195,7 +193,7 @@ export async function getMyInviteRecords(params: InviteRecordParam) {
|
|||||||
*/
|
*/
|
||||||
export async function validateInviteCode(scene: string) {
|
export async function validateInviteCode(scene: string) {
|
||||||
const res = await request.post<ApiResult<{ valid: boolean; inviterId?: number; source?: string }>>(
|
const res = await request.post<ApiResult<{ valid: boolean; inviterId?: number; source?: string }>>(
|
||||||
SERVER_API_URL + '/invite/validate-code',
|
'/invite/validate-code',
|
||||||
{ scene }
|
{ scene }
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -209,7 +207,7 @@ export async function validateInviteCode(scene: string) {
|
|||||||
*/
|
*/
|
||||||
export async function updateInviteStatus(inviteId: number, status: 'registered' | 'activated') {
|
export async function updateInviteStatus(inviteId: number, status: 'registered' | 'activated') {
|
||||||
const res = await request.put<ApiResult<unknown>>(
|
const res = await request.put<ApiResult<unknown>>(
|
||||||
SERVER_API_URL + `/invite/update-status/${inviteId}`,
|
`/invite/update-status/${inviteId}`,
|
||||||
{ status }
|
{ status }
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -229,7 +227,7 @@ export async function getInviteRanking(params?: { limit?: number; period?: 'day'
|
|||||||
successCount: number;
|
successCount: number;
|
||||||
conversionRate: number;
|
conversionRate: number;
|
||||||
}>>>(
|
}>>>(
|
||||||
SERVER_API_URL + '/invite/ranking',
|
'/invite/ranking',
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { PageParam } from '@/api/index';
|
import type { PageParam } from '@/api/index';
|
||||||
|
import {OrderGoods} from "@/api/system/orderGoods/model";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单
|
* 订单
|
||||||
@@ -144,6 +145,8 @@ export interface ShopOrder {
|
|||||||
selfTakeCode?: string;
|
selfTakeCode?: string;
|
||||||
// 是否已收到赠品
|
// 是否已收到赠品
|
||||||
hasTakeGift?: string;
|
hasTakeGift?: string;
|
||||||
|
// 订单商品项
|
||||||
|
orderGoods?: OrderGoods[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,61 +4,66 @@ import {Button, Loading} from '@nutui/nutui-react-taro'
|
|||||||
import {Share, Download, Copy, QrCode} from '@nutui/icons-react-taro'
|
import {Share, Download, Copy, QrCode} from '@nutui/icons-react-taro'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import {useDealerUser} from '@/hooks/useDealerUser'
|
import {useDealerUser} from '@/hooks/useDealerUser'
|
||||||
import {generateInviteCode, getInviteStats} from '@/api/invite'
|
import {generateInviteCode} from '@/api/invite'
|
||||||
import type {InviteStats} from '@/api/invite'
|
// import type {InviteStats} from '@/api/invite'
|
||||||
import {businessGradients} from '@/styles/gradients'
|
import {businessGradients} from '@/styles/gradients'
|
||||||
|
|
||||||
const DealerQrcode: React.FC = () => {
|
const DealerQrcode: React.FC = () => {
|
||||||
const [miniProgramCodeUrl, setMiniProgramCodeUrl] = useState<string>('')
|
const [miniProgramCodeUrl, setMiniProgramCodeUrl] = useState<string>('')
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
const [inviteStats, setInviteStats] = useState<InviteStats | null>(null)
|
// const [inviteStats, setInviteStats] = useState<InviteStats | null>(null)
|
||||||
const [statsLoading, setStatsLoading] = useState<boolean>(false)
|
// const [statsLoading, setStatsLoading] = useState<boolean>(false)
|
||||||
const {dealerUser} = useDealerUser()
|
const {dealerUser} = useDealerUser()
|
||||||
|
|
||||||
// 生成小程序码
|
// 生成小程序码
|
||||||
const generateMiniProgramCode = async () => {
|
const generateMiniProgramCode = async () => {
|
||||||
if (!dealerUser?.userId) return
|
if (!dealerUser?.userId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
// 生成邀请小程序码
|
// 生成邀请小程序码
|
||||||
const codeUrl = await generateInviteCode(dealerUser.userId)
|
const codeUrl = await generateInviteCode(dealerUser.userId)
|
||||||
console.log('小程序码生成成功:', codeUrl)
|
|
||||||
if (codeUrl) {
|
if (codeUrl) {
|
||||||
setMiniProgramCodeUrl(codeUrl)
|
setMiniProgramCodeUrl(codeUrl)
|
||||||
|
} else {
|
||||||
|
throw new Error('返回的小程序码URL为空')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('生成小程序码失败:', error)
|
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '生成小程序码失败',
|
title: error.message || '生成小程序码失败',
|
||||||
icon: 'error'
|
icon: 'error'
|
||||||
})
|
})
|
||||||
|
// 清空之前的二维码
|
||||||
|
setMiniProgramCodeUrl('')
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取邀请统计数据
|
// 获取邀请统计数据
|
||||||
const fetchInviteStats = async () => {
|
// const fetchInviteStats = async () => {
|
||||||
if (!dealerUser?.userId) return
|
// if (!dealerUser?.userId) return
|
||||||
|
//
|
||||||
try {
|
// try {
|
||||||
setStatsLoading(true)
|
// setStatsLoading(true)
|
||||||
const stats = await getInviteStats(dealerUser.userId)
|
// const stats = await getInviteStats(dealerUser.userId)
|
||||||
stats && setInviteStats(stats)
|
// stats && setInviteStats(stats)
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('获取邀请统计失败:', error)
|
// // 静默处理错误,不影响用户体验
|
||||||
} finally {
|
// } finally {
|
||||||
setStatsLoading(false)
|
// setStatsLoading(false)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 初始化生成小程序码和获取统计数据
|
// 初始化生成小程序码和获取统计数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dealerUser?.userId) {
|
if (dealerUser?.userId) {
|
||||||
generateMiniProgramCode()
|
generateMiniProgramCode()
|
||||||
fetchInviteStats()
|
// fetchInviteStats()
|
||||||
}
|
}
|
||||||
}, [dealerUser?.userId])
|
}, [dealerUser?.userId])
|
||||||
|
|
||||||
@@ -189,11 +194,6 @@ const DealerQrcode: React.FC = () => {
|
|||||||
|
|
||||||
<View className="px-4">
|
<View className="px-4">
|
||||||
{/* 小程序码展示区 */}
|
{/* 小程序码展示区 */}
|
||||||
{/*<Image*/}
|
|
||||||
{/* src={'http://127.0.0.1:9200/api/wx-login/getOrderQRCodeUnlimited/33103'}*/}
|
|
||||||
{/* className="w-full h-full"*/}
|
|
||||||
{/* mode="aspectFit"*/}
|
|
||||||
{/*/>*/}
|
|
||||||
<View className="bg-white rounded-2xl p-6 mb-6 shadow-sm">
|
<View className="bg-white rounded-2xl p-6 mb-6 shadow-sm">
|
||||||
<View className="text-center">
|
<View className="text-center">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
@@ -207,6 +207,20 @@ const DealerQrcode: React.FC = () => {
|
|||||||
src={miniProgramCodeUrl}
|
src={miniProgramCodeUrl}
|
||||||
className="w-full h-full"
|
className="w-full h-full"
|
||||||
mode="aspectFit"
|
mode="aspectFit"
|
||||||
|
onError={() => {
|
||||||
|
Taro.showModal({
|
||||||
|
title: '二维码加载失败',
|
||||||
|
content: '请检查网络连接或联系管理员',
|
||||||
|
showCancel: true,
|
||||||
|
confirmText: '重新生成',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
generateMiniProgramCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
@@ -227,9 +241,11 @@ const DealerQrcode: React.FC = () => {
|
|||||||
<View className="text-lg font-semibold text-gray-800 mb-2">
|
<View className="text-lg font-semibold text-gray-800 mb-2">
|
||||||
扫码加入我的团队
|
扫码加入我的团队
|
||||||
</View>
|
</View>
|
||||||
<View className="text-sm text-gray-500 mb-6">
|
<View className="text-sm text-gray-500 mb-4">
|
||||||
好友扫描小程序码即可直接进入小程序并建立邀请关系
|
好友扫描小程序码即可直接进入小程序并建立邀请关系
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -273,7 +289,7 @@ const DealerQrcode: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 推广说明 */}
|
{/* 推广说明 */}
|
||||||
<View className="bg-white rounded-2xl p-4 mt-6">
|
<View className="bg-white rounded-2xl p-4 mt-6 hidden">
|
||||||
<Text className="font-semibold text-gray-800 mb-3">推广说明</Text>
|
<Text className="font-semibold text-gray-800 mb-3">推广说明</Text>
|
||||||
<View className="space-y-2">
|
<View className="space-y-2">
|
||||||
<View className="flex items-start">
|
<View className="flex items-start">
|
||||||
@@ -298,82 +314,82 @@ const DealerQrcode: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 邀请统计数据 */}
|
{/* 邀请统计数据 */}
|
||||||
<View className="bg-white rounded-2xl p-4 mt-4 mb-6">
|
{/*<View className="bg-white rounded-2xl p-4 mt-4 mb-6">*/}
|
||||||
<Text className="font-semibold text-gray-800 mb-3">我的邀请数据</Text>
|
{/* <Text className="font-semibold text-gray-800 mb-3">我的邀请数据</Text>*/}
|
||||||
{statsLoading ? (
|
{/* {statsLoading ? (*/}
|
||||||
<View className="flex items-center justify-center py-8">
|
{/* <View className="flex items-center justify-center py-8">*/}
|
||||||
<Loading/>
|
{/* <Loading/>*/}
|
||||||
<Text className="text-gray-500 mt-2">加载中...</Text>
|
{/* <Text className="text-gray-500 mt-2">加载中...</Text>*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
) : inviteStats ? (
|
{/* ) : inviteStats ? (*/}
|
||||||
<View className="space-y-4">
|
{/* <View className="space-y-4">*/}
|
||||||
<View className="grid grid-cols-2 gap-4">
|
{/* <View className="grid grid-cols-2 gap-4">*/}
|
||||||
<View className="text-center">
|
{/* <View className="text-center">*/}
|
||||||
<Text className="text-2xl font-bold text-blue-500">
|
{/* <Text className="text-2xl font-bold text-blue-500">*/}
|
||||||
{inviteStats.totalInvites || 0}
|
{/* {inviteStats.totalInvites || 0}*/}
|
||||||
</Text>
|
{/* </Text>*/}
|
||||||
<Text className="text-sm text-gray-500">总邀请数</Text>
|
{/* <Text className="text-sm text-gray-500">总邀请数</Text>*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
<View className="text-center">
|
{/* <View className="text-center">*/}
|
||||||
<Text className="text-2xl font-bold text-green-500">
|
{/* <Text className="text-2xl font-bold text-green-500">*/}
|
||||||
{inviteStats.successfulRegistrations || 0}
|
{/* {inviteStats.successfulRegistrations || 0}*/}
|
||||||
</Text>
|
{/* </Text>*/}
|
||||||
<Text className="text-sm text-gray-500">成功注册</Text>
|
{/* <Text className="text-sm text-gray-500">成功注册</Text>*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
|
|
||||||
<View className="grid grid-cols-2 gap-4">
|
{/* <View className="grid grid-cols-2 gap-4">*/}
|
||||||
<View className="text-center">
|
{/* <View className="text-center">*/}
|
||||||
<Text className="text-2xl font-bold text-purple-500">
|
{/* <Text className="text-2xl font-bold text-purple-500">*/}
|
||||||
{inviteStats.conversionRate ? `${(inviteStats.conversionRate * 100).toFixed(1)}%` : '0%'}
|
{/* {inviteStats.conversionRate ? `${(inviteStats.conversionRate * 100).toFixed(1)}%` : '0%'}*/}
|
||||||
</Text>
|
{/* </Text>*/}
|
||||||
<Text className="text-sm text-gray-500">转化率</Text>
|
{/* <Text className="text-sm text-gray-500">转化率</Text>*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
<View className="text-center">
|
{/* <View className="text-center">*/}
|
||||||
<Text className="text-2xl font-bold text-orange-500">
|
{/* <Text className="text-2xl font-bold text-orange-500">*/}
|
||||||
{inviteStats.todayInvites || 0}
|
{/* {inviteStats.todayInvites || 0}*/}
|
||||||
</Text>
|
{/* </Text>*/}
|
||||||
<Text className="text-sm text-gray-500">今日邀请</Text>
|
{/* <Text className="text-sm text-gray-500">今日邀请</Text>*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
|
|
||||||
{/* 邀请来源统计 */}
|
{/* /!* 邀请来源统计 *!/*/}
|
||||||
{inviteStats.sourceStats && inviteStats.sourceStats.length > 0 && (
|
{/* {inviteStats.sourceStats && inviteStats.sourceStats.length > 0 && (*/}
|
||||||
<View className="mt-4">
|
{/* <View className="mt-4">*/}
|
||||||
<Text className="text-sm font-medium text-gray-700 mb-2">邀请来源分布</Text>
|
{/* <Text className="text-sm font-medium text-gray-700 mb-2">邀请来源分布</Text>*/}
|
||||||
<View className="space-y-2">
|
{/* <View className="space-y-2">*/}
|
||||||
{inviteStats.sourceStats.map((source, index) => (
|
{/* {inviteStats.sourceStats.map((source, index) => (*/}
|
||||||
<View key={index} className="flex items-center justify-between py-2 px-3 bg-gray-50 rounded-lg">
|
{/* <View key={index} className="flex items-center justify-between py-2 px-3 bg-gray-50 rounded-lg">*/}
|
||||||
<View className="flex items-center">
|
{/* <View className="flex items-center">*/}
|
||||||
<View className="w-3 h-3 rounded-full bg-blue-500 mr-2"></View>
|
{/* <View className="w-3 h-3 rounded-full bg-blue-500 mr-2"></View>*/}
|
||||||
<Text className="text-sm text-gray-700">{source.source}</Text>
|
{/* <Text className="text-sm text-gray-700">{source.source}</Text>*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
<View className="text-right">
|
{/* <View className="text-right">*/}
|
||||||
<Text className="text-sm font-medium text-gray-800">{source.count}</Text>
|
{/* <Text className="text-sm font-medium text-gray-800">{source.count}</Text>*/}
|
||||||
<Text className="text-xs text-gray-500">
|
{/* <Text className="text-xs text-gray-500">*/}
|
||||||
{source.conversionRate ? `${(source.conversionRate * 100).toFixed(1)}%` : '0%'}
|
{/* {source.conversionRate ? `${(source.conversionRate * 100).toFixed(1)}%` : '0%'}*/}
|
||||||
</Text>
|
{/* </Text>*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
))}
|
{/* ))}*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
)}
|
{/* )}*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
) : (
|
{/* ) : (*/}
|
||||||
<View className="text-center py-8">
|
{/* <View className="text-center py-8">*/}
|
||||||
<View className="text-gray-500">暂无邀请数据</View>
|
{/* <View className="text-gray-500">暂无邀请数据</View>*/}
|
||||||
<Button
|
{/* <Button*/}
|
||||||
size="small"
|
{/* size="small"*/}
|
||||||
type="primary"
|
{/* type="primary"*/}
|
||||||
className="mt-2"
|
{/* className="mt-2"*/}
|
||||||
onClick={fetchInviteStats}
|
{/* onClick={fetchInviteStats}*/}
|
||||||
>
|
{/* >*/}
|
||||||
刷新数据
|
{/* 刷新数据*/}
|
||||||
</Button>
|
{/* </Button>*/}
|
||||||
</View>
|
{/* </View>*/}
|
||||||
)}
|
{/* )}*/}
|
||||||
</View>
|
{/*</View>*/}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {Cell, CellGroup, Image, Space, Button} from '@nutui/nutui-react-taro'
|
import {Cell, CellGroup, Image, Space, Button} from '@nutui/nutui-react-taro'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
|
import {View} from '@tarojs/components'
|
||||||
import {ShopOrder} from "@/api/shop/shopOrder/model";
|
import {ShopOrder} from "@/api/shop/shopOrder/model";
|
||||||
import {getShopOrder, updateShopOrder} from "@/api/shop/shopOrder";
|
import {getShopOrder, updateShopOrder} from "@/api/shop/shopOrder";
|
||||||
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
|
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
|
||||||
@@ -65,14 +66,22 @@ const OrderDetail = () => {
|
|||||||
|
|
||||||
const getPayTypeText = (payType?: number) => {
|
const getPayTypeText = (payType?: number) => {
|
||||||
switch (payType) {
|
switch (payType) {
|
||||||
case 0: return '余额支付';
|
case 0:
|
||||||
case 1: return '微信支付';
|
return '余额支付';
|
||||||
case 102: return '微信Native';
|
case 1:
|
||||||
case 2: return '会员卡支付';
|
return '微信支付';
|
||||||
case 3: return '支付宝';
|
case 102:
|
||||||
case 4: return '现金';
|
return '微信Native';
|
||||||
case 5: return 'POS机';
|
case 2:
|
||||||
default: return '未知支付方式';
|
return '会员卡支付';
|
||||||
|
case 3:
|
||||||
|
return '支付宝';
|
||||||
|
case 4:
|
||||||
|
return '现金';
|
||||||
|
case 5:
|
||||||
|
return 'POS机';
|
||||||
|
default:
|
||||||
|
return '未知支付方式';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -101,7 +110,7 @@ const OrderDetail = () => {
|
|||||||
<div className={'order-detail-page'}>
|
<div className={'order-detail-page'}>
|
||||||
{/* 支付倒计时显示 - 详情页实时更新 */}
|
{/* 支付倒计时显示 - 详情页实时更新 */}
|
||||||
{!order.payStatus && order.orderStatus !== 2 && (
|
{!order.payStatus && order.orderStatus !== 2 && (
|
||||||
<div className="order-detail-countdown flex justify-center p-4 bg-red-50 border-b border-red-100">
|
<div className="order-detail-countdown flex justify-center p-4 border-b border-gray-50">
|
||||||
<PaymentCountdown
|
<PaymentCountdown
|
||||||
createTime={order.createTime}
|
createTime={order.createTime}
|
||||||
payStatus={order.payStatus}
|
payStatus={order.payStatus}
|
||||||
@@ -113,13 +122,7 @@ const OrderDetail = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<CellGroup title="订单信息">
|
<CellGroup>
|
||||||
<Cell title="订单编号" description={order.orderNo} />
|
|
||||||
<Cell title="下单时间" description={dayjs(order.createTime).format('YYYY-MM-DD HH:mm:ss')} />
|
|
||||||
<Cell title="订单状态" description={getOrderStatusText(order)} />
|
|
||||||
</CellGroup>
|
|
||||||
|
|
||||||
<CellGroup title="商品信息">
|
|
||||||
{orderGoodsList.map((item, index) => (
|
{orderGoodsList.map((item, index) => (
|
||||||
<Cell key={index}>
|
<Cell key={index}>
|
||||||
<div className={'flex items-center'}>
|
<div className={'flex items-center'}>
|
||||||
@@ -135,25 +138,36 @@ const OrderDetail = () => {
|
|||||||
))}
|
))}
|
||||||
</CellGroup>
|
</CellGroup>
|
||||||
|
|
||||||
<CellGroup title="收货信息">
|
<CellGroup>
|
||||||
|
<Cell title="订单编号" description={order.orderNo}/>
|
||||||
|
<Cell title="下单时间" description={dayjs(order.createTime).format('YYYY-MM-DD HH:mm:ss')}/>
|
||||||
|
<Cell title="订单状态" description={getOrderStatusText(order)}/>
|
||||||
|
</CellGroup>
|
||||||
|
|
||||||
|
<CellGroup>
|
||||||
<Cell title="收货人" description={order.realName}/>
|
<Cell title="收货人" description={order.realName}/>
|
||||||
<Cell title="手机号" description={order.phone}/>
|
<Cell title="手机号" description={order.phone}/>
|
||||||
<Cell title="收货地址" description={order.address}/>
|
<Cell title="收货地址" description={order.address}/>
|
||||||
</CellGroup>
|
</CellGroup>
|
||||||
|
|
||||||
<CellGroup title="支付信息">
|
{order.payStatus && (
|
||||||
|
<CellGroup>
|
||||||
<Cell title="支付方式" description={getPayTypeText(order.payType)}/>
|
<Cell title="支付方式" description={getPayTypeText(order.payType)}/>
|
||||||
<Cell title="实付金额" description={`¥${order.payPrice}`}/>
|
<Cell title="实付金额" description={`¥${order.payPrice}`}/>
|
||||||
</CellGroup>
|
</CellGroup>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className={'fixed-bottom'}>
|
<View className={'h5-div fixed z-50 bg-white w-full bottom-0 left-0 pt-4 pb-5 border-t border-gray-200'}>
|
||||||
|
<View className={'flex justify-end px-4'}>
|
||||||
<Space>
|
<Space>
|
||||||
{!order.payStatus && <Button onClick={() => console.log('取消订单')}>取消订单</Button>}
|
{!order.payStatus && <Button onClick={() => console.log('取消订单')}>取消订单</Button>}
|
||||||
{!order.payStatus && <Button type="primary" onClick={() => console.log('立即支付')}>立即支付</Button>}
|
{!order.payStatus && <Button type="primary" onClick={() => console.log('立即支付')}>立即支付</Button>}
|
||||||
{order.orderStatus === 1 && <Button onClick={() => console.log('申请退款')}>申请退款</Button>}
|
{order.orderStatus === 1 && <Button onClick={() => console.log('申请退款')}>申请退款</Button>}
|
||||||
{order.deliveryStatus === 20 && <Button type="primary" onClick={() => console.log('确认收货')}>确认收货</Button>}
|
{order.deliveryStatus === 20 &&
|
||||||
|
<Button type="primary" onClick={() => console.log('确认收货')}>确认收货</Button>}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</View>
|
||||||
|
</View>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import {View, Text} from '@tarojs/components'
|
|||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import {InfiniteLoading} from '@nutui/nutui-react-taro'
|
import {InfiniteLoading} from '@nutui/nutui-react-taro'
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {pageShopOrder, updateShopOrder} from "@/api/shop/shopOrder";
|
import {pageShopOrder, updateShopOrder, createOrder} from "@/api/shop/shopOrder";
|
||||||
import {ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model";
|
import {ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model";
|
||||||
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
|
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
|
||||||
import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model";
|
import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model";
|
||||||
import {copyText} from "@/utils/common";
|
import {copyText} from "@/utils/common";
|
||||||
import PaymentCountdown from "@/components/PaymentCountdown";
|
import PaymentCountdown from "@/components/PaymentCountdown";
|
||||||
|
import {PaymentType} from "@/utils/payment";
|
||||||
|
|
||||||
// 判断订单是否支付已过期
|
// 判断订单是否支付已过期
|
||||||
const isPaymentExpired = (createTime: string, timeoutHours: number = 24): boolean => {
|
const isPaymentExpired = (createTime: string, timeoutHours: number = 24): boolean => {
|
||||||
@@ -314,6 +315,124 @@ function OrderList(props: OrderListProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 立即支付
|
||||||
|
const payOrder = async (order: ShopOrder) => {
|
||||||
|
try {
|
||||||
|
if (!order.orderId || !order.orderNo) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '订单信息错误',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查订单是否已过期
|
||||||
|
if (order.createTime && isPaymentExpired(order.createTime)) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '订单已过期,无法支付',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查订单状态
|
||||||
|
if (order.payStatus) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '订单已支付',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.orderStatus === 2) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '订单已取消,无法支付',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Taro.showLoading({ title: '发起支付...' });
|
||||||
|
|
||||||
|
// 构建商品数据
|
||||||
|
const goodsItems = order.orderGoods?.map(goods => ({
|
||||||
|
goodsId: goods.goodsId,
|
||||||
|
quantity: goods.totalNum || 1
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
// 对于已存在的订单,我们需要重新发起支付
|
||||||
|
// 构建支付请求数据,包含完整的商品信息
|
||||||
|
const paymentData = {
|
||||||
|
orderId: order.orderId,
|
||||||
|
orderNo: order.orderNo,
|
||||||
|
goodsItems: goodsItems,
|
||||||
|
addressId: order.addressId,
|
||||||
|
payType: PaymentType.WECHAT
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('重新支付数据:', paymentData);
|
||||||
|
|
||||||
|
// 直接调用createOrder API进行重新支付
|
||||||
|
const result = await createOrder(paymentData as any);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('支付发起失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证微信支付必要参数
|
||||||
|
if (!result.timeStamp || !result.nonceStr || !result.package || !result.paySign) {
|
||||||
|
throw new Error('微信支付参数不完整');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用微信支付
|
||||||
|
await Taro.requestPayment({
|
||||||
|
timeStamp: result.timeStamp,
|
||||||
|
nonceStr: result.nonceStr,
|
||||||
|
package: result.package,
|
||||||
|
signType: result.signType as any,
|
||||||
|
paySign: result.paySign,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 支付成功
|
||||||
|
Taro.showToast({
|
||||||
|
title: '支付成功',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 重新加载订单列表
|
||||||
|
void reload(true);
|
||||||
|
props.onReload?.();
|
||||||
|
|
||||||
|
// 跳转到订单页面
|
||||||
|
setTimeout(() => {
|
||||||
|
Taro.navigateTo({ url: '/user/order/order' });
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('支付失败:', error);
|
||||||
|
|
||||||
|
let errorMessage = '支付失败,请重试';
|
||||||
|
if (error.message) {
|
||||||
|
if (error.message.includes('cancel')) {
|
||||||
|
errorMessage = '用户取消支付';
|
||||||
|
} else if (error.message.includes('余额不足')) {
|
||||||
|
errorMessage = '账户余额不足';
|
||||||
|
} else {
|
||||||
|
errorMessage = error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: errorMessage,
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
Taro.hideLoading();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void reload(true); // 首次加载或tab切换时重置页码
|
void reload(true); // 首次加载或tab切换时重置页码
|
||||||
}, [tapIndex]); // 监听tapIndex变化
|
}, [tapIndex]); // 监听tapIndex变化
|
||||||
@@ -499,7 +618,7 @@ function OrderList(props: OrderListProps) {
|
|||||||
}}>取消订单</Button>
|
}}>取消订单</Button>
|
||||||
<Button size={'small'} type="primary" onClick={(e) => {
|
<Button size={'small'} type="primary" onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
console.log('立即支付')
|
void payOrder(item);
|
||||||
}}>立即支付</Button>
|
}}>立即支付</Button>
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user