feat(dealer): 新增提现审核与收益明细功能
- 新增提现审核页面,支持审核通过、驳回及打款确认操作 - 新增收款人实名认证校验逻辑- 新增收益明细页面,展示各类资金流动记录 - 新增新注册奖励资金流水类型 - 完善资金流水详情页面字段展示逻辑 - 新增银行账户管理入口-优化部分页面导航配置与权限控制
This commit is contained in:
16
src/api/sdy/sdyTemplateMessage/index.ts
Normal file
16
src/api/sdy/sdyTemplateMessage/index.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
import type { ApiResult } from '@/api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 升级为管理员
|
||||||
|
* 推送模版消息
|
||||||
|
*/
|
||||||
|
export async function pushByUpdateAdmin(userId: number) {
|
||||||
|
const res = await request.get<ApiResult<unknown>>(
|
||||||
|
'/sdy/sdy-template-message/' + userId
|
||||||
|
);
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.message;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.message));
|
||||||
|
}
|
||||||
123
src/api/sdy/sdyTemplateMessage/model/index.ts
Normal file
123
src/api/sdy/sdyTemplateMessage/model/index.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import type { PageParam } from '@/api/index';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品文章
|
||||||
|
*/
|
||||||
|
export interface ShopArticle {
|
||||||
|
// 文章ID
|
||||||
|
articleId?: number;
|
||||||
|
// 文章标题
|
||||||
|
title?: string;
|
||||||
|
// 文章类型 0常规 1视频
|
||||||
|
type?: number;
|
||||||
|
// 模型
|
||||||
|
model?: string;
|
||||||
|
// 详情页模板
|
||||||
|
detail?: string;
|
||||||
|
// 文章分类ID
|
||||||
|
categoryId?: number;
|
||||||
|
// 上级id, 0是顶级
|
||||||
|
parentId?: number;
|
||||||
|
// 话题
|
||||||
|
topic?: string;
|
||||||
|
// 标签
|
||||||
|
tags?: string;
|
||||||
|
// 封面图
|
||||||
|
image?: string;
|
||||||
|
// 封面图宽
|
||||||
|
imageWidth?: number;
|
||||||
|
// 封面图高
|
||||||
|
imageHeight?: number;
|
||||||
|
// 付费金额
|
||||||
|
price?: string;
|
||||||
|
// 开始时间
|
||||||
|
startTime?: string;
|
||||||
|
// 结束时间
|
||||||
|
endTime?: string;
|
||||||
|
// 来源
|
||||||
|
source?: string;
|
||||||
|
// 产品概述
|
||||||
|
overview?: string;
|
||||||
|
// 虚拟阅读量(仅用作展示)
|
||||||
|
virtualViews?: number;
|
||||||
|
// 实际阅读量
|
||||||
|
actualViews?: number;
|
||||||
|
// 评分
|
||||||
|
rate?: string;
|
||||||
|
// 列表显示方式(10小图展示 20大图展示)
|
||||||
|
showType?: number;
|
||||||
|
// 访问密码
|
||||||
|
password?: string;
|
||||||
|
// 可见类型 0所有人 1登录可见 2密码可见
|
||||||
|
permission?: number;
|
||||||
|
// 发布来源客户端 (APP、H5、小程序等)
|
||||||
|
platform?: string;
|
||||||
|
// 文章附件
|
||||||
|
files?: string;
|
||||||
|
// 视频地址
|
||||||
|
video?: string;
|
||||||
|
// 接受的文件类型
|
||||||
|
accept?: string;
|
||||||
|
// 经度
|
||||||
|
longitude?: string;
|
||||||
|
// 纬度
|
||||||
|
latitude?: string;
|
||||||
|
// 所在省份
|
||||||
|
province?: string;
|
||||||
|
// 所在城市
|
||||||
|
city?: string;
|
||||||
|
// 所在辖区
|
||||||
|
region?: string;
|
||||||
|
// 街道地址
|
||||||
|
address?: string;
|
||||||
|
// 点赞数
|
||||||
|
likes?: number;
|
||||||
|
// 评论数
|
||||||
|
commentNumbers?: number;
|
||||||
|
// 提醒谁看
|
||||||
|
toUsers?: string;
|
||||||
|
// 作者
|
||||||
|
author?: string;
|
||||||
|
// 推荐
|
||||||
|
recommend?: number;
|
||||||
|
// 报名人数
|
||||||
|
bmUsers?: number;
|
||||||
|
// 用户ID
|
||||||
|
userId?: number;
|
||||||
|
// 项目ID
|
||||||
|
projectId?: number;
|
||||||
|
// 语言
|
||||||
|
lang?: string;
|
||||||
|
// 关联默认语言的文章ID
|
||||||
|
langArticleId?: number;
|
||||||
|
// 是否自动翻译
|
||||||
|
translation?: string;
|
||||||
|
// 编辑器类型 0 Markdown编辑器 1 富文本编辑器
|
||||||
|
editor?: string;
|
||||||
|
// pdf文件地址
|
||||||
|
pdfUrl?: string;
|
||||||
|
// 版本号
|
||||||
|
version?: number;
|
||||||
|
// 排序(数字越小越靠前)
|
||||||
|
sortNumber?: number;
|
||||||
|
// 备注
|
||||||
|
comments?: string;
|
||||||
|
// 状态, 0已发布, 1待审核 2已驳回 3违规内容
|
||||||
|
status?: number;
|
||||||
|
// 是否删除, 0否, 1是
|
||||||
|
deleted?: number;
|
||||||
|
// 租户id
|
||||||
|
tenantId?: number;
|
||||||
|
// 创建时间
|
||||||
|
createTime?: string;
|
||||||
|
// 修改时间
|
||||||
|
updateTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品文章搜索条件
|
||||||
|
*/
|
||||||
|
export interface ShopArticleParam extends PageParam {
|
||||||
|
articleId?: number;
|
||||||
|
keywords?: string;
|
||||||
|
}
|
||||||
@@ -38,6 +38,8 @@ export interface ShopDealerWithdraw {
|
|||||||
createTime?: string;
|
createTime?: string;
|
||||||
// 修改时间
|
// 修改时间
|
||||||
updateTime?: string;
|
updateTime?: string;
|
||||||
|
// 附件
|
||||||
|
image?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,4 +49,5 @@ export interface ShopDealerWithdrawParam extends PageParam {
|
|||||||
id?: number;
|
id?: number;
|
||||||
userId?: number;
|
userId?: number;
|
||||||
keywords?: string;
|
keywords?: string;
|
||||||
|
applyStatus?: number; // 申请状态筛选
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export interface UserVerify {
|
|||||||
*/
|
*/
|
||||||
export interface UserVerifyParam extends PageParam {
|
export interface UserVerifyParam extends PageParam {
|
||||||
id?: number;
|
id?: number;
|
||||||
|
userId?: number;
|
||||||
status?: number;
|
status?: number;
|
||||||
keywords?: string;
|
keywords?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,9 +57,11 @@ export default defineAppConfig({
|
|||||||
"index",
|
"index",
|
||||||
"apply/add",
|
"apply/add",
|
||||||
"withdraw/index",
|
"withdraw/index",
|
||||||
|
"withdraw/admin",
|
||||||
"orders/index",
|
"orders/index",
|
||||||
"capital/index",
|
"capital/index",
|
||||||
"capital/detail",
|
"capital/detail",
|
||||||
|
"capital/record",
|
||||||
"team/index",
|
"team/index",
|
||||||
"qrcode/index",
|
"qrcode/index",
|
||||||
"invite-stats/index",
|
"invite-stats/index",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {User} from "@/api/system/user/model";
|
|||||||
import {getStoredInviteParams, handleInviteRelation} from "@/utils/invite";
|
import {getStoredInviteParams, handleInviteRelation} from "@/utils/invite";
|
||||||
import {addShopDealerUser} from "@/api/shop/shopDealerUser";
|
import {addShopDealerUser} from "@/api/shop/shopDealerUser";
|
||||||
import {listUserRole, updateUserRole} from "@/api/system/userRole";
|
import {listUserRole, updateUserRole} from "@/api/system/userRole";
|
||||||
|
import {addShopDealerCapital} from "@/api/shop/shopDealerCapital";
|
||||||
|
|
||||||
// 类型定义
|
// 类型定义
|
||||||
interface ChooseAvatarEvent {
|
interface ChooseAvatarEvent {
|
||||||
@@ -202,6 +203,14 @@ const AddUserAddress = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获得50元奖励
|
||||||
|
await addShopDealerCapital({
|
||||||
|
userId: user?.userId,
|
||||||
|
flowType: 50,
|
||||||
|
money: '50',
|
||||||
|
toUserId: user?.refereeId,
|
||||||
|
comments: '新人注册奖励'
|
||||||
|
})
|
||||||
|
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: `注册成功`,
|
title: `注册成功`,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "@/api/shop/shopDealerBank";
|
} from "@/api/shop/shopDealerBank";
|
||||||
import FixedButton from "@/components/FixedButton";
|
import FixedButton from "@/components/FixedButton";
|
||||||
import {ShopDealerBank} from "@/api/shop/shopDealerBank/model";
|
import {ShopDealerBank} from "@/api/shop/shopDealerBank/model";
|
||||||
|
import {myUserVerify} from "@/api/system/userVerify";
|
||||||
|
|
||||||
const AddUserAddress = () => {
|
const AddUserAddress = () => {
|
||||||
const {params} = useRouter();
|
const {params} = useRouter();
|
||||||
@@ -21,6 +22,7 @@ const AddUserAddress = () => {
|
|||||||
const isEditMode = !!params.id
|
const isEditMode = !!params.id
|
||||||
const bankId = params.id ? Number(params.id) : undefined
|
const bankId = params.id ? Number(params.id) : undefined
|
||||||
|
|
||||||
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
// 如果是编辑模式,加载地址数据
|
// 如果是编辑模式,加载地址数据
|
||||||
if (isEditMode && bankId) {
|
if (isEditMode && bankId) {
|
||||||
@@ -39,7 +41,19 @@ const AddUserAddress = () => {
|
|||||||
|
|
||||||
// 提交表单
|
// 提交表单
|
||||||
const submitSucceed = async (values: any) => {
|
const submitSucceed = async (values: any) => {
|
||||||
console.log('.>>>>>>,....')
|
console.log('.>>>>>>,....',values)
|
||||||
|
|
||||||
|
|
||||||
|
const verify = await myUserVerify({userId: Taro.getStorageSync('UserId')})
|
||||||
|
if(verify?.realName !== values.bankAccount){
|
||||||
|
Taro.showToast({
|
||||||
|
title: '收款人姓名与实名认证信息不一致!',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 准备提交的数据
|
// 准备提交的数据
|
||||||
const submitData = {
|
const submitData = {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const DealerCapitalDetail = () => {
|
|||||||
if (index === 20) return '提现支出'
|
if (index === 20) return '提现支出'
|
||||||
if (index === 30) return '转账支出'
|
if (index === 30) return '转账支出'
|
||||||
if (index === 40) return '转账收入'
|
if (index === 40) return '转账收入'
|
||||||
|
if (index === 50) return '新注册奖励'
|
||||||
return 'warning'
|
return 'warning'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,9 +56,11 @@ const DealerCapitalDetail = () => {
|
|||||||
<Text className="text-sm my-1 text-gray-500">
|
<Text className="text-sm my-1 text-gray-500">
|
||||||
收益描述:{item.comments}
|
收益描述:{item.comments}
|
||||||
</Text>
|
</Text>
|
||||||
|
{item.orderNo && (
|
||||||
<Text className="text-sm my-1 text-gray-500">
|
<Text className="text-sm my-1 text-gray-500">
|
||||||
订单编号:{item.orderNo}
|
订单编号:{item.orderNo}
|
||||||
</Text>
|
</Text>
|
||||||
|
)}
|
||||||
<Text className="text-sm my-1 text-gray-500">
|
<Text className="text-sm my-1 text-gray-500">
|
||||||
创建时间:{item.createTime}
|
创建时间:{item.createTime}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
3
src/dealer/capital/record.config.ts
Normal file
3
src/dealer/capital/record.config.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '收益明细'
|
||||||
|
})
|
||||||
180
src/dealer/capital/record.tsx
Normal file
180
src/dealer/capital/record.tsx
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import React, {useState, useEffect, useCallback} from 'react'
|
||||||
|
import {View, Text, ScrollView} from '@tarojs/components'
|
||||||
|
import {Empty, PullToRefresh, Loading} from '@nutui/nutui-react-taro'
|
||||||
|
import Taro from '@tarojs/taro'
|
||||||
|
import {pageShopDealerCapital} from '@/api/shop/shopDealerCapital'
|
||||||
|
import {useDealerUser} from '@/hooks/useDealerUser'
|
||||||
|
import type {ShopDealerCapital} from '@/api/shop/shopDealerCapital/model'
|
||||||
|
import navTo from "@/utils/common";
|
||||||
|
import {pushByUpdateAdmin} from "@/api/sdy/sdyTemplateMessage";
|
||||||
|
|
||||||
|
const DealerCapital: React.FC = () => {
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [refreshing, setRefreshing] = useState<boolean>(false)
|
||||||
|
const [loadingMore, setLoadingMore] = useState<boolean>(false)
|
||||||
|
const [capital, setCapital] = useState<ShopDealerCapital[]>([])
|
||||||
|
const [currentPage, setCurrentPage] = useState<number>(1)
|
||||||
|
const [hasMore, setHasMore] = useState<boolean>(true)
|
||||||
|
|
||||||
|
const {dealerUser} = useDealerUser()
|
||||||
|
|
||||||
|
// 获取订单数据
|
||||||
|
const fetchCapital = useCallback(async (page: number = 1, isRefresh: boolean = false) => {
|
||||||
|
// if (!dealerUser?.userId) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isRefresh) {
|
||||||
|
setRefreshing(true)
|
||||||
|
} else if (page === 1) {
|
||||||
|
setLoading(true)
|
||||||
|
} else {
|
||||||
|
setLoadingMore(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await pageShopDealerCapital({
|
||||||
|
page,
|
||||||
|
limit: 10,
|
||||||
|
userId: Taro.getStorageSync('UserId')
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result?.list) {
|
||||||
|
const newCapital = result.list.map(item => ({
|
||||||
|
...item,
|
||||||
|
orderNo: item.orderNo
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (page === 1) {
|
||||||
|
setCapital(newCapital)
|
||||||
|
} else {
|
||||||
|
setCapital(prev => [...prev, ...newCapital])
|
||||||
|
}
|
||||||
|
|
||||||
|
setHasMore(newCapital.length === 10)
|
||||||
|
setCurrentPage(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取分销订单失败:', error)
|
||||||
|
Taro.showToast({
|
||||||
|
title: '获取订单失败',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
setRefreshing(false)
|
||||||
|
setLoadingMore(false)
|
||||||
|
}
|
||||||
|
}, [dealerUser?.userId])
|
||||||
|
|
||||||
|
// 下拉刷新
|
||||||
|
const handleRefresh = async () => {
|
||||||
|
await fetchCapital(1, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载更多
|
||||||
|
const handleLoadMore = async () => {
|
||||||
|
if (!loadingMore && hasMore) {
|
||||||
|
await fetchCapital(currentPage + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFlowType = (index?: number) => {
|
||||||
|
if (index === 10) return '电费收益'
|
||||||
|
if (index === 20) return '提现支出'
|
||||||
|
if (index === 30) return '转账支出'
|
||||||
|
if (index === 40) return '转账收入'
|
||||||
|
if (index === 50) return '新注册奖励'
|
||||||
|
return 'warning'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化加载数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (dealerUser?.userId) {
|
||||||
|
fetchCapital(1)
|
||||||
|
}
|
||||||
|
pushByUpdateAdmin(34423).then()
|
||||||
|
}, [fetchCapital])
|
||||||
|
|
||||||
|
const renderCapitalItem = (item: ShopDealerCapital) => (
|
||||||
|
<View key={item.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm"
|
||||||
|
onClick={() => navTo(`/dealer/capital/detail?id=${item.id}`)}>
|
||||||
|
<View className="flex justify-between items-start mb-1">
|
||||||
|
<Text className="font-semibold text-gray-800">
|
||||||
|
{getFlowType(item.flowType)}
|
||||||
|
</Text>
|
||||||
|
<Text className="text-lg text-orange-500 font-semibold">
|
||||||
|
¥{Number(item.money).toFixed(2)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{item.orderNo && (
|
||||||
|
<View className="flex justify-between items-center mb-1">
|
||||||
|
<Text className="text-sm text-gray-400">
|
||||||
|
{item.orderNo}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View className="flex justify-between items-center mb-1">
|
||||||
|
<Text className="text-sm text-gray-400">
|
||||||
|
{item.createTime}
|
||||||
|
</Text>
|
||||||
|
<Text className="text-sm text-gray-400">
|
||||||
|
我的收益:{item.money}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="min-h-screen bg-gray-50">
|
||||||
|
<PullToRefresh
|
||||||
|
onRefresh={handleRefresh}
|
||||||
|
disabled={refreshing}
|
||||||
|
pullingText="下拉刷新"
|
||||||
|
canReleaseText="释放刷新"
|
||||||
|
refreshingText="刷新中..."
|
||||||
|
completeText="刷新完成"
|
||||||
|
>
|
||||||
|
<ScrollView
|
||||||
|
scrollY
|
||||||
|
className={'h-screen'}
|
||||||
|
onScrollToLower={handleLoadMore}
|
||||||
|
lowerThreshold={50}
|
||||||
|
>
|
||||||
|
{/*账单列表*/}
|
||||||
|
<View className="p-4">
|
||||||
|
{loading && capital.length === 0 ? (
|
||||||
|
<View className="text-center py-8">
|
||||||
|
<Loading/>
|
||||||
|
<Text className="text-gray-500 mt-2">加载中...</Text>
|
||||||
|
</View>
|
||||||
|
) : capital.length > 0 ? (
|
||||||
|
<>
|
||||||
|
{capital.map(renderCapitalItem)}
|
||||||
|
{loadingMore && (
|
||||||
|
<View className="text-center py-4">
|
||||||
|
<Loading/>
|
||||||
|
<Text className="text-gray-500 mt-1 text-sm">加载更多...</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{!hasMore && capital.length > 0 && (
|
||||||
|
<View className="text-center py-4">
|
||||||
|
<Text className="text-gray-400 text-sm">没有更多数据了</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Empty description="暂无收益" style={{
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
}}/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</PullToRefresh>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DealerCapital
|
||||||
@@ -228,6 +228,14 @@ const DealerIndex: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
</Grid.Item>
|
</Grid.Item>
|
||||||
|
|
||||||
|
<Grid.Item text="收益明细" onClick={() => navigateToPage('/dealer/capital/record')}>
|
||||||
|
<View className="text-center">
|
||||||
|
<View className="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||||
|
<Purse color="#3b82f6" size="20"/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Grid.Item>
|
||||||
|
|
||||||
<Grid.Item text={'提现申请'} onClick={() => navigateToPage('/dealer/withdraw/index')}>
|
<Grid.Item text={'提现申请'} onClick={() => navigateToPage('/dealer/withdraw/index')}>
|
||||||
<View className="text-center">
|
<View className="text-center">
|
||||||
<View className="w-12 h-12 bg-green-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
<View className="w-12 h-12 bg-green-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||||
@@ -236,7 +244,7 @@ const DealerIndex: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
</Grid.Item>
|
</Grid.Item>
|
||||||
|
|
||||||
<Grid.Item text={'会员中心'} onClick={() => navigateToPage('/pages/user/user')}>
|
<Grid.Item text={'实名认证'} onClick={() => navigateToPage('/user/userVerify/index')}>
|
||||||
<View className="text-center">
|
<View className="text-center">
|
||||||
<View className="w-12 h-12 bg-purple-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
<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"/>
|
<People color="#8b5cf6" size="20"/>
|
||||||
@@ -251,6 +259,18 @@ const DealerIndex: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Grid.Item>
|
</Grid.Item>
|
||||||
|
{
|
||||||
|
(dealerUser?.userId == 33658 || dealerUser?.userId == 33677) && (
|
||||||
|
<Grid.Item text={'提现审核'} onClick={() => navigateToPage('/dealer/withdraw/admin')}>
|
||||||
|
<View className="text-center">
|
||||||
|
<View className="w-12 h-12 bg-red-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||||
|
<Purse color="#10b981" size="20"/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Grid.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* 第二行功能 */}
|
{/* 第二行功能 */}
|
||||||
|
|||||||
3
src/dealer/withdraw/admin.config.ts
Normal file
3
src/dealer/withdraw/admin.config.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '提现审核'
|
||||||
|
})
|
||||||
470
src/dealer/withdraw/admin.tsx
Normal file
470
src/dealer/withdraw/admin.tsx
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
import React, {useState, useEffect, useCallback} from 'react'
|
||||||
|
import {View, Text} from '@tarojs/components'
|
||||||
|
import {
|
||||||
|
Space,
|
||||||
|
Tabs,
|
||||||
|
Tag,
|
||||||
|
Empty,
|
||||||
|
ActionSheet,
|
||||||
|
Loading,
|
||||||
|
PullToRefresh,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
TextArea
|
||||||
|
} from '@nutui/nutui-react-taro'
|
||||||
|
import Taro from '@tarojs/taro'
|
||||||
|
import {useDealerUser} from '@/hooks/useDealerUser'
|
||||||
|
import {pageShopDealerWithdraw, updateShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw'
|
||||||
|
import type {ShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw/model'
|
||||||
|
import {ShopDealerBank} from "@/api/shop/shopDealerBank/model";
|
||||||
|
import {listShopDealerBank} from "@/api/shop/shopDealerBank";
|
||||||
|
|
||||||
|
interface WithdrawRecordWithDetails extends ShopDealerWithdraw {
|
||||||
|
accountDisplay?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const DealerWithdraw: React.FC = () => {
|
||||||
|
const [activeTab, setActiveTab] = useState<string | number>('10')
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [refreshing, setRefreshing] = useState<boolean>(false)
|
||||||
|
const [banks, setBanks] = useState<any[]>([])
|
||||||
|
const [isVisible, setIsVisible] = useState<boolean>(false)
|
||||||
|
const [withdrawRecords, setWithdrawRecords] = useState<WithdrawRecordWithDetails[]>([])
|
||||||
|
const [rejectDialogVisible, setRejectDialogVisible] = useState<boolean>(false)
|
||||||
|
const [rejectReason, setRejectReason] = useState<string>('')
|
||||||
|
const [currentRecord, setCurrentRecord] = useState<ShopDealerWithdraw | null>(null)
|
||||||
|
const [payDialogVisible, setPayDialogVisible] = useState<boolean>(false)
|
||||||
|
const [paymentImages, setPaymentImages] = useState<string[]>([])
|
||||||
|
|
||||||
|
const {dealerUser} = useDealerUser()
|
||||||
|
|
||||||
|
// Tab 切换处理函数
|
||||||
|
const handleTabChange = (value: string | number) => {
|
||||||
|
console.log('Tab切换到:', value)
|
||||||
|
setActiveTab(value)
|
||||||
|
// activeTab变化会自动触发useEffect重新获取数据,无需手动调用
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取提现记录
|
||||||
|
const fetchWithdrawRecords = useCallback(async () => {
|
||||||
|
if (!dealerUser?.userId) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
const currentStatus = Number(activeTab)
|
||||||
|
const result = await pageShopDealerWithdraw({
|
||||||
|
page: 1,
|
||||||
|
limit: 100,
|
||||||
|
applyStatus: currentStatus // 后端筛选,提高性能
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result?.list) {
|
||||||
|
const processedRecords = result.list.map(record => ({
|
||||||
|
...record,
|
||||||
|
accountDisplay: getAccountDisplay(record)
|
||||||
|
}))
|
||||||
|
setWithdrawRecords(processedRecords)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取提现记录失败:', error)
|
||||||
|
Taro.showToast({
|
||||||
|
title: '获取提现记录失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}, [dealerUser?.userId, activeTab])
|
||||||
|
|
||||||
|
function fetchBanks() {
|
||||||
|
listShopDealerBank({}).then(data => {
|
||||||
|
const list = data.map(d => {
|
||||||
|
d.name = d.bankName;
|
||||||
|
d.type = d.bankName;
|
||||||
|
return d;
|
||||||
|
})
|
||||||
|
setBanks(list.concat({
|
||||||
|
name: '管理银行卡',
|
||||||
|
type: 'add'
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化账户显示
|
||||||
|
const getAccountDisplay = (record: ShopDealerWithdraw) => {
|
||||||
|
if (record.payType === 10) {
|
||||||
|
return '微信钱包'
|
||||||
|
} else if (record.payType === 20 && record.alipayAccount) {
|
||||||
|
return `支付宝(${record.alipayAccount.slice(-4)})`
|
||||||
|
} else if (record.payType === 30 && record.bankCard) {
|
||||||
|
return `${record.bankName || '银行卡'}(尾号${record.bankCard.slice(-4)})`
|
||||||
|
}
|
||||||
|
return '未知账户'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新数据
|
||||||
|
const handleRefresh = async () => {
|
||||||
|
setRefreshing(true)
|
||||||
|
await Promise.all([fetchWithdrawRecords()])
|
||||||
|
setRefreshing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSelect = (item: ShopDealerBank) => {
|
||||||
|
if(item.type === 'add'){
|
||||||
|
return Taro.navigateTo({
|
||||||
|
url: '/dealer/bank/index'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setIsVisible(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审核通过
|
||||||
|
const handleApprove = async (record: ShopDealerWithdraw) => {
|
||||||
|
try {
|
||||||
|
await updateShopDealerWithdraw({
|
||||||
|
...record,
|
||||||
|
applyStatus: 20, // 审核通过
|
||||||
|
})
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: '审核通过',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
await fetchWithdrawRecords()
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
console.error('审核通过失败:', error)
|
||||||
|
Taro.showToast({
|
||||||
|
title: error.message || '操作失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 驳回申请
|
||||||
|
const handleReject = (record: ShopDealerWithdraw) => {
|
||||||
|
setCurrentRecord(record)
|
||||||
|
setRejectReason('')
|
||||||
|
setRejectDialogVisible(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认驳回
|
||||||
|
const confirmReject = async () => {
|
||||||
|
if (!rejectReason.trim()) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '请输入驳回原因',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateShopDealerWithdraw({
|
||||||
|
...currentRecord!,
|
||||||
|
applyStatus: 30, // 驳回
|
||||||
|
rejectReason: rejectReason.trim()
|
||||||
|
})
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: '已驳回',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
setRejectDialogVisible(false)
|
||||||
|
setCurrentRecord(null)
|
||||||
|
setRejectReason('')
|
||||||
|
await fetchWithdrawRecords()
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('驳回失败:', error)
|
||||||
|
Taro.showToast({
|
||||||
|
title: error.message || '操作失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认打款 - 打开打款对话框
|
||||||
|
const handleConfirmPay = (record: ShopDealerWithdraw) => {
|
||||||
|
setCurrentRecord(record)
|
||||||
|
setPaymentImages([])
|
||||||
|
setPayDialogVisible(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传打款凭证
|
||||||
|
const handleUploadPaymentImage = async () => {
|
||||||
|
try {
|
||||||
|
const res = await Taro.chooseImage({
|
||||||
|
count: 3 - paymentImages.length, // 最多3张
|
||||||
|
sizeType: ['compressed'],
|
||||||
|
sourceType: ['album', 'camera']
|
||||||
|
})
|
||||||
|
|
||||||
|
// 这里应该上传到服务器,获取图片URL
|
||||||
|
// 暂时使用本地路径演示
|
||||||
|
const newImages = [...paymentImages, ...res.tempFilePaths]
|
||||||
|
setPaymentImages(newImages.slice(0, 3))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('选择图片失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除打款凭证
|
||||||
|
const handleRemovePaymentImage = (index: number) => {
|
||||||
|
const newImages = paymentImages.filter((_, i) => i !== index)
|
||||||
|
setPaymentImages(newImages)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认提交打款
|
||||||
|
const confirmPayment = async () => {
|
||||||
|
try {
|
||||||
|
await updateShopDealerWithdraw({
|
||||||
|
...currentRecord!,
|
||||||
|
applyStatus: 40, // 已打款
|
||||||
|
image: paymentImages.length > 0 ? JSON.stringify(paymentImages) : undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: '打款确认成功',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
setPayDialogVisible(false)
|
||||||
|
setCurrentRecord(null)
|
||||||
|
setPaymentImages([])
|
||||||
|
await fetchWithdrawRecords()
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('确认打款失败:', error)
|
||||||
|
Taro.showToast({
|
||||||
|
title: error.message || '操作失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化加载数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (dealerUser?.userId) {
|
||||||
|
fetchWithdrawRecords().then()
|
||||||
|
fetchBanks()
|
||||||
|
}
|
||||||
|
}, [fetchWithdrawRecords])
|
||||||
|
|
||||||
|
const getStatusText = (status?: number) => {
|
||||||
|
switch (status) {
|
||||||
|
case 40:
|
||||||
|
return '已到账'
|
||||||
|
case 20:
|
||||||
|
return '审核通过'
|
||||||
|
case 10:
|
||||||
|
return '待审核'
|
||||||
|
case 30:
|
||||||
|
return '已驳回'
|
||||||
|
default:
|
||||||
|
return '未知'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusColor = (status?: number) => {
|
||||||
|
switch (status) {
|
||||||
|
case 40:
|
||||||
|
return 'success'
|
||||||
|
case 20:
|
||||||
|
return 'success'
|
||||||
|
case 10:
|
||||||
|
return 'warning'
|
||||||
|
case 30:
|
||||||
|
return 'danger'
|
||||||
|
default:
|
||||||
|
return 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderWithdrawRecords = () => {
|
||||||
|
console.log('渲染提现记录:', {loading, recordsCount: withdrawRecords.length, dealerUserId: dealerUser?.userId})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PullToRefresh
|
||||||
|
disabled={refreshing}
|
||||||
|
onRefresh={handleRefresh}
|
||||||
|
>
|
||||||
|
<View>
|
||||||
|
{loading ? (
|
||||||
|
<View className="text-center py-8">
|
||||||
|
<Loading/>
|
||||||
|
<Text className="text-gray-500 mt-2">加载中...</Text>
|
||||||
|
</View>
|
||||||
|
) : withdrawRecords.length > 0 ? (
|
||||||
|
withdrawRecords.map(record => (
|
||||||
|
<View key={record.id} className="rounded-lg bg-gray-50 p-4 mb-3 shadow-sm">
|
||||||
|
<View className="flex justify-between items-start mb-3">
|
||||||
|
<Space direction={'vertical'}>
|
||||||
|
<Text className="font-semibold text-gray-800 mb-1">
|
||||||
|
提现金额:¥{Number(record.money).toFixed(2)}
|
||||||
|
</Text>
|
||||||
|
<Text className="text-sm text-gray-500">
|
||||||
|
{record.comments}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
<Tag type={getStatusColor(record.applyStatus)}>
|
||||||
|
{getStatusText(record.applyStatus)}
|
||||||
|
</Tag>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="text-xs text-gray-400">
|
||||||
|
<Text>申请时间:{record.createTime}</Text>
|
||||||
|
{record.auditTime && (
|
||||||
|
<Text className="block mt-1">
|
||||||
|
审核时间:{new Date(record.auditTime).toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{record.rejectReason && (
|
||||||
|
<Text className="block mt-1 text-red-500">
|
||||||
|
驳回原因:{record.rejectReason}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 操作按钮 */}
|
||||||
|
{record.applyStatus === 10 && (
|
||||||
|
<View className="flex gap-2 mt-3">
|
||||||
|
<Button
|
||||||
|
type="success"
|
||||||
|
size="small"
|
||||||
|
className="flex-1"
|
||||||
|
onClick={() => handleApprove(record)}
|
||||||
|
>
|
||||||
|
审核通过
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
className="flex-1"
|
||||||
|
onClick={() => handleReject(record)}
|
||||||
|
>
|
||||||
|
驳回
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{record.applyStatus === 20 && (
|
||||||
|
<View className="mt-3">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
block
|
||||||
|
onClick={() => handleConfirmPay(record)}
|
||||||
|
>
|
||||||
|
确认打款
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Empty description="暂无提现记录"/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</PullToRefresh>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="bg-gray-50 min-h-screen">
|
||||||
|
<Tabs value={activeTab} onChange={handleTabChange}>
|
||||||
|
<Tabs.TabPane title="待审核" value="10">
|
||||||
|
{renderWithdrawRecords()}
|
||||||
|
</Tabs.TabPane>
|
||||||
|
|
||||||
|
<Tabs.TabPane title="已通过" value="20">
|
||||||
|
{renderWithdrawRecords()}
|
||||||
|
</Tabs.TabPane>
|
||||||
|
|
||||||
|
<Tabs.TabPane title="已打款" value="40">
|
||||||
|
{renderWithdrawRecords()}
|
||||||
|
</Tabs.TabPane>
|
||||||
|
|
||||||
|
<Tabs.TabPane title="已驳回" value="30">
|
||||||
|
{renderWithdrawRecords()}
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>
|
||||||
|
<ActionSheet
|
||||||
|
visible={isVisible}
|
||||||
|
options={banks}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
onCancel={() => setIsVisible(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 驳回原因对话框 */}
|
||||||
|
<Dialog
|
||||||
|
visible={rejectDialogVisible}
|
||||||
|
title="驳回原因"
|
||||||
|
onCancel={() => {
|
||||||
|
setRejectDialogVisible(false)
|
||||||
|
setCurrentRecord(null)
|
||||||
|
setRejectReason('')
|
||||||
|
}}
|
||||||
|
onConfirm={confirmReject}
|
||||||
|
>
|
||||||
|
<View className="p-4">
|
||||||
|
<TextArea
|
||||||
|
placeholder="请输入驳回原因"
|
||||||
|
value={rejectReason}
|
||||||
|
onChange={(value) => setRejectReason(value)}
|
||||||
|
maxLength={200}
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* 打款凭证对话框 */}
|
||||||
|
<Dialog
|
||||||
|
visible={payDialogVisible}
|
||||||
|
title="确认打款"
|
||||||
|
onCancel={() => {
|
||||||
|
setPayDialogVisible(false)
|
||||||
|
setCurrentRecord(null)
|
||||||
|
setPaymentImages([])
|
||||||
|
}}
|
||||||
|
onConfirm={confirmPayment}
|
||||||
|
>
|
||||||
|
<View className="p-4">
|
||||||
|
<View className="mb-3">
|
||||||
|
<Text className="text-sm text-gray-600">
|
||||||
|
打款金额:¥{Number(currentRecord?.money || 0).toFixed(2)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View className="mb-3">
|
||||||
|
<Text className="text-sm text-gray-600 mb-2 block">
|
||||||
|
上传打款凭证(选填,最多3张)
|
||||||
|
</Text>
|
||||||
|
<View className="flex flex-wrap gap-2">
|
||||||
|
{paymentImages.map((img, index) => (
|
||||||
|
<View key={index} className="relative w-20 h-20">
|
||||||
|
<img src={img} className="w-full h-full object-cover rounded" />
|
||||||
|
<View
|
||||||
|
className="absolute top-0 right-0 bg-red-500 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs"
|
||||||
|
onClick={() => handleRemovePaymentImage(index)}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
{paymentImages.length < 3 && (
|
||||||
|
<View
|
||||||
|
className="w-20 h-20 border-2 border-dashed border-gray-300 rounded flex items-center justify-center"
|
||||||
|
onClick={handleUploadPaymentImage}
|
||||||
|
>
|
||||||
|
<Text className="text-2xl text-gray-400">+</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Dialog>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DealerWithdraw
|
||||||
@@ -57,7 +57,7 @@ let baseUrl = Taro.getStorageSync('ApiUrl') || BaseUrl;
|
|||||||
|
|
||||||
// 开发环境配置
|
// 开发环境配置
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
baseUrl = 'http://localhost:9200/api'
|
// baseUrl = 'http://localhost:9200/api'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求拦截器
|
// 请求拦截器
|
||||||
|
|||||||
Reference in New Issue
Block a user