feat(dealer): 添加配送员解冻资金功能

- 在dealer页面添加配送员权限判断和解冻资金功能
- 导入useUser hook和updateShopDealerUser API
- 仅配送员角色可操作冻结金额转入可提现
- 点击待使用金额弹出确认框进行资金转移
- 统一rider和dealer页面的解冻资金逻辑实现
- 修改环境配置支持SERVER_API_URL变量导出
- 更新版权信息配置结构优化代码注释
- 优化待使用金额卡片点击交互体验
This commit is contained in:
2026-03-31 13:37:14 +08:00
parent 5ff710c6a0
commit 5fe881b927
8 changed files with 193 additions and 33 deletions

View File

@@ -0,0 +1,29 @@
# 2026-03-31 工作日志
## 新增 dealer/index.tsx 配送员解冻资金功能
`/rider/index.tsx``handleFreezeMoneyClick` 功能复制到 `/dealer/index.tsx`
- 导入 `useUser` hook 和 `updateShopDealerUser` API
- 添加 `isRider` 判断(只有 `hasRole('rider')` 才可操作)
- "待使用"卡片点击事件(仅配送员+有冻结金额时可用)
- 确认后冻结金额转入可提现
## 修改 rider/index.tsx (React Taro 项目)
修改了 `/Users/gxwebsoft/VUE/template-10584/src/rider/index.tsx`
### 修改内容:
1. **统一显示格式** - 把"桶数"改成"待使用"
2. **添加配送员权限控制** - 只有 `hasRole('rider')` 的用户才能点击操作
3. **点击解冻功能**
- 配送员点击"待使用"金额时显示蓝色高亮提示"(点击转入)"
- 弹出确认框:`确定要将 ¥xx.xx 待使用金额转入可提现吗?`
- 确认后调用 `updateShopDealerUser` API
-`freezeMoney` 清零,加到 `money`
- 成功后提示"更新成功"并刷新数据
### 关键代码:
- 使用 `useUser` hook 获取 `hasRole` 函数
- `isRider` 判断是否为配送员
- `handleFreezeMoneyClick` 处理点击解冻逻辑
- 使用 `Taro.showModal` / `Taro.showToast` 提示用户

View File

View File

@@ -10,5 +10,6 @@ export const BaseUrl = API_BASE_URL;
export const Version = 'v3.0.8'; export const Version = 'v3.0.8';
// 版权信息 // 版权信息
export const Copyright = '桂乐淘·购享无界 乐惠万家'; export const Copyright = '桂乐淘·购享无界 乐惠万家';
// export const Copyright = '测试环境 v3.2.6';
// java -jar CertificateDownloader.jar -k 0kF5OlPr482EZwtn9zGufUcqa7ovgxRL -m 1723321338 -f ./apiclient_key.pem -s 2B933F7C35014A1C363642623E4A62364B34C4EB -o ./ // java -jar CertificateDownloader.jar -k 0kF5OlPr482EZwtn9zGufUcqa7ovgxRL -m 1723321338 -f ./apiclient_key.pem -s 2B933F7C35014A1C363642623E4A62364B34C4EB -o ./

View File

@@ -1,44 +1,43 @@
// 环境变量配置 // 环境变量配置
// ============ 环境切换开关(修改这里即可切换环境)============
// 可选值: 'development' | 'test' | 'production'
const CURRENT_ENV = 'production' as const
// ===========================================================
export const ENV_CONFIG = { export const ENV_CONFIG = {
// 开发环境 // 开发环境
development: { development: {
API_BASE_URL: 'http://127.0.0.1:9200/api', API_BASE_URL: 'https://glt-dev-api.websoft.top/api',
// API_BASE_URL: 'https://glt-api2.websoft.top/api', SERVER_API_URL: 'https://glt-dev-server.websoft.top/api',
APP_NAME: '开发环境', APP_NAME: '开发环境',
DEBUG: 'true', DEBUG: 'true',
}, },
// 测试环境
test: {
API_BASE_URL: 'https://glt-dev-api.websoft.top/api',
SERVER_API_URL: 'https://glt-dev-server.websoft.top/api',
APP_NAME: '测试环境',
DEBUG: 'true',
},
// 生产环境 // 生产环境
production: { production: {
API_BASE_URL: 'https://glt-api2.websoft.top/api', API_BASE_URL: 'https://glt-api.websoft.top/api',
SERVER_API_URL: 'https://glt-server.websoft.top/api',
APP_NAME: '桂乐淘', APP_NAME: '桂乐淘',
DEBUG: 'false', DEBUG: 'false',
}, },
// 测试环境
test: {
// API_BASE_URL: 'http://127.0.0.1:9200/api',
API_BASE_URL: 'https://glt-api2.websoft.top/api',
APP_NAME: '测试环境',
DEBUG: 'true',
}
} }
// 获取当前环境配置 // 获取当前环境配置
export function getEnvConfig() { export function getEnvConfig() {
const env = process.env.NODE_ENV || 'development' return ENV_CONFIG[CURRENT_ENV]
if (env === 'production') {
return ENV_CONFIG.production
} else { // @ts-ignore
if (env === 'test') {
return ENV_CONFIG.test
} else {
return ENV_CONFIG.development
}
}
} }
// 导出环境变量 // 导出环境变量
export const { export const {
API_BASE_URL, API_BASE_URL,
SERVER_API_URL,
APP_NAME, APP_NAME,
DEBUG DEBUG
} = getEnvConfig() } = getEnvConfig()

View File

@@ -10,8 +10,10 @@ import {
People People
} from '@nutui/icons-react-taro' } from '@nutui/icons-react-taro'
import {useDealerUser} from '@/hooks/useDealerUser' import {useDealerUser} from '@/hooks/useDealerUser'
import {useUser} from '@/hooks/useUser'
import { useThemeStyles } from '@/hooks/useTheme' import { useThemeStyles } from '@/hooks/useTheme'
import {businessGradients, cardGradients, gradientUtils} from '@/styles/gradients' import {businessGradients, cardGradients, gradientUtils} from '@/styles/gradients'
import {updateShopDealerUser} from '@/api/shop/shopDealerUser'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
const DealerIndex: React.FC = () => { const DealerIndex: React.FC = () => {
@@ -21,6 +23,9 @@ const DealerIndex: React.FC = () => {
refresh, refresh,
} = useDealerUser() } = useDealerUser()
// 获取用户角色信息
const { hasRole } = useUser()
// 使用主题样式 // 使用主题样式
const themeStyles = useThemeStyles() const themeStyles = useThemeStyles()
@@ -55,6 +60,59 @@ const DealerIndex: React.FC = () => {
console.log(getGradientBackground(),'getGradientBackground()') console.log(getGradientBackground(),'getGradientBackground()')
// 判断是否是配送员
const isRider = hasRole('rider')
// 点击待使用金额 - 配送员专用:将冻结金额转入可提现
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) { if (error) {
return ( return (
<View className="p-4"> <View className="p-4">
@@ -140,13 +198,20 @@ const DealerIndex: React.FC = () => {
</Text> </Text>
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}></Text> <Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}></Text>
</View> </View>
<View className="text-center p-3 rounded-lg flex flex-col" style={{ <View
background: businessGradients.money.frozen 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"> <Text className="text-lg font-bold mb-1 text-white">
{formatMoney(dealerUser.freezeMoney)} {formatMoney(dealerUser.freezeMoney)}
</Text> </Text>
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}>使</Text> <Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}>
{isRider && Number(dealerUser.freezeMoney ?? 0) > 0 ? '待使用' : '待使用'}
</Text>
</View> </View>
<View className="text-center p-3 rounded-lg flex flex-col" style={{ <View className="text-center p-3 rounded-lg flex flex-col" style={{
background: businessGradients.money.total background: businessGradients.money.total

View File

@@ -5,7 +5,7 @@ import {Popup} from '@nutui/nutui-react-taro'
import {UserParam} from "@/api/system/user/model"; import {UserParam} from "@/api/system/user/model";
import {Button} from '@nutui/nutui-react-taro' import {Button} from '@nutui/nutui-react-taro'
import {Form, Input} from '@nutui/nutui-react-taro' import {Form, Input} from '@nutui/nutui-react-taro'
import {Copyright, Version} from "@/config/app"; import {Copyright} from "@/config/app";
const UserFooter = () => { const UserFooter = () => {
const [openLoginByPhone, setOpenLoginByPhone] = useState(false) const [openLoginByPhone, setOpenLoginByPhone] = useState(false)
const [clickNum, setClickNum] = useState<number>(0) const [clickNum, setClickNum] = useState<number>(0)

View File

@@ -11,8 +11,10 @@ import {
Scan Scan
} from '@nutui/icons-react-taro' } from '@nutui/icons-react-taro'
import {useDealerUser} from '@/hooks/useDealerUser' import {useDealerUser} from '@/hooks/useDealerUser'
import {useUser} from '@/hooks/useUser'
import { useThemeStyles } from '@/hooks/useTheme' import { useThemeStyles } from '@/hooks/useTheme'
import {businessGradients, cardGradients, gradientUtils} from '@/styles/gradients' import {businessGradients, cardGradients, gradientUtils} from '@/styles/gradients'
import {updateShopDealerUser} from '@/api/shop/shopDealerUser'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
const DealerIndex: React.FC = () => { const DealerIndex: React.FC = () => {
@@ -22,6 +24,9 @@ const DealerIndex: React.FC = () => {
refresh, refresh,
} = useDealerUser() } = useDealerUser()
// 获取用户角色信息
const { hasRole } = useUser()
// 使用主题样式 // 使用主题样式
const themeStyles = useThemeStyles() const themeStyles = useThemeStyles()
@@ -56,6 +61,59 @@ const DealerIndex: React.FC = () => {
console.log(getGradientBackground(),'getGradientBackground()') console.log(getGradientBackground(),'getGradientBackground()')
// 判断是否是配送员
const isRider = hasRole('rider')
// 点击待使用金额 - 配送员专用:将冻结金额转入可提现
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) { if (error) {
return ( return (
<View className="p-4"> <View className="p-4">
@@ -148,13 +206,20 @@ const DealerIndex: React.FC = () => {
</Text> </Text>
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}></Text> <Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}></Text>
</View> </View>
<View className="text-center p-3 rounded-lg flex flex-col" style={{ <View
background: businessGradients.money.frozen 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"> <Text className="text-lg font-bold mb-1 text-white">
{formatMoney(dealerUser.freezeMoney)} {formatMoney(dealerUser.freezeMoney)}
</Text> </Text>
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}></Text> <Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}>
{isRider && Number(dealerUser.freezeMoney ?? 0) > 0 ? '待使用' : '待使用'}
</Text>
</View> </View>
<View className="text-center p-3 rounded-lg flex flex-col" style={{ <View className="text-center p-3 rounded-lg flex flex-col" style={{
background: businessGradients.money.total background: businessGradients.money.total

View File

@@ -3,10 +3,11 @@ import {User} from "@/api/system/user/model";
// 模版套餐ID - 请根据实际情况修改 // 模版套餐ID - 请根据实际情况修改
export const TEMPLATE_ID = '10584'; export const TEMPLATE_ID = '10584';
// 服务接口 - 请根据实际情况修改 // 服务接口 - 从环境配置读取
export const SERVER_API_URL = 'https://glt-server.websoft.top/api'; // @ts-ignore
// export const SERVER_API_URL = 'https://server.websoft.top/api'; export const SERVER_API_URL = process.env.TARO_ENV === 'production'
// export const SERVER_API_URL = 'http://127.0.0.1:8000/api'; ? 'https://glt-server.websoft.top/api'
: 'https://glt-dev-server.websoft.top/api';
/** /**
* 保存用户信息到本地存储 * 保存用户信息到本地存储
* @param token * @param token