```
feat(shop): 新增聊天会话与消息模块API新增了聊天会话(ShopChatConversation)和聊天消息(ShopChatMessage)两个模块的完整API接口及数据模型,包括分页查询、列表查询、新增、修改、删除、批量删除及根据ID查询等功能。feat(system): 扩展用户模型并重构API调用方式 为用户模型添加推荐人ID字段(refereeId),并在用户相关API中引入SERVER_API_URL常量以统一管理接口前缀,优化调用结构。 feat(dealer): 优化经销商邀请注册流程将经销商申请页面调整为邀请注册模式,增强微信手机号获取、头像上传及昵称校验逻辑,完善邀请关系绑定机制,并更新页面标题提示信息。 ```
This commit is contained in:
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