From 1d731c00061bfedd0399aa59f5341c14792e2819 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Fri, 29 Aug 2025 16:46:47 +0800 Subject: [PATCH 001/146] =?UTF-8?q?app=E6=9B=B4=E6=96=B0=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/common/config/route.php | 7 +++++-- Server/application/common/controller/Api.php | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Server/application/common/config/route.php b/Server/application/common/config/route.php index 8b133a1a..8c6ccc6b 100644 --- a/Server/application/common/config/route.php +++ b/Server/application/common/config/route.php @@ -9,7 +9,7 @@ Route::group('v1/auth', function () { Route::post('login', 'app\common\controller\PasswordLoginController@index'); // 账号密码登录 Route::post('mobile-login', 'app\common\controller\Auth@mobileLogin'); // 手机号验证码登录 Route::post('code', 'app\common\controller\Auth@SendCodeController'); // 发送验证码 - + // 需要JWT认证的接口 Route::get('info', 'app\common\controller\Auth@info')->middleware(['jwt']); // 获取用户信息 Route::post('refresh', 'app\common\controller\Auth@refresh')->middleware(['jwt']); // 刷新令牌 @@ -19,4 +19,7 @@ Route::group('v1/auth', function () { Route::group('v1/', function () { Route::post('attachment/upload', 'app\common\controller\Attachment@upload'); // 上传附件 Route::get('attachment/:id', 'app\common\controller\Attachment@info'); // 获取附件信息 -})->middleware(['jwt']); \ No newline at end of file +})->middleware(['jwt']); + + +Route::get('app/update', 'app\common\controller\Api@uploadApp'); \ No newline at end of file diff --git a/Server/application/common/controller/Api.php b/Server/application/common/controller/Api.php index 5a554eff..42eb2eb6 100644 --- a/Server/application/common/controller/Api.php +++ b/Server/application/common/controller/Api.php @@ -125,4 +125,20 @@ class Api extends Controller $response->send(); exit; } + + + public function uploadApp() + { + $type = $this->request->param('type', ''); + if (empty($type)){ + return json_encode(['code' => 500,'msg' => '参数缺失']); + } + $data = [ + "version" => "1.1.0", + "downloadUrl"=> "http://kr-phone.quwanzhi.com/ckb.apk", + "updateContent"=> "1. 修复了已知问题\n2. 优化了用户体验\n3. 新增了某某功能" + ]; + return json_encode(['code' => 200,'msg' => '获取成功','data' => $data]); + + } } \ No newline at end of file From 2deb80fdbfe9414f937d386e3a2e956025fe7da6 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Fri, 29 Aug 2025 16:56:50 +0800 Subject: [PATCH 002/146] =?UTF-8?q?app=E6=9B=B4=E6=96=B0=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/common/controller/Api.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Server/application/common/controller/Api.php b/Server/application/common/controller/Api.php index 42eb2eb6..1c73576d 100644 --- a/Server/application/common/controller/Api.php +++ b/Server/application/common/controller/Api.php @@ -3,6 +3,7 @@ namespace app\common\controller; use think\Controller; +use think\Db; use think\facade\Config; use think\facade\Request; use think\facade\Response; @@ -133,11 +134,17 @@ class Api extends Controller if (empty($type)){ return json_encode(['code' => 500,'msg' => '参数缺失']); } - $data = [ - "version" => "1.1.0", - "downloadUrl"=> "http://kr-phone.quwanzhi.com/ckb.apk", - "updateContent"=> "1. 修复了已知问题\n2. 优化了用户体验\n3. 新增了某某功能" - ]; + + if (!in_array($type,['ckb','ai_store'])){ + return json_encode(['code' => 500,'msg' => '参数错误']); + } + + $data = Db::name('app_version') + ->field('version,downloadUrl,updateContent') + ->where(['type'=>$type]) + ->order('id DESC') + ->find(); + return json_encode(['code' => 200,'msg' => '获取成功','data' => $data]); } From fb10e430557da4c4b8b7951eace87daf32a09e1a Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Fri, 29 Aug 2025 17:04:11 +0800 Subject: [PATCH 003/146] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/common/controller/Api.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Server/application/common/controller/Api.php b/Server/application/common/controller/Api.php index 1c73576d..e95ca699 100644 --- a/Server/application/common/controller/Api.php +++ b/Server/application/common/controller/Api.php @@ -2,6 +2,7 @@ namespace app\common\controller; +use library\ResponseHelper; use think\Controller; use think\Db; use think\facade\Config; @@ -132,20 +133,19 @@ class Api extends Controller { $type = $this->request->param('type', ''); if (empty($type)){ - return json_encode(['code' => 500,'msg' => '参数缺失']); + return ResponseHelper::error('参数缺失'); } if (!in_array($type,['ckb','ai_store'])){ - return json_encode(['code' => 500,'msg' => '参数错误']); + return ResponseHelper::error('参数错误'); } - + $data = Db::name('app_version') ->field('version,downloadUrl,updateContent') ->where(['type'=>$type]) ->order('id DESC') ->find(); - - return json_encode(['code' => 200,'msg' => '获取成功','data' => $data]); + return ResponseHelper::success($data, '获取成功'); } } \ No newline at end of file From f6837f7819f2a483113b37b360f5be4acf992c92 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Sat, 30 Aug 2025 10:58:34 +0800 Subject: [PATCH 004/146] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ConversationController.php | 52 ++++++++++++++++--- .../cozeai/controller/MessageController.php | 14 ++--- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/Server/application/cozeai/controller/ConversationController.php b/Server/application/cozeai/controller/ConversationController.php index 59b69b5f..88821136 100644 --- a/Server/application/cozeai/controller/ConversationController.php +++ b/Server/application/cozeai/controller/ConversationController.php @@ -62,7 +62,15 @@ class ConversationController extends BaseController try { $bot_id = input('bot_id',''); if(empty($bot_id)){ - return errorJson('智能体ID不能为空'); + if($is_internal){ + return json_encode([ + 'code' => 400, + 'msg' => '智能体ID不能为空', + 'data' => [] + ]); + }else{ + return errorJson('智能体ID不能为空'); + } } $page = input('page',1); $limit = input('limit',20); @@ -78,7 +86,15 @@ class ConversationController extends BaseController $result = json_decode($result, true); if ($result['code'] != 0) { - return errorJson($result['msg'], $result['code']); + if($is_internal){ + return json_encode([ + 'code' => $result['code'], + 'msg' => $result['msg'], + 'data' => [] + ]); + }else{ + return errorJson($result['msg'], $result['code']); + } } // 处理返回的数据并存入数据库 @@ -107,7 +123,15 @@ class ConversationController extends BaseController $companyId = $userInfo['companyId']; if(empty($bot_id)){ - return errorJson('智能体ID不能为空'); + if($is_internal){ + return json_encode([ + 'code' => 400, + 'msg' => '智能体ID不能为空', + 'data' => [] + ]); + }else{ + return errorJson('智能体ID不能为空'); + } } // 构建元数据和消息 @@ -131,7 +155,15 @@ class ConversationController extends BaseController $result = $this->httpRequest($url, 'POST', json_encode($params,256), $this->headers); $result = json_decode($result, true); if ($result['code'] != 0) { - return errorJson($result['msg'], $result['code']); + if($is_internal){ + return json_encode([ + 'code' => $result['code'], + 'msg' => $result['msg'], + 'data' => [] + ]); + }else{ + return errorJson($result['msg'], $result['code']); + } } // 获取返回的对话数据并保存 @@ -167,7 +199,15 @@ class ConversationController extends BaseController } } catch (\Exception $e) { - return errorJson('创建对话失败:' . $e->getMessage()); + if($is_internal){ + return json_encode([ + 'code' => 500, + 'msg' => '创建对话失败:' . $e->getMessage(), + 'data' => [] + ]); + }else{ + return errorJson('创建对话失败:' . $e->getMessage()); + } } } @@ -370,4 +410,4 @@ class ConversationController extends BaseController return successJson($list, '获取成功'); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Server/application/cozeai/controller/MessageController.php b/Server/application/cozeai/controller/MessageController.php index c62208aa..8c3fbd16 100644 --- a/Server/application/cozeai/controller/MessageController.php +++ b/Server/application/cozeai/controller/MessageController.php @@ -16,6 +16,7 @@ class MessageController extends BaseController */ public function getMessages() { + return successJson([], '获取成功'); try { // 获取用户信息 $userInfo = request()->userInfo; @@ -37,12 +38,11 @@ class MessageController extends BaseController if (empty($conversation)) { $conversationController = new ConversationController(); $result = $conversationController->create(true); - $result = json_decode($result, true); - if ($result['code'] != 200) { - return errorJson('创建会话失败:' . $result['msg']); + $resultData = json_decode($result, true); + if ($resultData['code'] != 200) { + return errorJson('创建会话失败:' . $resultData['msg']); } - - $conversation_id = $result['data']['id']; + $conversation_id = $resultData['data']['id']; } else { $conversation_id = $conversation['conversation_id']; } @@ -59,6 +59,8 @@ class MessageController extends BaseController } } + + // 分页参数 $page = input('page', 1); $limit = input('limit', 20); @@ -114,4 +116,4 @@ class MessageController extends BaseController return errorJson('获取对话记录失败:' . $e->getMessage()); } } -} \ No newline at end of file +} \ No newline at end of file From 13cb684abd1076f8c49cec66e1f846fd89a7c765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 30 Aug 2025 11:52:52 +0800 Subject: [PATCH 005/146] =?UTF-8?q?refactor(store):=20=E9=87=8D=E6=9E=84ck?= =?UTF-8?q?chat=E6=A8=A1=E5=9D=97=E4=B8=BA=E5=AD=90=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat(test): 添加数据库测试页面和工具 将ckchat相关代码移动到store/module/ckchat子目录,包含数据定义和实现文件 添加数据库测试页面和工具类,支持服务器ID与本地ID映射 移除不再使用的initSafeArea函数 --- Cunkebao/src/pages/login/Login.tsx | 2 +- Cunkebao/src/pages/mobile/test/db.tsx | 597 +++++++++++ .../ChatWindow/components/Person/index.tsx | 2 +- .../pc/ckbox/components/ChatWindow/index.tsx | 2 +- .../SidebarMenu/WechatFriends/index.tsx | 2 +- .../pc/ckbox/components/SidebarMenu/index.tsx | 2 +- .../components/VerticalUserList/index.tsx | 2 +- Cunkebao/src/pages/pc/ckbox/index.tsx | 2 +- Cunkebao/src/pages/pc/ckbox/main.ts | 2 +- Cunkebao/src/router/module/test.tsx | 6 + Cunkebao/src/store/module/ckchat.ts | 2 +- .../store/module/{ => ckchat}/ckchat.data.ts | 0 Cunkebao/src/store/module/ckchat/ckchat.ts | 205 ++++ Cunkebao/src/store/module/websocket.ts | 2 +- Cunkebao/src/utils/common.ts | 6 - Cunkebao/src/utils/db-examples.ts | 980 +++++++++--------- Cunkebao/src/utils/db-test.ts | 146 +++ Cunkebao/src/utils/db.ts | 546 ++++------ 18 files changed, 1664 insertions(+), 842 deletions(-) create mode 100644 Cunkebao/src/pages/mobile/test/db.tsx rename Cunkebao/src/store/module/{ => ckchat}/ckchat.data.ts (100%) create mode 100644 Cunkebao/src/store/module/ckchat/ckchat.ts create mode 100644 Cunkebao/src/utils/db-test.ts diff --git a/Cunkebao/src/pages/login/Login.tsx b/Cunkebao/src/pages/login/Login.tsx index 73701a38..d375422a 100644 --- a/Cunkebao/src/pages/login/Login.tsx +++ b/Cunkebao/src/pages/login/Login.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { useCkChatStore } from "@/store/module/ckchat"; +import { useCkChatStore } from "@/store/module/ckchat/ckchat"; import { Form, Input, Button, Toast, Checkbox } from "antd-mobile"; import { EyeInvisibleOutline, diff --git a/Cunkebao/src/pages/mobile/test/db.tsx b/Cunkebao/src/pages/mobile/test/db.tsx new file mode 100644 index 00000000..abb773c4 --- /dev/null +++ b/Cunkebao/src/pages/mobile/test/db.tsx @@ -0,0 +1,597 @@ +import React, { useState, useEffect } from "react"; +import { + Button, + Card, + Statistic, + Row, + Col, + Alert, + Typography, + Space, + Spin, + Modal, + message, +} from "antd"; +import { + UserOutlined, + TeamOutlined, + ContactsOutlined, + FolderOutlined, + PlayCircleOutlined, + DeleteOutlined, + ReloadOutlined, +} from "@ant-design/icons"; +import { DatabaseExamples } from "@/utils/db-examples"; +import { + db, + kfUserService, + groupService, + contractService, + DatabaseService, +} from "@/utils/db"; +import { testDatabaseUpgrade, resetDatabase } from "@/utils/db-test"; +import { KfUserListData, GroupData, ContractData } from "@/pages/pc/ckbox/data"; + +const { Title, Text } = Typography; + +interface LogEntry { + id: number; + timestamp: string; + type: "info" | "success" | "error"; + message: string; +} + +const DatabaseTestPage: React.FC = () => { + const [logs, setLogs] = useState([]); + const [isRunning, setIsRunning] = useState(false); + const [dbStats, setDbStats] = useState({ + kfUsers: 0, + groups: 0, + contracts: 0, + newContactList: 0, + }); + + const addLog = (type: LogEntry["type"], msg: string) => { + const newLog: LogEntry = { + id: Date.now(), + timestamp: new Date().toLocaleTimeString(), + type, + message: msg, + }; + setLogs(prev => [newLog, ...prev].slice(0, 100)); // 保留最新100条日志 + + // 使用 Ant Design 的 message 组件显示通知 + if (type === "success") { + message.success(msg); + } else if (type === "error") { + message.error(msg); + } else { + message.info(msg); + } + }; + + const updateStats = async () => { + try { + const newContactListService = new DatabaseService(db.newContractList); + const stats = { + kfUsers: await kfUserService.count(), + groups: await groupService.count(), + contracts: await contractService.count(), + newContactList: await newContactListService.count(), + }; + setDbStats(stats); + } catch (error) { + addLog("error", `统计数据更新失败: ${error}`); + } + }; + + const initDatabase = async () => { + try { + const result = await testDatabaseUpgrade(); + if (result.success) { + addLog( + "success", + `数据库初始化成功 - 版本: ${result.version}, 表: ${result.tables?.join(", ")}`, + ); + await updateStats(); + } else { + addLog("error", `数据库初始化失败: ${result.error}`); + } + } catch (error) { + addLog("error", `数据库初始化失败: ${error}`); + } + }; + + const runKfUserExample = async () => { + setIsRunning(true); + addLog("info", "开始客服用户操作示例..."); + + try { + // 模拟从接口获取的客服用户数据(包含服务器ID) + const serverKfUser = { + id: Date.now(), // 模拟服务器返回的ID + tenantId: 1, + wechatId: `test_${Date.now()}`, + nickname: "测试客服", + alias: "客服小王", + avatar: "https://example.com/avatar.jpg", + gender: 1, + region: "北京", + signature: "专业客服,为您服务", + bindQQ: "123456789", + bindEmail: "test@example.com", + bindMobile: "13800138000", + createTime: new Date().toISOString(), + currentDeviceId: 1, + isDeleted: false, + deleteTime: "", + groupId: 1, + memo: "优秀客服", + wechatVersion: "8.0.0", + labels: ["VIP客服", "专业"], + lastUpdateTime: new Date().toISOString(), + isOnline: true, + }; + + const userId = await kfUserService.createWithServerId(serverKfUser); + addLog( + "success", + `创建客服用户成功,本地ID: ${userId}, 服务器ID: ${serverKfUser.id}`, + ); + + // 查询用户(按本地ID) + const user = await kfUserService.findById(userId); + addLog("info", `查询到用户: ${user?.nickname}`); + + // 根据服务器ID查询 + const userByServerId = await kfUserService.findByServerId( + serverKfUser.id, + ); + addLog("info", `根据服务器ID查询到用户: ${userByServerId?.nickname}`); + + // 更新用户 + await kfUserService.update(userId, { + nickname: "更新后的昵称", + lastUpdateTime: new Date().toISOString(), + }); + addLog("success", "用户信息更新成功"); + + await updateStats(); + } catch (error) { + addLog("error", `客服用户操作失败: ${error}`); + } finally { + setIsRunning(false); + } + }; + + const runGroupExample = async () => { + setIsRunning(true); + addLog("info", "开始群组操作示例..."); + + try { + // 模拟从接口获取的群组数据 + const serverGroup = { + id: Date.now(), // 模拟服务器返回的ID + wechatAccountId: 1, + tenantId: 1, + accountId: 1, + chatroomId: `chatroom_${Date.now()}`, + chatroomOwner: "owner_001", + conRemark: "测试群组", + nickname: "产品讨论群", + chatroomAvatar: "https://example.com/group-avatar.jpg", + groupId: 1, + config: { + chat: true, + }, + unreadCount: 0, + notice: "欢迎加入产品讨论群", + selfDisplyName: "群管理员", + }; + + const groupId = await groupService.createWithServerId(serverGroup); + addLog( + "success", + `创建群组成功,本地ID: ${groupId}, 服务器ID: ${serverGroup.id}`, + ); + + // 更新群组 + await groupService.update(groupId, { + unreadCount: 5, + notice: "更新的群公告", + }); + addLog("success", "群组信息更新成功"); + + await updateStats(); + } catch (error) { + addLog("error", `群组操作失败: ${error}`); + } finally { + setIsRunning(false); + } + }; + + const runContractExample = async () => { + setIsRunning(true); + addLog("info", "开始联系人操作示例..."); + + try { + // 模拟从接口获取的联系人数据 + const serverContract = { + id: Date.now(), // 模拟服务器返回的ID + wechatAccountId: 1, + wechatId: `contact_${Date.now()}`, + alias: "张三", + conRemark: "重要客户", + nickname: "张总", + quanPin: "zhangsan", + avatar: "https://example.com/contact-avatar.jpg", + gender: 1, + region: "上海", + addFrom: 1, + phone: "13900139000", + labels: ["VIP客户", "重点关注"], + signature: "专业人士", + accountId: 1, + extendFields: null, + city: "上海", + lastUpdateTime: new Date().toISOString(), + isPassed: true, + tenantId: 1, + groupId: 1, + thirdParty: null, + additionalPicture: "", + desc: "优质客户", + config: { + chat: true, + }, + lastMessageTime: Date.now(), + unreadCount: 0, + duplicate: false, + }; + + const contractId = + await contractService.createWithServerId(serverContract); + addLog( + "success", + `创建联系人成功,本地ID: ${contractId}, 服务器ID: ${serverContract.id}`, + ); + + // 查询联系人 + const searchResults = await contractService.findWhereStartsWith( + "nickname", + "张", + ); + addLog("info", `找到姓张的联系人: ${searchResults.length} 个`); + + await updateStats(); + } catch (error) { + addLog("error", `联系人操作失败: ${error}`); + } finally { + setIsRunning(false); + } + }; + + const runBatchExample = async () => { + setIsRunning(true); + addLog("info", "开始批量操作示例..."); + + try { + // 模拟从接口获取的批量联系人数据 + const batchServerContacts = Array.from({ length: 5 }, (_, i) => ({ + id: Date.now() + i, // 模拟服务器返回的ID + wechatAccountId: 1, + wechatId: `batch_${Date.now()}_${i}`, + alias: `批量用户${i + 1}`, + conRemark: "批量导入", + nickname: `用户${i + 1}`, + quanPin: `yonghu${i + 1}`, + gender: (i % 2) + 1, + region: i % 2 === 0 ? "北京" : "上海", + addFrom: 3, + phone: `1380000000${i}`, + labels: ["批量导入"], + signature: "", + accountId: 1, + extendFields: null, + lastUpdateTime: new Date().toISOString(), + isPassed: true, + tenantId: 1, + groupId: 1, + thirdParty: null, + additionalPicture: "", + desc: "批量导入的用户", + lastMessageTime: Date.now(), + unreadCount: 0, + duplicate: false, + })); + + const batchIds = + await contractService.createManyWithServerId(batchServerContacts); + addLog( + "success", + `批量创建联系人成功,创建了 ${batchIds.length} 个联系人`, + ); + + await updateStats(); + } catch (error) { + addLog("error", `批量操作失败: ${error}`); + } finally { + setIsRunning(false); + } + }; + + const clearAllData = async () => { + Modal.confirm({ + title: "确认清空数据", + content: "确定要清空所有测试数据吗?此操作不可恢复!", + okText: "确定", + cancelText: "取消", + onOk: async () => { + setIsRunning(true); + addLog("info", "开始清空数据..."); + try { + await db.transaction( + "rw", + [db.kfUsers, db.groups, db.contracts, db.newContractList], + async () => { + await db.kfUsers.clear(); + await db.groups.clear(); + await db.contracts.clear(); + await db.newContractList.clear(); + }, + ); + + addLog("success", "所有数据清空成功"); + await updateStats(); + } catch (error) { + addLog("error", `清空数据失败: ${error}`); + } finally { + setIsRunning(false); + } + }, + }); + }; + + const runAllExamples = async () => { + setIsRunning(true); + addLog("info", "开始运行所有示例..."); + + try { + await DatabaseExamples.runAllExamples(); + addLog("success", "所有示例运行完成"); + await updateStats(); + } catch (error) { + addLog("error", `运行示例失败: ${error}`); + } finally { + setIsRunning(false); + } + }; + + useEffect(() => { + initDatabase(); + }, []); + + return ( +
+ + 数据库操作演示 + + + {/* 数据统计 */} + + + + } + valueStyle={{ color: "#1890ff" }} + /> + + + + + } + valueStyle={{ color: "#52c41a" }} + /> + + + + + } + valueStyle={{ color: "#faad14" }} + /> + + + + + } + valueStyle={{ color: "#722ed1" }} + /> + + + + + {/* 操作按钮 */} + + + + + + + + + + + + + {/* 运行状态 */} + {isRunning && ( + } + style={{ marginBottom: "24px" }} + /> + )} + + {/* 日志显示 */} + } + onClick={() => setLogs([])} + size="small" + > + 清空日志 + + } + > +
+ {logs.length === 0 ? ( +
+ 暂无日志 +
+ ) : ( + + {logs.map(log => ( + + {log.message} + + {log.timestamp} + +
+ } + type={ + log.type === "success" + ? "success" + : log.type === "error" + ? "error" + : "info" + } + showIcon + style={{ margin: 0 }} + /> + ))} + + )} +
+ + + ); +}; + +export default DatabaseTestPage; diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx index da0dea8b..710d399a 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx @@ -24,7 +24,7 @@ import { CheckOutlined, } from "@ant-design/icons"; import { ContractData } from "@/pages/pc/ckbox/data"; -import { useCkChatStore } from "@/store/module/ckchat"; +import { useCkChatStore } from "@/store/module/ckchat/ckchat"; import styles from "./Person.module.scss"; const { Sider } = Layout; diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index 037edf5f..6402f78e 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -40,7 +40,7 @@ import { clearUnreadCount, getMessages } from "@/pages/pc/ckbox/api"; import styles from "./ChatWindow.module.scss"; import { useWebSocketStore, WebSocketMessage } from "@/store/module/websocket"; import { formatWechatTime } from "@/utils/common"; -import { useCkChatStore } from "@/store/module/ckchat"; +import { useCkChatStore } from "@/store/module/ckchat/ckchat"; import Person from "./components/Person"; const { Header, Content, Footer } = Layout; const { TextArea } = Input; diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx index fe0fe10d..730f3ede 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx @@ -2,7 +2,7 @@ import React, { useState, useCallback, useEffect } from "react"; import { List, Avatar, Collapse, Button } from "antd"; import type { CollapseProps } from "antd"; import styles from "./WechatFriends.module.scss"; -import { useCkChatStore } from "@/store/module/ckchat"; +import { useCkChatStore } from "@/store/module/ckchat/ckchat"; import { ContractData, GroupData } from "@/pages/pc/ckbox/data"; interface WechatFriendsProps { diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx index 4c3f88ab..98c5c712 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx @@ -10,7 +10,7 @@ import { ContractData, GroupData } from "@/pages/pc/ckbox/data"; import WechatFriends from "./WechatFriends"; import MessageList from "./MessageList/index"; import styles from "./SidebarMenu.module.scss"; -import { getChatSessions } from "@/store/module/ckchat"; +import { getChatSessions } from "@/store/module/ckchat/ckchat"; interface SidebarMenuProps { contracts: ContractData[] | GroupData[]; diff --git a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx index 387af936..dec9d17e 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { Avatar, Badge, Tooltip } from "antd"; import styles from "./VerticalUserList.module.scss"; -import { useCkChatStore, asyncKfSelected } from "@/store/module/ckchat"; +import { useCkChatStore, asyncKfSelected } from "@/store/module/ckchat/ckchat"; import { TeamOutlined } from "@ant-design/icons"; const VerticalUserList: React.FC = () => { diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx index 408f1ea5..c8f50eff 100644 --- a/Cunkebao/src/pages/pc/ckbox/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/index.tsx @@ -7,7 +7,7 @@ import SidebarMenu from "./components/SidebarMenu/index"; import VerticalUserList from "./components/VerticalUserList"; import PageSkeleton from "./components/Skeleton"; import styles from "./index.module.scss"; -import { addChatSession } from "@/store/module/ckchat"; +import { addChatSession } from "@/store/module/ckchat/ckchat"; const { Header, Content, Sider } = Layout; import { chatInitAPIdata } from "./main"; import { KfUserListData, GroupData, ContractData } from "@/pages/pc/ckbox/data"; diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts index c06209fd..ff62d021 100644 --- a/Cunkebao/src/pages/pc/ckbox/main.ts +++ b/Cunkebao/src/pages/pc/ckbox/main.ts @@ -4,7 +4,7 @@ import { asyncContractList, asyncChatSessions, asyncNewContractList, -} from "@/store/module/ckchat"; +} from "@/store/module/ckchat/ckchat"; import { useWebSocketStore } from "@/store/module/websocket"; import { diff --git a/Cunkebao/src/router/module/test.tsx b/Cunkebao/src/router/module/test.tsx index ab416284..53dedef3 100644 --- a/Cunkebao/src/router/module/test.tsx +++ b/Cunkebao/src/router/module/test.tsx @@ -1,6 +1,7 @@ import SelectTest from "@/pages/mobile/test/select"; import TestIndex from "@/pages/mobile/test/index"; import UploadTest from "@/pages/mobile/test/upload"; +import DbTest from "@/pages/mobile/test/db"; import UpdateNotificationTest from "@/pages/mobile/test/update-notification"; import IframeDebugPage from "@/pages/iframe"; import { DEV_FEATURES } from "@/utils/env"; @@ -33,6 +34,11 @@ const componentTestRoutes = DEV_FEATURES.SHOW_TEST_PAGES element: , auth: false, // 不需要认证,方便调试 }, + { + path: "/test/db", + element: , + auth: false, // 不需要认证,方便调试 + }, ] : []; diff --git a/Cunkebao/src/store/module/ckchat.ts b/Cunkebao/src/store/module/ckchat.ts index 591a2bf1..ed62f71a 100644 --- a/Cunkebao/src/store/module/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat.ts @@ -1,5 +1,5 @@ import { createPersistStore } from "@/store/createPersistStore"; -import { CkChatState, CkUserInfo, CkTenant } from "./ckchat.data"; +import { CkChatState, CkUserInfo, CkTenant } from "./ckchat/ckchat.data"; import { ContractData, GroupData, diff --git a/Cunkebao/src/store/module/ckchat.data.ts b/Cunkebao/src/store/module/ckchat/ckchat.data.ts similarity index 100% rename from Cunkebao/src/store/module/ckchat.data.ts rename to Cunkebao/src/store/module/ckchat/ckchat.data.ts diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts new file mode 100644 index 00000000..591a2bf1 --- /dev/null +++ b/Cunkebao/src/store/module/ckchat/ckchat.ts @@ -0,0 +1,205 @@ +import { createPersistStore } from "@/store/createPersistStore"; +import { CkChatState, CkUserInfo, CkTenant } from "./ckchat.data"; +import { + ContractData, + GroupData, + CkAccount, + KfUserListData, +} from "@/pages/pc/ckbox/data"; + +export const useCkChatStore = createPersistStore( + set => ({ + userInfo: null, + isLoggedIn: false, + contractList: [], //联系人列表 + chatSessions: [], //聊天会话 + kfUserList: [], //客服列表 + kfSelected: 0, + newContractList: [], //联系人分组 + kfSelectedUser: () => { + const state = useCkChatStore.getState(); + return state.kfUserList.find(item => item.id === state.kfSelected); + }, + asyncKfSelected: (data: number) => { + set({ kfSelected: data }); + }, + // 异步设置会话列表 + asyncNewContractList: data => { + set({ newContractList: data }); + }, + getNewContractList: () => { + const state = useCkChatStore.getState(); + return state.newContractList; + }, + // 异步设置会话列表 + asyncChatSessions: data => { + set({ chatSessions: data }); + }, + // 异步设置联系人列表 + asyncContractList: data => { + set({ contractList: data }); + }, + // 控制终端用户列表 + getkfUserList: () => { + const state = useCkChatStore.getState(); + return state.kfUserList; + }, + asyncKfUserList: data => { + set({ kfUserList: data }); + }, + // 删除控制终端用户 + deleteCtrlUser: (userId: number) => { + set(state => ({ + kfUserList: state.kfUserList.filter(item => item.id !== userId), + })); + }, + // 更新控制终端用户 + updateCtrlUser: (user: KfUserListData) => { + set(state => ({ + kfUserList: state.kfUserList.map(item => + item.id === user.id ? user : item, + ), + })); + }, + // 清空控制终端用户列表 + clearkfUserList: () => { + set({ kfUserList: [] }); + }, + // 获取聊天会话 + getChatSessions: () => { + const state = useCkChatStore.getState(); + return state.chatSessions; + }, + // 添加聊天会话 + addChatSession: (session: ContractData | GroupData) => { + set(state => { + // 检查是否已存在相同id的会话 + const exists = state.chatSessions.some(item => item.id === session.id); + // 如果已存在则不添加,否则添加到列表中 + return { + chatSessions: exists + ? state.chatSessions + : [...state.chatSessions, session as ContractData | GroupData], + }; + }); + }, + // 更新聊天会话 + updateChatSession: (session: ContractData | GroupData) => { + set(state => ({ + chatSessions: state.chatSessions.map(item => + item.id === session.id ? session : item, + ), + })); + }, + // 删除聊天会话 + deleteChatSession: (sessionId: string) => { + set(state => ({ + chatSessions: state.chatSessions.filter(item => item.id !== sessionId), + })); + }, + // 设置用户信息 + setUserInfo: (userInfo: CkUserInfo) => { + set({ userInfo, isLoggedIn: true }); + }, + + // 清除用户信息 + clearUserInfo: () => { + set({ userInfo: null, isLoggedIn: false }); + }, + + // 更新账户信息 + updateAccount: (account: Partial) => { + set(state => ({ + userInfo: state.userInfo + ? { + ...state.userInfo, + account: { ...state.userInfo.account, ...account }, + } + : null, + })); + }, + + // 更新租户信息 + updateTenant: (tenant: Partial) => { + set(state => ({ + userInfo: state.userInfo + ? { + ...state.userInfo, + tenant: { ...state.userInfo.tenant, ...tenant }, + } + : null, + })); + }, + + // 获取账户ID + getAccountId: () => { + const state = useCkChatStore.getState(); + return Number(state.userInfo?.account?.id) || null; + }, + + // 获取租户ID + getTenantId: () => { + const state = useCkChatStore.getState(); + return state.userInfo?.tenant?.id || null; + }, + + // 获取账户名称 + getAccountName: () => { + const state = useCkChatStore.getState(); + return ( + state.userInfo?.account?.realName || + state.userInfo?.account?.userName || + null + ); + }, + + // 获取租户名称 + getTenantName: () => { + const state = useCkChatStore.getState(); + return state.userInfo?.tenant?.name || null; + }, + }), + { + name: "ckchat-store", + partialize: state => ({ + userInfo: state.userInfo, + isLoggedIn: state.isLoggedIn, + }), + onRehydrateStorage: () => state => { + // console.log("CkChat store hydrated:", state); + }, + }, +); + +// 导出便捷的获取方法 +export const getCkAccountId = () => useCkChatStore.getState().getAccountId(); +export const getCkTenantId = () => useCkChatStore.getState().getTenantId(); +export const getCkAccountName = () => + useCkChatStore.getState().getAccountName(); +export const getCkTenantName = () => useCkChatStore.getState().getTenantName(); +export const getChatSessions = () => + useCkChatStore.getState().getChatSessions(); +export const addChatSession = (session: ContractData | GroupData) => + useCkChatStore.getState().addChatSession(session); +export const updateChatSession = (session: ContractData | GroupData) => + useCkChatStore.getState().updateChatSession(session); +export const deleteChatSession = (sessionId: string) => + useCkChatStore.getState().deleteChatSession(sessionId); +export const getkfUserList = () => useCkChatStore.getState().kfUserList; +export const addCtrlUser = (user: KfUserListData) => + useCkChatStore.getState().addCtrlUser(user); +export const deleteCtrlUser = (userId: number) => + useCkChatStore.getState().deleteCtrlUser(userId); +export const updateCtrlUser = (user: KfUserListData) => + useCkChatStore.getState().updateCtrlUser(user); +export const asyncKfUserList = (data: KfUserListData[]) => + useCkChatStore.getState().asyncKfUserList(data); +export const asyncContractList = (data: ContractData[]) => + useCkChatStore.getState().asyncContractList(data); +export const asyncChatSessions = (data: ContractData[]) => + useCkChatStore.getState().asyncChatSessions(data); +export const asyncNewContractList = ( + data: { groupName: string; contacts: any[] }[], +) => useCkChatStore.getState().asyncNewContractList(data); +export const asyncKfSelected = (data: number) => + useCkChatStore.getState().asyncKfSelected(data); diff --git a/Cunkebao/src/store/module/websocket.ts b/Cunkebao/src/store/module/websocket.ts index 397733c9..5c21f900 100644 --- a/Cunkebao/src/store/module/websocket.ts +++ b/Cunkebao/src/store/module/websocket.ts @@ -1,7 +1,7 @@ import { createPersistStore } from "@/store/createPersistStore"; import { Toast } from "antd-mobile"; import { useUserStore } from "./user"; -import { useCkChatStore } from "@/store/module/ckchat"; +import { useCkChatStore } from "@/store/module/ckchat/ckchat"; const { getAccountId } = useCkChatStore.getState(); // WebSocket消息类型 diff --git a/Cunkebao/src/utils/common.ts b/Cunkebao/src/utils/common.ts index fd740223..b68934bb 100644 --- a/Cunkebao/src/utils/common.ts +++ b/Cunkebao/src/utils/common.ts @@ -113,9 +113,3 @@ export function getSafeAreaHeight() { // 3. 默认值 return 0; } -// 设置全局 CSS 变量 -export function initSafeArea() { - const root = document.documentElement; - const height = getSafeAreaHeight(); - root.style.setProperty("--safe-area-top", `${height}px`); -} diff --git a/Cunkebao/src/utils/db-examples.ts b/Cunkebao/src/utils/db-examples.ts index daa7d71b..4387431e 100644 --- a/Cunkebao/src/utils/db-examples.ts +++ b/Cunkebao/src/utils/db-examples.ts @@ -1,508 +1,530 @@ -// 数据库使用示例 import { db, - userService, - messageService, - chatRoomService, - settingService, - userBusinessService, - messageBusinessService, - settingBusinessService, - DatabaseUtils, - type User, - type Message, - type ChatRoom, - type Setting, + kfUserService, + groupService, + contractService, + DatabaseService, + NewContactListData, } from "./db"; +import { KfUserListData, GroupData, ContractData } from "@/pages/pc/ckbox/data"; -// ============= 基础 CRUD 操作示例 ============= +// 创建 newContractList 服务实例 +const newContactListService = new DatabaseService(db.newContractList); -// 1. 用户操作示例 -export const userExamples = { - // 创建用户 - async createUser() { - const userId = await userService.create({ - name: "张三", - email: "zhangsan@example.com", - status: "active", - }); - console.log("创建用户成功,ID:", userId); - return userId; - }, - - // 批量创建用户 - async createMultipleUsers() { - const userIds = await userService.createMany([ - { name: "李四", email: "lisi@example.com", status: "active" }, - { name: "王五", email: "wangwu@example.com", status: "inactive" }, - { name: "赵六", email: "zhaoliu@example.com", status: "active" }, - ]); - console.log("批量创建用户成功,IDs:", userIds); - return userIds; - }, - - // 查询用户 - async findUsers() { - // 查询所有用户 - const allUsers = await userService.findAll(); - console.log("所有用户:", allUsers); - - // 根据ID查询 - const user = await userService.findById(1); - console.log("ID为1的用户:", user); - - // 条件查询 - const activeUsers = await userService.findWhere("status", "active"); - console.log("活跃用户:", activeUsers); - - return { allUsers, user, activeUsers }; - }, - - // 分页查询 - async paginateUsers() { - const result = await userService.paginate(1, 5); // 第1页,每页5条 - console.log("分页结果:", result); - return result; - }, - - // 更新用户 - async updateUser() { - const count = await userService.update(1, { - name: "张三(已更新)", - status: "inactive", - }); - console.log("更新用户数量:", count); - return count; - }, - - // 删除用户 - async deleteUser() { - await userService.delete(1); - console.log("删除用户成功"); - }, -}; - -// 2. 消息操作示例 -export const messageExamples = { - // 创建消息 - async createMessage() { - const messageId = await messageService.create({ - userId: 1, - content: "这是一条测试消息", - type: "text", - isRead: false, - }); - console.log("创建消息成功,ID:", messageId); - return messageId; - }, - - // 查询未读消息 - async findUnreadMessages() { - const unreadMessages = await messageService.findWhere("isRead", false); - console.log("未读消息:", unreadMessages); - return unreadMessages; - }, - - // 标记消息为已读 - async markMessageAsRead() { - const count = await messageService.update(1, { isRead: true }); - console.log("标记已读消息数量:", count); - return count; - }, - - // 获取最近消息 - async getRecentMessages() { - const recentMessages = await messageService.findAllSorted( - "createdAt", - "desc", - ); - console.log("最近消息:", recentMessages.slice(0, 10)); - return recentMessages.slice(0, 10); - }, -}; - -// 3. 设置操作示例 -export const settingExamples = { - // 保存设置 - async saveSetting() { - const settingId = await settingService.create({ - key: "theme", - value: "dark", - category: "appearance", - }); - console.log("保存设置成功,ID:", settingId); - return settingId; - }, - - // 批量保存设置 - async saveMultipleSettings() { - const settingIds = await settingService.createMany([ - { key: "language", value: "zh-CN", category: "general" }, - { key: "fontSize", value: 14, category: "appearance" }, - { key: "autoSave", value: true, category: "editor" }, - ]); - console.log("批量保存设置成功,IDs:", settingIds); - return settingIds; - }, - - // 查询设置 - async getSettings() { - // 查询所有设置 - const allSettings = await settingService.findAll(); - console.log("所有设置:", allSettings); - - // 按分类查询 - const appearanceSettings = await settingService.findWhere( - "category", - "appearance", - ); - console.log("外观设置:", appearanceSettings); - - return { allSettings, appearanceSettings }; - }, -}; - -// ============= 业务方法示例 ============= - -// 4. 用户业务操作示例 -export const userBusinessExamples = { - // 根据邮箱查找用户 - async findUserByEmail() { - const user = await userBusinessService.findByEmail("zhangsan@example.com"); - console.log("根据邮箱查找的用户:", user); - return user; - }, - - // 查找活跃用户 - async findActiveUsers() { - const activeUsers = await userBusinessService.findActiveUsers(); - console.log("活跃用户列表:", activeUsers); - return activeUsers; - }, - - // 搜索用户 - async searchUsers() { - const users = await userBusinessService.searchByName("张"); - console.log("搜索结果:", users); - return users; - }, - - // 更新用户状态 - async updateUserStatus() { - const count = await userBusinessService.updateStatus(1, "active"); - console.log("更新用户状态数量:", count); - return count; - }, -}; - -// 5. 消息业务操作示例 -export const messageBusinessExamples = { - // 查找用户消息 - async findUserMessages() { - const messages = await messageBusinessService.findByUserId(1); - console.log("用户消息:", messages); - return messages; - }, - - // 查找未读消息 - async findUnreadMessages() { - const unreadMessages = await messageBusinessService.findUnreadMessages(); - console.log("未读消息:", unreadMessages); - return unreadMessages; - }, - - // 标记消息为已读 - async markAsRead() { - const count = await messageBusinessService.markAsRead(1); - console.log("标记已读数量:", count); - return count; - }, - - // 标记用户所有消息为已读 - async markAllAsRead() { - const count = await messageBusinessService.markAllAsRead(1); - console.log("标记用户所有消息已读数量:", count); - return count; - }, - - // 获取最近消息 - async getRecentMessages() { - const messages = await messageBusinessService.getRecentMessages(20); - console.log("最近20条消息:", messages); - return messages; - }, -}; - -// 6. 设置业务操作示例 -export const settingBusinessExamples = { - // 获取设置值 - async getSetting() { - const theme = await settingBusinessService.getSetting("theme"); - console.log("主题设置:", theme); - return theme; - }, - - // 设置值 - async setSetting() { - const settingId = await settingBusinessService.setSetting( - "theme", - "light", - "appearance", - ); - console.log("设置主题成功,ID:", settingId); - return settingId; - }, - - // 获取分类设置 - async getSettingsByCategory() { - const settings = - await settingBusinessService.getSettingsByCategory("appearance"); - console.log("外观设置:", settings); - return settings; - }, - - // 删除设置 - async deleteSetting() { - await settingBusinessService.deleteSetting("oldSetting"); - console.log("删除设置成功"); - }, -}; - -// ============= 数据库工具示例 ============= - -// 7. 数据库工具操作示例 -export const databaseUtilsExamples = { - // 获取数据库统计信息 - async getStats() { - const stats = await DatabaseUtils.getStats(); - console.log("数据库统计:", stats); - return stats; - }, - - // 健康检查 - async healthCheck() { - const health = await DatabaseUtils.healthCheck(); - console.log("数据库健康状态:", health); - return health; - }, - - // 导出数据 - async exportData() { - const jsonData = await DatabaseUtils.exportData(); - console.log("导出的数据长度:", jsonData.length); - return jsonData; - }, - - // 备份到文件 - async backupToFile() { - await DatabaseUtils.backupToFile(); - console.log("备份文件下载成功"); - }, - - // 从文件恢复(需要文件输入) - async restoreFromFile(file: File) { +/** + * 数据库使用示例 + */ +export class DatabaseExamples { + /** + * 初始化数据库 + */ + static async initDatabase() { try { - await DatabaseUtils.restoreFromFile(file); - console.log("数据恢复成功"); + await db.open(); + console.log("数据库初始化成功"); } catch (error) { - console.error("数据恢复失败:", error); + console.error("数据库初始化失败:", error); } - }, + } - // 清空所有数据 - async clearAllData() { - const confirmed = confirm("确定要清空所有数据吗?此操作不可恢复!"); - if (confirmed) { - await DatabaseUtils.clearAllData(); - console.log("所有数据已清空"); - } - }, -}; + /** + * 客服用户操作示例 + */ + static async kfUserExamples() { + console.log("=== 客服用户操作示例 ==="); -// ============= 高级查询示例 ============= - -// 8. 高级查询示例 -export const advancedQueryExamples = { - // 复杂条件查询 - async complexQuery() { - // 查询活跃用户的未读消息 - const activeUsers = await db.users - .where("status") - .equals("active") - .toArray(); - const activeUserIds = activeUsers.map(user => user.id!); - const unreadMessages = await db.messages - .where("userId") - .anyOf(activeUserIds) - .and(msg => !msg.isRead) - .toArray(); - - console.log("活跃用户的未读消息:", unreadMessages); - return unreadMessages; - }, - - // 统计查询 - async statisticsQuery() { - const stats = { - totalUsers: await db.users.count(), - activeUsers: await db.users.where("status").equals("active").count(), - totalMessages: await db.messages.count(), - unreadMessages: await db.messages.where("isRead").equals(false).count(), - messagesThisWeek: await db.messages - .where("createdAt") - .above(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)) - .count(), + // 模拟从接口获取的数据(包含服务器ID) + const serverKfUser = { + id: 1001, // 服务器返回的ID + tenantId: 1, + wechatId: "test_wechat_001", + nickname: "测试客服", + alias: "客服小王", + avatar: "https://example.com/avatar.jpg", + gender: 1, + region: "北京", + signature: "专业客服,为您服务", + bindQQ: "123456789", + bindEmail: "test@example.com", + bindMobile: "13800138000", + createTime: new Date().toISOString(), + currentDeviceId: 1, + isDeleted: false, + deleteTime: "", + groupId: 1, + memo: "优秀客服", + wechatVersion: "8.0.0", + labels: ["VIP客服", "专业"], + lastUpdateTime: new Date().toISOString(), + isOnline: true, }; - console.log("统计信息:", stats); - return stats; - }, - - // 事务操作 - async transactionExample() { try { - await db.transaction("rw", [db.users, db.messages], async () => { - // 创建用户 - const userId = await db.users.add({ - name: "事务用户", - email: "transaction@example.com", - status: "active", - }); - - // 为该用户创建欢迎消息 - await db.messages.add({ - userId, - content: "欢迎使用我们的应用!", - type: "text", - isRead: false, - }); - - console.log("事务执行成功"); - }); - } catch (error) { - console.error("事务执行失败:", error); - } - }, -}; - -// ============= 完整使用流程示例 ============= - -// 9. 完整的应用场景示例 -export const fullScenarioExample = { - // 模拟用户注册和使用流程 - async simulateUserFlow() { - console.log("=== 开始模拟用户流程 ==="); - - try { - // 1. 用户注册 - const userId = await userBusinessService.create({ - name: "新用户", - email: "newuser@example.com", - status: "active", - }); - console.log("1. 用户注册成功,ID:", userId); - - // 2. 设置用户偏好 - await settingBusinessService.setSetting( - "theme", - "dark", - "user-" + userId, - ); - await settingBusinessService.setSetting( - "language", - "zh-CN", - "user-" + userId, - ); - console.log("2. 用户偏好设置完成"); - - // 3. 发送欢迎消息 - const messageId = await messageBusinessService.create({ + // 使用新方法添加客服用户(处理服务器ID映射) + const userId = await kfUserService.createWithServerId(serverKfUser); + console.log( + "创建客服用户成功,本地ID:", userId, - content: "欢迎加入我们的平台!", - type: "text", - isRead: false, + "服务器ID:", + serverKfUser.id, + ); + + // 查询单个用户(按本地ID) + const user = await kfUserService.findById(userId); + console.log("查询用户:", user); + + // 根据服务器ID查询 + const userByServerId = await kfUserService.findByServerId(1001); + console.log("根据服务器ID查询用户:", userByServerId); + + // 按条件查询 + const onlineUsers = await kfUserService.findWhere("isOnline", true); + console.log("在线用户数量:", onlineUsers.length); + + // 按租户查询 + const tenantUsers = await kfUserService.findWhere("tenantId", 1); + console.log("租户用户数量:", tenantUsers.length); + + // 更新用户信息 + await kfUserService.update(userId, { + nickname: "更新后的昵称", + lastUpdateTime: new Date().toISOString(), }); - console.log("3. 欢迎消息发送成功,ID:", messageId); + console.log("更新用户成功"); - // 4. 用户查看消息 - const userMessages = await messageBusinessService.findByUserId(userId); - console.log("4. 用户消息列表:", userMessages); - - // 5. 标记消息为已读 - await messageBusinessService.markAsRead(messageId); - console.log("5. 消息已标记为已读"); - - // 6. 获取用户统计信息 - const userStats = { - totalMessages: await messageService.countWhere("userId", userId), - unreadMessages: await db.messages - .where("userId") - .equals(userId) - .and(msg => !msg.isRead) - .count(), - }; - console.log("6. 用户统计信息:", userStats); - - console.log("=== 用户流程模拟完成 ==="); - return { userId, messageId, userStats }; + // 分页查询 + const paginatedUsers = await kfUserService.findWithPagination(1, 5); + console.log("分页查询结果:", paginatedUsers); } catch (error) { - console.error("用户流程模拟失败:", error); - throw error; + console.error("客服用户操作失败:", error); } - }, -}; + } -// 导出所有示例 -export const allExamples = { - userExamples, - messageExamples, - settingExamples, - userBusinessExamples, - messageBusinessExamples, - settingBusinessExamples, - databaseUtilsExamples, - advancedQueryExamples, - fullScenarioExample, -}; + /** + * 群组操作示例 + */ + static async groupExamples() { + console.log("=== 群组操作示例 ==="); -// 快速测试函数 -export async function quickTest() { - console.log("=== 开始快速测试 ==="); + // 模拟从接口获取的群组数据 + const serverGroup = { + id: 2001, // 服务器返回的ID + wechatAccountId: 1, + tenantId: 1, + accountId: 1, + chatroomId: "chatroom_001", + chatroomOwner: "owner_001", + conRemark: "测试群组", + nickname: "产品讨论群", + chatroomAvatar: "https://example.com/group-avatar.jpg", + groupId: 1, + config: { + chat: true, + }, + unreadCount: 0, + notice: "欢迎加入产品讨论群", + selfDisplyName: "群管理员", + }; - try { - // 健康检查 - const health = await DatabaseUtils.healthCheck(); - console.log("数据库健康状态:", health); + try { + // 使用新方法创建群组 + const groupId = await groupService.createWithServerId(serverGroup); + console.log( + "创建群组成功,本地ID:", + groupId, + "服务器ID:", + serverGroup.id, + ); - // 创建测试数据 - const userId = await userBusinessService.create({ - name: "测试用户", - email: "test@example.com", - status: "active", - }); + // 查询群组 + const groups = await groupService.findWhere("tenantId", 1); + console.log("租户群组数量:", groups.length); - const messageId = await messageBusinessService.create({ - userId, - content: "测试消息", - type: "text", - isRead: false, - }); + // 查询有未读消息的群组 + const unreadGroups = await groupService.findWhereGreaterThan( + "unreadCount", + 0, + ); + console.log("有未读消息的群组:", unreadGroups.length); - // 查询测试 - const user = await userBusinessService.findById(userId); - const message = await messageBusinessService.findById(messageId); + // 批量更新群组 + await groupService.updateMany([ + { + id: groupId, + data: { unreadCount: 5, notice: "更新的群公告" }, + }, + ]); + console.log("批量更新群组成功"); + } catch (error) { + console.error("群组操作失败:", error); + } + } - console.log("创建的用户:", user); - console.log("创建的消息:", message); + /** + * 联系人操作示例 + */ + static async contractExamples() { + console.log("=== 联系人操作示例 ==="); - // 统计信息 - const stats = await DatabaseUtils.getStats(); - console.log("数据库统计:", stats); + // 模拟从接口获取的联系人数据 + const serverContract = { + id: 3001, // 服务器返回的ID + wechatAccountId: 1, + wechatId: "contact_001", + alias: "张三", + conRemark: "重要客户", + nickname: "张总", + quanPin: "zhangsan", + avatar: "https://example.com/contact-avatar.jpg", + gender: 1, + region: "上海", + addFrom: 1, + phone: "13900139000", + labels: ["VIP客户", "重点关注"], + signature: "专业人士", + accountId: 1, + extendFields: null, + city: "上海", + lastUpdateTime: new Date().toISOString(), + isPassed: true, + tenantId: 1, + groupId: 1, + thirdParty: null, + additionalPicture: "", + desc: "优质客户", + config: { + chat: true, + }, + lastMessageTime: Date.now(), + unreadCount: 0, + duplicate: false, + }; - console.log("=== 快速测试完成 ==="); - return { userId, messageId, stats }; - } catch (error) { - console.error("快速测试失败:", error); - throw error; + try { + // 使用新方法创建联系人 + const contractId = + await contractService.createWithServerId(serverContract); + console.log( + "创建联系人成功,本地ID:", + contractId, + "服务器ID:", + serverContract.id, + ); + + // 模糊查询(按昵称开头) + const searchResults = await contractService.findWhereStartsWith( + "nickname", + "张", + ); + console.log("姓张的联系人:", searchResults.length); + + // 多条件查询 + const vipContacts = await contractService.findWhereMultiple([ + { field: "tenantId", operator: "equals", value: 1 }, + { field: "isPassed", operator: "equals", value: true }, + ]); + console.log("VIP联系人数量:", vipContacts.length); + + // 按更新时间排序 + const sortedContacts = await contractService.findAllSorted( + "lastUpdateTime", + "desc", + ); + console.log("最近更新的联系人:", sortedContacts.slice(0, 3)); + + // 统计联系人数量 + const totalCount = await contractService.count(); + const tenantCount = await contractService.countWhere("tenantId", 1); + console.log(`总联系人数: ${totalCount}, 租户联系人数: ${tenantCount}`); + } catch (error) { + console.error("联系人操作失败:", error); + } + } + + /** + * 新联系人列表操作示例 + */ + static async newContactListExamples() { + console.log("=== 新联系人列表操作示例 ==="); + + // 模拟从接口获取的联系人分组数据 + const serverContactGroup = { + id: 4001, // 服务器返回的ID + groupName: "已购买", + contacts: [ + { + id: 3002, // 联系人的服务器ID + wechatAccountId: 1, + wechatId: "buyer_001", + alias: "李四", + conRemark: "已购买客户", + nickname: "李总", + quanPin: "lisi", + gender: 1, + region: "深圳", + addFrom: 2, + phone: "13700137000", + labels: ["已购买"], + signature: "企业家", + accountId: 1, + extendFields: null, + lastUpdateTime: new Date().toISOString(), + isPassed: true, + tenantId: 1, + groupId: 2, + thirdParty: null, + additionalPicture: "", + desc: "已购买产品的客户", + lastMessageTime: Date.now(), + unreadCount: 0, + duplicate: false, + }, + ], + }; + + try { + // 使用新方法创建联系人分组 + const groupId = + await newContactListService.createWithServerId(serverContactGroup); + console.log( + "创建联系人分组成功,本地ID:", + groupId, + "服务器ID:", + serverContactGroup.id, + ); + + // 查询所有分组 + const allGroups = await newContactListService.findAll(); + console.log("所有联系人分组:", allGroups); + + // 按分组名称查询 + const purchasedGroups = await newContactListService.findWhere( + "groupName", + "已购买", + ); + console.log("已购买分组:", purchasedGroups); + + // 更新分组(添加更多联系人) + const updatedContacts = [ + ...serverContactGroup.contacts, + { + id: 3003, + wechatAccountId: 1, + conRemark: "已购买客户", + quanPin: "wangwu", + gender: 1, + region: "广州", + addFrom: 2, + phone: "13800000003", + labels: ["已购买"], + signature: "企业家", + accountId: 1, + extendFields: null, + lastUpdateTime: new Date().toISOString(), + isPassed: true, + tenantId: 1, + groupId: 2, + thirdParty: null, + additionalPicture: "", + desc: "已购买产品的客户", + lastMessageTime: Date.now(), + unreadCount: 0, + duplicate: false, + wechatId: "buyer_002", + alias: "王五", + nickname: "王总", + } as ContractData, + ]; + + await newContactListService.update(groupId, { + contacts: updatedContacts, + }); + console.log("更新联系人分组成功"); + } catch (error) { + console.error("新联系人列表操作失败:", error); + } + } + + /** + * 高级查询示例 + */ + static async advancedQueryExamples() { + console.log("=== 高级查询示例 ==="); + + try { + // 范围查询:查询最近一周更新的联系人 + const oneWeekAgo = new Date( + Date.now() - 7 * 24 * 60 * 60 * 1000, + ).toISOString(); + const now = new Date().toISOString(); + const recentContacts = await contractService.findWhereBetween( + "lastUpdateTime", + oneWeekAgo, + now, + ); + console.log("最近一周更新的联系人:", recentContacts.length); + + // IN 查询:查询指定租户的数据 + const tenantIds = [1, 2, 3]; + const multiTenantUsers = await kfUserService.findWhereIn( + "tenantId", + tenantIds, + ); + console.log("多租户用户数量:", multiTenantUsers.length); + + // 不等于查询:查询非删除状态的用户 + const activeUsers = await kfUserService.findWhereNot("isDeleted", true); + console.log("活跃用户数量:", activeUsers.length); + + // 复合条件查询:在线且有分组的用户 + const onlineGroupUsers = await kfUserService.findWhereMultiple([ + { field: "isOnline", operator: "equals", value: true }, + { field: "groupId", operator: "above", value: 0 }, + ]); + console.log("在线且有分组的用户:", onlineGroupUsers.length); + } catch (error) { + console.error("高级查询失败:", error); + } + } + + /** + * 批量操作示例 + */ + static async batchOperationExamples() { + console.log("=== 批量操作示例 ==="); + + try { + // 模拟从接口获取的批量联系人数据 + const batchServerContacts = [ + { + id: 3003, // 服务器ID + wechatAccountId: 1, + wechatId: "batch_001", + alias: "批量用户1", + conRemark: "批量导入", + nickname: "用户1", + quanPin: "yonghu1", + gender: 1, + region: "北京", + addFrom: 3, + phone: "13800000001", + labels: ["批量导入"], + signature: "", + accountId: 1, + extendFields: null, + lastUpdateTime: new Date().toISOString(), + isPassed: true, + tenantId: 1, + groupId: 1, + thirdParty: null, + additionalPicture: "", + desc: "批量导入的用户", + lastMessageTime: Date.now(), + unreadCount: 0, + duplicate: false, + }, + { + id: 3004, // 服务器ID + wechatAccountId: 1, + wechatId: "batch_002", + alias: "批量用户2", + conRemark: "批量导入", + nickname: "用户2", + quanPin: "yonghu2", + gender: 2, + region: "上海", + addFrom: 3, + phone: "13800000002", + labels: ["批量导入"], + signature: "", + accountId: 1, + extendFields: null, + lastUpdateTime: new Date().toISOString(), + isPassed: true, + tenantId: 1, + groupId: 1, + thirdParty: null, + additionalPicture: "", + desc: "批量导入的用户", + lastMessageTime: Date.now(), + unreadCount: 0, + duplicate: false, + }, + ]; + + const batchIds = + await contractService.createManyWithServerId(batchServerContacts); + console.log("批量创建联系人成功,本地IDs:", batchIds); + + // 批量更新 + const updateData = batchIds.map(id => ({ + id, + data: { + desc: "批量更新的描述", + lastUpdateTime: new Date().toISOString(), + }, + })); + + await contractService.updateMany(updateData); + console.log("批量更新成功"); + } catch (error) { + console.error("批量操作失败:", error); + } + } + + /** + * 数据清理示例 + */ + static async dataCleanupExamples() { + console.log("=== 数据清理示例 ==="); + + try { + // 清理测试数据(谨慎使用) + const testUsers = await kfUserService.findWhereStartsWith( + "wechatId", + "test_", + ); + console.log("找到测试用户:", testUsers.length); + + // 删除特定测试数据 + for (const user of testUsers) { + if (user.id) { + await kfUserService.delete(user.id); + } + } + console.log("清理测试用户完成"); + + // 统计清理后的数据 + const remainingCount = await kfUserService.count(); + console.log("剩余用户数量:", remainingCount); + } catch (error) { + console.error("数据清理失败:", error); + } + } + + /** + * 运行所有示例 + */ + static async runAllExamples() { + console.log("开始运行数据库操作示例..."); + + await this.initDatabase(); + await this.kfUserExamples(); + await this.groupExamples(); + await this.contractExamples(); + await this.newContactListExamples(); + await this.advancedQueryExamples(); + await this.batchOperationExamples(); + // await this.dataCleanupExamples(); // 谨慎使用 + + console.log("所有示例运行完成!"); } } + +// 导出便捷方法 +export const runDatabaseExamples = () => DatabaseExamples.runAllExamples(); + +// 如果直接运行此文件,执行示例 +if (typeof window !== "undefined" && (window as any).__RUN_DB_EXAMPLES__) { + runDatabaseExamples(); +} diff --git a/Cunkebao/src/utils/db-test.ts b/Cunkebao/src/utils/db-test.ts new file mode 100644 index 00000000..3a84f3b9 --- /dev/null +++ b/Cunkebao/src/utils/db-test.ts @@ -0,0 +1,146 @@ +/** + * 数据库版本升级测试脚本 + * 用于验证数据库版本升级逻辑是否正常工作 + */ + +import { db } from "./db"; + +// 重置数据库(完全删除并重新创建) +export async function resetDatabase() { + try { + console.log("开始重置数据库..."); + + // 关闭数据库连接 + if (db.isOpen()) { + db.close(); + } + + // 删除数据库 + await db.delete(); + console.log("旧数据库已删除"); + + // 重新打开数据库(这会创建新的数据库) + await db.open(); + + console.log("数据库重置成功!"); + console.log("当前数据库版本:", db.verno); + console.log("数据库名称:", db.name); + + return { + success: true, + version: db.verno, + tables: db.tables.map(table => table.name), + message: "数据库重置成功", + }; + } catch (error) { + console.error("数据库重置失败:", error); + return { + success: false, + error: error instanceof Error ? error.message : String(error), + message: "数据库重置失败", + }; + } +} + +// 测试数据库初始化和版本升级 +export async function testDatabaseUpgrade() { + try { + console.log("开始测试数据库初始化..."); + + // 首先尝试正常打开数据库 + try { + await db.open(); + } catch (upgradeError) { + // 如果遇到升级错误,尝试重置数据库 + if ( + upgradeError.message && + upgradeError.message.includes("primary key") + ) { + console.log("检测到主键冲突,尝试重置数据库..."); + const resetResult = await resetDatabase(); + if (!resetResult.success) { + throw new Error(`数据库重置失败: ${resetResult.error}`); + } + } else { + throw upgradeError; + } + } + + console.log("数据库初始化成功!"); + console.log("当前数据库版本:", db.verno); + console.log("数据库名称:", db.name); + + // 检查表是否存在 + const tables = db.tables.map(table => table.name); + console.log("数据库表:", tables); + + // 测试基本操作 + const testData = { + tenantId: 1, // 修正为number类型 + wechatId: "test-wechat-id", + nickname: "测试用户", + alias: "测试别名", + }; + + // 测试创建数据 + const userId = await db.kfUsers.add({ + ...testData, + id: 0, // 添加必需的id字段 + currentDeviceId: 0, + isDeleted: false, + deleteTime: "", + groupId: 0, + memo: "", // 备注信息 + wechatVersion: "", + labels: [], + lastUpdateTime: new Date().toISOString(), // 修复语法错误,使用字符串类型 + serverId: "test-server-id-001", // 提供有意义的测试值 + // 移除不属于KfUserListData接口的字段 + signature: "", + bindQQ: "", + bindEmail: "", + bindMobile: "", + bindWeixin: "", + bindAlipay: "", + bindTaobao: "", + bindJd: "", + bindDouyin: "", + bindKuaishou: "", + bindBilibili: "", + avatar: "", + gender: 0, + region: "", + createTime: new Date().toISOString(), // 使用字符串类型 + }); + console.log("创建测试用户成功,ID:", userId); + + // 测试查询数据 + const user = await db.kfUsers.get(userId); + console.log("查询测试用户:", user); + + // 清理测试数据 + await db.kfUsers.delete(userId); + console.log("清理测试数据完成"); + + return { + success: true, + version: db.verno, + tables: tables, + message: "数据库版本升级测试通过", + }; + } catch (error) { + console.error("数据库测试失败:", error); + return { + success: false, + error: error instanceof Error ? error.message : String(error), + message: "数据库版本升级测试失败", + }; + } +} + +// 如果直接运行此文件,执行测试 +if (typeof window === "undefined") { + testDatabaseUpgrade().then(result => { + console.log("测试结果:", result); + }); +} diff --git a/Cunkebao/src/utils/db.ts b/Cunkebao/src/utils/db.ts index 6f6fc9ff..2ec84c0b 100644 --- a/Cunkebao/src/utils/db.ts +++ b/Cunkebao/src/utils/db.ts @@ -1,102 +1,111 @@ +/** + * 数据库工具类 - 解决服务器ID与本地自增主键冲突问题 + * + * 问题描述: + * 接口返回的数据包含id字段,直接存储到数据库会与Dexie的自增主键(++id)产生冲突 + * + * 解决方案: + * 1. 将服务器返回的id字段映射为serverId字段存储 + * 2. 数据库使用自增的id作为主键 + * 3. 提供专门的方法处理服务器数据的存储和查询 + * + * 使用方法: + * - 存储接口数据:使用 createWithServerId() 或 createManyWithServerId() + * - 查询服务器数据:使用 findByServerId() + * - 常规操作:使用原有的 create(), findById() 等方法 + * + * 示例: + * const serverData = { id: 1001, name: '测试', ... }; // 接口返回的数据 + * const localId = await service.createWithServerId(serverData); // 存储,返回本地ID + * const data = await service.findByServerId(1001); // 根据服务器ID查询 + */ + import Dexie, { Table } from "dexie"; import { KfUserListData, GroupData, ContractData } from "@/pages/pc/ckbox/data"; -// 定义数据库表结构接口 -export interface BaseEntity { +// 扩展数据类型,添加serverId字段 +export interface KfUserWithServerId extends KfUserListData { + serverId?: number | string; // 服务器返回的原始ID +} + +export interface GroupWithServerId extends GroupData { + serverId?: number | string; // 服务器返回的原始ID +} + +export interface ContractWithServerId extends ContractData { + serverId?: number | string; // 服务器返回的原始ID +} + +// 新联系人列表数据接口 +export interface NewContactListData { id?: number; - createdAt?: Date; - updatedAt?: Date; -} - -export interface User extends BaseEntity { - name: string; - email: string; - avatar?: string; - status: "active" | "inactive"; -} - -export interface Message extends BaseEntity { - userId: number; - content: string; - type: "text" | "image" | "file"; - isRead: boolean; -} - -export interface ChatRoom extends BaseEntity { - name: string; - description?: string; - memberIds: number[]; - lastMessageAt?: Date; -} - -export interface Setting extends BaseEntity { - key: string; - value: any; - category: string; + serverId?: number | string; // 服务器返回的原始ID + groupName: string; + contacts: ContractData[] | GroupData[]; } // 数据库类 -class AppDatabase extends Dexie { - users!: Table; - messages!: Table; - chatRooms!: Table; - settings!: Table; +class CunkebaoDatabase extends Dexie { + kfUsers!: Table; + groups!: Table; + contracts!: Table; + newContractList!: Table; constructor() { super("CunkebaoDatabase"); + // 版本1:初始版本(不包含serverId字段) this.version(1).stores({ - users: "++id, name, email, status, createdAt", - messages: "++id, userId, type, isRead, createdAt", - chatRooms: "++id, name, lastMessageAt, createdAt", - settings: "++id, key, category, createdAt", + kfUsers: + "++id, tenantId, wechatId, nickname, alias, avatar, gender, region, signature, bindQQ, bindEmail, bindMobile, createTime, currentDeviceId, isDeleted, deleteTime, groupId, memo, wechatVersion, lastUpdateTime, isOnline", + groups: + "++id, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar, groupId, config, unreadCount, notice, selfDisplyName", + contracts: + "++id, wechatAccountId, wechatId, alias, conRemark, nickname, quanPin, avatar, gender, region, addFrom, phone, signature, accountId, extendFields, city, lastUpdateTime, isPassed, tenantId, groupId, thirdParty, additionalPicture, desc, config, lastMessageTime, unreadCount, duplicate", + newContractList: "++id, groupName, contacts", }); - // 自动添加时间戳 - this.users.hook("creating", (primKey, obj, trans) => { - obj.createdAt = new Date(); - obj.updatedAt = new Date(); - }); - - this.users.hook("updating", (modifications, primKey, obj, trans) => { - modifications.updatedAt = new Date(); - }); - - this.messages.hook("creating", (primKey, obj, trans) => { - obj.createdAt = new Date(); - obj.updatedAt = new Date(); - }); - - this.chatRooms.hook("creating", (primKey, obj, trans) => { - obj.createdAt = new Date(); - obj.updatedAt = new Date(); - }); - - this.settings.hook("creating", (primKey, obj, trans) => { - obj.createdAt = new Date(); - obj.updatedAt = new Date(); - }); + // 版本2:添加serverId字段支持 + this.version(2) + .stores({ + kfUsers: + "++id, serverId, tenantId, wechatId, nickname, alias, avatar, gender, region, signature, bindQQ, bindEmail, bindMobile, createTime, currentDeviceId, isDeleted, deleteTime, groupId, memo, wechatVersion, lastUpdateTime, isOnline", + groups: + "++id, serverId, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar, groupId, config, unreadCount, notice, selfDisplyName", + contracts: + "++id, serverId, wechatAccountId, wechatId, alias, conRemark, nickname, quanPin, avatar, gender, region, addFrom, phone, signature, accountId, extendFields, city, lastUpdateTime, isPassed, tenantId, groupId, thirdParty, additionalPicture, desc, config, lastMessageTime, unreadCount, duplicate", + newContractList: "++id, serverId, groupName, contacts", + }) + .upgrade(tx => { + // 数据库升级逻辑:为现有数据添加serverId字段(可选) + console.log("数据库升级到版本2:添加serverId字段支持"); + // 注意:这里不需要迁移数据,因为serverId是可选字段 + // 如果需要迁移现有数据,可以在这里添加相应逻辑 + }); } } // 创建数据库实例 -export const db = new AppDatabase(); +export const db = new CunkebaoDatabase(); -// 通用数据库操作类 -export class DatabaseService { +// 简单的数据库操作类 +export class DatabaseService { constructor(private table: Table) {} // 基础 CRUD 操作 - async create( - data: Omit, - ): Promise { + async create(data: Omit): Promise { return await this.table.add(data as T); } - async createMany( - dataList: Omit[], - ): Promise { - return await this.table.bulkAdd(dataList as T[], { allKeys: true }); + // 创建数据(处理服务器ID映射) + // 用于存储从接口获取的数据,将服务器的id字段映射为serverId,避免与数据库自增主键冲突 + async createWithServerId(data: any): Promise { + const { id, ...restData } = data; + const dataToInsert = { + ...restData, + serverId: id, // 将服务器的id映射为serverId + }; + return await this.table.add(dataToInsert as T); } async findById(id: number): Promise { @@ -107,49 +116,46 @@ export class DatabaseService { return await this.table.toArray(); } - async findByIds(ids: number[]): Promise { - return await this.table.where("id").anyOf(ids).toArray(); - } - - async update( - id: number, - data: Partial>, - ): Promise { - return await this.table.update(id, data); + async update(id: number, data: Partial): Promise { + return await this.table.update(id, data as any); } async updateMany( - updates: { id: number; data: Partial> }[], + dataList: { id: number; data: Partial }[], ): Promise { return await this.table.bulkUpdate( - updates.map(u => ({ key: u.id, changes: u.data })), + dataList.map(item => ({ + key: item.id, + changes: item.data as any, + })), ); } + async createMany(dataList: Omit[]): Promise { + return await this.table.bulkAdd(dataList as T[], { allKeys: true }); + } + + // 批量创建数据(处理服务器ID映射) + // 用于批量存储从接口获取的数据,将服务器的id字段映射为serverId + async createManyWithServerId(dataList: any[]): Promise { + const processedData = dataList.map(item => { + const { id, ...restData } = item; + return { + ...restData, + serverId: id, // 将服务器的id映射为serverId + }; + }); + return await this.table.bulkAdd(processedData as T[], { allKeys: true }); + } + async delete(id: number): Promise { await this.table.delete(id); } - async deleteMany(ids: number[]): Promise { - await this.table.bulkDelete(ids); - } - - async deleteAll(): Promise { + async clear(): Promise { await this.table.clear(); } - // 分页查询 - async paginate( - page: number = 1, - limit: number = 10, - ): Promise<{ data: T[]; total: number; page: number; limit: number }> { - const offset = (page - 1) * limit; - const total = await this.table.count(); - const data = await this.table.offset(offset).limit(limit).toArray(); - - return { data, total, page, limit }; - } - // 条件查询 async findWhere(field: keyof T, value: any): Promise { return await this.table @@ -158,6 +164,13 @@ export class DatabaseService { .toArray(); } + // 根据服务器ID查询 + // 用于根据原始的服务器ID查找数据 + async findByServerId(serverId: any): Promise { + return await this.table.where("serverId").equals(serverId).first(); + } + + // 多值查询(IN 查询) async findWhereIn(field: keyof T, values: any[]): Promise { return await this.table .where(field as string) @@ -165,6 +178,23 @@ export class DatabaseService { .toArray(); } + // 范围查询 + async findWhereBetween(field: keyof T, min: any, max: any): Promise { + return await this.table + .where(field as string) + .between(min, max) + .toArray(); + } + + // 模糊查询(以指定字符串开头) + async findWhereStartsWith(field: keyof T, prefix: string): Promise { + return await this.table + .where(field as string) + .startsWith(prefix) + .toArray(); + } + + // 不等于查询 async findWhereNot(field: keyof T, value: any): Promise { return await this.table .where(field as string) @@ -172,6 +202,68 @@ export class DatabaseService { .toArray(); } + // 大于查询 + async findWhereGreaterThan(field: keyof T, value: any): Promise { + return await this.table + .where(field as string) + .above(value) + .toArray(); + } + + // 小于查询 + async findWhereLessThan(field: keyof T, value: any): Promise { + return await this.table + .where(field as string) + .below(value) + .toArray(); + } + + // 复合条件查询 + async findWhereMultiple( + conditions: { + field: keyof T; + operator: "equals" | "above" | "below" | "startsWith"; + value: any; + }[], + ): Promise { + let collection = this.table.toCollection(); + + for (const condition of conditions) { + const { field, operator, value } = condition; + collection = collection.and(item => { + const fieldValue = (item as any)[field]; + switch (operator) { + case "equals": + return fieldValue === value; + case "above": + return fieldValue > value; + case "below": + return fieldValue < value; + case "startsWith": + return ( + typeof fieldValue === "string" && fieldValue.startsWith(value) + ); + default: + return true; + } + }); + } + + return await collection.toArray(); + } + + // 分页查询 + async findWithPagination( + page: number = 1, + limit: number = 10, + ): Promise<{ data: T[]; total: number; page: number; limit: number }> { + const offset = (page - 1) * limit; + const total = await this.table.count(); + const data = await this.table.offset(offset).limit(limit).toArray(); + + return { data, total, page, limit }; + } + // 排序查询 async findAllSorted( field: keyof T, @@ -183,264 +275,24 @@ export class DatabaseService { : await collection.toArray(); } - // 搜索功能 - async search(field: keyof T, keyword: string): Promise { - return await this.table - .where(field as string) - .startsWithIgnoreCase(keyword) - .toArray(); - } - - // 统计功能 + // 统计 async count(): Promise { return await this.table.count(); } + // 条件统计 async countWhere(field: keyof T, value: any): Promise { return await this.table .where(field as string) .equals(value) .count(); } - - // 存在性检查 - async exists(id: number): Promise { - const item = await this.table.get(id); - return !!item; - } - - async existsWhere(field: keyof T, value: any): Promise { - const count = await this.table - .where(field as string) - .equals(value) - .count(); - return count > 0; - } } // 创建各表的服务实例 -export const userService = new DatabaseService(db.users); -export const messageService = new DatabaseService(db.messages); -export const chatRoomService = new DatabaseService(db.chatRooms); -export const settingService = new DatabaseService(db.settings); - -// 专门的业务方法 -export class UserService extends DatabaseService { - constructor() { - super(db.users); - } - - async findByEmail(email: string): Promise { - return await db.users.where("email").equals(email).first(); - } - - async findActiveUsers(): Promise { - return await db.users.where("status").equals("active").toArray(); - } - - async searchByName(name: string): Promise { - return await db.users.where("name").startsWithIgnoreCase(name).toArray(); - } - - async updateStatus( - id: number, - status: "active" | "inactive", - ): Promise { - return await this.update(id, { status }); - } -} - -export class MessageService extends DatabaseService { - constructor() { - super(db.messages); - } - - async findByUserId(userId: number): Promise { - return await db.messages.where("userId").equals(userId).toArray(); - } - - async findUnreadMessages(): Promise { - return await db.messages.where("isRead").equals(false).toArray(); - } - - async markAsRead(id: number): Promise { - return await this.update(id, { isRead: true }); - } - - async markAllAsRead(userId: number): Promise { - const messages = await db.messages - .where("userId") - .equals(userId) - .and(msg => !msg.isRead) - .toArray(); - const updates = messages.map(msg => ({ - id: msg.id!, - data: { isRead: true }, - })); - return await this.updateMany(updates); - } - - async getRecentMessages(limit: number = 50): Promise { - return await db.messages - .orderBy("createdAt") - .reverse() - .limit(limit) - .toArray(); - } -} - -export class SettingService extends DatabaseService { - constructor() { - super(db.settings); - } - - async getSetting(key: string): Promise { - const setting = await db.settings.where("key").equals(key).first(); - return setting?.value; - } - - async setSetting( - key: string, - value: any, - category: string = "general", - ): Promise { - const existing = await db.settings.where("key").equals(key).first(); - if (existing) { - return await this.update(existing.id!, { value }); - } else { - return await this.create({ key, value, category }); - } - } - - async getSettingsByCategory(category: string): Promise { - return await db.settings.where("category").equals(category).toArray(); - } - - async deleteSetting(key: string): Promise { - await db.settings.where("key").equals(key).delete(); - } -} - -// 数据库工具类 -export class DatabaseUtils { - // 数据导出 - static async exportData(): Promise { - const data = { - users: await db.users.toArray(), - messages: await db.messages.toArray(), - chatRooms: await db.chatRooms.toArray(), - settings: await db.settings.toArray(), - exportedAt: new Date().toISOString(), - }; - return JSON.stringify(data, null, 2); - } - - // 数据导入 - static async importData(jsonData: string): Promise { - try { - const data = JSON.parse(jsonData); - - await db.transaction( - "rw", - [db.users, db.messages, db.chatRooms, db.settings], - async () => { - if (data.users) await db.users.bulkPut(data.users); - if (data.messages) await db.messages.bulkPut(data.messages); - if (data.chatRooms) await db.chatRooms.bulkPut(data.chatRooms); - if (data.settings) await db.settings.bulkPut(data.settings); - }, - ); - } catch (error) { - throw new Error("导入数据失败: " + error); - } - } - - // 清空所有数据 - static async clearAllData(): Promise { - await db.transaction( - "rw", - [db.users, db.messages, db.chatRooms, db.settings], - async () => { - await db.users.clear(); - await db.messages.clear(); - await db.chatRooms.clear(); - await db.settings.clear(); - }, - ); - } - - // 获取数据库统计信息 - static async getStats(): Promise<{ - users: number; - messages: number; - chatRooms: number; - settings: number; - totalSize: number; - }> { - const [users, messages, chatRooms, settings] = await Promise.all([ - db.users.count(), - db.messages.count(), - db.chatRooms.count(), - db.settings.count(), - ]); - - // 估算数据库大小(简单估算) - const totalSize = users + messages + chatRooms + settings; - - return { users, messages, chatRooms, settings, totalSize }; - } - - // 数据库健康检查 - static async healthCheck(): Promise<{ - status: "healthy" | "error"; - message: string; - }> { - try { - await db.users.limit(1).toArray(); - return { status: "healthy", message: "数据库连接正常" }; - } catch (error) { - return { status: "error", message: "数据库连接异常: " + error }; - } - } - - // 数据备份到文件 - static async backupToFile(): Promise { - const data = await this.exportData(); - const blob = new Blob([data], { type: "application/json" }); - const url = URL.createObjectURL(blob); - - const a = document.createElement("a"); - a.href = url; - a.download = `cunkebao-backup-${new Date().toISOString().split("T")[0]}.json`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } - - // 从文件恢复数据 - static async restoreFromFile(file: File): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = async e => { - try { - const jsonData = e.target?.result as string; - await this.importData(jsonData); - resolve(); - } catch (error) { - reject(error); - } - }; - reader.onerror = () => reject(new Error("文件读取失败")); - reader.readAsText(file); - }); - } -} - -// 创建业务服务实例 -export const userBusinessService = new UserService(); -export const messageBusinessService = new MessageService(); -export const settingBusinessService = new SettingService(); +export const kfUserService = new DatabaseService(db.kfUsers); +export const groupService = new DatabaseService(db.groups); +export const contractService = new DatabaseService(db.contracts); // 默认导出数据库实例 export default db; From d4336ed447d1f24f1d32ec03436d07835f5f10ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 30 Aug 2025 14:23:12 +0800 Subject: [PATCH 006/146] =?UTF-8?q?refactor(store):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=9C=AA=E4=BD=BF=E7=94=A8=E7=9A=84ckchat=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=8F=8A=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 清理不再使用的ckchat store模块及其相关导入,简化代码结构 --- Cunkebao/src/pages/mobile/test/db.tsx | 1 - Cunkebao/src/store/module/ckchat.ts | 205 -------------------------- 2 files changed, 206 deletions(-) delete mode 100644 Cunkebao/src/store/module/ckchat.ts diff --git a/Cunkebao/src/pages/mobile/test/db.tsx b/Cunkebao/src/pages/mobile/test/db.tsx index abb773c4..1c551148 100644 --- a/Cunkebao/src/pages/mobile/test/db.tsx +++ b/Cunkebao/src/pages/mobile/test/db.tsx @@ -30,7 +30,6 @@ import { DatabaseService, } from "@/utils/db"; import { testDatabaseUpgrade, resetDatabase } from "@/utils/db-test"; -import { KfUserListData, GroupData, ContractData } from "@/pages/pc/ckbox/data"; const { Title, Text } = Typography; diff --git a/Cunkebao/src/store/module/ckchat.ts b/Cunkebao/src/store/module/ckchat.ts deleted file mode 100644 index ed62f71a..00000000 --- a/Cunkebao/src/store/module/ckchat.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { createPersistStore } from "@/store/createPersistStore"; -import { CkChatState, CkUserInfo, CkTenant } from "./ckchat/ckchat.data"; -import { - ContractData, - GroupData, - CkAccount, - KfUserListData, -} from "@/pages/pc/ckbox/data"; - -export const useCkChatStore = createPersistStore( - set => ({ - userInfo: null, - isLoggedIn: false, - contractList: [], //联系人列表 - chatSessions: [], //聊天会话 - kfUserList: [], //客服列表 - kfSelected: 0, - newContractList: [], //联系人分组 - kfSelectedUser: () => { - const state = useCkChatStore.getState(); - return state.kfUserList.find(item => item.id === state.kfSelected); - }, - asyncKfSelected: (data: number) => { - set({ kfSelected: data }); - }, - // 异步设置会话列表 - asyncNewContractList: data => { - set({ newContractList: data }); - }, - getNewContractList: () => { - const state = useCkChatStore.getState(); - return state.newContractList; - }, - // 异步设置会话列表 - asyncChatSessions: data => { - set({ chatSessions: data }); - }, - // 异步设置联系人列表 - asyncContractList: data => { - set({ contractList: data }); - }, - // 控制终端用户列表 - getkfUserList: () => { - const state = useCkChatStore.getState(); - return state.kfUserList; - }, - asyncKfUserList: data => { - set({ kfUserList: data }); - }, - // 删除控制终端用户 - deleteCtrlUser: (userId: number) => { - set(state => ({ - kfUserList: state.kfUserList.filter(item => item.id !== userId), - })); - }, - // 更新控制终端用户 - updateCtrlUser: (user: KfUserListData) => { - set(state => ({ - kfUserList: state.kfUserList.map(item => - item.id === user.id ? user : item, - ), - })); - }, - // 清空控制终端用户列表 - clearkfUserList: () => { - set({ kfUserList: [] }); - }, - // 获取聊天会话 - getChatSessions: () => { - const state = useCkChatStore.getState(); - return state.chatSessions; - }, - // 添加聊天会话 - addChatSession: (session: ContractData | GroupData) => { - set(state => { - // 检查是否已存在相同id的会话 - const exists = state.chatSessions.some(item => item.id === session.id); - // 如果已存在则不添加,否则添加到列表中 - return { - chatSessions: exists - ? state.chatSessions - : [...state.chatSessions, session as ContractData | GroupData], - }; - }); - }, - // 更新聊天会话 - updateChatSession: (session: ContractData | GroupData) => { - set(state => ({ - chatSessions: state.chatSessions.map(item => - item.id === session.id ? session : item, - ), - })); - }, - // 删除聊天会话 - deleteChatSession: (sessionId: string) => { - set(state => ({ - chatSessions: state.chatSessions.filter(item => item.id !== sessionId), - })); - }, - // 设置用户信息 - setUserInfo: (userInfo: CkUserInfo) => { - set({ userInfo, isLoggedIn: true }); - }, - - // 清除用户信息 - clearUserInfo: () => { - set({ userInfo: null, isLoggedIn: false }); - }, - - // 更新账户信息 - updateAccount: (account: Partial) => { - set(state => ({ - userInfo: state.userInfo - ? { - ...state.userInfo, - account: { ...state.userInfo.account, ...account }, - } - : null, - })); - }, - - // 更新租户信息 - updateTenant: (tenant: Partial) => { - set(state => ({ - userInfo: state.userInfo - ? { - ...state.userInfo, - tenant: { ...state.userInfo.tenant, ...tenant }, - } - : null, - })); - }, - - // 获取账户ID - getAccountId: () => { - const state = useCkChatStore.getState(); - return Number(state.userInfo?.account?.id) || null; - }, - - // 获取租户ID - getTenantId: () => { - const state = useCkChatStore.getState(); - return state.userInfo?.tenant?.id || null; - }, - - // 获取账户名称 - getAccountName: () => { - const state = useCkChatStore.getState(); - return ( - state.userInfo?.account?.realName || - state.userInfo?.account?.userName || - null - ); - }, - - // 获取租户名称 - getTenantName: () => { - const state = useCkChatStore.getState(); - return state.userInfo?.tenant?.name || null; - }, - }), - { - name: "ckchat-store", - partialize: state => ({ - userInfo: state.userInfo, - isLoggedIn: state.isLoggedIn, - }), - onRehydrateStorage: () => state => { - // console.log("CkChat store hydrated:", state); - }, - }, -); - -// 导出便捷的获取方法 -export const getCkAccountId = () => useCkChatStore.getState().getAccountId(); -export const getCkTenantId = () => useCkChatStore.getState().getTenantId(); -export const getCkAccountName = () => - useCkChatStore.getState().getAccountName(); -export const getCkTenantName = () => useCkChatStore.getState().getTenantName(); -export const getChatSessions = () => - useCkChatStore.getState().getChatSessions(); -export const addChatSession = (session: ContractData | GroupData) => - useCkChatStore.getState().addChatSession(session); -export const updateChatSession = (session: ContractData | GroupData) => - useCkChatStore.getState().updateChatSession(session); -export const deleteChatSession = (sessionId: string) => - useCkChatStore.getState().deleteChatSession(sessionId); -export const getkfUserList = () => useCkChatStore.getState().kfUserList; -export const addCtrlUser = (user: KfUserListData) => - useCkChatStore.getState().addCtrlUser(user); -export const deleteCtrlUser = (userId: number) => - useCkChatStore.getState().deleteCtrlUser(userId); -export const updateCtrlUser = (user: KfUserListData) => - useCkChatStore.getState().updateCtrlUser(user); -export const asyncKfUserList = (data: KfUserListData[]) => - useCkChatStore.getState().asyncKfUserList(data); -export const asyncContractList = (data: ContractData[]) => - useCkChatStore.getState().asyncContractList(data); -export const asyncChatSessions = (data: ContractData[]) => - useCkChatStore.getState().asyncChatSessions(data); -export const asyncNewContractList = ( - data: { groupName: string; contacts: any[] }[], -) => useCkChatStore.getState().asyncNewContractList(data); -export const asyncKfSelected = (data: number) => - useCkChatStore.getState().asyncKfSelected(data); From 5bdd299dad3ac22fb6dcb8958b1587fea57bc2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 30 Aug 2025 15:00:26 +0800 Subject: [PATCH 007/146] =?UTF-8?q?refactor(db):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E6=9E=B6=E6=9E=84=E4=BD=BF=E7=94=A8?= =?UTF-8?q?serverId=E4=BD=9C=E4=B8=BA=E4=B8=BB=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将数据库主键从自增id改为直接使用serverId,避免ID冲突 - 简化数据存储和查询逻辑,提高性能 - 添加重复数据检测和去重功能 - 更新相关组件以适配新的数据库接口 - 在应用启动时初始化数据库连接 --- Cunkebao/src/main.tsx | 25 ++- .../ChatWindow/components/Person/index.tsx | 4 +- .../pc/ckbox/components/ChatWindow/index.tsx | 2 +- .../components/VerticalUserList/index.tsx | 21 ++- .../src/store/module/ckchat/ckchat.data.ts | 2 +- Cunkebao/src/store/module/ckchat/ckchat.ts | 27 +-- Cunkebao/src/utils/db.ts | 166 ++++++++++-------- 7 files changed, 152 insertions(+), 95 deletions(-) diff --git a/Cunkebao/src/main.tsx b/Cunkebao/src/main.tsx index 637e8569..49a04054 100644 --- a/Cunkebao/src/main.tsx +++ b/Cunkebao/src/main.tsx @@ -1,10 +1,25 @@ +// main.tsx import React from "react"; import { createRoot } from "react-dom/client"; import App from "./App"; import "./styles/global.scss"; -// 引入错误处理器来抑制findDOMNode警告 -// import VConsole from "vconsole"; -// new VConsole(); +import { db } from "@/utils/db"; // 引入数据库实例 -const root = createRoot(document.getElementById("root")!); -root.render(); +// 数据库初始化 +async function initializeApp() { + try { + // 确保数据库已打开 + await db.open(); + console.log("数据库初始化成功"); + } catch (error) { + console.error("数据库初始化失败:", error); + // 可以选择显示错误提示或使用降级方案 + } + + // 渲染应用 + const root = createRoot(document.getElementById("root")!); + root.render(); +} + +// 启动应用 +initializeApp(); diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx index 710d399a..177e071b 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx @@ -44,7 +44,7 @@ const Person: React.FC = ({ const [isEditingRemark, setIsEditingRemark] = useState(false); const [remarkValue, setRemarkValue] = useState(contract.conRemark || ""); - const kfSelectedUser = useCkChatStore(state => state.kfSelectedUser()); + const getKfSelectedUser = useCkChatStore(state => state.getKfSelectedUser()); // 当contract变化时更新备注值 useEffect(() => { @@ -124,7 +124,7 @@ const Person: React.FC = ({
- {JSON.stringify(kfSelectedUser)} + {JSON.stringify(getKfSelectedUser)} {isEditingRemark ? (
= ({ >({}); const messagesEndRef = useRef(null); - const kfSelectedUser = useCkChatStore(state => state.kfSelectedUser()); + const getKfSelectedUser = useCkChatStore(state => state.getKfSelectedUser()); useEffect(() => { clearUnreadCount([contract.id]).then(() => { setLoading(true); diff --git a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx index dec9d17e..ff4af178 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Avatar, Badge, Tooltip } from "antd"; import styles from "./VerticalUserList.module.scss"; import { useCkChatStore, asyncKfSelected } from "@/store/module/ckchat/ckchat"; @@ -14,9 +14,24 @@ const VerticalUserList: React.FC = () => { const handleUserSelect = (userId: number) => { asyncKfSelected(userId); }; - const kfUserList = useCkChatStore(state => state.kfUserList); + const getkfUserList = useCkChatStore(state => state.getkfUserList); const kfSelected = useCkChatStore(state => state.kfSelected); + const [kefuList, setKefuList] = useState([]); + // 获取客服列表数据 + useEffect(() => { + const fetchKfUserList = async () => { + try { + const data = await getkfUserList(); + setKefuList(data || []); + } catch (error) { + console.error("获取客服列表失败:", error); + setKefuList([]); + } + }; + + fetchKfUserList(); + }, [getkfUserList]); return (
{
全部好友
- {kfUserList.map(user => ( + {kefuList.map(user => (
KfUserListData | undefined; + getKfSelectedUser: () => KfUserListData | undefined; newContractList: { groupName: string; contacts: any[] }[]; asyncKfSelected: (data: number) => void; getkfUserList: () => KfUserListData[]; diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts index 591a2bf1..899bb9f7 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.ts @@ -6,7 +6,7 @@ import { CkAccount, KfUserListData, } from "@/pages/pc/ckbox/data"; - +import { kfUserService } from "@/utils/db"; export const useCkChatStore = createPersistStore( set => ({ userInfo: null, @@ -16,9 +16,18 @@ export const useCkChatStore = createPersistStore( kfUserList: [], //客服列表 kfSelected: 0, newContractList: [], //联系人分组 - kfSelectedUser: () => { - const state = useCkChatStore.getState(); - return state.kfUserList.find(item => item.id === state.kfSelected); + //客服列表 + asyncKfUserList: async data => { + console.log(data); + + await kfUserService.createManyWithServerId(data); + // set({ kfUserList: data }); + }, + // 获取客服列表 + getkfUserList: async () => { + return await kfUserService.findAll(); + // const state = useCkChatStore.getState(); + // return state.kfUserList; }, asyncKfSelected: (data: number) => { set({ kfSelected: data }); @@ -39,14 +48,12 @@ export const useCkChatStore = createPersistStore( asyncContractList: data => { set({ contractList: data }); }, - // 控制终端用户列表 - getkfUserList: () => { + //获取选中的客服信息 + getgetKfSelectedUser: () => { const state = useCkChatStore.getState(); - return state.kfUserList; - }, - asyncKfUserList: data => { - set({ kfUserList: data }); + return state.kfUserList.find(item => item.id === state.kfSelected); }, + // 删除控制终端用户 deleteCtrlUser: (userId: number) => { set(state => ({ diff --git a/Cunkebao/src/utils/db.ts b/Cunkebao/src/utils/db.ts index 2ec84c0b..943e3b44 100644 --- a/Cunkebao/src/utils/db.ts +++ b/Cunkebao/src/utils/db.ts @@ -1,45 +1,53 @@ /** - * 数据库工具类 - 解决服务器ID与本地自增主键冲突问题 + * 数据库工具类 - 使用serverId作为主键的优化架构 * - * 问题描述: - * 接口返回的数据包含id字段,直接存储到数据库会与Dexie的自增主键(++id)产生冲突 + * 架构设计: + * 1. 使用serverId作为数据库主键,直接对应接口返回的id字段 + * 2. 保留原始的id字段,用于存储接口数据的完整性 + * 3. 简化数据处理逻辑,避免ID映射的复杂性 * - * 解决方案: - * 1. 将服务器返回的id字段映射为serverId字段存储 - * 2. 数据库使用自增的id作为主键 - * 3. 提供专门的方法处理服务器数据的存储和查询 + * 优势: + * - 直接使用服务器ID作为主键,避免ID冲突 + * - 保持数据的一致性和可追溯性 + * - 简化查询逻辑,提高性能 + * - 支持重复数据检测和去重 * * 使用方法: * - 存储接口数据:使用 createWithServerId() 或 createManyWithServerId() - * - 查询服务器数据:使用 findByServerId() - * - 常规操作:使用原有的 create(), findById() 等方法 + * - 查询数据:使用 findById(id) 根据原始ID查询,或 findByPrimaryKey(serverId) 根据主键查询 + * - 批量查询:使用 findByIds([id1, id2, ...]) 根据原始ID批量查询 + * - 内部操作:serverId作为主键用于数据库内部管理 * * 示例: * const serverData = { id: 1001, name: '测试', ... }; // 接口返回的数据 - * const localId = await service.createWithServerId(serverData); // 存储,返回本地ID - * const data = await service.findByServerId(1001); // 根据服务器ID查询 + * const serverId = await service.createWithServerId(serverData); // 存储,返回serverId + * const data = await service.findById(1001); // 根据原始ID查询(用户友好) + * const dataByPK = await service.findByPrimaryKey(serverId); // 根据主键查询(内部使用) */ import Dexie, { Table } from "dexie"; import { KfUserListData, GroupData, ContractData } from "@/pages/pc/ckbox/data"; -// 扩展数据类型,添加serverId字段 -export interface KfUserWithServerId extends KfUserListData { - serverId?: number | string; // 服务器返回的原始ID +// 数据类型定义,使用serverId作为主键 +export interface KfUserWithServerId extends Omit { + serverId: number | string; // 服务器ID作为主键 + id?: number; // 接口数据的原始ID字段 } -export interface GroupWithServerId extends GroupData { - serverId?: number | string; // 服务器返回的原始ID +export interface GroupWithServerId extends Omit { + serverId: number | string; // 服务器ID作为主键 + id?: number; // 接口数据的原始ID字段 } -export interface ContractWithServerId extends ContractData { - serverId?: number | string; // 服务器返回的原始ID +export interface ContractWithServerId extends Omit { + serverId: number | string; // 服务器ID作为主键 + id?: number; // 接口数据的原始ID字段 } // 新联系人列表数据接口 export interface NewContactListData { - id?: number; - serverId?: number | string; // 服务器返回的原始ID + serverId: number | string; // 服务器ID作为主键 + id?: number; // 接口数据的原始ID字段 groupName: string; contacts: ContractData[] | GroupData[]; } @@ -54,34 +62,16 @@ class CunkebaoDatabase extends Dexie { constructor() { super("CunkebaoDatabase"); - // 版本1:初始版本(不包含serverId字段) + // 版本1:使用serverId作为主键的架构 this.version(1).stores({ kfUsers: - "++id, tenantId, wechatId, nickname, alias, avatar, gender, region, signature, bindQQ, bindEmail, bindMobile, createTime, currentDeviceId, isDeleted, deleteTime, groupId, memo, wechatVersion, lastUpdateTime, isOnline", + "serverId, id, tenantId, wechatId, nickname, alias, avatar, gender, region, signature, bindQQ, bindEmail, bindMobile, createTime, currentDeviceId, isDeleted, deleteTime, groupId, memo, wechatVersion, lastUpdateTime, isOnline", groups: - "++id, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar, groupId, config, unreadCount, notice, selfDisplyName", + "serverId, id, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar, groupId, config, unreadCount, notice, selfDisplyName", contracts: - "++id, wechatAccountId, wechatId, alias, conRemark, nickname, quanPin, avatar, gender, region, addFrom, phone, signature, accountId, extendFields, city, lastUpdateTime, isPassed, tenantId, groupId, thirdParty, additionalPicture, desc, config, lastMessageTime, unreadCount, duplicate", - newContractList: "++id, groupName, contacts", + "serverId, id, wechatAccountId, wechatId, alias, conRemark, nickname, quanPin, avatar, gender, region, addFrom, phone, signature, accountId, extendFields, city, lastUpdateTime, isPassed, tenantId, groupId, thirdParty, additionalPicture, desc, config, lastMessageTime, unreadCount, duplicate", + newContractList: "serverId, id, groupName, contacts", }); - - // 版本2:添加serverId字段支持 - this.version(2) - .stores({ - kfUsers: - "++id, serverId, tenantId, wechatId, nickname, alias, avatar, gender, region, signature, bindQQ, bindEmail, bindMobile, createTime, currentDeviceId, isDeleted, deleteTime, groupId, memo, wechatVersion, lastUpdateTime, isOnline", - groups: - "++id, serverId, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar, groupId, config, unreadCount, notice, selfDisplyName", - contracts: - "++id, serverId, wechatAccountId, wechatId, alias, conRemark, nickname, quanPin, avatar, gender, region, addFrom, phone, signature, accountId, extendFields, city, lastUpdateTime, isPassed, tenantId, groupId, thirdParty, additionalPicture, desc, config, lastMessageTime, unreadCount, duplicate", - newContractList: "++id, serverId, groupName, contacts", - }) - .upgrade(tx => { - // 数据库升级逻辑:为现有数据添加serverId字段(可选) - console.log("数据库升级到版本2:添加serverId字段支持"); - // 注意:这里不需要迁移数据,因为serverId是可选字段 - // 如果需要迁移现有数据,可以在这里添加相应逻辑 - }); } } @@ -92,64 +82,90 @@ export const db = new CunkebaoDatabase(); export class DatabaseService { constructor(private table: Table) {} - // 基础 CRUD 操作 - async create(data: Omit): Promise { + // 基础 CRUD 操作 - 使用serverId作为主键 + async create(data: Omit): Promise { return await this.table.add(data as T); } - // 创建数据(处理服务器ID映射) - // 用于存储从接口获取的数据,将服务器的id字段映射为serverId,避免与数据库自增主键冲突 - async createWithServerId(data: any): Promise { - const { id, ...restData } = data; + // 创建数据(直接使用接口数据) + // 接口数据的id字段直接作为serverId主键,原id字段保留 + async createWithServerId(data: any): Promise { const dataToInsert = { - ...restData, - serverId: id, // 将服务器的id映射为serverId + ...data, + serverId: data.id, // 使用接口的id作为serverId主键 }; return await this.table.add(dataToInsert as T); } - async findById(id: number): Promise { - return await this.table.get(id); + // 根据原始ID查询(用户友好的查询方法) + async findById(id: string | number): Promise { + return await this.table.where("id").equals(id).first(); + } + + // 根据serverId查询(内部主键查询) + async findByPrimaryKey(serverId: string | number): Promise { + return await this.table.get(serverId); } async findAll(): Promise { return await this.table.toArray(); } - async update(id: number, data: Partial): Promise { - return await this.table.update(id, data as any); + async update(serverId: string | number, data: Partial): Promise { + return await this.table.update(serverId, data as any); } async updateMany( - dataList: { id: number; data: Partial }[], + dataList: { serverId: string | number; data: Partial }[], ): Promise { return await this.table.bulkUpdate( dataList.map(item => ({ - key: item.id, + key: item.serverId, changes: item.data as any, })), ); } - async createMany(dataList: Omit[]): Promise { + async createMany( + dataList: Omit[], + ): Promise<(string | number)[]> { return await this.table.bulkAdd(dataList as T[], { allKeys: true }); } - // 批量创建数据(处理服务器ID映射) - // 用于批量存储从接口获取的数据,将服务器的id字段映射为serverId - async createManyWithServerId(dataList: any[]): Promise { - const processedData = dataList.map(item => { - const { id, ...restData } = item; - return { - ...restData, - serverId: id, // 将服务器的id映射为serverId - }; - }); + // 批量创建数据(直接使用接口数据) + // 接口数据的id字段直接作为serverId主键 + async createManyWithServerId(dataList: any[]): Promise<(string | number)[]> { + // 检查是否存在重复的serverId + const serverIds = dataList.map(item => item.id); + const existingData = await this.table + .where("serverId") + .anyOf(serverIds) + .toArray(); + const existingServerIds = new Set( + existingData.map((item: any) => item.serverId), + ); + + // 过滤掉已存在的数据 + const newData = dataList.filter(item => !existingServerIds.has(item.id)); + + if (newData.length === 0) { + console.log("所有数据都已存在,跳过插入"); + return []; + } + + const processedData = newData.map(item => ({ + ...item, + serverId: item.id, // 使用接口的id作为serverId主键 + })); + + console.log( + `插入 ${processedData.length} 条新数据,跳过 ${dataList.length - newData.length} 条重复数据`, + ); return await this.table.bulkAdd(processedData as T[], { allKeys: true }); } - async delete(id: number): Promise { - await this.table.delete(id); + async delete(serverId: string | number): Promise { + await this.table.delete(serverId); } async clear(): Promise { @@ -164,10 +180,14 @@ export class DatabaseService { .toArray(); } - // 根据服务器ID查询 - // 用于根据原始的服务器ID查找数据 + // 根据服务器ID查询(兼容性方法) async findByServerId(serverId: any): Promise { - return await this.table.where("serverId").equals(serverId).first(); + return await this.table.get(serverId); + } + + // 根据原始ID批量查询 + async findByIds(ids: (string | number)[]): Promise { + return await this.table.where("id").anyOf(ids).toArray(); } // 多值查询(IN 查询) From 8581432bf4d0bfbcf1918dd9a7c5c78dace05a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 30 Aug 2025 15:36:03 +0800 Subject: [PATCH 008/146] =?UTF-8?q?refactor(=E6=95=B0=E6=8D=AE=E5=BA=93):?= =?UTF-8?q?=20=E9=87=8D=E5=91=BD=E5=90=8DGroupData=E4=B8=BAwechatGroup?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE=E5=BA=93=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构GroupData接口为wechatGroup以更准确描述微信群组数据结构 在数据库初始化时添加调试模式清理数据功能 更新所有相关引用和数据库服务实例名称 --- Cunkebao/src/main.tsx | 8 +++++ Cunkebao/src/pages/mobile/test/db.tsx | 8 ++--- .../SidebarMenu/MessageList/index.tsx | 8 ++--- .../SidebarMenu/WechatFriends/index.tsx | 8 ++--- .../pc/ckbox/components/SidebarMenu/index.tsx | 10 +++---- Cunkebao/src/pages/pc/ckbox/data.ts | 10 ++++++- Cunkebao/src/pages/pc/ckbox/index.tsx | 10 +++++-- Cunkebao/src/pages/pc/ckbox/main.ts | 30 +++++++++++++++---- Cunkebao/src/store/module/ckchat/ckchat.ts | 17 ++++------- Cunkebao/src/utils/db-examples.ts | 16 ++++++---- Cunkebao/src/utils/db.ts | 17 +++++++---- 11 files changed, 93 insertions(+), 49 deletions(-) diff --git a/Cunkebao/src/main.tsx b/Cunkebao/src/main.tsx index 49a04054..612fb438 100644 --- a/Cunkebao/src/main.tsx +++ b/Cunkebao/src/main.tsx @@ -11,6 +11,14 @@ async function initializeApp() { // 确保数据库已打开 await db.open(); console.log("数据库初始化成功"); + + // 调试模式:清理所有数据 + console.log("调试模式:开始清理数据库数据..."); + await db.kfUsers.clear(); + await db.wechatGroup.clear(); + await db.contracts.clear(); + await db.newContractList.clear(); + console.log("数据库数据清理完成"); } catch (error) { console.error("数据库初始化失败:", error); // 可以选择显示错误提示或使用降级方案 diff --git a/Cunkebao/src/pages/mobile/test/db.tsx b/Cunkebao/src/pages/mobile/test/db.tsx index 1c551148..b8d076e7 100644 --- a/Cunkebao/src/pages/mobile/test/db.tsx +++ b/Cunkebao/src/pages/mobile/test/db.tsx @@ -25,7 +25,7 @@ import { DatabaseExamples } from "@/utils/db-examples"; import { db, kfUserService, - groupService, + wechatGroupService, contractService, DatabaseService, } from "@/utils/db"; @@ -74,7 +74,7 @@ const DatabaseTestPage: React.FC = () => { const newContactListService = new DatabaseService(db.newContractList); const stats = { kfUsers: await kfUserService.count(), - groups: await groupService.count(), + groups: await wechatGroupService.count(), contracts: await contractService.count(), newContactList: await newContactListService.count(), }; @@ -188,14 +188,14 @@ const DatabaseTestPage: React.FC = () => { selfDisplyName: "群管理员", }; - const groupId = await groupService.createWithServerId(serverGroup); + const groupId = await wechatGroupService.createWithServerId(serverGroup); addLog( "success", `创建群组成功,本地ID: ${groupId}, 服务器ID: ${serverGroup.id}`, ); // 更新群组 - await groupService.update(groupId, { + await wechatGroupService.update(groupId, { unreadCount: 5, notice: "更新的群公告", }); diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx index ab399177..b25b1f86 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx @@ -1,13 +1,13 @@ import React from "react"; import { List, Avatar, Badge } from "antd"; import { UserOutlined, TeamOutlined } from "@ant-design/icons"; -import { ContractData, GroupData } from "@/pages/pc/ckbox/data"; +import { ContractData, wechatGroup } from "@/pages/pc/ckbox/data"; import styles from "./MessageList.module.scss"; import { formatWechatTime } from "@/utils/common"; interface MessageListProps { - chatSessions: ContractData[] | GroupData[]; - currentChat: ContractData | GroupData; - onChatSelect: (chat: ContractData | GroupData) => void; + chatSessions: ContractData[] | wechatGroup[]; + currentChat: ContractData | wechatGroup; + onChatSelect: (chat: ContractData | wechatGroup) => void; } const MessageList: React.FC = ({ diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx index 730f3ede..76107e53 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx @@ -3,12 +3,12 @@ import { List, Avatar, Collapse, Button } from "antd"; import type { CollapseProps } from "antd"; import styles from "./WechatFriends.module.scss"; import { useCkChatStore } from "@/store/module/ckchat/ckchat"; -import { ContractData, GroupData } from "@/pages/pc/ckbox/data"; +import { ContractData, wechatGroup } from "@/pages/pc/ckbox/data"; interface WechatFriendsProps { - contracts: ContractData[] | GroupData[]; - onContactClick: (contract: ContractData | GroupData) => void; - selectedContactId?: ContractData | GroupData; + contracts: ContractData[] | wechatGroup[]; + onContactClick: (contract: ContractData | wechatGroup) => void; + selectedContactId?: ContractData | wechatGroup; } const ContactListSimple: React.FC = ({ diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx index 98c5c712..fc649d2a 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx @@ -6,17 +6,17 @@ import { ChromeOutlined, MessageOutlined, } from "@ant-design/icons"; -import { ContractData, GroupData } from "@/pages/pc/ckbox/data"; +import { ContractData, wechatGroup } from "@/pages/pc/ckbox/data"; import WechatFriends from "./WechatFriends"; import MessageList from "./MessageList/index"; import styles from "./SidebarMenu.module.scss"; import { getChatSessions } from "@/store/module/ckchat/ckchat"; interface SidebarMenuProps { - contracts: ContractData[] | GroupData[]; - currentChat: ContractData | GroupData; - onContactClick: (contract: ContractData | GroupData) => void; - onChatSelect: (chat: ContractData | GroupData) => void; + contracts: ContractData[] | wechatGroup[]; + currentChat: ContractData | wechatGroup; + onContactClick: (contract: ContractData | wechatGroup) => void; + onChatSelect: (chat: ContractData | wechatGroup) => void; loading?: boolean; } diff --git a/Cunkebao/src/pages/pc/ckbox/data.ts b/Cunkebao/src/pages/pc/ckbox/data.ts index 20e79c83..8487b251 100644 --- a/Cunkebao/src/pages/pc/ckbox/data.ts +++ b/Cunkebao/src/pages/pc/ckbox/data.ts @@ -1,3 +1,11 @@ +//联系人标签分组 +export interface ContactGroupByLabel { + id: number; + accountId?: number; + groupName: string; + tenantId?: number; + [key: string]: any; +} //终端用户数据接口 export interface KfUserListData { id: number; @@ -41,7 +49,7 @@ export interface CkAccount { } //群聊数据接口 -export interface GroupData { +export interface wechatGroup { id?: number; wechatAccountId: number; tenantId: number; diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx index c8f50eff..45f63da5 100644 --- a/Cunkebao/src/pages/pc/ckbox/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/index.tsx @@ -10,12 +10,16 @@ import styles from "./index.module.scss"; import { addChatSession } from "@/store/module/ckchat/ckchat"; const { Header, Content, Sider } = Layout; import { chatInitAPIdata } from "./main"; -import { KfUserListData, GroupData, ContractData } from "@/pages/pc/ckbox/data"; +import { + KfUserListData, + wechatGroup, + ContractData, +} from "@/pages/pc/ckbox/data"; const CkboxPage: React.FC = () => { const [messageApi, contextHolder] = message.useMessage(); const [contracts, setContacts] = useState([]); - const [currentChat, setCurrentChat] = useState( + const [currentChat, setCurrentChat] = useState( null, ); @@ -53,7 +57,7 @@ const CkboxPage: React.FC = () => { }); }, []); - const handleContactClick = (contract: ContractData | GroupData) => { + const handleContactClick = (contract: ContractData | wechatGroup) => { addChatSession(contract); setCurrentChat(contract); }; diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts index ff62d021..e324fbcc 100644 --- a/Cunkebao/src/pages/pc/ckbox/main.ts +++ b/Cunkebao/src/pages/pc/ckbox/main.ts @@ -1,5 +1,4 @@ import { - useCkChatStore, asyncKfUserList, asyncContractList, asyncChatSessions, @@ -16,7 +15,11 @@ import { } from "./api"; const { sendCommand } = useWebSocketStore.getState(); import { useUserStore } from "@/store/module/user"; -import { ContractData, GroupData, KfUserListData } from "@/pages/pc/ckbox/data"; +import { + ContractData, + wechatGroup, + KfUserListData, +} from "@/pages/pc/ckbox/data"; const { login2 } = useUserStore.getState(); //获取触客宝基础信息 export const chatInitAPIdata = async () => { @@ -95,7 +98,7 @@ export const chatInitAPIdata = async () => { //构建联系人列表标签 export const createContractList = async ( contractList: ContractData[], - groupList: GroupData[], + groupList: wechatGroup[], ) => { const LablesRes = await Promise.all( [1, 2].map(item => @@ -105,8 +108,25 @@ export const createContractList = async ( ), ); const [friend, group] = LablesRes; + const lastIndex = friend.length + group.length + 1; + const countLables = [ + ...[ + { + id: 0, + groupName: "默认群分组", + }, + ], + ...friend, + ...group, + ...[ + { + id: lastIndex, + groupName: "未分组", + }, + ], + ]; - const countLables = [...friend, ...group]; + console.log(countLables); // 根据countLables中的groupName整理contractList数据 // 返回按标签分组的联系人数组,包括未分组标签(在数组最后) @@ -122,7 +142,7 @@ export const createContractList = async ( export const organizeContactsByLabels = ( countLables: any[], contractList: ContractData[], - groupList: GroupData[], + groupList: wechatGroup[], ) => { // 创建结果对象,用于存储按标签分组的联系人 const result: { [key: string]: any[] } = {}; diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts index 899bb9f7..e5bb1fe6 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.ts @@ -2,7 +2,7 @@ import { createPersistStore } from "@/store/createPersistStore"; import { CkChatState, CkUserInfo, CkTenant } from "./ckchat.data"; import { ContractData, - GroupData, + wechatGroup, CkAccount, KfUserListData, } from "@/pages/pc/ckbox/data"; @@ -18,16 +18,11 @@ export const useCkChatStore = createPersistStore( newContractList: [], //联系人分组 //客服列表 asyncKfUserList: async data => { - console.log(data); - await kfUserService.createManyWithServerId(data); - // set({ kfUserList: data }); }, // 获取客服列表 getkfUserList: async () => { return await kfUserService.findAll(); - // const state = useCkChatStore.getState(); - // return state.kfUserList; }, asyncKfSelected: (data: number) => { set({ kfSelected: data }); @@ -78,7 +73,7 @@ export const useCkChatStore = createPersistStore( return state.chatSessions; }, // 添加聊天会话 - addChatSession: (session: ContractData | GroupData) => { + addChatSession: (session: ContractData | wechatGroup) => { set(state => { // 检查是否已存在相同id的会话 const exists = state.chatSessions.some(item => item.id === session.id); @@ -86,12 +81,12 @@ export const useCkChatStore = createPersistStore( return { chatSessions: exists ? state.chatSessions - : [...state.chatSessions, session as ContractData | GroupData], + : [...state.chatSessions, session as ContractData | wechatGroup], }; }); }, // 更新聊天会话 - updateChatSession: (session: ContractData | GroupData) => { + updateChatSession: (session: ContractData | wechatGroup) => { set(state => ({ chatSessions: state.chatSessions.map(item => item.id === session.id ? session : item, @@ -186,9 +181,9 @@ export const getCkAccountName = () => export const getCkTenantName = () => useCkChatStore.getState().getTenantName(); export const getChatSessions = () => useCkChatStore.getState().getChatSessions(); -export const addChatSession = (session: ContractData | GroupData) => +export const addChatSession = (session: ContractData | wechatGroup) => useCkChatStore.getState().addChatSession(session); -export const updateChatSession = (session: ContractData | GroupData) => +export const updateChatSession = (session: ContractData | wechatGroup) => useCkChatStore.getState().updateChatSession(session); export const deleteChatSession = (sessionId: string) => useCkChatStore.getState().deleteChatSession(sessionId); diff --git a/Cunkebao/src/utils/db-examples.ts b/Cunkebao/src/utils/db-examples.ts index 4387431e..0127da81 100644 --- a/Cunkebao/src/utils/db-examples.ts +++ b/Cunkebao/src/utils/db-examples.ts @@ -1,12 +1,16 @@ import { db, kfUserService, - groupService, + wechatGroupService, contractService, DatabaseService, NewContactListData, } from "./db"; -import { KfUserListData, GroupData, ContractData } from "@/pages/pc/ckbox/data"; +import { + KfUserListData, + wechatGroup, + ContractData, +} from "@/pages/pc/ckbox/data"; // 创建 newContractList 服务实例 const newContactListService = new DatabaseService(db.newContractList); @@ -128,7 +132,7 @@ export class DatabaseExamples { try { // 使用新方法创建群组 - const groupId = await groupService.createWithServerId(serverGroup); + const groupId = await wechatGroupService.createWithServerId(serverGroup); console.log( "创建群组成功,本地ID:", groupId, @@ -137,18 +141,18 @@ export class DatabaseExamples { ); // 查询群组 - const groups = await groupService.findWhere("tenantId", 1); + const groups = await wechatGroupService.findWhere("tenantId", 1); console.log("租户群组数量:", groups.length); // 查询有未读消息的群组 - const unreadGroups = await groupService.findWhereGreaterThan( + const unreadGroups = await wechatGroupService.findWhereGreaterThan( "unreadCount", 0, ); console.log("有未读消息的群组:", unreadGroups.length); // 批量更新群组 - await groupService.updateMany([ + await wechatGroupService.updateMany([ { id: groupId, data: { unreadCount: 5, notice: "更新的群公告" }, diff --git a/Cunkebao/src/utils/db.ts b/Cunkebao/src/utils/db.ts index 943e3b44..e779d99d 100644 --- a/Cunkebao/src/utils/db.ts +++ b/Cunkebao/src/utils/db.ts @@ -26,7 +26,11 @@ */ import Dexie, { Table } from "dexie"; -import { KfUserListData, GroupData, ContractData } from "@/pages/pc/ckbox/data"; +import { + KfUserListData, + wechatGroup, + ContractData, +} from "@/pages/pc/ckbox/data"; // 数据类型定义,使用serverId作为主键 export interface KfUserWithServerId extends Omit { @@ -34,7 +38,7 @@ export interface KfUserWithServerId extends Omit { id?: number; // 接口数据的原始ID字段 } -export interface GroupWithServerId extends Omit { +export interface GroupWithServerId extends Omit { serverId: number | string; // 服务器ID作为主键 id?: number; // 接口数据的原始ID字段 } @@ -49,13 +53,14 @@ export interface NewContactListData { serverId: number | string; // 服务器ID作为主键 id?: number; // 接口数据的原始ID字段 groupName: string; - contacts: ContractData[] | GroupData[]; + contacts: ContractData[]; + wechatGroup: wechatGroup[]; } // 数据库类 class CunkebaoDatabase extends Dexie { kfUsers!: Table; - groups!: Table; + wechatGroup!: Table; contracts!: Table; newContractList!: Table; @@ -66,7 +71,7 @@ class CunkebaoDatabase extends Dexie { this.version(1).stores({ kfUsers: "serverId, id, tenantId, wechatId, nickname, alias, avatar, gender, region, signature, bindQQ, bindEmail, bindMobile, createTime, currentDeviceId, isDeleted, deleteTime, groupId, memo, wechatVersion, lastUpdateTime, isOnline", - groups: + wechatGroup: "serverId, id, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar, groupId, config, unreadCount, notice, selfDisplyName", contracts: "serverId, id, wechatAccountId, wechatId, alias, conRemark, nickname, quanPin, avatar, gender, region, addFrom, phone, signature, accountId, extendFields, city, lastUpdateTime, isPassed, tenantId, groupId, thirdParty, additionalPicture, desc, config, lastMessageTime, unreadCount, duplicate", @@ -311,7 +316,7 @@ export class DatabaseService { // 创建各表的服务实例 export const kfUserService = new DatabaseService(db.kfUsers); -export const groupService = new DatabaseService(db.groups); +export const wechatwechatGroupService = new DatabaseService(db.wechatGroup); export const contractService = new DatabaseService(db.contracts); // 默认导出数据库实例 From 136e0f622566759461716a57cbdbc690584b267f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 30 Aug 2025 15:55:15 +0800 Subject: [PATCH 009/146] =?UTF-8?q?refactor(ckbox):=20=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=91=BD=E5=90=8D=E5=B0=86wechatGroup?= =?UTF-8?q?=E6=94=B9=E4=B8=BAweChatGroup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构微信相关接口命名,将wechatGroup统一改为weChatGroup以保持命名一致性 更新相关组件、store和数据库服务以适配新接口名称 --- Cunkebao/src/pages/mobile/test/db.tsx | 8 +++---- .../SidebarMenu/MessageList/index.tsx | 8 +++---- .../SidebarMenu/WechatFriends/index.tsx | 9 ++++---- .../pc/ckbox/components/SidebarMenu/index.tsx | 10 ++++----- Cunkebao/src/pages/pc/ckbox/data.ts | 2 +- Cunkebao/src/pages/pc/ckbox/index.tsx | 6 +++--- Cunkebao/src/pages/pc/ckbox/main.ts | 17 ++++++--------- .../src/store/module/ckchat/ckchat.data.ts | 11 ++++++++-- Cunkebao/src/store/module/ckchat/ckchat.ts | 21 ++++++++++++------- Cunkebao/src/utils/db-examples.ts | 12 +++++------ Cunkebao/src/utils/db.ts | 12 +++++------ 11 files changed, 62 insertions(+), 54 deletions(-) diff --git a/Cunkebao/src/pages/mobile/test/db.tsx b/Cunkebao/src/pages/mobile/test/db.tsx index b8d076e7..04330b40 100644 --- a/Cunkebao/src/pages/mobile/test/db.tsx +++ b/Cunkebao/src/pages/mobile/test/db.tsx @@ -25,7 +25,7 @@ import { DatabaseExamples } from "@/utils/db-examples"; import { db, kfUserService, - wechatGroupService, + weChatGroupService, contractService, DatabaseService, } from "@/utils/db"; @@ -74,7 +74,7 @@ const DatabaseTestPage: React.FC = () => { const newContactListService = new DatabaseService(db.newContractList); const stats = { kfUsers: await kfUserService.count(), - groups: await wechatGroupService.count(), + groups: await weChatGroupService.count(), contracts: await contractService.count(), newContactList: await newContactListService.count(), }; @@ -188,14 +188,14 @@ const DatabaseTestPage: React.FC = () => { selfDisplyName: "群管理员", }; - const groupId = await wechatGroupService.createWithServerId(serverGroup); + const groupId = await weChatGroupService.createWithServerId(serverGroup); addLog( "success", `创建群组成功,本地ID: ${groupId}, 服务器ID: ${serverGroup.id}`, ); // 更新群组 - await wechatGroupService.update(groupId, { + await weChatGroupService.update(groupId, { unreadCount: 5, notice: "更新的群公告", }); diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx index b25b1f86..7b11aa93 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx @@ -1,13 +1,13 @@ import React from "react"; import { List, Avatar, Badge } from "antd"; import { UserOutlined, TeamOutlined } from "@ant-design/icons"; -import { ContractData, wechatGroup } from "@/pages/pc/ckbox/data"; +import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import styles from "./MessageList.module.scss"; import { formatWechatTime } from "@/utils/common"; interface MessageListProps { - chatSessions: ContractData[] | wechatGroup[]; - currentChat: ContractData | wechatGroup; - onChatSelect: (chat: ContractData | wechatGroup) => void; + chatSessions: ContractData[] | weChatGroup[]; + currentChat: ContractData | weChatGroup; + onChatSelect: (chat: ContractData | weChatGroup) => void; } const MessageList: React.FC = ({ diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx index 76107e53..812636f5 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx @@ -3,12 +3,12 @@ import { List, Avatar, Collapse, Button } from "antd"; import type { CollapseProps } from "antd"; import styles from "./WechatFriends.module.scss"; import { useCkChatStore } from "@/store/module/ckchat/ckchat"; -import { ContractData, wechatGroup } from "@/pages/pc/ckbox/data"; +import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; interface WechatFriendsProps { - contracts: ContractData[] | wechatGroup[]; - onContactClick: (contract: ContractData | wechatGroup) => void; - selectedContactId?: ContractData | wechatGroup; + contracts: ContractData[] | weChatGroup[]; + onContactClick: (contract: ContractData | weChatGroup) => void; + selectedContactId?: ContractData | weChatGroup; } const ContactListSimple: React.FC = ({ @@ -16,6 +16,7 @@ const ContactListSimple: React.FC = ({ onContactClick, selectedContactId, }) => { + // const newContractList = useCkChatStore(state => state.newContractList); const newContractList = useCkChatStore(state => state.newContractList); const [activeKey, setActiveKey] = useState([]); // 默认展开第一个分组 diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx index fc649d2a..d948ff83 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx @@ -6,17 +6,17 @@ import { ChromeOutlined, MessageOutlined, } from "@ant-design/icons"; -import { ContractData, wechatGroup } from "@/pages/pc/ckbox/data"; +import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import WechatFriends from "./WechatFriends"; import MessageList from "./MessageList/index"; import styles from "./SidebarMenu.module.scss"; import { getChatSessions } from "@/store/module/ckchat/ckchat"; interface SidebarMenuProps { - contracts: ContractData[] | wechatGroup[]; - currentChat: ContractData | wechatGroup; - onContactClick: (contract: ContractData | wechatGroup) => void; - onChatSelect: (chat: ContractData | wechatGroup) => void; + contracts: ContractData[] | weChatGroup[]; + currentChat: ContractData | weChatGroup; + onContactClick: (contract: ContractData | weChatGroup) => void; + onChatSelect: (chat: ContractData | weChatGroup) => void; loading?: boolean; } diff --git a/Cunkebao/src/pages/pc/ckbox/data.ts b/Cunkebao/src/pages/pc/ckbox/data.ts index 8487b251..17f21f16 100644 --- a/Cunkebao/src/pages/pc/ckbox/data.ts +++ b/Cunkebao/src/pages/pc/ckbox/data.ts @@ -49,7 +49,7 @@ export interface CkAccount { } //群聊数据接口 -export interface wechatGroup { +export interface weChatGroup { id?: number; wechatAccountId: number; tenantId: number; diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx index 45f63da5..deb4e0e9 100644 --- a/Cunkebao/src/pages/pc/ckbox/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/index.tsx @@ -12,14 +12,14 @@ const { Header, Content, Sider } = Layout; import { chatInitAPIdata } from "./main"; import { KfUserListData, - wechatGroup, + weChatGroup, ContractData, } from "@/pages/pc/ckbox/data"; const CkboxPage: React.FC = () => { const [messageApi, contextHolder] = message.useMessage(); const [contracts, setContacts] = useState([]); - const [currentChat, setCurrentChat] = useState( + const [currentChat, setCurrentChat] = useState( null, ); @@ -57,7 +57,7 @@ const CkboxPage: React.FC = () => { }); }, []); - const handleContactClick = (contract: ContractData | wechatGroup) => { + const handleContactClick = (contract: ContractData | weChatGroup) => { addChatSession(contract); setCurrentChat(contract); }; diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts index e324fbcc..4583b73d 100644 --- a/Cunkebao/src/pages/pc/ckbox/main.ts +++ b/Cunkebao/src/pages/pc/ckbox/main.ts @@ -17,7 +17,7 @@ const { sendCommand } = useWebSocketStore.getState(); import { useUserStore } from "@/store/module/user"; import { ContractData, - wechatGroup, + weChatGroup, KfUserListData, } from "@/pages/pc/ckbox/data"; const { login2 } = useUserStore.getState(); @@ -45,9 +45,7 @@ export const chatInitAPIdata = async () => { const groupList = await getAllGroupList(); //构建联系人列表标签 - const newContractList = await createContractList(contractList, groupList); - console.log("分组信息", newContractList); - + const newContractList = await createContractList(); // 会话列表分组 asyncNewContractList(newContractList); //获取消息会话列表并按lastUpdateTime排序 @@ -96,10 +94,7 @@ export const chatInitAPIdata = async () => { }; //构建联系人列表标签 -export const createContractList = async ( - contractList: ContractData[], - groupList: wechatGroup[], -) => { +export const createContractList = async () => { const LablesRes = await Promise.all( [1, 2].map(item => WechatGroup({ @@ -126,11 +121,11 @@ export const createContractList = async ( ], ]; - console.log(countLables); + return countLables; // 根据countLables中的groupName整理contractList数据 // 返回按标签分组的联系人数组,包括未分组标签(在数组最后) - return organizeContactsByLabels(countLables, contractList, groupList); + // return organizeContactsByLabels(countLables, contractList, groupList); }; /** @@ -142,7 +137,7 @@ export const createContractList = async ( export const organizeContactsByLabels = ( countLables: any[], contractList: ContractData[], - groupList: wechatGroup[], + groupList: weChatGroup[], ) => { // 创建结果对象,用于存储按标签分组的联系人 const result: { [key: string]: any[] } = {}; diff --git a/Cunkebao/src/store/module/ckchat/ckchat.data.ts b/Cunkebao/src/store/module/ckchat/ckchat.data.ts index 43b897e3..7ee70b61 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.data.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.data.ts @@ -1,4 +1,10 @@ -import { ContractData, KfUserListData, CkAccount } from "@/pages/pc/ckbox/data"; +import { + ContractData, + KfUserListData, + CkAccount, + ContactGroupByLabel, + weChatGroup, +} from "@/pages/pc/ckbox/data"; // 权限片段接口 export interface PrivilegeFrag { @@ -32,8 +38,9 @@ export interface CkChatState { kfUserList: KfUserListData[]; kfSelected: number; getKfSelectedUser: () => KfUserListData | undefined; - newContractList: { groupName: string; contacts: any[] }[]; + newContractList: ContactGroupByLabel[]; asyncKfSelected: (data: number) => void; + asyncWeChatGroup: (data: weChatGroup[]) => void; getkfUserList: () => KfUserListData[]; asyncKfUserList: (data: KfUserListData[]) => void; asyncContractList: (data: ContractData[]) => void; diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts index e5bb1fe6..5bbd149b 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.ts @@ -2,9 +2,10 @@ import { createPersistStore } from "@/store/createPersistStore"; import { CkChatState, CkUserInfo, CkTenant } from "./ckchat.data"; import { ContractData, - wechatGroup, + weChatGroup, CkAccount, KfUserListData, + ContactGroupByLabel, } from "@/pages/pc/ckbox/data"; import { kfUserService } from "@/utils/db"; export const useCkChatStore = createPersistStore( @@ -14,8 +15,8 @@ export const useCkChatStore = createPersistStore( contractList: [], //联系人列表 chatSessions: [], //聊天会话 kfUserList: [], //客服列表 - kfSelected: 0, newContractList: [], //联系人分组 + kfSelected: 0, //客服列表 asyncKfUserList: async data => { await kfUserService.createManyWithServerId(data); @@ -28,7 +29,7 @@ export const useCkChatStore = createPersistStore( set({ kfSelected: data }); }, // 异步设置会话列表 - asyncNewContractList: data => { + asyncNewContractList: (data: ContactGroupByLabel[]) => { set({ newContractList: data }); }, getNewContractList: () => { @@ -43,6 +44,10 @@ export const useCkChatStore = createPersistStore( asyncContractList: data => { set({ contractList: data }); }, + //异步设置联系人分组 + asyncWeChatGroup: (data: ContactGroupByLabel[]) => { + set({ weChatGroup: data }); + }, //获取选中的客服信息 getgetKfSelectedUser: () => { const state = useCkChatStore.getState(); @@ -73,7 +78,7 @@ export const useCkChatStore = createPersistStore( return state.chatSessions; }, // 添加聊天会话 - addChatSession: (session: ContractData | wechatGroup) => { + addChatSession: (session: ContractData | weChatGroup) => { set(state => { // 检查是否已存在相同id的会话 const exists = state.chatSessions.some(item => item.id === session.id); @@ -81,12 +86,12 @@ export const useCkChatStore = createPersistStore( return { chatSessions: exists ? state.chatSessions - : [...state.chatSessions, session as ContractData | wechatGroup], + : [...state.chatSessions, session as ContractData | weChatGroup], }; }); }, // 更新聊天会话 - updateChatSession: (session: ContractData | wechatGroup) => { + updateChatSession: (session: ContractData | weChatGroup) => { set(state => ({ chatSessions: state.chatSessions.map(item => item.id === session.id ? session : item, @@ -181,9 +186,9 @@ export const getCkAccountName = () => export const getCkTenantName = () => useCkChatStore.getState().getTenantName(); export const getChatSessions = () => useCkChatStore.getState().getChatSessions(); -export const addChatSession = (session: ContractData | wechatGroup) => +export const addChatSession = (session: ContractData | weChatGroup) => useCkChatStore.getState().addChatSession(session); -export const updateChatSession = (session: ContractData | wechatGroup) => +export const updateChatSession = (session: ContractData | weChatGroup) => useCkChatStore.getState().updateChatSession(session); export const deleteChatSession = (sessionId: string) => useCkChatStore.getState().deleteChatSession(sessionId); diff --git a/Cunkebao/src/utils/db-examples.ts b/Cunkebao/src/utils/db-examples.ts index 0127da81..a3dd1ca6 100644 --- a/Cunkebao/src/utils/db-examples.ts +++ b/Cunkebao/src/utils/db-examples.ts @@ -1,14 +1,14 @@ import { db, kfUserService, - wechatGroupService, + weChatGroupService, contractService, DatabaseService, NewContactListData, } from "./db"; import { KfUserListData, - wechatGroup, + weChatGroup, ContractData, } from "@/pages/pc/ckbox/data"; @@ -132,7 +132,7 @@ export class DatabaseExamples { try { // 使用新方法创建群组 - const groupId = await wechatGroupService.createWithServerId(serverGroup); + const groupId = await weChatGroupService.createWithServerId(serverGroup); console.log( "创建群组成功,本地ID:", groupId, @@ -141,18 +141,18 @@ export class DatabaseExamples { ); // 查询群组 - const groups = await wechatGroupService.findWhere("tenantId", 1); + const groups = await weChatGroupService.findWhere("tenantId", 1); console.log("租户群组数量:", groups.length); // 查询有未读消息的群组 - const unreadGroups = await wechatGroupService.findWhereGreaterThan( + const unreadGroups = await weChatGroupService.findWhereGreaterThan( "unreadCount", 0, ); console.log("有未读消息的群组:", unreadGroups.length); // 批量更新群组 - await wechatGroupService.updateMany([ + await weChatGroupService.updateMany([ { id: groupId, data: { unreadCount: 5, notice: "更新的群公告" }, diff --git a/Cunkebao/src/utils/db.ts b/Cunkebao/src/utils/db.ts index e779d99d..33ccc16b 100644 --- a/Cunkebao/src/utils/db.ts +++ b/Cunkebao/src/utils/db.ts @@ -28,7 +28,7 @@ import Dexie, { Table } from "dexie"; import { KfUserListData, - wechatGroup, + weChatGroup, ContractData, } from "@/pages/pc/ckbox/data"; @@ -38,7 +38,7 @@ export interface KfUserWithServerId extends Omit { id?: number; // 接口数据的原始ID字段 } -export interface GroupWithServerId extends Omit { +export interface GroupWithServerId extends Omit { serverId: number | string; // 服务器ID作为主键 id?: number; // 接口数据的原始ID字段 } @@ -54,13 +54,13 @@ export interface NewContactListData { id?: number; // 接口数据的原始ID字段 groupName: string; contacts: ContractData[]; - wechatGroup: wechatGroup[]; + weChatGroup: weChatGroup[]; } // 数据库类 class CunkebaoDatabase extends Dexie { kfUsers!: Table; - wechatGroup!: Table; + weChatGroup!: Table; contracts!: Table; newContractList!: Table; @@ -71,7 +71,7 @@ class CunkebaoDatabase extends Dexie { this.version(1).stores({ kfUsers: "serverId, id, tenantId, wechatId, nickname, alias, avatar, gender, region, signature, bindQQ, bindEmail, bindMobile, createTime, currentDeviceId, isDeleted, deleteTime, groupId, memo, wechatVersion, lastUpdateTime, isOnline", - wechatGroup: + weChatGroup: "serverId, id, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar, groupId, config, unreadCount, notice, selfDisplyName", contracts: "serverId, id, wechatAccountId, wechatId, alias, conRemark, nickname, quanPin, avatar, gender, region, addFrom, phone, signature, accountId, extendFields, city, lastUpdateTime, isPassed, tenantId, groupId, thirdParty, additionalPicture, desc, config, lastMessageTime, unreadCount, duplicate", @@ -316,7 +316,7 @@ export class DatabaseService { // 创建各表的服务实例 export const kfUserService = new DatabaseService(db.kfUsers); -export const wechatwechatGroupService = new DatabaseService(db.wechatGroup); +export const weChatGroupService = new DatabaseService(db.weChatGroup); export const contractService = new DatabaseService(db.contracts); // 默认导出数据库实例 From 14f0a6d7f33908f5aeebacacb11fa58e0cb9b1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 30 Aug 2025 15:55:54 +0800 Subject: [PATCH 010/146] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E6=B8=85=E7=90=86=E4=B8=AD=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E7=BE=A4=E7=9A=84=E8=A1=A8=E5=90=8D=E6=8B=BC=E5=86=99=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/src/main.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cunkebao/src/main.tsx b/Cunkebao/src/main.tsx index 612fb438..813ccaed 100644 --- a/Cunkebao/src/main.tsx +++ b/Cunkebao/src/main.tsx @@ -15,7 +15,7 @@ async function initializeApp() { // 调试模式:清理所有数据 console.log("调试模式:开始清理数据库数据..."); await db.kfUsers.clear(); - await db.wechatGroup.clear(); + await db.weChatGroup.clear(); await db.contracts.clear(); await db.newContractList.clear(); console.log("数据库数据清理完成"); From 0975995486dbdf5b53bd90113ae43b8bb1406283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 30 Aug 2025 17:02:31 +0800 Subject: [PATCH 011/146] =?UTF-8?q?feat(ckbox):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E8=81=94=E7=B3=BB=E4=BA=BA=E5=88=86=E7=BB=84?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=B9=B6=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在ContactGroupByLabel和weChatGroup接口中添加count和labels字段 - 将weChatGroup重命名为weChatGroupServerId以更准确描述用途 - 修改数据库操作,使用service直接处理数据存储 - 重构createContractList函数,根据groupType查询不同服务获取数据 - 移除VerticalUserList中不必要的异步获取逻辑,直接使用store数据 --- Cunkebao/src/pages/mobile/test/db.tsx | 4 +- .../SidebarMenu/WechatFriends/index.tsx | 2 +- .../components/VerticalUserList/index.tsx | 30 +++++++------- Cunkebao/src/pages/pc/ckbox/data.ts | 2 + Cunkebao/src/pages/pc/ckbox/main.ts | 40 ++++++++++++++----- Cunkebao/src/store/module/ckchat/ckchat.ts | 15 ++++--- Cunkebao/src/utils/db.ts | 4 +- 7 files changed, 62 insertions(+), 35 deletions(-) diff --git a/Cunkebao/src/pages/mobile/test/db.tsx b/Cunkebao/src/pages/mobile/test/db.tsx index 04330b40..9843dbc7 100644 --- a/Cunkebao/src/pages/mobile/test/db.tsx +++ b/Cunkebao/src/pages/mobile/test/db.tsx @@ -331,10 +331,10 @@ const DatabaseTestPage: React.FC = () => { try { await db.transaction( "rw", - [db.kfUsers, db.groups, db.contracts, db.newContractList], + [db.kfUsers, db.weChatGroups, db.contracts, db.newContractList], async () => { await db.kfUsers.clear(); - await db.groups.clear(); + await db.weChatGroups.clear(); await db.contracts.clear(); await db.newContractList.clear(); }, diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx index 812636f5..82b227bc 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx @@ -16,8 +16,8 @@ const ContactListSimple: React.FC = ({ onContactClick, selectedContactId, }) => { - // const newContractList = useCkChatStore(state => state.newContractList); const newContractList = useCkChatStore(state => state.newContractList); + const [activeKey, setActiveKey] = useState([]); // 默认展开第一个分组 // 分页加载相关状态 diff --git a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx index ff4af178..b0c28db0 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx @@ -14,24 +14,24 @@ const VerticalUserList: React.FC = () => { const handleUserSelect = (userId: number) => { asyncKfSelected(userId); }; - const getkfUserList = useCkChatStore(state => state.getkfUserList); + const kfUserList = useCkChatStore(state => state.kfUserList); const kfSelected = useCkChatStore(state => state.kfSelected); const [kefuList, setKefuList] = useState([]); - // 获取客服列表数据 - useEffect(() => { - const fetchKfUserList = async () => { - try { - const data = await getkfUserList(); - setKefuList(data || []); - } catch (error) { - console.error("获取客服列表失败:", error); - setKefuList([]); - } - }; + // // 获取客服列表数据 + // useEffect(() => { + // const fetchKfUserList = async () => { + // try { + // const data = await getkfUserList(); + // setKefuList(data || []); + // } catch (error) { + // console.error("获取客服列表失败:", error); + // setKefuList([]); + // } + // }; - fetchKfUserList(); - }, [getkfUserList]); + // fetchKfUserList(); + // }, [getkfUserList]); return (
{
全部好友
- {kefuList.map(user => ( + {kfUserList.map(user => (
{ await getControlTerminalListByWechatAccountIds(uniqueWechatAccountIds); //获取用户列表 - asyncKfUserList(kfUserList); + await asyncKfUserList(kfUserList); //获取群列表 const groupList = await getAllGroupList(); + await asyncWeChatGroup(groupList); + //构建联系人列表标签 const newContractList = await createContractList(); // 会话列表分组 - asyncNewContractList(newContractList); + await asyncNewContractList(newContractList); //获取消息会话列表并按lastUpdateTime排序 const filterUserSessions = contractList?.filter( v => v?.config && v.config?.chat, @@ -103,29 +107,47 @@ export const createContractList = async () => { ), ); const [friend, group] = LablesRes; - const lastIndex = friend.length + group.length + 1; const countLables = [ ...[ { id: 0, groupName: "默认群分组", + groupType: 2, }, ], - ...friend, ...group, + ...friend, ...[ { - id: lastIndex, + id: 0, groupName: "未分组", + groupType: 1, }, ], ]; - return countLables; + // 根据 groupType 决定查询不同的服务 + const dataByLabels = []; + for (const label of countLables) { + let data; + if (label.groupType === 1) { + // groupType: 1, 查询 contractService + data = await contractService.findWhere("groupId", label.id); + // console.log(`标签 ${label.groupName} 对应的联系人数据:`, data); + } else if (label.groupType === 2) { + // groupType: 2, 查询 weChatGroupService + data = await weChatGroupService.findWhere("groupId", label.id); + } else { + console.warn(`未知的 groupType: ${label.groupType}`); + data = []; + } + dataByLabels.push({ + ...label, + contacts: data, + }); + } - // 根据countLables中的groupName整理contractList数据 - // 返回按标签分组的联系人数组,包括未分组标签(在数组最后) - // return organizeContactsByLabels(countLables, contractList, groupList); + return dataByLabels; }; /** diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts index 5bbd149b..73749218 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.ts @@ -7,7 +7,7 @@ import { KfUserListData, ContactGroupByLabel, } from "@/pages/pc/ckbox/data"; -import { kfUserService } from "@/utils/db"; +import { kfUserService, weChatGroupService, contractService } from "@/utils/db"; export const useCkChatStore = createPersistStore( set => ({ userInfo: null, @@ -19,7 +19,8 @@ export const useCkChatStore = createPersistStore( kfSelected: 0, //客服列表 asyncKfUserList: async data => { - await kfUserService.createManyWithServerId(data); + set({ kfUserList: data }); + // await kfUserService.createManyWithServerId(data); }, // 获取客服列表 getkfUserList: async () => { @@ -41,12 +42,12 @@ export const useCkChatStore = createPersistStore( set({ chatSessions: data }); }, // 异步设置联系人列表 - asyncContractList: data => { - set({ contractList: data }); + asyncContractList: async (data: ContractData[]) => { + await contractService.createManyWithServerId(data); }, //异步设置联系人分组 - asyncWeChatGroup: (data: ContactGroupByLabel[]) => { - set({ weChatGroup: data }); + asyncWeChatGroup: async (data: weChatGroup[]) => { + await weChatGroupService.createManyWithServerId(data); }, //获取选中的客服信息 getgetKfSelectedUser: () => { @@ -210,3 +211,5 @@ export const asyncNewContractList = ( ) => useCkChatStore.getState().asyncNewContractList(data); export const asyncKfSelected = (data: number) => useCkChatStore.getState().asyncKfSelected(data); +export const asyncWeChatGroup = (data: weChatGroup[]) => + useCkChatStore.getState().asyncWeChatGroup(data); diff --git a/Cunkebao/src/utils/db.ts b/Cunkebao/src/utils/db.ts index 33ccc16b..3257fae1 100644 --- a/Cunkebao/src/utils/db.ts +++ b/Cunkebao/src/utils/db.ts @@ -38,7 +38,7 @@ export interface KfUserWithServerId extends Omit { id?: number; // 接口数据的原始ID字段 } -export interface GroupWithServerId extends Omit { +export interface weChatGroupServerId extends Omit { serverId: number | string; // 服务器ID作为主键 id?: number; // 接口数据的原始ID字段 } @@ -60,7 +60,7 @@ export interface NewContactListData { // 数据库类 class CunkebaoDatabase extends Dexie { kfUsers!: Table; - weChatGroup!: Table; + weChatGroup!: Table; contracts!: Table; newContractList!: Table; From b780a4db582c8006068e37f4ddc8f6948b2c6738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 30 Aug 2025 17:07:08 +0800 Subject: [PATCH 012/146] =?UTF-8?q?fix(ckchat):=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E9=80=89=E4=B8=AD=E5=AE=A2=E6=9C=8D=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E7=9A=84=E6=96=B9=E6=B3=95=E5=90=8D=E6=8B=BC=E5=86=99?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/src/store/module/ckchat/ckchat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts index 73749218..02abe08e 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.ts @@ -50,7 +50,7 @@ export const useCkChatStore = createPersistStore( await weChatGroupService.createManyWithServerId(data); }, //获取选中的客服信息 - getgetKfSelectedUser: () => { + getKfSelectedUser: () => { const state = useCkChatStore.getState(); return state.kfUserList.find(item => item.id === state.kfSelected); }, From b9d88160a2633c603f3aef4a264bc7d1ba67fe42 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Sat, 30 Aug 2025 17:07:53 +0800 Subject: [PATCH 013/146] =?UTF-8?q?=E8=A7=A6=E5=AE=A2=E5=AE=9D=E5=8F=B3?= =?UTF-8?q?=E8=BE=B9=E6=A0=8F=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Person/Person.module.scss | 30 +++- .../ChatWindow/components/Person/index.tsx | 157 ++++++++++-------- .../pc/ckbox/components/ChatWindow/index.tsx | 2 +- Cunkebao/src/store/module/ckchat.data.ts | 2 +- Cunkebao/src/store/module/ckchat.ts | 5 +- 5 files changed, 115 insertions(+), 81 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/Person.module.scss b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/Person.module.scss index 7d96cc69..10fb299b 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/Person.module.scss +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/Person.module.scss @@ -18,7 +18,7 @@ .closeButton { color: #8c8c8c; - + &:hover { color: #262626; background: #f5f5f5; @@ -44,7 +44,7 @@ font-size: 18px; font-weight: 600; color: #262626; - max-width: 200px; + max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -52,12 +52,12 @@ .profileRemark { margin-bottom: 12px; - + .remarkText { color: #8c8c8c; font-size: 14px; cursor: pointer; - + &:hover { color: #1890ff; } @@ -107,7 +107,7 @@ display: flex; align-items: center; margin-bottom: 12px; - + &:last-child { margin-bottom: 0; } @@ -131,6 +131,20 @@ font-size: 14px; flex: 1; word-break: break-all; + + // 备注编辑区域样式 + :global(.ant-input) { + font-size: 14px; + } + + :global(.ant-btn) { + font-size: 12px; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + } } } @@ -169,7 +183,7 @@ @media (max-width: 768px) { .profileSider { width: 280px !important; - + .profileContainer { padding: 12px; } @@ -189,7 +203,7 @@ .infoItem { margin-bottom: 10px; - + .infoLabel { width: 50px; font-size: 13px; @@ -201,4 +215,4 @@ } } } -} \ No newline at end of file +} diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx index da0dea8b..776f7bfe 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx @@ -43,14 +43,33 @@ const Person: React.FC = ({ const [messageApi, contextHolder] = message.useMessage(); const [isEditingRemark, setIsEditingRemark] = useState(false); const [remarkValue, setRemarkValue] = useState(contract.conRemark || ""); + const [selectedTags, setSelectedTags] = useState(contract.labels || []); + const [allAvailableTags, setAllAvailableTags] = useState([]); - const kfSelectedUser = useCkChatStore(state => state.kfSelectedUser()); + const kfSelectedUser = useCkChatStore(state => state.kfSelectedUser(contract.wechatAccountId || 0)); - // 当contract变化时更新备注值 + // 获取所有可用标签 + useEffect(() => { + const fetchAvailableTags = async () => { + try { + // 从kfSelectedUser.labels和contract.labels合并获取所有标签 + const kfTags = kfSelectedUser?.labels || []; + const contractTags = contract.labels || []; + const allTags = [...new Set([...kfTags, ...contractTags])]; + setAllAvailableTags(allTags); + } catch (error) { + console.error('获取标签失败:', error); + } + }; + fetchAvailableTags(); + }, [kfSelectedUser, contract.labels]); + + // 当contract变化时更新备注值和标签 useEffect(() => { setRemarkValue(contract.conRemark || ""); setIsEditingRemark(false); - }, [contract.conRemark]); + setSelectedTags(contract.labels || []); + }, [contract.conRemark, contract.labels]); // 处理备注保存 const handleSaveRemark = () => { @@ -67,6 +86,18 @@ const Person: React.FC = ({ setIsEditingRemark(false); }; + // 处理标签点击切换 + const handleTagToggle = (tagName: string) => { + const newSelectedTags = selectedTags.includes(tagName) + ? selectedTags.filter(tag => tag !== tagName) + : [...selectedTags, tagName]; + + setSelectedTags(newSelectedTags); + + // 这里应该调用API保存标签到后端 + messageApi.success(`标签"${tagName}"${selectedTags.includes(tagName) ? '已取消' : '已选中'}`); + }; + // 模拟联系人详细信息 const contractInfo = { name: contract.name, @@ -80,10 +111,10 @@ const Person: React.FC = ({ department: contract.department || "-", position: contract.position || "-", company: contract.company || "-", - location: contract.location || "-", + region: contract.region || "-", joinDate: contract.joinDate || "-", status: "在线", - tags: contract.labels, + tags: selectedTags, bio: contract.bio || "-", }; @@ -123,8 +154,36 @@ const Person: React.FC = ({ -
- {JSON.stringify(kfSelectedUser)} + + +
+ + {contractInfo.status} +
+
+
+ + {/* 详细信息卡片 */} + +
+ + 微信号: + {contractInfo.alias || contractInfo.wechatId} +
+
+ + 电话: + {contractInfo.phone} +
+
+ + 地区: + {contractInfo.region} +
+
+ + 备注: +
{isEditingRemark ? (
= ({ gap: "8px", }} > - + {contractInfo.conRemark || "点击添加备注"}
)}
- -
- - {contractInfo.status} -
-
-
- - {/* 详细信息卡片 */} - -
- - 微信号: - {contractInfo.wechatId} -
-
- - 昵称: - {contractInfo.alias} -
-
- - 电话: - {contractInfo.phone} -
-
- - 邮箱: - {contractInfo.email} -
-
- - 部门: - - {contractInfo.department} - -
-
- - 职位: - {contractInfo.position} -
-
- - 公司: - {contractInfo.company} -
-
- - 地区: - {contractInfo.location} -
-
- - 入职时间: - {contractInfo.joinDate}
{/* 标签 */}
- {contractInfo.tags?.map((tag, index) => ( - - {tag} - - ))} + {/* 渲染所有可用标签,选中的排在前面 */} + {[...new Set([...selectedTags, ...allAvailableTags])].map((tag, index) => { + const isSelected = selectedTags.includes(tag); + return ( + handleTagToggle(tag)} + > + {tag} + + ); + })} + {allAvailableTags.length === 0 && ( + 暂无可用标签 + )}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index 037edf5f..b693b6b8 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -68,7 +68,7 @@ const ChatWindow: React.FC = ({ >({}); const messagesEndRef = useRef(null); - const kfSelectedUser = useCkChatStore(state => state.kfSelectedUser()); + const kfSelectedUser = useCkChatStore(state => state.kfSelectedUser(contract.wechatAccountId || 0)); useEffect(() => { clearUnreadCount([contract.id]).then(() => { setLoading(true); diff --git a/Cunkebao/src/store/module/ckchat.data.ts b/Cunkebao/src/store/module/ckchat.data.ts index 7280ee7e..87d48c4a 100644 --- a/Cunkebao/src/store/module/ckchat.data.ts +++ b/Cunkebao/src/store/module/ckchat.data.ts @@ -31,7 +31,7 @@ export interface CkChatState { chatSessions: any[]; kfUserList: KfUserListData[]; kfSelected: number; - kfSelectedUser: () => KfUserListData | undefined; + kfSelectedUser: (kfId: number) => KfUserListData | undefined; newContractList: { groupName: string; contacts: any[] }[]; asyncKfSelected: (data: number) => void; getkfUserList: () => KfUserListData[]; diff --git a/Cunkebao/src/store/module/ckchat.ts b/Cunkebao/src/store/module/ckchat.ts index 591a2bf1..edcee848 100644 --- a/Cunkebao/src/store/module/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat.ts @@ -16,9 +16,10 @@ export const useCkChatStore = createPersistStore( kfUserList: [], //客服列表 kfSelected: 0, newContractList: [], //联系人分组 - kfSelectedUser: () => { + kfSelectedUser: (kfId:number) => { const state = useCkChatStore.getState(); - return state.kfUserList.find(item => item.id === state.kfSelected); + + return state.kfUserList.find(item => item.id === (kfId)); }, asyncKfSelected: (data: number) => { set({ kfSelected: data }); From 8e16067cf3e11beb69bd4bb698df8bf795929e10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 30 Aug 2025 17:08:06 +0800 Subject: [PATCH 014/146] =?UTF-8?q?fix(SidebarMenu/WechatFriends):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=B4=E5=83=8F=E6=98=BE=E7=A4=BA=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E4=BC=98=E5=85=88=E5=B1=95=E7=A4=BAchatroomA?= =?UTF-8?q?vatar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当contact.avatar不存在时,现在会尝试使用contact.chatroomAvatar作为头像来源 --- .../ckbox/components/SidebarMenu/WechatFriends/index.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx index 82b227bc..042e9fa4 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx @@ -37,8 +37,12 @@ const ContactListSimple: React.FC = ({ >
{contact.nickname.charAt(0)}} + src={contact.avatar || contact.chatroomAvatar} + icon={ + !(contact.avatar || contact.chatroomAvatar) && ( + {contact.nickname.charAt(0)} + ) + } className={styles.avatar} />
From 343c142d876f6f04a77f401d2e0750a5cc3ef12b Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Sat, 30 Aug 2025 17:14:57 +0800 Subject: [PATCH 015/146] =?UTF-8?q?=E9=97=A8=E5=BA=97=E7=AB=AF=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/controller/CustomerController.php | 38 ++++--- .../store/controller/StatisticsController.php | 100 ++++++++++++------ Store_vue/App.vue | 36 ++++++- Store_vue/api/config/index.js | 4 +- Store_vue/components/CustomerManagement.vue | 2 +- Store_vue/components/DataStatistics.vue | 2 +- Store_vue/components/LoginRegister.vue | 2 +- Store_vue/components/PackageDetail.vue | 2 +- Store_vue/components/SideMenu.vue | 9 +- Store_vue/components/SupplyChainPurchase.vue | 2 +- Store_vue/components/SupplyItemDetail.vue | 2 +- Store_vue/components/SystemSettings.vue | 2 +- Store_vue/components/TrafficPurchase.vue | 2 +- Store_vue/pages/chat/index.vue | 2 +- Store_vue/pages/login/index.vue | 18 ++-- 15 files changed, 147 insertions(+), 76 deletions(-) diff --git a/Server/application/store/controller/CustomerController.php b/Server/application/store/controller/CustomerController.php index 9bec2a68..e61e62e7 100644 --- a/Server/application/store/controller/CustomerController.php +++ b/Server/application/store/controller/CustomerController.php @@ -27,24 +27,33 @@ class CustomerController extends Api $pageSize = isset($params['pageSize']) ? intval($params['pageSize']) : 10; $userInfo = request()->userInfo; - + $where = []; // 必要的查询条件 $userId = $userInfo['id']; $companyId = $userInfo['companyId']; - + if (empty($userId) || empty($companyId)) { return errorJson('缺少必要参数'); } // 构建查询条件 - $where = [ - 'du.userId' => $userId, - 'du.companyId' => $companyId - ]; - + $deviceIds = Db::name('device_user')->where(['userId' => $userId, 'companyId' => $companyId])->order('id DESC')->column('deviceId'); + if (empty($deviceIds)) { + return errorJson('设备不存在'); + } + $wechatIds = []; + foreach ($deviceIds as $deviceId) { + $wechatIds[] = Db::name('device_wechat_login') + ->where(['deviceId' => $deviceId]) + ->order('id DESC') + ->value('wechatId'); + } + + + // 搜索条件 if (!empty($params['keyword'])) { - $where['wf.alias|wf.nickname|wf.wechatId'] = ['like', '%' . $params['keyword'] . '%']; + $where['alias|nickname|wechatId'] = ['like', '%' . $params['keyword'] . '%']; } // if (!empty($params['email'])) { // $where['wa.bindEmail'] = ['like', '%' . $params['email'] . '%']; @@ -54,14 +63,10 @@ class CustomerController extends Api // } // 构建查询 - $query = Db::name('device_user') - ->alias('du') - ->join(['s2_device' => 'd'], 'd.id = du.deviceId','left') - ->join(['s2_wechat_account' => 'wa'], 'wa.imei = d.imei','left') - ->join(['s2_wechat_friend' => 'wf'], 'wf.ownerWechatId = wa.wechatId','left') + $query = Db::table('s2_wechat_friend') ->where($where) - ->field('d.id as deviceId,d.imei,wf.*') - ->group('wf.wechatId'); // 防止重复数据 + ->whereIn('ownerWechatId',$wechatIds) + ->group('wechatId'); // 防止重复数据 // 克隆查询对象,用于计算总数 $countQuery = clone $query; @@ -69,10 +74,9 @@ class CustomerController extends Api // 获取分页数据 $list = $query->page($page, $pageSize) - ->order('wa.id DESC') + ->order('id DESC') ->select(); - // 格式化数据 foreach ($list as &$item) { diff --git a/Server/application/store/controller/StatisticsController.php b/Server/application/store/controller/StatisticsController.php index 56ac7c2a..11cda5fb 100644 --- a/Server/application/store/controller/StatisticsController.php +++ b/Server/application/store/controller/StatisticsController.php @@ -4,7 +4,8 @@ namespace app\store\controller; use app\store\model\WechatFriendModel; use app\store\model\WechatMessageModel; -use think\facade\Db; +use think\Db; + /** * 数据统计控制器 @@ -18,8 +19,23 @@ class StatisticsController extends BaseController { try { $companyId = $this->userInfo['companyId']; - $wechatAccountId = $this->device['wechatAccountId']; - $ownerWechatId = $this->device['wechatId']; + $userId = $this->userInfo['id']; + + // 构建查询条件 + $deviceIds = Db::name('device_user')->where(['userId' => $userId, 'companyId' => $companyId])->order('id DESC')->column('deviceId'); + if (empty($deviceIds)) { + return errorJson('设备不存在'); + } + $ownerWechatIds = []; + foreach ($deviceIds as $deviceId) { + $ownerWechatIds[] = Db::name('device_wechat_login') + ->where(['deviceId' => $deviceId]) + ->order('id DESC') + ->value('wechatId'); + } + + $wechatAccountIds = Db::table('s2_wechat_account')->whereIn('wechatId',$ownerWechatIds)->column('id'); + // 获取时间范围 $timeRange = $this->getTimeRange(); @@ -33,37 +49,37 @@ class StatisticsController extends BaseController // 1. 总客户数 - $totalCustomers = WechatFriendModel::where(['ownerWechatId'=> $ownerWechatId]) + $totalCustomers = WechatFriendModel::whereIn('ownerWechatId',$ownerWechatIds) ->whereTime('createTime', '>=', $startTime) ->whereTime('createTime', '<', $endTime) ->count(); // 上期总客户数 - $lastTotalCustomers = WechatFriendModel::where(['ownerWechatId'=> $ownerWechatId]) + $lastTotalCustomers = WechatFriendModel::whereIn('ownerWechatId',$ownerWechatIds) ->whereTime('createTime', '>=', $lastStartTime) ->whereTime('createTime', '<', $lastEndTime) ->count(); // 2. 新增客户数 - $newCustomers = WechatFriendModel::where(['ownerWechatId'=> $ownerWechatId]) + $newCustomers = WechatFriendModel::whereIn('ownerWechatId',$ownerWechatIds) ->whereTime('createTime', '>=', $startTime) ->whereTime('createTime', '<', $endTime) ->count(); // 上期新增客户数 - $lastNewCustomers = WechatFriendModel::where(['ownerWechatId'=> $ownerWechatId]) + $lastNewCustomers = WechatFriendModel::whereIn('ownerWechatId',$ownerWechatIds) ->whereTime('createTime', '>=', $lastStartTime) ->whereTime('createTime', '<', $lastEndTime) ->count(); //3. 互动次数 - $interactionCount = WechatMessageModel::where(['wechatAccountId'=> $wechatAccountId]) + $interactionCount = WechatMessageModel::whereIn('wechatAccountId', $wechatAccountIds) ->where('createTime', '>=', $startTime) ->where('createTime', '<', $endTime) ->count(); // 上期互动次数 - $lastInteractionCount = WechatMessageModel::where(['wechatAccountId'=> $wechatAccountId]) + $lastInteractionCount = WechatMessageModel::whereIn('wechatAccountId', $wechatAccountIds) ->where('createTime', '>=', $lastStartTime) ->where('createTime', '<', $lastEndTime) ->count(); @@ -103,10 +119,20 @@ class StatisticsController extends BaseController { try { $companyId = $this->userInfo['companyId']; - $wechatAccountId = $this->device['wechatAccountId']; - $ownerWechatId = $this->device['wechatId']; - + $userId = $this->userInfo['id']; + // 构建查询条件 + $deviceIds = Db::name('device_user')->where(['userId' => $userId, 'companyId' => $companyId])->order('id DESC')->column('deviceId'); + if (empty($deviceIds)) { + return errorJson('设备不存在'); + } + $ownerWechatIds = []; + foreach ($deviceIds as $deviceId) { + $ownerWechatIds[] = Db::name('device_wechat_login') + ->where(['deviceId' => $deviceId]) + ->order('id DESC') + ->value('wechatId'); + } @@ -116,35 +142,35 @@ class StatisticsController extends BaseController $endTime = $timeRange['end_time']; // 1. 客户增长趋势数据 - $totalCustomers = WechatFriendModel::where(['ownerWechatId'=> $ownerWechatId]) + $totalCustomers = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds) ->whereTime('createTime', '<', $endTime) ->count(); - $newCustomers = WechatFriendModel::where(['ownerWechatId'=> $ownerWechatId]) + $newCustomers = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds) ->whereTime('createTime', '>=', $startTime) ->whereTime('createTime', '<', $endTime) ->count(); // 计算流失客户数(假设超过30天未互动的客户为流失客户) $thirtyDaysAgo = strtotime('-30 days'); - $lostCustomers = WechatFriendModel::where(['ownerWechatId'=> $ownerWechatId]) + $lostCustomers = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds) ->where('createTime', '>', 0) ->where('deleteTime', '<', $thirtyDaysAgo) ->count(); // 2. 客户来源分布数据 // 朋友推荐 - $friendRecommend = WechatFriendModel::where(['ownerWechatId'=> $ownerWechatId]) + $friendRecommend = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds) // ->whereIn('addFrom', [17, 1000017]) ->count(); // 微信搜索 - $wechatSearch = WechatFriendModel::where(['ownerWechatId'=> $ownerWechatId]) + $wechatSearch = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds) // ->whereIn('addFrom', [3, 15, 1000003, 1000015]) ->count(); // 微信群 - $wechatGroup = WechatFriendModel::where(['ownerWechatId'=> $ownerWechatId]) + $wechatGroup = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds) // ->whereIn('addFrom', [14, 1000014]) ->count(); @@ -202,9 +228,23 @@ class StatisticsController extends BaseController public function getInteractionAnalysis() { try { - $companyId = $this->userInfo['companyId']; - $wechatAccountId = $this->device['wechatAccountId']; - + $companyId = $this->userInfo['companyId']; + $userId = $this->userInfo['id']; + + // 构建查询条件 + $deviceIds = Db::name('device_user')->where(['userId' => $userId, 'companyId' => $companyId])->order('id DESC')->column('deviceId'); + if (empty($deviceIds)) { + return errorJson('设备不存在'); + } + $ownerWechatIds = []; + foreach ($deviceIds as $deviceId) { + $ownerWechatIds[] = Db::name('device_wechat_login') + ->where(['deviceId' => $deviceId]) + ->order('id DESC') + ->value('wechatId'); + } + $wechatAccountIds = Db::table('s2_wechat_account')->whereIn('wechatId',$ownerWechatIds)->column('id'); + // 获取时间范围 $timeRange = $this->getTimeRange(); $startTime = $timeRange['start_time']; @@ -216,7 +256,7 @@ class StatisticsController extends BaseController // 1. 互动频率分析 // 高频互动用户数(每天3次以上) - $highFrequencyUsers = WechatMessageModel::where(['wechatAccountId' => $wechatAccountId]) + $highFrequencyUsers = WechatMessageModel::whereIn('wechatAccountId' , $wechatAccountIds) ->where('createTime', '>=', $startTime) ->where('createTime', '<', $endTime) ->field('wechatFriendId, COUNT(*) as count') @@ -225,7 +265,7 @@ class StatisticsController extends BaseController ->count(); // 中频互动用户数(每天1-3次) - $midFrequencyUsers = WechatMessageModel::where(['wechatAccountId' => $wechatAccountId]) + $midFrequencyUsers = WechatMessageModel::whereIn('wechatAccountId' , $wechatAccountIds) ->where('createTime', '>=', $startTime) ->where('createTime', '<', $endTime) ->field('wechatFriendId, COUNT(*) as count') @@ -234,7 +274,7 @@ class StatisticsController extends BaseController ->count(); // 低频互动用户数(仅有1次) - $lowFrequencyUsers = WechatMessageModel::where(['wechatAccountId' => $wechatAccountId]) + $lowFrequencyUsers = WechatMessageModel::whereIn('wechatAccountId' , $wechatAccountIds) ->where('createTime', '>=', $startTime) ->where('createTime', '<', $endTime) ->field('wechatFriendId, COUNT(*) as count') @@ -245,41 +285,39 @@ class StatisticsController extends BaseController // 2. 互动内容分析 // 文字消息数量 $textMessages = WechatMessageModel::where([ - 'wechatAccountId' => $wechatAccountId, 'msgType' => 1 ]) ->where('createTime', '>=', $startTime) ->where('createTime', '<', $endTime) + ->whereIn('wechatAccountId' , $wechatAccountIds) ->count(); // 图片互动数量 $imgInteractions = WechatMessageModel::where([ - 'wechatAccountId' => $wechatAccountId, 'msgType' => 3 ]) ->where('createTime', '>=', $startTime) ->where('createTime', '<', $endTime) + ->whereIn('wechatAccountId' , $wechatAccountIds) ->count(); // 群聊互动数量 $groupInteractions = WechatMessageModel::where([ - 'wechatAccountId' => $wechatAccountId, 'type' => 2 ]) ->where('createTime', '>=', $startTime) ->where('createTime', '<', $endTime) + ->whereIn('wechatAccountId' , $wechatAccountIds) ->count(); // 产品咨询数量 (通过消息内容模糊查询) - $productInquiries = WechatMessageModel::where([ - 'wechatAccountId' => $wechatAccountId - ]) - ->where('createTime', '>=', $startTime) + $productInquiries = WechatMessageModel::where('createTime', '>=', $startTime) ->where('createTime', '<', $endTime) ->where('content', 'like', '%产品%') ->whereOr('content', 'like', '%价格%') ->whereOr('content', 'like', '%购买%') ->whereOr('content', 'like', '%优惠%') + ->whereIn('wechatAccountId' , $wechatAccountIds) ->count(); // 构建返回数据 diff --git a/Store_vue/App.vue b/Store_vue/App.vue index a75c505b..b97e7bc2 100644 --- a/Store_vue/App.vue +++ b/Store_vue/App.vue @@ -29,6 +29,34 @@ } } } + + export function getSafeAreaHeight() { + // 1. 优先使用 CSS 环境变量 + if (CSS.supports("padding-top", "env(safe-area-inset-top)")) { + const safeAreaTop = getComputedStyle( + document.documentElement, + ).getPropertyValue("env(safe-area-inset-top)"); + const height = parseInt(safeAreaTop) || 0; + if (height > 0) return height; + } + + // 2. 设备检测 + const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent); + const isAndroid = /Android/.test(navigator.userAgent); + const isAppMode = getSetting("isAppMode"); + if (isIOS && isAppMode) { + // iOS 设备 + const isIPhoneX = window.screen.height >= 812; + return isIPhoneX ? 44 : 20; + } else if (isAndroid) { + // Android 设备 + return 24; + } + + // 3. 默认值 + return 0; + } + - + - - + + - +
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/WechatFriends.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/WechatFriends.module.scss index 8b371fa2..00633207 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/WechatFriends.module.scss +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/WechatFriends.module.scss @@ -60,6 +60,13 @@ padding: 10px 0; } +.noResults { + text-align: center; + color: #999; + padding: 20px; + font-size: 14px; +} + .list { flex: 1; overflow-y: auto; diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx index 33573b04..24ae993f 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx @@ -2,7 +2,10 @@ import React, { useState, useCallback, useEffect } from "react"; import { List, Avatar, Collapse, Button } from "antd"; import type { CollapseProps } from "antd"; import styles from "./WechatFriends.module.scss"; -import { useCkChatStore } from "@/store/module/ckchat/ckchat"; +import { + useCkChatStore, + searchContactsAndGroups, +} from "@/store/module/ckchat/ckchat"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; interface WechatFriendsProps { @@ -17,6 +20,9 @@ const ContactListSimple: React.FC = ({ selectedContactId, }) => { const [newContractList, setNewContractList] = useState([]); + const [searchResults, setSearchResults] = useState< + (ContractData | weChatGroup)[] + >([]); const getNewContractListFn = useCkChatStore( state => state.getNewContractList, ); @@ -26,17 +32,27 @@ const ContactListSimple: React.FC = ({ // 使用useEffect来处理异步的getNewContractList调用 useEffect(() => { - const fetchNewContractList = async () => { + const fetchData = async () => { try { - const result = await getNewContractListFn(); - setNewContractList(result || []); + if (searchKeyword.trim()) { + // 有搜索关键词时,获取搜索结果 + const searchResult = await searchContactsAndGroups(); + setSearchResults(searchResult || []); + setNewContractList([]); + } else { + // 无搜索关键词时,获取分组列表 + const result = await getNewContractListFn(); + setNewContractList(result || []); + setSearchResults([]); + } } catch (error) { - console.error("获取联系人分组列表失败:", error); + console.error("获取联系人数据失败:", error); setNewContractList([]); + setSearchResults([]); } }; - fetchNewContractList(); + fetchData(); }, [getNewContractListFn, kfSelected, countLables, searchKeyword]); const [activeKey, setActiveKey] = useState([]); // 默认展开第一个分组 @@ -50,30 +66,32 @@ const ContactListSimple: React.FC = ({ const [page, setPage] = useState<{ [key: string]: number }>({}); // 渲染联系人项 - const renderContactItem = (contact: ContractData) => ( - onContactClick(contact)} - className={`${styles.contractItem} ${contact.id === selectedContactId?.id ? styles.selected : ""}`} - > -
- {contact.nickname.charAt(0)} - ) - } - className={styles.avatar} - /> -
-
-
- {contact.conRemark || contact.nickname} + const renderContactItem = (contact: ContractData | weChatGroup) => { + // 判断是否为群组 + const isGroup = "chatroomId" in contact; + const avatar = contact.avatar || contact.chatroomAvatar; + const name = contact.conRemark || contact.nickname; + + return ( + onContactClick(contact)} + className={`${styles.contractItem} ${contact.id === selectedContactId?.id ? styles.selected : ""}`} + > +
+ {contact.nickname.charAt(0)}} + className={styles.avatar} + />
-
- - ); +
+
{name}
+ {isGroup &&
群聊
} +
+ + ); + }; // 初始化分页数据 useEffect(() => { @@ -188,7 +206,21 @@ const ContactListSimple: React.FC = ({ return (
- {newContractList && newContractList.length > 0 ? ( + {searchKeyword.trim() ? ( + // 搜索模式:直接显示搜索结果列表 + <> +
搜索结果
+ + {searchResults.length === 0 && ( +
未找到匹配的联系人
+ )} + + ) : newContractList && newContractList.length > 0 ? ( + // 正常模式:显示分组 = ({ items={getCollapseItems()} /> ) : ( + // 备用模式:显示传入的联系人列表 <> -
全部好友
+
全部联系人
diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts index db1bf9e6..bc490b04 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.ts @@ -131,6 +131,71 @@ export const useCkChatStore = createPersistStore( return cachedResult; }; })(), + // 搜索好友和群组的新方法 - 从本地数据库查询并返回扁平化的搜索结果 + searchContactsAndGroups: (() => { + let cachedResult: (ContractData | weChatGroup)[] = []; + let lastKfSelected: number | null = null; + let lastSearchKeyword: string = ""; + + return async () => { + const state = useCkChatStore.getState(); + + // 检查是否需要重新计算缓存 + const shouldRecalculate = + lastKfSelected !== state.kfSelected || + lastSearchKeyword !== state.searchKeyword; + + if (shouldRecalculate) { + if (state.searchKeyword.trim()) { + const keyword = state.searchKeyword.toLowerCase(); + + // 从本地数据库查询联系人数据 + let allContacts: ContractData[] = await contractService.findAll(); + + // 根据选中的客服筛选联系人 + if (state.kfSelected !== 0) { + allContacts = allContacts.filter( + item => item.wechatAccountId === state.kfSelected, + ); + } + + // 从本地数据库查询群组数据 + let allGroups: weChatGroup[] = await weChatGroupService.findAll(); + + // 根据选中的客服筛选群组 + if (state.kfSelected !== 0) { + allGroups = allGroups.filter( + item => item.wechatAccountId === state.kfSelected, + ); + } + + // 搜索匹配的联系人 + const matchedContacts = allContacts.filter(item => { + const nickname = (item.nickname || "").toLowerCase(); + const conRemark = (item.conRemark || "").toLowerCase(); + return nickname.includes(keyword) || conRemark.includes(keyword); + }); + + // 搜索匹配的群组 + const matchedGroups = allGroups.filter(item => { + const nickname = (item.nickname || "").toLowerCase(); + const conRemark = (item.conRemark || "").toLowerCase(); + return nickname.includes(keyword) || conRemark.includes(keyword); + }); + + // 合并搜索结果 + cachedResult = [...matchedContacts, ...matchedGroups]; + } else { + cachedResult = []; + } + + lastKfSelected = state.kfSelected; + lastSearchKeyword = state.searchKeyword; + } + + return cachedResult; + }; + })(), // 异步设置联系人分组列表 asyncNewContractList: async (data: any[]) => { set({ newContractList: data }); @@ -472,4 +537,6 @@ export const setSearchKeyword = (keyword: string) => useCkChatStore.getState().setSearchKeyword(keyword); export const clearSearchKeyword = () => useCkChatStore.getState().clearSearchKeyword(); +export const searchContactsAndGroups = () => + useCkChatStore.getState().searchContactsAndGroups(); useCkChatStore.getState().getKfSelectedUser(); From 92e9b69348b6d3b5bf15cae15a8787c17a7c5c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Tue, 2 Sep 2025 17:40:11 +0800 Subject: [PATCH 046/146] =?UTF-8?q?feat(WechatFriends):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=81=94=E7=B3=BB=E4=BA=BA=E5=88=97=E8=A1=A8=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增搜索联系人及群组功能,支持本地数据库查询 - 添加无搜索结果时的提示样式 - 优化联系人列表渲染逻辑,区分搜索模式和分组模式 - 统一联系人项展示样式,支持群组标识 --- Cunkebao/src/store/module/ckchat/ckchat.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts index bc490b04..054c4bd5 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.ts @@ -150,7 +150,10 @@ export const useCkChatStore = createPersistStore( const keyword = state.searchKeyword.toLowerCase(); // 从本地数据库查询联系人数据 - let allContacts: ContractData[] = await contractService.findAll(); + let allContacts: any[] = await contractService.findAll(); + + // 从本地数据库查询群组数据 + let allGroups: any[] = await weChatGroupService.findAll(); // 根据选中的客服筛选联系人 if (state.kfSelected !== 0) { @@ -159,9 +162,6 @@ export const useCkChatStore = createPersistStore( ); } - // 从本地数据库查询群组数据 - let allGroups: weChatGroup[] = await weChatGroupService.findAll(); - // 根据选中的客服筛选群组 if (state.kfSelected !== 0) { allGroups = allGroups.filter( From 4c5e049c16c4dec207f767d2d0db976de114e794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Tue, 2 Sep 2025 17:51:37 +0800 Subject: [PATCH 047/146] =?UTF-8?q?feat(ckbox):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=BE=AE=E4=BF=A1=E5=A5=BD=E5=8F=8B=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=9A=84API=E5=92=8C=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在聊天会话初始化时调用updateConfig API更新好友配置,标记为可聊天状态 --- Cunkebao/src/pages/pc/ckbox/api.ts | 5 +++++ Cunkebao/src/pages/pc/ckbox/index.tsx | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts index 9bf2e54c..daa8a79a 100644 --- a/Cunkebao/src/pages/pc/ckbox/api.ts +++ b/Cunkebao/src/pages/pc/ckbox/api.ts @@ -22,6 +22,11 @@ export function WechatGroup(params) { export function clearUnreadCount(params) { return request("/api/WechatFriend/clearUnreadCount", params, "PUT"); } + +//更新配置 +export function updateConfig(params) { + return request("/api/WechatFriend/updateConfig", params, "PUT"); +} //获取聊天记录-2 获取列表 export function getChatMessages(params: { wechatAccountId: number; diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx index b0112675..7376104c 100644 --- a/Cunkebao/src/pages/pc/ckbox/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/index.tsx @@ -10,7 +10,7 @@ import styles from "./index.module.scss"; import { addChatSession } from "@/store/module/ckchat/ckchat"; const { Header, Content, Sider } = Layout; import { chatInitAPIdata, initSocket } from "./main"; -import { clearUnreadCount } from "@/pages/pc/ckbox/api"; +import { clearUnreadCount, updateConfig } from "@/pages/pc/ckbox/api"; import { KfUserListData, weChatGroup, @@ -82,6 +82,10 @@ const CkboxPage: React.FC = () => { contract.unreadCount = 0; addChatSession(contract); setCurrentChat(contract); + updateConfig({ + id: contract.id, + config: { chat: true }, + }); }); }; From 22560a5ce2e5e52ba0889ea0ae1107e53b76a4eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Tue, 2 Sep 2025 17:58:19 +0800 Subject: [PATCH 048/146] =?UTF-8?q?fix(SidebarMenu):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E6=A1=86=E5=8D=A0=E4=BD=8D=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E4=B8=BA"=E6=90=9C=E7=B4=A2=E5=AE=A2=E6=88=B7..."?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原占位文本"搜索联系人、群组"不符合当前业务场景,更新为更准确的"搜索客户..." --- Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx index b143d406..4704cf38 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx @@ -93,7 +93,7 @@ const SidebarMenu: React.FC = ({ {/* 搜索栏 */}
} value={searchKeyword} onChange={e => handleSearch(e.target.value)} From eb63dc1d8dad647394d8b4bcf16ac372ccaf3501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 09:35:09 +0800 Subject: [PATCH 049/146] =?UTF-8?q?refactor(websocket):=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86=E5=99=A8=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E7=A9=BA=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/src/store/module/websocket/msgManage.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/Cunkebao/src/store/module/websocket/msgManage.ts b/Cunkebao/src/store/module/websocket/msgManage.ts index fdcc0c1e..2883be13 100644 --- a/Cunkebao/src/store/module/websocket/msgManage.ts +++ b/Cunkebao/src/store/module/websocket/msgManage.ts @@ -19,7 +19,6 @@ const messageHandlers: Record = { }); asyncKfUserList(kfUserList); }, - // 发送消息响应 CmdSendMessageResp: message => { console.log("发送消息响应", message); From 0d6ac88dea214bcc45eec19be560f7d146b0fc36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 09:52:58 +0800 Subject: [PATCH 050/146] =?UTF-8?q?refactor(websocket):=20=E5=B0=86?= =?UTF-8?q?=E5=AE=A2=E6=9C=8D=E7=8A=B6=E6=80=81=E6=9F=A5=E8=AF=A2=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E5=99=A8=E9=80=BB=E8=BE=91=E7=A7=BB=E8=87=B3websocket?= =?UTF-8?q?=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将原本在组件中的客服状态查询定时器逻辑重构到websocket store中,统一管理定时器的启动和停止。同时在连接成功时自动启动定时器,断开连接时自动停止,避免内存泄漏。 --- Cunkebao/src/pages/pc/ckbox/index.tsx | 15 ----- .../src/store/module/websocket/websocket.ts | 57 +++++++++++++++++++ 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx index 7376104c..e02d5739 100644 --- a/Cunkebao/src/pages/pc/ckbox/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/index.tsx @@ -16,8 +16,6 @@ import { weChatGroup, ContractData, } from "@/pages/pc/ckbox/data"; -import { useWebSocketStore } from "@/store/module/websocket/websocket"; -import { useCkChatStore } from "@/store/module/ckchat/ckchat"; const CkboxPage: React.FC = () => { const [messageApi, contextHolder] = message.useMessage(); @@ -25,22 +23,9 @@ const CkboxPage: React.FC = () => { const [currentChat, setCurrentChat] = useState( null, ); - const status = useWebSocketStore(state => state.status); // 不要在组件初始化时获取sendCommand,而是在需要时动态获取 const [loading, setLoading] = useState(false); const [showProfile, setShowProfile] = useState(true); - const kfUserList = useCkChatStore(state => state.kfUserList); - const { sendCommand } = useWebSocketStore.getState(); - useEffect(() => { - if (status == "connected" && kfUserList.length > 0) { - //查询客服用户激活状态 - setInterval(() => { - sendCommand("CmdRequestWechatAccountsAliveStatus", { - wechatAccountIds: kfUserList.map(v => v.id), - }); - }, 10 * 1000); - } - }, [status]); useEffect(() => { // 方法一:使用 Promise 链式调用处理异步函数 diff --git a/Cunkebao/src/store/module/websocket/websocket.ts b/Cunkebao/src/store/module/websocket/websocket.ts index ebfe53a2..a1327038 100644 --- a/Cunkebao/src/store/module/websocket/websocket.ts +++ b/Cunkebao/src/store/module/websocket/websocket.ts @@ -51,6 +51,7 @@ interface WebSocketState { // 重连相关 reconnectAttempts: number; reconnectTimer: NodeJS.Timeout | null; + aliveStatusTimer: NodeJS.Timeout | null; // 客服用户状态查询定时器 // 方法 connect: (config: Partial) => void; @@ -68,6 +69,8 @@ interface WebSocketState { _handleError: (event: Event) => void; _startReconnectTimer: () => void; _stopReconnectTimer: () => void; + _startAliveStatusTimer: () => void; // 启动客服状态查询定时器 + _stopAliveStatusTimer: () => void; // 停止客服状态查询定时器 } // 默认配置 @@ -92,6 +95,7 @@ export const useWebSocketStore = createPersistStore( unreadCount: 0, reconnectAttempts: 0, reconnectTimer: null, + aliveStatusTimer: null, // 连接WebSocket connect: (config: Partial) => { @@ -183,6 +187,7 @@ export const useWebSocketStore = createPersistStore( } currentState._stopReconnectTimer(); + currentState._stopAliveStatusTimer(); set({ status: WebSocketStatus.DISCONNECTED, @@ -291,6 +296,9 @@ export const useWebSocketStore = createPersistStore( } Toast.show({ content: "WebSocket连接成功", position: "top" }); + + // 启动客服状态查询定时器 + currentState._startAliveStatusTimer(); }, // 内部方法:处理消息接收 @@ -319,6 +327,9 @@ export const useWebSocketStore = createPersistStore( }); } + // 停止客服状态查询定时器 + get()._stopAliveStatusTimer(); + // 断开连接 get().disconnect(); return; @@ -414,6 +425,51 @@ export const useWebSocketStore = createPersistStore( set({ reconnectTimer: null }); } }, + + // 内部方法:启动客服状态查询定时器 + _startAliveStatusTimer: () => { + const currentState = get(); + + // 先停止现有定时器 + currentState._stopAliveStatusTimer(); + + // 获取客服用户列表 + const { kfUserList } = useCkChatStore.getState(); + + // 如果没有客服用户,不启动定时器 + if (!kfUserList || kfUserList.length === 0) { + return; + } + + // 启动定时器,每5秒查询一次 + const timer = setInterval(() => { + const state = get(); + // 检查连接状态 + if (state.status === WebSocketStatus.CONNECTED) { + const { kfUserList: currentKfUserList } = useCkChatStore.getState(); + if (currentKfUserList && currentKfUserList.length > 0) { + state.sendCommand("CmdRequestWechatAccountsAliveStatus", { + wechatAccountIds: currentKfUserList.map(v => v.id), + }); + } + } else { + // 如果连接断开,停止定时器 + state._stopAliveStatusTimer(); + } + }, 5000); + + set({ aliveStatusTimer: timer }); + }, + + // 内部方法:停止客服状态查询定时器 + _stopAliveStatusTimer: () => { + const currentState = get(); + + if (currentState.aliveStatusTimer) { + clearInterval(currentState.aliveStatusTimer); + set({ aliveStatusTimer: null }); + } + }, }), { name: "websocket-store", @@ -424,6 +480,7 @@ export const useWebSocketStore = createPersistStore( messages: state.messages.slice(-100), // 只保留最近100条消息 unreadCount: state.unreadCount, reconnectAttempts: state.reconnectAttempts, + // 注意:定时器不需要持久化,重新连接时会重新创建 }), onRehydrateStorage: () => state => { // 页面刷新后,如果之前是连接状态,尝试重新连接 From 2995755cdac24d3c45931186f449f0ae3825efa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 09:54:40 +0800 Subject: [PATCH 051/146] =?UTF-8?q?refactor(websocket):=20=E5=B0=86?= =?UTF-8?q?=E7=A1=AC=E7=BC=96=E7=A0=81=E7=9A=845000=E6=AF=AB=E7=A7=92?= =?UTF-8?q?=E6=94=B9=E4=B8=BA5=20*=201000=E4=BB=A5=E6=8F=90=E9=AB=98?= =?UTF-8?q?=E5=8F=AF=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/src/store/module/websocket/websocket.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cunkebao/src/store/module/websocket/websocket.ts b/Cunkebao/src/store/module/websocket/websocket.ts index a1327038..485931c6 100644 --- a/Cunkebao/src/store/module/websocket/websocket.ts +++ b/Cunkebao/src/store/module/websocket/websocket.ts @@ -456,7 +456,7 @@ export const useWebSocketStore = createPersistStore( // 如果连接断开,停止定时器 state._stopAliveStatusTimer(); } - }, 5000); + }, 5 * 1000); set({ aliveStatusTimer: timer }); }, From 414660a0a5656fd3937419d2997ba748eab85fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 10:01:26 +0800 Subject: [PATCH 052/146] =?UTF-8?q?refactor(ChatWindow):=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E8=B0=83=E8=AF=95=E7=94=A8=E7=9A=84console.log?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 清理无用代码,避免生产环境输出不必要的信息 --- Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index d3cc1488..565881be 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -60,8 +60,6 @@ const ChatWindow: React.FC = ({ useEffect(() => { setLoading(true); - console.log(contract); - const params: any = { wechatAccountId: contract.wechatAccountId, From: 1, From c6da2062f2bf7c469d0bbd4dc59a3c36d7c0d7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 10:45:19 +0800 Subject: [PATCH 053/146] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MessageList/MessageList.module.scss | 7 +++-- .../src/store/module/websocket/msg.data.ts | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 Cunkebao/src/store/module/websocket/msg.data.ts diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/MessageList.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/MessageList.module.scss index 7f78afc9..e70717c2 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/MessageList.module.scss +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/MessageList.module.scss @@ -63,7 +63,6 @@ .lastMessage { font-size: 12px; color: #8c8c8c; - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; @@ -71,7 +70,7 @@ padding-right: 5px; height: 18px; // 添加固定高度 line-height: 18px; // 设置行高与高度一致 - + &::before { content: attr(data-count); position: absolute; @@ -88,7 +87,7 @@ text-align: center; display: none; } - + &[data-count]:not([data-count=""]):not([data-count="0"]) { &::before { display: inline-block; @@ -106,7 +105,7 @@ } } } - + .lastDayMessage { position: absolute; bottom: 0; diff --git a/Cunkebao/src/store/module/websocket/msg.data.ts b/Cunkebao/src/store/module/websocket/msg.data.ts new file mode 100644 index 00000000..ed8510b1 --- /dev/null +++ b/Cunkebao/src/store/module/websocket/msg.data.ts @@ -0,0 +1,27 @@ +export interface FriendMessage { + id: number; + wechatFriendId: number; + wechatAccountId: number; + tenantId: number; + accountId: number; + synergyAccountId: number; + content: string; + msgType: number; + msgSubType: number; + msgSvrId: string; + isSend: boolean; + createTime: string; + isDeleted: boolean; + deleteTime: string; + sendStatus: number; + wechatTime: number; + origin: number; + msgId: number; + recalled: boolean; +} +export interface Messages { + friendMessage: FriendMessage | null; + chatroomMessage: string; + seq: number; + cmdType: string; +} From 48880bed0d8eecf0b9ce1b09ff626a8abe416fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 11:25:10 +0800 Subject: [PATCH 054/146] =?UTF-8?q?feat(wechat):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E8=81=8A=E5=A4=A9=E7=8A=B6=E6=80=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=8F=8A=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加微信聊天状态管理store,包含联系人管理、消息发送/接收、未读消息计数等功能 修复聊天窗口参数传递问题,统一使用contract.id作为标识 调整消息加载数量从10条减少到5条 --- .../components/MessageEnter/index.tsx | 2 +- .../pc/ckbox/components/ChatWindow/index.tsx | 4 +- Cunkebao/src/store/module/weChat.ts | 601 ++++++++++++++++++ .../src/store/module/websocket/msgManage.ts | 3 +- 4 files changed, 606 insertions(+), 4 deletions(-) create mode 100644 Cunkebao/src/store/module/weChat.ts diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx index e869a45d..0f4650d2 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx @@ -38,7 +38,7 @@ const MessageEnter: React.FC = ({ console.log("发送消息", contract); const params = { wechatAccountId: contract.wechatAccountId, - wechatChatroomId: contract?.chatroomId || 0, + wechatChatroomId: contract?.chatroomId ? contract.id : 0, wechatFriendId: contract?.chatroomId ? 0 : contract.id, msgSubType: 0, msgType: 1, diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index 565881be..22e96c0f 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -64,11 +64,11 @@ const ChatWindow: React.FC = ({ wechatAccountId: contract.wechatAccountId, From: 1, To: 4704624000000, - Count: 10, + Count: 5, olderData: true, }; if (contract.chatroomId) { - params.wechatChatroomId = contract.chatroomId; + params.wechatChatroomId = contract.id; } else { params.wechatFriendId = contract.id; } diff --git a/Cunkebao/src/store/module/weChat.ts b/Cunkebao/src/store/module/weChat.ts new file mode 100644 index 00000000..e1cf17a4 --- /dev/null +++ b/Cunkebao/src/store/module/weChat.ts @@ -0,0 +1,601 @@ +import { create } from "zustand"; +import { createPersistStore } from "../createPersistStore"; +import { getChatMessages } from "@/pages/pc/ckbox/api"; +import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { useWebSocketStore } from "./websocket/websocket"; + +// 聊天列表项类型定义 +export interface ChatListItem { + id: string; + name: string; + avatar?: string; + type: "friend" | "group"; + lastMessage?: ChatRecord; + unreadCount: number; + isOnline?: boolean; + lastActiveTime: number; // 最后活跃时间,用于排序 + contractData: ContractData | weChatGroup; // 保存完整的联系人数据 +} + +// 微信聊天相关的类型定义 +export interface WeChatState { + // 当前选中的联系人/群组 + currentContract: ContractData | weChatGroup | null; + + // 当前聊天用户的消息列表(只存储当前聊天用户的消息) + currentMessages: ChatRecord[]; + + // 联系人列表(所有好友和群组) + contacts: (ContractData | weChatGroup)[]; + + // 聊天列表(有聊天记录的联系人/群组,按最后活跃时间排序) + chatList: ChatListItem[]; + + // 消息发送状态 + sendingStatus: Record; // key为消息临时ID,value为发送状态 + + // 消息加载状态 + loadingMessages: boolean; + + // 待处理的视频请求 + pendingVideoRequests: Record; // messageId -> requestId + + // 输入框内容状态 - 按联系人/群组ID分组存储 + inputValues: Record; + + // 素材模态框状态 + showMaterialModal: boolean; + selectedMaterialType: string; +} + +export interface WeChatActions { + // 设置当前选中的联系人/群组 + setCurrentContract: (contract: ContractData | weChatGroup | null) => void; + + // 获取聊天消息 + fetchChatMessages: (contract: ContractData | weChatGroup) => Promise; + + // 设置当前消息列表 + setCurrentMessages: (messages: ChatRecord[]) => void; + + // 添加消息到当前聊天 + addMessageToCurrentChat: (message: ChatRecord) => void; + + // 更新消息内容(用于视频加载状态更新等) + updateMessage: (messageId: number, updates: Partial) => void; + + // 发送消息 + sendMessage: ( + contract: ContractData | weChatGroup, + content: string, + msgType?: number, + ) => Promise; + + // 处理视频播放请求 + handleVideoPlayRequest: ( + tencentUrl: string, + messageId: number, + contract: ContractData | weChatGroup, + ) => void; + + // 设置待处理的视频请求 + setPendingVideoRequest: (messageId: string, requestId: string) => void; + + // 移除待处理的视频请求 + removePendingVideoRequest: (messageId: string) => void; + + // 处理视频下载响应 + handleVideoDownloadResponse: (messageId: string, videoUrl: string) => void; + + // 设置输入框内容 + setInputValue: (contractId: string, value: string) => void; + + // 获取输入框内容 + getInputValue: (contractId: string) => string; + + // 清空输入框内容 + clearInputValue: (contractId: string) => void; + + // 素材模态框相关操作 + setShowMaterialModal: (show: boolean) => void; + setSelectedMaterialType: (type: string) => void; + + // 新消息处理 + handleNewMessage: ( + message: ChatRecord, + fromContract: ContractData | weChatGroup, + ) => void; + + // 聊天列表管理 + setContacts: (contacts: (ContractData | weChatGroup)[]) => void; + setChatList: (chatList: ChatListItem[]) => void; + updateChatListItem: ( + contractId: string, + updates: Partial, + ) => void; + moveToTopOfChatList: (contractId: string) => void; + addToChatList: (contract: ContractData | weChatGroup) => void; + + // 未读消息管理 + incrementUnreadCount: (contractId: string) => void; + clearUnreadCount: (contractId: string) => void; + + // 便捷选择器 + getUnreadTotal: () => number; + getChatListSorted: () => ChatListItem[]; + + // 清理指定联系人的数据 + clearContractData: (contractId: string) => void; + + // 清理所有数据 + clearAllData: () => void; +} + +type WeChatStore = WeChatState & WeChatActions; + +// 生成联系人/群组的唯一ID +const getContractId = (contract: ContractData | weChatGroup): string => { + if ("chatroomId" in contract && contract.chatroomId) { + return `group_${contract.chatroomId}`; + } + return `friend_${contract.id}`; +}; + +export const useWeChatStore = create()( + createPersistStore( + (set, get) => ({ + // 初始状态 + currentContract: null, + currentMessages: [], + contacts: [], + chatList: [], + sendingStatus: {}, + loadingMessages: false, + pendingVideoRequests: {}, + inputValues: {}, + showMaterialModal: false, + selectedMaterialType: "", + + // Actions + setCurrentContract: contract => { + set({ currentContract: contract }); + // 切换联系人时清空当前消息,等待重新加载 + set({ currentMessages: [] }); + // 清除该联系人的未读数 + if (contract) { + const contractId = getContractId(contract); + get().clearUnreadCount(contractId); + } + }, + + fetchChatMessages: async contract => { + const { currentMessages } = get(); + + // 如果已经有消息数据,不重复加载 + if (currentMessages.length > 0) { + return; + } + + set({ loadingMessages: true }); + + try { + const params: any = { + wechatAccountId: contract.wechatAccountId, + From: 1, + To: 4704624000000, + Count: 10, + olderData: true, + }; + + if ("chatroomId" in contract && contract.chatroomId) { + params.wechatChatroomId = contract.chatroomId; + } else { + params.wechatFriendId = contract.id; + } + + const messages = await getChatMessages(params); + + set({ currentMessages: messages }); + } catch (error) { + console.error("获取聊天消息失败:", error); + } finally { + set({ loadingMessages: false }); + } + }, + + setCurrentMessages: messages => { + set({ currentMessages: messages }); + }, + + addMessageToCurrentChat: message => { + set(state => ({ + currentMessages: [...state.currentMessages, message], + })); + }, + + updateMessage: (messageId, updates) => { + set(state => ({ + currentMessages: state.currentMessages.map(msg => + msg.id === messageId ? { ...msg, ...updates } : msg, + ), + })); + }, + + sendMessage: async (contract, content, msgType = 1) => { + const contractId = getContractId(contract); + const tempId = `temp_${Date.now()}`; + + // 设置发送状态 + set(state => ({ + sendingStatus: { + ...state.sendingStatus, + [tempId]: true, + }, + })); + + try { + const params = { + wechatAccountId: contract.wechatAccountId, + wechatChatroomId: + "chatroomId" in contract && contract.chatroomId + ? contract.chatroomId + : 0, + wechatFriendId: + "chatroomId" in contract && contract.chatroomId ? 0 : contract.id, + msgSubType: 0, + msgType, + content, + }; + + // 通过WebSocket发送消息 + const { sendCommand } = useWebSocketStore.getState(); + await sendCommand("CmdSendMessage", params); + + // 清空对应的输入框内容 + get().clearInputValue(contractId); + } catch (error) { + console.error("发送消息失败:", error); + } finally { + // 移除发送状态 + set(state => { + const newSendingStatus = { ...state.sendingStatus }; + delete newSendingStatus[tempId]; + return { sendingStatus: newSendingStatus }; + }); + } + }, + + handleVideoPlayRequest: (tencentUrl, messageId, contract) => { + const requestSeq = `${Date.now()}`; + + console.log("发送视频下载请求:", { messageId, requestSeq }); + + // 构建socket请求数据 + const { sendCommand } = useWebSocketStore.getState(); + sendCommand("CmdDownloadVideo", { + chatroomMessageId: + "chatroomId" in contract && contract.chatroomId ? messageId : 0, + friendMessageId: + "chatroomId" in contract && contract.chatroomId ? 0 : messageId, + seq: requestSeq, + tencentUrl: tencentUrl, + wechatAccountId: contract.wechatAccountId, + }); + + // 添加到待处理队列 + get().setPendingVideoRequest( + messageId.toString(), + messageId.toString(), + ); + + // 更新消息状态为加载中 + const messages = get().currentMessages; + const targetMessage = messages.find(msg => msg.id === messageId); + if (targetMessage) { + try { + const originalContent = + typeof targetMessage.content === "string" + ? JSON.parse(targetMessage.content) + : targetMessage.content; + + get().updateMessage(messageId, { + content: JSON.stringify({ + ...originalContent, + isLoading: true, + }), + }); + } catch (e) { + console.error("解析消息内容失败:", e); + } + } + }, + + setPendingVideoRequest: (messageId, requestId) => { + set(state => ({ + pendingVideoRequests: { + ...state.pendingVideoRequests, + [messageId]: requestId, + }, + })); + }, + + removePendingVideoRequest: messageId => { + set(state => { + const newRequests = { ...state.pendingVideoRequests }; + delete newRequests[messageId]; + return { pendingVideoRequests: newRequests }; + }); + }, + + handleVideoDownloadResponse: (messageId, videoUrl) => { + const { currentMessages } = get(); + const targetMessage = currentMessages.find( + msg => msg.id === Number(messageId), + ); + + if (targetMessage) { + try { + const msgContent = + typeof targetMessage.content === "string" + ? JSON.parse(targetMessage.content) + : targetMessage.content; + + // 更新消息内容,添加视频URL并移除加载状态 + get().updateMessage(Number(messageId), { + content: JSON.stringify({ + ...msgContent, + videoUrl: videoUrl, + isLoading: false, + }), + }); + + // 从待处理队列中移除 + get().removePendingVideoRequest(messageId); + } catch (e) { + console.error("解析消息内容失败:", e); + } + } + }, + + setInputValue: (contractId, value) => { + set(state => ({ + inputValues: { + ...state.inputValues, + [contractId]: value, + }, + })); + }, + + getInputValue: contractId => { + return get().inputValues[contractId] || ""; + }, + + clearInputValue: contractId => { + set(state => { + const newInputValues = { ...state.inputValues }; + delete newInputValues[contractId]; + return { inputValues: newInputValues }; + }); + }, + + setShowMaterialModal: show => { + set({ showMaterialModal: show }); + }, + + setSelectedMaterialType: type => { + set({ selectedMaterialType: type }); + }, + + // 新消息处理 + handleNewMessage: (message, fromContract) => { + const { currentContract } = get(); + const contractId = getContractId(fromContract); + + // 如果是当前聊天用户的消息,直接添加到当前消息列表 + if (currentContract && getContractId(currentContract) === contractId) { + get().addMessageToCurrentChat(message); + } else { + // 如果不是当前聊天用户,更新聊天列表 + get().incrementUnreadCount(contractId); + get().moveToTopOfChatList(contractId); + + // 更新聊天列表项的最后消息 + get().updateChatListItem(contractId, { + lastMessage: message, + lastActiveTime: message.createTime || Date.now(), + }); + } + }, + + // 聊天列表管理 + setContacts: contacts => { + set({ contacts }); + }, + + setChatList: chatList => { + set({ chatList }); + }, + + updateChatListItem: (contractId, updates) => { + set(state => ({ + chatList: state.chatList.map(item => + getContractId(item.contractData) === contractId + ? { ...item, ...updates } + : item, + ), + })); + }, + + moveToTopOfChatList: contractId => { + set(state => { + const chatList = [...state.chatList]; + const itemIndex = chatList.findIndex( + item => getContractId(item.contractData) === contractId, + ); + + if (itemIndex > 0) { + // 移动到顶部 + const item = chatList.splice(itemIndex, 1)[0]; + chatList.unshift({ ...item, lastActiveTime: Date.now() }); + } else if (itemIndex === -1) { + // 如果不在聊天列表中,从联系人列表找到并添加 + const contract = state.contacts.find( + c => getContractId(c) === contractId, + ); + if (contract) { + get().addToChatList(contract); + } + } + + return { chatList }; + }); + }, + + addToChatList: contract => { + set(state => { + const contractId = getContractId(contract); + // 检查是否已存在 + const exists = state.chatList.some( + item => getContractId(item.contractData) === contractId, + ); + if (exists) return state; + + const newChatItem: ChatListItem = { + id: contractId, + name: contract.nickName || contract.name || "", + avatar: contract.avatar, + type: "chatroomId" in contract ? "group" : "friend", + unreadCount: 0, + lastActiveTime: Date.now(), + contractData: contract, + }; + + return { + chatList: [newChatItem, ...state.chatList], + }; + }); + }, + + // 未读消息管理 + incrementUnreadCount: contractId => { + // 更新聊天列表中的未读数 + set(state => ({ + chatList: state.chatList.map(item => + getContractId(item.contractData) === contractId + ? { ...item, unreadCount: item.unreadCount + 1 } + : item, + ), + })); + }, + + clearUnreadCount: contractId => { + // 清除聊天列表中的未读数 + set(state => ({ + chatList: state.chatList.map(item => + getContractId(item.contractData) === contractId + ? { ...item, unreadCount: 0 } + : item, + ), + })); + }, + + // 便捷选择器 + getUnreadTotal: () => { + const { chatList } = get(); + return chatList.reduce((total, item) => total + item.unreadCount, 0); + }, + + getChatListSorted: () => { + const { chatList } = get(); + return [...chatList].sort( + (a, b) => b.lastActiveTime - a.lastActiveTime, + ); + }, + + clearContractData: contractId => { + set(state => { + const newInputValues = { ...state.inputValues }; + delete newInputValues[contractId]; + + return { + inputValues: newInputValues, + chatList: state.chatList.filter( + item => getContractId(item.contractData) !== contractId, + ), + }; + }); + }, + + clearAllData: () => { + set({ + currentContract: null, + currentMessages: [], + contacts: [], + chatList: [], + sendingStatus: {}, + loadingMessages: false, + pendingVideoRequests: {}, + inputValues: {}, + showMaterialModal: false, + selectedMaterialType: "", + }); + }, + }), + { + name: "wechat-store", + // 只持久化部分状态,排除临时状态 + partialize: state => ({ + contacts: state.contacts, + chatList: state.chatList, + inputValues: state.inputValues, + }), + }, + ), +); + +// 导出便捷的选择器函数 +export const useCurrentContract = () => + useWeChatStore(state => state.currentContract); +export const useCurrentMessages = () => + useWeChatStore(state => state.currentMessages); +export const useCurrentInputValue = () => { + const { currentContract, getInputValue } = useWeChatStore(); + if (!currentContract) return ""; + const contractId = getContractId(currentContract); + return getInputValue(contractId); +}; +export const useChatList = () => + useWeChatStore(state => state.getChatListSorted()); +export const useUnreadTotal = () => + useWeChatStore(state => state.getUnreadTotal()); + +// 初始化WebSocket消息监听 +if (typeof window !== "undefined") { + // 监听WebSocket消息,处理视频下载响应 + useWebSocketStore.subscribe(state => { + const messages = state.messages as any[]; + const { pendingVideoRequests, handleVideoDownloadResponse } = + useWeChatStore.getState(); + + // 只有当有待处理的视频请求时才处理消息 + if (Object.keys(pendingVideoRequests).length === 0) { + return; + } + + messages.forEach(message => { + if (message?.content?.cmdType === "CmdDownloadVideoResult") { + console.log("收到视频下载响应:", message.content); + + // 检查是否是我们正在等待的视频响应 + const messageId = Object.keys(pendingVideoRequests).find( + id => pendingVideoRequests[id] === message.content.friendMessageId, + ); + + if (messageId) { + console.log("找到对应的消息ID:", messageId); + handleVideoDownloadResponse(messageId, message.content.url); + } + } + }); + }); +} diff --git a/Cunkebao/src/store/module/websocket/msgManage.ts b/Cunkebao/src/store/module/websocket/msgManage.ts index 2883be13..484058af 100644 --- a/Cunkebao/src/store/module/websocket/msgManage.ts +++ b/Cunkebao/src/store/module/websocket/msgManage.ts @@ -2,6 +2,7 @@ import { deepCopy } from "@/utils/common"; import { WebSocketMessage } from "./websocket"; import { getkfUserList, asyncKfUserList } from "@/store/module/ckchat/ckchat"; +import { Messages } from "./msg.data"; // 消息处理器类型定义 type MessageHandler = (message: WebSocketMessage) => void; @@ -30,7 +31,7 @@ const messageHandlers: Record = { // 在这里添加具体的处理逻辑 }, //收到消息 - CmdNewMessage: message => { + CmdNewMessage: (message: Messages) => { console.log("收到消息", message.friendMessage); // 在这里添加具体的处理逻辑 }, From d3ae45a360164cd2db1cb03db4ca009ca202ae11 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Wed, 3 Sep 2025 14:34:26 +0800 Subject: [PATCH 055/146] =?UTF-8?q?=E7=BE=A4=E6=8E=A8=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/job/WorkbenchGroupPushJob.php | 132 ++++++++++-------- 1 file changed, 73 insertions(+), 59 deletions(-) diff --git a/Server/application/job/WorkbenchGroupPushJob.php b/Server/application/job/WorkbenchGroupPushJob.php index 2af080fa..4cffc80d 100644 --- a/Server/application/job/WorkbenchGroupPushJob.php +++ b/Server/application/job/WorkbenchGroupPushJob.php @@ -58,7 +58,7 @@ class WorkbenchGroupPushJob { try { // 获取所有工作台 - $workbenches = Workbench::where(['status' => 1, 'type' => 3, 'isDel' => 0,'id' => 178])->order('id desc')->select(); + $workbenches = Workbench::where(['status' => 1, 'type' => 3, 'isDel' => 0])->order('id desc')->select(); foreach ($workbenches as $workbench) { // 获取工作台配置 $config = WorkbenchGroupPush::where('workbenchId', $workbench->id)->find(); @@ -87,7 +87,7 @@ class WorkbenchGroupPushJob } - // 发微信个人消息 + // 发微信消息 public function sendMsgToGroup($workbench, $config, $msgConf) { // 消息拼接 msgType(1:文本 3:图片 43:视频 47:动图表情包(gif、其他表情包) 49:小程序/其他:图文、文件) @@ -117,6 +117,7 @@ class WorkbenchGroupPushJob } // 建立WebSocket $wsController = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]); + foreach ($msgConf as $content) { $sendData = []; $sqlData = []; @@ -293,69 +294,82 @@ class WorkbenchGroupPushJob return false; } - if ($config['pushType'] == 1) { - $limit = 10; - } else { - $limit = 1; - } + $limit = ($config['pushType'] == 1) ? 10 : 1; + $order = ($config['pushOrder'] == 1) ? 'ci.sendTime desc, ci.id asc' : 'ci.sendTime desc, ci.id desc'; + // 基础查询构建器 + $baseQuery = function() use ($workbench, $contentids) { + return Db::name('content_library')->alias('cl') + ->join('content_item ci', 'ci.libraryId = cl.id') + ->where(['cl.isDel' => 0, 'ci.isDel' => 0]) + ->where('ci.sendTime <= ' . (time() + 60)) + ->whereIn('cl.id', $contentids) + ->field('ci.id,ci.libraryId,ci.contentType,ci.title,ci.content,ci.resUrls,ci.urls,ci.comment,ci.sendTime'); + }; - //推送顺序 - if ($config['pushOrder'] == 1) { - $order = 'ci.sendTime desc, ci.id asc'; - } else { - $order = 'ci.sendTime desc, ci.id desc'; - } - - // 基础查询 - $query = Db::name('content_library')->alias('cl') - ->join('content_item ci', 'ci.libraryId = cl.id') + // 获取未发送的内容 + $unsentContent = $baseQuery() ->join('workbench_group_push_item wgpi', 'wgpi.contentId = ci.id and wgpi.workbenchId = ' . $workbench->id, 'left') - ->where(['cl.isDel' => 0, 'ci.isDel' => 0,'wgpi.isLoop' => 0]) + ->where('wgpi.id', 'null') + ->order($order) + ->limit($limit) + ->select(); + + if (!empty($unsentContent)) { + return $unsentContent; + } + + // 如果不允许循环发送,直接返回空 + if ($config['isLoop'] != 1) { + return []; + } + + // 循环发送逻辑:检查是否需要标记循环完成 + $this->checkAndResetLoop($workbench->id, $contentids); + + // 获取下一个要发送的内容(从内容库中查询,排除isLoop为0的数据) + $isPushIds = Db::name('workbench_group_push_item') + ->where(['workbenchId' => $workbench->id,'isLoop' => 0]) + ->column('contentId'); + $nextContent = $baseQuery() + ->whereNotIn('ci.id', $isPushIds) + ->group('ci.id') + ->order('ci.id asc') + ->limit($limit) + ->select(); + return $nextContent; + } + + /** + * 检查循环状态 + * @param int $workbenchId + * @param array $contentids + */ + private function checkAndResetLoop($workbenchId, $contentids) + { + // 统计总内容数 + $totalCount = Db::name('content_library')->alias('cl') + ->join('content_item ci', 'ci.libraryId = cl.id') + ->where(['cl.isDel' => 0, 'ci.isDel' => 0]) ->where('ci.sendTime <= ' . (time() + 60)) ->whereIn('cl.id', $contentids) - ->field([ - 'ci.id', - 'ci.libraryId', - 'ci.contentType', - 'ci.title', - 'ci.content', - 'ci.resUrls', - 'ci.urls', - 'ci.comment', - 'ci.sendTime' - ]); - // 复制 query - $query2 = clone $query; - $query3 = clone $query; - // 根据accountType处理不同的发送逻辑 - if ($config['isLoop'] == 1) { - // 可以循环发送 - // 1. 优先获取未发送的内容 - $unsentContent = $query->where('wgpi.id', 'null') - ->order($order) - ->limit(0, $limit) - ->select(); + ->count(); - if (!empty($unsentContent)) { - return $unsentContent; - } - $lastSendData = Db::name('workbench_group_push_item')->where('workbenchId', $workbench->id)->order('id desc')->find(); - $fastSendData = Db::name('workbench_group_push_item')->where('workbenchId', $workbench->id)->order('id asc')->find(); + // 统计已发送内容数(排除isLoop为0的数据) + $sentCount = Db::name('workbench_group_push_item') + ->alias('wgpi') + ->join('content_item ci', 'ci.id = wgpi.contentId') + ->join('content_library cl', 'cl.id = ci.libraryId') + ->where('wgpi.workbenchId', $workbenchId) + ->where('wgpi.isLoop', 0) + ->whereIn('cl.id', $contentids) + ->count('DISTINCT wgpi.contentId'); - $sentContent = $query2->where('wgpi.contentId', '<', $lastSendData['contentId'])->order('wgpi.id ASC')->group('wgpi.contentId')->limit(0, $limit)->select(); - - if (empty($sentContent)) { - $sentContent = $query3->where('wgpi.contentId', '=', $fastSendData['contentId'])->order('wgpi.id ASC')->group('wgpi.contentId')->limit(0, $limit)->select(); - } - return $sentContent; - } else { - // 不能循环发送,只获取未发送的内容 - $list = $query->where('wgpi.id', 'null') - ->order($order) - ->limit(0, $limit) - ->select(); - return $list; + // 记录循环状态 + if ($sentCount >= $totalCount) { + Db::name('workbench_group_push_item') + ->where(['workbenchId' => $workbenchId, 'isLoop' => 0]) + ->update(['isLoop' => 1]); } } @@ -408,4 +422,4 @@ class WorkbenchGroupPushJob return false; } -} \ No newline at end of file +} \ No newline at end of file From 647e2a5f7e8f71377d21ff45d6e22473a1db4acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 14:52:32 +0800 Subject: [PATCH 056/146] =?UTF-8?q?refactor(weChat):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E8=81=8A=E5=A4=A9=E6=A8=A1=E5=9D=97=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将微信聊天相关状态从组件中提取到独立的zustand store 移除冗余的props传递,使用store管理当前联系人和消息 优化消息加载逻辑,统一处理消息状态 --- .../components/MessageEnter/index.tsx | 8 +- .../pc/ckbox/components/ChatWindow/index.tsx | 43 +- .../SidebarMenu/MessageList/index.tsx | 16 +- .../SidebarMenu/WechatFriends/index.tsx | 24 +- .../pc/ckbox/components/SidebarMenu/index.tsx | 27 +- Cunkebao/src/pages/pc/ckbox/index.tsx | 71 +-- Cunkebao/src/store/module/weChat.ts | 601 ------------------ .../src/store/module/weChat/weChat.data.ts | 14 + Cunkebao/src/store/module/weChat/weChat.ts | 106 +++ 9 files changed, 154 insertions(+), 756 deletions(-) delete mode 100644 Cunkebao/src/store/module/weChat.ts create mode 100644 Cunkebao/src/store/module/weChat/weChat.data.ts create mode 100644 Cunkebao/src/store/module/weChat/weChat.ts diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx index 0f4650d2..edd49db9 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx @@ -12,7 +12,7 @@ import { EnvironmentOutlined, StarOutlined, } from "@ant-design/icons"; -import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { useWebSocketStore } from "@/store/module/websocket/websocket"; import styles from "./MessageEnter.module.scss"; @@ -21,15 +21,11 @@ const { TextArea } = Input; interface MessageEnterProps { contract: ContractData | weChatGroup; - onSendMessage: (message: string) => void; } const { sendCommand } = useWebSocketStore.getState(); -const MessageEnter: React.FC = ({ - contract, - onSendMessage, -}) => { +const MessageEnter: React.FC = ({ contract }) => { const [inputValue, setInputValue] = useState(""); const [showMaterialModal, setShowMaterialModal] = useState(false); diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index 22e96c0f..087029ff 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -1,14 +1,5 @@ import React, { useState, useEffect, useRef } from "react"; -import { - Layout, - Button, - Avatar, - Space, - Dropdown, - Menu, - message, - Tooltip, -} from "antd"; +import { Layout, Button, Avatar, Space, Dropdown, Menu, Tooltip } from "antd"; import { PhoneOutlined, VideoCameraOutlined, @@ -26,7 +17,6 @@ import { EnvironmentOutlined, } from "@ant-design/icons"; import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; -import { getChatMessages } from "@/pages/pc/ckbox/api"; import styles from "./ChatWindow.module.scss"; import { useWebSocketStore, @@ -35,51 +25,27 @@ import { import { formatWechatTime } from "@/utils/common"; import Person from "./components/Person"; import MessageEnter from "./components/MessageEnter"; +import { useWeChatStore } from "@/store/module/weChat/weChat"; const { Header, Content } = Layout; interface ChatWindowProps { contract: ContractData | weChatGroup; - onSendMessage: (message: string) => void; showProfile?: boolean; onToggleProfile?: () => void; } const ChatWindow: React.FC = ({ contract, - onSendMessage, showProfile = true, onToggleProfile, }) => { - const [, contextHolder] = message.useMessage(); const [messages, setMessages] = useState([]); const [loading, setLoading] = useState(false); const [pendingVideoRequests, setPendingVideoRequests] = useState< Record >({}); const messagesEndRef = useRef(null); - - useEffect(() => { - setLoading(true); - const params: any = { - wechatAccountId: contract.wechatAccountId, - From: 1, - To: 4704624000000, - Count: 5, - olderData: true, - }; - if (contract.chatroomId) { - params.wechatChatroomId = contract.id; - } else { - params.wechatFriendId = contract.id; - } - getChatMessages(params) - .then(msg => { - setMessages(msg); - }) - .finally(() => { - setLoading(false); - }); - }, [contract.id]); + const currentMessages = useWeChatStore(state => state.currentMessages); useEffect(() => { // 只有在非视频加载操作时才自动滚动到底部 @@ -709,7 +675,6 @@ const ChatWindow: React.FC = ({ return ( - {contextHolder} {/* 聊天主体区域 */} {/* 聊天头部 */} @@ -775,7 +740,7 @@ const ChatWindow: React.FC = ({ {/* 消息输入组件 */} - + {/* 右侧个人资料卡片 */} diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx index 877b9c9e..3507fc6d 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx @@ -2,19 +2,19 @@ import React from "react"; import { List, Avatar, Badge } from "antd"; import { UserOutlined, TeamOutlined } from "@ant-design/icons"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { useWeChatStore } from "@/store/module/weChat/weChat"; import styles from "./MessageList.module.scss"; import { formatWechatTime } from "@/utils/common"; interface MessageListProps { chatSessions: ContractData[] | weChatGroup[]; - currentChat: ContractData | weChatGroup; - onContactClick: (chat: ContractData | weChatGroup) => void; } -const MessageList: React.FC = ({ - chatSessions, - currentChat, - onContactClick, -}) => { +const MessageList: React.FC = ({ chatSessions }) => { + const { setCurrentContact, currentContract } = useWeChatStore(); + + const onContactClick = (session: ContractData | weChatGroup) => { + setCurrentContact(session); + }; return (
= ({ onContactClick(session)} > diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx index 24ae993f..76f5f981 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx @@ -7,16 +7,13 @@ import { searchContactsAndGroups, } from "@/store/module/ckchat/ckchat"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { addChatSession } from "@/store/module/ckchat/ckchat"; +import { useWeChatStore } from "@/store/module/weChat/weChat"; interface WechatFriendsProps { - contracts: ContractData[] | weChatGroup[]; - onContactClick: (contract: ContractData | weChatGroup) => void; selectedContactId?: ContractData | weChatGroup; } - const ContactListSimple: React.FC = ({ - contracts, - onContactClick, selectedContactId, }) => { const [newContractList, setNewContractList] = useState([]); @@ -64,6 +61,11 @@ const ContactListSimple: React.FC = ({ const [loading, setLoading] = useState<{ [key: string]: boolean }>({}); const [hasMore, setHasMore] = useState<{ [key: string]: boolean }>({}); const [page, setPage] = useState<{ [key: string]: number }>({}); + const { setCurrentContact } = useWeChatStore(); + const onContactClick = (contact: ContractData | weChatGroup) => { + addChatSession(contact); + setCurrentContact(contact); + }; // 渲染联系人项 const renderContactItem = (contact: ContractData | weChatGroup) => { @@ -219,7 +221,7 @@ const ContactListSimple: React.FC = ({
未找到匹配的联系人
)} - ) : newContractList && newContractList.length > 0 ? ( + ) : ( // 正常模式:显示分组 = ({ onChange={keys => setActiveKey(keys as string[])} items={getCollapseItems()} /> - ) : ( - // 备用模式:显示传入的联系人列表 - <> -
全部联系人
- - )}
); diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx index 4704cf38..d9152f95 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx @@ -6,24 +6,19 @@ import { ChromeOutlined, MessageOutlined, } from "@ant-design/icons"; -import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import WechatFriends from "./WechatFriends"; import MessageList from "./MessageList/index"; import styles from "./SidebarMenu.module.scss"; import { useCkChatStore } from "@/store/module/ckchat/ckchat"; - +import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; interface SidebarMenuProps { - contracts: ContractData[] | weChatGroup[]; - currentChat: ContractData | weChatGroup; - onContactClick: (contract: ContractData | weChatGroup) => void; loading?: boolean; + currentContract?: ContractData | weChatGroup; } const SidebarMenu: React.FC = ({ - contracts, - currentChat, - onContactClick, loading = false, + currentContract, }) => { const chatSessions = useCkChatStore(state => state.getChatSessions()); const searchKeyword = useCkChatStore(state => state.searchKeyword); @@ -133,21 +128,9 @@ const SidebarMenu: React.FC = ({ const renderContent = () => { switch (activeTab) { case "chats": - return ( - - ); + return ; case "contracts": - return ( - - ); + return ; case "groups": return (
diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx index e02d5739..38c9cf84 100644 --- a/Cunkebao/src/pages/pc/ckbox/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/index.tsx @@ -10,23 +10,15 @@ import styles from "./index.module.scss"; import { addChatSession } from "@/store/module/ckchat/ckchat"; const { Header, Content, Sider } = Layout; import { chatInitAPIdata, initSocket } from "./main"; -import { clearUnreadCount, updateConfig } from "@/pages/pc/ckbox/api"; -import { - KfUserListData, - weChatGroup, - ContractData, -} from "@/pages/pc/ckbox/data"; +import { useWeChatStore } from "@/store/module/weChat/weChat"; + +import { KfUserListData } from "@/pages/pc/ckbox/data"; const CkboxPage: React.FC = () => { - const [messageApi, contextHolder] = message.useMessage(); - const [contracts, setContacts] = useState([]); - const [currentChat, setCurrentChat] = useState( - null, - ); // 不要在组件初始化时获取sendCommand,而是在需要时动态获取 const [loading, setLoading] = useState(false); const [showProfile, setShowProfile] = useState(true); - + const currentContract = useWeChatStore(state => state.currentContract); useEffect(() => { // 方法一:使用 Promise 链式调用处理异步函数 setLoading(true); @@ -48,8 +40,6 @@ const CkboxPage: React.FC = () => { addChatSession(v); }); - setContacts(isChatList); - // 数据加载完成后初始化WebSocket连接 initSocket(); }) @@ -61,50 +51,9 @@ const CkboxPage: React.FC = () => { }); }, []); - //开始开启聊天 - const handleContactClick = (contract: ContractData | weChatGroup) => { - clearUnreadCount([contract.id]).then(() => { - contract.unreadCount = 0; - addChatSession(contract); - setCurrentChat(contract); - updateConfig({ - id: contract.id, - config: { chat: true }, - }); - }); - }; - - const handleSendMessage = async (message: string) => { - if (!currentChat || !message.trim()) return; - - try { - // 更新当前聊天会话 - const updatedSession = { - ...currentChat, - lastMessage: message, - lastTime: dayjs().toISOString(), - unreadCount: 0, - }; - - setCurrentChat(updatedSession); - - messageApi.success("消息发送成功"); - } catch (error) { - messageApi.error("消息发送失败"); - } - }; - - // 处理垂直侧边栏用户选择 - const handleVerticalUserSelect = (userId: string) => { - // setActiveVerticalUserId(userId); - // 这里可以根据选择的用户类别筛选不同的联系人列表 - // 例如:根据userId加载不同分类的联系人 - }; - return ( - {contextHolder}
触客宝
{/* 垂直侧边栏 */} @@ -115,17 +64,12 @@ const CkboxPage: React.FC = () => { {/* 左侧联系人边栏 */} - + {/* 主内容区 */} - {currentChat ? ( + {currentContract ? (
@@ -142,8 +86,7 @@ const CkboxPage: React.FC = () => {
setShowProfile(!showProfile)} /> diff --git a/Cunkebao/src/store/module/weChat.ts b/Cunkebao/src/store/module/weChat.ts deleted file mode 100644 index e1cf17a4..00000000 --- a/Cunkebao/src/store/module/weChat.ts +++ /dev/null @@ -1,601 +0,0 @@ -import { create } from "zustand"; -import { createPersistStore } from "../createPersistStore"; -import { getChatMessages } from "@/pages/pc/ckbox/api"; -import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; -import { useWebSocketStore } from "./websocket/websocket"; - -// 聊天列表项类型定义 -export interface ChatListItem { - id: string; - name: string; - avatar?: string; - type: "friend" | "group"; - lastMessage?: ChatRecord; - unreadCount: number; - isOnline?: boolean; - lastActiveTime: number; // 最后活跃时间,用于排序 - contractData: ContractData | weChatGroup; // 保存完整的联系人数据 -} - -// 微信聊天相关的类型定义 -export interface WeChatState { - // 当前选中的联系人/群组 - currentContract: ContractData | weChatGroup | null; - - // 当前聊天用户的消息列表(只存储当前聊天用户的消息) - currentMessages: ChatRecord[]; - - // 联系人列表(所有好友和群组) - contacts: (ContractData | weChatGroup)[]; - - // 聊天列表(有聊天记录的联系人/群组,按最后活跃时间排序) - chatList: ChatListItem[]; - - // 消息发送状态 - sendingStatus: Record; // key为消息临时ID,value为发送状态 - - // 消息加载状态 - loadingMessages: boolean; - - // 待处理的视频请求 - pendingVideoRequests: Record; // messageId -> requestId - - // 输入框内容状态 - 按联系人/群组ID分组存储 - inputValues: Record; - - // 素材模态框状态 - showMaterialModal: boolean; - selectedMaterialType: string; -} - -export interface WeChatActions { - // 设置当前选中的联系人/群组 - setCurrentContract: (contract: ContractData | weChatGroup | null) => void; - - // 获取聊天消息 - fetchChatMessages: (contract: ContractData | weChatGroup) => Promise; - - // 设置当前消息列表 - setCurrentMessages: (messages: ChatRecord[]) => void; - - // 添加消息到当前聊天 - addMessageToCurrentChat: (message: ChatRecord) => void; - - // 更新消息内容(用于视频加载状态更新等) - updateMessage: (messageId: number, updates: Partial) => void; - - // 发送消息 - sendMessage: ( - contract: ContractData | weChatGroup, - content: string, - msgType?: number, - ) => Promise; - - // 处理视频播放请求 - handleVideoPlayRequest: ( - tencentUrl: string, - messageId: number, - contract: ContractData | weChatGroup, - ) => void; - - // 设置待处理的视频请求 - setPendingVideoRequest: (messageId: string, requestId: string) => void; - - // 移除待处理的视频请求 - removePendingVideoRequest: (messageId: string) => void; - - // 处理视频下载响应 - handleVideoDownloadResponse: (messageId: string, videoUrl: string) => void; - - // 设置输入框内容 - setInputValue: (contractId: string, value: string) => void; - - // 获取输入框内容 - getInputValue: (contractId: string) => string; - - // 清空输入框内容 - clearInputValue: (contractId: string) => void; - - // 素材模态框相关操作 - setShowMaterialModal: (show: boolean) => void; - setSelectedMaterialType: (type: string) => void; - - // 新消息处理 - handleNewMessage: ( - message: ChatRecord, - fromContract: ContractData | weChatGroup, - ) => void; - - // 聊天列表管理 - setContacts: (contacts: (ContractData | weChatGroup)[]) => void; - setChatList: (chatList: ChatListItem[]) => void; - updateChatListItem: ( - contractId: string, - updates: Partial, - ) => void; - moveToTopOfChatList: (contractId: string) => void; - addToChatList: (contract: ContractData | weChatGroup) => void; - - // 未读消息管理 - incrementUnreadCount: (contractId: string) => void; - clearUnreadCount: (contractId: string) => void; - - // 便捷选择器 - getUnreadTotal: () => number; - getChatListSorted: () => ChatListItem[]; - - // 清理指定联系人的数据 - clearContractData: (contractId: string) => void; - - // 清理所有数据 - clearAllData: () => void; -} - -type WeChatStore = WeChatState & WeChatActions; - -// 生成联系人/群组的唯一ID -const getContractId = (contract: ContractData | weChatGroup): string => { - if ("chatroomId" in contract && contract.chatroomId) { - return `group_${contract.chatroomId}`; - } - return `friend_${contract.id}`; -}; - -export const useWeChatStore = create()( - createPersistStore( - (set, get) => ({ - // 初始状态 - currentContract: null, - currentMessages: [], - contacts: [], - chatList: [], - sendingStatus: {}, - loadingMessages: false, - pendingVideoRequests: {}, - inputValues: {}, - showMaterialModal: false, - selectedMaterialType: "", - - // Actions - setCurrentContract: contract => { - set({ currentContract: contract }); - // 切换联系人时清空当前消息,等待重新加载 - set({ currentMessages: [] }); - // 清除该联系人的未读数 - if (contract) { - const contractId = getContractId(contract); - get().clearUnreadCount(contractId); - } - }, - - fetchChatMessages: async contract => { - const { currentMessages } = get(); - - // 如果已经有消息数据,不重复加载 - if (currentMessages.length > 0) { - return; - } - - set({ loadingMessages: true }); - - try { - const params: any = { - wechatAccountId: contract.wechatAccountId, - From: 1, - To: 4704624000000, - Count: 10, - olderData: true, - }; - - if ("chatroomId" in contract && contract.chatroomId) { - params.wechatChatroomId = contract.chatroomId; - } else { - params.wechatFriendId = contract.id; - } - - const messages = await getChatMessages(params); - - set({ currentMessages: messages }); - } catch (error) { - console.error("获取聊天消息失败:", error); - } finally { - set({ loadingMessages: false }); - } - }, - - setCurrentMessages: messages => { - set({ currentMessages: messages }); - }, - - addMessageToCurrentChat: message => { - set(state => ({ - currentMessages: [...state.currentMessages, message], - })); - }, - - updateMessage: (messageId, updates) => { - set(state => ({ - currentMessages: state.currentMessages.map(msg => - msg.id === messageId ? { ...msg, ...updates } : msg, - ), - })); - }, - - sendMessage: async (contract, content, msgType = 1) => { - const contractId = getContractId(contract); - const tempId = `temp_${Date.now()}`; - - // 设置发送状态 - set(state => ({ - sendingStatus: { - ...state.sendingStatus, - [tempId]: true, - }, - })); - - try { - const params = { - wechatAccountId: contract.wechatAccountId, - wechatChatroomId: - "chatroomId" in contract && contract.chatroomId - ? contract.chatroomId - : 0, - wechatFriendId: - "chatroomId" in contract && contract.chatroomId ? 0 : contract.id, - msgSubType: 0, - msgType, - content, - }; - - // 通过WebSocket发送消息 - const { sendCommand } = useWebSocketStore.getState(); - await sendCommand("CmdSendMessage", params); - - // 清空对应的输入框内容 - get().clearInputValue(contractId); - } catch (error) { - console.error("发送消息失败:", error); - } finally { - // 移除发送状态 - set(state => { - const newSendingStatus = { ...state.sendingStatus }; - delete newSendingStatus[tempId]; - return { sendingStatus: newSendingStatus }; - }); - } - }, - - handleVideoPlayRequest: (tencentUrl, messageId, contract) => { - const requestSeq = `${Date.now()}`; - - console.log("发送视频下载请求:", { messageId, requestSeq }); - - // 构建socket请求数据 - const { sendCommand } = useWebSocketStore.getState(); - sendCommand("CmdDownloadVideo", { - chatroomMessageId: - "chatroomId" in contract && contract.chatroomId ? messageId : 0, - friendMessageId: - "chatroomId" in contract && contract.chatroomId ? 0 : messageId, - seq: requestSeq, - tencentUrl: tencentUrl, - wechatAccountId: contract.wechatAccountId, - }); - - // 添加到待处理队列 - get().setPendingVideoRequest( - messageId.toString(), - messageId.toString(), - ); - - // 更新消息状态为加载中 - const messages = get().currentMessages; - const targetMessage = messages.find(msg => msg.id === messageId); - if (targetMessage) { - try { - const originalContent = - typeof targetMessage.content === "string" - ? JSON.parse(targetMessage.content) - : targetMessage.content; - - get().updateMessage(messageId, { - content: JSON.stringify({ - ...originalContent, - isLoading: true, - }), - }); - } catch (e) { - console.error("解析消息内容失败:", e); - } - } - }, - - setPendingVideoRequest: (messageId, requestId) => { - set(state => ({ - pendingVideoRequests: { - ...state.pendingVideoRequests, - [messageId]: requestId, - }, - })); - }, - - removePendingVideoRequest: messageId => { - set(state => { - const newRequests = { ...state.pendingVideoRequests }; - delete newRequests[messageId]; - return { pendingVideoRequests: newRequests }; - }); - }, - - handleVideoDownloadResponse: (messageId, videoUrl) => { - const { currentMessages } = get(); - const targetMessage = currentMessages.find( - msg => msg.id === Number(messageId), - ); - - if (targetMessage) { - try { - const msgContent = - typeof targetMessage.content === "string" - ? JSON.parse(targetMessage.content) - : targetMessage.content; - - // 更新消息内容,添加视频URL并移除加载状态 - get().updateMessage(Number(messageId), { - content: JSON.stringify({ - ...msgContent, - videoUrl: videoUrl, - isLoading: false, - }), - }); - - // 从待处理队列中移除 - get().removePendingVideoRequest(messageId); - } catch (e) { - console.error("解析消息内容失败:", e); - } - } - }, - - setInputValue: (contractId, value) => { - set(state => ({ - inputValues: { - ...state.inputValues, - [contractId]: value, - }, - })); - }, - - getInputValue: contractId => { - return get().inputValues[contractId] || ""; - }, - - clearInputValue: contractId => { - set(state => { - const newInputValues = { ...state.inputValues }; - delete newInputValues[contractId]; - return { inputValues: newInputValues }; - }); - }, - - setShowMaterialModal: show => { - set({ showMaterialModal: show }); - }, - - setSelectedMaterialType: type => { - set({ selectedMaterialType: type }); - }, - - // 新消息处理 - handleNewMessage: (message, fromContract) => { - const { currentContract } = get(); - const contractId = getContractId(fromContract); - - // 如果是当前聊天用户的消息,直接添加到当前消息列表 - if (currentContract && getContractId(currentContract) === contractId) { - get().addMessageToCurrentChat(message); - } else { - // 如果不是当前聊天用户,更新聊天列表 - get().incrementUnreadCount(contractId); - get().moveToTopOfChatList(contractId); - - // 更新聊天列表项的最后消息 - get().updateChatListItem(contractId, { - lastMessage: message, - lastActiveTime: message.createTime || Date.now(), - }); - } - }, - - // 聊天列表管理 - setContacts: contacts => { - set({ contacts }); - }, - - setChatList: chatList => { - set({ chatList }); - }, - - updateChatListItem: (contractId, updates) => { - set(state => ({ - chatList: state.chatList.map(item => - getContractId(item.contractData) === contractId - ? { ...item, ...updates } - : item, - ), - })); - }, - - moveToTopOfChatList: contractId => { - set(state => { - const chatList = [...state.chatList]; - const itemIndex = chatList.findIndex( - item => getContractId(item.contractData) === contractId, - ); - - if (itemIndex > 0) { - // 移动到顶部 - const item = chatList.splice(itemIndex, 1)[0]; - chatList.unshift({ ...item, lastActiveTime: Date.now() }); - } else if (itemIndex === -1) { - // 如果不在聊天列表中,从联系人列表找到并添加 - const contract = state.contacts.find( - c => getContractId(c) === contractId, - ); - if (contract) { - get().addToChatList(contract); - } - } - - return { chatList }; - }); - }, - - addToChatList: contract => { - set(state => { - const contractId = getContractId(contract); - // 检查是否已存在 - const exists = state.chatList.some( - item => getContractId(item.contractData) === contractId, - ); - if (exists) return state; - - const newChatItem: ChatListItem = { - id: contractId, - name: contract.nickName || contract.name || "", - avatar: contract.avatar, - type: "chatroomId" in contract ? "group" : "friend", - unreadCount: 0, - lastActiveTime: Date.now(), - contractData: contract, - }; - - return { - chatList: [newChatItem, ...state.chatList], - }; - }); - }, - - // 未读消息管理 - incrementUnreadCount: contractId => { - // 更新聊天列表中的未读数 - set(state => ({ - chatList: state.chatList.map(item => - getContractId(item.contractData) === contractId - ? { ...item, unreadCount: item.unreadCount + 1 } - : item, - ), - })); - }, - - clearUnreadCount: contractId => { - // 清除聊天列表中的未读数 - set(state => ({ - chatList: state.chatList.map(item => - getContractId(item.contractData) === contractId - ? { ...item, unreadCount: 0 } - : item, - ), - })); - }, - - // 便捷选择器 - getUnreadTotal: () => { - const { chatList } = get(); - return chatList.reduce((total, item) => total + item.unreadCount, 0); - }, - - getChatListSorted: () => { - const { chatList } = get(); - return [...chatList].sort( - (a, b) => b.lastActiveTime - a.lastActiveTime, - ); - }, - - clearContractData: contractId => { - set(state => { - const newInputValues = { ...state.inputValues }; - delete newInputValues[contractId]; - - return { - inputValues: newInputValues, - chatList: state.chatList.filter( - item => getContractId(item.contractData) !== contractId, - ), - }; - }); - }, - - clearAllData: () => { - set({ - currentContract: null, - currentMessages: [], - contacts: [], - chatList: [], - sendingStatus: {}, - loadingMessages: false, - pendingVideoRequests: {}, - inputValues: {}, - showMaterialModal: false, - selectedMaterialType: "", - }); - }, - }), - { - name: "wechat-store", - // 只持久化部分状态,排除临时状态 - partialize: state => ({ - contacts: state.contacts, - chatList: state.chatList, - inputValues: state.inputValues, - }), - }, - ), -); - -// 导出便捷的选择器函数 -export const useCurrentContract = () => - useWeChatStore(state => state.currentContract); -export const useCurrentMessages = () => - useWeChatStore(state => state.currentMessages); -export const useCurrentInputValue = () => { - const { currentContract, getInputValue } = useWeChatStore(); - if (!currentContract) return ""; - const contractId = getContractId(currentContract); - return getInputValue(contractId); -}; -export const useChatList = () => - useWeChatStore(state => state.getChatListSorted()); -export const useUnreadTotal = () => - useWeChatStore(state => state.getUnreadTotal()); - -// 初始化WebSocket消息监听 -if (typeof window !== "undefined") { - // 监听WebSocket消息,处理视频下载响应 - useWebSocketStore.subscribe(state => { - const messages = state.messages as any[]; - const { pendingVideoRequests, handleVideoDownloadResponse } = - useWeChatStore.getState(); - - // 只有当有待处理的视频请求时才处理消息 - if (Object.keys(pendingVideoRequests).length === 0) { - return; - } - - messages.forEach(message => { - if (message?.content?.cmdType === "CmdDownloadVideoResult") { - console.log("收到视频下载响应:", message.content); - - // 检查是否是我们正在等待的视频响应 - const messageId = Object.keys(pendingVideoRequests).find( - id => pendingVideoRequests[id] === message.content.friendMessageId, - ); - - if (messageId) { - console.log("找到对应的消息ID:", messageId); - handleVideoDownloadResponse(messageId, message.content.url); - } - } - }); - }); -} diff --git a/Cunkebao/src/store/module/weChat/weChat.data.ts b/Cunkebao/src/store/module/weChat/weChat.data.ts new file mode 100644 index 00000000..3bd851eb --- /dev/null +++ b/Cunkebao/src/store/module/weChat/weChat.data.ts @@ -0,0 +1,14 @@ +import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +// 微信聊天相关的类型定义 +export interface WeChatState { + // 当前选中的联系人/群组 + currentContract: ContractData | weChatGroup | null; + + // 当前聊天用户的消息列表(只存储当前聊天用户的消息) + currentMessages: ChatRecord[]; + + setCurrentContact: (contract: ContractData | weChatGroup) => void; + // 消息加载状态 + messagesLoading: boolean; + loadChatMessages: (contact: ContractData | weChatGroup) => Promise; +} diff --git a/Cunkebao/src/store/module/weChat/weChat.ts b/Cunkebao/src/store/module/weChat/weChat.ts new file mode 100644 index 00000000..66ee7818 --- /dev/null +++ b/Cunkebao/src/store/module/weChat/weChat.ts @@ -0,0 +1,106 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; +import { getChatMessages } from "@/pages/pc/ckbox/api"; +import { WeChatState } from "./weChat.data"; +import { clearUnreadCount, updateConfig } from "@/pages/pc/ckbox/api"; +import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { addChatSession } from "@/store/module/ckchat/ckchat"; +export const useWeChatStore = create()( + persist( + (set, get) => ({ + // 初始状态 + currentContract: null, + currentMessages: [], + messagesLoading: false, + + // Actions + setCurrentContact: (contract: ContractData | weChatGroup) => { + const state = useWeChatStore.getState(); + // 切换联系人时清空当前消息,等待重新加载 + set({ currentMessages: [] }); + clearUnreadCount([contract.id]).then(() => { + contract.unreadCount = 0; + addChatSession(contract); + set({ currentContract: contract }); + updateConfig({ + id: contract.id, + config: { chat: true }, + }); + state.loadChatMessages(contract); + }); + }, + + loadChatMessages: async contact => { + set({ messagesLoading: true }); + + try { + const params: any = { + wechatAccountId: contact.wechatAccountId, + From: 1, + To: 4704624000000, + Count: 10, + olderData: true, + }; + + if ("chatroomId" in contact && contact.chatroomId) { + params.wechatChatroomId = contact.chatroomId; + } else { + params.wechatFriendId = contact.id; + } + + const messages = await getChatMessages(params); + set({ currentMessages: messages }); + } catch (error) { + console.error("获取聊天消息失败:", error); + } finally { + set({ messagesLoading: false }); + } + }, + + setMessageLoading: loading => { + set({ messagesLoading: loading }); + }, + + addMessage: message => { + set(state => ({ + currentMessages: [...state.currentMessages, message], + })); + }, + + updateMessage: (messageId, updates) => { + set(state => ({ + currentMessages: state.currentMessages.map(msg => + msg.id === messageId ? { ...msg, ...updates } : msg, + ), + })); + }, + + // 便捷选择器 + getCurrentContact: () => get().currentContract, + getCurrentMessages: () => get().currentMessages, + getMessagesLoading: () => get().messagesLoading, + + clearAllData: () => { + set({ + currentContract: null, + currentMessages: [], + messagesLoading: false, + }); + }, + }), + { + name: "wechat-storage", + partialize: state => ({ + currentContract: state.currentContract, + }), + }, + ), +); + +// 导出便捷的选择器函数 +export const useCurrentContact = () => + useWeChatStore(state => state.currentContract); +export const useCurrentMessages = () => + useWeChatStore(state => state.currentMessages); +export const useMessagesLoading = () => + useWeChatStore(state => state.messagesLoading); From 8b1387a8ed2d7a8424e89959b71b9cf005570f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 15:08:29 +0800 Subject: [PATCH 057/146] =?UTF-8?q?feat(weChat):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E8=AF=B7=E6=B1=82=E5=A4=84=E7=90=86=E5=92=8C?= =?UTF-8?q?WebSocket=E7=9B=91=E5=90=AC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在weChat store中新增pendingVideoRequests状态和相关操作方法 - 实现WebSocket监听处理视频下载响应 - 重构ChatWindow组件,将视频处理逻辑移至store - 优化消息分组和渲染逻辑 --- .../pc/ckbox/components/ChatWindow/index.tsx | 149 +++--------------- .../src/store/module/weChat/weChat.data.ts | 16 +- Cunkebao/src/store/module/weChat/weChat.ts | 117 +++++++++++++- 3 files changed, 155 insertions(+), 127 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index 087029ff..c4473c64 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from "react"; +import React, { useEffect, useRef } from "react"; import { Layout, Button, Avatar, Space, Dropdown, Menu, Tooltip } from "antd"; import { PhoneOutlined, @@ -18,10 +18,7 @@ import { } from "@ant-design/icons"; import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import styles from "./ChatWindow.module.scss"; -import { - useWebSocketStore, - WebSocketMessage, -} from "@/store/module/websocket/websocket"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; import { formatWechatTime } from "@/utils/common"; import Person from "./components/Person"; import MessageEnter from "./components/MessageEnter"; @@ -39,18 +36,13 @@ const ChatWindow: React.FC = ({ showProfile = true, onToggleProfile, }) => { - const [messages, setMessages] = useState([]); - const [loading, setLoading] = useState(false); - const [pendingVideoRequests, setPendingVideoRequests] = useState< - Record - >({}); const messagesEndRef = useRef(null); const currentMessages = useWeChatStore(state => state.currentMessages); useEffect(() => { // 只有在非视频加载操作时才自动滚动到底部 // 检查是否有视频正在加载中 - const hasLoadingVideo = messages.some(msg => { + const hasLoadingVideo = currentMessages.some(msg => { try { const content = typeof msg.content === "string" @@ -65,78 +57,13 @@ const ChatWindow: React.FC = ({ if (!hasLoadingVideo) { scrollToBottom(); } - }, [messages]); + }, [currentMessages]); - // 添加 WebSocket 消息订阅 - 监听视频下载响应消息 + // 初始化WebSocket监听 useEffect(() => { - // 只有当有待处理的视频请求时才订阅WebSocket消息 - if (Object.keys(pendingVideoRequests).length === 0) { - return; - } - - console.log("开始监听视频下载响应,当前待处理请求:", pendingVideoRequests); - - // 订阅 WebSocket 消息变化 - const unsubscribe = useWebSocketStore.subscribe(state => { - // 只处理新增的消息 - const messages = state.messages as WebSocketMessage[]; - - // 筛选出视频下载响应消息 - messages.forEach(message => { - if (message?.content?.cmdType === "CmdDownloadVideoResult") { - console.log("收到视频下载响应:", message.content); - - // 检查是否是我们正在等待的视频响应 - const messageId = Object.keys(pendingVideoRequests).find( - id => pendingVideoRequests[id] === message.content.friendMessageId, - ); - - if (messageId) { - console.log("找到对应的消息ID:", messageId); - - // 从待处理队列中移除 - setPendingVideoRequests(prev => { - const newRequests = { ...prev }; - delete newRequests[messageId]; - return newRequests; - }); - - // 更新消息内容,将视频URL添加到对应的消息中 - setMessages(prevMessages => { - return prevMessages.map(msg => { - if (msg.id === Number(messageId)) { - try { - const msgContent = - typeof msg.content === "string" - ? JSON.parse(msg.content) - : msg.content; - - // 更新消息内容,添加视频URL并移除加载状态 - return { - ...msg, - content: JSON.stringify({ - ...msgContent, - videoUrl: message.content.url, - isLoading: false, - }), - }; - } catch (e) { - console.error("解析消息内容失败:", e); - } - } - return msg; - }); - }); - } - } - }); - }); - - // 组件卸载时取消订阅 - return () => { - unsubscribe(); - }; - }, [pendingVideoRequests]); // 依赖于pendingVideoRequests,当队列变化时重新设置订阅 + const unsubscribe = useWeChatStore.getState().initWebSocketListener(); + return unsubscribe; + }, []); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); @@ -157,29 +84,13 @@ const ChatWindow: React.FC = ({ wechatAccountId: contract.wechatAccountId, }); - // 将消息ID和请求序列号添加到待处理队列 - setPendingVideoRequests(prev => ({ - ...prev, - [messageId]: messageId, - })); + // 将消息ID添加到待处理队列 + useWeChatStore + .getState() + .addPendingVideoRequest(messageId.toString(), messageId.toString()); // 更新消息状态为加载中 - setMessages(prevMessages => { - return prevMessages.map(msg => { - if (msg.id === messageId) { - // 保存原始内容,添加loading状态 - const originalContent = msg.content; - return { - ...msg, - content: JSON.stringify({ - ...JSON.parse(originalContent), - isLoading: true, - }), - }; - } - return msg; - }); - }); + useWeChatStore.getState().setMessageLoading(messageId.toString(), true); }; // 解析消息内容,判断消息类型并返回对应的渲染内容 @@ -613,14 +524,10 @@ const ChatWindow: React.FC = ({ // 用于分组消息并添加时间戳的辅助函数 const groupMessagesByTime = (messages: ChatRecord[]) => { - const groups: { time: string; messages: ChatRecord[] }[] = []; - messages.forEach(msg => { - // 使用 formatWechatTime 函数格式化时间戳 - const formattedTime = formatWechatTime(msg.wechatTime); - groups.push({ time: formattedTime, messages: [msg] }); - }); - - return groups; + return messages.map(msg => ({ + time: formatWechatTime(msg.wechatTime), + messages: [msg], + })); }; const renderMessage = (msg: ChatRecord) => { @@ -721,21 +628,13 @@ const ChatWindow: React.FC = ({ {/* 聊天内容 */}
- {loading ? ( -
-
加载中...
-
- ) : ( - <> - {groupMessagesByTime(messages).map((group, groupIndex) => ( - -
{group.time}
- {group.messages.map(renderMessage)} -
- ))} -
- - )} + {groupMessagesByTime(currentMessages).map((group, groupIndex) => ( + +
{group.time}
+ {group.messages.map(renderMessage)} +
+ ))} +
diff --git a/Cunkebao/src/store/module/weChat/weChat.data.ts b/Cunkebao/src/store/module/weChat/weChat.data.ts index 3bd851eb..955ce031 100644 --- a/Cunkebao/src/store/module/weChat/weChat.data.ts +++ b/Cunkebao/src/store/module/weChat/weChat.data.ts @@ -7,8 +7,22 @@ export interface WeChatState { // 当前聊天用户的消息列表(只存储当前聊天用户的消息) currentMessages: ChatRecord[]; - setCurrentContact: (contract: ContractData | weChatGroup) => void; // 消息加载状态 messagesLoading: boolean; + + // 待处理的视频请求队列 + pendingVideoRequests: Record; + + // Actions + setCurrentContact: (contract: ContractData | weChatGroup) => void; loadChatMessages: (contact: ContractData | weChatGroup) => Promise; + + // 视频请求相关方法 + addPendingVideoRequest: (messageId: string, requestId: string) => void; + removePendingVideoRequest: (messageId: string) => void; + updateMessageWithVideo: (messageId: string, videoUrl: string) => void; + setMessageLoading: (messageId: string, isLoading: boolean) => void; + + // WebSocket监听初始化 + initWebSocketListener: () => void; } diff --git a/Cunkebao/src/store/module/weChat/weChat.ts b/Cunkebao/src/store/module/weChat/weChat.ts index 66ee7818..633969b8 100644 --- a/Cunkebao/src/store/module/weChat/weChat.ts +++ b/Cunkebao/src/store/module/weChat/weChat.ts @@ -5,6 +5,10 @@ import { WeChatState } from "./weChat.data"; import { clearUnreadCount, updateConfig } from "@/pages/pc/ckbox/api"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { addChatSession } from "@/store/module/ckchat/ckchat"; +import { + useWebSocketStore, + WebSocketMessage, +} from "@/store/module/websocket/websocket"; export const useWeChatStore = create()( persist( (set, get) => ({ @@ -12,6 +16,7 @@ export const useWeChatStore = create()( currentContract: null, currentMessages: [], messagesLoading: false, + pendingVideoRequests: {}, // Actions setCurrentContact: (contract: ContractData | weChatGroup) => { @@ -58,7 +63,7 @@ export const useWeChatStore = create()( }, setMessageLoading: loading => { - set({ messagesLoading: loading }); + set({ messagesLoading: Boolean(loading) }); }, addMessage: message => { @@ -80,11 +85,121 @@ export const useWeChatStore = create()( getCurrentMessages: () => get().currentMessages, getMessagesLoading: () => get().messagesLoading, + // 视频请求相关方法 + addPendingVideoRequest: (messageId: string, requestId: string) => { + set(state => ({ + pendingVideoRequests: { + ...state.pendingVideoRequests, + [messageId]: requestId, + }, + })); + }, + + removePendingVideoRequest: (messageId: string) => { + set(state => { + const newRequests = { ...state.pendingVideoRequests }; + delete newRequests[messageId]; + return { pendingVideoRequests: newRequests }; + }); + }, + + updateMessageWithVideo: (messageId: string, videoUrl: string) => { + set(state => ({ + currentMessages: state.currentMessages.map(msg => { + if (msg.id === Number(messageId)) { + try { + const msgContent = + typeof msg.content === "string" + ? JSON.parse(msg.content) + : msg.content; + return { + ...msg, + content: JSON.stringify({ + ...msgContent, + videoUrl: videoUrl, + isLoading: false, + }), + }; + } catch (e) { + console.error("解析消息内容失败:", e); + } + } + return msg; + }), + })); + }, + + setMessageLoadingStatus: (messageId: string, isLoading: boolean) => { + set(state => ({ + currentMessages: state.currentMessages.map(msg => { + if (msg.id === Number(messageId)) { + try { + const originalContent = msg.content; + return { + ...msg, + content: JSON.stringify({ + ...JSON.parse(originalContent), + isLoading: isLoading, + }), + }; + } catch (e) { + console.error("解析消息内容失败:", e); + } + } + return msg; + }), + })); + }, + + // WebSocket监听初始化 + initWebSocketListener: () => { + const unsubscribe = useWebSocketStore.subscribe(state => { + const messages = state.messages as WebSocketMessage[]; + const currentState = get(); + + // 只有当有待处理的视频请求时才处理 + if (Object.keys(currentState.pendingVideoRequests).length === 0) { + return; + } + + messages.forEach(message => { + if (message?.content?.cmdType === "CmdDownloadVideoResult") { + console.log("收到视频下载响应:", message.content); + + // 检查是否是我们正在等待的视频响应 + const messageId = Object.keys( + currentState.pendingVideoRequests, + ).find( + id => + currentState.pendingVideoRequests[id] === + message.content.friendMessageId, + ); + + if (messageId) { + console.log("找到对应的消息ID:", messageId); + + // 从待处理队列中移除 + currentState.removePendingVideoRequest(messageId); + + // 更新消息内容,添加视频URL + currentState.updateMessageWithVideo( + messageId, + message.content.url, + ); + } + } + }); + }); + + return unsubscribe; + }, + clearAllData: () => { set({ currentContract: null, currentMessages: [], messagesLoading: false, + pendingVideoRequests: {}, }); }, }), From ef853d273692757564007c1dcf2dfb1886516a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 16:06:12 +0800 Subject: [PATCH 058/146] =?UTF-8?q?feat(weChat):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=B9=B6=E4=BC=98=E5=8C=96=E5=8A=A0=E8=BD=BD=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除旧的待处理视频请求队列及相关方法 - 新增setVideoLoading和setVideoUrl方法简化视频状态管理 - 优化ChatWindow组件中的视频加载状态检测和滚动行为 - 添加CmdDownloadVideoResult消息处理器自动更新视频URL --- .../pc/ckbox/components/ChatWindow/index.tsx | 113 +++++++++-------- .../src/store/module/weChat/weChat.data.ts | 14 +-- Cunkebao/src/store/module/weChat/weChat.ts | 116 +++++------------- .../src/store/module/websocket/msgManage.ts | 6 + 4 files changed, 96 insertions(+), 153 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index c4473c64..bb9edc97 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -38,9 +38,37 @@ const ChatWindow: React.FC = ({ }) => { const messagesEndRef = useRef(null); const currentMessages = useWeChatStore(state => state.currentMessages); + const prevMessagesRef = useRef(currentMessages); useEffect(() => { - // 只有在非视频加载操作时才自动滚动到底部 + const prevMessages = prevMessagesRef.current; + + // 检查是否有视频状态变化(从加载中变为已完成) + const hasVideoStateChange = currentMessages.some((msg, index) => { + const prevMsg = prevMessages[index]; + if (!prevMsg) return false; + + try { + const currentContent = + typeof msg.content === "string" + ? JSON.parse(msg.content) + : msg.content; + const prevContent = + typeof prevMsg.content === "string" + ? JSON.parse(prevMsg.content) + : prevMsg.content; + + // 检查是否从加载中变为已完成(有videoUrl) + return ( + prevContent.isLoading === true && + currentContent.isLoading === false && + currentContent.videoUrl + ); + } catch (e) { + return false; + } + }); + // 检查是否有视频正在加载中 const hasLoadingVideo = currentMessages.some(msg => { try { @@ -54,16 +82,14 @@ const ChatWindow: React.FC = ({ } }); - if (!hasLoadingVideo) { + // 只有在没有视频加载且没有视频状态变化时才自动滚动到底部 + if (!hasLoadingVideo && !hasVideoStateChange) { scrollToBottom(); } - }, [currentMessages]); - // 初始化WebSocket监听 - useEffect(() => { - const unsubscribe = useWeChatStore.getState().initWebSocketListener(); - return unsubscribe; - }, []); + // 更新上一次的消息状态 + prevMessagesRef.current = currentMessages; + }, [currentMessages]); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); @@ -71,26 +97,19 @@ const ChatWindow: React.FC = ({ // 处理视频播放请求,发送socket请求获取真实视频地址 const handleVideoPlayRequest = (tencentUrl: string, messageId: number) => { - // 生成请求ID (使用当前时间戳作为唯一标识) - const requestSeq = `${+new Date()}`; - console.log("发送视频下载请求:", { messageId, requestSeq }); + console.log("发送视频下载请求:", { messageId, tencentUrl }); + + // 先设置加载状态 + useWeChatStore.getState().setVideoLoading(messageId, true); // 构建socket请求数据 useWebSocketStore.getState().sendCommand("CmdDownloadVideo", { chatroomMessageId: contract.chatroomId ? messageId : 0, friendMessageId: contract.chatroomId ? 0 : messageId, - seq: requestSeq, // 使用唯一的请求ID + seq: `${+new Date()}`, // 使用时间戳作为请求序列号 tencentUrl: tencentUrl, wechatAccountId: contract.wechatAccountId, }); - - // 将消息ID添加到待处理队列 - useWeChatStore - .getState() - .addPendingVideoRequest(messageId.toString(), messageId.toString()); - - // 更新消息状态为加载中 - useWeChatStore.getState().setMessageLoading(messageId.toString(), true); }; // 解析消息内容,判断消息类型并返回对应的渲染内容 @@ -121,21 +140,22 @@ const ChatWindow: React.FC = ({ content.trim().endsWith("}") ) { const videoData = JSON.parse(content); - // 处理用户提供的JSON格式 {"previewImage":"https://...", "tencentUrl":"..."} + // 处理视频消息格式 {"previewImage":"https://...", "tencentUrl":"...", "videoUrl":"...", "isLoading":true} if (videoData.previewImage && videoData.tencentUrl) { // 提取预览图URL,去掉可能的引号 const previewImageUrl = videoData.previewImage.replace(/[`"']/g, ""); - // 创建点击处理函数,调用handleVideoPlayRequest发送socket请求获取真实视频地址 + // 创建点击处理函数 const handlePlayClick = (e: React.MouseEvent) => { e.stopPropagation(); - // 调用处理函数,传入tencentUrl和消息ID - handleVideoPlayRequest(videoData.tencentUrl, msg.id); + // 如果没有视频URL且不在加载中,则发起下载请求 + if (!videoData.videoUrl && !videoData.isLoading) { + handleVideoPlayRequest(videoData.tencentUrl, msg.id); + } }; - // 检查是否已下载视频URL + // 如果已有视频URL,显示视频播放器 if (videoData.videoUrl) { - // 已获取到视频URL,显示视频播放器 return (
@@ -158,30 +178,7 @@ const ChatWindow: React.FC = ({ ); } - // 检查是否处于加载状态 - if (videoData.isLoading) { - return ( -
-
- 视频预览 -
-
-
-
-
- ); - } - - // 默认显示预览图和播放按钮 + // 显示预览图,根据加载状态显示不同的图标 return (
@@ -189,12 +186,20 @@ const ChatWindow: React.FC = ({ src={previewImageUrl} alt="视频预览" className={styles.videoThumbnail} - style={{ maxWidth: "100%", borderRadius: "8px" }} + style={{ + maxWidth: "100%", + borderRadius: "8px", + opacity: videoData.isLoading ? "0.7" : "1", + }} />
- + {videoData.isLoading ? ( +
+ ) : ( + + )}
diff --git a/Cunkebao/src/store/module/weChat/weChat.data.ts b/Cunkebao/src/store/module/weChat/weChat.data.ts index 955ce031..1f938d32 100644 --- a/Cunkebao/src/store/module/weChat/weChat.data.ts +++ b/Cunkebao/src/store/module/weChat/weChat.data.ts @@ -10,19 +10,11 @@ export interface WeChatState { // 消息加载状态 messagesLoading: boolean; - // 待处理的视频请求队列 - pendingVideoRequests: Record; - // Actions setCurrentContact: (contract: ContractData | weChatGroup) => void; loadChatMessages: (contact: ContractData | weChatGroup) => Promise; - // 视频请求相关方法 - addPendingVideoRequest: (messageId: string, requestId: string) => void; - removePendingVideoRequest: (messageId: string) => void; - updateMessageWithVideo: (messageId: string, videoUrl: string) => void; - setMessageLoading: (messageId: string, isLoading: boolean) => void; - - // WebSocket监听初始化 - initWebSocketListener: () => void; + // 视频消息处理方法 + setVideoLoading: (messageId: number, isLoading: boolean) => void; + setVideoUrl: (messageId: number, videoUrl: string) => void; } diff --git a/Cunkebao/src/store/module/weChat/weChat.ts b/Cunkebao/src/store/module/weChat/weChat.ts index 633969b8..30d02e14 100644 --- a/Cunkebao/src/store/module/weChat/weChat.ts +++ b/Cunkebao/src/store/module/weChat/weChat.ts @@ -5,10 +5,7 @@ import { WeChatState } from "./weChat.data"; import { clearUnreadCount, updateConfig } from "@/pages/pc/ckbox/api"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { addChatSession } from "@/store/module/ckchat/ckchat"; -import { - useWebSocketStore, - WebSocketMessage, -} from "@/store/module/websocket/websocket"; + export const useWeChatStore = create()( persist( (set, get) => ({ @@ -16,7 +13,6 @@ export const useWeChatStore = create()( currentContract: null, currentMessages: [], messagesLoading: false, - pendingVideoRequests: {}, // Actions setCurrentContact: (contract: ContractData | weChatGroup) => { @@ -85,43 +81,22 @@ export const useWeChatStore = create()( getCurrentMessages: () => get().currentMessages, getMessagesLoading: () => get().messagesLoading, - // 视频请求相关方法 - addPendingVideoRequest: (messageId: string, requestId: string) => { - set(state => ({ - pendingVideoRequests: { - ...state.pendingVideoRequests, - [messageId]: requestId, - }, - })); - }, - - removePendingVideoRequest: (messageId: string) => { - set(state => { - const newRequests = { ...state.pendingVideoRequests }; - delete newRequests[messageId]; - return { pendingVideoRequests: newRequests }; - }); - }, - - updateMessageWithVideo: (messageId: string, videoUrl: string) => { + // 视频消息处理方法 + setVideoLoading: (messageId: number, isLoading: boolean) => { set(state => ({ currentMessages: state.currentMessages.map(msg => { - if (msg.id === Number(messageId)) { + if (msg.id === messageId) { try { - const msgContent = - typeof msg.content === "string" - ? JSON.parse(msg.content) - : msg.content; + const content = JSON.parse(msg.content); + // 更新加载状态 + const updatedContent = { ...content, isLoading }; return { ...msg, - content: JSON.stringify({ - ...msgContent, - videoUrl: videoUrl, - isLoading: false, - }), + content: JSON.stringify(updatedContent), }; } catch (e) { - console.error("解析消息内容失败:", e); + console.error("更新视频加载状态失败:", e); + return msg; } } return msg; @@ -129,77 +104,42 @@ export const useWeChatStore = create()( })); }, - setMessageLoadingStatus: (messageId: string, isLoading: boolean) => { + setVideoUrl: (messageId: number, videoUrl: string) => { set(state => ({ currentMessages: state.currentMessages.map(msg => { - if (msg.id === Number(messageId)) { + if (msg.id === messageId) { try { - const originalContent = msg.content; + const content = JSON.parse(msg.content); + // 检查视频是否已经下载完毕,避免重复更新 + if (content.videoUrl && content.videoUrl === videoUrl) { + console.log("视频已下载,跳过重复更新:", messageId); + return msg; + } + + // 设置视频URL并清除加载状态 + const updatedContent = { + ...content, + videoUrl, + isLoading: false, + }; return { ...msg, - content: JSON.stringify({ - ...JSON.parse(originalContent), - isLoading: isLoading, - }), + content: JSON.stringify(updatedContent), }; } catch (e) { - console.error("解析消息内容失败:", e); + console.error("更新视频URL失败:", e); + return msg; } } return msg; }), })); }, - - // WebSocket监听初始化 - initWebSocketListener: () => { - const unsubscribe = useWebSocketStore.subscribe(state => { - const messages = state.messages as WebSocketMessage[]; - const currentState = get(); - - // 只有当有待处理的视频请求时才处理 - if (Object.keys(currentState.pendingVideoRequests).length === 0) { - return; - } - - messages.forEach(message => { - if (message?.content?.cmdType === "CmdDownloadVideoResult") { - console.log("收到视频下载响应:", message.content); - - // 检查是否是我们正在等待的视频响应 - const messageId = Object.keys( - currentState.pendingVideoRequests, - ).find( - id => - currentState.pendingVideoRequests[id] === - message.content.friendMessageId, - ); - - if (messageId) { - console.log("找到对应的消息ID:", messageId); - - // 从待处理队列中移除 - currentState.removePendingVideoRequest(messageId); - - // 更新消息内容,添加视频URL - currentState.updateMessageWithVideo( - messageId, - message.content.url, - ); - } - } - }); - }); - - return unsubscribe; - }, - clearAllData: () => { set({ currentContract: null, currentMessages: [], messagesLoading: false, - pendingVideoRequests: {}, }); }, }), diff --git a/Cunkebao/src/store/module/websocket/msgManage.ts b/Cunkebao/src/store/module/websocket/msgManage.ts index 484058af..4efc7a56 100644 --- a/Cunkebao/src/store/module/websocket/msgManage.ts +++ b/Cunkebao/src/store/module/websocket/msgManage.ts @@ -3,6 +3,8 @@ import { deepCopy } from "@/utils/common"; import { WebSocketMessage } from "./websocket"; import { getkfUserList, asyncKfUserList } from "@/store/module/ckchat/ckchat"; import { Messages } from "./msg.data"; + +import { useWeChatStore } from "@/store/module/weChat/weChat"; // 消息处理器类型定义 type MessageHandler = (message: WebSocketMessage) => void; @@ -52,6 +54,10 @@ const messageHandlers: Record = { } }, + CmdDownloadVideoResult: message => { + // 在这里添加具体的处理逻辑 + useWeChatStore.getState().setVideoUrl(message.friendMessageId, message.url); + }, // 可以继续添加更多处理器... }; From a3ba45626d4b6c92556970f277f9632f748a6a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 16:33:52 +0800 Subject: [PATCH 059/146] =?UTF-8?q?feat(=E5=BE=AE=E4=BF=A1/websocket):=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=B9=B6=E4=BC=98=E5=8C=96websocket=E8=BF=9E=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在WeChatState接口中添加addMessage方法用于接收新消息 - 在websocket消息处理器中调用addMessage存储接收到的消息 - 优化websocket连接逻辑,断开时自动尝试重连 - 改进聊天窗口视频状态检测逻辑,减少不必要的滚动 --- .../pc/ckbox/components/ChatWindow/index.tsx | 42 +++++++++---------- .../src/store/module/weChat/weChat.data.ts | 1 + .../src/store/module/websocket/msgManage.ts | 6 ++- .../src/store/module/websocket/websocket.ts | 17 +++++++- 4 files changed, 40 insertions(+), 26 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index bb9edc97..72c88cb7 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -43,10 +43,10 @@ const ChatWindow: React.FC = ({ useEffect(() => { const prevMessages = prevMessagesRef.current; - // 检查是否有视频状态变化(从加载中变为已完成) + // 检查是否有视频状态变化(从加载中变为已完成或开始加载) const hasVideoStateChange = currentMessages.some((msg, index) => { const prevMsg = prevMessages[index]; - if (!prevMsg) return false; + if (!prevMsg || prevMsg.id !== msg.id) return false; try { const currentContent = @@ -58,37 +58,33 @@ const ChatWindow: React.FC = ({ ? JSON.parse(prevMsg.content) : prevMsg.content; - // 检查是否从加载中变为已完成(有videoUrl) - return ( - prevContent.isLoading === true && - currentContent.isLoading === false && - currentContent.videoUrl - ); + // 检查视频状态是否发生变化(开始加载、完成加载、获得URL) + const currentHasVideo = + currentContent.previewImage && currentContent.tencentUrl; + const prevHasVideo = prevContent.previewImage && prevContent.tencentUrl; + + if (currentHasVideo && prevHasVideo) { + // 检查加载状态变化或视频URL变化 + return ( + currentContent.isLoading !== prevContent.isLoading || + currentContent.videoUrl !== prevContent.videoUrl + ); + } + + return false; } catch (e) { return false; } }); - // 检查是否有视频正在加载中 - const hasLoadingVideo = currentMessages.some(msg => { - try { - const content = - typeof msg.content === "string" - ? JSON.parse(msg.content) - : msg.content; - return content.isLoading === true; - } catch (e) { - return false; - } - }); - - // 只有在没有视频加载且没有视频状态变化时才自动滚动到底部 - if (!hasLoadingVideo && !hasVideoStateChange) { + // 只有在没有视频状态变化时才自动滚动到底部 + if (!hasVideoStateChange) { scrollToBottom(); } // 更新上一次的消息状态 prevMessagesRef.current = currentMessages; + console.log("视频状态变化:", hasVideoStateChange, currentMessages); }, [currentMessages]); const scrollToBottom = () => { diff --git a/Cunkebao/src/store/module/weChat/weChat.data.ts b/Cunkebao/src/store/module/weChat/weChat.data.ts index 1f938d32..dfd2e103 100644 --- a/Cunkebao/src/store/module/weChat/weChat.data.ts +++ b/Cunkebao/src/store/module/weChat/weChat.data.ts @@ -17,4 +17,5 @@ export interface WeChatState { // 视频消息处理方法 setVideoLoading: (messageId: number, isLoading: boolean) => void; setVideoUrl: (messageId: number, videoUrl: string) => void; + addMessage: (message: ChatRecord) => void; } diff --git a/Cunkebao/src/store/module/websocket/msgManage.ts b/Cunkebao/src/store/module/websocket/msgManage.ts index 4efc7a56..66610f93 100644 --- a/Cunkebao/src/store/module/websocket/msgManage.ts +++ b/Cunkebao/src/store/module/websocket/msgManage.ts @@ -7,7 +7,8 @@ import { Messages } from "./msg.data"; import { useWeChatStore } from "@/store/module/weChat/weChat"; // 消息处理器类型定义 type MessageHandler = (message: WebSocketMessage) => void; - +const setVideoUrl = useWeChatStore.getState().setVideoUrl; +const addMessage = useWeChatStore.getState().addMessage; // 消息处理器映射 const messageHandlers: Record = { // 微信账号存活状态响应 @@ -35,6 +36,7 @@ const messageHandlers: Record = { //收到消息 CmdNewMessage: (message: Messages) => { console.log("收到消息", message.friendMessage); + addMessage(message.friendMessage); // 在这里添加具体的处理逻辑 }, @@ -56,7 +58,7 @@ const messageHandlers: Record = { CmdDownloadVideoResult: message => { // 在这里添加具体的处理逻辑 - useWeChatStore.getState().setVideoUrl(message.friendMessageId, message.url); + setVideoUrl(message.friendMessageId, message.url); }, // 可以继续添加更多处理器... }; diff --git a/Cunkebao/src/store/module/websocket/websocket.ts b/Cunkebao/src/store/module/websocket/websocket.ts index 485931c6..d67e2a06 100644 --- a/Cunkebao/src/store/module/websocket/websocket.ts +++ b/Cunkebao/src/store/module/websocket/websocket.ts @@ -231,7 +231,16 @@ export const useWebSocketStore = createPersistStore( currentState.status !== WebSocketStatus.CONNECTED || !currentState.ws ) { - Toast.show({ content: "WebSocket未连接", position: "top" }); + Toast.show({ + content: "WebSocket未连接,正在重新连接...", + position: "top", + }); + + // 重置连接状态并发起重新连接 + set({ status: WebSocketStatus.DISCONNECTED }); + if (currentState.config) { + currentState.connect(currentState.config); + } return; } @@ -247,6 +256,12 @@ export const useWebSocketStore = createPersistStore( } catch (error) { // console.error("命令发送失败:", error); Toast.show({ content: "命令发送失败", position: "top" }); + + // 发送失败时也尝试重新连接 + set({ status: WebSocketStatus.DISCONNECTED }); + if (currentState.config) { + currentState.connect(currentState.config); + } } }, From c27642bc1234d5c0a447b67476c7057f0c910bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 16:36:10 +0800 Subject: [PATCH 060/146] =?UTF-8?q?feat(websocket):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=B8=85=E7=A9=BA=E8=BF=9E=E6=8E=A5=E7=8A=B6=E6=80=81=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E7=94=A8=E4=BA=8E=E9=80=80=E5=87=BA=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增clearConnectionState方法,用于在用户退出登录时清空WebSocket连接状态。该方法会关闭现有连接、停止所有定时器并重置所有相关状态变量,确保用户登出后不会保留之前的连接信息。 --- .../src/store/module/websocket/websocket.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Cunkebao/src/store/module/websocket/websocket.ts b/Cunkebao/src/store/module/websocket/websocket.ts index d67e2a06..46f9ed18 100644 --- a/Cunkebao/src/store/module/websocket/websocket.ts +++ b/Cunkebao/src/store/module/websocket/websocket.ts @@ -61,6 +61,7 @@ interface WebSocketState { clearMessages: () => void; markAsRead: () => void; reconnect: () => void; + clearConnectionState: () => void; // 清空连接状态 // 内部方法 _handleOpen: () => void; @@ -289,6 +290,34 @@ export const useWebSocketStore = createPersistStore( } }, + // 清空连接状态(用于退出登录时) + clearConnectionState: () => { + const currentState = get(); + + // 断开现有连接 + if (currentState.ws) { + currentState.ws.close(); + } + + // 停止所有定时器 + currentState._stopReconnectTimer(); + currentState._stopAliveStatusTimer(); + + // 重置所有状态 + set({ + status: WebSocketStatus.DISCONNECTED, + ws: null, + config: null, + messages: [], + unreadCount: 0, + reconnectAttempts: 0, + reconnectTimer: null, + aliveStatusTimer: null, + }); + + // console.log("WebSocket连接状态已清空"); + }, + // 内部方法:处理连接打开 _handleOpen: () => { const currentState = get(); From 9cdfe552e0b051411f7e1d4a7a6a78470830e007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 16:49:45 +0800 Subject: [PATCH 061/146] =?UTF-8?q?fix:=20=E7=A7=BB=E9=99=A4currentContrac?= =?UTF-8?q?t=E6=8C=81=E4=B9=85=E5=8C=96=E5=B9=B6=E6=B8=85=E7=90=86?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E6=97=A5=E5=BF=97=E5=92=8CWebSocket=E7=8A=B6?= =?UTF-8?q?=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除weChat模块中currentContract的持久化配置,避免登录和刷新时数据残留 清理ChatWindow组件中的调试日志 在登录时清空WebSocket连接状态 --- Cunkebao/src/pages/login/Login.tsx | 3 +++ Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx | 1 - Cunkebao/src/store/module/weChat/weChat.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cunkebao/src/pages/login/Login.tsx b/Cunkebao/src/pages/login/Login.tsx index d375422a..972710b2 100644 --- a/Cunkebao/src/pages/login/Login.tsx +++ b/Cunkebao/src/pages/login/Login.tsx @@ -7,6 +7,7 @@ import { UserOutline, } from "antd-mobile-icons"; import { useUserStore } from "@/store/module/user"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api"; import style from "./login.module.scss"; @@ -75,6 +76,8 @@ const Login: React.FC = () => { response.then(res => { const { member, kefuData, deviceTotal } = res; + // 清空WebSocket连接状态 + useWebSocketStore.getState().clearConnectionState(); login(res.token, member, deviceTotal); const { self, token } = kefuData; login2(token.access_token); diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index 72c88cb7..edff2cd2 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -84,7 +84,6 @@ const ChatWindow: React.FC = ({ // 更新上一次的消息状态 prevMessagesRef.current = currentMessages; - console.log("视频状态变化:", hasVideoStateChange, currentMessages); }, [currentMessages]); const scrollToBottom = () => { diff --git a/Cunkebao/src/store/module/weChat/weChat.ts b/Cunkebao/src/store/module/weChat/weChat.ts index 30d02e14..fd547121 100644 --- a/Cunkebao/src/store/module/weChat/weChat.ts +++ b/Cunkebao/src/store/module/weChat/weChat.ts @@ -146,7 +146,7 @@ export const useWeChatStore = create()( { name: "wechat-storage", partialize: state => ({ - currentContract: state.currentContract, + // currentContract 不做持久化,登录和页面刷新时直接清空 }), }, ), From 0bddc4dc5b7968ba3cb168e91673b2e655df2cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 17:05:53 +0800 Subject: [PATCH 062/146] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=8F=91=E9=80=81=E5=90=8E=E8=BE=93=E5=85=A5=E6=A1=86?= =?UTF-8?q?=E6=9C=AA=E6=B8=85=E7=A9=BA=E5=8F=8A=E8=87=AA=E5=8A=A8=E7=82=B9?= =?UTF-8?q?=E8=B5=9E=E7=8A=B6=E6=80=81=E5=88=87=E6=8D=A2=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复消息发送后输入框未清空的问题,优化自动点赞任务状态切换逻辑 更新消息处理逻辑,修正构建文件引用路径 --- Cunkebao/dist/.vite/manifest.json | 18 +++++++++--------- Cunkebao/dist/index.html | 8 ++++---- .../mobile/workspace/auto-like/list/api.ts | 4 ++-- .../mobile/workspace/auto-like/list/index.tsx | 3 +-- .../components/MessageEnter/index.tsx | 1 + .../src/store/module/websocket/msgManage.ts | 7 ++++++- 6 files changed, 23 insertions(+), 18 deletions(-) diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json index cc80cbd8..90e573c1 100644 --- a/Cunkebao/dist/.vite/manifest.json +++ b/Cunkebao/dist/.vite/manifest.json @@ -1,14 +1,14 @@ { - "_charts-Cbn6yFil.js": { - "file": "assets/charts-Cbn6yFil.js", + "_charts-BET_YNJb.js": { + "file": "assets/charts-BET_YNJb.js", "name": "charts", "imports": [ - "_ui-Cytu8vuP.js", + "_ui-BSfOMVFg.js", "_vendor-2vc8h_ct.js" ] }, - "_ui-Cytu8vuP.js": { - "file": "assets/ui-Cytu8vuP.js", + "_ui-BSfOMVFg.js": { + "file": "assets/ui-BSfOMVFg.js", "name": "ui", "imports": [ "_vendor-2vc8h_ct.js" @@ -33,18 +33,18 @@ "name": "vendor" }, "index.html": { - "file": "assets/index-QManA_e5.js", + "file": "assets/index-DX2o9_TA.js", "name": "index", "src": "index.html", "isEntry": true, "imports": [ "_vendor-2vc8h_ct.js", "_utils-6WF66_dS.js", - "_ui-Cytu8vuP.js", - "_charts-Cbn6yFil.js" + "_ui-BSfOMVFg.js", + "_charts-BET_YNJb.js" ], "css": [ - "assets/index-BkBrwKGe.css" + "assets/index-DwDrBOQB.css" ] } } \ No newline at end of file diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html index 93421b47..d9e4faec 100644 --- a/Cunkebao/dist/index.html +++ b/Cunkebao/dist/index.html @@ -11,13 +11,13 @@ - + - - + + - +
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-like/list/api.ts b/Cunkebao/src/pages/mobile/workspace/auto-like/list/api.ts index 615cb7ff..ddc48d91 100644 --- a/Cunkebao/src/pages/mobile/workspace/auto-like/list/api.ts +++ b/Cunkebao/src/pages/mobile/workspace/auto-like/list/api.ts @@ -5,7 +5,7 @@ import { UpdateLikeTaskData, LikeRecord, PaginatedResponse, -} from "@/pages/workspace/auto-like/record/data"; +} from "@/pages/mobile/workspace/auto-like/record/data"; // 获取自动点赞任务列表 export function fetchAutoLikeTasks( @@ -36,7 +36,7 @@ export function deleteAutoLikeTask(id: string): Promise { // 切换任务状态 export function toggleAutoLikeTask(data): Promise { - return request("/v1/workbench/update-status", { ...data, type: 1 }, "POST"); + return request("/v1/workbench/update-status", { ...data }, "POST"); } // 复制自动点赞任务 diff --git a/Cunkebao/src/pages/mobile/workspace/auto-like/list/index.tsx b/Cunkebao/src/pages/mobile/workspace/auto-like/list/index.tsx index 073603ee..a967f725 100644 --- a/Cunkebao/src/pages/mobile/workspace/auto-like/list/index.tsx +++ b/Cunkebao/src/pages/mobile/workspace/auto-like/list/index.tsx @@ -201,8 +201,7 @@ const AutoLike: React.FC = () => { // 切换任务状态 const toggleTaskStatus = async (id: string, status: number) => { try { - const newStatus = status === 1 ? "2" : "1"; - await toggleAutoLikeTask(id, newStatus); + await toggleAutoLikeTask({ id }); Toast.show({ content: status === 1 ? "已暂停" : "已启动", position: "top", diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx index edd49db9..a98efaf1 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx @@ -41,6 +41,7 @@ const MessageEnter: React.FC = ({ contract }) => { content: inputValue, }; sendCommand("CmdSendMessage", params); + setInputValue(""); // try { // onSendMessage(inputValue); // setInputValue(""); diff --git a/Cunkebao/src/store/module/websocket/msgManage.ts b/Cunkebao/src/store/module/websocket/msgManage.ts index 66610f93..3940a83b 100644 --- a/Cunkebao/src/store/module/websocket/msgManage.ts +++ b/Cunkebao/src/store/module/websocket/msgManage.ts @@ -26,17 +26,22 @@ const messageHandlers: Record = { // 发送消息响应 CmdSendMessageResp: message => { console.log("发送消息响应", message); + addMessage(message.friendMessage); + // 在这里添加具体的处理逻辑 + }, + CmdSendMessageResult: message => { + console.log("发送消息结果", message); // 在这里添加具体的处理逻辑 }, // 接收消息响应 CmdReceiveMessageResp: message => { console.log("接收消息响应", message); + addMessage(message.friendMessage); // 在这里添加具体的处理逻辑 }, //收到消息 CmdNewMessage: (message: Messages) => { console.log("收到消息", message.friendMessage); - addMessage(message.friendMessage); // 在这里添加具体的处理逻辑 }, From ff96312d53587d909f93cc77fe3958728ac80da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 17:18:19 +0800 Subject: [PATCH 063/146] =?UTF-8?q?feat(websocket):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=E5=B9=B6?= =?UTF-8?q?=E6=B8=85=E7=90=86=E6=97=A0=E7=94=A8=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在websocket消息处理器中添加addMessage调用以处理新消息 移除ChatWindow组件中无用的样式和注释代码 --- .../pc/ckbox/components/ChatWindow/ChatWindow.module.scss | 5 ----- .../components/ChatWindow/components/MessageEnter/index.tsx | 6 ------ Cunkebao/src/store/module/websocket/msgManage.ts | 1 + 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss index 355cb2a4..917988e3 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss @@ -128,9 +128,6 @@ } } - - - // 右侧个人资料卡片 .profileSider { background: #fff; @@ -629,8 +626,6 @@ } } - - .messageItem { .messageContent { max-width: 85%; diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx index a98efaf1..e226b02a 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx @@ -42,12 +42,6 @@ const MessageEnter: React.FC = ({ contract }) => { }; sendCommand("CmdSendMessage", params); setInputValue(""); - // try { - // onSendMessage(inputValue); - // setInputValue(""); - // } catch (error) { - // console.error("发送失败", error); - // } }; const handleKeyPress = (e: React.KeyboardEvent) => { diff --git a/Cunkebao/src/store/module/websocket/msgManage.ts b/Cunkebao/src/store/module/websocket/msgManage.ts index 3940a83b..3aca7d7a 100644 --- a/Cunkebao/src/store/module/websocket/msgManage.ts +++ b/Cunkebao/src/store/module/websocket/msgManage.ts @@ -43,6 +43,7 @@ const messageHandlers: Record = { CmdNewMessage: (message: Messages) => { console.log("收到消息", message.friendMessage); // 在这里添加具体的处理逻辑 + addMessage(message.friendMessage); }, // 登录响应 From 533c16be3f3314d1aa99f604df1294625a055483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 17:47:43 +0800 Subject: [PATCH 064/146] =?UTF-8?q?feat(weChat):=20=E6=B7=BB=E5=8A=A0recei?= =?UTF-8?q?vedMsg=E6=96=B9=E6=B3=95=E5=A4=84=E7=90=86=E6=8E=A5=E6=94=B6?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增receivedMsg方法用于处理微信消息接收逻辑,区分当前会话消息和未读消息更新 优化updateChatSession实现,使用深拷贝避免直接修改状态 调整ckchat.data.ts中方法顺序,保持代码整洁 --- .../src/store/module/ckchat/ckchat.data.ts | 4 +-- Cunkebao/src/store/module/ckchat/ckchat.ts | 20 ++++--------- .../src/store/module/weChat/weChat.data.ts | 1 + Cunkebao/src/store/module/weChat/weChat.ts | 29 ++++++++++++++++++- .../src/store/module/websocket/msgManage.ts | 5 ++-- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/Cunkebao/src/store/module/ckchat/ckchat.data.ts b/Cunkebao/src/store/module/ckchat/ckchat.data.ts index f9780ab6..af7f8743 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.data.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.data.ts @@ -52,13 +52,13 @@ export interface CkChatState { asyncKfUserList: (data: KfUserListData[]) => void; getKfUserInfo: (wechatAccountId: number) => KfUserListData | undefined; asyncContractList: (data: ContractData[]) => void; + getChatSessions: () => any[]; asyncChatSessions: (data: any[]) => void; + updateChatSession: (session: ContractData | weChatGroup) => void; deleteCtrlUser: (userId: number) => void; updateCtrlUser: (user: KfUserListData) => void; clearkfUserList: () => void; - getChatSessions: () => any[]; addChatSession: (session: any) => void; - updateChatSession: (session: any) => void; deleteChatSession: (sessionId: string) => void; setUserInfo: (userInfo: CkUserInfo) => void; clearUserInfo: () => void; diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts index 054c4bd5..3bdf173a 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.ts @@ -9,6 +9,7 @@ import { } from "@/pages/pc/ckbox/data"; import { kfUserService, weChatGroupService, contractService } from "@/utils/db"; import { createContractList } from "@/store/module/ckchat/api"; +import { deepCopy } from "../../../utils/common"; export const useCkChatStore = createPersistStore( set => ({ @@ -384,20 +385,11 @@ export const useCkChatStore = createPersistStore( }, // 更新聊天会话 updateChatSession: (session: ContractData | weChatGroup) => { - set(state => ({ - chatSessions: state.chatSessions.map(item => - item.id === session.id ? session : item, - ), - })); - // 清除getChatSessions缓存 - const state = useCkChatStore.getState(); - if ( - state.getChatSessions && - typeof state.getChatSessions === "function" - ) { - // 触发缓存重新计算 - state.getChatSessions(); - } + const state = deepCopy(useCkChatStore.getState()); + const newSession = state.chatSessions.map(item => + item.id === session.id ? session : item, + ); + set({ chatSessions: newSession }); }, // 删除聊天会话 deleteChatSession: (sessionId: string) => { diff --git a/Cunkebao/src/store/module/weChat/weChat.data.ts b/Cunkebao/src/store/module/weChat/weChat.data.ts index dfd2e103..78dc6508 100644 --- a/Cunkebao/src/store/module/weChat/weChat.data.ts +++ b/Cunkebao/src/store/module/weChat/weChat.data.ts @@ -18,4 +18,5 @@ export interface WeChatState { setVideoLoading: (messageId: number, isLoading: boolean) => void; setVideoUrl: (messageId: number, videoUrl: string) => void; addMessage: (message: ChatRecord) => void; + receivedMsg: (message: ChatRecord) => void; } diff --git a/Cunkebao/src/store/module/weChat/weChat.ts b/Cunkebao/src/store/module/weChat/weChat.ts index fd547121..6280d68c 100644 --- a/Cunkebao/src/store/module/weChat/weChat.ts +++ b/Cunkebao/src/store/module/weChat/weChat.ts @@ -4,7 +4,11 @@ import { getChatMessages } from "@/pages/pc/ckbox/api"; import { WeChatState } from "./weChat.data"; import { clearUnreadCount, updateConfig } from "@/pages/pc/ckbox/api"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; -import { addChatSession } from "@/store/module/ckchat/ckchat"; +import { + addChatSession, + getChatSessions, + updateChatSession, +} from "@/store/module/ckchat/ckchat"; export const useWeChatStore = create()( persist( @@ -68,6 +72,29 @@ export const useWeChatStore = create()( })); }, + receivedMsg: message => { + const currentContract = useWeChatStore.getState().currentContract; + if ( + currentContract && + currentContract.wechatAccountId == message.wechatAccountId && + currentContract.id == message.wechatFriendId + ) { + set(state => ({ + currentMessages: [...state.currentMessages, message], + })); + } else { + //更新消息列表unread数值,根据接收的++1 这样 + const chatSessions = getChatSessions(); + const session = chatSessions.find( + item => item.id == message.wechatFriendId, + ); + session.unreadCount++; + console.log("新消息", session); + + updateChatSession(session); + } + }, + updateMessage: (messageId, updates) => { set(state => ({ currentMessages: state.currentMessages.map(msg => diff --git a/Cunkebao/src/store/module/websocket/msgManage.ts b/Cunkebao/src/store/module/websocket/msgManage.ts index 3aca7d7a..423fab57 100644 --- a/Cunkebao/src/store/module/websocket/msgManage.ts +++ b/Cunkebao/src/store/module/websocket/msgManage.ts @@ -9,6 +9,8 @@ import { useWeChatStore } from "@/store/module/weChat/weChat"; type MessageHandler = (message: WebSocketMessage) => void; const setVideoUrl = useWeChatStore.getState().setVideoUrl; const addMessage = useWeChatStore.getState().addMessage; +const receivedMsg = useWeChatStore.getState().receivedMsg; + // 消息处理器映射 const messageHandlers: Record = { // 微信账号存活状态响应 @@ -41,9 +43,8 @@ const messageHandlers: Record = { }, //收到消息 CmdNewMessage: (message: Messages) => { - console.log("收到消息", message.friendMessage); // 在这里添加具体的处理逻辑 - addMessage(message.friendMessage); + receivedMsg(message.friendMessage); }, // 登录响应 From 7e5617bd80162c6ee73ecf4e55379022ff4b2dac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 17:55:25 +0800 Subject: [PATCH 065/146] =?UTF-8?q?refactor(ckbox):=20=E7=A7=BB=E9=99=A4Si?= =?UTF-8?q?debarMenu=E7=9A=84currentContract=E5=B1=9E=E6=80=A7=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除不再使用的currentContract属性和相关props传递 - 将chatSessions状态管理移至MessageList组件内部 - 添加CmdFriendInfoChanged消息处理器 - 添加调试日志用于会话更新 --- .../components/SidebarMenu/MessageList/index.tsx | 12 ++++++------ .../pages/pc/ckbox/components/SidebarMenu/index.tsx | 10 ++-------- Cunkebao/src/pages/pc/ckbox/index.tsx | 2 +- Cunkebao/src/store/module/ckchat/ckchat.ts | 1 + Cunkebao/src/store/module/websocket/msgManage.ts | 5 +++++ 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx index 3507fc6d..dc4901e1 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx @@ -3,22 +3,22 @@ import { List, Avatar, Badge } from "antd"; import { UserOutlined, TeamOutlined } from "@ant-design/icons"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { useWeChatStore } from "@/store/module/weChat/weChat"; +import { useCkChatStore } from "@/store/module/ckchat/ckchat"; + import styles from "./MessageList.module.scss"; import { formatWechatTime } from "@/utils/common"; -interface MessageListProps { - chatSessions: ContractData[] | weChatGroup[]; -} +interface MessageListProps {} -const MessageList: React.FC = ({ chatSessions }) => { +const MessageList: React.FC = () => { const { setCurrentContact, currentContract } = useWeChatStore(); - + const chatSessions = useCkChatStore(state => state.getChatSessions()); const onContactClick = (session: ContractData | weChatGroup) => { setCurrentContact(session); }; return (
( = ({ - loading = false, - currentContract, -}) => { - const chatSessions = useCkChatStore(state => state.getChatSessions()); +const SidebarMenu: React.FC = ({ loading = false }) => { const searchKeyword = useCkChatStore(state => state.searchKeyword); const setSearchKeyword = useCkChatStore(state => state.setSearchKeyword); const clearSearchKeyword = useCkChatStore(state => state.clearSearchKeyword); @@ -128,7 +122,7 @@ const SidebarMenu: React.FC = ({ const renderContent = () => { switch (activeTab) { case "chats": - return ; + return ; case "contracts": return ; case "groups": diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx index 38c9cf84..0cbe80f4 100644 --- a/Cunkebao/src/pages/pc/ckbox/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/index.tsx @@ -64,7 +64,7 @@ const CkboxPage: React.FC = () => { {/* 左侧联系人边栏 */} - + {/* 主内容区 */} diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts index 3bdf173a..b83cd436 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.ts @@ -389,6 +389,7 @@ export const useCkChatStore = createPersistStore( const newSession = state.chatSessions.map(item => item.id === session.id ? session : item, ); + console.log("新数组", newSession); set({ chatSessions: newSession }); }, // 删除聊天会话 diff --git a/Cunkebao/src/store/module/websocket/msgManage.ts b/Cunkebao/src/store/module/websocket/msgManage.ts index 423fab57..3146e213 100644 --- a/Cunkebao/src/store/module/websocket/msgManage.ts +++ b/Cunkebao/src/store/module/websocket/msgManage.ts @@ -46,6 +46,10 @@ const messageHandlers: Record = { // 在这里添加具体的处理逻辑 receivedMsg(message.friendMessage); }, + CmdFriendInfoChanged: message => { + // console.log("好友信息变更", message); + // 在这里添加具体的处理逻辑 + }, // 登录响应 CmdSignInResp: message => { @@ -67,6 +71,7 @@ const messageHandlers: Record = { // 在这里添加具体的处理逻辑 setVideoUrl(message.friendMessageId, message.url); }, + // 可以继续添加更多处理器... }; From 30cafc5619d537dde29bb264a6c79c7d53866089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 18:04:39 +0800 Subject: [PATCH 066/146] =?UTF-8?q?refactor(weChat/ckchat):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BC=9A=E8=AF=9D=E6=9B=B4=E6=96=B0=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E5=B9=B6=E7=A7=BB=E9=99=A4=E5=86=97=E4=BD=99=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复未读消息计数时可能出现的类型问题 - 直接使用chatSessions状态而非getChatSessions方法 - 简化会话更新逻辑,移除不必要的缓存处理 - 使用更简洁的路径引用deepCopy工具 --- .../SidebarMenu/MessageList/index.tsx | 2 +- Cunkebao/src/store/module/ckchat/ckchat.ts | 31 ++++--------------- Cunkebao/src/store/module/weChat/weChat.ts | 4 +-- 3 files changed, 8 insertions(+), 29 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx index dc4901e1..6fc7d049 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx @@ -11,7 +11,7 @@ interface MessageListProps {} const MessageList: React.FC = () => { const { setCurrentContact, currentContract } = useWeChatStore(); - const chatSessions = useCkChatStore(state => state.getChatSessions()); + const chatSessions = useCkChatStore(state => state.chatSessions); const onContactClick = (session: ContractData | weChatGroup) => { setCurrentContact(session); }; diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts index b83cd436..686ee2f9 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.ts @@ -9,7 +9,7 @@ import { } from "@/pages/pc/ckbox/data"; import { kfUserService, weChatGroupService, contractService } from "@/utils/db"; import { createContractList } from "@/store/module/ckchat/api"; -import { deepCopy } from "../../../utils/common"; +import { deepCopy } from "@/utils/common"; export const useCkChatStore = createPersistStore( set => ({ @@ -373,39 +373,20 @@ export const useCkChatStore = createPersistStore( : [...state.chatSessions, session as ContractData | weChatGroup], }; }); - // 清除getChatSessions缓存 - const state = useCkChatStore.getState(); - if ( - state.getChatSessions && - typeof state.getChatSessions === "function" - ) { - // 触发缓存重新计算 - state.getChatSessions(); - } }, // 更新聊天会话 updateChatSession: (session: ContractData | weChatGroup) => { - const state = deepCopy(useCkChatStore.getState()); - const newSession = state.chatSessions.map(item => - item.id === session.id ? session : item, - ); - console.log("新数组", newSession); - set({ chatSessions: newSession }); + set(state => ({ + chatSessions: state.chatSessions.map(item => + item.id === session.id ? { ...item, ...session } : item, + ), + })); }, // 删除聊天会话 deleteChatSession: (sessionId: string) => { set(state => ({ chatSessions: state.chatSessions.filter(item => item.id !== sessionId), })); - // 清除getChatSessions缓存 - const state = useCkChatStore.getState(); - if ( - state.getChatSessions && - typeof state.getChatSessions === "function" - ) { - // 触发缓存重新计算 - state.getChatSessions(); - } }, // 设置用户信息 setUserInfo: (userInfo: CkUserInfo) => { diff --git a/Cunkebao/src/store/module/weChat/weChat.ts b/Cunkebao/src/store/module/weChat/weChat.ts index 6280d68c..182fa937 100644 --- a/Cunkebao/src/store/module/weChat/weChat.ts +++ b/Cunkebao/src/store/module/weChat/weChat.ts @@ -88,9 +88,7 @@ export const useWeChatStore = create()( const session = chatSessions.find( item => item.id == message.wechatFriendId, ); - session.unreadCount++; - console.log("新消息", session); - + session.unreadCount = Number(session.unreadCount) + 1; updateChatSession(session); } }, From 7bd7bd3564685e2a9f7f5ee509cfb6f368f3beac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 18:20:24 +0800 Subject: [PATCH 067/146] =?UTF-8?q?fix(weChat/ckchat):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=88=87=E6=8D=A2=E8=81=94=E7=B3=BB=E4=BA=BA=E6=97=B6?= =?UTF-8?q?=E6=9C=AA=E9=87=8D=E7=BD=AE=E6=9C=AA=E8=AF=BB=E8=AE=A1=E6=95=B0?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在切换联系人时,确保会话的未读计数被正确重置为0。将重置逻辑统一移至addChatSession方法中处理,避免遗漏。 --- Cunkebao/src/store/module/ckchat/ckchat.ts | 2 +- Cunkebao/src/store/module/weChat/weChat.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts index 686ee2f9..d0a16103 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.ts @@ -9,7 +9,6 @@ import { } from "@/pages/pc/ckbox/data"; import { kfUserService, weChatGroupService, contractService } from "@/utils/db"; import { createContractList } from "@/store/module/ckchat/api"; -import { deepCopy } from "@/utils/common"; export const useCkChatStore = createPersistStore( set => ({ @@ -363,6 +362,7 @@ export const useCkChatStore = createPersistStore( })(), // 添加聊天会话 addChatSession: (session: ContractData | weChatGroup) => { + session.unreadCount = 0; set(state => { // 检查是否已存在相同id的会话 const exists = state.chatSessions.some(item => item.id === session.id); diff --git a/Cunkebao/src/store/module/weChat/weChat.ts b/Cunkebao/src/store/module/weChat/weChat.ts index 182fa937..30d65ead 100644 --- a/Cunkebao/src/store/module/weChat/weChat.ts +++ b/Cunkebao/src/store/module/weChat/weChat.ts @@ -24,7 +24,6 @@ export const useWeChatStore = create()( // 切换联系人时清空当前消息,等待重新加载 set({ currentMessages: [] }); clearUnreadCount([contract.id]).then(() => { - contract.unreadCount = 0; addChatSession(contract); set({ currentContract: contract }); updateConfig({ From df0bbddbd5c6f9d071e64db9d6f97fb97f4e6106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 18:33:05 +0800 Subject: [PATCH 068/146] =?UTF-8?q?feat(weChat):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=81=94=E7=B3=BB=E4=BA=BA=E5=AD=98=E5=9C=A8=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=B9=B6=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在setCurrentContact方法中添加isExist参数,用于区分新增联系人和已存在联系人的处理逻辑 优化消息处理流程,确保未读消息计数正确更新 --- .../SidebarMenu/MessageList/index.tsx | 2 +- .../src/store/module/weChat/weChat.data.ts | 5 ++++- Cunkebao/src/store/module/weChat/weChat.ts | 21 +++++++++++++------ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx index 6fc7d049..dfaab7e4 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx @@ -13,7 +13,7 @@ const MessageList: React.FC = () => { const { setCurrentContact, currentContract } = useWeChatStore(); const chatSessions = useCkChatStore(state => state.chatSessions); const onContactClick = (session: ContractData | weChatGroup) => { - setCurrentContact(session); + setCurrentContact(session, true); }; return (
diff --git a/Cunkebao/src/store/module/weChat/weChat.data.ts b/Cunkebao/src/store/module/weChat/weChat.data.ts index 78dc6508..a63ff292 100644 --- a/Cunkebao/src/store/module/weChat/weChat.data.ts +++ b/Cunkebao/src/store/module/weChat/weChat.data.ts @@ -11,7 +11,10 @@ export interface WeChatState { messagesLoading: boolean; // Actions - setCurrentContact: (contract: ContractData | weChatGroup) => void; + setCurrentContact: ( + contract: ContractData | weChatGroup, + isExist?: boolean, + ) => void; loadChatMessages: (contact: ContractData | weChatGroup) => Promise; // 视频消息处理方法 diff --git a/Cunkebao/src/store/module/weChat/weChat.ts b/Cunkebao/src/store/module/weChat/weChat.ts index 30d65ead..914f5e71 100644 --- a/Cunkebao/src/store/module/weChat/weChat.ts +++ b/Cunkebao/src/store/module/weChat/weChat.ts @@ -6,8 +6,8 @@ import { clearUnreadCount, updateConfig } from "@/pages/pc/ckbox/api"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { addChatSession, - getChatSessions, updateChatSession, + useCkChatStore, } from "@/store/module/ckchat/ckchat"; export const useWeChatStore = create()( @@ -19,12 +19,19 @@ export const useWeChatStore = create()( messagesLoading: false, // Actions - setCurrentContact: (contract: ContractData | weChatGroup) => { + setCurrentContact: ( + contract: ContractData | weChatGroup, + isExist?: boolean, + ) => { const state = useWeChatStore.getState(); // 切换联系人时清空当前消息,等待重新加载 set({ currentMessages: [] }); clearUnreadCount([contract.id]).then(() => { - addChatSession(contract); + if (isExist) { + updateChatSession({ ...contract, unreadCount: 0 }); + } else { + addChatSession(contract); + } set({ currentContract: contract }); updateConfig({ id: contract.id, @@ -83,12 +90,14 @@ export const useWeChatStore = create()( })); } else { //更新消息列表unread数值,根据接收的++1 这样 - const chatSessions = getChatSessions(); + const chatSessions = useCkChatStore.getState().chatSessions; const session = chatSessions.find( item => item.id == message.wechatFriendId, ); - session.unreadCount = Number(session.unreadCount) + 1; - updateChatSession(session); + if (session) { + session.unreadCount = Number(session.unreadCount) + 1; + updateChatSession(session); + } } }, From 1f0ef3c64d0c39ff30d612af2a995ff205a6feb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 4 Sep 2025 10:47:57 +0800 Subject: [PATCH 069/146] =?UTF-8?q?refactor(chat):=20=E9=87=8D=E5=91=BD?= =?UTF-8?q?=E5=90=8DPerson=E7=BB=84=E4=BB=B6=E4=B8=BAProfileCard=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复微信聊天消息可能为undefined的问题 添加调试日志检查消息状态 确保wechatTime字段安全访问 --- .../components/{Person => ProfileCard}/Person.module.scss | 0 .../components/{Person => ProfileCard}/index.tsx | 2 +- .../src/pages/pc/ckbox/components/ChatWindow/index.tsx | 8 +++++--- Cunkebao/src/store/module/weChat/weChat.ts | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) rename Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/{Person => ProfileCard}/Person.module.scss (100%) rename Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/{Person => ProfileCard}/index.tsx (99%) diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/Person.module.scss b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/Person.module.scss similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/Person.module.scss rename to Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/Person.module.scss diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx similarity index 99% rename from Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx rename to Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx index c96eea65..ed60e655 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx @@ -111,7 +111,7 @@ const Person: React.FC = ({ conRemark: remarkValue, // 使用当前编辑的备注值 alias: contract.alias, wechatId: contract.wechatId, - avatar: contract.avatar, + avatar: contract.avatar || contract.chatroomAvatar, phone: contract.phone || "-", email: contract.email || "-", department: contract.department || "-", diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index edff2cd2..5470c535 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -20,7 +20,7 @@ import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import styles from "./ChatWindow.module.scss"; import { useWebSocketStore } from "@/store/module/websocket/websocket"; import { formatWechatTime } from "@/utils/common"; -import Person from "./components/Person"; +import ProfileCard from "./components/ProfileCard"; import MessageEnter from "./components/MessageEnter"; import { useWeChatStore } from "@/store/module/weChat/weChat"; const { Header, Content } = Layout; @@ -44,6 +44,8 @@ const ChatWindow: React.FC = ({ const prevMessages = prevMessagesRef.current; // 检查是否有视频状态变化(从加载中变为已完成或开始加载) + console.log("currentMessages", currentMessages); + const hasVideoStateChange = currentMessages.some((msg, index) => { const prevMsg = prevMessages[index]; if (!prevMsg || prevMsg.id !== msg.id) return false; @@ -525,7 +527,7 @@ const ChatWindow: React.FC = ({ // 用于分组消息并添加时间戳的辅助函数 const groupMessagesByTime = (messages: ChatRecord[]) => { return messages.map(msg => ({ - time: formatWechatTime(msg.wechatTime), + time: formatWechatTime(msg?.wechatTime), messages: [msg], })); }; @@ -643,7 +645,7 @@ const ChatWindow: React.FC = ({ {/* 右侧个人资料卡片 */} - ()( } const messages = await getChatMessages(params); - set({ currentMessages: messages }); + set({ currentMessages: messages || [] }); } catch (error) { console.error("获取聊天消息失败:", error); } finally { From 3c68a603af8b80166c5e194eabc3e280189d1a23 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Thu, 4 Sep 2025 10:49:22 +0800 Subject: [PATCH 070/146] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChatWindow/components/Person/index.tsx | 597 +++++++++++++++--- .../api/controller/UserController.php | 12 +- Server/application/cunkebao/config/route.php | 9 +- .../controller/KeFuLoginController.php | 78 +++ .../application/job/WorkbenchGroupPushJob.php | 127 ++-- 5 files changed, 661 insertions(+), 162 deletions(-) create mode 100644 Server/application/cunkebao/controller/KeFuLoginController.php diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx index c96eea65..3803956f 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx @@ -8,6 +8,7 @@ import { Card, Tag, message, + Modal, } from "antd"; import { PhoneOutlined, @@ -22,9 +23,12 @@ import { StarOutlined, EditOutlined, CheckOutlined, + PlusOutlined, + NotificationOutlined, } from "@ant-design/icons"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { useCkChatStore } from "@/store/module/ckchat/ckchat"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; import styles from "./Person.module.scss"; const { Sider } = Layout; @@ -47,10 +51,27 @@ const Person: React.FC = ({ contract.labels || [], ); const [allAvailableTags, setAllAvailableTags] = useState([]); + const [isAddingTag, setIsAddingTag] = useState(false); + const [newTagValue, setNewTagValue] = useState(""); + + // 判断是否为群聊 + const isGroup = 'chatroomId' in contract; + + // 群聊相关状态 + const [isEditingGroupName, setIsEditingGroupName] = useState(false); + const [groupNameValue, setGroupNameValue] = useState(contract.name || ''); + const [isEditingGroupNotice, setIsEditingGroupNotice] = useState(false); + const [groupNoticeValue, setGroupNoticeValue] = useState(contract.notice || ''); + const [isEditingSelfDisplayName, setIsEditingSelfDisplayName] = useState(false); + const [selfDisplayNameValue, setSelfDisplayNameValue] = useState(contract.selfDisplyName || ''); + const [isGroupNoticeModalVisible, setIsGroupNoticeModalVisible] = useState(false); + + // 构建联系人或群聊详细信息 const kfSelectedUser = useCkChatStore(state => state.getKfUserInfo(contract.wechatAccountId || 0), ); + const { sendCommand } = useWebSocketStore(); // 获取所有可用标签 useEffect(() => { @@ -68,22 +89,102 @@ const Person: React.FC = ({ fetchAvailableTags(); }, [kfSelectedUser, contract.labels]); - // 当contract变化时更新备注值和标签 + // 当contract变化时更新各种值 useEffect(() => { setRemarkValue(contract.conRemark || ""); setIsEditingRemark(false); setSelectedTags(contract.labels || []); - }, [contract.conRemark, contract.labels]); + + if (isGroup) { + setGroupNameValue(contract.name || ''); + setIsEditingGroupName(false); + setGroupNoticeValue(contract.notice || ''); + setIsEditingGroupNotice(false); + setSelfDisplayNameValue(contract.selfDisplyName || ''); + setIsEditingSelfDisplayName(false); + } + }, [contract.conRemark, contract.labels, contract.name, contract.notice, contract.selfDisplyName, isGroup]); // 处理备注保存 const handleSaveRemark = () => { - // 这里应该调用API保存备注到后端 - // 暂时只更新本地状态 + if (isGroup) { + // 群聊备注修改 + sendCommand("CmdModifyGroupRemark", { + wechatAccountId: contract.wechatAccountId, + chatroomId: contract.chatroomId, + newRemark: remarkValue, + }); + } else { + // 好友备注修改 + sendCommand("CmdModifyFriendRemark", { + wechatAccountId: contract.wechatAccountId, + wechatFriendId: contract.id, + newRemark: remarkValue, + }); + } + messageApi.success("备注保存成功"); setIsEditingRemark(false); // 更新contract对象中的备注(实际项目中应该通过props回调或状态管理) }; + // 处理群名称保存 + const handleSaveGroupName = () => { + sendCommand("CmdChatroomOperate", { + wechatAccountId: contract.wechatAccountId, + wechatChatroomId: contract.id, + chatroomOperateType: 6, + extra: `{\"chatroomName\":\"${groupNameValue}\"}`, + }); + + messageApi.success("群名称修改成功"); + setIsEditingGroupName(false); + }; + + // 点击编辑群名称按钮 + const handleEditGroupName = () => { + setGroupNameValue(contract.name || ''); + setIsEditingGroupName(true); + }; + + // 处理群公告保存 + const handleSaveGroupNotice = () => { + sendCommand("CmdChatroomOperate", { + wechatAccountId: contract.wechatAccountId, + wechatChatroomId: contract.id, + chatroomOperateType: 5, + extra: `{\"announce\":\"${groupNoticeValue}\"}`, + }); + + messageApi.success("群公告修改成功"); + setIsEditingGroupNotice(false); + }; + + // 点击编辑群公告按钮 + const handleEditGroupNotice = () => { + setGroupNoticeValue(contract.notice || ''); + setIsGroupNoticeModalVisible(true); + }; + + // 处理我在本群中的昵称保存 + const handleSaveSelfDisplayName = () => { + sendCommand("CmdChatroomOperate", { + wechatAccountId: contract.wechatAccountId, + wechatChatroomId: contract.id, + chatroomOperateType: 8, + extra: `${selfDisplayNameValue}`, + }); + + messageApi.success("群昵称修改成功"); + setIsEditingSelfDisplayName(false); + }; + + // 点击编辑群昵称按钮 + const handleEditSelfDisplayName = () => { + setSelfDisplayNameValue(contract.selfDisplyName || ''); + setIsEditingSelfDisplayName(true); + }; + // 处理取消编辑 const handleCancelEdit = () => { setRemarkValue(contract.conRemark || ""); @@ -98,20 +199,90 @@ const Person: React.FC = ({ setSelectedTags(newSelectedTags); - // 这里应该调用API保存标签到后端 + // 使用WebSocket发送修改标签命令 + if (isGroup) { + // 群聊标签修改 + sendCommand("CmdModifyGroupLabel", { + labels: newSelectedTags, + seq: +new Date(), + wechatAccountId: contract.wechatAccountId, + chatroomId: contract.chatroomId, + }); + } else { + // 好友标签修改 + sendCommand("CmdModifyFriendLabel", { + labels: newSelectedTags, + seq: +new Date(), + wechatAccountId: contract.wechatAccountId, + wechatFriendId: contract.id, + }); + } + messageApi.success( `标签"${tagName}"${selectedTags.includes(tagName) ? "已取消" : "已选中"}`, ); }; - // 模拟联系人详细信息 + // 处理新增标签 + const handleAddTag = () => { + if (!newTagValue.trim()) { + messageApi.error("请输入标签名称"); + return; + } + + if (allAvailableTags.includes(newTagValue.trim())) { + messageApi.error("标签已存在"); + return; + } + + const newTag = newTagValue.trim(); + + // 添加到可用标签列表 + setAllAvailableTags(prev => [...prev, newTag]); + + // 自动选中新添加的标签 + setSelectedTags(prev => [...prev, newTag]); + + // 使用WebSocket发送新增标签命令 + if (isGroup) { + // 群聊标签修改 + sendCommand("CmdModifyGroupLabel", { + labels: [...selectedTags, newTag], + seq: +new Date(), + wechatAccountId: contract.wechatAccountId, + chatroomId: contract.chatroomId, + }); + } else { + // 好友标签修改 + sendCommand("CmdModifyFriendLabel", { + labels: [...selectedTags, newTag], + seq: +new Date(), + wechatAccountId: contract.wechatAccountId, + wechatFriendId: contract.id || contract.wechatId, + }); + } + + messageApi.success(`标签"${newTag}"添加成功`); + setNewTagValue(""); + setIsAddingTag(false); + }; + + // 处理取消新增标签 + const handleCancelAddTag = () => { + setNewTagValue(""); + setIsAddingTag(false); + }; + + // 构建联系人或群聊详细信息 const contractInfo = { - name: contract.name, + name: contract.name || contract.nickname, nickname: contract.nickname, conRemark: remarkValue, // 使用当前编辑的备注值 alias: contract.alias, wechatId: contract.wechatId, - avatar: contract.avatar, + chatroomId: isGroup ? contract.chatroomId : undefined, + chatroomOwner: isGroup ? contract.chatroomOwner : undefined, + avatar: contract.avatar || contract.chatroomAvatar, phone: contract.phone || "-", email: contract.email || "-", department: contract.department || "-", @@ -119,9 +290,11 @@ const Person: React.FC = ({ company: contract.company || "-", region: contract.region || "-", joinDate: contract.joinDate || "-", + notice: isGroup ? contract.notice : undefined, + selfDisplyName: isGroup ? contract.selfDisplyName : undefined, status: "在线", tags: selectedTags, - bio: contract.bio || "-", + bio: contract.bio || contract.signature || "-", }; if (!showProfile) { @@ -151,14 +324,59 @@ const Person: React.FC = ({ icon={} />
- -

- {contractInfo.nickname || contractInfo.name} -

-
+ {isGroup && isEditingGroupName ? ( +
+ setGroupNameValue(e.target.value)} + placeholder="请输入群名称" + size="small" + style={{ flex: 1 }} + /> +
+ ) : ( + +
+

+ {contractInfo.nickname || contractInfo.name} +

+ {isGroup && ( +
+
+ )}
@@ -169,58 +387,245 @@ const Person: React.FC = ({ {/* 详细信息卡片 */} -
- - 微信号: - - {contractInfo.alias || contractInfo.wechatId} - -
-
- - 电话: - {contractInfo.phone} -
-
- - 地区: - {contractInfo.region} -
-
- - 备注: -
- {isEditingRemark ? ( + {isGroup ? ( + // 群聊信息 + <> +
+ + 群ID: + + {contractInfo.chatroomId} + +
+
+ + 群主: + {contractInfo.chatroomOwner} +
+
+ + 群昵称: +
+ {isEditingSelfDisplayName ? ( +
+ setSelfDisplayNameValue(e.target.value)} + placeholder="请输入群昵称" + size="small" + style={{ flex: 1 }} + /> +
+ ) : ( +
+ {contractInfo.selfDisplyName || "点击添加群昵称"} +
+ )} +
+
+ + ) : ( + // 好友信息 + <> +
+ + 微信号: + + {contractInfo.alias || contractInfo.wechatId} + +
+
+ + 电话: + {contractInfo.phone} +
+
+ + 地区: + {contractInfo.region} +
+ + )} + {!isGroup && ( +
+ + 备注: +
+ {isEditingRemark ? ( +
+ setRemarkValue(e.target.value)} + placeholder="请输入备注" + size="small" + style={{ flex: 1 }} + /> +
+ ) : ( +
+ {contractInfo.conRemark || "点击添加备注"} +
+ )} +
+
+ )} + + + {/* 标签 - 仅在非群聊时显示 */} + {!isGroup && ( + +
+ {/* 渲染所有可用标签,选中的排在前面 */} + {[...new Set([...selectedTags, ...allAvailableTags])].map( + (tag, index) => { + const isSelected = selectedTags.includes(tag); + return ( + handleTagToggle(tag)} + > + {tag} + + ); + }, + )} + + {/* 新增标签区域 */} + {isAddingTag ? (
setRemarkValue(e.target.value)} - placeholder="请输入备注" + value={newTagValue} + onChange={e => setNewTagValue(e.target.value)} + placeholder="请输入标签名称" size="small" - style={{ flex: 1 }} + style={{ width: "120px" }} + onPressEnter={handleAddTag} />
) : ( + setIsAddingTag(true)} + > + 新增标签 + + )} + + {allAvailableTags.length === 0 && !isAddingTag && ( + + 暂无可用标签 + + )} +
+
+ )} + + {/* 个人简介或群公告 */} + + {isGroup ? ( + // 群聊简介(原群公告) +
+ {isEditingGroupNotice ? (
= ({ gap: "8px", }} > - {contractInfo.conRemark || "点击添加备注"} + setGroupNoticeValue(e.target.value)} + placeholder="请输入内容" + rows={6} + style={{ width: '100%' }} + /> +
+ ) : ( +
+
+ {contractInfo.notice || "点击添加群公告"} +
)}
-
- - - {/* 标签 */} - -
- {/* 渲染所有可用标签,选中的排在前面 */} - {[...new Set([...selectedTags, ...allAvailableTags])].map( - (tag, index) => { - const isSelected = selectedTags.includes(tag); - return ( - handleTagToggle(tag)} - > - {tag} - - ); - }, - )} - {allAvailableTags.length === 0 && ( - - 暂无可用标签 - - )} -
-
- - {/* 个人简介 */} - -

{contractInfo.bio}

+ ) : ( + // 个人简介 +

{contractInfo.bio}

+ )}
{/* 操作按钮 */} @@ -294,6 +689,36 @@ const Person: React.FC = ({
+ + {/* 群公告编辑弹窗 */} + setIsGroupNoticeModalVisible(false)} + footer={[ + , + , + ]} + > + setGroupNoticeValue(e.target.value)} + placeholder="请输入内容" + rows={6} + style={{ width: '100%' }} + /> + ); }; diff --git a/Server/application/api/controller/UserController.php b/Server/application/api/controller/UserController.php index b36c9554..f0fcdf5e 100644 --- a/Server/application/api/controller/UserController.php +++ b/Server/application/api/controller/UserController.php @@ -266,7 +266,7 @@ class UserController extends BaseController * 获取验证码 * @return \think\response\Json */ - public function getVerifyCode() + public function getVerifyCode($isJson = false) { $headerData = ['client:' . self::CLIENT_TYPE]; $header = setHeader($headerData, '', 'plain'); @@ -279,17 +279,19 @@ class UserController extends BaseController if (is_array($response)) { // 如果verifyCodeImage和verifySessionId都不为null,返回它们 if (!empty($response['verifyCodeImage']) && !empty($response['verifySessionId'])) { - return successJson([ + $returnData = [ 'verifyCodeImage' => $response['verifyCodeImage'], 'verifySessionId' => $response['verifySessionId'] - ]); + ]; + return !empty($isJson) ? json_encode(['code' => 200,'data' => $returnData]) : successJson($returnData); } } // 如果不是预期的格式,返回原始数据 - return successJson($response); + return !empty($isJson) ? json_encode(['code' => 500,'data' => $response]) : errorJson('无需验证码'); } catch (\Exception $e) { - return errorJson('获取验证码失败:' . $e->getMessage()); + $msg = '获取验证码失败'. $e->getMessage(); + return !empty($isJson) ? json_encode(['code' => 400,'msg' => $msg]) : errorJson($msg); } } diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index 64a14985..d9c55088 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -141,11 +141,16 @@ Route::group('v1/', function () { Route::get('friendRequestTaskStats', 'app\cunkebao\controller\StatsController@getFriendRequestTaskStats'); Route::get('userInfoStats', 'app\cunkebao\controller\StatsController@userInfoStats'); }); - - })->middleware(['jwt']); +// 客服登录 +Route::group('v1/kefu', function () { + Route::post('login', 'app\cunkebao\controller\KeFuLoginController@index'); // 获取好友列表 +}); + + + Route::group('v1/api/scenarios', function () { Route::any('', 'app\cunkebao\controller\plan\PostExternalApiV1Controller@index'); diff --git a/Server/application/cunkebao/controller/KeFuLoginController.php b/Server/application/cunkebao/controller/KeFuLoginController.php new file mode 100644 index 00000000..45190e7b --- /dev/null +++ b/Server/application/cunkebao/controller/KeFuLoginController.php @@ -0,0 +1,78 @@ +request->param('username', ''); + $password = !empty($password) ? $password : $this->request->param('password', ''); + + $verifySessionId =!empty($verifySessionId) ? $verifySessionId : $this->request->param('verifySessionId', ''); + $verifyCode = !empty($verifyCode) ? $verifyCode : $this->request->param('verifyCode', ''); + + + if (empty($username) || empty($password)) { + return ResponseHelper::error('请输入账号密码'); + } + + + //登录参数 + $params = [ + 'grant_type' => 'password', + 'username' => $username, + 'password' => $password + ]; + + if (!empty($verifySessionId) && !empty($verifyCode)){ + $params[] = 'verifysessionid:' . $verifySessionId; + $params[] = 'verifycode:' . $verifyCode; + } + + + //获取验证码 + // $UserController = new UserController(); + // $verifyCode = $UserController->getVerifyCode(true); + // $verifyCode = json_decode($verifyCode, true); + // if ($verifyCode['code'] != 200) { + // exit_data($verifyCode); + // } + + try { + // 调用登录接口获取token + $headerData = ['client:kefu-client']; + $header = setHeader($headerData, '', 'plain'); + $result = requestCurl('https://s2.siyuguanli.com:9991/token', $params, 'POST', $header); + $token = handleApiResponse($result); + $userData['kefuData']['token'] = $token; + if (isset($token['access_token']) && !empty($token['access_token'])) { + $headerData = ['client:kefu-client']; + $header = setHeader($headerData, $token['access_token']); + $result = requestCurl('https://s2.siyuguanli.com:9991/api/account/self', [], 'GET', $header, 'json'); + $self = handleApiResponse($result); + $userData['kefuData']['self'] = $self; + } + + return ResponseHelper::success($userData, '登录成功'); + } catch (Exception $e) { + return ResponseHelper::error($e->getMessage(), $e->getCode()); + } + } +} \ No newline at end of file diff --git a/Server/application/job/WorkbenchGroupPushJob.php b/Server/application/job/WorkbenchGroupPushJob.php index 4cffc80d..1b080950 100644 --- a/Server/application/job/WorkbenchGroupPushJob.php +++ b/Server/application/job/WorkbenchGroupPushJob.php @@ -58,7 +58,7 @@ class WorkbenchGroupPushJob { try { // 获取所有工作台 - $workbenches = Workbench::where(['status' => 1, 'type' => 3, 'isDel' => 0])->order('id desc')->select(); + $workbenches = Workbench::where(['status' => 1, 'type' => 3, 'isDel' => 0,'id' => 178])->order('id desc')->select(); foreach ($workbenches as $workbench) { // 获取工作台配置 $config = WorkbenchGroupPush::where('workbenchId', $workbench->id)->find(); @@ -87,7 +87,7 @@ class WorkbenchGroupPushJob } - // 发微信消息 + // 发微信个人消息 public function sendMsgToGroup($workbench, $config, $msgConf) { // 消息拼接 msgType(1:文本 3:图片 43:视频 47:动图表情包(gif、其他表情包) 49:小程序/其他:图文、文件) @@ -117,7 +117,6 @@ class WorkbenchGroupPushJob } // 建立WebSocket $wsController = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]); - foreach ($msgConf as $content) { $sendData = []; $sqlData = []; @@ -294,82 +293,72 @@ class WorkbenchGroupPushJob return false; } - $limit = ($config['pushType'] == 1) ? 10 : 1; - $order = ($config['pushOrder'] == 1) ? 'ci.sendTime desc, ci.id asc' : 'ci.sendTime desc, ci.id desc'; - - // 基础查询构建器 - $baseQuery = function() use ($workbench, $contentids) { - return Db::name('content_library')->alias('cl') - ->join('content_item ci', 'ci.libraryId = cl.id') - ->where(['cl.isDel' => 0, 'ci.isDel' => 0]) - ->where('ci.sendTime <= ' . (time() + 60)) - ->whereIn('cl.id', $contentids) - ->field('ci.id,ci.libraryId,ci.contentType,ci.title,ci.content,ci.resUrls,ci.urls,ci.comment,ci.sendTime'); - }; - - // 获取未发送的内容 - $unsentContent = $baseQuery() - ->join('workbench_group_push_item wgpi', 'wgpi.contentId = ci.id and wgpi.workbenchId = ' . $workbench->id, 'left') - ->where('wgpi.id', 'null') - ->order($order) - ->limit($limit) - ->select(); - - if (!empty($unsentContent)) { - return $unsentContent; + if ($config['pushType'] == 1) { + $limit = 10; + } else { + $limit = 1; } - // 如果不允许循环发送,直接返回空 - if ($config['isLoop'] != 1) { - return []; + + //推送顺序 + if ($config['pushOrder'] == 1) { + $order = 'ci.sendTime desc, ci.id asc'; + } else { + $order = 'ci.sendTime desc, ci.id desc'; } - // 循环发送逻辑:检查是否需要标记循环完成 - $this->checkAndResetLoop($workbench->id, $contentids); - // 获取下一个要发送的内容(从内容库中查询,排除isLoop为0的数据) - $isPushIds = Db::name('workbench_group_push_item') - ->where(['workbenchId' => $workbench->id,'isLoop' => 0]) - ->column('contentId'); - $nextContent = $baseQuery() - ->whereNotIn('ci.id', $isPushIds) - ->group('ci.id') - ->order('ci.id asc') - ->limit($limit) - ->select(); - return $nextContent; - } - - /** - * 检查循环状态 - * @param int $workbenchId - * @param array $contentids - */ - private function checkAndResetLoop($workbenchId, $contentids) - { - // 统计总内容数 - $totalCount = Db::name('content_library')->alias('cl') + // 基础查询 + $query = Db::name('content_library')->alias('cl') ->join('content_item ci', 'ci.libraryId = cl.id') + ->join('workbench_group_push_item wgpi', 'wgpi.contentId = ci.id and wgpi.workbenchId = ' . $workbench->id, 'left') ->where(['cl.isDel' => 0, 'ci.isDel' => 0]) ->where('ci.sendTime <= ' . (time() + 60)) ->whereIn('cl.id', $contentids) - ->count(); + ->field([ + 'ci.id', + 'ci.libraryId', + 'ci.contentType', + 'ci.title', + 'ci.content', + 'ci.resUrls', + 'ci.urls', + 'ci.comment', + 'ci.sendTime' + ]); + // 复制 query + $query2 = clone $query; + $query3 = clone $query; + // 根据accountType处理不同的发送逻辑 + if ($config['isLoop'] == 1) { + // 可以循环发送 + // 1. 优先获取未发送的内容 + $unsentContent = $query->where('wgpi.id', 'null') + ->order($order) + ->limit(0, $limit) + ->select(); + exit_data($unsentContent); + if (!empty($unsentContent)) { + return $unsentContent; + } + $lastSendData = Db::name('workbench_group_push_item')->where('workbenchId', $workbench->id)->order('id desc')->find(); + $fastSendData = Db::name('workbench_group_push_item')->where('workbenchId', $workbench->id)->order('id asc')->find(); - // 统计已发送内容数(排除isLoop为0的数据) - $sentCount = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->join('content_item ci', 'ci.id = wgpi.contentId') - ->join('content_library cl', 'cl.id = ci.libraryId') - ->where('wgpi.workbenchId', $workbenchId) - ->where('wgpi.isLoop', 0) - ->whereIn('cl.id', $contentids) - ->count('DISTINCT wgpi.contentId'); + $sentContent = $query2->where('wgpi.contentId', '<', $lastSendData['contentId'])->order('wgpi.id ASC')->group('wgpi.contentId')->limit(0, $limit)->select(); - // 记录循环状态 - if ($sentCount >= $totalCount) { - Db::name('workbench_group_push_item') - ->where(['workbenchId' => $workbenchId, 'isLoop' => 0]) - ->update(['isLoop' => 1]); + if (empty($sentContent)) { + $sentContent = $query3->where('wgpi.contentId', '=', $fastSendData['contentId'])->order('wgpi.id ASC')->group('wgpi.contentId')->limit(0, $limit)->select(); + } + return $sentContent; + } else { + // 不能循环发送,只获取未发送的内容 + $list = $query->where('wgpi.id', 'null') + ->order($order) + ->limit(0, $limit) + ->select(); + + exit_data($list); + return $list; } } @@ -422,4 +411,4 @@ class WorkbenchGroupPushJob return false; } -} \ No newline at end of file +} \ No newline at end of file From 890e3c338b344118410571e31c2c5909dd435539 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 4 Sep 2025 11:07:44 +0800 Subject: [PATCH 071/146] =?UTF-8?q?fix(ProfileCard):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=BE=A4=E8=81=8A=E4=BF=A1=E6=81=AF=E7=BC=96=E8=BE=91=E6=97=B6?= =?UTF-8?q?JSON=E5=AD=97=E7=AC=A6=E4=B8=B2=E6=A0=BC=E5=BC=8F=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复群名称和群公告编辑时JSON字符串格式错误,将单引号替换为双引号以确保正确解析 --- Cunkebao/dist/.vite/manifest.json | 24 ++--- Cunkebao/dist/index.html | 6 +- .../components/ProfileCard/index.tsx | 87 ++++++++++++------- 3 files changed, 73 insertions(+), 44 deletions(-) diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json index 90e573c1..31b55eab 100644 --- a/Cunkebao/dist/.vite/manifest.json +++ b/Cunkebao/dist/.vite/manifest.json @@ -1,14 +1,18 @@ { - "_charts-BET_YNJb.js": { - "file": "assets/charts-BET_YNJb.js", + "_charts-Bwx_qhiY.js": { + "file": "assets/charts-Bwx_qhiY.js", "name": "charts", "imports": [ - "_ui-BSfOMVFg.js", + "_ui-D94amoNo.js", "_vendor-2vc8h_ct.js" ] }, - "_ui-BSfOMVFg.js": { - "file": "assets/ui-BSfOMVFg.js", + "_ui-D0C0OGrH.css": { + "file": "assets/ui-D0C0OGrH.css", + "src": "_ui-D0C0OGrH.css" + }, + "_ui-D94amoNo.js": { + "file": "assets/ui-D94amoNo.js", "name": "ui", "imports": [ "_vendor-2vc8h_ct.js" @@ -17,10 +21,6 @@ "assets/ui-D0C0OGrH.css" ] }, - "_ui-D0C0OGrH.css": { - "file": "assets/ui-D0C0OGrH.css", - "src": "_ui-D0C0OGrH.css" - }, "_utils-6WF66_dS.js": { "file": "assets/utils-6WF66_dS.js", "name": "utils", @@ -33,15 +33,15 @@ "name": "vendor" }, "index.html": { - "file": "assets/index-DX2o9_TA.js", + "file": "assets/index-_v-odON8.js", "name": "index", "src": "index.html", "isEntry": true, "imports": [ "_vendor-2vc8h_ct.js", "_utils-6WF66_dS.js", - "_ui-BSfOMVFg.js", - "_charts-BET_YNJb.js" + "_ui-D94amoNo.js", + "_charts-Bwx_qhiY.js" ], "css": [ "assets/index-DwDrBOQB.css" diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html index d9e4faec..aa3f73ba 100644 --- a/Cunkebao/dist/index.html +++ b/Cunkebao/dist/index.html @@ -11,11 +11,11 @@ - + - - + + diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx index 3803956f..622489d9 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx @@ -55,16 +55,22 @@ const Person: React.FC = ({ const [newTagValue, setNewTagValue] = useState(""); // 判断是否为群聊 - const isGroup = 'chatroomId' in contract; + const isGroup = "chatroomId" in contract; // 群聊相关状态 const [isEditingGroupName, setIsEditingGroupName] = useState(false); - const [groupNameValue, setGroupNameValue] = useState(contract.name || ''); + const [groupNameValue, setGroupNameValue] = useState(contract.name || ""); const [isEditingGroupNotice, setIsEditingGroupNotice] = useState(false); - const [groupNoticeValue, setGroupNoticeValue] = useState(contract.notice || ''); - const [isEditingSelfDisplayName, setIsEditingSelfDisplayName] = useState(false); - const [selfDisplayNameValue, setSelfDisplayNameValue] = useState(contract.selfDisplyName || ''); - const [isGroupNoticeModalVisible, setIsGroupNoticeModalVisible] = useState(false); + const [groupNoticeValue, setGroupNoticeValue] = useState( + contract.notice || "", + ); + const [isEditingSelfDisplayName, setIsEditingSelfDisplayName] = + useState(false); + const [selfDisplayNameValue, setSelfDisplayNameValue] = useState( + contract.selfDisplyName || "", + ); + const [isGroupNoticeModalVisible, setIsGroupNoticeModalVisible] = + useState(false); // 构建联系人或群聊详细信息 @@ -96,14 +102,21 @@ const Person: React.FC = ({ setSelectedTags(contract.labels || []); if (isGroup) { - setGroupNameValue(contract.name || ''); + setGroupNameValue(contract.name || ""); setIsEditingGroupName(false); - setGroupNoticeValue(contract.notice || ''); + setGroupNoticeValue(contract.notice || ""); setIsEditingGroupNotice(false); - setSelfDisplayNameValue(contract.selfDisplyName || ''); + setSelfDisplayNameValue(contract.selfDisplyName || ""); setIsEditingSelfDisplayName(false); } - }, [contract.conRemark, contract.labels, contract.name, contract.notice, contract.selfDisplyName, isGroup]); + }, [ + contract.conRemark, + contract.labels, + contract.name, + contract.notice, + contract.selfDisplyName, + isGroup, + ]); // 处理备注保存 const handleSaveRemark = () => { @@ -134,7 +147,7 @@ const Person: React.FC = ({ wechatAccountId: contract.wechatAccountId, wechatChatroomId: contract.id, chatroomOperateType: 6, - extra: `{\"chatroomName\":\"${groupNameValue}\"}`, + extra: `{"chatroomName":"${groupNameValue}"}`, }); messageApi.success("群名称修改成功"); @@ -143,7 +156,7 @@ const Person: React.FC = ({ // 点击编辑群名称按钮 const handleEditGroupName = () => { - setGroupNameValue(contract.name || ''); + setGroupNameValue(contract.name || ""); setIsEditingGroupName(true); }; @@ -153,7 +166,7 @@ const Person: React.FC = ({ wechatAccountId: contract.wechatAccountId, wechatChatroomId: contract.id, chatroomOperateType: 5, - extra: `{\"announce\":\"${groupNoticeValue}\"}`, + extra: `{"announce":"${groupNoticeValue}"}`, }); messageApi.success("群公告修改成功"); @@ -162,7 +175,7 @@ const Person: React.FC = ({ // 点击编辑群公告按钮 const handleEditGroupNotice = () => { - setGroupNoticeValue(contract.notice || ''); + setGroupNoticeValue(contract.notice || ""); setIsGroupNoticeModalVisible(true); }; @@ -181,7 +194,7 @@ const Person: React.FC = ({ // 点击编辑群昵称按钮 const handleEditSelfDisplayName = () => { - setSelfDisplayNameValue(contract.selfDisplyName || ''); + setSelfDisplayNameValue(contract.selfDisplyName || ""); setIsEditingSelfDisplayName(true); }; @@ -351,7 +364,7 @@ const Person: React.FC = ({ size="small" icon={} onClick={() => { - setGroupNameValue(contract.name || ''); + setGroupNameValue(contract.name || ""); setIsEditingGroupName(false); }} style={{ color: "#ff4d4f" }} @@ -400,7 +413,9 @@ const Person: React.FC = ({
群主: - {contractInfo.chatroomOwner} + + {contractInfo.chatroomOwner} +
@@ -416,7 +431,9 @@ const Person: React.FC = ({ > setSelfDisplayNameValue(e.target.value)} + onChange={e => + setSelfDisplayNameValue(e.target.value) + } placeholder="请输入群昵称" size="small" style={{ flex: 1 }} @@ -433,7 +450,9 @@ const Person: React.FC = ({ size="small" icon={} onClick={() => { - setSelfDisplayNameValue(contract.selfDisplyName || ''); + setSelfDisplayNameValue( + contract.selfDisplyName || "", + ); setIsEditingSelfDisplayName(false); }} style={{ color: "#ff4d4f" }} @@ -447,7 +466,9 @@ const Person: React.FC = ({ gap: "8px", }} > - {contractInfo.selfDisplyName || "点击添加群昵称"} + + {contractInfo.selfDisplyName || "点击添加群昵称"} + , -
+ + {/* 头像和基本信息 */} +
+ } + /> +
+ {isGroup && isEditingGroupName ? ( +
+ setGroupNameValue(e.target.value)} + placeholder="请输入群名称" + size="small" + style={{ flex: 1 }} + /> +
+ ) : ( + +
+

+ {contractInfo.nickname || contractInfo.name} +

+ {isGroup && ( +
+
+ )} + +
+ + {contractInfo.status} +
+
+
+ + {/* 详细信息卡片 */} + + {isGroup ? ( + // 群聊信息 + <> +
+ + 群ID: + + {contractInfo.chatroomId} + +
+
+ + 群主: + {contractInfo.chatroomOwner} +
+
+ + 群昵称: +
+ {isEditingSelfDisplayName ? ( +
+ setSelfDisplayNameValue(e.target.value)} + placeholder="请输入群昵称" + size="small" + style={{ flex: 1 }} + /> +
+ ) : ( +
+ {contractInfo.selfDisplyName || "点击添加群昵称"} +
+ )} +
+
+ + ) : ( + // 好友信息 + <> +
+ + 微信号: + + {contractInfo.alias || contractInfo.wechatId} + +
+
+ + 电话: + {contractInfo.phone} +
+
+ + 地区: + {contractInfo.region} +
+ + )} + {!isGroup && ( +
+ + 备注: +
+ {isEditingRemark ? ( +
+ setRemarkValue(e.target.value)} + placeholder="请输入备注" + size="small" + style={{ flex: 1 }} + /> +
+ ) : ( +
+ {contractInfo.conRemark || "点击添加备注"} +
+ )} +
+
+ )} +
+ + {/* 标签 - 仅在非群聊时显示 */} + {!isGroup && ( + +
+ {/* 渲染所有可用标签,选中的排在前面 */} + {[...new Set([...selectedTags, ...allAvailableTags])].map( + (tag, index) => { + const isSelected = selectedTags.includes(tag); + return ( + handleTagToggle(tag)} + > + {tag} + + ); + }, + )} + + {/* 新增标签区域 */} + {isAddingTag ? ( +
+ setNewTagValue(e.target.value)} + placeholder="请输入标签名称" + size="small" + style={{ width: "120px" }} + onPressEnter={handleAddTag} + /> +
+ ) : ( + setIsAddingTag(true)} + > + 新增标签 + + )} + + {allAvailableTags.length === 0 && !isAddingTag && ( + + 暂无可用标签 + + )} +
+
+ )} + + {/* 个人简介或群公告 */} + + {isGroup ? ( + // 群聊简介(原群公告) +
+
+
+ {contractInfo.notice || "点击添加群公告"} +
+
+ ) : ( + // 个人简介 +

{contractInfo.bio}

+ )} + + + {/* 操作按钮 */} +
+ + +
+
+
+ + + {/* 群公告编辑弹窗 */} + setIsGroupNoticeModalVisible(false)} + footer={[ + , + , + ]} + > + setGroupNoticeValue(e.target.value)} + placeholder="请输入内容" + rows={6} + style={{ width: '100%' }} + /> + + + ); +}; + +export default Person; diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/Person.module.scss b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/Person.module.scss index 10fb299b..1ef28153 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/Person.module.scss +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/Person.module.scss @@ -168,6 +168,31 @@ } } + .groupManagement { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 16px; + } + + .groupMemberList { + .groupMember { + display: flex; + align-items: center; + margin-bottom: 12px; + + &:last-child { + margin-bottom: 0; + } + + span { + margin-left: 8px; + font-size: 14px; + color: #262626; + } + } + } + .profileActions { margin-top: auto; padding-top: 16px; diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx index 3803956f..cca918b2 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx @@ -25,6 +25,8 @@ import { CheckOutlined, PlusOutlined, NotificationOutlined, + MinusOutlined, + SwapOutlined, } from "@ant-design/icons"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { useCkChatStore } from "@/store/module/ckchat/ckchat"; @@ -65,6 +67,66 @@ const Person: React.FC = ({ const [isEditingSelfDisplayName, setIsEditingSelfDisplayName] = useState(false); const [selfDisplayNameValue, setSelfDisplayNameValue] = useState(contract.selfDisplyName || ''); const [isGroupNoticeModalVisible, setIsGroupNoticeModalVisible] = useState(false); + const [confirmLoading, setConfirmLoading] = useState(false); + const [groupMembers, setGroupMembers] = useState([ + { + "friendId": 17453058, + "wechatId": "WANGMINGZHENG000", + "nickname": "wong", + "avatar": " https://wx.qlogo.cn/mmhead/ver_1/W67xuTtrYxgP80VoJGiceswibsxibqhrC06ib8Lh49pwV3ibibdqfXaDvRD4obSWdVibkdPOHWaedqviazEiasAiciaib3HJrvv7C0yyKtMosCbWxLcNWtXuN0WPicKKaTrQMe1NicQ20G/0 ", + "isAdmin": false, + "isDeleted": false, + "deletedDate": "0001-01-01T00:00:00" + }, + { + "friendId": 10411197, + "wechatId": "wxid_480es52qsj2812", + "nickname": "老坑爹- 解放双手,释放时间", + "avatar": " https://wx.qlogo.cn/mmhead/ver_1/r7xs2IJVd2Yhkd7yObdJzJkkiayOahjHNKtCZdiaECWyqR0XBqqlhE46UwT7hPZSf7QzplQiadVFuZtMIBL1LSliaJBK2azvL9PPyPT8MEHZQMA/0 ", + "isAdmin": false, + "isDeleted": false, + "deletedDate": "0001-01-01T00:00:00" + }, + { + "friendId": 21168549, + "wechatId": "wxid_dlhi90odctcl22", + "nickname": "许永平", + "avatar": " https://wx.qlogo.cn/mmhead/ver_1/KF8xrYxmYgHbdZpaPw78NFx7RibwWv1jsy5dlLEjgBDYkLBv0dadXSyLHicI218a34G3pMVhOsy1jA0QrDDKicSsYyGMzkdOT2xbwSpw0LKbvQ7yPtibJgrNPsbia0MYbHZXb/0 ", + "isAdmin": false, + "isDeleted": false, + "deletedDate": "2025-08-26T15:42:54.9914073+08:00" + } + ]); + + const [hoveredMember, setHoveredMember] = useState(null); + const [isAddFriendModalVisible, setIsAddFriendModalVisible] = useState(false); + const [selectedMember, setSelectedMember] = useState(null); + const [greeting, setGreeting] = useState(""); + + const handleAddFriend = (member) => { + setSelectedMember(member); + setGreeting(`你好, 我来自群聊${contractInfo.name}`); + setIsAddFriendModalVisible(true); + }; + + const handleSendFriendRequest = () => { + if (!selectedMember) return; + + sendCommand("CmdChatroomOperate", { + chatroomOperateTyp: 1, + extra: JSON.stringify({ + wechatId: selectedMember.wechatId, + sendWord: greeting, + }), + wechatAccountId: contract.wechatAccountId, + wechatChatroomId: contract.id, + }); + + messageApi.success('好友请求已发送'); + setIsAddFriendModalVisible(false); + setSelectedMember(null); + setGreeting(''); + }; // 构建联系人或群聊详细信息 @@ -134,7 +196,7 @@ const Person: React.FC = ({ wechatAccountId: contract.wechatAccountId, wechatChatroomId: contract.id, chatroomOperateType: 6, - extra: `{\"chatroomName\":\"${groupNameValue}\"}`, + extra: `{"chatroomName":"${groupNameValue}"}`, }); messageApi.success("群名称修改成功"); @@ -143,21 +205,26 @@ const Person: React.FC = ({ // 点击编辑群名称按钮 const handleEditGroupName = () => { - setGroupNameValue(contract.name || ''); + setGroupNameValue(contractInfo.name || ''); setIsEditingGroupName(true); }; // 处理群公告保存 const handleSaveGroupNotice = () => { + setConfirmLoading(true); sendCommand("CmdChatroomOperate", { wechatAccountId: contract.wechatAccountId, wechatChatroomId: contract.id, chatroomOperateType: 5, - extra: `{\"announce\":\"${groupNoticeValue}\"}`, + extra: `{"announce":"${groupNoticeValue}"}`, }); - messageApi.success("群公告修改成功"); - setIsEditingGroupNotice(false); + // 模拟延迟 + setTimeout(() => { + messageApi.success("群公告修改成功"); + setIsGroupNoticeModalVisible(false); + setConfirmLoading(false); + }, 1000); }; // 点击编辑群公告按钮 @@ -273,6 +340,29 @@ const Person: React.FC = ({ setIsAddingTag(false); }; + // 处理退出群聊 + const handleLeaveGroup = () => { + Modal.confirm({ + title: "确定要退出群聊吗?", + content: "退出后将不再接收此群聊消息。", + okText: "确定", + cancelText: "取消", + onOk: () => { + sendCommand("CmdChatroomOperate", { + wechatAccountId: contract.wechatAccountId, + wechatChatroomId: contract.id, + chatroomOperateType: 4, // 4 for quit + extra: "", + }); + messageApi.success("已退出群聊"); + // 可能还需要一个回调来关闭侧边栏或切换到另一个聊天 + if (onToggleProfile) { + onToggleProfile(); + } + }, + }); + }; + // 构建联系人或群聊详细信息 const contractInfo = { name: contract.name || contract.nickname, @@ -317,71 +407,28 @@ const Person: React.FC = ({
{/* 头像和基本信息 */} -
+
} /> -
- {isGroup && isEditingGroupName ? ( -
- setGroupNameValue(e.target.value)} - placeholder="请输入群名称" - size="small" - style={{ flex: 1 }} - /> -
- ) : ( - -
-

- {contractInfo.nickname || contractInfo.name} -

- {isGroup && ( -
-
- )} - +
{contractInfo.status}
+ +

+ {contractInfo.nickname || contractInfo.name} +

+
@@ -390,6 +437,72 @@ const Person: React.FC = ({ {isGroup ? ( // 群聊信息 <> +
+ + 群名称: +
+ {isEditingGroupName ? ( +
+ setGroupNameValue(e.target.value)} + placeholder="请输入群名称" + size="small" + style={{ flex: 1 }} + /> +
+ ) : ( +
+ +

+ {contractInfo.nickname || contractInfo.name} +

+
+
+ )} +
+
群ID: @@ -624,49 +737,39 @@ const Person: React.FC = ({ {isGroup ? ( // 群聊简介(原群公告) -
- {isEditingGroupNotice ? ( +
{ + setGroupNoticeValue(contractInfo.notice || ""); + setIsGroupNoticeModalVisible(true); + }} + style={{ cursor: "pointer" }} + > +
- setGroupNoticeValue(e.target.value)} - placeholder="请输入内容" - rows={6} - style={{ width: '100%' }} - /> + {contractInfo.notice || "点击添加群公告"}
- ) : ( -
-
- {contractInfo.notice || "点击添加群公告"} -
-
- )} +
) : ( // 个人简介 @@ -674,6 +777,59 @@ const Person: React.FC = ({ )} + {isGroup && ( + +
+ + + + + +
+
+ {groupMembers.map(member => ( +
setHoveredMember(member.friendId)} + onMouseLeave={() => setHoveredMember(null)} + > + + {member.nickname} + {hoveredMember === member.friendId && ( +
+ ))} +
+ +
+ )} + + setIsAddFriendModalVisible(false)} + okText="确定" + cancelText="取消" + > + setGreeting(e.target.value)} + placeholder="请输入招呼语" + rows={4} + /> + + {/* 操作按钮 */}
, - , @@ -716,7 +870,6 @@ const Person: React.FC = ({ onChange={e => setGroupNoticeValue(e.target.value)} placeholder="请输入内容" rows={6} - style={{ width: '100%' }} /> From 711159aa2b61d05538a3b45f5074d1a658369a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 4 Sep 2025 16:33:03 +0800 Subject: [PATCH 078/146] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/src/pages/pc/ckbox/api.ts | 12 ++++++++++++ Cunkebao/src/store/module/weChat/weChat.ts | 11 ++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts index daa8a79a..e8927829 100644 --- a/Cunkebao/src/pages/pc/ckbox/api.ts +++ b/Cunkebao/src/pages/pc/ckbox/api.ts @@ -39,6 +39,18 @@ export function getChatMessages(params: { }) { return request("/api/FriendMessage/SearchMessage", params, "GET"); } +export function getChatroomMessages(params: { + wechatAccountId: number; + wechatFriendId?: number; + wechatChatroomId?: number; + From: number; + To: number; + Count: number; + olderData: boolean; +}) { + return request("/api/ChatroomMessage/SearchMessage", params, "GET"); +} + //获取群列表 export function getGroupList(params: { prevId: number; count: number }) { return request( diff --git a/Cunkebao/src/store/module/weChat/weChat.ts b/Cunkebao/src/store/module/weChat/weChat.ts index 6025102c..22afe3b8 100644 --- a/Cunkebao/src/store/module/weChat/weChat.ts +++ b/Cunkebao/src/store/module/weChat/weChat.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -import { getChatMessages } from "@/pages/pc/ckbox/api"; +import { getChatMessages, getChatroomMessages } from "@/pages/pc/ckbox/api"; import { WeChatState } from "./weChat.data"; import { clearUnreadCount, updateConfig } from "@/pages/pc/ckbox/api"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; @@ -49,18 +49,19 @@ export const useWeChatStore = create()( wechatAccountId: contact.wechatAccountId, From: 1, To: 4704624000000, - Count: 10, + Count: 5, olderData: true, }; if ("chatroomId" in contact && contact.chatroomId) { params.wechatChatroomId = contact.id; + const messages = await getChatroomMessages(params); + set({ currentMessages: messages || [] }); } else { params.wechatFriendId = contact.id; + const messages = await getChatMessages(params); + set({ currentMessages: messages || [] }); } - - const messages = await getChatMessages(params); - set({ currentMessages: messages || [] }); } catch (error) { console.error("获取聊天消息失败:", error); } finally { From 876b4e4a5d5325d75c8630e0fba73167bd2270ae Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Thu, 4 Sep 2025 17:34:49 +0800 Subject: [PATCH 079/146] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cunkebao/controller/ContentLibraryController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/application/cunkebao/controller/ContentLibraryController.php b/Server/application/cunkebao/controller/ContentLibraryController.php index 5e77af28..726625a6 100644 --- a/Server/application/cunkebao/controller/ContentLibraryController.php +++ b/Server/application/cunkebao/controller/ContentLibraryController.php @@ -213,7 +213,7 @@ class ContentLibraryController extends Controller ['userId', '=', $this->request->userInfo['id']], ['isDel', '=', 0] // 只查询未删除的记录 ]) - ->field('id,name,sourceType,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,userId,companyId,createTime,updateTime,groupMembers') + ->field('id,name,sourceType,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,userId,companyId,createTime,updateTime,groupMembers,catchType') ->find(); if (empty($library)) { From 0d18623473c752d25a929c268d7b7931edc0d31e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 4 Sep 2025 18:07:42 +0800 Subject: [PATCH 080/146] =?UTF-8?q?feat(=E7=BE=A4=E8=81=8A):=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E7=BE=A4=E8=81=8A=E6=B6=88=E6=81=AF=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E5=8F=8A=E6=88=90=E5=91=98=E4=BF=A1=E6=81=AF=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在WeChatState中新增currentGroupMembers字段存储群成员信息 - 添加getGroupMembers API接口获取群成员数据 - 重构消息类型定义,统一使用ChatRecord接口 - 修改消息处理逻辑,支持群聊消息展示 - 调整聊天窗口样式,区分群聊和私聊消息显示 - 实现群成员头像和昵称展示功能 --- Cunkebao/src/pages/pc/ckbox/api.ts | 9 +++ .../ChatWindow/ChatWindow.module.scss | 21 ++--- .../pc/ckbox/components/ChatWindow/index.tsx | 76 +++++++++++++++---- Cunkebao/src/pages/pc/ckbox/data.ts | 9 +++ .../src/store/module/weChat/weChat.data.ts | 1 + Cunkebao/src/store/module/weChat/weChat.ts | 12 ++- .../src/store/module/websocket/msg.data.ts | 26 +------ .../src/store/module/websocket/msgManage.ts | 6 +- 8 files changed, 109 insertions(+), 51 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts index e8927829..98bc336a 100644 --- a/Cunkebao/src/pages/pc/ckbox/api.ts +++ b/Cunkebao/src/pages/pc/ckbox/api.ts @@ -60,6 +60,15 @@ export function getGroupList(params: { prevId: number; count: number }) { ); } +//获取群成员 +export function getGroupMembers(params: { id: number }) { + return request( + "/api/WechatChatroom/listMembersByWechatChatroomId", + params, + "GET", + ); +} + //触客宝登陆 export function loginWithToken(params: any) { return request( diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss index 917988e3..dfd8c0ce 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss @@ -351,12 +351,7 @@ } .messageBubble { - background: #fff; - border-radius: 12px; - padding: 8px 12px; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); max-width: 100%; - .messageSender { font-size: 12px; color: #8c8c8c; @@ -367,6 +362,11 @@ color: #262626; line-height: 1.5; word-break: break-word; + background: #fff; + padding: 8px 12px; + border-radius: 8px; + + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } .emojiMessage { @@ -567,11 +567,14 @@ margin-left: auto; .messageBubble { - background: #1890ff; - color: #fff; - + color: #262626; + line-height: 1.5; + word-break: break-word; + background: #fff; + border-radius: 8px; + max-width: 100%; .messageText { - color: #fff; + color: #333; } .messageTime { diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index e1bfb7ca..0d107a46 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -38,6 +38,9 @@ const ChatWindow: React.FC = ({ }) => { const messagesEndRef = useRef(null); const currentMessages = useWeChatStore(state => state.currentMessages); + const currentGroupMembers = useWeChatStore( + state => state.currentGroupMembers, + ); const prevMessagesRef = useRef(currentMessages); useEffect(() => { @@ -540,12 +543,22 @@ const ChatWindow: React.FC = ({ messages: [msg], })); }; - + const groupMemberAvatar = (msg: ChatRecord) => { + const groupMembers = currentGroupMembers.find( + v => v.wechatId == msg.sender.wechatId, + ); + return groupMembers.avatar; + }; + const clearWechatidInContent = (sender, content: string) => { + return content.replace(new RegExp(`${sender.wechatId}:\n`, "g"), ""); + }; const renderMessage = (msg: ChatRecord) => { + console.log(msg); // 添加null检查,防止访问null对象的属性 if (!msg) return null; const isOwn = msg?.isSend; + const isGroup = !!contract.chatroomId; return (
= ({ }`} >
- {!isOwn && ( - } - className={styles.messageAvatar} - /> + {/* 如果不是群聊 */} + {!isGroup && !isOwn && ( + <> + } + className={styles.messageAvatar} + /> + +
+ {!isOwn && ( +
+ {contract.nickname} +
+ )} + {parseMessageContent(msg?.content, msg)} +
+ + )} + {/* 如果是群聊 */} + {isGroup && !isOwn && ( + <> + } + className={styles.messageAvatar} + /> + +
+ {!isOwn && ( +
+ {msg?.sender?.nickname} +
+ )} + {parseMessageContent( + clearWechatidInContent(msg?.sender, msg?.content), + msg, + )} +
+ + )} + + {isOwn && ( +
+ {parseMessageContent(msg?.content, msg)} +
)} -
- {!isOwn && ( -
{msg?.senderName}
- )} - {parseMessageContent(msg?.content, msg)} -
); diff --git a/Cunkebao/src/pages/pc/ckbox/data.ts b/Cunkebao/src/pages/pc/ckbox/data.ts index c65df896..089d8935 100644 --- a/Cunkebao/src/pages/pc/ckbox/data.ts +++ b/Cunkebao/src/pages/pc/ckbox/data.ts @@ -178,6 +178,15 @@ export interface ChatRecord { origin: number; msgId: number; recalled: boolean; + sender?: { + chatroomNickname: string; + isAdmin: boolean; + isDeleted: boolean; + nickname: string; + ownerWechatId: string; + wechatId: string; + [key: string]: any; + }; [key: string]: any; } diff --git a/Cunkebao/src/store/module/weChat/weChat.data.ts b/Cunkebao/src/store/module/weChat/weChat.data.ts index a63ff292..99bfcb15 100644 --- a/Cunkebao/src/store/module/weChat/weChat.data.ts +++ b/Cunkebao/src/store/module/weChat/weChat.data.ts @@ -9,6 +9,7 @@ export interface WeChatState { // 消息加载状态 messagesLoading: boolean; + currentGroupMembers: any[]; // Actions setCurrentContact: ( diff --git a/Cunkebao/src/store/module/weChat/weChat.ts b/Cunkebao/src/store/module/weChat/weChat.ts index 22afe3b8..28e32d49 100644 --- a/Cunkebao/src/store/module/weChat/weChat.ts +++ b/Cunkebao/src/store/module/weChat/weChat.ts @@ -1,6 +1,10 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -import { getChatMessages, getChatroomMessages } from "@/pages/pc/ckbox/api"; +import { + getChatMessages, + getChatroomMessages, + getGroupMembers, +} from "@/pages/pc/ckbox/api"; import { WeChatState } from "./weChat.data"; import { clearUnreadCount, updateConfig } from "@/pages/pc/ckbox/api"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; @@ -17,6 +21,7 @@ export const useWeChatStore = create()( currentContract: null, currentMessages: [], messagesLoading: false, + currentGroupMembers: [], // Actions setCurrentContact: ( @@ -56,7 +61,10 @@ export const useWeChatStore = create()( if ("chatroomId" in contact && contact.chatroomId) { params.wechatChatroomId = contact.id; const messages = await getChatroomMessages(params); - set({ currentMessages: messages || [] }); + const currentGroupMembers = await getGroupMembers({ + id: contact.id, + }); + set({ currentMessages: messages || [], currentGroupMembers }); } else { params.wechatFriendId = contact.id; const messages = await getChatMessages(params); diff --git a/Cunkebao/src/store/module/websocket/msg.data.ts b/Cunkebao/src/store/module/websocket/msg.data.ts index ed8510b1..ec1a29c7 100644 --- a/Cunkebao/src/store/module/websocket/msg.data.ts +++ b/Cunkebao/src/store/module/websocket/msg.data.ts @@ -1,27 +1,7 @@ -export interface FriendMessage { - id: number; - wechatFriendId: number; - wechatAccountId: number; - tenantId: number; - accountId: number; - synergyAccountId: number; - content: string; - msgType: number; - msgSubType: number; - msgSvrId: string; - isSend: boolean; - createTime: string; - isDeleted: boolean; - deleteTime: string; - sendStatus: number; - wechatTime: number; - origin: number; - msgId: number; - recalled: boolean; -} +import { ChatRecord } from "@/pages/pc/ckbox/data"; export interface Messages { - friendMessage: FriendMessage | null; - chatroomMessage: string; + friendMessage?: ChatRecord | null; + chatroomMessage?: ChatRecord | null; seq: number; cmdType: string; } diff --git a/Cunkebao/src/store/module/websocket/msgManage.ts b/Cunkebao/src/store/module/websocket/msgManage.ts index 3146e213..92ec518c 100644 --- a/Cunkebao/src/store/module/websocket/msgManage.ts +++ b/Cunkebao/src/store/module/websocket/msgManage.ts @@ -28,7 +28,7 @@ const messageHandlers: Record = { // 发送消息响应 CmdSendMessageResp: message => { console.log("发送消息响应", message); - addMessage(message.friendMessage); + addMessage(message.friendMessage || message.chatroomMessage); // 在这里添加具体的处理逻辑 }, CmdSendMessageResult: message => { @@ -38,13 +38,13 @@ const messageHandlers: Record = { // 接收消息响应 CmdReceiveMessageResp: message => { console.log("接收消息响应", message); - addMessage(message.friendMessage); + addMessage(message.friendMessage || message.chatroomMessage); // 在这里添加具体的处理逻辑 }, //收到消息 CmdNewMessage: (message: Messages) => { // 在这里添加具体的处理逻辑 - receivedMsg(message.friendMessage); + receivedMsg(message.friendMessage || message.chatroomMessage); }, CmdFriendInfoChanged: message => { // console.log("好友信息变更", message); From 9c9b982a568368b82109b6b564e1484dd8bb9717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 4 Sep 2025 18:13:39 +0800 Subject: [PATCH 081/146] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=BA=93=E7=9B=B8=E5=85=B3=E9=A1=B5=E9=9D=A2=E5=92=8C?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除不再使用的内容库相关页面、组件和API文件 重命名表单字段以更清晰地表达其用途 优化代码结构和组织方式 --- Cunkebao/src/pages/mobile/content/form/api.ts | 26 - .../src/pages/mobile/content/form/data.ts | 61 -- .../mobile/content/form/index.module.scss | 140 ---- .../src/pages/mobile/content/form/index.tsx | 330 ---------- Cunkebao/src/pages/mobile/content/list/api.ts | 49 -- .../src/pages/mobile/content/list/data.ts | 66 -- .../mobile/content/list/index.module.scss | 217 ------ .../src/pages/mobile/content/list/index.tsx | 317 --------- .../mobile/content/materials/form/api.ts | 20 - .../mobile/content/materials/form/data.ts | 93 --- .../content/materials/form/index.module.scss | 160 ----- .../mobile/content/materials/form/index.tsx | 403 ------------ .../mobile/content/materials/list/api.ts | 37 -- .../mobile/content/materials/list/data.ts | 106 --- .../content/materials/list/index.module.scss | 615 ------------------ .../mobile/content/materials/list/index.tsx | 413 ------------ .../pages/mobile/mine/content/form/index.tsx | 4 +- 17 files changed, 2 insertions(+), 3055 deletions(-) delete mode 100644 Cunkebao/src/pages/mobile/content/form/api.ts delete mode 100644 Cunkebao/src/pages/mobile/content/form/data.ts delete mode 100644 Cunkebao/src/pages/mobile/content/form/index.module.scss delete mode 100644 Cunkebao/src/pages/mobile/content/form/index.tsx delete mode 100644 Cunkebao/src/pages/mobile/content/list/api.ts delete mode 100644 Cunkebao/src/pages/mobile/content/list/data.ts delete mode 100644 Cunkebao/src/pages/mobile/content/list/index.module.scss delete mode 100644 Cunkebao/src/pages/mobile/content/list/index.tsx delete mode 100644 Cunkebao/src/pages/mobile/content/materials/form/api.ts delete mode 100644 Cunkebao/src/pages/mobile/content/materials/form/data.ts delete mode 100644 Cunkebao/src/pages/mobile/content/materials/form/index.module.scss delete mode 100644 Cunkebao/src/pages/mobile/content/materials/form/index.tsx delete mode 100644 Cunkebao/src/pages/mobile/content/materials/list/api.ts delete mode 100644 Cunkebao/src/pages/mobile/content/materials/list/data.ts delete mode 100644 Cunkebao/src/pages/mobile/content/materials/list/index.module.scss delete mode 100644 Cunkebao/src/pages/mobile/content/materials/list/index.tsx diff --git a/Cunkebao/src/pages/mobile/content/form/api.ts b/Cunkebao/src/pages/mobile/content/form/api.ts deleted file mode 100644 index bc136d88..00000000 --- a/Cunkebao/src/pages/mobile/content/form/api.ts +++ /dev/null @@ -1,26 +0,0 @@ -import request from "@/api/request"; -import { - ContentLibrary, - CreateContentLibraryParams, - UpdateContentLibraryParams, -} from "./data"; - -// 获取内容库详情 -export function getContentLibraryDetail(id: string): Promise { - return request("/v1/content/library/detail", { id }, "GET"); -} - -// 创建内容库 -export function createContentLibrary( - params: CreateContentLibraryParams, -): Promise { - return request("/v1/content/library/create", params, "POST"); -} - -// 更新内容库 -export function updateContentLibrary( - params: UpdateContentLibraryParams, -): Promise { - const { id, ...data } = params; - return request(`/v1/content/library/update`, { id, ...data }, "POST"); -} diff --git a/Cunkebao/src/pages/mobile/content/form/data.ts b/Cunkebao/src/pages/mobile/content/form/data.ts deleted file mode 100644 index 3f3f3425..00000000 --- a/Cunkebao/src/pages/mobile/content/form/data.ts +++ /dev/null @@ -1,61 +0,0 @@ -// 内容库表单数据类型定义 -export interface ContentLibrary { - id: string; - name: string; - sourceType: number; // 1=微信好友, 2=聊天群 - creatorName?: string; - updateTime: string; - status: number; // 0=未启用, 1=已启用 - itemCount?: number; - createTime: string; - sourceFriends?: string[]; - sourceGroups?: string[]; - keywordInclude?: string[]; - keywordExclude?: string[]; - aiPrompt?: string; - timeEnabled?: number; - timeStart?: string; - timeEnd?: string; - selectedFriends?: any[]; - selectedGroups?: any[]; - selectedGroupMembers?: WechatGroupMember[]; -} - -// 微信群成员 -export interface WechatGroupMember { - id: string; - nickname: string; - wechatId: string; - avatar: string; - gender?: "male" | "female"; - role?: "owner" | "admin" | "member"; - joinTime?: string; -} - -// API 响应类型 -export interface ApiResponse { - code: number; - msg: string; - data: T; -} - -// 创建内容库参数 -export interface CreateContentLibraryParams { - name: string; - sourceType: number; - sourceFriends?: string[]; - sourceGroups?: string[]; - keywordInclude?: string[]; - keywordExclude?: string[]; - aiPrompt?: string; - timeEnabled?: number; - timeStart?: string; - timeEnd?: string; -} - -// 更新内容库参数 -export interface UpdateContentLibraryParams - extends Partial { - id: string; - status?: number; -} diff --git a/Cunkebao/src/pages/mobile/content/form/index.module.scss b/Cunkebao/src/pages/mobile/content/form/index.module.scss deleted file mode 100644 index b2f49942..00000000 --- a/Cunkebao/src/pages/mobile/content/form/index.module.scss +++ /dev/null @@ -1,140 +0,0 @@ -.form-page { - background: #f7f8fa; - padding: 16px; -} - -.form-main { - max-width: 420px; - margin: 0 auto; - padding: 16px 0 0 0; -} - -.form-section { - margin-bottom: 18px; -} - -.form-card { - border-radius: 16px; - box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); - padding: 24px 18px 18px 18px; - background: #fff; -} - -.form-label { - font-weight: 600; - font-size: 16px; - color: #222; - display: block; - margin-bottom: 6px; -} - -.section-title { - font-size: 16px; - font-weight: 700; - color: #222; - margin-top: 28px; - margin-bottom: 12px; - letter-spacing: 0.5px; -} - -.section-block { - padding: 12px 0 8px 0; - border-bottom: 1px solid #f0f0f0; - margin-bottom: 8px; -} - -.tabs-bar { - .adm-tabs-header { - background: #f7f8fa; - border-radius: 8px; - margin-bottom: 8px; - } - .adm-tabs-tab { - font-size: 15px; - font-weight: 500; - padding: 8px 0; - } -} - -.collapse { - margin-top: 12px; - .adm-collapse-panel-content { - padding-bottom: 8px; - background: #f8fafc; - border-radius: 10px; - padding: 18px 14px 10px 14px; - margin-top: 2px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03); - } - .form-section { - margin-bottom: 22px; - } - .form-label { - font-size: 15px; - font-weight: 500; - margin-bottom: 4px; - color: #333; - } - .adm-input { - min-height: 42px; - font-size: 15px; - border-radius: 7px; - margin-bottom: 2px; - } -} - -.ai-row, -.section-block { - display: flex; - align-items: center; - gap: 12px; -} - -.ai-desc { - color: #888; - font-size: 13px; - flex: 1; -} - -.date-row, -.section-block { - display: flex; - gap: 12px; - align-items: center; -} - -.adm-input { - min-height: 44px; - font-size: 15px; - border-radius: 8px; -} - -.submit-btn { - margin-top: 32px; - height: 48px !important; - border-radius: 10px !important; - font-size: 17px; - font-weight: 600; - letter-spacing: 1px; -} - -@media (max-width: 600px) { - .form-main { - max-width: 100vw; - padding: 0; - } - .form-card { - border-radius: 0; - box-shadow: none; - padding: 16px 6px 12px 6px; - } - .section-title { - font-size: 15px; - margin-top: 22px; - margin-bottom: 8px; - } - .submit-btn { - height: 44px !important; - font-size: 15px; - } -} diff --git a/Cunkebao/src/pages/mobile/content/form/index.tsx b/Cunkebao/src/pages/mobile/content/form/index.tsx deleted file mode 100644 index 1e093f1a..00000000 --- a/Cunkebao/src/pages/mobile/content/form/index.tsx +++ /dev/null @@ -1,330 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { useNavigate, useParams } from "react-router-dom"; -import { Input as AntdInput, Switch } from "antd"; -import { Button, Collapse, Toast, DatePicker, Tabs } from "antd-mobile"; -import NavCommon from "@/components/NavCommon"; -import FriendSelection from "@/components/FriendSelection"; -import GroupSelection from "@/components/GroupSelection"; -import Layout from "@/components/Layout/Layout"; -import style from "./index.module.scss"; -import request from "@/api/request"; -import { getContentLibraryDetail, updateContentLibrary } from "./api"; -import { GroupSelectionItem } from "@/components/GroupSelection/data"; -import { FriendSelectionItem } from "@/components/FriendSelection/data"; - -const { TextArea } = AntdInput; - -function formatDate(date: Date | null) { - if (!date) return ""; - // 格式化为 YYYY-MM-DD - const y = date.getFullYear(); - const m = (date.getMonth() + 1).toString().padStart(2, "0"); - const d = date.getDate().toString().padStart(2, "0"); - return `${y}-${m}-${d}`; -} - -export default function ContentForm() { - const navigate = useNavigate(); - const { id } = useParams<{ id?: string }>(); - const isEdit = !!id; - const [sourceType, setSourceType] = useState<"friends" | "groups">("friends"); - const [name, setName] = useState(""); - const [friendsGroups, setSelectedFriends] = useState([]); - const [friendsGroupsOptions, setSelectedFriendsOptions] = useState< - FriendSelectionItem[] - >([]); - const [selectedGroups, setSelectedGroups] = useState([]); - const [selectedGroupsOptions, setSelectedGroupsOptions] = useState< - GroupSelectionItem[] - >([]); - const [useAI, setUseAI] = useState(false); - const [aiPrompt, setAIPrompt] = useState(""); - const [enabled, setEnabled] = useState(true); - const [dateRange, setDateRange] = useState<[Date | null, Date | null]>([ - null, - null, - ]); - const [showStartPicker, setShowStartPicker] = useState(false); - const [showEndPicker, setShowEndPicker] = useState(false); - const [keywordsInclude, setKeywordsInclude] = useState(""); - const [keywordsExclude, setKeywordsExclude] = useState(""); - const [submitting, setSubmitting] = useState(false); - const [loading, setLoading] = useState(false); - - // 编辑模式下拉详情并回填 - useEffect(() => { - if (isEdit && id) { - setLoading(true); - getContentLibraryDetail(id) - .then(data => { - setName(data.name || ""); - setSourceType(data.sourceType === 1 ? "friends" : "groups"); - setSelectedFriends(data.sourceFriends || []); - setSelectedGroups(data.selectedGroups || []); - setSelectedGroupsOptions(data.selectedGroupsOptions || []); - - setSelectedFriendsOptions(data.sourceFriendsOptions || []); - - setKeywordsInclude((data.keywordInclude || []).join(",")); - setKeywordsExclude((data.keywordExclude || []).join(",")); - setAIPrompt(data.aiPrompt || ""); - setUseAI(!!data.aiPrompt); - setEnabled(data.status === 1); - // 时间范围 - const start = data.timeStart || data.startTime; - const end = data.timeEnd || data.endTime; - setDateRange([ - start ? new Date(start) : null, - end ? new Date(end) : null, - ]); - }) - .catch(e => { - Toast.show({ - content: e?.message || "获取详情失败", - position: "top", - }); - }) - .finally(() => setLoading(false)); - } - }, [isEdit, id]); - - const handleSubmit = async (e?: React.FormEvent) => { - if (e) e.preventDefault(); - if (!name.trim()) { - Toast.show({ content: "请输入内容库名称", position: "top" }); - return; - } - setSubmitting(true); - try { - const payload = { - name, - sourceType: sourceType === "friends" ? 1 : 2, - friends: friendsGroups, - groups: selectedGroups, - groupMembers: {}, - keywordInclude: keywordsInclude - .split(/,|,|\n|\s+/) - .map(s => s.trim()) - .filter(Boolean), - keywordExclude: keywordsExclude - .split(/,|,|\n|\s+/) - .map(s => s.trim()) - .filter(Boolean), - aiPrompt, - timeEnabled: dateRange[0] || dateRange[1] ? 1 : 0, - startTime: dateRange[0] ? formatDate(dateRange[0]) : "", - endTime: dateRange[1] ? formatDate(dateRange[1]) : "", - status: enabled ? 1 : 0, - }; - if (isEdit && id) { - await updateContentLibrary({ id, ...payload }); - Toast.show({ content: "保存成功", position: "top" }); - } else { - await request("/v1/content/library/create", payload, "POST"); - Toast.show({ content: "创建成功", position: "top" }); - } - navigate("/mine/content"); - } catch (e: any) { - Toast.show({ - content: e?.message || (isEdit ? "保存失败" : "创建失败"), - position: "top", - }); - } finally { - setSubmitting(false); - } - }; - - const handleGroupsChange = (groups: GroupSelectionItem[]) => { - setSelectedGroups(groups.map(g => g.id.toString())); - setSelectedGroupsOptions(groups); - }; - - const handleFriendsChange = (friends: FriendSelectionItem[]) => { - setSelectedFriends(friends.map(f => f.id.toString())); - setSelectedFriendsOptions(friends); - }; - - return ( - } - footer={ -
- -
- } - > -
-
e.preventDefault()} - autoComplete="off" - > -
- - setName(e.target.value)} - className={style["input"]} - /> -
- -
数据来源配置
-
- setSourceType(key as "friends" | "groups")} - className={style["tabs-bar"]} - > - - - - - - - -
- - - 关键词设置} - > -
- -