forked from gxwebsoft/mp-10550
```
feat(shop): 新增聊天会话与消息模块API新增了聊天会话(ShopChatConversation)和聊天消息(ShopChatMessage)两个模块的完整API接口及数据模型,包括分页查询、列表查询、新增、修改、删除、批量删除及根据ID查询等功能。feat(system): 扩展用户模型并重构API调用方式 为用户模型添加推荐人ID字段(refereeId),并在用户相关API中引入SERVER_API_URL常量以统一管理接口前缀,优化调用结构。 feat(dealer): 优化经销商邀请注册流程将经销商申请页面调整为邀请注册模式,增强微信手机号获取、头像上传及昵称校验逻辑,完善邀请关系绑定机制,并更新页面标题提示信息。 ```
This commit is contained in:
101
src/api/shop/shopChatConversation/index.ts
Normal file
101
src/api/shop/shopChatConversation/index.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { ShopChatConversation, ShopChatConversationParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询聊天会话表
|
||||
*/
|
||||
export async function pageShopChatConversation(params: ShopChatConversationParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ShopChatConversation>>>(
|
||||
'/shop/shop-chat-conversation/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询聊天会话表列表
|
||||
*/
|
||||
export async function listShopChatConversation(params?: ShopChatConversationParam) {
|
||||
const res = await request.get<ApiResult<ShopChatConversation[]>>(
|
||||
'/shop/shop-chat-conversation',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加聊天会话表
|
||||
*/
|
||||
export async function addShopChatConversation(data: ShopChatConversation) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-chat-conversation',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改聊天会话表
|
||||
*/
|
||||
export async function updateShopChatConversation(data: ShopChatConversation) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-chat-conversation',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除聊天会话表
|
||||
*/
|
||||
export async function removeShopChatConversation(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-chat-conversation/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除聊天会话表
|
||||
*/
|
||||
export async function removeShopBatchChatConversation(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-chat-conversation/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询聊天会话表
|
||||
*/
|
||||
export async function getShopChatConversation(id: number) {
|
||||
const res = await request.get<ApiResult<ShopChatConversation>>(
|
||||
'/shop/shop-chat-conversation/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
37
src/api/shop/shopChatConversation/model/index.ts
Normal file
37
src/api/shop/shopChatConversation/model/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 聊天消息表
|
||||
*/
|
||||
export interface ShopChatConversation {
|
||||
// 自增ID
|
||||
id?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 好友ID
|
||||
friendId?: number;
|
||||
// 消息类型
|
||||
type?: number;
|
||||
// 消息内容
|
||||
content?: string;
|
||||
// 未读消息
|
||||
unRead?: number;
|
||||
// 状态, 0未读, 1已读
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天消息表搜索条件
|
||||
*/
|
||||
export interface ShopChatConversationParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
115
src/api/shop/shopChatMessage/index.ts
Normal file
115
src/api/shop/shopChatMessage/index.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { ShopChatMessage, ShopChatMessageParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询聊天消息表
|
||||
*/
|
||||
export async function pageShopChatMessage(params: ShopChatMessageParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ShopChatMessage>>>(
|
||||
'/shop/shop-chat-message/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询聊天消息表列表
|
||||
*/
|
||||
export async function listShopChatMessage(params?: ShopChatMessageParam) {
|
||||
const res = await request.get<ApiResult<ShopChatMessage[]>>(
|
||||
'/shop/shop-chat-message',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加聊天消息表
|
||||
*/
|
||||
export async function addShopChatMessage(data: ShopChatMessage) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-chat-message',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加聊天消息表
|
||||
*/
|
||||
export async function addShopBatchChatMessage(data: ShopChatMessage[]) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-chat-message/batch',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改聊天消息表
|
||||
*/
|
||||
export async function updateShopChatMessage(data: ShopChatMessage) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-chat-message',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除聊天消息表
|
||||
*/
|
||||
export async function removeShopChatMessage(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-chat-message/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除聊天消息表
|
||||
*/
|
||||
export async function removeShopBatchChatMessage(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-chat-message/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询聊天消息表
|
||||
*/
|
||||
export async function getShopChatMessage(id: number) {
|
||||
const res = await request.get<ApiResult<ShopChatMessage>>(
|
||||
'/shop/shop-chat-message/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
63
src/api/shop/shopChatMessage/model/index.ts
Normal file
63
src/api/shop/shopChatMessage/model/index.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 聊天消息表
|
||||
*/
|
||||
export interface ShopChatMessage {
|
||||
// 自增ID
|
||||
id?: number;
|
||||
// 发送人ID
|
||||
formUserId?: number;
|
||||
// 发送人名称
|
||||
formUserName?: string;
|
||||
// 发送人头像
|
||||
formUserAvatar?: string;
|
||||
// 发送人手机号
|
||||
formUserPhone?: string;
|
||||
// 发送人别名
|
||||
formUserAlias?: string;
|
||||
// 接收人ID
|
||||
toUserId?: number;
|
||||
// 接收人名称
|
||||
toUserName?: string;
|
||||
// 接收人头像
|
||||
toUserAvatar?: string;
|
||||
// 接收人手机号
|
||||
toUserPhone?: string;
|
||||
// 接收人别名
|
||||
toUserAlias?: string;
|
||||
// 消息类型
|
||||
type?: string;
|
||||
// 消息内容
|
||||
content?: string;
|
||||
// 屏蔽接收方
|
||||
sideTo?: number;
|
||||
// 屏蔽发送方
|
||||
sideFrom?: number;
|
||||
// 是否撤回
|
||||
withdraw?: number;
|
||||
// 文件信息
|
||||
fileInfo?: string;
|
||||
// 批量发送
|
||||
toUserIds?: any[];
|
||||
// 存在联系方式
|
||||
hasContact?: number;
|
||||
// 状态, 0未读, 1已读
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天消息表搜索条件
|
||||
*/
|
||||
export interface ShopChatMessageParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -27,4 +27,5 @@ export interface ShopDealerRefereeParam extends PageParam {
|
||||
id?: number;
|
||||
dealerId?: number;
|
||||
keywords?: string;
|
||||
deleted?: number;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import request from '@/utils/request';
|
||||
import type {ApiResult, PageResult} from '@/api/index';
|
||||
import type {ApiResult, PageResult} from '@/api';
|
||||
import type {User, UserParam} from './model';
|
||||
import {SERVER_API_URL} from "@/utils/server";
|
||||
|
||||
@@ -8,8 +8,8 @@ import {SERVER_API_URL} from "@/utils/server";
|
||||
*/
|
||||
export async function pageUsers(params: UserParam) {
|
||||
const res = await request.get<ApiResult<PageResult<User>>>(
|
||||
'/system/user/page',
|
||||
{params}
|
||||
SERVER_API_URL + '/system/user/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
@@ -22,10 +22,8 @@ export async function pageUsers(params: UserParam) {
|
||||
*/
|
||||
export async function listUsers(params?: UserParam) {
|
||||
const res = await request.get<ApiResult<User[]>>(
|
||||
'/system/user',
|
||||
{
|
||||
SERVER_API_URL + '/system/user',
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
@@ -38,7 +36,7 @@ export async function listUsers(params?: UserParam) {
|
||||
*/
|
||||
export async function getStaffs(params?: UserParam) {
|
||||
const res = await request.get<ApiResult<User[]>>(
|
||||
'/system/user',
|
||||
SERVER_API_URL + '/system/user',
|
||||
{
|
||||
params
|
||||
}
|
||||
@@ -54,7 +52,7 @@ export async function getStaffs(params?: UserParam) {
|
||||
*/
|
||||
export async function getCompanyList(params?: UserParam) {
|
||||
const res = await request.get<ApiResult<User[]>>(
|
||||
'/system/user',
|
||||
SERVER_API_URL + '/system/user',
|
||||
{
|
||||
params
|
||||
}
|
||||
@@ -70,7 +68,7 @@ export async function getCompanyList(params?: UserParam) {
|
||||
*/
|
||||
export async function getUser(id: number) {
|
||||
const res = await request.get<ApiResult<User>>(
|
||||
'/system/user/' + id,
|
||||
SERVER_API_URL + '/system/user/' + id,
|
||||
{}
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
@@ -84,7 +82,7 @@ export async function getUser(id: number) {
|
||||
*/
|
||||
export async function addUser(data: User) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/system/user',
|
||||
SERVER_API_URL + '/system/user',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
@@ -112,7 +110,7 @@ export async function updateUser(data: User) {
|
||||
*/
|
||||
export async function removeUser(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/system/user/' + id
|
||||
SERVER_API_URL + '/system/user/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
@@ -125,7 +123,7 @@ export async function removeUser(id?: number) {
|
||||
*/
|
||||
export async function removeUsers(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/system/user/batch',
|
||||
SERVER_API_URL + '/system/user/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
@@ -141,7 +139,7 @@ export async function removeUsers(data: (number | undefined)[]) {
|
||||
*/
|
||||
export async function updateUserStatus(userId?: number, status?: number) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/system/user/status',
|
||||
SERVER_API_URL + '/system/user/status',
|
||||
{
|
||||
userId,
|
||||
status
|
||||
@@ -156,9 +154,9 @@ export async function updateUserStatus(userId?: number, status?: number) {
|
||||
/**
|
||||
* 修改推荐状态
|
||||
*/
|
||||
export async function updateUserRecommend(form) {
|
||||
export async function updateUserRecommend(form:any) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/system/user/recommend',
|
||||
SERVER_API_URL + '/system/user/recommend',
|
||||
form
|
||||
);
|
||||
if (res.code === 0) {
|
||||
@@ -172,7 +170,7 @@ export async function updateUserRecommend(form) {
|
||||
*/
|
||||
export async function updateUserPassword(userId?: number, password = '123456') {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/system/user/password',
|
||||
SERVER_API_URL + '/system/user/password',
|
||||
{
|
||||
userId,
|
||||
password
|
||||
@@ -191,7 +189,7 @@ export async function importUsers(file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/system/user/import',
|
||||
SERVER_API_URL + '/system/user/import',
|
||||
formData
|
||||
);
|
||||
if (res.code === 0) {
|
||||
@@ -209,7 +207,7 @@ export async function checkExistence(
|
||||
id?: number
|
||||
) {
|
||||
const res = await request.get<ApiResult<unknown>>(
|
||||
'/system/user/existence',
|
||||
SERVER_API_URL + '/system/user/existence',
|
||||
{
|
||||
params: {field, value, id}
|
||||
}
|
||||
@@ -225,7 +223,7 @@ export async function checkExistence(
|
||||
*/
|
||||
export async function countUserBalance(params?: UserParam) {
|
||||
const res = await request.get<ApiResult<unknown>>(
|
||||
'/system/user/countUserBalance',
|
||||
SERVER_API_URL + '/system/user/countUserBalance',
|
||||
{
|
||||
params
|
||||
}
|
||||
@@ -243,7 +241,7 @@ export async function countUserBalance(params?: UserParam) {
|
||||
*/
|
||||
export async function listAdminsByPhoneAll(params?: UserParam) {
|
||||
const res = await request.get<ApiResult<User[]>>(
|
||||
'/system/user/listAdminsByPhoneAll',
|
||||
SERVER_API_URL + '/system/user/listAdminsByPhoneAll',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
|
||||
@@ -128,6 +128,8 @@ export interface User {
|
||||
certification?: boolean;
|
||||
// 实名认证类型
|
||||
certificationType?: number;
|
||||
// 推荐人ID
|
||||
refereeId?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,6 +60,10 @@ export default {
|
||||
"store/verification",
|
||||
"theme/index",
|
||||
"poster/poster",
|
||||
"chat/conversation/index",
|
||||
"chat/message/index",
|
||||
"chat/message/add",
|
||||
"chat/message/detail"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '注册成为VIP',
|
||||
navigationBarTitleText: '邀请注册',
|
||||
navigationBarTextStyle: 'black'
|
||||
})
|
||||
|
||||
@@ -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<boolean>(true)
|
||||
const [FormData, setFormData] = useState<ShopDealerApply>()
|
||||
const [FormData, setFormData] = useState<User>()
|
||||
const formRef = useRef<any>(null)
|
||||
const [isEditMode, setIsEditMode] = useState<boolean>(false)
|
||||
const [existingApply, setExistingApply] = useState<ShopDealerApply | null>(null)
|
||||
|
||||
// 获取审核状态文字
|
||||
const getApplyStatusText = (status?: number) => {
|
||||
switch (status) {
|
||||
case 10:
|
||||
return '待审核'
|
||||
case 20:
|
||||
return '审核通过'
|
||||
case 30:
|
||||
return '驳回'
|
||||
default:
|
||||
return '未知状态'
|
||||
}
|
||||
}
|
||||
|
||||
const reload = async () => {
|
||||
// 判断用户是否登录
|
||||
if (!user?.userId) {
|
||||
return false;
|
||||
}
|
||||
// 查询当前用户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)
|
||||
const inviteParams = getStoredInviteParams()
|
||||
if (inviteParams?.inviter) {
|
||||
setFormData({
|
||||
...user,
|
||||
refereeId: Number(inviteParams.inviter),
|
||||
// 清空昵称,强制用户手动输入
|
||||
nickname: '',
|
||||
})
|
||||
} else {
|
||||
setIsEditMode(false);
|
||||
setExistingApply(null);
|
||||
setLoading(false)
|
||||
// 如果没有邀请参数,也要确保昵称为空
|
||||
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) {
|
||||
setLoading(true)
|
||||
console.error('查询申请记录失败:', error);
|
||||
setIsEditMode(false);
|
||||
setExistingApply(null);
|
||||
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...')
|
||||
|
||||
// 准备提交的数据
|
||||
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 <Loading className={'px-2'}>加载中</Loading>
|
||||
}
|
||||
@@ -139,50 +382,49 @@ const AddUserAddress = () => {
|
||||
>
|
||||
<View className={'bg-gray-100 h-3'}></View>
|
||||
<CellGroup style={{padding: '4px 0'}}>
|
||||
<Form.Item name="realName" label="名称" initialValue={user?.nickname} required>
|
||||
<Input placeholder="经销商名称" maxLength={10}/>
|
||||
</Form.Item>
|
||||
<Form.Item name="mobile" label="手机号" initialValue={user?.mobile} required>
|
||||
<Input placeholder="请输入手机号" disabled={true} maxLength={11}/>
|
||||
</Form.Item>
|
||||
<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required>
|
||||
<Input placeholder="邀请人ID"/>
|
||||
<Input placeholder="邀请人ID" disabled={true}/>
|
||||
</Form.Item>
|
||||
<Form.Item name="phone" label="手机号" initialValue={FormData?.phone} required>
|
||||
<View className="flex items-center justify-between">
|
||||
<Input
|
||||
placeholder="请填写手机号"
|
||||
disabled={true}
|
||||
maxLength={11}
|
||||
value={FormData?.phone || ''}
|
||||
/>
|
||||
<Button style={{color: '#ffffff'}} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
|
||||
<Space>
|
||||
<Button size="small">点击获取</Button>
|
||||
</Space>
|
||||
</Button>
|
||||
</View>
|
||||
</Form.Item>
|
||||
{
|
||||
FormData?.phone && <Form.Item name="avatar" label="头像" initialValue={FormData?.avatar} required>
|
||||
<Button open-type="chooseAvatar" style={{height: '58px'}} onChooseAvatar={uploadAvatar}>
|
||||
<Avatar src={FormData?.avatar || user?.avatar} size="54"/>
|
||||
</Button>
|
||||
</Form.Item>
|
||||
}
|
||||
<Form.Item name="realName" label="昵称" initialValue="" required>
|
||||
<Input
|
||||
type="nickname"
|
||||
className="info-content__input"
|
||||
placeholder="请获取微信昵称"
|
||||
value={FormData?.nickname || ''}
|
||||
onInput={(e: InputEvent) => getWxNickname(e.detail.value)}
|
||||
/>
|
||||
</Form.Item>
|
||||
</CellGroup>
|
||||
</Form>
|
||||
{/* 审核状态显示(仅在编辑模式下显示) */}
|
||||
{isEditMode && (
|
||||
<CellGroup>
|
||||
<Cell
|
||||
title={'审核状态'}
|
||||
extra={
|
||||
<span style={{
|
||||
color: FormData?.applyStatus === 20 ? '#52c41a' :
|
||||
FormData?.applyStatus === 30 ? '#ff4d4f' : '#faad14'
|
||||
}}>
|
||||
{getApplyStatusText(FormData?.applyStatus)}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
{FormData?.applyStatus === 20 && (
|
||||
<Cell title={'审核时间'} extra={FormData?.auditTime || '无'}/>
|
||||
)}
|
||||
{FormData?.applyStatus === 30 && (
|
||||
<Cell title={'驳回原因'} extra={FormData?.rejectReason || '无'}/>
|
||||
)}
|
||||
</CellGroup>
|
||||
)}
|
||||
|
||||
|
||||
{/* 底部浮动按钮 */}
|
||||
{(!isEditMode || FormData?.applyStatus === 10 || FormData?.applyStatus === 30) && (
|
||||
<FixedButton
|
||||
icon={<Edit/>}
|
||||
text={isEditMode ? '保存修改' : '提交申请'}
|
||||
disabled={FormData?.applyStatus === 10}
|
||||
text={'立即注册'}
|
||||
onClick={handleFixedButtonClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '我的团队'
|
||||
navigationBarTitleText: '邀请推广'
|
||||
})
|
||||
|
||||
@@ -1,66 +1,79 @@
|
||||
import React, {useState, useEffect, useCallback} from 'react'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {Space, Avatar, Loading} from '@nutui/nutui-react-taro'
|
||||
import {User} from '@nutui/icons-react-taro'
|
||||
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<TeamMemberWithStats[]>([])
|
||||
const {dealerUser} = useDealerUser()
|
||||
const [dealerId, setDealerId] = useState<number>()
|
||||
// 层级栈,用于支持返回上一层
|
||||
const [levelStack, setLevelStack] = useState<LevelInfo[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
// 当前查看的用户名称
|
||||
const [currentDealerName, setCurrentDealerName] = useState<string>('')
|
||||
|
||||
// 获取团队数据
|
||||
const fetchTeamData = useCallback(async () => {
|
||||
if (!dealerUser?.userId && !dealerId) return
|
||||
// 异步加载成员统计数据
|
||||
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 {
|
||||
console.log(dealerId, 'dealerId>>>>>>>>>')
|
||||
// 获取团队成员关系
|
||||
const refereeResult = await listShopDealerReferee({
|
||||
dealerId: dealerId ? dealerId : dealerUser?.userId
|
||||
})
|
||||
|
||||
if (refereeResult) {
|
||||
// 处理团队成员数据
|
||||
const processedMembers: TeamMemberWithStats[] = refereeResult.map(member => ({
|
||||
...member,
|
||||
name: `用户${member.userId}`,
|
||||
avatar: '',
|
||||
orderCount: 0,
|
||||
commission: '0.00',
|
||||
status: 'active' as const,
|
||||
subMembers: 0,
|
||||
joinTime: member.createTime
|
||||
}))
|
||||
|
||||
// 并行获取每个成员的订单统计
|
||||
const memberStats = await Promise.all(
|
||||
processedMembers.map(async (member) => {
|
||||
try {
|
||||
const orderResult = await pageShopDealerOrder({
|
||||
// 并行获取订单统计和下级成员数量
|
||||
const [orderResult, subMembersResult] = await Promise.all([
|
||||
pageShopDealerOrder({
|
||||
page: 1,
|
||||
limit: 100,
|
||||
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
|
||||
const orderCount = orders.length
|
||||
const commission = orders.reduce((sum, order) => {
|
||||
orderCount = orders.length
|
||||
commission = orders.reduce((sum, order) => {
|
||||
const levelCommission = member.level === 1 ? order.firstMoney :
|
||||
member.level === 2 ? order.secondMoney :
|
||||
order.thirdMoney
|
||||
@@ -73,23 +86,79 @@ const DealerTeam: React.FC = () => {
|
||||
const hasRecentOrder = orders.some(order =>
|
||||
new Date(order.createTime || '') > thirtyDaysAgo
|
||||
)
|
||||
status = hasRecentOrder ? 'active' : 'inactive'
|
||||
}
|
||||
|
||||
return {
|
||||
...member,
|
||||
orderCount,
|
||||
commission,
|
||||
status: hasRecentOrder ? 'active' as const : 'inactive' as const
|
||||
status,
|
||||
subMembers: subMembersResult?.length || 0
|
||||
}
|
||||
}
|
||||
return member
|
||||
} catch (error) {
|
||||
console.error(`获取成员${member.userId}订单失败:`, error)
|
||||
return member
|
||||
console.error(`获取成员${member.userId}数据失败:`, error)
|
||||
return {
|
||||
...member,
|
||||
orderCount: 0,
|
||||
commission: '0.00',
|
||||
status: 'inactive' as const,
|
||||
subMembers: 0
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
setTeamMembers(memberStats)
|
||||
// 更新这一批成员的数据
|
||||
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) {
|
||||
@@ -98,12 +167,106 @@ const DealerTeam: React.FC = () => {
|
||||
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)
|
||||
}
|
||||
|
||||
// 监听数据变化,获取团队数据
|
||||
@@ -113,81 +276,164 @@ const DealerTeam: React.FC = () => {
|
||||
}
|
||||
}, [fetchTeamData])
|
||||
|
||||
const renderMemberItem = (member: TeamMemberWithStats) => (
|
||||
<View key={member.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm" onClick={() => getNextUser(member)}>
|
||||
// 初始化当前用户名称
|
||||
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 (
|
||||
<View
|
||||
key={member.id}
|
||||
className={`bg-white rounded-lg p-4 mb-3 shadow-sm ${
|
||||
canClick ? 'cursor-pointer' : 'cursor-default opacity-75'
|
||||
}`}
|
||||
onClick={() => getNextUser(member)}
|
||||
>
|
||||
<View className="flex items-center mb-3">
|
||||
<Avatar
|
||||
size="40"
|
||||
src={member.avatar}
|
||||
icon={<User/>}
|
||||
className="mr-3"
|
||||
/>
|
||||
<View className="flex-1">
|
||||
<View className="flex items-center mb-1">
|
||||
<Text className="font-semibold text-gray-800 mr-2">
|
||||
{member.name}
|
||||
<View className="flex items-center justify-between mb-1">
|
||||
<View className="flex items-center">
|
||||
<Space>
|
||||
{member.alias ? <Text className="font-semibold text-blue-700 mr-2">{member.alias}</Text> :
|
||||
<Text className="font-semibold text-gray-800 mr-2">{member.nickname}</Text>}
|
||||
{/*别名备注*/}
|
||||
<Edit size={16} className={'text-blue-500 mr-2'} onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
editAlias(member, index)
|
||||
}}/>
|
||||
{/*发送消息*/}
|
||||
<Message size={16} className={'text-orange-500 mr-2'} onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
sendMessage(member)
|
||||
}}/>
|
||||
</Space>
|
||||
</View>
|
||||
{/* 显示手机号(仅本级可见) */}
|
||||
{showPhone && member.phone && (
|
||||
<Text className="text-sm text-gray-500" onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
makePhoneCall(member.phone || '');
|
||||
}}>
|
||||
{member.phone}
|
||||
<Phone size={12} className="ml-1 text-green-500"/>
|
||||
</Text>
|
||||
{/*{getLevelIcon(Number(member.level))}*/}
|
||||
{/*<Text className="text-xs text-gray-500 ml-1">*/}
|
||||
{/* {member.level}级*/}
|
||||
{/*</Text>*/}
|
||||
)}
|
||||
</View>
|
||||
<Text className="text-xs text-gray-500">
|
||||
加入时间:{member.joinTime}
|
||||
</Text>
|
||||
</View>
|
||||
{/*<View className="text-right">*/}
|
||||
{/* <Tag*/}
|
||||
{/* type={member.status === 'active' ? 'success' : 'default'}*/}
|
||||
{/* >*/}
|
||||
{/* {member.status === 'active' ? '活跃' : '沉默'}*/}
|
||||
{/* </Tag>*/}
|
||||
{/*</View>*/}
|
||||
</View>
|
||||
|
||||
<View className="grid grid-cols-3 gap-4 text-center">
|
||||
<Space>
|
||||
<Text className="text-xs text-gray-500">订单数</Text>
|
||||
<Text className="text-sm font-semibold text-blue-600">
|
||||
{member.orderCount}
|
||||
{isStatsLoading ? '-' : member.orderCount}
|
||||
</Text>
|
||||
</Space>
|
||||
<Space>
|
||||
<Text className="text-xs text-gray-500">贡献佣金</Text>
|
||||
<Text className="text-sm font-semibold text-green-600">
|
||||
¥{member.commission}
|
||||
{isStatsLoading ? '-' : `¥${member.commission}`}
|
||||
</Text>
|
||||
</Space>
|
||||
<Space>
|
||||
<Text className="text-xs text-gray-500">团队成员</Text>
|
||||
<Text className="text-sm font-semibold text-purple-600">
|
||||
{member.subMembers}
|
||||
<Text className={`text-sm font-semibold ${
|
||||
canClick ? 'text-purple-600' : 'text-gray-400'
|
||||
}`}>
|
||||
{isStatsLoading ? '-' : (member.subMembers || 0)}
|
||||
</Text>
|
||||
</Space>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const renderOverview = () => (
|
||||
<View className="rounded-xl p-4">
|
||||
{teamMembers.slice(0, 3).map(renderMemberItem)}
|
||||
<View
|
||||
className={'bg-white rounded-lg py-2 px-4 mb-3 shadow-sm text-right text-sm font-medium flex justify-between items-center'}>
|
||||
<Text className="text-lg font-semibold">我的团队成员</Text>
|
||||
<Text className={'text-gray-500 '}>成员数:{teamMembers.length}</Text>
|
||||
</View>
|
||||
{teamMembers.map(renderMemberItem)}
|
||||
</View>
|
||||
)
|
||||
|
||||
if (!dealerUser) {
|
||||
// 渲染顶部导航栏
|
||||
const renderHeader = () => {
|
||||
if (levelStack.length === 0) return null
|
||||
|
||||
return (
|
||||
<View className="bg-gray-50 min-h-screen flex items-center justify-center">
|
||||
<Loading/>
|
||||
<Text className="text-gray-500 mt-2">加载中...</Text>
|
||||
<View className="bg-white p-4 mb-3 shadow-sm">
|
||||
<View className="flex items-center justify-between">
|
||||
<View className="flex items-center">
|
||||
<Text className="text-lg font-semibold">
|
||||
{currentDealerName}的团队成员
|
||||
</Text>
|
||||
</View>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={goBack}
|
||||
className="bg-blue-500"
|
||||
>
|
||||
返回上一层
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
if (!dealerUser) {
|
||||
return (
|
||||
<View className="min-h-screen">
|
||||
{renderOverview()}
|
||||
<Space className="flex items-center justify-center">
|
||||
<Empty description="您还不是业务人员" style={{
|
||||
backgroundColor: 'transparent'
|
||||
}} actions={[{text: '立即申请', onClick: () => navTo(`/dealer/apply/add`, true)}]}
|
||||
/>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderHeader()}
|
||||
|
||||
{loading ? (
|
||||
<View className="flex items-center justify-center mt-20">
|
||||
<Text className="text-gray-500">加载中...</Text>
|
||||
</View>
|
||||
) : teamMembers.length > 0 ? (
|
||||
renderOverview()
|
||||
) : (
|
||||
<View className="flex items-center justify-center mt-20">
|
||||
<Empty description="暂无成员" style={{
|
||||
backgroundColor: 'transparent'
|
||||
}}/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<FixedButton text={'立即邀请'} onClick={() => navTo(`/dealer/qrcode/index`, true)}/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DealerTeam
|
||||
export default DealerTeam;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
Location,
|
||||
Tips,
|
||||
Ask,
|
||||
Dongdong,
|
||||
// Dongdong,
|
||||
People,
|
||||
// AfterSaleService,
|
||||
Logout,
|
||||
@@ -95,13 +95,13 @@ const UserCell = () => {
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'我的邀请码'} onClick={() => navTo('/dealer/qrcode/index', true)}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-orange-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Dongdong color="#f59e0b" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
{/*<Grid.Item text={'我的邀请码'} onClick={() => navTo('/dealer/qrcode/index', true)}>*/}
|
||||
{/* <View className="text-center">*/}
|
||||
{/* <View className="w-12 h-12 bg-orange-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
|
||||
{/* <Dongdong color="#f59e0b" size="20"/>*/}
|
||||
{/* </View>*/}
|
||||
{/* </View>*/}
|
||||
{/*</Grid.Item>*/}
|
||||
|
||||
{/*<Grid.Item text={'管理中心'} onClick={() => navTo('/admin/index', true)}>*/}
|
||||
{/* <View className="text-center">*/}
|
||||
|
||||
3
src/user/chat/conversation/index.config.ts
Normal file
3
src/user/chat/conversation/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '站内消息'
|
||||
})
|
||||
167
src/user/chat/conversation/index.tsx
Normal file
167
src/user/chat/conversation/index.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
import {useState, useCallback, useEffect} from 'react'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {Loading, InfiniteLoading, Empty, Space, Tag} from '@nutui/nutui-react-taro'
|
||||
import {pageShopChatConversation} from "@/api/shop/shopChatConversation";
|
||||
import FixedButton from "@/components/FixedButton";
|
||||
|
||||
const Index = () => {
|
||||
const [list, setList] = useState<any[]>([])
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [page, setPage] = useState(1)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
|
||||
// 获取消息数据
|
||||
const fetchMessageData = useCallback(async (resetPage = false, targetPage?: number, searchKeyword?: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const currentPage = resetPage ? 1 : (targetPage || page);
|
||||
|
||||
// 构建API参数,根据状态筛选
|
||||
const params: any = {
|
||||
page: currentPage
|
||||
};
|
||||
|
||||
// 添加搜索关键词
|
||||
if (searchKeyword && searchKeyword.trim()) {
|
||||
params.keywords = searchKeyword.trim();
|
||||
}
|
||||
|
||||
const res = await pageShopChatConversation(params);
|
||||
|
||||
if (res?.list && res.list.length > 0) {
|
||||
// 正确映射状态
|
||||
const mappedList = res.list.map(customer => ({
|
||||
...customer
|
||||
}));
|
||||
|
||||
// 如果是重置页面或第一页,直接设置新数据;否则追加数据
|
||||
if (resetPage || currentPage === 1) {
|
||||
setList(mappedList);
|
||||
} else {
|
||||
setList(prevList => prevList.concat(mappedList));
|
||||
}
|
||||
|
||||
// 正确判断是否还有更多数据
|
||||
const hasMoreData = res.list.length >= 10; // 假设每页10条数据
|
||||
setHasMore(hasMoreData);
|
||||
} else {
|
||||
if (resetPage || currentPage === 1) {
|
||||
setList([]);
|
||||
}
|
||||
setHasMore(false);
|
||||
}
|
||||
|
||||
setPage(currentPage);
|
||||
} catch (error) {
|
||||
console.error('获取消息数据失败:', error);
|
||||
Taro.showToast({
|
||||
title: '加载失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [page]);
|
||||
|
||||
const reloadMore = async () => {
|
||||
if (loading || !hasMore) return; // 防止重复加载
|
||||
const nextPage = page + 1;
|
||||
await fetchMessageData(false, nextPage);
|
||||
}
|
||||
|
||||
|
||||
// 获取列表数据(现在使用服务端搜索,不需要消息端过滤)
|
||||
const getFilteredList = () => {
|
||||
return list;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 初始化时加载数据
|
||||
fetchMessageData(true, 1, '');
|
||||
}, []);
|
||||
|
||||
// 渲染消息项
|
||||
const renderMessageItem = (customer: any) => (
|
||||
<View key={customer.userId} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
|
||||
<View className="flex items-center">
|
||||
<View className="flex-1">
|
||||
<View className="flex items-center justify-between mb-1">
|
||||
<Text className="font-semibold text-gray-800 mr-2">
|
||||
关于XXXX的通知
|
||||
</Text>
|
||||
<Tag type={'warning'}>未读</Tag>
|
||||
{/*<Tag type={'success'}>已读</Tag>*/}
|
||||
</View>
|
||||
<Space direction={'vertical'}>
|
||||
{/*<Text className="text-xs text-gray-500">统一代码:{customer.dealerCode}</Text>*/}
|
||||
<Text className="text-xs text-gray-500">
|
||||
创建时间:{customer.createTime}
|
||||
</Text>
|
||||
</Space>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
// 渲染消息列表
|
||||
const renderMessageList = () => {
|
||||
const filteredList = getFilteredList();
|
||||
|
||||
return (
|
||||
<View className="p-4" style={{
|
||||
height: '90vh',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden'
|
||||
}}>
|
||||
<InfiniteLoading
|
||||
target="scroll"
|
||||
hasMore={hasMore}
|
||||
onLoadMore={reloadMore}
|
||||
onScroll={() => {
|
||||
// 滚动事件处理
|
||||
}}
|
||||
onScrollToUpper={() => {
|
||||
// 滚动到顶部事件处理
|
||||
}}
|
||||
loadingText={
|
||||
<>
|
||||
加载中...
|
||||
</>
|
||||
}
|
||||
loadMoreText={
|
||||
filteredList.length === 0 ? (
|
||||
<Empty
|
||||
style={{backgroundColor: 'transparent'}}
|
||||
description={loading ? "加载中..." : "暂无消息数据"}
|
||||
/>
|
||||
) : (
|
||||
<View className={'h-12 flex items-center justify-center'}>
|
||||
<Text className="text-gray-500 text-sm">没有更多了</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
>
|
||||
{loading && filteredList.length === 0 ? (
|
||||
<View className="flex items-center justify-center py-8">
|
||||
<Loading/>
|
||||
<Text className="text-gray-500 mt-2 ml-2">加载中...</Text>
|
||||
</View>
|
||||
) : (
|
||||
filteredList.map(renderMessageItem)
|
||||
)}
|
||||
</InfiniteLoading>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="min-h-screen bg-gray-50">
|
||||
{/* 消息列表 */}
|
||||
{renderMessageList()}
|
||||
<FixedButton />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
||||
4
src/user/chat/message/add.config.ts
Normal file
4
src/user/chat/message/add.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '发送消息',
|
||||
navigationBarTextStyle: 'black'
|
||||
})
|
||||
135
src/user/chat/message/add.tsx
Normal file
135
src/user/chat/message/add.tsx
Normal file
@@ -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<User>()
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
const [FormData, _] = useState<ShopChatMessage>()
|
||||
const formRef = useRef<any>(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 <Loading className={'px-2'}>加载中</Loading>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Cell title={toUser ? (
|
||||
<View className={'flex items-center'}>
|
||||
<Avatar src={toUser.avatar}/>
|
||||
<View className={'ml-2 flex flex-col'}>
|
||||
<Text>{toUser.alias || toUser.nickname}</Text>
|
||||
<Text className={'text-gray-300'}>{toUser.mobile}</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : '选择发送对象'} extra={(
|
||||
<ArrowRight color="#cccccc" className={toUser ? 'mt-2' : ''} size={toUser ? 20 : 18}/>
|
||||
)}
|
||||
onClick={() => navTo(`/dealer/team/index`, true)}/>
|
||||
<Form
|
||||
ref={formRef}
|
||||
divider
|
||||
initialValues={FormData}
|
||||
labelPosition="left"
|
||||
onFinish={(values) => submitSucceed(values)}
|
||||
onFinishFailed={(errors) => submitFailed(errors)}
|
||||
>
|
||||
<CellGroup style={{padding: '4px 0'}}>
|
||||
<Form.Item name="content" initialValue={FormData?.content} required>
|
||||
<Input placeholder="填写消息内容" maxLength={300}/>
|
||||
</Form.Item>
|
||||
</CellGroup>
|
||||
</Form>
|
||||
|
||||
{/* 底部浮动按钮 */}
|
||||
<FixedButton text={isEditMode ? '立即发送' : '立即发送'} onClick={() => formRef.current?.submit()}/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddMessage;
|
||||
4
src/user/chat/message/detail.config.ts
Normal file
4
src/user/chat/message/detail.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '查看消息',
|
||||
navigationBarTextStyle: 'black'
|
||||
})
|
||||
77
src/user/chat/message/detail.tsx
Normal file
77
src/user/chat/message/detail.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import {useRouter} from '@tarojs/taro'
|
||||
import {CellGroup, Cell, Loading, Avatar} from '@nutui/nutui-react-taro'
|
||||
import {View,Text} from '@tarojs/components'
|
||||
import {ArrowRight} from '@nutui/icons-react-taro'
|
||||
import {getShopChatMessage, updateShopChatMessage} from "@/api/shop/shopChatMessage";
|
||||
import {ShopChatMessage} from "@/api/shop/shopChatMessage/model";
|
||||
import navTo from "@/utils/common";
|
||||
|
||||
const AddMessageDetail = () => {
|
||||
const {params} = useRouter();
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
const [item, setItem] = useState<ShopChatMessage>()
|
||||
|
||||
const reload = () => {
|
||||
const id = params.id ? Number(params.id) : undefined
|
||||
if (id) {
|
||||
getShopChatMessage(id).then(data => {
|
||||
setItem(data)
|
||||
setLoading(false)
|
||||
updateShopChatMessage({
|
||||
...data,
|
||||
status: 1
|
||||
}).then(() => {
|
||||
console.log('设为已读')
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
reload()
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Loading className={'px-2'}>加载中</Loading>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Cell style={{
|
||||
display: 'none'
|
||||
}} title={item?.formUserId ? (
|
||||
<View className={'flex items-center'}>
|
||||
<Avatar src={item.formUserAvatar}/>
|
||||
<View className={'ml-2 flex flex-col'}>
|
||||
<Text>{item.formUserAlias || item.formUserName}</Text>
|
||||
<Text className={'text-gray-300'}>{item.formUserPhone}</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : '选择发送对象'} extra={(
|
||||
<ArrowRight color="#cccccc" className={item ? 'mt-2' : ''} size={item ? 20 : 18}/>
|
||||
)}
|
||||
onClick={() => navTo(`/dealer/team/index`, true)}/>
|
||||
<CellGroup>
|
||||
<Cell title={'发布人'} extra={item?.formUserAlias || item?.formUserName}/>
|
||||
<Cell title={'创建时间'} extra={item?.createTime}/>
|
||||
<Cell title={'状态'} extra={
|
||||
item?.status === 0 ? '未读' : '已读'
|
||||
}/>
|
||||
{/*<Cell title={(*/}
|
||||
{/* <>*/}
|
||||
{/* <Text>{'消息内容:'}</Text>*/}
|
||||
{/* <Text>{item?.content}</Text>*/}
|
||||
{/* </>*/}
|
||||
{/*)} />*/}
|
||||
</CellGroup>
|
||||
<CellGroup>
|
||||
<Cell title={(
|
||||
<Text>{item?.content}</Text>
|
||||
)} />
|
||||
</CellGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddMessageDetail;
|
||||
3
src/user/chat/message/index.config.ts
Normal file
3
src/user/chat/message/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '我的消息'
|
||||
})
|
||||
179
src/user/chat/message/index.tsx
Normal file
179
src/user/chat/message/index.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
import {useState, useCallback, useEffect} from 'react'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {Loading, InfiniteLoading, Empty, Avatar, Badge} from '@nutui/nutui-react-taro'
|
||||
import FixedButton from "@/components/FixedButton";
|
||||
import {ShopChatMessage} from "@/api/shop/shopChatMessage/model";
|
||||
import {pageShopChatMessage} from "@/api/shop/shopChatMessage";
|
||||
import navTo from "@/utils/common";
|
||||
|
||||
const MessageIndex = () => {
|
||||
const [list, setList] = useState<ShopChatMessage[]>([])
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [page, setPage] = useState(1)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
|
||||
// 获取消息数据
|
||||
const fetchMessageData = useCallback(async (resetPage = false, targetPage?: number, searchKeyword?: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const currentPage = resetPage ? 1 : (targetPage || page);
|
||||
|
||||
// 构建API参数,根据状态筛选
|
||||
const params: any = {
|
||||
type: 'text',
|
||||
page: currentPage,
|
||||
toUserId: Taro.getStorageSync('UserId')
|
||||
};
|
||||
|
||||
// 添加搜索关键词
|
||||
if (searchKeyword && searchKeyword.trim()) {
|
||||
params.keywords = searchKeyword.trim();
|
||||
}
|
||||
|
||||
const res = await pageShopChatMessage(params);
|
||||
|
||||
if (res?.list && res.list.length > 0) {
|
||||
// 正确映射状态
|
||||
const mappedList = res.list.map(customer => ({
|
||||
...customer
|
||||
}));
|
||||
|
||||
// 如果是重置页面或第一页,直接设置新数据;否则追加数据
|
||||
if (resetPage || currentPage === 1) {
|
||||
setList(mappedList);
|
||||
} else {
|
||||
setList(prevList => prevList.concat(mappedList));
|
||||
}
|
||||
|
||||
// 正确判断是否还有更多数据
|
||||
const hasMoreData = res.list.length >= 10; // 假设每页10条数据
|
||||
setHasMore(hasMoreData);
|
||||
} else {
|
||||
if (resetPage || currentPage === 1) {
|
||||
setList([]);
|
||||
}
|
||||
setHasMore(false);
|
||||
}
|
||||
|
||||
setPage(currentPage);
|
||||
} catch (error) {
|
||||
console.error('获取消息数据失败:', error);
|
||||
Taro.showToast({
|
||||
title: '加载失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [page]);
|
||||
|
||||
const reloadMore = async () => {
|
||||
if (loading || !hasMore) return; // 防止重复加载
|
||||
const nextPage = page + 1;
|
||||
await fetchMessageData(false, nextPage);
|
||||
}
|
||||
|
||||
|
||||
// 获取列表数据(现在使用服务端搜索,不需要消息端过滤)
|
||||
const getFilteredList = () => {
|
||||
return list;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 初始化时加载数据
|
||||
fetchMessageData(true, 1, '');
|
||||
}, []);
|
||||
|
||||
// 渲染消息项
|
||||
const renderMessageItem = (item: any) => (
|
||||
<View key={item.userId} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
|
||||
<View className="flex items-center" onClick={() => navTo(`/user/chat/message/detail?id=${item.id}`,true)}>
|
||||
<View className="flex-1">
|
||||
<View className="flex items-center justify-between mb-1">
|
||||
<View className={'flex w-full'}>
|
||||
<Badge style={{marginInlineEnd: '10px'}} dot={item.status === 0} top="2" right="4">
|
||||
<Avatar
|
||||
size="40"
|
||||
src={item.formUserAvatar}
|
||||
/>
|
||||
</Badge>
|
||||
<View className="flex flex-col w-full">
|
||||
<View className="flex items-center w-full justify-between">
|
||||
<Text className="font-semibold text-gray-800 mr-2">{item.formUserAlias || item.formUserName}</Text>
|
||||
<Text className="text-xs text-gray-500">
|
||||
{item.createTime}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className="text-gray-500 mt-2 mr-2">
|
||||
{item.content}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
// 渲染消息列表
|
||||
const renderMessageList = () => {
|
||||
const filteredList = getFilteredList();
|
||||
|
||||
return (
|
||||
<View className="p-4" style={{
|
||||
height: '90vh',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden'
|
||||
}}>
|
||||
<InfiniteLoading
|
||||
target="scroll"
|
||||
hasMore={hasMore}
|
||||
onLoadMore={reloadMore}
|
||||
onScroll={() => {
|
||||
// 滚动事件处理
|
||||
}}
|
||||
onScrollToUpper={() => {
|
||||
// 滚动到顶部事件处理
|
||||
}}
|
||||
loadingText={
|
||||
<>
|
||||
加载中...
|
||||
</>
|
||||
}
|
||||
loadMoreText={
|
||||
filteredList.length === 0 ? (
|
||||
<Empty
|
||||
style={{backgroundColor: 'transparent'}}
|
||||
description={loading ? "加载中..." : "暂无消息数据"}
|
||||
/>
|
||||
) : (
|
||||
<View className={'h-12 flex items-center justify-center'}>
|
||||
<Text className="text-gray-500 text-sm">没有更多了</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
>
|
||||
{loading && filteredList.length === 0 ? (
|
||||
<View className="flex items-center justify-center py-8">
|
||||
<Loading/>
|
||||
<Text className="text-gray-500 mt-2 ml-2">加载中...</Text>
|
||||
</View>
|
||||
) : (
|
||||
filteredList.map(renderMessageItem)
|
||||
)}
|
||||
</InfiniteLoading>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="min-h-screen bg-gray-50">
|
||||
{/* 消息列表 */}
|
||||
{renderMessageList()}
|
||||
<FixedButton text={'发送消息'} onClick={() => navTo(`/user/chat/message/add`,true)}/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageIndex;
|
||||
Reference in New Issue
Block a user