Files
template-10560/src/dealer/qrcode/index.tsx
赵忠林 1368155736 feat(dealer): 添加微信客服功能并优化订单和客户页面
- 在 app.config.ts 中添加微信客服页面路由
- 修改订单页面标题为"电费收益"
- 新增微信客服页面组件和样式
- 优化客户交易页面,添加搜索功能
- 更新二维码页面文案- 新增微信二维码图片说明文档
2025-09-03 14:43:41 +08:00

399 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, {useState, useEffect} from 'react'
import {View, Text, Image} from '@tarojs/components'
import {Button, Loading} from '@nutui/nutui-react-taro'
import {Share, Download, Copy, QrCode} from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro'
import {useDealerUser} from '@/hooks/useDealerUser'
import {generateInviteCode} from '@/api/invite'
// import type {InviteStats} from '@/api/invite'
import {businessGradients} from '@/styles/gradients'
const DealerQrcode: React.FC = () => {
const [miniProgramCodeUrl, setMiniProgramCodeUrl] = useState<string>('')
const [loading, setLoading] = useState<boolean>(false)
// const [inviteStats, setInviteStats] = useState<InviteStats | null>(null)
// const [statsLoading, setStatsLoading] = useState<boolean>(false)
const {dealerUser} = useDealerUser()
// 生成小程序码
const generateMiniProgramCode = async () => {
if (!dealerUser?.userId) {
return
}
try {
setLoading(true)
// 生成邀请小程序码
const codeUrl = await generateInviteCode(dealerUser.userId)
if (codeUrl) {
setMiniProgramCodeUrl(codeUrl)
} else {
throw new Error('返回的小程序码URL为空')
}
} catch (error: any) {
Taro.showToast({
title: error.message || '生成小程序码失败',
icon: 'error'
})
// 清空之前的二维码
setMiniProgramCodeUrl('')
} finally {
setLoading(false)
}
}
// 获取邀请统计数据
// const fetchInviteStats = async () => {
// if (!dealerUser?.userId) return
//
// try {
// setStatsLoading(true)
// const stats = await getInviteStats(dealerUser.userId)
// stats && setInviteStats(stats)
// } catch (error) {
// // 静默处理错误,不影响用户体验
// } finally {
// setStatsLoading(false)
// }
// }
// 初始化生成小程序码和获取统计数据
useEffect(() => {
if (dealerUser?.userId) {
generateMiniProgramCode()
// fetchInviteStats()
}
}, [dealerUser?.userId])
// 保存小程序码到相册
const saveMiniProgramCode = async () => {
if (!miniProgramCodeUrl) {
Taro.showToast({
title: '小程序码未生成',
icon: 'error'
})
return
}
try {
// 先下载图片到本地
const res = await Taro.downloadFile({
url: miniProgramCodeUrl
})
if (res.statusCode === 200) {
// 保存到相册
await Taro.saveImageToPhotosAlbum({
filePath: res.tempFilePath
})
Taro.showToast({
title: '保存成功',
icon: 'success'
})
}
} catch (error: any) {
if (error.errMsg?.includes('auth deny')) {
Taro.showModal({
title: '提示',
content: '需要您授权保存图片到相册',
success: (res) => {
if (res.confirm) {
Taro.openSetting()
}
}
})
} else {
Taro.showToast({
title: '保存失败',
icon: 'error'
})
}
}
}
// 复制邀请信息
const copyInviteInfo = () => {
if (!dealerUser?.userId) {
Taro.showToast({
title: '用户信息未加载',
icon: 'error'
})
return
}
const inviteText = `🎉 邀请您加入我的团队!
扫描小程序码或搜索"九云售电云"小程序,即可享受优质商品和服务!
💰 成为我的团队成员,一起赚取丰厚佣金
🎁 新用户专享优惠等你来拿
邀请码:${dealerUser.userId}
快来加入我们吧!`
Taro.setClipboardData({
data: inviteText,
success: () => {
Taro.showToast({
title: '邀请信息已复制',
icon: 'success'
})
}
})
}
// 分享小程序码
const shareMiniProgramCode = () => {
if (!dealerUser?.userId) {
Taro.showToast({
title: '用户信息未加载',
icon: 'error'
})
return
}
// 小程序分享
Taro.showShareMenu({
withShareTicket: true,
showShareItems: ['shareAppMessage']
})
}
if (!dealerUser) {
return (
<View className="bg-gray-50 min-h-screen flex items-center justify-center">
<Loading/>
<Text className="text-gray-500 mt-2">...</Text>
</View>
)
}
return (
<View className="bg-gray-50 min-h-screen">
{/* 头部卡片 */}
<View className="rounded-b-3xl p-6 mb-6 text-white relative overflow-hidden" style={{
background: businessGradients.dealer.header
}}>
{/* 装饰背景 */}
<View className="absolute w-32 h-32 rounded-full" style={{
backgroundColor: 'rgba(255, 255, 255, 0.1)',
top: '-16px',
right: '-16px'
}}></View>
<View className="relative z-10 flex flex-col">
<Text className="text-2xl font-bold mb-2 text-white"></Text>
<Text className="text-white text-opacity-80">
</Text>
</View>
</View>
<View className="px-4">
{/* 小程序码展示区 */}
<View className="bg-white rounded-2xl p-6 mb-6 shadow-sm">
<View className="text-center">
{loading ? (
<View className="w-48 h-48 mx-auto mb-4 flex items-center justify-center bg-gray-50 rounded-xl">
<Loading/>
<Text className="text-gray-500 mt-2">...</Text>
</View>
) : miniProgramCodeUrl ? (
<View className="w-48 h-48 mx-auto mb-4 bg-white rounded-xl shadow-sm p-4">
<Image
src={miniProgramCodeUrl}
className="w-full h-full"
mode="aspectFit"
onError={() => {
Taro.showModal({
title: '二维码加载失败',
content: '请检查网络连接或联系管理员',
showCancel: true,
confirmText: '重新生成',
success: (res) => {
if (res.confirm) {
generateMiniProgramCode();
}
}
});
}}
/>
</View>
) : (
<View className="w-48 h-48 mx-auto mb-4 flex items-center justify-center bg-gray-50 rounded-xl">
<QrCode size="48" className="text-gray-400 mb-2"/>
<Text className="text-gray-500"></Text>
<Button
size="small"
type="primary"
className="mt-2"
onClick={generateMiniProgramCode}
>
</Button>
</View>
)}
<View className="text-lg font-semibold text-gray-800 mb-2">
</View>
<View className="text-sm text-gray-500 mb-4">
</View>
</View>
</View>
{/* 操作按钮 */}
<View className={'gap-2'}>
<View className={'my-2'}>
<Button
type="primary"
size="large"
block
icon={<Download/>}
onClick={saveMiniProgramCode}
disabled={!miniProgramCodeUrl || loading}
>
</Button>
</View>
<View className={'my-2 bg-white'}>
<Button
size="large"
block
icon={<Copy/>}
onClick={copyInviteInfo}
disabled={!dealerUser?.userId || loading}
>
</Button>
</View>
<View className={'my-2 bg-white'}>
<Button
size="large"
block
fill="outline"
icon={<Share/>}
onClick={shareMiniProgramCode}
disabled={!dealerUser?.userId || loading}
>
</Button>
</View>
</View>
{/* 推广说明 */}
<View className="bg-white rounded-2xl p-4 mt-6 hidden">
<Text className="font-semibold text-gray-800 mb-3">广</Text>
<View className="space-y-2">
<View className="flex items-start">
<View className="w-2 h-2 bg-blue-500 rounded-full mt-2 mr-3 flex-shrink-0"></View>
<Text className="text-sm text-gray-600">
</Text>
</View>
<View className="flex items-start">
<View className="w-2 h-2 bg-green-500 rounded-full mt-2 mr-3 flex-shrink-0"></View>
<Text className="text-sm text-gray-600">
</Text>
</View>
<View className="flex items-start">
<View className="w-2 h-2 bg-purple-500 rounded-full mt-2 mr-3 flex-shrink-0"></View>
<Text className="text-sm text-gray-600">
</Text>
</View>
</View>
</View>
{/* 邀请统计数据 */}
{/*<View className="bg-white rounded-2xl p-4 mt-4 mb-6">*/}
{/* <Text className="font-semibold text-gray-800 mb-3">我的邀请数据</Text>*/}
{/* {statsLoading ? (*/}
{/* <View className="flex items-center justify-center py-8">*/}
{/* <Loading/>*/}
{/* <Text className="text-gray-500 mt-2">加载中...</Text>*/}
{/* </View>*/}
{/* ) : inviteStats ? (*/}
{/* <View className="space-y-4">*/}
{/* <View className="grid grid-cols-2 gap-4">*/}
{/* <View className="text-center">*/}
{/* <Text className="text-2xl font-bold text-blue-500">*/}
{/* {inviteStats.totalInvites || 0}*/}
{/* </Text>*/}
{/* <Text className="text-sm text-gray-500">总邀请数</Text>*/}
{/* </View>*/}
{/* <View className="text-center">*/}
{/* <Text className="text-2xl font-bold text-green-500">*/}
{/* {inviteStats.successfulRegistrations || 0}*/}
{/* </Text>*/}
{/* <Text className="text-sm text-gray-500">成功注册</Text>*/}
{/* </View>*/}
{/* </View>*/}
{/* <View className="grid grid-cols-2 gap-4">*/}
{/* <View className="text-center">*/}
{/* <Text className="text-2xl font-bold text-purple-500">*/}
{/* {inviteStats.conversionRate ? `${(inviteStats.conversionRate * 100).toFixed(1)}%` : '0%'}*/}
{/* </Text>*/}
{/* <Text className="text-sm text-gray-500">转化率</Text>*/}
{/* </View>*/}
{/* <View className="text-center">*/}
{/* <Text className="text-2xl font-bold text-orange-500">*/}
{/* {inviteStats.todayInvites || 0}*/}
{/* </Text>*/}
{/* <Text className="text-sm text-gray-500">今日邀请</Text>*/}
{/* </View>*/}
{/* </View>*/}
{/* /!* 邀请来源统计 *!/*/}
{/* {inviteStats.sourceStats && inviteStats.sourceStats.length > 0 && (*/}
{/* <View className="mt-4">*/}
{/* <Text className="text-sm font-medium text-gray-700 mb-2">邀请来源分布</Text>*/}
{/* <View className="space-y-2">*/}
{/* {inviteStats.sourceStats.map((source, index) => (*/}
{/* <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="w-3 h-3 rounded-full bg-blue-500 mr-2"></View>*/}
{/* <Text className="text-sm text-gray-700">{source.source}</Text>*/}
{/* </View>*/}
{/* <View className="text-right">*/}
{/* <Text className="text-sm font-medium text-gray-800">{source.count}</Text>*/}
{/* <Text className="text-xs text-gray-500">*/}
{/* {source.conversionRate ? `${(source.conversionRate * 100).toFixed(1)}%` : '0%'}*/}
{/* </Text>*/}
{/* </View>*/}
{/* </View>*/}
{/* ))}*/}
{/* </View>*/}
{/* </View>*/}
{/* )}*/}
{/* </View>*/}
{/* ) : (*/}
{/* <View className="text-center py-8">*/}
{/* <View className="text-gray-500">暂无邀请数据</View>*/}
{/* <Button*/}
{/* size="small"*/}
{/* type="primary"*/}
{/* className="mt-2"*/}
{/* onClick={fetchInviteStats}*/}
{/* >*/}
{/* 刷新数据*/}
{/* </Button>*/}
{/* </View>*/}
{/* )}*/}
{/*</View>*/}
</View>
</View>
)
}
export default DealerQrcode