From 0a517b1247e935dff874cb73090ba5de535215bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Sun, 28 Sep 2025 14:36:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(branding):=20=E6=9B=B4=E6=96=B0=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=90=8D=E7=A7=B0=E5=8F=8A=E6=97=B6=E9=87=8C=E9=99=A2?= =?UTF-8?q?=E5=AD=90=E5=B8=82=E9=9B=86=E7=9B=B8=E5=85=B3=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将应用名称从"时里院子市集"更新为"通源堂健康生态平台" - 修改了config/env.ts中的生产环境应用名称配置 - 更新了src/cms/category/index.tsx中的分享标题引用 - 调整了src/admin/components/UserCell.tsx中的导航路径从/dealer/index到/doctor/index --- config/env.ts | 2 +- project.tt.json | 4 +- src/admin/components/UserCell.tsx | 2 +- src/api/cms/cmsAd/index.ts | 2 +- src/api/cms/cmsAd/model/index.ts | 2 +- src/api/cms/cmsArticle/index.ts | 14 +- src/api/cms/cmsWebsiteField/model/index.ts | 5 +- src/api/cmsArticle/index.ts | 218 ++++++ src/api/cmsArticle/model/index.ts | 132 ++++ src/api/invite/index.ts | 1 + src/api/shop/shopDealerApply/model/index.ts | 25 +- src/app.config.ts | 8 +- src/app.ts | 11 + src/cms/category/index.tsx | 2 +- src/components/UnifiedQRButton.tsx | 2 +- src/dealer/apply/add.tsx | 433 ----------- src/dealer/customer/index.config.ts | 3 - src/dealer/index.config.ts | 3 - src/dealer/index.scss | 0 src/dealer/index.tsx | 295 -------- src/dealer/info.tsx | 157 ---- src/dealer/invite-stats/index.config.ts | 7 - src/dealer/invite-stats/index.tsx | 336 --------- src/dealer/orders/index.config.ts | 3 - src/dealer/orders/index.tsx | 192 ----- src/dealer/qrcode/index.config.ts | 3 - src/dealer/qrcode/index.tsx | 398 ----------- src/dealer/team/index.config.ts | 3 - src/dealer/team/index.tsx | 439 ------------ .../withdraw/__tests__/withdraw.test.tsx | 184 ----- src/dealer/withdraw/index.config.ts | 3 - src/dealer/withdraw/index.tsx | 499 ------------- src/doctor/apply/add.config.ts | 2 +- src/doctor/apply/add.tsx | 456 +++++++++--- src/{dealer => doctor}/bank/add.config.ts | 0 src/{dealer => doctor}/bank/add.tsx | 0 src/{dealer => doctor}/bank/index.config.ts | 0 src/{dealer => doctor}/bank/index.tsx | 6 +- src/{dealer => doctor}/customer/README.md | 2 +- .../apply => doctor/customer}/add.config.ts | 2 +- src/{dealer => doctor}/customer/add.tsx | 0 src/doctor/customer/index.config.ts | 3 + src/{dealer => doctor}/customer/index.tsx | 125 ++-- .../customer/trading.config.ts | 0 src/{dealer => doctor}/customer/trading.tsx | 0 src/doctor/index.config.ts | 2 +- src/doctor/index.tsx | 151 ++-- src/doctor/info.tsx | 2 +- .../customer => doctor/orders}/add.config.ts | 2 +- src/doctor/orders/add.tsx | 135 ++++ src/doctor/orders/index.config.ts | 2 +- src/doctor/orders/index.tsx | 417 +++-------- src/doctor/qrcode/index.tsx | 136 ++-- src/doctor/team/index.config.ts | 2 +- src/doctor/team/index.tsx | 670 ++++++++++-------- src/{dealer => doctor}/wechat/index.config.ts | 0 src/{dealer => doctor}/wechat/index.scss | 0 src/{dealer => doctor}/wechat/index.tsx | 0 src/{dealer => doctor}/withdraw/debug.tsx | 0 src/doctor/withdraw/index.tsx | 447 ++++++------ src/hooks/useUser.ts | 2 +- src/pages/cart/cart.tsx | 2 +- src/pages/cms/category/index.tsx | 2 +- src/pages/user/components/IsDealer.tsx | 20 +- src/pages/user/components/UserCard.tsx | 101 +-- src/pages/user/components/UserGrid.tsx | 4 +- src/pages/user/user.tsx | 20 +- src/shop/category/index.tsx | 2 +- src/user/chat/message/add.tsx | 2 +- src/user/chat/message/detail.tsx | 2 +- src/utils/customerStatus.ts | 103 +++ src/utils/dateUtils.ts | 126 ++++ 72 files changed, 2140 insertions(+), 4196 deletions(-) create mode 100644 src/api/cmsArticle/index.ts create mode 100644 src/api/cmsArticle/model/index.ts delete mode 100644 src/dealer/apply/add.tsx delete mode 100644 src/dealer/customer/index.config.ts delete mode 100644 src/dealer/index.config.ts delete mode 100644 src/dealer/index.scss delete mode 100644 src/dealer/index.tsx delete mode 100644 src/dealer/info.tsx delete mode 100644 src/dealer/invite-stats/index.config.ts delete mode 100644 src/dealer/invite-stats/index.tsx delete mode 100644 src/dealer/orders/index.config.ts delete mode 100644 src/dealer/orders/index.tsx delete mode 100644 src/dealer/qrcode/index.config.ts delete mode 100644 src/dealer/qrcode/index.tsx delete mode 100644 src/dealer/team/index.config.ts delete mode 100644 src/dealer/team/index.tsx delete mode 100644 src/dealer/withdraw/__tests__/withdraw.test.tsx delete mode 100644 src/dealer/withdraw/index.config.ts delete mode 100644 src/dealer/withdraw/index.tsx rename src/{dealer => doctor}/bank/add.config.ts (100%) rename src/{dealer => doctor}/bank/add.tsx (100%) rename src/{dealer => doctor}/bank/index.config.ts (100%) rename src/{dealer => doctor}/bank/index.tsx (95%) rename src/{dealer => doctor}/customer/README.md (99%) rename src/{dealer/apply => doctor/customer}/add.config.ts (62%) rename src/{dealer => doctor}/customer/add.tsx (100%) create mode 100644 src/doctor/customer/index.config.ts rename src/{dealer => doctor}/customer/index.tsx (85%) rename src/{dealer => doctor}/customer/trading.config.ts (100%) rename src/{dealer => doctor}/customer/trading.tsx (100%) rename src/{dealer/customer => doctor/orders}/add.config.ts (62%) create mode 100644 src/doctor/orders/add.tsx rename src/{dealer => doctor}/wechat/index.config.ts (100%) rename src/{dealer => doctor}/wechat/index.scss (100%) rename src/{dealer => doctor}/wechat/index.tsx (100%) rename src/{dealer => doctor}/withdraw/debug.tsx (100%) create mode 100644 src/utils/customerStatus.ts create mode 100644 src/utils/dateUtils.ts diff --git a/config/env.ts b/config/env.ts index c92883b..edbcd70 100644 --- a/config/env.ts +++ b/config/env.ts @@ -9,7 +9,7 @@ export const ENV_CONFIG = { // 生产环境 production: { API_BASE_URL: 'https://cms-api.websoft.top/api', - APP_NAME: '时里院子市集', + APP_NAME: '通源堂健康生态平台', DEBUG: 'false', }, // 测试环境 diff --git a/project.tt.json b/project.tt.json index 71d3700..942b617 100644 --- a/project.tt.json +++ b/project.tt.json @@ -1,7 +1,7 @@ { "miniprogramRoot": "./", - "projectname": "mp-react", - "description": "时里院子市集", + "projectname": "template-10559", + "description": "通源堂健康生态平台", "appid": "touristappid", "setting": { "urlCheck": true, diff --git a/src/admin/components/UserCell.tsx b/src/admin/components/UserCell.tsx index 0928980..57bb70c 100644 --- a/src/admin/components/UserCell.tsx +++ b/src/admin/components/UserCell.tsx @@ -36,7 +36,7 @@ const UserCell = () => { backgroundImage: 'linear-gradient(to right bottom, #54a799, #177b73)', }} title={ - navTo('/dealer/index', true)}> + navTo('/doctor/index', true)}> 开通会员 享优惠 diff --git a/src/api/cms/cmsAd/index.ts b/src/api/cms/cmsAd/index.ts index a9b1ca7..37dae87 100644 --- a/src/api/cms/cmsAd/index.ts +++ b/src/api/cms/cmsAd/index.ts @@ -102,7 +102,7 @@ export async function getCmsAd(id: number) { } /** - * 根据id查询广告位 + * 根据code查询广告位 */ export async function getCmsAdByCode(code: string) { const res = await request.get>( diff --git a/src/api/cms/cmsAd/model/index.ts b/src/api/cms/cmsAd/model/index.ts index 76fd9a4..3f900a7 100644 --- a/src/api/cms/cmsAd/model/index.ts +++ b/src/api/cms/cmsAd/model/index.ts @@ -1,4 +1,4 @@ -import type { PageParam } from '@/api/index'; +import type { PageParam } from '@/api'; /** * 广告位 diff --git a/src/api/cms/cmsArticle/index.ts b/src/api/cms/cmsArticle/index.ts index 7bc2c44..94339ec 100644 --- a/src/api/cms/cmsArticle/index.ts +++ b/src/api/cms/cmsArticle/index.ts @@ -1,5 +1,5 @@ import request from '@/utils/request'; -import type {ApiResult, PageResult} from '@/api/index'; +import type {ApiResult, PageResult} from '@/api'; import type {CmsArticle, CmsArticleParam} from './model'; /** @@ -204,3 +204,15 @@ export async function getByIds(params?: CmsArticleParam) { return Promise.reject(new Error(res.message)); } +/** + * 根据code查询文章 + */ +export async function getCmsArticleByCode(code: string) { + const res = await request.get>( + '/cms/cms-article/getByCode/' + code + ); + if (res.code === 0 && res.data) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} diff --git a/src/api/cms/cmsWebsiteField/model/index.ts b/src/api/cms/cmsWebsiteField/model/index.ts index 3a13b4a..4863d36 100644 --- a/src/api/cms/cmsWebsiteField/model/index.ts +++ b/src/api/cms/cmsWebsiteField/model/index.ts @@ -1,4 +1,4 @@ -import type { PageParam } from '@/api/index'; +import type { PageParam } from '@/api'; /** * 应用参数 @@ -48,10 +48,13 @@ export interface Config { loginBgImg?: string; address?: string; tel?: string; + theme?: string; workDay?: string; kefu2?: string; kefu1?: string; email?: string; loginTitle?: string; sysLogo?: string; + vipText?: string; + vipComments?: string; } diff --git a/src/api/cmsArticle/index.ts b/src/api/cmsArticle/index.ts new file mode 100644 index 0000000..608c0e5 --- /dev/null +++ b/src/api/cmsArticle/index.ts @@ -0,0 +1,218 @@ +import request from '@/utils/request'; +import type {ApiResult, PageResult} from '@/api'; +import type {CmsArticle, CmsArticleParam} from './model'; + +/** + * 分页查询文章 + */ +export async function pageCmsArticle(params: CmsArticleParam) { + const res = await request.get>>( + '/cms/cms-article/page', + params + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 查询文章列表 + */ +export async function listCmsArticle(params?: CmsArticleParam) { + const res = await request.get>( + '/cms/cms-article', + params + ); + if (res.code === 0 && res.data) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 添加文章 + */ +export async function addCmsArticle(data: CmsArticle) { + const res = await request.post>( + '/cms/cms-article', + data + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 修改文章 + */ +export async function updateCmsArticle(data: CmsArticle) { + const res = await request.put>( + '/cms/cms-article', + data + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 删除文章 + */ +export async function removeCmsArticle(id?: number) { + const res = await request.del>( + '/cms/cms-article/' + id + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 批量删除文章 + */ +export async function removeBatchCmsArticle(data: (number | undefined)[]) { + const res = await request.del>( + '/cms/cms-article/batch', + { + data + } + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 根据id查询文章 + */ +export async function getCmsArticle(id: number) { + const res = await request.get>( + '/cms/cms-article/' + id + ); + if (res.code === 0 && res.data) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +export async function getCount(params?: CmsArticleParam) { + const res = await request.get>('/cms/cms-article/data', { + params + }); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 上一篇 + * @param params + */ +export async function getPrevious(params?: CmsArticleParam) { + const res = await request.get>( + '/cms/cms-article/getPrevious/' + params?.articleId, + { + params + } + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 下一篇 + * @param params + */ +export async function getNext(params?: CmsArticleParam) { + const res = await request.get>( + '/cms/cms-article/getNext/' + params?.articleId, + { + params + } + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 验证文章密码 + * @param params + */ +export async function checkArticlePassword(params?: CmsArticleParam) { + const res = await request.get>( + '/cms/cms-article/checkArticlePassword', + { + params + } + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +export async function findTags(params?: CmsArticleParam) { + const res = await request.get>( + '/cms/cms-article/findTags', + { + params + } + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +export async function pageTags(params?: CmsArticleParam) { + const res = await request.get>( + '/cms/cms-article/pageTags', + { + params + } + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 按IDS查询文章 + * @param params + */ +export async function getByIds(params?: CmsArticleParam) { + const res = await request.get>( + '/cms/cms-article/getByIds', + { + params + } + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 根据code查询文章 + */ +export async function getByCode(code: string) { + const res = await request.get>( + '/cms/cms-article/getByCode/' + code + ); + if (res.code === 0 && res.data) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} diff --git a/src/api/cmsArticle/model/index.ts b/src/api/cmsArticle/model/index.ts new file mode 100644 index 0000000..5206b48 --- /dev/null +++ b/src/api/cmsArticle/model/index.ts @@ -0,0 +1,132 @@ +import type { PageParam } from '@/api/index'; + +/** + * 文章 + */ +export interface CmsArticle { + // 文章ID + articleId?: number; + // 文章标题 + title?: string; + // 文章类型 0常规 1视频 + type?: number; + // 文章模型 + model?: string; + // 文章详情页模板 + detail?: string; + // banner图片 + banner?: string; + // 列表显示方式(10小图展示 20大图展示) + showType?: number; + // 话题 + topic?: string; + // 标签 + tags?: any; + // 栏目ID + categoryId?: number; + // 栏目名称 + categoryName?: string; + // 封面图 + image?: string; + // 价格 + price?: number; + startTime?: any; + endTime?: any; + // 缩列图 + thumbnail?: string; + // 来源 + source?: string; + // 产品概述 + overview?: string; + // 虚拟阅读量(仅用作展示) + virtualViews?: number; + // 实际阅读量 + actualViews?: number; + // 购买人数 + bmUsers?: number; + // 浏览权限(0公开 1会员 2密码) + permission?: number; + // 访问密码 + password?: string; + // 确认密码 + password2?: string; + // 发布来源客户端 (APP、H5、小程序等) + platform?: string; + // 文章附件 + files?: string; + // 视频地址 + video?: string; + // 接受的文件类型 + accept?: string; + // 经度 + longitude?: string; + // 纬度 + latitude?: string; + // 所在省份 + province?: string; + // 所在城市 + city?: string; + // 所在辖区 + region?: string; + // 街道地址 + address?: string; + // 点赞数 + likes?: number; + // pdf地址 + pdfUrl?: string; + // 评论数 + commentNumbers?: number; + // 提醒谁看 + toUsers?: string; + // 文章内容 + content?: string; + // 是否推荐 + recommend?: number; + // 用户ID + userId?: number; + // 排序(数字越小越靠前) + sortNumber?: number; + // 备注 + comments?: string; + // 状态, 0已发布, 1待审核 2已驳回 3违规内容 + status?: number; + // 是否删除, 0否, 1是 + deleted?: number; + // 租户id + tenantId?: number; + // 创建时间 + createTime?: string; + // 修改时间 + updateTime?: string; + // 父级id + parentId?: number; + nickname?: string; + username?: string; + author?: string; + shopId?: number; + tenantName?: string; + logo?: string; + fileList?: any; + // 编辑器类型 + editor?: number; +} + +/** + * 文章搜索条件 + */ +export interface CmsArticleParam extends PageParam { + articleId?: number; + articleIds?: string; + categoryId?: number; + parentId?: number; + status?: number; + // 是否推荐 + recommend?: number; + keywords?: string; + // 验证密码 + password?: string; + password2?: string; + tags?: string; + detail?: string; + sceneType?: string; +} diff --git a/src/api/invite/index.ts b/src/api/invite/index.ts index ba38f75..9dc508e 100644 --- a/src/api/invite/index.ts +++ b/src/api/invite/index.ts @@ -111,6 +111,7 @@ export interface InviteRecordParam { */ export async function generateMiniProgramCode(data: MiniProgramCodeParam) { try { + // return 'http://127.0.0.1:9200/api/wx-login/getOrderQRCodeUnlimited/' + data.scene; const url = '/wx-login/getOrderQRCodeUnlimited/' + data.scene; // 由于接口直接返回图片buffer,我们直接构建完整的URL return `${BaseUrl}${url}`; diff --git a/src/api/shop/shopDealerApply/model/index.ts b/src/api/shop/shopDealerApply/model/index.ts index 8ea27cc..3187633 100644 --- a/src/api/shop/shopDealerApply/model/index.ts +++ b/src/api/shop/shopDealerApply/model/index.ts @@ -1,4 +1,4 @@ -import type { PageParam } from '@/api/index'; +import type { PageParam } from '@/api'; /** * 分销商申请记录表 @@ -10,6 +10,14 @@ export interface ShopDealerApply { userId?: number; // 姓名 realName?: string; + // 分销商名称 + dealerName?: string; + // 分销商编号 + dealerCode?: string; + // 详细地址 + address?: string; + // 金额 + money?: number; // 手机号 mobile?: string; // 推荐人用户ID @@ -17,7 +25,9 @@ export interface ShopDealerApply { // 申请方式(10需后台审核 20无需审核) applyType?: number; // 申请时间 - applyTime?: number; + applyTime?: string; + // 签单时间 + contractTime?: string; // 审核状态 (10待审核 20审核通过 30驳回) applyStatus?: number; // 审核时间 @@ -30,6 +40,14 @@ export interface ShopDealerApply { createTime?: string; // 修改时间 updateTime?: string; + // 过期时间 + expirationTime?: string; + // 备注 + comments?: string; + // 昵称 + nickName?: string; + // 推荐人名称 + refereeName?: string; } /** @@ -37,7 +55,10 @@ export interface ShopDealerApply { */ export interface ShopDealerApplyParam extends PageParam { applyId?: number; + type?: number; + dealerName?: string; mobile?: string; userId?: number; keywords?: string; + applyStatus?: number; // 申请状态筛选 (10待审核 20审核通过 30驳回) } diff --git a/src/app.config.ts b/src/app.config.ts index 3f49c23..5e83456 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -73,16 +73,20 @@ export default { ] }, { - "root": "dealer", + "root": "doctor", "pages": [ "index", "apply/add", "withdraw/index", "orders/index", + "orders/add", "team/index", "qrcode/index", "invite-stats/index", - "info" + "info", + "customer/index", + "customer/add", + "customer/trading", ] }, { diff --git a/src/app.ts b/src/app.ts index 9eba816..41fd67b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -7,6 +7,7 @@ import {loginByOpenId} from "@/api/layout"; import {TenantId} from "@/config/app"; import {saveStorageByLoginUser} from "@/utils/server"; import {parseInviteParams, saveInviteParams, trackInviteSource, handleInviteRelation, debugInviteInfo} from "@/utils/invite"; +import {configWebsiteField} from "@/api/cms/cmsWebsiteField"; function App(props: { children: any; }) { const reload = () => { @@ -52,6 +53,7 @@ function App(props: { children: any; }) { // 处理小程序启动参数中的邀请信息 const options = Taro.getLaunchOptionsSync() handleLaunchOptions(options) + handleTheme() }) // 处理启动参数 @@ -94,6 +96,15 @@ function App(props: { children: any; }) { } } + const handleTheme = () => { + configWebsiteField().then(data => { + // 设置主题 + if(data.theme && !Taro.getStorageSync('user_theme')){ + Taro.setStorageSync('user_theme', data.theme) + } + }) + } + // 对应 onHide useDidHide(() => { }) diff --git a/src/cms/category/index.tsx b/src/cms/category/index.tsx index fe81623..b6ccd89 100644 --- a/src/cms/category/index.tsx +++ b/src/cms/category/index.tsx @@ -44,7 +44,7 @@ function Category() { useShareAppMessage(() => { return { - title: `${nav?.categoryName}_时里院子市集`, + title: `${nav?.categoryName}_通源堂健康生态平台`, path: `/shop/category/index?id=${categoryId}`, success: function () { console.log('分享成功'); diff --git a/src/components/UnifiedQRButton.tsx b/src/components/UnifiedQRButton.tsx index 18fee99..6c019d5 100644 --- a/src/components/UnifiedQRButton.tsx +++ b/src/components/UnifiedQRButton.tsx @@ -29,7 +29,7 @@ export interface UnifiedQRButtonProps { * 支持登录和核销两种类型的二维码扫描 */ const UnifiedQRButton: React.FC = ({ - type = 'default', + type = 'danger', size = 'small', text = '扫码', showIcon = true, diff --git a/src/dealer/apply/add.tsx b/src/dealer/apply/add.tsx deleted file mode 100644 index f862bf3..0000000 --- a/src/dealer/apply/add.tsx +++ /dev/null @@ -1,433 +0,0 @@ -import {useEffect, useState, useRef} from "react"; -import {Loading, CellGroup, Input, Form, Avatar, Button, Space} from '@nutui/nutui-react-taro' -import {Edit} from '@nutui/icons-react-taro' -import Taro from '@tarojs/taro' -import {View} from '@tarojs/components' -import FixedButton from "@/components/FixedButton"; -import {useUser} from "@/hooks/useUser"; -import {TenantId} from "@/config/app"; -import {updateUser} from "@/api/system/user"; -import {User} from "@/api/system/user/model"; -import {getStoredInviteParams, handleInviteRelation} from "@/utils/invite"; -import {addShopDealerUser} from "@/api/shop/shopDealerUser"; -import {listUserRole, updateUserRole} from "@/api/system/userRole"; - -// 类型定义 -interface ChooseAvatarEvent { - detail: { - avatarUrl: string; - }; -} - -interface InputEvent { - detail: { - value: string; - }; -} - -const AddUserAddress = () => { - const {user, loginUser} = useUser() - const [loading, setLoading] = useState(true) - const [FormData, setFormData] = useState() - const formRef = useRef(null) - - const reload = async () => { - const inviteParams = getStoredInviteParams() - if (inviteParams?.inviter) { - setFormData({ - ...user, - refereeId: Number(inviteParams.inviter), - // 清空昵称,强制用户手动输入 - nickname: '', - }) - } else { - // 如果没有邀请参数,也要确保昵称为空 - setFormData({ - ...user, - nickname: '', - }) - } - } - - - const uploadAvatar = ({detail}: ChooseAvatarEvent) => { - // 先更新本地显示的头像(临时显示) - const tempFormData = { - ...FormData, - avatar: `${detail.avatarUrl}`, - } - setFormData(tempFormData) - - Taro.uploadFile({ - url: 'https://server.websoft.top/api/oss/upload', - filePath: detail.avatarUrl, - name: 'file', - header: { - 'content-type': 'application/json', - TenantId - }, - success: async (res) => { - const data = JSON.parse(res.data); - if (data.code === 0) { - const finalAvatarUrl = `${data.data.thumbnail}` - - try { - // 使用 useUser hook 的 updateUser 方法更新头像 - await updateUser({ - avatar: finalAvatarUrl - }) - - Taro.showToast({ - title: '头像上传成功', - icon: 'success', - duration: 1500 - }) - } catch (error) { - console.error('更新用户头像失败:', error) - } - - // 无论用户信息更新是否成功,都要更新本地FormData - const finalFormData = { - ...tempFormData, - avatar: finalAvatarUrl - } - setFormData(finalFormData) - - // 同步更新表单字段 - if (formRef.current) { - formRef.current.setFieldsValue({ - avatar: finalAvatarUrl - }) - } - } else { - // 上传失败,恢复原来的头像 - setFormData({ - ...FormData, - avatar: user?.avatar || '' - }) - Taro.showToast({ - title: '上传失败', - icon: 'error' - }) - } - }, - fail: (error) => { - console.error('上传头像失败:', error) - Taro.showToast({ - title: '上传失败', - icon: 'error' - }) - // 恢复原来的头像 - setFormData({ - ...FormData, - avatar: user?.avatar || '' - }) - } - }) - } - - // 提交表单 - const submitSucceed = async (values: any) => { - try { - // 验证必填字段 - if (!values.phone && !FormData?.phone) { - Taro.showToast({ - title: '请先获取手机号', - icon: 'error' - }); - return; - } - - // 验证昵称:必须填写且不能是默认的微信昵称 - const nickname = values.realName || FormData?.nickname || ''; - if (!nickname || nickname.trim() === '') { - Taro.showToast({ - title: '请填写昵称', - icon: 'error' - }); - return; - } - - // 检查是否为默认的微信昵称(常见的默认昵称) - const defaultNicknames = ['微信用户', 'WeChat User', '微信昵称']; - if (defaultNicknames.includes(nickname.trim())) { - Taro.showToast({ - title: '请填写真实昵称,不能使用默认昵称', - icon: 'error' - }); - return; - } - - // 验证昵称长度 - if (nickname.trim().length < 2) { - Taro.showToast({ - title: '昵称至少需要2个字符', - icon: 'error' - }); - return; - } - - if (!values.avatar && !FormData?.avatar) { - Taro.showToast({ - title: '请上传头像', - icon: 'error' - }); - return; - } - console.log(values,FormData) - - const roles = await listUserRole({userId: user?.userId}) - console.log(roles, 'roles...') - - // 准备提交的数据 - await updateUser({ - userId: user?.userId, - nickname: values.realName || FormData?.nickname, - phone: values.phone || FormData?.phone, - avatar: values.avatar || FormData?.avatar, - refereeId: values.refereeId || FormData?.refereeId - }); - - await addShopDealerUser({ - userId: user?.userId, - realName: values.realName || FormData?.nickname, - mobile: values.phone || FormData?.phone, - refereeId: values.refereeId || FormData?.refereeId - }) - - if (roles.length > 0) { - await updateUserRole({ - ...roles[0], - roleId: 1848 - }) - } - - - Taro.showToast({ - title: `注册成功`, - icon: 'success' - }); - - setTimeout(() => { - Taro.navigateBack(); - }, 1000); - - } catch (error) { - console.error('验证邀请人失败:', error); - } - } - - // 获取微信昵称 - const getWxNickname = (nickname: string) => { - // 更新表单数据 - const updatedFormData = { - ...FormData, - nickname: nickname - } - setFormData(updatedFormData); - - // 同步更新表单字段 - if (formRef.current) { - formRef.current.setFieldsValue({ - realName: nickname - }) - } - } - - /* 获取用户手机号 */ - const handleGetPhoneNumber = ({detail}: { detail: { code?: string, encryptedData?: string, iv?: string } }) => { - const {code, encryptedData, iv} = detail - Taro.login({ - success: (loginRes) => { - if (code) { - Taro.request({ - url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone', - method: 'POST', - data: { - authCode: loginRes.code, - code, - encryptedData, - iv, - notVerifyPhone: true, - refereeId: 0, - sceneType: 'save_referee', - tenantId: TenantId - }, - header: { - 'content-type': 'application/json', - TenantId - }, - success: async function (res) { - if (res.data.code == 1) { - Taro.showToast({ - title: res.data.message, - icon: 'error', - duration: 2000 - }) - return false; - } - // 登录成功 - const token = res.data.data.access_token; - const userData = res.data.data.user; - console.log(userData, 'userData...') - // 使用useUser Hook的loginUser方法更新状态 - loginUser(token, userData); - - if (userData.phone) { - console.log('手机号已获取', userData.phone) - const updatedFormData = { - ...FormData, - phone: userData.phone, - // 不自动填充微信昵称,保持用户已输入的昵称 - nickname: FormData?.nickname || '', - // 只在没有头像时才使用微信头像 - avatar: FormData?.avatar || userData.avatar - } - setFormData(updatedFormData) - - // 更新表单字段值 - if (formRef.current) { - formRef.current.setFieldsValue({ - phone: userData.phone, - // 不覆盖用户已输入的昵称 - realName: FormData?.nickname || '', - avatar: FormData?.avatar || userData.avatar - }) - } - - Taro.showToast({ - title: '手机号获取成功', - icon: 'success', - duration: 1500 - }) - } - - - // 处理邀请关系 - if (userData?.userId) { - try { - const inviteSuccess = await handleInviteRelation(userData.userId) - if (inviteSuccess) { - Taro.showToast({ - title: '邀请关系建立成功', - icon: 'success', - duration: 2000 - }) - } - } catch (error) { - console.error('处理邀请关系失败:', error) - } - } - - // 显示登录成功提示 - // Taro.showToast({ - // title: '注册成功', - // icon: 'success', - // duration: 1500 - // }) - - // 不需要重新启动小程序,状态已经通过useUser更新 - // 可以选择性地刷新当前页面数据 - // await reload(); - } - }) - } else { - console.log('登录失败!') - } - } - }) - } - - // 处理固定按钮点击事件 - const handleFixedButtonClick = () => { - // 触发表单提交 - formRef.current?.submit(); - }; - - const submitFailed = (error: any) => { - console.log(error, 'err...') - } - - useEffect(() => { - reload().then(() => { - setLoading(false) - }) - }, [user?.userId]); // 依赖用户ID,当用户变化时重新加载 - - // 当FormData变化时,同步更新表单字段值 - useEffect(() => { - if (formRef.current && FormData) { - formRef.current.setFieldsValue({ - refereeId: FormData.refereeId, - phone: FormData.phone, - avatar: FormData.avatar, - realName: FormData.nickname - }); - } - }, [FormData]); - - if (loading) { - return 加载中 - } - - return ( - <> -
submitSucceed(values)} - onFinishFailed={(errors) => submitFailed(errors)} - > - - - - - - - - - - - - - - { - FormData?.phone && - - - } - - getWxNickname(e.detail.value)} - /> - - -
- - {/* 底部浮动按钮 */} - } - text={'立即注册'} - onClick={handleFixedButtonClick} - /> - - - ); -}; - -export default AddUserAddress; diff --git a/src/dealer/customer/index.config.ts b/src/dealer/customer/index.config.ts deleted file mode 100644 index 6e26749..0000000 --- a/src/dealer/customer/index.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default definePageConfig({ - navigationBarTitleText: '客户列表' -}) diff --git a/src/dealer/index.config.ts b/src/dealer/index.config.ts deleted file mode 100644 index d456dbd..0000000 --- a/src/dealer/index.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default definePageConfig({ - navigationBarTitleText: '分销中心' -}) diff --git a/src/dealer/index.scss b/src/dealer/index.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/dealer/index.tsx b/src/dealer/index.tsx deleted file mode 100644 index 06c514f..0000000 --- a/src/dealer/index.tsx +++ /dev/null @@ -1,295 +0,0 @@ -import React from 'react' -import {View, Text} from '@tarojs/components' -import {ConfigProvider, Button, Grid, Avatar} from '@nutui/nutui-react-taro' -import { - User, - Shopping, - Dongdong, - ArrowRight, - Purse, - People -} from '@nutui/icons-react-taro' -import {useDealerUser} from '@/hooks/useDealerUser' -import { useThemeStyles } from '@/hooks/useTheme' -import {businessGradients, cardGradients, gradientUtils} from '@/styles/gradients' -import Taro from '@tarojs/taro' - -const DealerIndex: React.FC = () => { - const { - dealerUser, - error, - refresh, - } = useDealerUser() - - // 使用主题样式 - 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()') - - if (error) { - return ( - - - {error} - - - - ) - } - - return ( - - - {/*头部信息*/} - {dealerUser && ( - - {/* 装饰性背景元素 - 小程序兼容版本 */} - - - - - } - className="mr-4" - style={{ - border: '2px solid rgba(255, 255, 255, 0.3)' - }} - /> - - - {dealerUser?.realName || '分销商'} - - - ID: {dealerUser.userId} | 推荐人: {dealerUser.refereeId || '无'} - - - - 加入时间 - - {formatTime(dealerUser.createTime)} - - - - - )} - - {/* 佣金统计卡片 */} - {dealerUser && ( - - - 佣金统计 - - - - - {formatMoney(dealerUser.money)} - - 可提现 - - - - {formatMoney(dealerUser.freezeMoney)} - - 冻结中 - - - - {formatMoney(dealerUser.totalMoney)} - - 累计收益 - - - - )} - - {/* 团队统计 */} - {dealerUser && ( - - - 我的邀请 - navigateToPage('/dealer/team/index')} - > - 查看详情 - - - - - - - {dealerUser.firstNum || 0} - - 一级成员 - - - - {dealerUser.secondNum || 0} - - 二级成员 - - - - {dealerUser.thirdNum || 0} - - 三级成员 - - - - )} - - {/* 功能导航 */} - - 分销工具 - - - navigateToPage('/dealer/orders/index')}> - - - - - - - - navigateToPage('/dealer/withdraw/index')}> - - - - - - - - navigateToPage('/dealer/team/index')}> - - - - - - - - navigateToPage('/dealer/qrcode/index')}> - - - - - - - - - {/* 第二行功能 */} - {/**/} - {/* navigateToPage('/dealer/invite-stats/index')}>*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - - {/* /!* 预留其他功能位置 *!/*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/**/} - - - - - {/* 底部安全区域 */} - - - ) -} - -export default DealerIndex diff --git a/src/dealer/info.tsx b/src/dealer/info.tsx deleted file mode 100644 index 0f18841..0000000 --- a/src/dealer/info.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import React from 'react' -import { View, Text } from '@tarojs/components' -import { Button, Cell, CellGroup, Tag } from '@nutui/nutui-react-taro' -import { useDealerUser } from '@/hooks/useDealerUser' -import Taro from '@tarojs/taro' - -const DealerInfo: React.FC = () => { - const { - dealerUser, - loading, - error, - refresh, - } = useDealerUser() - - // 跳转到申请页面 - const navigateToApply = () => { - Taro.navigateTo({ - url: '/pages/dealer/apply/add' - }) - } - - if (error) { - return ( - - - {error} - - - - ) - } - - return ( - - {/* 页面标题 */} - - - 经销商信息 - - - - {!dealerUser ? ( - // 非经销商状态 - - - 您还不是经销商 - - 成为经销商后可享受专属价格和佣金收益 - - - - - ) : ( - // 经销商信息展示 - - {/* 状态卡片 */} - - - 经销商状态 - - {dealerUser.realName} - - - - {/* 基本信息 */} - - - - - - - - {/* 操作按钮 */} - - - - - - {/* 经销商权益 */} - - 经销商权益 - - - • 享受经销商专属价格 - - - • 获得推广佣金收益 - - - • 优先获得新品信息 - - - • 专属客服支持 - - - - - {/* 佣金统计 */} - - 佣金统计 - - - 0 - 今日佣金 - - - 0 - 本月佣金 - - - 0 - 累计佣金 - - - - - )} - - {/* 刷新按钮 */} - - - 点击刷新数据 - - - - ) -} - -export default DealerInfo diff --git a/src/dealer/invite-stats/index.config.ts b/src/dealer/invite-stats/index.config.ts deleted file mode 100644 index 246a9aa..0000000 --- a/src/dealer/invite-stats/index.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default definePageConfig({ - navigationBarTitleText: '邀请统计', - navigationBarBackgroundColor: '#ffffff', - navigationBarTextStyle: 'black', - backgroundColor: '#f5f5f5', - enablePullDownRefresh: true -}) diff --git a/src/dealer/invite-stats/index.tsx b/src/dealer/invite-stats/index.tsx deleted file mode 100644 index 3139ad4..0000000 --- a/src/dealer/invite-stats/index.tsx +++ /dev/null @@ -1,336 +0,0 @@ -import React, { useState, useEffect, useCallback } from 'react' -import { View, Text } from '@tarojs/components' -import { - Empty, - Tabs, - Loading, - PullToRefresh, - Card, -} from '@nutui/nutui-react-taro' -import { - User, - ArrowUp, - Calendar, - Share, - Target, - Gift -} from '@nutui/icons-react-taro' -import Taro from '@tarojs/taro' -import { useDealerUser } from '@/hooks/useDealerUser' -import { - getInviteStats, - getMyInviteRecords, - getInviteRanking -} from '@/api/invite' -import type { - InviteStats, - InviteRecord -} from '@/api/invite' -import { businessGradients } from '@/styles/gradients' -import {InviteRanking} from "@/api/invite/model"; - -const InviteStatsPage: React.FC = () => { - const [activeTab, setActiveTab] = useState('stats') - const [loading, setLoading] = useState(false) - const [inviteStats, setInviteStats] = useState(null) - const [inviteRecords, setInviteRecords] = useState([]) - const [ranking, setRanking] = useState([]) - const [dateRange, setDateRange] = useState('month') - const { dealerUser } = useDealerUser() - - // 获取邀请统计数据 - const fetchInviteStats = useCallback(async () => { - if (!dealerUser?.userId) return - - try { - setLoading(true) - const stats = await getInviteStats(dealerUser.userId) - stats && setInviteStats(stats) - } catch (error) { - console.error('获取邀请统计失败:', error) - Taro.showToast({ - title: '获取统计数据失败', - icon: 'error' - }) - } finally { - setLoading(false) - } - }, [dealerUser?.userId]) - - // 获取邀请记录 - const fetchInviteRecords = useCallback(async () => { - if (!dealerUser?.userId) return - - try { - const result = await getMyInviteRecords({ - page: 1, - limit: 50, - inviterId: dealerUser.userId - }) - setInviteRecords(result?.list || []) - } catch (error) { - console.error('获取邀请记录失败:', error) - } - }, [dealerUser?.userId]) - - // 获取邀请排行榜 - const fetchRanking = useCallback(async () => { - try { - const result = await getInviteRanking({ - limit: 20, - period: dateRange as 'day' | 'week' | 'month' - }) - setRanking(result || []) - } catch (error) { - console.error('获取排行榜失败:', error) - } - }, [dateRange]) - - // 刷新数据 - const handleRefresh = async () => { - await Promise.all([ - fetchInviteStats(), - fetchInviteRecords(), - fetchRanking() - ]) - } - - // 初始化数据 - useEffect(() => { - if (dealerUser?.userId) { - fetchInviteStats().then() - fetchInviteRecords().then() - fetchRanking().then() - } - }, [fetchInviteStats, fetchInviteRecords, fetchRanking]) - - // 获取状态显示文本 - const getStatusText = (status: string) => { - const statusMap: Record = { - 'pending': '待注册', - 'registered': '已注册', - 'activated': '已激活' - } - return statusMap[status] || status - } - - // 获取状态颜色 - const getStatusColor = (status: string) => { - const colorMap: Record = { - 'pending': 'text-orange-500', - 'registered': 'text-blue-500', - 'activated': 'text-green-500' - } - return colorMap[status] || 'text-gray-500' - } - - // 渲染统计概览 - const renderStatsOverview = () => ( - - {/* 核心数据卡片 */} - - - 邀请概览 - {loading ? ( - - - - ) : inviteStats ? ( - - - - - {inviteStats.totalInvites || 0} - - 总邀请数 - - - - - - {inviteStats.successfulRegistrations || 0} - - 成功注册 - - - - - - {inviteStats.conversionRate ? `${(inviteStats.conversionRate * 100).toFixed(1)}%` : '0%'} - - 转化率 - - - - - - {inviteStats.todayInvites || 0} - - 今日邀请 - - - ) : ( - - 暂无统计数据 - - )} - - - - {/* 邀请来源分析 */} - {inviteStats?.sourceStats && inviteStats.sourceStats.length > 0 && ( - - - 邀请来源分析 - - {inviteStats.sourceStats.map((source, index) => ( - - - - {source.source} - - - {source.count} - - 转化率 {source.conversionRate ? `${(source.conversionRate * 100).toFixed(1)}%` : '0%'} - - - - ))} - - - - )} - - ) - - // 渲染邀请记录 - const renderInviteRecords = () => ( - - {inviteRecords.length > 0 ? ( - - {inviteRecords.map((record, index) => ( - - - - - {record.inviteeName || `用户${record.inviteeId}`} - - - {getStatusText(record.status || 'pending')} - - - - - 来源: {record.source || '未知'} - {record.inviteTime ? new Date(record.inviteTime).toLocaleDateString() : ''} - - - {record.registerTime && ( - - 注册时间: {new Date(record.registerTime).toLocaleString()} - - )} - - - ))} - - ) : ( - - )} - - ) - - // 渲染排行榜 - const renderRanking = () => ( - - - setDateRange}> - - - - - - - {ranking.length > 0 ? ( - - {ranking.map((item, index) => ( - - - - {index < 3 ? ( - - ) : ( - {index + 1} - )} - - - - {item.inviterName} - - 邀请 {item.inviteCount} 人 · 转化率 {item.conversionRate ? `${(item.conversionRate * 100).toFixed(1)}%` : '0%'} - - - - {item.successCount} - - - ))} - - ) : ( - - )} - - ) - - if (!dealerUser) { - return ( - - - 加载中... - - ) - } - - return ( - - {/* 头部 */} - - - - - 邀请统计 - - 查看您的邀请效果和推广数据 - - - - - {/* 标签页 */} - - setActiveTab}> - - - - - - - {/* 内容区域 */} - - - {activeTab === 'stats' && renderStatsOverview()} - {activeTab === 'records' && renderInviteRecords()} - {activeTab === 'ranking' && renderRanking()} - - - - ) -} - -export default InviteStatsPage diff --git a/src/dealer/orders/index.config.ts b/src/dealer/orders/index.config.ts deleted file mode 100644 index 3bb5694..0000000 --- a/src/dealer/orders/index.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default definePageConfig({ - navigationBarTitleText: '分销订单' -}) diff --git a/src/dealer/orders/index.tsx b/src/dealer/orders/index.tsx deleted file mode 100644 index f3b6bbf..0000000 --- a/src/dealer/orders/index.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import React, {useState, useEffect, useCallback} from 'react' -import {View, Text, ScrollView} from '@tarojs/components' -import {Empty, Tag, PullToRefresh, Loading} from '@nutui/nutui-react-taro' -import Taro from '@tarojs/taro' -import {pageShopDealerOrder} from '@/api/shop/shopDealerOrder' -import {useDealerUser} from '@/hooks/useDealerUser' -import type {ShopDealerOrder} from '@/api/shop/shopDealerOrder/model' - -interface OrderWithDetails extends ShopDealerOrder { - orderNo?: string - customerName?: string - userCommission?: string -} - -const DealerOrders: React.FC = () => { - const [loading, setLoading] = useState(false) - const [refreshing, setRefreshing] = useState(false) - const [loadingMore, setLoadingMore] = useState(false) - const [orders, setOrders] = useState([]) - const [currentPage, setCurrentPage] = useState(1) - const [hasMore, setHasMore] = useState(true) - - const {dealerUser} = useDealerUser() - - // 获取订单数据 - const fetchOrders = 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 pageShopDealerOrder({ - page, - limit: 10 - }) - - if (result?.list) { - const newOrders = result.list.map(order => ({ - ...order, - orderNo: `${order.orderId}`, - customerName: `用户${order.userId}`, - userCommission: order.firstMoney || '0.00' - })) - - if (page === 1) { - setOrders(newOrders) - } else { - setOrders(prev => [...prev, ...newOrders]) - } - - setHasMore(newOrders.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 fetchOrders(1, true) - } - - // 加载更多 - const handleLoadMore = async () => { - if (!loadingMore && hasMore) { - await fetchOrders(currentPage + 1) - } - } - - // 初始化加载数据 - useEffect(() => { - if (dealerUser?.userId) { - fetchOrders(1) - } - }, [fetchOrders]) - - const getStatusText = (isSettled?: number, isInvalid?: number) => { - if (isInvalid === 1) return '已失效' - if (isSettled === 1) return '已结算' - return '待结算' - } - - const getStatusColor = (isSettled?: number, isInvalid?: number) => { - if (isInvalid === 1) return 'danger' - if (isSettled === 1) return 'success' - return 'warning' - } - - const renderOrderItem = (order: OrderWithDetails) => ( - - - - 订单号:{order.orderNo} - - - {getStatusText(order.isSettled, order.isInvalid)} - - - - - - 订单金额:¥{order.orderPrice || '0.00'} - - - 我的佣金:¥{order.userCommission} - - - - - - 客户:{order.customerName} - - - {order.createTime} - - - - ) - - if (!dealerUser) { - return ( - - - 加载中... - - ) - } - - return ( - - - - - {loading && orders.length === 0 ? ( - - - 加载中... - - ) : orders.length > 0 ? ( - <> - {orders.map(renderOrderItem)} - {loadingMore && ( - - - 加载更多... - - )} - {!hasMore && orders.length > 0 && ( - - 没有更多数据了 - - )} - - ) : ( - - )} - - - - - ) -} - -export default DealerOrders diff --git a/src/dealer/qrcode/index.config.ts b/src/dealer/qrcode/index.config.ts deleted file mode 100644 index b075b21..0000000 --- a/src/dealer/qrcode/index.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default definePageConfig({ - navigationBarTitleText: '推广二维码' -}) diff --git a/src/dealer/qrcode/index.tsx b/src/dealer/qrcode/index.tsx deleted file mode 100644 index 90edb58..0000000 --- a/src/dealer/qrcode/index.tsx +++ /dev/null @@ -1,398 +0,0 @@ -import React, {useState, useEffect} from 'react' -import {View, Text, Image} from '@tarojs/components' -import {Button, Loading} from '@nutui/nutui-react-taro' -import {Download, 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('') - const [loading, setLoading] = useState(false) - // const [inviteStats, setInviteStats] = useState(null) - // const [statsLoading, setStatsLoading] = useState(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 ( - - - 加载中... - - ) - } - - return ( - - {/* 头部卡片 */} - - {/* 装饰背景 */} - - - - 我的邀请小程序码 - - 分享小程序码邀请好友,获得丰厚佣金奖励 - - - - - - {/* 小程序码展示区 */} - - - {loading ? ( - - - 生成中... - - ) : miniProgramCodeUrl ? ( - - { - Taro.showModal({ - title: '二维码加载失败', - content: '请检查网络连接或联系管理员', - showCancel: true, - confirmText: '重新生成', - success: (res) => { - if (res.confirm) { - generateMiniProgramCode(); - } - } - }); - }} - - /> - - ) : ( - - - 小程序码生成失败 - - - )} - - - 扫码加入我的团队 - - - 好友扫描小程序码即可直接进入小程序并建立邀请关系 - - - - - - - {/* 操作按钮 */} - - - - - {/**/} - {/* }*/} - {/* onClick={copyInviteInfo}*/} - {/* disabled={!dealerUser?.userId || loading}*/} - {/* >*/} - {/* 复制邀请信息*/} - {/* */} - {/**/} - {/**/} - {/* }*/} - {/* onClick={shareMiniProgramCode}*/} - {/* disabled={!dealerUser?.userId || loading}*/} - {/* >*/} - {/* 分享给好友*/} - {/* */} - {/**/} - - - {/* 推广说明 */} - - 推广说明 - - - - - 好友通过您的二维码或链接注册成为您的团队成员 - - - - - - 好友购买商品时,您可获得相应层级的分销佣金 - - - - - - 支持三级分销,团队越大收益越多 - - - - - - {/* 邀请统计数据 */} - {/**/} - {/* 我的邀请数据*/} - {/* {statsLoading ? (*/} - {/* */} - {/* */} - {/* 加载中...*/} - {/* */} - {/* ) : inviteStats ? (*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* {inviteStats.totalInvites || 0}*/} - {/* */} - {/* 总邀请数*/} - {/* */} - {/* */} - {/* */} - {/* {inviteStats.successfulRegistrations || 0}*/} - {/* */} - {/* 成功注册*/} - {/* */} - {/* */} - - {/* */} - {/* */} - {/* */} - {/* {inviteStats.conversionRate ? `${(inviteStats.conversionRate * 100).toFixed(1)}%` : '0%'}*/} - {/* */} - {/* 转化率*/} - {/* */} - {/* */} - {/* */} - {/* {inviteStats.todayInvites || 0}*/} - {/* */} - {/* 今日邀请*/} - {/* */} - {/* */} - - {/* /!* 邀请来源统计 *!/*/} - {/* {inviteStats.sourceStats && inviteStats.sourceStats.length > 0 && (*/} - {/* */} - {/* 邀请来源分布*/} - {/* */} - {/* {inviteStats.sourceStats.map((source, index) => (*/} - {/* */} - {/* */} - {/* */} - {/* {source.source}*/} - {/* */} - {/* */} - {/* {source.count}*/} - {/* */} - {/* {source.conversionRate ? `${(source.conversionRate * 100).toFixed(1)}%` : '0%'}*/} - {/* */} - {/* */} - {/* */} - {/* ))}*/} - {/* */} - {/* */} - {/* )}*/} - {/* */} - {/* ) : (*/} - {/* */} - {/* 暂无邀请数据*/} - {/* */} - {/* 刷新数据*/} - {/* */} - {/* */} - {/* )}*/} - {/**/} - - - ) -} - -export default DealerQrcode diff --git a/src/dealer/team/index.config.ts b/src/dealer/team/index.config.ts deleted file mode 100644 index ddd6b66..0000000 --- a/src/dealer/team/index.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default definePageConfig({ - navigationBarTitleText: '邀请推广' -}) diff --git a/src/dealer/team/index.tsx b/src/dealer/team/index.tsx deleted file mode 100644 index af7d8e7..0000000 --- a/src/dealer/team/index.tsx +++ /dev/null @@ -1,439 +0,0 @@ -import React, {useState, useEffect, useCallback} from 'react' -import {View, Text} from '@tarojs/components' -import {Phone, Edit, Message} from '@nutui/icons-react-taro' -import {Space, Empty, Avatar, Button} from '@nutui/nutui-react-taro' -import Taro from '@tarojs/taro' -import {useDealerUser} from '@/hooks/useDealerUser' -import {listShopDealerReferee} from '@/api/shop/shopDealerReferee' -import {pageShopDealerOrder} from '@/api/shop/shopDealerOrder' -import type {ShopDealerReferee} from '@/api/shop/shopDealerReferee/model' -import FixedButton from "@/components/FixedButton"; -import navTo from "@/utils/common"; -import {updateUser} from "@/api/system/user"; - -interface TeamMemberWithStats extends ShopDealerReferee { - name?: string - avatar?: string - nickname?: string; - alias?: string; - phone?: string; - orderCount?: number - commission?: string - status?: 'active' | 'inactive' - subMembers?: number - joinTime?: string - dealerAvatar?: string; - dealerName?: string; - dealerPhone?: string; -} - -// 层级信息接口 -interface LevelInfo { - dealerId: number - dealerName?: string - level: number -} - -const DealerTeam: React.FC = () => { - const [teamMembers, setTeamMembers] = useState([]) - const {dealerUser} = useDealerUser() - const [dealerId, setDealerId] = useState() - // 层级栈,用于支持返回上一层 - const [levelStack, setLevelStack] = useState([]) - const [loading, setLoading] = useState(false) - // 当前查看的用户名称 - const [currentDealerName, setCurrentDealerName] = useState('') - - // 异步加载成员统计数据 - const loadMemberStats = async (members: TeamMemberWithStats[]) => { - // 分批处理,避免过多并发请求 - const batchSize = 3 - for (let i = 0; i < members.length; i += batchSize) { - const batch = members.slice(i, i + batchSize) - - const batchStats = await Promise.all( - batch.map(async (member) => { - try { - // 并行获取订单统计和下级成员数量 - const [orderResult, subMembersResult] = await Promise.all([ - pageShopDealerOrder({ - page: 1, - userId: member.userId - }), - listShopDealerReferee({ - dealerId: member.userId, - deleted: 0 - }) - ]) - - let orderCount = 0 - let commission = '0.00' - let status: 'active' | 'inactive' = 'inactive' - - if (orderResult?.list) { - const orders = orderResult.list - orderCount = orders.length - commission = orders.reduce((sum, order) => { - const levelCommission = member.level === 1 ? order.firstMoney : - member.level === 2 ? order.secondMoney : - order.thirdMoney - return sum + parseFloat(levelCommission || '0') - }, 0).toFixed(2) - - // 判断活跃状态(30天内有订单为活跃) - const thirtyDaysAgo = new Date() - thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30) - const hasRecentOrder = orders.some(order => - new Date(order.createTime || '') > thirtyDaysAgo - ) - status = hasRecentOrder ? 'active' : 'inactive' - } - - return { - ...member, - orderCount, - commission, - status, - subMembers: subMembersResult?.length || 0 - } - } catch (error) { - console.error(`获取成员${member.userId}数据失败:`, error) - return { - ...member, - orderCount: 0, - commission: '0.00', - status: 'inactive' as const, - subMembers: 0 - } - } - }) - ) - - // 更新这一批成员的数据 - setTeamMembers(prevMembers => { - const updatedMembers = [...prevMembers] - batchStats.forEach(updatedMember => { - const index = updatedMembers.findIndex(m => m.userId === updatedMember.userId) - if (index !== -1) { - updatedMembers[index] = updatedMember - } - }) - return updatedMembers - }) - - // 添加小延迟,避免请求过于密集 - if (i + batchSize < members.length) { - await new Promise(resolve => setTimeout(resolve, 100)) - } - } - } - - // 获取团队数据 - const fetchTeamData = useCallback(async () => { - if (!dealerUser?.userId && !dealerId) return - - try { - setLoading(true) - console.log(dealerId, 'dealerId>>>>>>>>>') - // 获取团队成员关系 - const refereeResult = await listShopDealerReferee({ - dealerId: dealerId ? dealerId : dealerUser?.userId - }) - - if (refereeResult) { - console.log('团队成员原始数据:', refereeResult) - // 处理团队成员数据 - const processedMembers: TeamMemberWithStats[] = refereeResult.map(member => ({ - ...member, - name: `${member.userId}`, - orderCount: 0, - commission: '0.00', - status: 'active' as const, - subMembers: 0, - joinTime: member.createTime - })) - - // 先显示基础数据,然后异步加载详细统计 - setTeamMembers(processedMembers) - setLoading(false) - - // 异步加载每个成员的详细统计数据 - loadMemberStats(processedMembers) - - } - } catch (error) { - console.error('获取团队数据失败:', error) - Taro.showToast({ - title: '获取团队数据失败', - icon: 'error' - }) - } finally { - setLoading(false) - } - }, [dealerUser?.userId, dealerId]) - - // 查看下级成员 - const getNextUser = (item: TeamMemberWithStats) => { - // 检查层级限制:最多只能查看2层(levelStack.length >= 1 表示已经是第2层了) - if (levelStack.length >= 1) { - return - } - - // 如果没有下级成员,不允许点击 - if (!item.subMembers || item.subMembers === 0) { - return - } - - console.log('点击用户:', item.userId, item.name) - - // 将当前层级信息推入栈中 - const currentLevel: LevelInfo = { - dealerId: dealerId || dealerUser?.userId || 0, - dealerName: currentDealerName || (dealerId ? '上级' : dealerUser?.realName || '我'), - level: levelStack.length - } - setLevelStack(prev => [...prev, currentLevel]) - - // 切换到下级 - setDealerId(item.userId) - setCurrentDealerName(item.nickname || item.dealerName || `用户${item.userId}`) - } - - // 返回上一层 - const goBack = () => { - if (levelStack.length === 0) { - // 如果栈为空,返回首页或上一页 - Taro.navigateBack() - return - } - - // 从栈中弹出上一层信息 - const prevLevel = levelStack[levelStack.length - 1] - setLevelStack(prev => prev.slice(0, -1)) - - if (prevLevel.dealerId === (dealerUser?.userId || 0)) { - // 返回到根层级 - setDealerId(undefined) - setCurrentDealerName('') - } else { - setDealerId(prevLevel.dealerId) - setCurrentDealerName(prevLevel.dealerName || '') - } - } - - // 一键拨打 - const makePhoneCall = (phone: string) => { - Taro.makePhoneCall({ - phoneNumber: phone, - fail: () => { - Taro.showToast({ - title: '拨打取消', - icon: 'error' - }); - } - }); - }; - - // 别名备注 - const editAlias = (item: any, index: number) => { - Taro.showModal({ - title: '备注', - // @ts-ignore - editable: true, - placeholderText: '真实姓名', - content: item.alias || '', - success: async (res: any) => { - if (res.confirm && res.content !== undefined) { - try { - // 更新跟进情况 - await updateUser({ - userId: item.userId, - alias: res.content.trim() - }); - teamMembers[index].alias = res.content.trim() - setTeamMembers(teamMembers) - } catch (error) { - console.error('备注失败:', error); - Taro.showToast({ - title: '备注失败,请重试', - icon: 'error' - }); - } - } - } - }); - }; - - // 发送消息 - const sendMessage = (item: TeamMemberWithStats) => { - return navTo(`/user/chat/message/add?id=${item.userId}`, true) - } - - // 监听数据变化,获取团队数据 - useEffect(() => { - if (dealerUser?.userId || dealerId) { - fetchTeamData().then() - } - }, [fetchTeamData]) - - // 初始化当前用户名称 - useEffect(() => { - if (!dealerId && dealerUser?.realName && !currentDealerName) { - setCurrentDealerName(dealerUser.realName) - } - }, [dealerUser, dealerId, currentDealerName]) - - const renderMemberItem = (member: TeamMemberWithStats, index: number) => { - // 判断是否可以点击:有下级成员且未达到层级限制 - const canClick = member.subMembers && member.subMembers > 0 && levelStack.length < 1 - // 判断是否显示手机号:只有本级(levelStack.length === 0)才显示 - const showPhone = levelStack.length === 0 - // 判断数据是否还在加载中(初始值都是0或'0.00') - const isStatsLoading = member.orderCount === 0 && member.commission === '0.00' && member.subMembers === 0 - - return ( - getNextUser(member)} - > - - - - - - - {member.alias ? {member.alias} : - {member.nickname}} - {/*别名备注*/} - { - e.stopPropagation() - editAlias(member, index) - }}/> - {/*发送消息*/} - { - e.stopPropagation() - sendMessage(member) - }}/> - - - {/* 显示手机号(仅本级可见) */} - {showPhone && member.phone && ( - { - e.stopPropagation(); - makePhoneCall(member.phone || ''); - }}> - {member.phone} - - - )} - - - 加入时间:{member.joinTime} - - - - - - - 订单数 - - {isStatsLoading ? '-' : member.orderCount} - - - - 贡献佣金 - - {isStatsLoading ? '-' : `¥${member.commission}`} - - - - 团队成员 - - {isStatsLoading ? '-' : (member.subMembers || 0)} - - - - - ) - } - - const renderOverview = () => ( - - - 我的团队成员 - 成员数:{teamMembers.length} - - {teamMembers.map(renderMemberItem)} - - ) - - // 渲染顶部导航栏 - const renderHeader = () => { - if (levelStack.length === 0) return null - - return ( - - - - - {currentDealerName}的团队成员 - - - - - - ) - } - - if (!dealerUser) { - return ( - - navTo(`/dealer/apply/add`, true)}]} - /> - - ) - } - - return ( - <> - {renderHeader()} - - {loading ? ( - - 加载中... - - ) : teamMembers.length > 0 ? ( - renderOverview() - ) : ( - - - - )} - - navTo(`/dealer/qrcode/index`, true)}/> - - ) -} - -export default DealerTeam; diff --git a/src/dealer/withdraw/__tests__/withdraw.test.tsx b/src/dealer/withdraw/__tests__/withdraw.test.tsx deleted file mode 100644 index c3aeab9..0000000 --- a/src/dealer/withdraw/__tests__/withdraw.test.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import React from 'react' -import { render, fireEvent, waitFor } from '@testing-library/react' -import DealerWithdraw from '../index' -import { useDealerUser } from '@/hooks/useDealerUser' -import * as withdrawAPI from '@/api/shop/shopDealerWithdraw' - -// Mock dependencies -jest.mock('@/hooks/useDealerUser') -jest.mock('@/api/shop/shopDealerWithdraw') -jest.mock('@tarojs/taro', () => ({ - showToast: jest.fn(), - getStorageSync: jest.fn(() => 123), -})) - -const mockUseDealerUser = useDealerUser as jest.MockedFunction -const mockAddShopDealerWithdraw = withdrawAPI.addShopDealerWithdraw as jest.MockedFunction -const mockPageShopDealerWithdraw = withdrawAPI.pageShopDealerWithdraw as jest.MockedFunction - -describe('DealerWithdraw', () => { - const mockDealerUser = { - userId: 123, - money: '10000.00', - realName: '测试用户', - mobile: '13800138000' - } - - beforeEach(() => { - mockUseDealerUser.mockReturnValue({ - dealerUser: mockDealerUser, - loading: false, - error: null, - refresh: jest.fn() - }) - - mockPageShopDealerWithdraw.mockResolvedValue({ - list: [], - count: 0 - }) - }) - - afterEach(() => { - jest.clearAllMocks() - }) - - test('应该正确显示可提现余额', () => { - const { getByText } = render() - expect(getByText('10000.00')).toBeInTheDocument() - expect(getByText('可提现余额')).toBeInTheDocument() - }) - - test('应该验证最低提现金额', async () => { - mockAddShopDealerWithdraw.mockResolvedValue('success') - - const { getByPlaceholderText, getByText } = render() - - // 输入低于最低金额的数值 - const amountInput = getByPlaceholderText('请输入提现金额') - fireEvent.change(amountInput, { target: { value: '50' } }) - - // 选择提现方式 - const wechatRadio = getByText('微信钱包') - fireEvent.click(wechatRadio) - - // 提交表单 - const submitButton = getByText('申请提现') - fireEvent.click(submitButton) - - await waitFor(() => { - expect(require('@tarojs/taro').showToast).toHaveBeenCalledWith({ - title: '最低提现金额为100元', - icon: 'error' - }) - }) - }) - - test('应该验证提现金额不超过可用余额', async () => { - const { getByPlaceholderText, getByText } = render() - - // 输入超过可用余额的金额 - const amountInput = getByPlaceholderText('请输入提现金额') - fireEvent.change(amountInput, { target: { value: '20000' } }) - - // 选择提现方式 - const wechatRadio = getByText('微信钱包') - fireEvent.click(wechatRadio) - - // 提交表单 - const submitButton = getByText('申请提现') - fireEvent.click(submitButton) - - await waitFor(() => { - expect(require('@tarojs/taro').showToast).toHaveBeenCalledWith({ - title: '提现金额超过可用余额', - icon: 'error' - }) - }) - }) - - test('应该验证支付宝账户信息完整性', async () => { - const { getByPlaceholderText, getByText } = render() - - // 输入有效金额 - const amountInput = getByPlaceholderText('请输入提现金额') - fireEvent.change(amountInput, { target: { value: '1000' } }) - - // 选择支付宝提现 - const alipayRadio = getByText('支付宝') - fireEvent.click(alipayRadio) - - // 只填写账号,不填写姓名 - const accountInput = getByPlaceholderText('请输入支付宝账号') - fireEvent.change(accountInput, { target: { value: 'test@alipay.com' } }) - - // 提交表单 - const submitButton = getByText('申请提现') - fireEvent.click(submitButton) - - await waitFor(() => { - expect(require('@tarojs/taro').showToast).toHaveBeenCalledWith({ - title: '请填写完整的支付宝信息', - icon: 'error' - }) - }) - }) - - test('应该成功提交微信提现申请', async () => { - mockAddShopDealerWithdraw.mockResolvedValue('success') - - const { getByPlaceholderText, getByText } = render() - - // 输入有效金额 - const amountInput = getByPlaceholderText('请输入提现金额') - fireEvent.change(amountInput, { target: { value: '1000' } }) - - // 选择微信提现 - const wechatRadio = getByText('微信钱包') - fireEvent.click(wechatRadio) - - // 提交表单 - const submitButton = getByText('申请提现') - fireEvent.click(submitButton) - - await waitFor(() => { - expect(mockAddShopDealerWithdraw).toHaveBeenCalledWith({ - userId: 123, - money: '1000', - payType: 10, - applyStatus: 10, - platform: 'MiniProgram' - }) - }) - - await waitFor(() => { - expect(require('@tarojs/taro').showToast).toHaveBeenCalledWith({ - title: '提现申请已提交', - icon: 'success' - }) - }) - }) - - test('快捷金额按钮应该正常工作', () => { - const { getByText, getByPlaceholderText } = render() - - // 点击快捷金额按钮 - const quickAmountButton = getByText('500') - fireEvent.click(quickAmountButton) - - // 验证金额输入框的值 - const amountInput = getByPlaceholderText('请输入提现金额') as HTMLInputElement - expect(amountInput.value).toBe('500') - }) - - test('全部按钮应该设置为可用余额', () => { - const { getByText, getByPlaceholderText } = render() - - // 点击全部按钮 - const allButton = getByText('全部') - fireEvent.click(allButton) - - // 验证金额输入框的值 - const amountInput = getByPlaceholderText('请输入提现金额') as HTMLInputElement - expect(amountInput.value).toBe('10000.00') - }) -}) diff --git a/src/dealer/withdraw/index.config.ts b/src/dealer/withdraw/index.config.ts deleted file mode 100644 index 00a9f9b..0000000 --- a/src/dealer/withdraw/index.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default definePageConfig({ - navigationBarTitleText: '提现申请' -}) diff --git a/src/dealer/withdraw/index.tsx b/src/dealer/withdraw/index.tsx deleted file mode 100644 index 04a3cef..0000000 --- a/src/dealer/withdraw/index.tsx +++ /dev/null @@ -1,499 +0,0 @@ -import React, {useState, useRef, useEffect, useCallback} from 'react' -import {View, Text} from '@tarojs/components' -import { - Cell, - Space, - Button, - Form, - Input, - CellGroup, - Radio, - Tabs, - Tag, - Empty, - Loading, - PullToRefresh -} from '@nutui/nutui-react-taro' -import {Wallet} from '@nutui/icons-react-taro' -import {businessGradients} from '@/styles/gradients' -import Taro from '@tarojs/taro' -import {useDealerUser} from '@/hooks/useDealerUser' -import {pageShopDealerWithdraw, addShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw' -import type {ShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw/model' - -interface WithdrawRecordWithDetails extends ShopDealerWithdraw { - accountDisplay?: string -} - -const DealerWithdraw: React.FC = () => { - const [activeTab, setActiveTab] = useState('0') - const [selectedAccount, setSelectedAccount] = useState('') - const [loading, setLoading] = useState(false) - const [refreshing, setRefreshing] = useState(false) - const [submitting, setSubmitting] = useState(false) - const [availableAmount, setAvailableAmount] = useState('0.00') - const [withdrawRecords, setWithdrawRecords] = useState([]) - const formRef = useRef(null) - - const {dealerUser} = useDealerUser() - - // Tab 切换处理函数 - const handleTabChange = (value: string | number) => { - console.log('Tab切换到:', value) - setActiveTab(value) - - // 如果切换到提现记录页面,刷新数据 - if (String(value) === '1') { - fetchWithdrawRecords() - } - } - - // 获取可提现余额 - const fetchBalance = useCallback(async () => { - console.log(dealerUser, 'dealerUser...') - try { - setAvailableAmount(dealerUser?.money || '0.00') - } catch (error) { - console.error('获取余额失败:', error) - } - }, [dealerUser]) - - // 获取提现记录 - const fetchWithdrawRecords = useCallback(async () => { - if (!dealerUser?.userId) return - - try { - setLoading(true) - const result = await pageShopDealerWithdraw({ - page: 1, - limit: 100, - userId: dealerUser.userId - }) - - if (result?.list) { - const processedRecords = result.list.map(record => ({ - ...record, - accountDisplay: getAccountDisplay(record) - })) - setWithdrawRecords(processedRecords) - } - } catch (error) { - console.error('获取提现记录失败:', error) - Taro.showToast({ - title: '获取提现记录失败', - icon: 'error' - }) - } finally { - setLoading(false) - } - }, [dealerUser?.userId]) - - // 格式化账户显示 - 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([fetchBalance(), fetchWithdrawRecords()]) - setRefreshing(false) - } - - // 初始化加载数据 - useEffect(() => { - if (dealerUser?.userId) { - fetchBalance().then() - fetchWithdrawRecords().then() - } - }, [fetchBalance, 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 handleSubmit = async (values: any) => { - if (!dealerUser?.userId) { - Taro.showToast({ - title: '用户信息获取失败', - icon: 'error' - }) - return - } - - if (!values.accountType) { - Taro.showToast({ - title: '请选择提现方式', - icon: 'error' - }) - return - } - - // 验证提现金额 - const amount = parseFloat(values.amount) - const available = parseFloat(availableAmount.replace(/,/g, '')) - - if (isNaN(amount) || amount <= 0) { - Taro.showToast({ - title: '请输入有效的提现金额', - icon: 'error' - }) - return - } - - if (amount < 100) { - Taro.showToast({ - title: '最低提现金额为100元', - icon: 'error' - }) - return - } - - if (amount > available) { - Taro.showToast({ - title: '提现金额超过可用余额', - icon: 'error' - }) - return - } - - // 验证账户信息 - if (values.accountType === 'alipay') { - if (!values.account || !values.accountName) { - Taro.showToast({ - title: '请填写完整的支付宝信息', - icon: 'error' - }) - return - } - } else if (values.accountType === 'bank') { - if (!values.account || !values.accountName || !values.bankName) { - Taro.showToast({ - title: '请填写完整的银行卡信息', - icon: 'error' - }) - return - } - } - - try { - setSubmitting(true) - - const withdrawData: ShopDealerWithdraw = { - userId: dealerUser.userId, - money: values.amount, - payType: values.accountType === 'wechat' ? 10 : - values.accountType === 'alipay' ? 20 : 30, - applyStatus: 10, // 待审核 - platform: 'MiniProgram' - } - - // 根据提现方式设置账户信息 - if (values.accountType === 'alipay') { - withdrawData.alipayAccount = values.account - withdrawData.alipayName = values.accountName - } else if (values.accountType === 'bank') { - withdrawData.bankCard = values.account - withdrawData.bankAccount = values.accountName - withdrawData.bankName = values.bankName || '银行卡' - } - - await addShopDealerWithdraw(withdrawData) - - Taro.showToast({ - title: '提现申请已提交', - icon: 'success' - }) - - // 重置表单 - formRef.current?.resetFields() - setSelectedAccount('') - - // 刷新数据 - await handleRefresh() - - // 切换到提现记录页面 - setActiveTab('1') - - } catch (error: any) { - console.error('提现申请失败:', error) - Taro.showToast({ - title: error.message || '提现申请失败', - icon: 'error' - }) - } finally { - setSubmitting(false) - } - } - - const quickAmounts = ['100', '300', '500', '1000'] - - const setQuickAmount = (amount: string) => { - formRef.current?.setFieldsValue({amount}) - } - - const setAllAmount = () => { - formRef.current?.setFieldsValue({amount: availableAmount.replace(/,/g, '')}) - } - - // 格式化金额 - const formatMoney = (money?: string) => { - if (!money) return '0.00' - return parseFloat(money).toFixed(2) - } - - const renderWithdrawForm = () => ( - - {/* 余额卡片 */} - - {/* 装饰背景 - 小程序兼容版本 */} - - - - - {formatMoney(dealerUser?.money)} - 可提现余额 - - - - - - - - 最低提现金额:¥100 | 手续费:免费 - - - - -
- - - { - // 实时验证提现金额 - const amount = parseFloat(value) - const available = parseFloat(availableAmount.replace(/,/g, '')) - if (!isNaN(amount) && amount > available) { - // 可以在这里添加实时提示,但不阻止输入 - } - }} - /> - - - {/* 快捷金额 */} - - 快捷金额 - - {quickAmounts.map(amount => ( - - ))} - - - - - - setSelectedAccount}> - - - 微信钱包 - - - 支付宝 - - - 银行卡 - - - - - - {selectedAccount === 'alipay' && ( - <> - - - - - - - - )} - - {selectedAccount === 'bank' && ( - <> - - - - - - - - - - - )} - - {selectedAccount === 'wechat' && ( - - - 微信钱包提现将直接转入您的微信零钱 - - - )} - - - - - -
-
- ) - - const renderWithdrawRecords = () => { - console.log('渲染提现记录:', {loading, recordsCount: withdrawRecords.length, dealerUserId: dealerUser?.userId}) - - return ( - - - {loading ? ( - - - 加载中... - - ) : withdrawRecords.length > 0 ? ( - withdrawRecords.map(record => ( - - - - - 提现金额:¥{record.money} - - - 提现账户:{record.accountDisplay} - - - - {getStatusText(record.applyStatus)} - - - - - 申请时间:{record.createTime} - {record.auditTime && ( - - 审核时间:{new Date(record.auditTime).toLocaleString()} - - )} - {record.rejectReason && ( - - 驳回原因:{record.rejectReason} - - )} - - - )) - ) : ( - - )} - - - ) - } - - if (!dealerUser) { - return ( - - - 加载中... - - ) - } - - return ( - - - - {renderWithdrawForm()} - - - - {renderWithdrawRecords()} - - - - ) -} - -export default DealerWithdraw diff --git a/src/doctor/apply/add.config.ts b/src/doctor/apply/add.config.ts index 3f7f9ad..ac37521 100644 --- a/src/doctor/apply/add.config.ts +++ b/src/doctor/apply/add.config.ts @@ -1,4 +1,4 @@ export default definePageConfig({ - navigationBarTitleText: '医生入驻申请通道', + navigationBarTitleText: '邀请注册', navigationBarTextStyle: 'black' }) diff --git a/src/doctor/apply/add.tsx b/src/doctor/apply/add.tsx index 08c2f15..f862bf3 100644 --- a/src/doctor/apply/add.tsx +++ b/src/doctor/apply/add.tsx @@ -1,96 +1,210 @@ import {useEffect, useState, useRef} from "react"; -import {Loading, CellGroup, Cell, Input, Form} from '@nutui/nutui-react-taro' +import {Loading, CellGroup, Input, Form, Avatar, Button, Space} from '@nutui/nutui-react-taro' import {Edit} from '@nutui/icons-react-taro' import Taro from '@tarojs/taro' import {View} from '@tarojs/components' import FixedButton from "@/components/FixedButton"; import {useUser} from "@/hooks/useUser"; -import {ShopDealerApply} from "@/api/shop/shopDealerApply/model"; -import { - addShopDealerApply, - pageShopDealerApply, - updateShopDealerApply -} from "@/api/shop/shopDealerApply"; -import {getShopDealerUser} from "@/api/shop/shopDealerUser"; +import {TenantId} from "@/config/app"; +import {updateUser} from "@/api/system/user"; +import {User} from "@/api/system/user/model"; +import {getStoredInviteParams, handleInviteRelation} from "@/utils/invite"; +import {addShopDealerUser} from "@/api/shop/shopDealerUser"; +import {listUserRole, updateUserRole} from "@/api/system/userRole"; + +// 类型定义 +interface ChooseAvatarEvent { + detail: { + avatarUrl: string; + }; +} + +interface InputEvent { + detail: { + value: string; + }; +} const AddUserAddress = () => { - const {user} = useUser() + const {user, loginUser} = useUser() const [loading, setLoading] = useState(true) - const [FormData, setFormData] = useState() + const [FormData, setFormData] = useState() const formRef = useRef(null) - const [isEditMode, setIsEditMode] = useState(false) - const [existingApply, setExistingApply] = useState(null) - // 获取审核状态文字 - const getApplyStatusText = (status?: number) => { - switch (status) { - case 10: - return '待审核' - case 20: - return '审核通过' - case 30: - return '驳回' - default: - return '未知状态' + const reload = async () => { + const inviteParams = getStoredInviteParams() + if (inviteParams?.inviter) { + setFormData({ + ...user, + refereeId: Number(inviteParams.inviter), + // 清空昵称,强制用户手动输入 + nickname: '', + }) + } else { + // 如果没有邀请参数,也要确保昵称为空 + setFormData({ + ...user, + nickname: '', + }) } } - const reload = async () => { - // 判断用户是否登录 - if (!user?.userId) { - return false; + + const uploadAvatar = ({detail}: ChooseAvatarEvent) => { + // 先更新本地显示的头像(临时显示) + const tempFormData = { + ...FormData, + avatar: `${detail.avatarUrl}`, } - // 查询当前用户ID是否已有申请记录 - try { - const res = await pageShopDealerApply({userId: user?.userId}); - if (res && res.count > 0) { - setIsEditMode(true); - setExistingApply(res.list[0]); - // 如果有记录,填充表单数据 - setFormData(res.list[0]); - setLoading(false) - } else { - setIsEditMode(false); - setExistingApply(null); - setLoading(false) + setFormData(tempFormData) + + Taro.uploadFile({ + url: 'https://server.websoft.top/api/oss/upload', + filePath: detail.avatarUrl, + name: 'file', + header: { + 'content-type': 'application/json', + TenantId + }, + success: async (res) => { + const data = JSON.parse(res.data); + if (data.code === 0) { + const finalAvatarUrl = `${data.data.thumbnail}` + + try { + // 使用 useUser hook 的 updateUser 方法更新头像 + await updateUser({ + avatar: finalAvatarUrl + }) + + Taro.showToast({ + title: '头像上传成功', + icon: 'success', + duration: 1500 + }) + } catch (error) { + console.error('更新用户头像失败:', error) + } + + // 无论用户信息更新是否成功,都要更新本地FormData + const finalFormData = { + ...tempFormData, + avatar: finalAvatarUrl + } + setFormData(finalFormData) + + // 同步更新表单字段 + if (formRef.current) { + formRef.current.setFieldsValue({ + avatar: finalAvatarUrl + }) + } + } else { + // 上传失败,恢复原来的头像 + setFormData({ + ...FormData, + avatar: user?.avatar || '' + }) + Taro.showToast({ + title: '上传失败', + icon: 'error' + }) + } + }, + fail: (error) => { + console.error('上传头像失败:', error) + Taro.showToast({ + title: '上传失败', + icon: 'error' + }) + // 恢复原来的头像 + setFormData({ + ...FormData, + avatar: user?.avatar || '' + }) } - } catch (error) { - setLoading(true) - console.error('查询申请记录失败:', error); - setIsEditMode(false); - setExistingApply(null); - } + }) } // 提交表单 const submitSucceed = async (values: any) => { try { + // 验证必填字段 + if (!values.phone && !FormData?.phone) { + Taro.showToast({ + title: '请先获取手机号', + icon: 'error' + }); + return; + } + + // 验证昵称:必须填写且不能是默认的微信昵称 + const nickname = values.realName || FormData?.nickname || ''; + if (!nickname || nickname.trim() === '') { + Taro.showToast({ + title: '请填写昵称', + icon: 'error' + }); + return; + } + + // 检查是否为默认的微信昵称(常见的默认昵称) + const defaultNicknames = ['微信用户', 'WeChat User', '微信昵称']; + if (defaultNicknames.includes(nickname.trim())) { + Taro.showToast({ + title: '请填写真实昵称,不能使用默认昵称', + icon: 'error' + }); + return; + } + + // 验证昵称长度 + if (nickname.trim().length < 2) { + Taro.showToast({ + title: '昵称至少需要2个字符', + icon: 'error' + }); + return; + } + + if (!values.avatar && !FormData?.avatar) { + Taro.showToast({ + title: '请上传头像', + icon: 'error' + }); + return; + } + console.log(values,FormData) + + const roles = await listUserRole({userId: user?.userId}) + console.log(roles, 'roles...') // 准备提交的数据 - const submitData = { - ...values, - realName: values.realName || user?.nickname, - mobile: user?.phone, - refereeId: values.refereeId || FormData?.refereeId, - applyStatus: 10, - auditTime: undefined - }; - await getShopDealerUser(submitData.refereeId); + await updateUser({ + userId: user?.userId, + nickname: values.realName || FormData?.nickname, + phone: values.phone || FormData?.phone, + avatar: values.avatar || FormData?.avatar, + refereeId: values.refereeId || FormData?.refereeId + }); - // 如果是编辑模式,添加现有申请的id - if (isEditMode && existingApply?.applyId) { - submitData.applyId = existingApply.applyId; + await addShopDealerUser({ + userId: user?.userId, + realName: values.realName || FormData?.nickname, + mobile: values.phone || FormData?.phone, + refereeId: values.refereeId || FormData?.refereeId + }) + + if (roles.length > 0) { + await updateUserRole({ + ...roles[0], + roleId: 1848 + }) } - // 执行新增或更新操作 - if (isEditMode) { - await updateShopDealerApply(submitData); - } else { - await addShopDealerApply(submitData); - } Taro.showToast({ - title: `${isEditMode ? '提交' : '提交'}成功`, + title: `注册成功`, icon: 'success' }); @@ -100,13 +214,130 @@ const AddUserAddress = () => { } catch (error) { console.error('验证邀请人失败:', error); - return Taro.showToast({ - title: '邀请人ID不存在', - icon: 'error' - }); } } + // 获取微信昵称 + const getWxNickname = (nickname: string) => { + // 更新表单数据 + const updatedFormData = { + ...FormData, + nickname: nickname + } + setFormData(updatedFormData); + + // 同步更新表单字段 + if (formRef.current) { + formRef.current.setFieldsValue({ + realName: nickname + }) + } + } + + /* 获取用户手机号 */ + const handleGetPhoneNumber = ({detail}: { detail: { code?: string, encryptedData?: string, iv?: string } }) => { + const {code, encryptedData, iv} = detail + Taro.login({ + success: (loginRes) => { + if (code) { + Taro.request({ + url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone', + method: 'POST', + data: { + authCode: loginRes.code, + code, + encryptedData, + iv, + notVerifyPhone: true, + refereeId: 0, + sceneType: 'save_referee', + tenantId: TenantId + }, + header: { + 'content-type': 'application/json', + TenantId + }, + success: async function (res) { + if (res.data.code == 1) { + Taro.showToast({ + title: res.data.message, + icon: 'error', + duration: 2000 + }) + return false; + } + // 登录成功 + const token = res.data.data.access_token; + const userData = res.data.data.user; + console.log(userData, 'userData...') + // 使用useUser Hook的loginUser方法更新状态 + loginUser(token, userData); + + if (userData.phone) { + console.log('手机号已获取', userData.phone) + const updatedFormData = { + ...FormData, + phone: userData.phone, + // 不自动填充微信昵称,保持用户已输入的昵称 + nickname: FormData?.nickname || '', + // 只在没有头像时才使用微信头像 + avatar: FormData?.avatar || userData.avatar + } + setFormData(updatedFormData) + + // 更新表单字段值 + if (formRef.current) { + formRef.current.setFieldsValue({ + phone: userData.phone, + // 不覆盖用户已输入的昵称 + realName: FormData?.nickname || '', + avatar: FormData?.avatar || userData.avatar + }) + } + + Taro.showToast({ + title: '手机号获取成功', + icon: 'success', + duration: 1500 + }) + } + + + // 处理邀请关系 + if (userData?.userId) { + try { + const inviteSuccess = await handleInviteRelation(userData.userId) + if (inviteSuccess) { + Taro.showToast({ + title: '邀请关系建立成功', + icon: 'success', + duration: 2000 + }) + } + } catch (error) { + console.error('处理邀请关系失败:', error) + } + } + + // 显示登录成功提示 + // Taro.showToast({ + // title: '注册成功', + // icon: 'success', + // duration: 1500 + // }) + + // 不需要重新启动小程序,状态已经通过useUser更新 + // 可以选择性地刷新当前页面数据 + // await reload(); + } + }) + } else { + console.log('登录失败!') + } + } + }) + } + // 处理固定按钮点击事件 const handleFixedButtonClick = () => { // 触发表单提交 @@ -123,6 +354,18 @@ const AddUserAddress = () => { }) }, [user?.userId]); // 依赖用户ID,当用户变化时重新加载 + // 当FormData变化时,同步更新表单字段值 + useEffect(() => { + if (formRef.current && FormData) { + formRef.current.setFieldsValue({ + refereeId: FormData.refereeId, + phone: FormData.phone, + avatar: FormData.avatar, + realName: FormData.nickname + }); + } + }, [FormData]); + if (loading) { return 加载中 } @@ -139,50 +382,49 @@ const AddUserAddress = () => { > - - - - - - - + + + + + + + + + + + { + FormData?.phone && + + + } + + getWxNickname(e.detail.value)} + /> - {/* 审核状态显示(仅在编辑模式下显示) */} - {isEditMode && ( - - - {getApplyStatusText(FormData?.applyStatus)} - - } - /> - {FormData?.applyStatus === 20 && ( - - )} - {FormData?.applyStatus === 30 && ( - - )} - - )} - {/* 底部浮动按钮 */} - {(!isEditMode || FormData?.applyStatus === 10 || FormData?.applyStatus === 30) && ( - } - text={isEditMode ? '保存修改' : '提交申请'} - disabled={FormData?.applyStatus === 10} - onClick={handleFixedButtonClick} - /> - )} + } + text={'立即注册'} + onClick={handleFixedButtonClick} + /> ); diff --git a/src/dealer/bank/add.config.ts b/src/doctor/bank/add.config.ts similarity index 100% rename from src/dealer/bank/add.config.ts rename to src/doctor/bank/add.config.ts diff --git a/src/dealer/bank/add.tsx b/src/doctor/bank/add.tsx similarity index 100% rename from src/dealer/bank/add.tsx rename to src/doctor/bank/add.tsx diff --git a/src/dealer/bank/index.config.ts b/src/doctor/bank/index.config.ts similarity index 100% rename from src/dealer/bank/index.config.ts rename to src/doctor/bank/index.config.ts diff --git a/src/dealer/bank/index.tsx b/src/doctor/bank/index.tsx similarity index 95% rename from src/dealer/bank/index.tsx rename to src/doctor/bank/index.tsx index 71046ff..9587c72 100644 --- a/src/dealer/bank/index.tsx +++ b/src/doctor/bank/index.tsx @@ -86,9 +86,9 @@ const DealerBank = () => { description="您还没有地址哦" /> - + + onClick={() => Taro.navigateTo({url: '/doctor/bank/wxAddress'})}>获取微信地址 @@ -126,7 +126,7 @@ const DealerBank = () => { ))} {/* 底部浮动按钮 */} - Taro.navigateTo({url: '/dealer/bank/add'})} /> + Taro.navigateTo({url: '/doctor/bank/add'})} />
); }; diff --git a/src/dealer/customer/README.md b/src/doctor/customer/README.md similarity index 99% rename from src/dealer/customer/README.md rename to src/doctor/customer/README.md index 20ccfd6..14cb5ef 100644 --- a/src/dealer/customer/README.md +++ b/src/doctor/customer/README.md @@ -99,7 +99,7 @@ CustomerManagement ## 文件结构 ``` -src/dealer/customer/ +src/doctor/customer/ ├── index.tsx # 主页面组件 └── README.md # 说明文档 diff --git a/src/dealer/apply/add.config.ts b/src/doctor/customer/add.config.ts similarity index 62% rename from src/dealer/apply/add.config.ts rename to src/doctor/customer/add.config.ts index ac37521..596fab1 100644 --- a/src/dealer/apply/add.config.ts +++ b/src/doctor/customer/add.config.ts @@ -1,4 +1,4 @@ export default definePageConfig({ - navigationBarTitleText: '邀请注册', + navigationBarTitleText: '患者报备', navigationBarTextStyle: 'black' }) diff --git a/src/dealer/customer/add.tsx b/src/doctor/customer/add.tsx similarity index 100% rename from src/dealer/customer/add.tsx rename to src/doctor/customer/add.tsx diff --git a/src/doctor/customer/index.config.ts b/src/doctor/customer/index.config.ts new file mode 100644 index 0000000..539f8b2 --- /dev/null +++ b/src/doctor/customer/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '患者管理' +}) diff --git a/src/dealer/customer/index.tsx b/src/doctor/customer/index.tsx similarity index 85% rename from src/dealer/customer/index.tsx rename to src/doctor/customer/index.tsx index bc316ec..14ec7dd 100644 --- a/src/dealer/customer/index.tsx +++ b/src/doctor/customer/index.tsx @@ -1,7 +1,7 @@ import {useState, useEffect, useCallback} from 'react' import {View, Text} from '@tarojs/components' import Taro, {useDidShow} from '@tarojs/taro' -import {Loading, InfiniteLoading, Empty, Space, Tabs, TabPane, Tag, Button} from '@nutui/nutui-react-taro' +import {Loading, InfiniteLoading, Empty, Space, Tabs, TabPane, Tag, Button, SearchBar} from '@nutui/nutui-react-taro' import {Phone, AngleDoubleLeft} from '@nutui/icons-react-taro' import type {ShopDealerApply, ShopDealerApply as UserType} from "@/api/shop/shopDealerApply/model"; import { @@ -26,7 +26,8 @@ const CustomerIndex = () => { const [list, setList] = useState([]) const [loading, setLoading] = useState(false) const [activeTab, setActiveTab] = useState('all') - const [searchValue, _] = useState('') + const [searchValue, setSearchValue] = useState('') + const [displaySearchValue, setDisplaySearchValue] = useState('') const [page, setPage] = useState(1) const [hasMore, setHasMore] = useState(true) @@ -227,13 +228,22 @@ const CustomerIndex = () => { } + // 防抖搜索功能 + useEffect(() => { + const timer = setTimeout(() => { + setDisplaySearchValue(searchValue); + }, 300); // 300ms 防抖 + + return () => clearTimeout(timer); + }, [searchValue]); + // 根据搜索条件筛选数据(状态筛选已在API层面处理) const getFilteredList = () => { let filteredList = list; // 按搜索关键词筛选 - if (searchValue.trim()) { - const keyword = searchValue.trim().toLowerCase(); + if (displaySearchValue.trim()) { + const keyword = displaySearchValue.trim().toLowerCase(); filteredList = filteredList.filter(customer => (customer.realName && customer.realName.toLowerCase().includes(keyword)) || (customer.dealerName && customer.dealerName.toLowerCase().includes(keyword)) || @@ -466,56 +476,81 @@ const CustomerIndex = () => { // 渲染客户列表 const renderCustomerList = () => { const filteredList = getFilteredList(); + const isSearching = displaySearchValue.trim().length > 0; return ( - - { - // 滚动事件处理 - }} - onScrollToUpper={() => { - // 滚动到顶部事件处理 - }} - loadingText={ - <> - 加载中... - - } - loadMoreText={ - filteredList.length === 0 ? ( - - ) : ( - - 没有更多了 + + {/* 搜索结果统计 */} + {isSearching && ( + + + 搜索 "{displaySearchValue}" 的结果,共找到 {filteredList.length} 条记录 + + + )} + + + { + // 滚动事件处理 + }} + onScrollToUpper={() => { + // 滚动到顶部事件处理 + }} + loadingText={ + <> + 加载中... + + } + loadMoreText={ + filteredList.length === 0 ? ( + + ) : ( + + 没有更多了 + + ) + } + > + {loading && filteredList.length === 0 ? ( + + + 加载中... - ) - } - > - {loading && filteredList.length === 0 ? ( - - - 加载中... - - ) : ( - filteredList.map(renderCustomerItem) - )} - + ) : ( + filteredList.map(renderCustomerItem) + )} + +
); }; return ( + {/* 搜索栏 */} + + setSearchValue(value)} + onClear={() => { + setSearchValue(''); + setDisplaySearchValue(''); + }} + clearable + /> + {/* 顶部Tabs */} diff --git a/src/dealer/customer/trading.config.ts b/src/doctor/customer/trading.config.ts similarity index 100% rename from src/dealer/customer/trading.config.ts rename to src/doctor/customer/trading.config.ts diff --git a/src/dealer/customer/trading.tsx b/src/doctor/customer/trading.tsx similarity index 100% rename from src/dealer/customer/trading.tsx rename to src/doctor/customer/trading.tsx diff --git a/src/doctor/index.config.ts b/src/doctor/index.config.ts index bbd5ebc..f05d7ba 100644 --- a/src/doctor/index.config.ts +++ b/src/doctor/index.config.ts @@ -1,3 +1,3 @@ export default definePageConfig({ - navigationBarTitleText: '医生版' + navigationBarTitleText: '医生端' }) diff --git a/src/doctor/index.tsx b/src/doctor/index.tsx index da6ce5d..130c6fd 100644 --- a/src/doctor/index.tsx +++ b/src/doctor/index.tsx @@ -3,15 +3,18 @@ import {View, Text} from '@tarojs/components' import {ConfigProvider, Button, Grid, Avatar} from '@nutui/nutui-react-taro' import { User, - Shopping, - Dongdong, - ArrowRight, - Purse, - People + UserAdd, + Edit, + Comment, + QrCode, + Notice, + Orderlist, + Health, + PickedUp } from '@nutui/icons-react-taro' import {useDealerUser} from '@/hooks/useDealerUser' import { useThemeStyles } from '@/hooks/useTheme' -import {businessGradients, cardGradients, gradientUtils} from '@/styles/gradients' +import {gradientUtils} from '@/styles/gradients' import Taro from '@tarojs/taro' const DealerIndex: React.FC = () => { @@ -30,10 +33,10 @@ const DealerIndex: React.FC = () => { } // 格式化金额 - const formatMoney = (money?: string) => { - if (!money) return '0.00' - return parseFloat(money).toFixed(2) - } + // const formatMoney = (money?: string) => { + // if (!money) return '0.00' + // return parseFloat(money).toFixed(2) + // } // 格式化时间 const formatTime = (time?: string) => { @@ -103,12 +106,12 @@ const DealerIndex: React.FC = () => { - {dealerUser?.realName || '分销商'} + {dealerUser?.realName || '医生名称'} - ID: {dealerUser.userId} | 推荐人: {dealerUser.refereeId || '无'} + 医生编号: {dealerUser.userId} @@ -125,80 +128,9 @@ const DealerIndex: React.FC = () => { )} - {/* 佣金统计卡片 */} - {dealerUser && ( - - - 佣金统计 - - - - - ¥{formatMoney(dealerUser.money)} - - 可提现 - - - - ¥{formatMoney(dealerUser.freezeMoney)} - - 冻结中 - - - - ¥{formatMoney(dealerUser.totalMoney)} - - 累计收益 - - - - )} - - {/* 团队统计 */} - {dealerUser && ( - - - 我的邀请 - navigateToPage('/dealer/team/index')} - > - 查看详情 - - - - - - - {dealerUser.firstNum || 0} - - 一级成员 - - - - {dealerUser.secondNum || 0} - - 二级成员 - - - - {dealerUser.thirdNum || 0} - - 三级成员 - - - - )} - {/* 功能导航 */} - 分销工具 + 管理工具 { border: 'none' } as React.CSSProperties} > - navigateToPage('/dealer/orders/index')}> + navigateToPage('/doctor/customer/index')}> - + - navigateToPage('/dealer/withdraw/index')}> + navigateToPage('/doctor/orders/add')}> - + - navigateToPage('/dealer/team/index')}> + navigateToPage('/doctor/team/index')}> - + - navigateToPage('/dealer/qrcode/index')}> + navigateToPage('/doctor/orders/index')}> - + + + + + + + + + + + navigateToPage('/doctor/team/index')}> + + + + + + + + navigateToPage('/doctor/qrcode/index')}> + + + + + + + + navigateToPage('/doctor/apply/add')}> + + + + + + + {/* 第二行功能 */} @@ -252,7 +217,7 @@ const DealerIndex: React.FC = () => { {/* border: 'none'*/} {/* } as React.CSSProperties}*/} {/*>*/} - {/* navigateToPage('/dealer/invite-stats/index')}>*/} + {/* navigateToPage('/doctor/invite-stats/index')}>*/} {/* */} {/* */} {/* */} diff --git a/src/doctor/info.tsx b/src/doctor/info.tsx index 0f18841..8e03a69 100644 --- a/src/doctor/info.tsx +++ b/src/doctor/info.tsx @@ -15,7 +15,7 @@ const DealerInfo: React.FC = () => { // 跳转到申请页面 const navigateToApply = () => { Taro.navigateTo({ - url: '/pages/dealer/apply/add' + url: '/pages/doctor/apply/add' }) } diff --git a/src/dealer/customer/add.config.ts b/src/doctor/orders/add.config.ts similarity index 62% rename from src/dealer/customer/add.config.ts rename to src/doctor/orders/add.config.ts index fb7c4ce..00434e4 100644 --- a/src/dealer/customer/add.config.ts +++ b/src/doctor/orders/add.config.ts @@ -1,4 +1,4 @@ export default definePageConfig({ - navigationBarTitleText: '客户报备', + navigationBarTitleText: '在线开方', navigationBarTextStyle: 'black' }) diff --git a/src/doctor/orders/add.tsx b/src/doctor/orders/add.tsx new file mode 100644 index 0000000..36126a3 --- /dev/null +++ b/src/doctor/orders/add.tsx @@ -0,0 +1,135 @@ +import {useEffect, useState, useRef} from "react"; +import {useRouter} from '@tarojs/taro' +import {Loading, CellGroup, Input, Form, Cell, Avatar} from '@nutui/nutui-react-taro' +import {ArrowRight} from '@nutui/icons-react-taro' +import {View, Text} from '@tarojs/components' +import Taro from '@tarojs/taro' +import FixedButton from "@/components/FixedButton"; +import {addShopChatMessage} from "@/api/shop/shopChatMessage"; +import {ShopChatMessage} from "@/api/shop/shopChatMessage/model"; +import navTo from "@/utils/common"; +import {getUser} from "@/api/system/user"; +import {User} from "@/api/system/user/model"; + +const AddMessage = () => { + const {params} = useRouter(); + const [toUser, setToUser] = useState() + const [loading, setLoading] = useState(true) + const [FormData, _] = useState() + const formRef = useRef(null) + + // 判断是编辑还是新增模式 + const isEditMode = !!params.id + const toUserId = params.id ? Number(params.id) : undefined + + const reload = async () => { + if(toUserId){ + getUser(Number(toUserId)).then(data => { + setToUser(data) + }) + } + } + + // 提交表单 + const submitSucceed = async (values: any) => { + try { + // 准备提交的数据 + const submitData = { + ...values + }; + + console.log('提交数据:', submitData) + + // 参数校验 + if(!toUser){ + Taro.showToast({ + title: `请选择发送对象`, + icon: 'error' + }); + return false; + } + + // 判断内容是否为空 + if (!values.content) { + Taro.showToast({ + title: `请输入内容`, + icon: 'error' + }); + return false; + } + // 执行新增或更新操作 + await addShopChatMessage({ + toUserId: toUserId, + formUserId: Taro.getStorageSync('UserId'), + type: 'text', + content: values.content + }); + + Taro.showToast({ + title: `发送成功`, + icon: 'success' + }); + + setTimeout(() => { + Taro.navigateBack(); + }, 1000); + + } catch (error) { + console.error('发送失败:', error); + Taro.showToast({ + title: `发送失败`, + icon: 'error' + }); + } + } + + const submitFailed = (error: any) => { + console.log(error, 'err...') + } + + useEffect(() => { + reload().then(() => { + setLoading(false) + }) + }, [isEditMode]); + + if (loading) { + return 加载中 + } + + return ( + <> + + + + {toUser.alias || toUser.nickname} + {toUser.mobile} + + + ) : '选择患者'} extra={( + + )} + onClick={() => navTo(`/doctor/customer/index`, true)}/> +
submitSucceed(values)} + onFinishFailed={(errors) => submitFailed(errors)} + > + + + + + +
+ + {/* 底部浮动按钮 */} + formRef.current?.submit()}/> + + ); +}; + +export default AddMessage; diff --git a/src/doctor/orders/index.config.ts b/src/doctor/orders/index.config.ts index 3bb5694..ba927a9 100644 --- a/src/doctor/orders/index.config.ts +++ b/src/doctor/orders/index.config.ts @@ -1,3 +1,3 @@ export default definePageConfig({ - navigationBarTitleText: '分销订单' + navigationBarTitleText: '处方管理' }) diff --git a/src/doctor/orders/index.tsx b/src/doctor/orders/index.tsx index 1cc459b..196e715 100644 --- a/src/doctor/orders/index.tsx +++ b/src/doctor/orders/index.tsx @@ -1,161 +1,63 @@ -import React, { useState, useEffect, useCallback } from 'react' -import { View, Text } from '@tarojs/components' -import { Empty, Tabs, Tag, PullToRefresh, Loading } from '@nutui/nutui-react-taro' +import React, {useState, useEffect, useCallback} from 'react' +import {View, Text, ScrollView} from '@tarojs/components' +import {Empty, Tag, PullToRefresh, Loading} from '@nutui/nutui-react-taro' import Taro from '@tarojs/taro' -import { pageShopDealerOrder } from '@/api/shop/shopDealerOrder' -import { useDealerUser } from '@/hooks/useDealerUser' -import type { ShopDealerOrder } from '@/api/shop/shopDealerOrder/model' +import {pageShopDealerOrder} from '@/api/shop/shopDealerOrder' +import {useDealerUser} from '@/hooks/useDealerUser' +import type {ShopDealerOrder} from '@/api/shop/shopDealerOrder/model' interface OrderWithDetails extends ShopDealerOrder { orderNo?: string customerName?: string - totalCommission?: string - // 当前用户在此订单中的层级和佣金 - userLevel?: 1 | 2 | 3 userCommission?: string } const DealerOrders: React.FC = () => { - const [activeTab, setActiveTab] = useState('0') const [loading, setLoading] = useState(false) + const [refreshing, setRefreshing] = useState(false) + const [loadingMore, setLoadingMore] = useState(false) const [orders, setOrders] = useState([]) - const [statistics, setStatistics] = useState({ - totalOrders: 0, - totalCommission: '0.00', - pendingCommission: '0.00', - // 分层统计 - level1: { orders: 0, commission: '0.00' }, - level2: { orders: 0, commission: '0.00' }, - level3: { orders: 0, commission: '0.00' } - }) + const [currentPage, setCurrentPage] = useState(1) + const [hasMore, setHasMore] = useState(true) - const { dealerUser } = useDealerUser() + const {dealerUser} = useDealerUser() - // 获取订单数据 - 查询当前用户作为各层级分销商的所有订单 - const fetchOrders = useCallback(async () => { + // 获取订单数据 + const fetchOrders = useCallback(async (page: number = 1, isRefresh: boolean = false) => { if (!dealerUser?.userId) return try { - setLoading(true) - - // 并行查询三个层级的订单 - const [level1Result, level2Result, level3Result] = await Promise.all([ - // 一级分销商订单 - pageShopDealerOrder({ - page: 1, - limit: 100, - firstUserId: dealerUser.userId - }), - // 二级分销商订单 - pageShopDealerOrder({ - page: 1, - limit: 100, - secondUserId: dealerUser.userId - }), - // 三级分销商订单 - pageShopDealerOrder({ - page: 1, - limit: 100, - thirdUserId: dealerUser.userId - }) - ]) - - const allOrders: OrderWithDetails[] = [] - const stats = { - totalOrders: 0, - totalCommission: '0.00', - pendingCommission: '0.00', - level1: { orders: 0, commission: '0.00' }, - level2: { orders: 0, commission: '0.00' }, - level3: { orders: 0, commission: '0.00' } + if (isRefresh) { + setRefreshing(true) + } else if (page === 1) { + setLoading(true) + } else { + setLoadingMore(true) } - // 处理一级分销订单 - if (level1Result?.list) { - const level1Orders = level1Result.list.map(order => ({ + const result = await pageShopDealerOrder({ + page, + limit: 10 + }) + + if (result?.list) { + const newOrders = result.list.map(order => ({ ...order, - orderNo: `DD${order.orderId}`, + orderNo: `${order.orderId}`, customerName: `用户${order.userId}`, - userLevel: 1 as const, - userCommission: order.firstMoney || '0.00', - totalCommission: ( - parseFloat(order.firstMoney || '0') + - parseFloat(order.secondMoney || '0') + - parseFloat(order.thirdMoney || '0') - ).toFixed(2) + userCommission: order.firstMoney || '0.00' })) - allOrders.push(...level1Orders) - stats.level1.orders = level1Orders.length - stats.level1.commission = level1Orders.reduce((sum, order) => - sum + parseFloat(order.userCommission || '0'), 0 - ).toFixed(2) + if (page === 1) { + setOrders(newOrders) + } else { + setOrders(prev => [...prev, ...newOrders]) + } + + setHasMore(newOrders.length === 10) + setCurrentPage(page) } - // 处理二级分销订单 - if (level2Result?.list) { - const level2Orders = level2Result.list.map(order => ({ - ...order, - orderNo: `DD${order.orderId}`, - customerName: `用户${order.userId}`, - userLevel: 2 as const, - userCommission: order.secondMoney || '0.00', - totalCommission: ( - parseFloat(order.firstMoney || '0') + - parseFloat(order.secondMoney || '0') + - parseFloat(order.thirdMoney || '0') - ).toFixed(2) - })) - - allOrders.push(...level2Orders) - stats.level2.orders = level2Orders.length - stats.level2.commission = level2Orders.reduce((sum, order) => - sum + parseFloat(order.userCommission || '0'), 0 - ).toFixed(2) - } - - // 处理三级分销订单 - if (level3Result?.list) { - const level3Orders = level3Result.list.map(order => ({ - ...order, - orderNo: `DD${order.orderId}`, - customerName: `用户${order.userId}`, - userLevel: 3 as const, - userCommission: order.thirdMoney || '0.00', - totalCommission: ( - parseFloat(order.firstMoney || '0') + - parseFloat(order.secondMoney || '0') + - parseFloat(order.thirdMoney || '0') - ).toFixed(2) - })) - - allOrders.push(...level3Orders) - stats.level3.orders = level3Orders.length - stats.level3.commission = level3Orders.reduce((sum, order) => - sum + parseFloat(order.userCommission || '0'), 0 - ).toFixed(2) - } - - // 去重(同一个订单可能在多个层级中出现) - const uniqueOrders = allOrders.filter((order, index, self) => - index === self.findIndex(o => o.orderId === order.orderId) - ) - - // 计算总统计 - stats.totalOrders = uniqueOrders.length - stats.totalCommission = ( - parseFloat(stats.level1.commission) + - parseFloat(stats.level2.commission) + - parseFloat(stats.level3.commission) - ).toFixed(2) - stats.pendingCommission = allOrders - .filter(order => order.isSettled === 0) - .reduce((sum, order) => sum + parseFloat(order.userCommission || '0'), 0) - .toFixed(2) - - setOrders(uniqueOrders) - setStatistics(stats) - } catch (error) { console.error('获取分销订单失败:', error) Taro.showToast({ @@ -164,18 +66,27 @@ const DealerOrders: React.FC = () => { }) } finally { setLoading(false) + setRefreshing(false) + setLoadingMore(false) } }, [dealerUser?.userId]) - // 刷新数据 + // 下拉刷新 const handleRefresh = async () => { - await fetchOrders() + await fetchOrders(1, true) + } + + // 加载更多 + const handleLoadMore = async () => { + if (!loadingMore && hasMore) { + await fetchOrders(currentPage + 1) + } } // 初始化加载数据 useEffect(() => { if (dealerUser?.userId) { - fetchOrders().then() + fetchOrders(1) } }, [fetchOrders]) @@ -193,198 +104,80 @@ const DealerOrders: React.FC = () => { const renderOrderItem = (order: OrderWithDetails) => ( - - - - 订单号:{order.orderNo} - - - 客户:{order.customerName} - - {/* 显示用户在此订单中的层级 */} - - {order.userLevel === 1 && '一级分销'} - {order.userLevel === 2 && '二级分销'} - {order.userLevel === 3 && '三级分销'} - - + + + 订单号:{order.orderNo} + {getStatusText(order.isSettled, order.isInvalid)} + + + 订单金额:¥{order.orderPrice || '0.00'} + + + 我的佣金:¥{order.userCommission} + + + - - - 订单金额:¥{order.orderPrice || '0.00'} - - - 我的佣金:¥{order.userCommission} - - - 总佣金:¥{order.totalCommission} - - - + + 客户:{order.customerName} + + {order.createTime} ) - // 根据状态和层级过滤订单 - const getFilteredOrders = (filter: string) => { - switch (filter) { - case '1': // 一级分销 - return orders.filter(order => order.userLevel === 1) - case '2': // 二级分销 - return orders.filter(order => order.userLevel === 2) - case '3': // 三级分销 - return orders.filter(order => order.userLevel === 3) - case '4': // 待结算 - return orders.filter(order => order.isSettled === 0 && order.isInvalid === 0) - case '5': // 已结算 - return orders.filter(order => order.isSettled === 1) - case '6': // 已失效 - return orders.filter(order => order.isInvalid === 1) - default: // 全部 - return orders - } - } - - if (!dealerUser) { - return ( - - - 加载中... - - ) - } - return ( - - {/* 统计卡片 */} - - {/* 总体统计 */} - - - {statistics.totalOrders} - 总订单 - - - ¥{statistics.totalCommission} - 总佣金 - - - ¥{statistics.pendingCommission} - 待结算 - - - - {/* 分层统计 */} - - 分层统计 - - - {statistics.level1.orders} - 一级订单 - ¥{statistics.level1.commission} - - - {statistics.level2.orders} - 二级订单 - ¥{statistics.level2.commission} - - - {statistics.level3.orders} - 三级订单 - ¥{statistics.level3.commission} - - - - - - {/* 订单列表 */} - setActiveTab}> - - - - {loading ? ( - - - 加载中... - - ) : getFilteredOrders('0').length > 0 ? ( - getFilteredOrders('0').map(renderOrderItem) - ) : ( - - )} - - - - - + + + - {getFilteredOrders('1').length > 0 ? ( - getFilteredOrders('1').map(renderOrderItem) + {loading && orders.length === 0 ? ( + + + 加载中... + + ) : orders.length > 0 ? ( + <> + {orders.map(renderOrderItem)} + {loadingMore && ( + + + 加载更多... + + )} + {!hasMore && orders.length > 0 && ( + + 没有更多数据了 + + )} + ) : ( - + )} - - - - - {getFilteredOrders('2').length > 0 ? ( - getFilteredOrders('2').map(renderOrderItem) - ) : ( - - )} - - - - - - {getFilteredOrders('3').length > 0 ? ( - getFilteredOrders('3').map(renderOrderItem) - ) : ( - - )} - - - - - - {getFilteredOrders('4').length > 0 ? ( - getFilteredOrders('4').map(renderOrderItem) - ) : ( - - )} - - - - - - {getFilteredOrders('5').length > 0 ? ( - getFilteredOrders('5').map(renderOrderItem) - ) : ( - - )} - - - - - - {getFilteredOrders('6').length > 0 ? ( - getFilteredOrders('6').map(renderOrderItem) - ) : ( - - )} - - - + + ) } diff --git a/src/doctor/qrcode/index.tsx b/src/doctor/qrcode/index.tsx index c8d376a..268580a 100644 --- a/src/doctor/qrcode/index.tsx +++ b/src/doctor/qrcode/index.tsx @@ -1,7 +1,7 @@ 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 {Download, QrCode} from '@nutui/icons-react-taro' import Taro from '@tarojs/taro' import {useDealerUser} from '@/hooks/useDealerUser' import {generateInviteCode} from '@/api/invite' @@ -115,52 +115,52 @@ const DealerQrcode: React.FC = () => { } // 复制邀请信息 - 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 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', 'shareTimeline'] - }) - } + // const shareMiniProgramCode = () => { + // if (!dealerUser?.userId) { + // Taro.showToast({ + // title: '用户信息未加载', + // icon: 'error' + // }) + // return + // } + // + // // 小程序分享 + // Taro.showShareMenu({ + // withShareTicket: true, + // showShareItems: ['shareAppMessage'] + // }) + // } if (!dealerUser) { return ( @@ -263,29 +263,29 @@ const DealerQrcode: React.FC = () => { 保存小程序码到相册 - - - - - - + {/**/} + {/* }*/} + {/* onClick={copyInviteInfo}*/} + {/* disabled={!dealerUser?.userId || loading}*/} + {/* >*/} + {/* 复制邀请信息*/} + {/* */} + {/**/} + {/**/} + {/* }*/} + {/* onClick={shareMiniProgramCode}*/} + {/* disabled={!dealerUser?.userId || loading}*/} + {/* >*/} + {/* 分享给好友*/} + {/* */} + {/**/}
{/* 推广说明 */} diff --git a/src/doctor/team/index.config.ts b/src/doctor/team/index.config.ts index 926f186..539f8b2 100644 --- a/src/doctor/team/index.config.ts +++ b/src/doctor/team/index.config.ts @@ -1,3 +1,3 @@ export default definePageConfig({ - navigationBarTitleText: '我的团队' + navigationBarTitleText: '患者管理' }) diff --git a/src/doctor/team/index.tsx b/src/doctor/team/index.tsx index 923aa17..36f6b14 100644 --- a/src/doctor/team/index.tsx +++ b/src/doctor/team/index.tsx @@ -1,56 +1,151 @@ -import React, { useState, useEffect, useCallback } from 'react' -import { View, Text } from '@tarojs/components' -import { Empty, Tabs, Avatar, Tag, Progress, Loading, PullToRefresh } from '@nutui/nutui-react-taro' -import { User, Star, StarFill } from '@nutui/icons-react-taro' +import React, {useState, useEffect, useCallback} from 'react' +import {View, Text} from '@tarojs/components' +import {Phone, Edit, Message} from '@nutui/icons-react-taro' +import {Space, Empty, Avatar, Button} from '@nutui/nutui-react-taro' import Taro from '@tarojs/taro' -import { useDealerUser } from '@/hooks/useDealerUser' -import { listShopDealerReferee } from '@/api/shop/shopDealerReferee' -import { pageShopDealerOrder } from '@/api/shop/shopDealerOrder' -import type { ShopDealerReferee } from '@/api/shop/shopDealerReferee/model' +import {useDealerUser} from '@/hooks/useDealerUser' +import {listShopDealerReferee} from '@/api/shop/shopDealerReferee' +import {pageShopDealerOrder} from '@/api/shop/shopDealerOrder' +import type {ShopDealerReferee} from '@/api/shop/shopDealerReferee/model' +import FixedButton from "@/components/FixedButton"; +import navTo from "@/utils/common"; +import {updateUser} from "@/api/system/user"; interface TeamMemberWithStats extends ShopDealerReferee { name?: string avatar?: string + nickname?: string; + alias?: string; + phone?: string; orderCount?: number commission?: string status?: 'active' | 'inactive' subMembers?: number joinTime?: string + dealerAvatar?: string; + dealerName?: string; + dealerPhone?: string; +} + +// 层级信息接口 +interface LevelInfo { + dealerId: number + dealerName?: string + level: number } const DealerTeam: React.FC = () => { - const [activeTab, setActiveTab] = useState('0') - const [loading, setLoading] = useState(false) - const [refreshing, setRefreshing] = useState(false) const [teamMembers, setTeamMembers] = useState([]) - const [teamStats, setTeamStats] = useState({ - total: 0, - firstLevel: 0, - secondLevel: 0, - thirdLevel: 0, - monthlyCommission: '0.00' - }) + const {dealerUser} = useDealerUser() + const [dealerId, setDealerId] = useState() + // 层级栈,用于支持返回上一层 + const [levelStack, setLevelStack] = useState([]) + const [loading, setLoading] = useState(false) + // 当前查看的用户名称 + const [currentDealerName, setCurrentDealerName] = useState('') - const { dealerUser } = useDealerUser() + // 异步加载成员统计数据 + const loadMemberStats = async (members: TeamMemberWithStats[]) => { + // 分批处理,避免过多并发请求 + const batchSize = 3 + for (let i = 0; i < members.length; i += batchSize) { + const batch = members.slice(i, i + batchSize) + + const batchStats = await Promise.all( + batch.map(async (member) => { + try { + // 并行获取订单统计和下级成员数量 + const [orderResult, subMembersResult] = await Promise.all([ + pageShopDealerOrder({ + page: 1, + userId: member.userId + }), + listShopDealerReferee({ + dealerId: member.userId, + deleted: 0 + }) + ]) + + let orderCount = 0 + let commission = '0.00' + let status: 'active' | 'inactive' = 'inactive' + + if (orderResult?.list) { + const orders = orderResult.list + orderCount = orders.length + commission = orders.reduce((sum, order) => { + const levelCommission = member.level === 1 ? order.firstMoney : + member.level === 2 ? order.secondMoney : + order.thirdMoney + return sum + parseFloat(levelCommission || '0') + }, 0).toFixed(2) + + // 判断活跃状态(30天内有订单为活跃) + const thirtyDaysAgo = new Date() + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30) + const hasRecentOrder = orders.some(order => + new Date(order.createTime || '') > thirtyDaysAgo + ) + status = hasRecentOrder ? 'active' : 'inactive' + } + + return { + ...member, + orderCount, + commission, + status, + subMembers: subMembersResult?.length || 0 + } + } catch (error) { + console.error(`获取成员${member.userId}数据失败:`, error) + return { + ...member, + orderCount: 0, + commission: '0.00', + status: 'inactive' as const, + subMembers: 0 + } + } + }) + ) + + // 更新这一批成员的数据 + setTeamMembers(prevMembers => { + const updatedMembers = [...prevMembers] + batchStats.forEach(updatedMember => { + const index = updatedMembers.findIndex(m => m.userId === updatedMember.userId) + if (index !== -1) { + updatedMembers[index] = updatedMember + } + }) + return updatedMembers + }) + + // 添加小延迟,避免请求过于密集 + if (i + batchSize < members.length) { + await new Promise(resolve => setTimeout(resolve, 100)) + } + } + } // 获取团队数据 const fetchTeamData = useCallback(async () => { - if (!dealerUser?.userId) return + if (!dealerUser?.userId && !dealerId) return try { setLoading(true) - + console.log(dealerId, 'dealerId>>>>>>>>>') // 获取团队成员关系 const refereeResult = await listShopDealerReferee({ - dealerId: dealerUser.userId + dealerId: dealerId ? dealerId : dealerUser?.userId }) if (refereeResult) { + console.log('团队成员原始数据:', refereeResult) // 处理团队成员数据 const processedMembers: TeamMemberWithStats[] = refereeResult.map(member => ({ ...member, - name: `用户${member.userId}`, - avatar: '', + name: `${member.userId}`, orderCount: 0, commission: '0.00', status: 'active' as const, @@ -58,62 +153,13 @@ const DealerTeam: React.FC = () => { joinTime: member.createTime })) - // 并行获取每个成员的订单统计 - const memberStats = await Promise.all( - processedMembers.map(async (member) => { - try { - const orderResult = await pageShopDealerOrder({ - page: 1, - limit: 100, - userId: member.userId - }) + // 先显示基础数据,然后异步加载详细统计 + setTeamMembers(processedMembers) + setLoading(false) - if (orderResult?.list) { - const orders = orderResult.list - const orderCount = orders.length - const commission = orders.reduce((sum, order) => { - const levelCommission = member.level === 1 ? order.firstMoney : - member.level === 2 ? order.secondMoney : - order.thirdMoney - return sum + parseFloat(levelCommission || '0') - }, 0).toFixed(2) + // 异步加载每个成员的详细统计数据 + loadMemberStats(processedMembers) - // 判断活跃状态(30天内有订单为活跃) - const thirtyDaysAgo = new Date() - thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30) - const hasRecentOrder = orders.some(order => - new Date(order.createTime || '') > thirtyDaysAgo - ) - - return { - ...member, - orderCount, - commission, - status: hasRecentOrder ? 'active' as const : 'inactive' as const - } - } - return member - } catch (error) { - console.error(`获取成员${member.userId}订单失败:`, error) - return member - } - }) - ) - - setTeamMembers(memberStats) - - // 计算统计数据 - const stats = { - total: memberStats.length, - firstLevel: memberStats.filter(m => m.level === 1).length, - secondLevel: memberStats.filter(m => m.level === 2).length, - thirdLevel: memberStats.filter(m => m.level === 3).length, - monthlyCommission: memberStats.reduce((sum, member) => - sum + parseFloat(member.commission || '0'), 0 - ).toFixed(2) - } - - setTeamStats(stats) } } catch (error) { console.error('获取团队数据失败:', error) @@ -124,244 +170,270 @@ const DealerTeam: React.FC = () => { } finally { setLoading(false) } - }, [dealerUser?.userId]) + }, [dealerUser?.userId, dealerId]) - // 刷新数据 - const handleRefresh = async () => { - setRefreshing(true) - await fetchTeamData() - setRefreshing(false) + // 查看下级成员 + const getNextUser = (item: TeamMemberWithStats) => { + // 检查层级限制:最多只能查看2层(levelStack.length >= 1 表示已经是第2层了) + if (levelStack.length >= 1) { + return + } + + // 如果没有下级成员,不允许点击 + if (!item.subMembers || item.subMembers === 0) { + return + } + + console.log('点击用户:', item.userId, item.name) + + // 将当前层级信息推入栈中 + const currentLevel: LevelInfo = { + dealerId: dealerId || dealerUser?.userId || 0, + dealerName: currentDealerName || (dealerId ? '上级' : dealerUser?.realName || '我'), + level: levelStack.length + } + setLevelStack(prev => [...prev, currentLevel]) + + // 切换到下级 + setDealerId(item.userId) + setCurrentDealerName(item.nickname || item.dealerName || `用户${item.userId}`) } - // 初始化加载数据 + // 返回上一层 + const goBack = () => { + if (levelStack.length === 0) { + // 如果栈为空,返回首页或上一页 + Taro.navigateBack() + return + } + + // 从栈中弹出上一层信息 + const prevLevel = levelStack[levelStack.length - 1] + setLevelStack(prev => prev.slice(0, -1)) + + if (prevLevel.dealerId === (dealerUser?.userId || 0)) { + // 返回到根层级 + setDealerId(undefined) + setCurrentDealerName('') + } else { + setDealerId(prevLevel.dealerId) + setCurrentDealerName(prevLevel.dealerName || '') + } + } + + // 一键拨打 + const makePhoneCall = (phone: string) => { + Taro.makePhoneCall({ + phoneNumber: phone, + fail: () => { + Taro.showToast({ + title: '拨打取消', + icon: 'error' + }); + } + }); + }; + + // 别名备注 + const editAlias = (item: any, index: number) => { + Taro.showModal({ + title: '备注', + // @ts-ignore + editable: true, + placeholderText: '真实姓名', + content: item.alias || '', + success: async (res: any) => { + if (res.confirm && res.content !== undefined) { + try { + // 更新跟进情况 + await updateUser({ + userId: item.userId, + alias: res.content.trim() + }); + teamMembers[index].alias = res.content.trim() + setTeamMembers(teamMembers) + } catch (error) { + console.error('备注失败:', error); + Taro.showToast({ + title: '备注失败,请重试', + icon: 'error' + }); + } + } + } + }); + }; + + // 发送消息 + const sendMessage = (item: TeamMemberWithStats) => { + return navTo(`/user/chat/message/add?id=${item.userId}`, true) + } + + // 监听数据变化,获取团队数据 useEffect(() => { - if (dealerUser?.userId) { + if (dealerUser?.userId || dealerId) { fetchTeamData().then() } }, [fetchTeamData]) - const getLevelColor = (level: number) => { - switch (level) { - case 1: return '#f59e0b' - case 2: return '#8b5cf6' - case 3: return '#ec4899' - default: return '#6b7280' + // 初始化当前用户名称 + useEffect(() => { + if (!dealerId && dealerUser?.realName && !currentDealerName) { + setCurrentDealerName(dealerUser.realName) } - } + }, [dealerUser, dealerId, currentDealerName]) - const getLevelIcon = (level: number) => { - switch (level) { - case 1: return - case 2: return - case 3: return - default: return - } - } + const renderMemberItem = (member: TeamMemberWithStats, index: number) => { + // 判断是否可以点击:有下级成员且未达到层级限制 + const canClick = member.subMembers && member.subMembers > 0 && levelStack.length < 1 + // 判断是否显示手机号:只有本级(levelStack.length === 0)才显示 + const showPhone = levelStack.length === 0 + // 判断数据是否还在加载中(初始值都是0或'0.00') + const isStatsLoading = member.orderCount === 0 && member.commission === '0.00' && member.subMembers === 0 - const renderMemberItem = (member: TeamMemberWithStats) => ( - - - } - className="mr-3" - /> - - - - {member.name} - - {getLevelIcon(Number(member.level))} - - {member.level}级 - - - - 加入时间:{member.joinTime} - - - - - {member.status === 'active' ? '活跃' : '沉默'} - - - - - - - - {member.orderCount} - - 订单数 - - - - ¥{member.commission} - - 贡献佣金 - - - - {member.subMembers} - - 团队成员 - - - - ) - - const renderOverview = () => ( - - {/* 团队统计卡片 */} - - {/* 装饰背景 - 小程序兼容版本 */} - - - - - 团队总览 - - - {teamStats.total} - 团队总人数 - - - ¥{teamStats.monthlyCommission} - 本月团队佣金 - - - - - - {/* 层级分布 */} - - 层级分布 - - - - - 一级成员 - - - {teamStats.firstLevel} - - - - - - - - 二级成员 - - - {teamStats.secondLevel} - - - - - - - - 三级成员 - - - {teamStats.thirdLevel} - - - - - - - {/* 最新成员 */} - - 最新成员 - {teamMembers.slice(0, 3).map(renderMemberItem)} - - - ) - - const renderMemberList = (level?: number) => ( - - - {loading ? ( - - - 加载中... - - ) : teamMembers - .filter(member => !level || member.level === level) - .length > 0 ? ( - teamMembers - .filter(member => !level || member.level === level) - .map(renderMemberItem) - ) : ( - - )} - - - ) - - if (!dealerUser) { return ( - - - 加载中... + getNextUser(member)} + > + + + + + + + {member.alias ? {member.alias} : + {member.nickname}} + {/*别名备注*/} + { + e.stopPropagation() + editAlias(member, index) + }}/> + {/*发送消息*/} + { + e.stopPropagation() + sendMessage(member) + }}/> + + + {/* 显示手机号(仅本级可见) */} + {showPhone && member.phone && ( + { + e.stopPropagation(); + makePhoneCall(member.phone || ''); + }}> + {member.phone} + + + )} + + + 加入时间:{member.joinTime} + + + + + + + 订单数 + + {isStatsLoading ? '-' : member.orderCount} + + + + 贡献佣金 + + {isStatsLoading ? '-' : `¥${member.commission}`} + + + + 团队成员 + + {isStatsLoading ? '-' : (member.subMembers || 0)} + + + ) } - return ( - - setActiveTab}> - - {renderOverview()} - - - - {renderMemberList(1)} - - - - {renderMemberList(2)} - - - - {renderMemberList(3)} - - + const renderOverview = () => ( + + + 我的团队成员 + 成员数:{teamMembers.length} + + {teamMembers.map(renderMemberItem)} ) + + // 渲染顶部导航栏 + const renderHeader = () => { + if (levelStack.length === 0) return null + + return ( + + + + + {currentDealerName}的团队成员 + + + + + + ) + } + + if (!dealerUser) { + return ( + + navTo(`/doctor/apply/add`, true)}]} + /> + + ) + } + + return ( + <> + {renderHeader()} + + {loading ? ( + + 加载中... + + ) : teamMembers.length > 0 ? ( + renderOverview() + ) : ( + + + + )} + + navTo(`/doctor/qrcode/index`, true)}/> + + ) } -export default DealerTeam +export default DealerTeam; diff --git a/src/dealer/wechat/index.config.ts b/src/doctor/wechat/index.config.ts similarity index 100% rename from src/dealer/wechat/index.config.ts rename to src/doctor/wechat/index.config.ts diff --git a/src/dealer/wechat/index.scss b/src/doctor/wechat/index.scss similarity index 100% rename from src/dealer/wechat/index.scss rename to src/doctor/wechat/index.scss diff --git a/src/dealer/wechat/index.tsx b/src/doctor/wechat/index.tsx similarity index 100% rename from src/dealer/wechat/index.tsx rename to src/doctor/wechat/index.tsx diff --git a/src/dealer/withdraw/debug.tsx b/src/doctor/withdraw/debug.tsx similarity index 100% rename from src/dealer/withdraw/debug.tsx rename to src/doctor/withdraw/debug.tsx diff --git a/src/doctor/withdraw/index.tsx b/src/doctor/withdraw/index.tsx index 9ef56f5..18f257b 100644 --- a/src/doctor/withdraw/index.tsx +++ b/src/doctor/withdraw/index.tsx @@ -1,49 +1,67 @@ -import React, { useState, useRef, useEffect, useCallback } from 'react' -import { View, Text } from '@tarojs/components' +import React, {useState, useEffect, useCallback} from 'react' +import {View, Text} from '@tarojs/components' import { Cell, + Space, Button, - Form, Input, CellGroup, - Radio, Tabs, Tag, Empty, + ActionSheet, Loading, PullToRefresh } from '@nutui/nutui-react-taro' -import { Wallet } from '@nutui/icons-react-taro' -import { businessGradients } from '@/styles/gradients' +import {Wallet, ArrowRight} from '@nutui/icons-react-taro' +import {businessGradients} from '@/styles/gradients' import Taro from '@tarojs/taro' -import { useDealerUser } from '@/hooks/useDealerUser' -import { pageShopDealerWithdraw, addShopDealerWithdraw } from '@/api/shop/shopDealerWithdraw' -import type { ShopDealerWithdraw } from '@/api/shop/shopDealerWithdraw/model' +import {useDealerUser} from '@/hooks/useDealerUser' +import {pageShopDealerWithdraw, addShopDealerWithdraw} 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"; +import {listCmsWebsiteField} from "@/api/cms/cmsWebsiteField"; interface WithdrawRecordWithDetails extends ShopDealerWithdraw { accountDisplay?: string } const DealerWithdraw: React.FC = () => { - const [activeTab, setActiveTab] = useState('0') - const [selectedAccount, setSelectedAccount] = useState('') + const [activeTab, setActiveTab] = useState('0') const [loading, setLoading] = useState(false) const [refreshing, setRefreshing] = useState(false) const [submitting, setSubmitting] = useState(false) + const [banks, setBanks] = useState([]) + const [bank, setBank] = useState() + const [isVisible, setIsVisible] = useState(false) const [availableAmount, setAvailableAmount] = useState('0.00') const [withdrawRecords, setWithdrawRecords] = useState([]) - const formRef = useRef(null) + const [withdrawAmount, setWithdrawAmount] = useState('') + const [withdrawValue, setWithdrawValue] = useState('') - const { dealerUser } = useDealerUser() + const {dealerUser} = useDealerUser() + + // Tab 切换处理函数 + const handleTabChange = (value: string | number) => { + console.log('Tab切换到:', value) + setActiveTab(value) + + // 如果切换到提现记录页面,刷新数据 + if (String(value) === '1') { + fetchWithdrawRecords().then() + } + } // 获取可提现余额 const fetchBalance = useCallback(async () => { + console.log(dealerUser, 'dealerUser...') try { - setAvailableAmount(dealerUser?.money || '0.00') + setAvailableAmount(String(dealerUser?.money || '0.00')) } catch (error) { console.error('获取余额失败:', error) } - }, []) + }, [dealerUser]) // 获取提现记录 const fetchWithdrawRecords = useCallback(async () => { @@ -75,6 +93,21 @@ const DealerWithdraw: React.FC = () => { } }, [dealerUser?.userId]) + 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' + })) + setBank(data[0]) + }) + } + // 格式化账户显示 const getAccountDisplay = (record: ShopDealerWithdraw) => { if (record.payType === 10) { @@ -94,35 +127,66 @@ const DealerWithdraw: React.FC = () => { setRefreshing(false) } + const handleSelect = (item: ShopDealerBank) => { + if(item.type === 'add'){ + return Taro.navigateTo({ + url: '/doctor/bank/index' + }) + } + setBank(item) + setIsVisible(false) + } + + function fetchCmsField() { + listCmsWebsiteField({ name: 'WithdrawValue'}).then(res => { + if(res && res.length > 0){ + const text = res[0].value; + setWithdrawValue(text || '') + } + }) + } + // 初始化加载数据 useEffect(() => { if (dealerUser?.userId) { fetchBalance().then() fetchWithdrawRecords().then() + fetchBanks() + fetchCmsField() } }, [fetchBalance, fetchWithdrawRecords]) const getStatusText = (status?: number) => { switch (status) { - case 40: return '已到账' - case 20: return '审核通过' - case 10: return '待审核' - case 30: return '已驳回' - default: return '未知' + 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' + case 40: + return 'success' + case 20: + return 'success' + case 10: + return 'warning' + case 30: + return 'danger' + default: + return 'default' } } - const handleSubmit = async (values: any) => { + const handleSubmit = async () => { if (!dealerUser?.userId) { Taro.showToast({ title: '用户信息获取失败', @@ -131,9 +195,26 @@ const DealerWithdraw: React.FC = () => { return } + if (!bank) { + Taro.showToast({ + title: '请选择提现银行卡', + icon: 'error' + }) + return + } + // 验证提现金额 - const amount = parseFloat(values.amount) - const available = parseFloat(availableAmount.replace(',', '')) + const amount = parseFloat(withdrawAmount) + const availableStr = String(availableAmount || '0') + const available = parseFloat(availableStr.replace(/,/g, '')) + + if (isNaN(amount) || amount <= 0) { + Taro.showToast({ + title: '请输入有效的提现金额', + icon: 'error' + }) + return + } if (amount < 100) { Taro.showToast({ @@ -151,26 +232,27 @@ const DealerWithdraw: React.FC = () => { return } + // 验证银行卡信息 + if (!bank.bankCard || !bank.bankAccount || !bank.bankName) { + Taro.showToast({ + title: '银行卡信息不完整', + icon: 'error' + }) + return + } + try { setSubmitting(true) const withdrawData: ShopDealerWithdraw = { userId: dealerUser.userId, - money: values.amount, - payType: values.accountType === 'wechat' ? 10 : - values.accountType === 'alipay' ? 20 : 30, + money: withdrawAmount, + payType: 30, // 银行卡提现 applyStatus: 10, // 待审核 - platform: 'MiniProgram' - } - - // 根据提现方式设置账户信息 - if (values.accountType === 'alipay') { - withdrawData.alipayAccount = values.account - withdrawData.alipayName = values.accountName - } else if (values.accountType === 'bank') { - withdrawData.bankCard = values.account - withdrawData.bankAccount = values.accountName - withdrawData.bankName = values.bankName || '银行卡' + platform: 'MiniProgram', + bankCard: bank.bankCard, + bankAccount: bank.bankAccount, + bankName: bank.bankName } await addShopDealerWithdraw(withdrawData) @@ -181,8 +263,7 @@ const DealerWithdraw: React.FC = () => { }) // 重置表单 - formRef.current?.resetFields() - setSelectedAccount('') + setWithdrawAmount('') // 刷新数据 await handleRefresh() @@ -201,18 +282,26 @@ const DealerWithdraw: React.FC = () => { } } - const quickAmounts = ['100', '300', '500', '1000'] - - const setQuickAmount = (amount: string) => { - formRef.current?.setFieldsValue({ amount }) + // 格式化金额 + const formatMoney = (money?: string) => { + if (!money) return '0.00' + return parseFloat(money).toFixed(2) } - const setAllAmount = () => { - formRef.current?.setFieldsValue({ amount: availableAmount.replace(',', '') }) + // 计算预计到账金额 + const calculateExpectedAmount = (amount: string) => { + if (!amount || isNaN(parseFloat(amount))) return '0.00' + const withdrawAmount = parseFloat(amount) + // 提现费率 16% + 3元 + const feeRate = 0.16 + const fixedFee = 3 + const totalFee = withdrawAmount * feeRate + fixedFee + const expectedAmount = withdrawAmount - totalFee + return Math.max(0, expectedAmount).toFixed(2) } const renderWithdrawForm = () => ( - + {/* 余额卡片 */} { }}> - + + {formatMoney(dealerUser?.money)} 可提现余额 - ¥{availableAmount} - + - 最低提现金额:¥100 | 手续费:免费 + 最低提现金额:¥100 -
- - + + + setWithdrawAmount(value)} + style={{ + padding: '0 10px', + fontSize: '20px' + }} /> - - - {/* 快捷金额 */} - - 快捷金额 - - {quickAmounts.map(amount => ( - - ))} - - + + } + /> + setIsVisible(true)} extra={ + + {bank ? {bank.bankName} : 请选择} + + + }/> + + ¥{calculateExpectedAmount(withdrawAmount)} + + }/> + 说明:{withdrawValue}}/> + - - setSelectedAccount}> - - - 微信钱包 - - - 支付宝 - - - 银行卡 - - - - - - {selectedAccount === 'alipay' && ( - <> - - - - - - - - )} - - {selectedAccount === 'bank' && ( - <> - - - - - - - - - - - )} - - {selectedAccount === 'wechat' && ( - - - 微信钱包提现将直接转入您的微信零钱 - - - )} - - - - - -
+ + +
) - const renderWithdrawRecords = () => ( - - - {loading ? ( - - - 加载中... - - ) : withdrawRecords.length > 0 ? ( - withdrawRecords.map(record => ( - - - - - 提现金额:¥{record.money} - - - 提现账户:{record.accountDisplay} - - - - {getStatusText(record.applyStatus)} - - + const renderWithdrawRecords = () => { + console.log('渲染提现记录:', {loading, recordsCount: withdrawRecords.length, dealerUserId: dealerUser?.userId}) - - 申请时间:{record.createTime} - {record.auditTime && ( - - 审核时间:{new Date(record.auditTime).toLocaleString()} - - )} - {record.rejectReason && ( - - 驳回原因:{record.rejectReason} - - )} - - - )) - ) : ( - - )} - - - ) - - if (!dealerUser) { return ( - - - 加载中... - + + + {loading ? ( + + + 加载中... + + ) : withdrawRecords.length > 0 ? ( + withdrawRecords.map(record => ( + + + + + 提现金额:¥{record.money} + + + 提现账户:{record.accountDisplay} + + + + {getStatusText(record.applyStatus)} + + + + + 申请时间:{record.createTime} + {record.auditTime && ( + + 审核时间:{new Date(record.auditTime).toLocaleString()} + + )} + {record.rejectReason && ( + + 驳回原因:{record.rejectReason} + + )} + + + )) + ) : ( + + )} + + ) } return ( - setActiveTab}> + {renderWithdrawForm()} @@ -417,6 +448,12 @@ const DealerWithdraw: React.FC = () => { {renderWithdrawRecords()} + setIsVisible(false)} + /> ) } diff --git a/src/hooks/useUser.ts b/src/hooks/useUser.ts index 479a0f3..1a662e0 100644 --- a/src/hooks/useUser.ts +++ b/src/hooks/useUser.ts @@ -50,7 +50,7 @@ export const useUser = () => { const inviteParams = getStoredInviteParams() if (currentPage?.route !== 'dealer/apply/add' && inviteParams?.inviter) { return Taro.navigateTo({ - url: '/dealer/apply/add' + url: '/doctor/apply/add' }); } }); diff --git a/src/pages/cart/cart.tsx b/src/pages/cart/cart.tsx index e415a03..0feed41 100644 --- a/src/pages/cart/cart.tsx +++ b/src/pages/cart/cart.tsx @@ -41,7 +41,7 @@ function Cart() { useShareAppMessage(() => { return { - title: '购物车 - 时里院子市集', + title: '购物车 - 通源堂健康生态平台', success: function () { console.log('分享成功'); }, diff --git a/src/pages/cms/category/index.tsx b/src/pages/cms/category/index.tsx index 6886cc3..17a632b 100644 --- a/src/pages/cms/category/index.tsx +++ b/src/pages/cms/category/index.tsx @@ -44,7 +44,7 @@ function Category() { useShareAppMessage(() => { return { - title: `${nav?.categoryName}_时里院子市集`, + title: `${nav?.categoryName}_通源堂健康生态平台`, path: `/shop/category/index?id=${categoryId}`, success: function () { console.log('分享成功'); diff --git a/src/pages/user/components/IsDealer.tsx b/src/pages/user/components/IsDealer.tsx index 3d4ef65..32069ba 100644 --- a/src/pages/user/components/IsDealer.tsx +++ b/src/pages/user/components/IsDealer.tsx @@ -5,8 +5,10 @@ import {ArrowRight, Reward, Setting} from '@nutui/icons-react-taro' import {useUser} from '@/hooks/useUser' import {useEffect} from "react"; import {useDealerUser} from "@/hooks/useDealerUser"; +import {useThemeStyles} from "@/hooks/useTheme"; const IsDealer = () => { + const themeStyles = useThemeStyles(); const {isSuperAdmin} = useUser(); const {dealerUser} = useDealerUser() @@ -23,9 +25,7 @@ const IsDealer = () => { @@ -49,19 +49,17 @@ const IsDealer = () => { 分销中心 + className={'pl-3 text-orange-100 font-medium'}>VIP申请 {/*门店核销*/} } extra={} - onClick={() => navTo('/dealer/index', true)} + onClick={() => navTo('/doctor/index', true)} /> @@ -76,9 +74,7 @@ const IsDealer = () => { @@ -87,7 +83,7 @@ const IsDealer = () => { } extra={} - onClick={() => navTo('/dealer/apply/add', true)} + onClick={() => navTo('/doctor/apply/add', true)} />
diff --git a/src/pages/user/components/UserCard.tsx b/src/pages/user/components/UserCard.tsx index f0f0268..0235e65 100644 --- a/src/pages/user/components/UserCard.tsx +++ b/src/pages/user/components/UserCard.tsx @@ -1,5 +1,5 @@ -import {Avatar, Tag, Space, Button} from '@nutui/nutui-react-taro' -import {View, Text, Image} from '@tarojs/components' +import {Avatar, Tag, Space} from '@nutui/nutui-react-taro' +import {View, Text} from '@tarojs/components' import {getUserInfo, getWxOpenId} from '@/api/layout'; import Taro from '@tarojs/taro'; import {useEffect, useState, forwardRef, useImperativeHandle} from "react"; @@ -10,6 +10,7 @@ import {useUser} from "@/hooks/useUser"; import {useUserData} from "@/hooks/useUserData"; import {getStoredInviteParams} from "@/utils/invite"; import UnifiedQRButton from "@/components/UnifiedQRButton"; +import {useThemeStyles} from "@/hooks/useTheme"; const UserCard = forwardRef((_, ref) => { const {data, refresh} = useUserData() @@ -17,6 +18,8 @@ const UserCard = forwardRef((_, ref) => { const [IsLogin, setIsLogin] = useState(false) const [userInfo, setUserInfo] = useState() + const themeStyles = useThemeStyles(); + // 下拉刷新 const handleRefresh = async () => { await refresh() @@ -95,7 +98,6 @@ const UserCard = forwardRef((_, ref) => { }); }; - const openSetting = () => { // Taro.openSetting:调起客户端小程序设置界面,返回用户设置的操作结果。设置界面只会出现小程序已经向用户请求过的权限。 Taro.openSetting({ @@ -118,6 +120,11 @@ const UserCard = forwardRef((_, ref) => { const handleGetPhoneNumber = ({detail}: { detail: { code?: string, encryptedData?: string, iv?: string } }) => { const {code, encryptedData, iv} = detail + // 判断用户是否已登录 + if(IsLogin){ + return navTo(`/user/profile/profile`) + } + // 获取存储的邀请参数 const inviteParams = getStoredInviteParams() const refereeId = inviteParams?.inviter ? parseInt(inviteParams.inviter) : 0 @@ -165,35 +172,19 @@ const UserCard = forwardRef((_, ref) => { } return ( - - + {/* 使用相对定位容器,让个人资料图片可以绝对定位在右上角 */} - - { - IsLogin ? ( - - ) : ( - - ) - } + + - {getDisplayName()} + {getDisplayName()} {IsLogin ? ( @@ -211,7 +202,7 @@ const UserCard = forwardRef((_, ref) => { }}> {/*统一扫码入口 - 支持登录和核销*/} { console.log('统一扫码成功:', result); @@ -230,47 +221,31 @@ const UserCard = forwardRef((_, ref) => { /> - - navTo('/user/wallet/wallet', true)}> - 余额 - {data?.balance || '0.00'} - - - 积分 - {data?.points || 0} - - navTo('/user/coupon/index', true)}> - 优惠券 - {data?.coupons || 0} - - navTo('/user/gift/index', true)}> - 礼品卡 - {data?.giftCards || 0} + + + navTo('/user/wallet/wallet', true)}> + 余额 + {data?.balance || '0.00'} + + + 积分 + {data?.points || 0} + + navTo('/user/coupon/index', true)}> + 优惠券 + {data?.coupons || 0} + + navTo('/user/gift/index', true)}> + 礼品卡 + {data?.giftCards || 0} + - - {/* 个人资料图片,定位在右上角 */} - navTo('/user/profile/profile', true)} - > - - - ) }) diff --git a/src/pages/user/components/UserGrid.tsx b/src/pages/user/components/UserGrid.tsx index 5c3e5da..dad57ca 100644 --- a/src/pages/user/components/UserGrid.tsx +++ b/src/pages/user/components/UserGrid.tsx @@ -87,7 +87,7 @@ const UserCell = () => {
- navTo('/dealer/team/index', true)}> + navTo('/doctor/team/index', true)}> @@ -95,7 +95,7 @@ const UserCell = () => { - {/* navTo('/dealer/qrcode/index', true)}>*/} + {/* navTo('/doctor/qrcode/index', true)}>*/} {/* */} {/* */} {/* */} diff --git a/src/pages/user/user.tsx b/src/pages/user/user.tsx index 603a63d..9d49ce2 100644 --- a/src/pages/user/user.tsx +++ b/src/pages/user/user.tsx @@ -33,7 +33,25 @@ function User() { onRefresh={handleRefresh} headHeight={60} > - + {/* 装饰性背景 */} + + {/* 装饰性背景元素 - 小程序兼容版本 */} + + + + diff --git a/src/shop/category/index.tsx b/src/shop/category/index.tsx index 4f81fd7..2f300a6 100644 --- a/src/shop/category/index.tsx +++ b/src/shop/category/index.tsx @@ -42,7 +42,7 @@ function Category() { useShareAppMessage(() => { return { - title: `${nav?.categoryName}_时里院子市集`, + title: `${nav?.categoryName}_通源堂健康生态平台`, path: `/shop/category/index?id=${categoryId}`, success: function () { console.log('分享成功'); diff --git a/src/user/chat/message/add.tsx b/src/user/chat/message/add.tsx index a455fd0..cadf847 100644 --- a/src/user/chat/message/add.tsx +++ b/src/user/chat/message/add.tsx @@ -110,7 +110,7 @@ const AddMessage = () => { ) : '选择发送对象'} extra={( )} - onClick={() => navTo(`/dealer/team/index`, true)}/> + onClick={() => navTo(`/doctor/team/index`, true)}/>
{ ) : '选择发送对象'} extra={( )} - onClick={() => navTo(`/dealer/team/index`, true)}/> + onClick={() => navTo(`/doctor/team/index`, true)}/> diff --git a/src/utils/customerStatus.ts b/src/utils/customerStatus.ts new file mode 100644 index 0000000..d77f63b --- /dev/null +++ b/src/utils/customerStatus.ts @@ -0,0 +1,103 @@ +/** + * 客户状态管理工具函数 + */ + +// 客户状态类型定义 +export type CustomerStatus = 'all' | 'pending' | 'signed' | 'cancelled'; + +// 客户状态配置 +export const CUSTOMER_STATUS_CONFIG = { + all: { + label: '全部', + color: '#666666', + tagType: 'default' as const + }, + pending: { + label: '跟进中', + color: '#ff8800', + tagType: 'warning' as const + }, + signed: { + label: '已签约', + color: '#52c41a', + tagType: 'success' as const + }, + cancelled: { + label: '已取消', + color: '#999999', + tagType: 'default' as const + } +}; + +/** + * 获取状态文本 + */ +export const getStatusText = (status: CustomerStatus): string => { + return CUSTOMER_STATUS_CONFIG[status]?.label || ''; +}; + +/** + * 获取状态标签类型 + */ +export const getStatusTagType = (status: CustomerStatus) => { + return CUSTOMER_STATUS_CONFIG[status]?.tagType || 'default'; +}; + +/** + * 获取状态颜色 + */ +export const getStatusColor = (status: CustomerStatus): string => { + return CUSTOMER_STATUS_CONFIG[status]?.color || '#666666'; +}; + +/** + * 获取所有状态选项 + */ +export const getStatusOptions = () => { + return Object.entries(CUSTOMER_STATUS_CONFIG).map(([value, config]) => ({ + value: value as CustomerStatus, + label: config.label + })); +}; + +/** + * 将数字状态映射为字符串状态 + */ +export const mapApplyStatusToCustomerStatus = (applyStatus: number): CustomerStatus => { + switch (applyStatus) { + case 10: + return 'pending'; // 跟进中 + case 20: + return 'signed'; // 已签约 + case 30: + return 'cancelled'; // 已取消 + default: + return 'pending'; // 默认为跟进中 + } +}; + +/** + * 将字符串状态映射为数字状态 + */ +export const mapCustomerStatusToApplyStatus = (customerStatus: CustomerStatus): number | undefined => { + switch (customerStatus) { + case 'pending': + return 10; // 跟进中 + case 'signed': + return 20; // 已签约 + case 'cancelled': + return 30; // 已取消 + case 'all': + return undefined; // 全部,不筛选 + default: + return undefined; + } +}; + +/** + * 临时函数:生成随机状态(实际项目中应该删除,从数据库获取真实状态) + */ +export const getRandomStatus = (): CustomerStatus => { + const statuses: CustomerStatus[] = ['pending', 'signed', 'cancelled']; + return statuses[Math.floor(Math.random() * statuses.length)]; +}; diff --git a/src/utils/dateUtils.ts b/src/utils/dateUtils.ts new file mode 100644 index 0000000..126e75f --- /dev/null +++ b/src/utils/dateUtils.ts @@ -0,0 +1,126 @@ +/** + * 日期格式化工具函数 + * 用于处理各种日期格式转换 + */ + +/** + * 格式化日期为数据库格式 YYYY-MM-DD HH:mm:ss + * @param dateStr 输入的日期字符串,支持多种格式 + * @returns 数据库格式的日期字符串 + */ +export const formatDateForDatabase = (dateStr: string): string => { + if (!dateStr) return '' + + let parts: string[] = [] + + // 处理不同的日期格式 + if (dateStr.includes('/')) { + // 处理 YYYY/MM/DD 或 YYYY/M/D 格式 + parts = dateStr.split('/') + } else if (dateStr.includes('-')) { + // 处理 YYYY-MM-DD 或 YYYY-M-D 格式 + parts = dateStr.split('-') + } else { + return dateStr + } + + if (parts.length !== 3) return dateStr + + const year = parts[0] + const month = parts[1].padStart(2, '0') + const day = parts[2].padStart(2, '0') + + return `${year}-${month}-${day} 00:00:00` +} + +/** + * 从数据库格式提取日期部分用于Calendar组件显示 + * @param dateTimeStr 数据库格式的日期时间字符串 + * @returns Calendar组件需要的格式 (YYYY-M-D) + */ +export const extractDateForCalendar = (dateTimeStr: string): string => { + if (!dateTimeStr) return '' + + // 处理不同的输入格式 + let dateStr = '' + if (dateTimeStr.includes(' ')) { + // 从 "YYYY-MM-DD HH:mm:ss" 格式中提取日期部分 + dateStr = dateTimeStr.split(' ')[0] + } else { + dateStr = dateTimeStr + } + + // 转换为Calendar组件需要的格式 (YYYY-M-D) + if (dateStr.includes('-')) { + const parts = dateStr.split('-') + if (parts.length === 3) { + const year = parts[0] + const month = parseInt(parts[1]).toString() // 去掉前导0 + const day = parseInt(parts[2]).toString() // 去掉前导0 + return `${year}-${month}-${day}` + } + } + + return dateStr +} + +/** + * 格式化日期为用户友好的显示格式 YYYY-MM-DD + * @param dateStr 输入的日期字符串 + * @returns 用户友好的日期格式 + */ +export const formatDateForDisplay = (dateStr: string): string => { + if (!dateStr) return '' + + // 如果是数据库格式,先提取日期部分 + let dateOnly = dateStr + if (dateStr.includes(' ')) { + dateOnly = dateStr.split(' ')[0] + } + + // 如果已经是标准格式,直接返回 + if (/^\d{4}-\d{2}-\d{2}$/.test(dateOnly)) { + return dateOnly + } + + // 处理其他格式 + let parts: string[] = [] + if (dateOnly.includes('/')) { + parts = dateOnly.split('/') + } else if (dateOnly.includes('-')) { + parts = dateOnly.split('-') + } else { + return dateStr + } + + if (parts.length !== 3) return dateStr + + const year = parts[0] + const month = parts[1].padStart(2, '0') + const day = parts[2].padStart(2, '0') + + return `${year}-${month}-${day}` +} + +/** + * 获取当前日期的字符串格式 + * @param format 'database' | 'display' | 'calendar' + * @returns 格式化的当前日期 + */ +export const getCurrentDate = (format: 'database' | 'display' | 'calendar' = 'display'): string => { + const now = new Date() + const year = now.getFullYear() + const month = now.getMonth() + 1 + const day = now.getDate() + + switch (format) { + case 'database': + return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')} 00:00:00` + case 'display': + return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}` + case 'calendar': + return `${year}-${month}-${day}` + default: + return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}` + } +}