forked from gxwebsoft/mp-10550
- 移除 config/env.ts 文件并将环境配置转换为 config/env.js - 更新 config/index.ts 中的导入路径以匹配新的 JavaScript 文件扩展名 - 修改 src/utils/server.ts 中的开发服务器 URL 配置 - 更新 tsconfig.json 的 include 配置移除 config 目录 - 调整环境配置中的 API 地址设置统一使用生产环境地址 - 更新 .workbuddy/expert-history.json 中的时间戳记录
494 lines
18 KiB
TypeScript
494 lines
18 KiB
TypeScript
import React, { useEffect } from 'react'
|
||
import {View, Text} from '@tarojs/components'
|
||
import {ConfigProvider, Button, Grid, Avatar, Badge} from '@nutui/nutui-react-taro'
|
||
import {
|
||
User,
|
||
Shopping,
|
||
Dongdong,
|
||
ArrowRight,
|
||
Purse,
|
||
People,
|
||
Scan,
|
||
Setting
|
||
} from '@nutui/icons-react-taro'
|
||
import {useDealerUser} from '@/hooks/useDealerUser'
|
||
import {useUser} from '@/hooks/useUser'
|
||
import { useThemeStyles } from '@/hooks/useTheme'
|
||
import { useRiderNotification } from '@/hooks/useRiderNotification'
|
||
import {businessGradients, cardGradients, gradientUtils} from '@/styles/gradients'
|
||
import {updateShopDealerUser} from '@/api/shop/shopDealerUser'
|
||
import Taro from '@tarojs/taro'
|
||
|
||
const DealerIndex: React.FC = () => {
|
||
const {
|
||
dealerUser,
|
||
error,
|
||
refresh,
|
||
} = useDealerUser()
|
||
|
||
// 获取用户角色信息
|
||
const { hasRole } = useUser()
|
||
|
||
// 配送员通知功能
|
||
const { pendingCount, startPolling, stopPolling, soundEnabled, toggleSound } = useRiderNotification()
|
||
|
||
// 页面生命周期管理
|
||
useEffect(() => {
|
||
startPolling()
|
||
return () => stopPolling()
|
||
}, [startPolling, stopPolling])
|
||
|
||
// 使用主题样式
|
||
const themeStyles = useThemeStyles()
|
||
|
||
// 导航到各个功能页面
|
||
const navigateToPage = (url: string) => {
|
||
Taro.navigateTo({url})
|
||
}
|
||
|
||
// 格式化金额
|
||
const formatMoney = (money?: string) => {
|
||
if (!money) return '0.00'
|
||
return parseFloat(money).toFixed(2)
|
||
}
|
||
|
||
// 格式化时间
|
||
const formatTime = (time?: string) => {
|
||
if (!time) return '-'
|
||
return new Date(time).toLocaleDateString()
|
||
}
|
||
|
||
// 获取用户主题
|
||
const userTheme = gradientUtils.getThemeByUserId(dealerUser?.userId)
|
||
|
||
// 获取渐变背景
|
||
const getGradientBackground = (themeColor?: string) => {
|
||
if (themeColor) {
|
||
const darkerColor = gradientUtils.adjustColorBrightness(themeColor, -30)
|
||
return gradientUtils.createGradient(themeColor, darkerColor)
|
||
}
|
||
return userTheme.background
|
||
}
|
||
|
||
console.log(getGradientBackground(),'getGradientBackground()')
|
||
|
||
// 判断是否是配送员
|
||
const isRider = hasRole('rider')
|
||
|
||
// 请求订阅消息授权
|
||
const handleRequestSubscribeMessage = () => {
|
||
// 微信订阅消息模板ID(需在微信公众平台配置后替换)
|
||
const templateIds = [
|
||
'YOUR_TEMPLATE_ID', // TODO: 替换为实际的订阅消息模板ID
|
||
]
|
||
|
||
// 过滤出有效的模板ID
|
||
const validTemplateIds = templateIds.filter(id => id && !id.includes('YOUR_'))
|
||
|
||
if (validTemplateIds.length === 0) {
|
||
Taro.showModal({
|
||
title: '提示',
|
||
content: '订阅消息功能尚未配置,请联系管理员',
|
||
showCancel: false
|
||
})
|
||
return
|
||
}
|
||
|
||
// 请求订阅
|
||
Taro.requestSubscribeMessage({
|
||
tmplIds: validTemplateIds,
|
||
success: (res) => {
|
||
console.log('订阅消息授权结果:', res)
|
||
const accepted = Object.values(res).some(v => v === 'accept')
|
||
if (accepted) {
|
||
Taro.showToast({
|
||
title: '订阅成功',
|
||
icon: 'success'
|
||
})
|
||
// 保存授权状态到本地
|
||
Taro.setStorageSync('rider_subscribed', '1')
|
||
} else {
|
||
Taro.showToast({
|
||
title: '您已拒绝订阅',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
console.error('订阅消息授权失败:', err)
|
||
Taro.showToast({
|
||
title: '授权失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
// 点击待使用金额 - 配送员专用:将冻结金额转入可提现
|
||
const handleFreezeMoneyClick = async () => {
|
||
// 检查是否是配送员
|
||
if (!isRider) {
|
||
return
|
||
}
|
||
|
||
// 检查冻结金额是否为 0
|
||
const freezeMoney = Number(dealerUser?.freezeMoney ?? 0)
|
||
if (freezeMoney <= 0) {
|
||
return
|
||
}
|
||
|
||
// 弹出确认框
|
||
Taro.showModal({
|
||
title: '确认操作',
|
||
content: `确定要将 ¥${freezeMoney.toFixed(2)} 转入钱包吗?`,
|
||
confirmText: '确定',
|
||
cancelText: '取消',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
try {
|
||
Taro.showLoading({ title: '处理中...' })
|
||
const currentMoney = Number(dealerUser?.money ?? 0)
|
||
await updateShopDealerUser({
|
||
id: dealerUser?.id,
|
||
money: (currentMoney + freezeMoney).toFixed(2),
|
||
freezeMoney: '0.00'
|
||
})
|
||
Taro.hideLoading()
|
||
Taro.showToast({
|
||
title: '更新成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
})
|
||
// 刷新数据
|
||
refresh()
|
||
} catch (error) {
|
||
Taro.hideLoading()
|
||
Taro.showToast({
|
||
title: '更新失败',
|
||
icon: 'none',
|
||
duration: 1500
|
||
})
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<View className="p-4">
|
||
<View className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
|
||
<Text className="text-red-600">{error}</Text>
|
||
</View>
|
||
<Button type="primary" onClick={refresh}>
|
||
重试
|
||
</Button>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<View className="bg-gray-100 min-h-screen">
|
||
<View>
|
||
{/*头部信息*/}
|
||
{dealerUser && (
|
||
<View
|
||
className="px-4 py-6 relative overflow-hidden"
|
||
style={{
|
||
...themeStyles.primaryBackground,
|
||
background: businessGradients.order.processing,
|
||
color: '#ffffff'
|
||
}}
|
||
>
|
||
{/* 装饰性背景元素 - 小程序兼容版本 */}
|
||
<View className="absolute w-32 h-32 rounded-full" style={{
|
||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||
top: '-16px',
|
||
right: '-16px'
|
||
}}></View>
|
||
<View className="absolute w-24 h-24 rounded-full" style={{
|
||
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
||
bottom: '-12px',
|
||
left: '-12px'
|
||
}}></View>
|
||
<View className="absolute w-16 h-16 rounded-full" style={{
|
||
backgroundColor: 'rgba(255, 255, 255, 0.05)',
|
||
top: '60px',
|
||
left: '120px'
|
||
}}></View>
|
||
<View className="flex items-center justify-between relative z-10 mb-4">
|
||
<Avatar
|
||
size="50"
|
||
src={dealerUser?.qrcode}
|
||
icon={<User/>}
|
||
className="mr-4"
|
||
style={{
|
||
border: '2px solid rgba(255, 255, 255, 0.3)'
|
||
}}
|
||
/>
|
||
<View className="flex-1 flex-col">
|
||
<View className="text-white text-lg font-bold mb-1" style={{
|
||
}}>
|
||
{dealerUser?.realName || '分销商'}
|
||
</View>
|
||
<View className="text-sm" style={{
|
||
color: 'rgba(255, 255, 255, 0.8)'
|
||
}}>
|
||
ID: {dealerUser.userId}
|
||
</View>
|
||
</View>
|
||
<View className="text-right hidden">
|
||
<Text className="text-xs" style={{
|
||
color: 'rgba(255, 255, 255, 0.9)'
|
||
}}>加入时间</Text>
|
||
<Text className="text-xs" style={{
|
||
color: 'rgba(255, 255, 255, 0.7)'
|
||
}}>
|
||
{formatTime(dealerUser.createTime)}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
{/* 佣金统计卡片 */}
|
||
{dealerUser && (
|
||
<View className="mx-4 -mt-6 rounded-xl p-4 relative z-10" style={cardGradients.elevated}>
|
||
<View className="mb-4">
|
||
<Text className="font-semibold text-gray-800">配送提成</Text>
|
||
</View>
|
||
<View className="grid grid-cols-3 gap-3">
|
||
<View className="text-center p-3 rounded-lg flex flex-col" style={{
|
||
background: businessGradients.money.available
|
||
}}>
|
||
<Text className="text-lg font-bold mb-1 text-white">
|
||
{formatMoney(dealerUser.money)}
|
||
</Text>
|
||
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}>本月配送佣金</Text>
|
||
</View>
|
||
<View
|
||
className="text-center p-3 rounded-lg flex flex-col"
|
||
style={{
|
||
background: businessGradients.money.frozen,
|
||
opacity: isRider && Number(dealerUser.freezeMoney ?? 0) > 0 ? 1 : 0.8
|
||
}}
|
||
onClick={isRider && Number(dealerUser.freezeMoney ?? 0) > 0 ? handleFreezeMoneyClick : undefined}
|
||
>
|
||
<Text className="text-lg font-bold mb-1 text-white">
|
||
{formatMoney(dealerUser.freezeMoney)}
|
||
</Text>
|
||
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}>
|
||
{isRider && Number(dealerUser.freezeMoney ?? 0) > 0 ? '待使用' : '待使用'}
|
||
</Text>
|
||
</View>
|
||
<View className="text-center p-3 rounded-lg flex flex-col" style={{
|
||
background: businessGradients.money.total
|
||
}}>
|
||
<Text className="text-lg font-bold mb-1 text-white">
|
||
{formatMoney(dealerUser.totalMoney)}
|
||
</Text>
|
||
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}>累计收入</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
{/* 团队统计 */}
|
||
{dealerUser && (
|
||
<View className="bg-white mx-4 mt-4 rounded-xl p-4 hidden">
|
||
<View className="flex items-center justify-between mb-4">
|
||
<Text className="font-semibold text-gray-800">我的邀请</Text>
|
||
<View
|
||
className="text-gray-400 text-sm flex items-center"
|
||
onClick={() => navigateToPage('/dealer/team/index')}
|
||
>
|
||
<Text>查看详情</Text>
|
||
<ArrowRight size="12"/>
|
||
</View>
|
||
</View>
|
||
<View className="grid grid-cols-3 gap-4">
|
||
<View className="text-center grid">
|
||
<Text className="text-xl font-bold text-purple-500 mb-1">
|
||
{dealerUser.firstNum || 0}
|
||
</Text>
|
||
<Text className="text-xs text-gray-500">一级成员</Text>
|
||
</View>
|
||
<View className="text-center grid">
|
||
<Text className="text-xl font-bold text-indigo-500 mb-1">
|
||
{dealerUser.secondNum || 0}
|
||
</Text>
|
||
<Text className="text-xs text-gray-500">二级成员</Text>
|
||
</View>
|
||
<View className="text-center grid">
|
||
<Text className="text-xl font-bold text-pink-500 mb-1">
|
||
{dealerUser.thirdNum || 0}
|
||
</Text>
|
||
<Text className="text-xs text-gray-500">三级成员</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
{/* 功能导航 */}
|
||
<View className="bg-white mx-4 mt-4 rounded-xl p-4">
|
||
<View className="font-semibold mb-4 text-gray-800">配送工具</View>
|
||
<ConfigProvider>
|
||
<Grid
|
||
columns={4}
|
||
className="no-border-grid"
|
||
style={{
|
||
'--nutui-grid-border-color': 'transparent',
|
||
'--nutui-grid-item-border-width': '0px',
|
||
border: 'none'
|
||
} as React.CSSProperties}
|
||
>
|
||
<Grid.Item text="配送订单" onClick={() => navigateToPage('/rider/orders/index')}>
|
||
<View className="text-center">
|
||
<View className="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center mx-auto mb-2 relative">
|
||
<Shopping color="#3b82f6" size="20"/>
|
||
{pendingCount > 0 && (
|
||
<Badge
|
||
value={pendingCount > 99 ? '99+' : pendingCount}
|
||
max={99}
|
||
style={{ position: 'absolute', top: '-4px', right: '-4px' }}
|
||
/>
|
||
)}
|
||
</View>
|
||
</View>
|
||
</Grid.Item>
|
||
|
||
<Grid.Item text={'收入明细'} onClick={() => navigateToPage('/rider/withdraw/index')}>
|
||
<View className="text-center">
|
||
<View className="w-12 h-12 bg-green-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||
<Purse color="#10b981" size="20"/>
|
||
</View>
|
||
</View>
|
||
</Grid.Item>
|
||
|
||
<Grid.Item text={'配送小区'} onClick={() => navigateToPage('/rider/team/index')}>
|
||
<View className="text-center">
|
||
<View className="w-12 h-12 bg-purple-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||
<People color="#8b5cf6" size="20"/>
|
||
</View>
|
||
</View>
|
||
</Grid.Item>
|
||
|
||
<Grid.Item text={'仓库地址'} onClick={() => navigateToPage('/rider/qrcode/index')}>
|
||
<View className="text-center">
|
||
<View className="w-12 h-12 bg-orange-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||
<Dongdong color="#f59e0b" size="20"/>
|
||
</View>
|
||
</View>
|
||
</Grid.Item>
|
||
|
||
<Grid.Item text={'水票核销'} onClick={() => navigateToPage('/rider/ticket/verification/index?auto=1')}>
|
||
<View className="text-center">
|
||
<View className="w-12 h-12 bg-cyan-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||
<Scan color="#06b6d4" size="20" />
|
||
</View>
|
||
</View>
|
||
</Grid.Item>
|
||
</Grid>
|
||
|
||
{/* 第二行功能 - 通知设置 */}
|
||
<Grid
|
||
columns={4}
|
||
className="no-border-grid mt-4"
|
||
style={{
|
||
'--nutui-grid-border-color': 'transparent',
|
||
'--nutui-grid-item-border-width': '0px',
|
||
border: 'none'
|
||
} as React.CSSProperties}
|
||
>
|
||
<Grid.Item text={'通知设置'} onClick={() => {
|
||
const isSubscribed = Taro.getStorageSync('rider_subscribed') === '1'
|
||
Taro.showModal({
|
||
title: '通知设置',
|
||
content: `声音提醒:${soundEnabled ? '已开启' : '已关闭'}\n订阅消息:${isSubscribed ? '已订阅' : '未订阅'}`,
|
||
confirmText: '更多设置',
|
||
cancelText: '关闭',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// 显示更多设置选项
|
||
Taro.showActionSheet({
|
||
itemList: [
|
||
soundEnabled ? '关闭声音提醒' : '开启声音提醒',
|
||
isSubscribed ? '订阅状态正常' : '订阅消息通知',
|
||
'检查更新'
|
||
],
|
||
success: (sheetRes) => {
|
||
if (sheetRes.tapIndex === 0) {
|
||
// 切换声音
|
||
toggleSound()
|
||
Taro.showToast({
|
||
title: soundEnabled ? '已关闭声音' : '已开启声音',
|
||
icon: 'none'
|
||
})
|
||
} else if (sheetRes.tapIndex === 1) {
|
||
// 订阅消息
|
||
if (!isSubscribed) {
|
||
handleRequestSubscribeMessage()
|
||
} else {
|
||
Taro.showToast({
|
||
title: '已订阅消息通知',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
} else if (sheetRes.tapIndex === 2) {
|
||
Taro.showToast({
|
||
title: '已是最新版本',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
})
|
||
}}>
|
||
<View className="text-center">
|
||
<View className="w-12 h-12 bg-indigo-50 rounded-xl flex items-center justify-center mx-auto mb-2 relative">
|
||
<Setting color={soundEnabled ? '#6366f1' : '#9ca3af'} size="20"/>
|
||
{soundEnabled ? (
|
||
<View className="absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-white"></View>
|
||
) : (
|
||
<View className="absolute -bottom-1 -right-1 w-3 h-3 bg-gray-400 rounded-full border-2 border-white"></View>
|
||
)}
|
||
</View>
|
||
</View>
|
||
</Grid.Item>
|
||
|
||
{/* 预留功能位置 */}
|
||
<Grid.Item text={''}>
|
||
<View className="text-center">
|
||
<View className="w-12 h-12 bg-gray-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||
</View>
|
||
</View>
|
||
</Grid.Item>
|
||
|
||
<Grid.Item text={''}>
|
||
<View className="text-center">
|
||
<View className="w-12 h-12 bg-gray-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||
</View>
|
||
</View>
|
||
</Grid.Item>
|
||
|
||
<Grid.Item text={''}>
|
||
<View className="text-center">
|
||
<View className="w-12 h-12 bg-gray-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||
</View>
|
||
</View>
|
||
</Grid.Item>
|
||
</Grid>
|
||
</ConfigProvider>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 底部安全区域 */}
|
||
<View className="h-20"></View>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
export default DealerIndex
|