From 0681c69d67435f7f67883729b0a1c95e31a9d9e5 Mon Sep 17 00:00:00 2001
From: wong <106998207@qq.com>
Date: Wed, 20 Aug 2025 17:35:34 +0800
Subject: [PATCH 18/78] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=BB=BA=E7=BE=A4?=
=?UTF-8?q?=E7=BE=A4=E6=8F=90=E4=BA=A4=20+=20=E8=87=AA=E5=8A=A8=E7=82=B9?=
=?UTF-8?q?=E8=B5=9E=E6=95=B0=E6=8D=AE=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../controller/WorkbenchController.php | 199 ++++++++++--------
...PotentialListWithInCompanyV1Controller.php | 43 +++-
.../cunkebao/validate/Workbench.php | 26 ++-
3 files changed, 164 insertions(+), 104 deletions(-)
diff --git a/Server/application/cunkebao/controller/WorkbenchController.php b/Server/application/cunkebao/controller/WorkbenchController.php
index 8f8c6d5f..599efbfa 100644
--- a/Server/application/cunkebao/controller/WorkbenchController.php
+++ b/Server/application/cunkebao/controller/WorkbenchController.php
@@ -123,11 +123,14 @@ class WorkbenchController extends Controller
case self::TYPE_GROUP_CREATE: // 自动建群
$config = new WorkbenchGroupCreate;
$config->workbenchId = $workbench->id;
- $config->groupNamePrefix = $param['groupNamePrefix'];
- $config->maxGroups = $param['maxGroups'];
- $config->membersPerGroup = $param['membersPerGroup'];
- $config->devices = json_encode($param['deveiceGroups']);
- $config->targetGroups = json_encode($param['targetGroups']);
+ $config->devices = json_encode($param['deveiceGroups'], JSON_UNESCAPED_UNICODE);
+ $config->startTime = $param['startTime'];
+ $config->endTime = $param['endTime'];
+ $config->groupSizeMin = $param['groupSizeMin'];
+ $config->groupSizeMax = $param['groupSizeMax'];
+ $config->maxGroupsPerDay = $param['maxGroupsPerDay'];
+ $config->groupNameTemplate = $param['groupNameTemplate'];
+ $config->groupDescription = $param['groupDescription'];
$config->createTime = time();
$config->updateTime = time();
$config->save();
@@ -197,6 +200,9 @@ class WorkbenchController extends Controller
'groupPush' => function ($query) {
$query->field('workbenchId,pushType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,contentLibraries');
},
+ 'groupCreate' => function($query) {
+ $query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription');
+ },
'user' => function ($query) {
$query->field('id,username');
}
@@ -279,7 +285,6 @@ class WorkbenchController extends Controller
if (!empty($item->groupCreate)) {
$item->config = $item->groupCreate;
$item->config->devices = json_decode($item->config->devices, true);
- $item->config->targetGroups = json_decode($item->config->targetGroups, true);
}
unset($item->groupCreate, $item->group_create);
break;
@@ -385,11 +390,10 @@ class WorkbenchController extends Controller
'groupPush' => function ($query) {
$query->field('workbenchId,pushType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,contentLibraries');
},
- // 'groupCreate' => function($query) {
- // $query->field('workbenchId,groupNamePrefix,maxGroups,membersPerGroup,devices,targetGroups');
- // }
+ 'groupCreate' => function($query) {
+ $query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription');
+ }
];
-
$workbench = Workbench::where([
['id', '=', $id],
['userId', '=', $this->request->userInfo['id']],
@@ -454,65 +458,6 @@ class WorkbenchController extends Controller
$workbench->config = $workbench->groupPush;
$workbench->config->wechatGroups = json_decode($workbench->config->groups, true);
$workbench->config->contentLibraries = json_decode($workbench->config->contentLibraries, true);
-
- /* // 获取群组内容库
- $contentLibraryList = ContentLibrary::where('id', 'in', $workbench->config->contentLibraries)
- ->field('id,name,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,sourceType,userId,createTime,updateTime')
- ->with(['user' => function ($query) {
- $query->field('id,username');
- }])
- ->order('id', 'desc')
- ->select();
-
- // 处理JSON字段
- foreach ($contentLibraryList as &$item) {
- $item['sourceFriends'] = json_decode($item['sourceFriends'] ?: '[]', true);
- $item['sourceGroups'] = json_decode($item['sourceGroups'] ?: '[]', true);
- $item['keywordInclude'] = json_decode($item['keywordInclude'] ?: '[]', true);
- $item['keywordExclude'] = json_decode($item['keywordExclude'] ?: '[]', true);
- // 添加创建人名称
- $item['creatorName'] = $item['user']['username'] ?? '';
- $item['itemCount'] = Db::name('content_item')->where('libraryId', $item['id'])->count();
-
- // 获取好友详细信息
- if (!empty($item['sourceFriends'] && $item['sourceType'] == 1)) {
- $friendIds = $item['sourceFriends'];
- $friendsInfo = [];
-
- if (!empty($friendIds)) {
- // 查询好友信息,使用wechat_friendship表
- $friendsInfo = Db::name('wechat_friendship')->alias('wf')
- ->field('wf.id,wf.wechatId, wa.nickname, wa.avatar')
- ->join('wechat_account wa', 'wf.wechatId = wa.wechatId')
- ->whereIn('wf.id', $friendIds)
- ->select();
- }
-
- // 将好友信息添加到返回数据中
- $item['selectedFriends'] = $friendsInfo;
- }
-
-
- if (!empty($item['sourceGroups']) && $item['sourceType'] == 2) {
- $groupIds = $item['sourceGroups'];
- $groupsInfo = [];
-
- if (!empty($groupIds)) {
- // 查询群组信息
- $groupsInfo = Db::name('wechat_group')->alias('g')
- ->field('g.id, g.chatroomId, g.name, g.avatar, g.ownerWechatId')
- ->whereIn('g.id', $groupIds)
- ->select();
- }
-
- // 将群组信息添加到返回数据中
- $item['selectedGroups'] = $groupsInfo;
- }
-
- unset($item['user']); // 移除关联数据
- }
- $workbench->config->contentLibraryList = $contentLibraryList;*/
-
unset($workbench->groupPush, $workbench->group_push);
}
break;
@@ -521,7 +466,7 @@ class WorkbenchController extends Controller
if (!empty($workbench->groupCreate)) {
$workbench->config = $workbench->groupCreate;
$workbench->config->deveiceGroups = json_decode($workbench->config->devices, true);
- $workbench->config->targetGroups = json_decode($workbench->config->targetGroups, true);
+ unset($workbench->groupCreate, $workbench->group_create);
}
break;
//流量分发
@@ -534,7 +479,6 @@ class WorkbenchController extends Controller
$config_item = Db::name('workbench_traffic_config_item')->where(['workbenchId' => $workbench->id])->order('id DESC')->find();
$workbench->config->lastUpdated = !empty($config_item) ? date('Y-m-d H:i', $config_item['createTime']) : '--';
-
//统计
$labels = $workbench->config->pools;
$totalUsers = Db::table('s2_wechat_friend')->alias('wf')
@@ -768,11 +712,14 @@ class WorkbenchController extends Controller
case self::TYPE_GROUP_CREATE:
$config = WorkbenchGroupCreate::where('workbenchId', $param['id'])->find();
if ($config) {
- $config->groupNamePrefix = $param['groupNamePrefix'];
- $config->maxGroups = $param['maxGroups'];
- $config->membersPerGroup = $param['membersPerGroup'];
- $config->devices = json_encode($param['deveiceGroups']);
- $config->targetGroups = json_encode($param['targetGroups']);
+ $config->devices = json_encode($param['deveiceGroups'], JSON_UNESCAPED_UNICODE);
+ $config->startTime = $param['startTime'];
+ $config->endTime = $param['endTime'];
+ $config->groupSizeMin = $param['groupSizeMin'];
+ $config->groupSizeMax = $param['groupSizeMax'];
+ $config->maxGroupsPerDay = $param['maxGroupsPerDay'];
+ $config->groupNameTemplate = $param['groupNameTemplate'];
+ $config->groupDescription = $param['groupDescription'];
$config->updateTime = time();
$config->save();
}
@@ -954,12 +901,16 @@ class WorkbenchController extends Controller
if ($config) {
$newConfig = new WorkbenchGroupCreate;
$newConfig->workbenchId = $newWorkbench->id;
- $newConfig->groupNamePrefix = $config->groupNamePrefix;
- $newConfig->maxGroups = $config->maxGroups;
- $newConfig->membersPerGroup = $config->membersPerGroup;
$newConfig->devices = $config->devices;
- $newConfig->targetGroups = $config->targetGroups;
- $newConfig->account = $config->account;
+ $newConfig->startTime = $config->startTime;
+ $newConfig->endTime = $config->endTime;
+ $newConfig->groupSizeMin = $config->groupSizeMin;
+ $newConfig->groupSizeMax = $config->groupSizeMax;
+ $newConfig->maxGroupsPerDay = $config->maxGroupsPerDay;
+ $newConfig->groupNameTemplate = $config->groupNameTemplate;
+ $newConfig->groupDescription = $config->groupDescription;
+ $newConfig->createTime = time();
+ $newConfig->updateTime = time();
$newConfig->save();
}
break;
@@ -990,8 +941,6 @@ class WorkbenchController extends Controller
// 查询点赞记录
$list = Db::name('workbench_auto_like_item')->alias('wali')
->join(['s2_wechat_moments' => 'wm'], 'wali.snsId = wm.snsId')
- ->join(['s2_wechat_account' => 'wa'], 'wali.wechatAccountId = wa.id')
- ->join(['s2_wechat_friend' => 'wf'], 'wali.wechatFriendId = wf.id')
->field([
'wali.id',
'wali.workbenchId',
@@ -1004,10 +953,6 @@ class WorkbenchController extends Controller
'wm.resUrls',
'wm.createTime as momentTime',
'wm.userName',
- 'wa.nickName as operatorName',
- 'wa.avatar as operatorAvatar',
- 'wf.nickName as friendName',
- 'wf.avatar as friendAvatar',
])
->where($where)
->order('wali.createTime', 'desc')
@@ -1015,8 +960,36 @@ class WorkbenchController extends Controller
->page($page, $limit)
->select();
+
// 处理数据
foreach ($list as &$item) {
+ //处理用户信息
+ $friend = Db::table('s2_wechat_friend')
+ ->where(['id' => $item['wechatFriendId']])
+ ->field('nickName,avatar')
+ ->find();
+ if(!empty($friend)){
+ $item['friendName'] = $friend['nickName'];
+ $item['friendAvatar'] = $friend['avatar'];
+ }else{
+ $item['friendName'] = '';
+ $item['friendAvatar'] = '';
+ }
+
+
+ //处理客服
+ $friend = Db::table('s2_wechat_account')
+ ->where(['id' => $item['wechatAccountId']])
+ ->field('nickName,avatar')
+ ->find();
+ if(!empty($friend)){
+ $item['operatorName'] = $friend['nickName'];
+ $item['operatorAvatar'] = $friend['avatar'];
+ }else{
+ $item['operatorName'] = '';
+ $item['operatorAvatar'] = '';
+ }
+
// 处理时间格式
$item['likeTime'] = date('Y-m-d H:i:s', $item['likeTime']);
$item['momentTime'] = !empty($item['momentTime']) ? date('Y-m-d H:i:s', $item['momentTime']) : '';
@@ -1573,4 +1546,56 @@ class WorkbenchController extends Controller
}
+ public function getTrafficList()
+ {
+ $companyId = $this->request->userInfo['companyId'];
+ $page = $this->request->param('page', 1);
+ $limit = $this->request->param('limit', 10);
+ $keyword = $this->request->param('keyword', '');
+ $workbenchId = $this->request->param('workbenchId', '');
+ if (empty($workbenchId)) {
+ return json(['code' => 400, 'msg' => '参数错误']);
+ }
+
+ $workbench = Db::name('workbench')->where(['id' => $workbenchId,'isDel' => 0,'companyId' => $companyId,'type' => 5])->find();
+
+ if (empty($workbench)){
+ return json(['code' => 400, 'msg' => '该任务不存在或已删除']);
+ }
+ $query = Db::name('workbench_traffic_config_item')->alias('wtc')
+ ->join(['s2_wechat_friend' => 'wf'],'wtc.wechatFriendId = wf.id')
+ ->join('users u','wtc.wechatAccountId = u.s2_accountId','left')
+ ->field([
+ 'wtc.id','wtc.isRecycle','wtc.isRecycle','wtc.createTime',
+ 'wf.wechatId','wf.alias','wf.nickname','wf.avatar','wf.gender','wf.phone',
+ 'u.account','u.username'
+ ])
+ ->where(['wtc.workbenchId' => $workbenchId])
+ ->order('wtc.id DESC');
+
+ if (!empty($keyword)){
+ $query->where('wf.wechatId|wf.alias|wf.nickname|wf.phone|u.account|u.username','like','%' . $keyword . '%');
+ }
+
+
+
+ $total = $query->count();
+ $list = $query->page($page, $limit)->select();
+
+ foreach ($list as &$item) {
+ $item['createTime'] = date('Y-m-d H:i:s', $item['createTime']);
+ }
+ unset($item);
+
+
+
+ $data = [
+ 'total' => $total,
+ 'list' => $list,
+ ];
+
+ return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]);
+
+ }
+
}
\ No newline at end of file
diff --git a/Server/application/cunkebao/controller/traffic/GetPotentialListWithInCompanyV1Controller.php b/Server/application/cunkebao/controller/traffic/GetPotentialListWithInCompanyV1Controller.php
index e8f2260f..006e800b 100644
--- a/Server/application/cunkebao/controller/traffic/GetPotentialListWithInCompanyV1Controller.php
+++ b/Server/application/cunkebao/controller/traffic/GetPotentialListWithInCompanyV1Controller.php
@@ -364,12 +364,36 @@ class GetPotentialListWithInCompanyV1Controller extends BaseController
public function getPackage()
{
+
+ $page = $this->request->param('page',1);
+ $limit = $this->request->param('limit',10);
+ $keyword = $this->request->param('keyword','');
+
$companyId = $this->getUserInfo('companyId');
- $package = Db::name('traffic_source_package')
- ->whereIn('companyId', [$companyId,0])
- ->field('id,name')
- ->select();
- return ResponseHelper::success($package);
+ $package = Db::name('traffic_source_package')->alias('tsp')
+ ->join('traffic_source_package_item tspi','tspi.packageId=tsp.id','left')
+ ->whereIn('tsp.companyId', [$companyId,0])
+ ->field('tsp.id,tsp.name,tsp.description,tsp.createTime,count(tspi.id) as num')
+ ->group('tsp.id');
+
+ if (!empty($keyword)){
+ $package->where('tsp.name|tsp.description','like','%'.$keyword.'%');
+ }
+
+ $list = $package->page($page,$limit)->select();
+ $total = $package->count();
+
+ foreach ($list as $k => &$v) {
+ $v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
+ }
+ unset($v);
+
+ $data = [
+ 'total' => $total,
+ 'list' => $list,
+ ];
+
+ return ResponseHelper::success($data);
}
@@ -405,7 +429,14 @@ class GetPotentialListWithInCompanyV1Controller extends BaseController
if (!empty($package)){
return ResponseHelper::error('该流量池名称已存在');
}
- $packageId = Db::name('traffic_source_package')->insertGetId(['userId' => $userId,'companyId' => $companyId,'name' => $packageName,'isDel' => 0]);
+ $packageId = Db::name('traffic_source_package')->insertGetId([
+ 'userId' => $userId,
+ 'companyId' => $companyId,
+ 'name' => $packageName,
+ 'matchingRules' => json_encode($this->makeWhere()),
+ 'createTime' => time(),
+ 'isDel' => 0,
+ ]);
}
diff --git a/Server/application/cunkebao/validate/Workbench.php b/Server/application/cunkebao/validate/Workbench.php
index 7f801503..01fa360c 100644
--- a/Server/application/cunkebao/validate/Workbench.php
+++ b/Server/application/cunkebao/validate/Workbench.php
@@ -46,9 +46,10 @@ class Workbench extends Validate
'groups' => 'requireIf:type,3|array|min:1',
'contentLibraries' => 'requireIf:type,3|array|min:1',
// 自动建群特有参数
- 'groupNamePrefix' => 'requireIf:type,4|max:50',
- 'maxGroups' => 'requireIf:type,4|number|min:1',
- 'membersPerGroup' => 'requireIf:type,4|number|min:1',
+ 'groupNameTemplate' => 'requireIf:type,4|max:50',
+ 'maxGroupsPerDay' => 'requireIf:type,4|number|min:1',
+ 'groupSizeMin' => 'requireIf:type,4|number|min:1',
+ 'groupSizeMax' => 'requireIf:type,4|number|min:1',
// 流量分发特有参数
'distributeType' => 'requireIf:type,5|in:1,2',
'maxPerDay' => 'requireIf:type,5|number|min:1',
@@ -121,14 +122,17 @@ class Workbench extends Validate
'groups.array' => '推送群组格式错误',
'groups.min' => '至少选择一个推送群组',
// 自动建群相关提示
- 'groupNamePrefix.requireIf' => '请设置群名称前缀',
- 'groupNamePrefix.max' => '群名称前缀最多50个字符',
- 'maxGroups.requireIf' => '请设置最大建群数量',
- 'maxGroups.number' => '最大建群数量必须为数字',
- 'maxGroups.min' => '最大建群数量必须大于0',
- 'membersPerGroup.requireIf' => '请设置每个群的人数',
- 'membersPerGroup.number' => '每个群的人数必须为数字',
- 'membersPerGroup.min' => '每个群的人数必须大于0',
+ 'groupNameTemplate.requireIf' => '请设置群名称前缀',
+ 'groupNameTemplate.max' => '群名称前缀最多50个字符',
+ 'maxGroupsPerDay.requireIf' => '请设置最大建群数量',
+ 'maxGroupsPerDay.number' => '最大建群数量必须为数字',
+ 'maxGroupsPerDay.min' => '最大建群数量必须大于0',
+ 'groupSizeMin.requireIf' => '请设置每个群的人数',
+ 'groupSizeMin.number' => '每个群的人数必须为数字',
+ 'groupSizeMin.min' => '每个群的人数必须大于0',
+ 'groupSizeMax.requireIf' => '请设置每个群的人数',
+ 'groupSizeMax.number' => '每个群的人数必须为数字',
+ 'groupSizeMax.min' => '每个群的人数必须大于0',
// 流量分发相关提示
'distributeType.requireIf' => '请选择流量分发类型',
'distributeType.in' => '流量分发类型错误',
From ad5fc0bb52cb24cad946cc2bf4181fcd5479a44b 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, 20 Aug 2025 17:38:59 +0800
Subject: [PATCH 19/78] =?UTF-8?q?feat(ckbox):=20=E9=87=8D=E6=9E=84?=
=?UTF-8?q?=E8=81=94=E7=B3=BB=E4=BA=BA=E5=88=97=E8=A1=A8=E8=8E=B7=E5=8F=96?=
=?UTF-8?q?=E9=80=BB=E8=BE=91=E5=B9=B6=E4=BC=98=E5=8C=96=E7=99=BB=E5=BD=95?=
=?UTF-8?q?=E6=B5=81=E7=A8=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将联系人列表从模拟数据改为从API获取
- 优化登录处理流程,分离存客宝和触客宝的token获取
- 添加messageApi统一处理消息提示
- 修复路径引用问题并移除未使用的导入
---
Cunkebao/src/api/request2.ts | 1 -
Cunkebao/src/pages/login/Login.tsx | 59 +++++------
Cunkebao/src/pages/pc/ckbox/api.ts | 8 +-
.../pc/ckbox/components/ChatWindow/index.tsx | 6 +-
.../pc/ckbox/components/MessageList/api.ts | 6 ++
.../pc/ckbox/components/MessageList/index.tsx | 2 +-
Cunkebao/src/pages/pc/ckbox/index.tsx | 100 +++++-------------
Cunkebao/src/store/module/user.ts | 2 +
8 files changed, 70 insertions(+), 114 deletions(-)
create mode 100644 Cunkebao/src/pages/pc/ckbox/components/MessageList/api.ts
diff --git a/Cunkebao/src/api/request2.ts b/Cunkebao/src/api/request2.ts
index cacbdaf7..fd0e1f19 100644
--- a/Cunkebao/src/api/request2.ts
+++ b/Cunkebao/src/api/request2.ts
@@ -28,7 +28,6 @@ const instance: AxiosInstance = axios.create({
instance.interceptors.request.use((config: any) => {
// 在每次请求时动态获取最新的 token2
const { token2 } = useUserStore.getState();
-
if (token2) {
config.headers = config.headers || {};
config.headers["Authorization"] = `bearer ${token2}`;
diff --git a/Cunkebao/src/pages/login/Login.tsx b/Cunkebao/src/pages/login/Login.tsx
index 33f16e5b..621e6bf2 100644
--- a/Cunkebao/src/pages/login/Login.tsx
+++ b/Cunkebao/src/pages/login/Login.tsx
@@ -69,32 +69,6 @@ const Login: React.FC = () => {
}
};
- // 登录处理
- const handleLogin = async (values: any) => {
- if (!agreeToTerms) {
- Toast.show({ content: "请同意用户协议和隐私政策", position: "top" });
- return;
- }
- getToken(values).then(() => {
- getChuKeBaoUserInfo().then(res => {
- setUserInfo(res);
- getToken2().then(Token => {
- // // 使用WebSocket store连接
- // const { connect } = useWebSocketStore.getState();
- // connect({
- // accessToken: Token,
- // accountId: getAccountId()?.toString() || "",
- // client: "kefu-client",
- // autoReconnect: true,
- // reconnectInterval: 3000,
- // maxReconnectAttempts: 5,
- // });
- });
- });
- setLoading(false);
- });
- };
-
const getToken = (values: any) => {
return new Promise((resolve, reject) => {
// 添加typeId参数
@@ -130,13 +104,34 @@ const Login: React.FC = () => {
password: "kr123456",
username: "kr_xf3",
};
- const response = loginWithToken(params);
- response.then(res => {
- login2(res.access_token);
- resolve(res.access_token);
+ loginWithToken(params)
+ .then(res => {
+ login2(res.access_token);
+ resolve(res.access_token);
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ };
+
+ // 登录处理
+ const handleLogin = async (values: any) => {
+ if (!agreeToTerms) {
+ Toast.show({ content: "请同意用户协议和隐私政策", position: "top" });
+ return;
+ }
+ //获取存客宝
+ getToken(values)
+ .then(() => {})
+ .finally(() => {
+ setLoading(false);
});
- response.catch(err => {
- reject(err);
+
+ //获取触客宝
+ getToken2().then(() => {
+ getChuKeBaoUserInfo().then(res => {
+ setUserInfo(res);
});
});
};
diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts
index 5728fbf3..f71abd9f 100644
--- a/Cunkebao/src/pages/pc/ckbox/api.ts
+++ b/Cunkebao/src/pages/pc/ckbox/api.ts
@@ -1,11 +1,9 @@
-import request from "@/api/request";
+import request from "@/api/request2";
import {
ContactData,
- ContactListResponse,
ChatSession,
MessageData,
ChatHistoryResponse,
- SendMessageRequest,
MessageType,
GroupData,
OnlineStatus,
@@ -17,8 +15,8 @@ import {
} from "./data";
// 获取联系人列表
-export const getContactList = (): Promise => {
- return request("/v1/contacts", {}, "GET");
+export const getContactList = (params: { prevId: number; count: number }) => {
+ return request("/api/wechatFriend/list", params, "GET");
};
// 搜索联系人
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
index b9e0ff20..f02bee3c 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
@@ -60,6 +60,7 @@ const ChatWindow: React.FC = ({
showProfile = true,
onToggleProfile,
}) => {
+ const [messageApi, contextHolder] = message.useMessage();
const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState("");
const [loading, setLoading] = useState(false);
@@ -109,7 +110,7 @@ const ChatWindow: React.FC = ({
];
setMessages(mockMessages);
} catch (error) {
- message.error("获取聊天记录失败");
+ messageApi.error("获取聊天记录失败");
} finally {
setLoading(false);
}
@@ -137,7 +138,7 @@ const ChatWindow: React.FC = ({
onSendMessage(inputValue);
setInputValue("");
} catch (error) {
- message.error("发送失败");
+ messageApi.error("发送失败");
}
};
@@ -259,6 +260,7 @@ const ChatWindow: React.FC = ({
return (
+ {contextHolder}
{/* 聊天主体区域 */}
{/* 聊天头部 */}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/MessageList/api.ts b/Cunkebao/src/pages/pc/ckbox/components/MessageList/api.ts
new file mode 100644
index 00000000..cbb8c79d
--- /dev/null
+++ b/Cunkebao/src/pages/pc/ckbox/components/MessageList/api.ts
@@ -0,0 +1,6 @@
+import request from "@/api/request";
+
+// 获取联系人列表
+export const getContactList = (params: { prevId: string; count: number }) => {
+ return request("/api/wechatFriend/list", params, "GET");
+};
diff --git a/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx
index d2e9b0c1..fd0e0422 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx
@@ -2,7 +2,7 @@ import React from "react";
import { List, Avatar, Badge } from "antd";
import { UserOutlined, TeamOutlined } from "@ant-design/icons";
import dayjs from "dayjs";
-import { ChatSession } from "../data";
+import { ChatSession } from "@/pages/pc/ckbox/data";
import styles from "./MessageList.module.scss";
interface MessageListProps {
diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx
index 1dfd486f..0722949c 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/index.tsx
@@ -1,33 +1,10 @@
import React, { useState, useEffect, useRef } from "react";
-import {
- Layout,
- Input,
- Button,
- Avatar,
- List,
- Badge,
- Tabs,
- Space,
- Dropdown,
- Menu,
- message,
- Popover,
- Tooltip,
- Divider,
-} from "antd";
+import { Layout, Input, Button, Tabs, Space, message, Tooltip } from "antd";
import {
SearchOutlined,
- PlusOutlined,
- MoreOutlined,
- SendOutlined,
- SmileOutlined,
- PaperClipOutlined,
- PhoneOutlined,
- VideoCameraOutlined,
UserOutlined,
TeamOutlined,
MessageOutlined,
- SettingOutlined,
InfoCircleOutlined,
} from "@ant-design/icons";
import dayjs from "dayjs";
@@ -36,11 +13,12 @@ import ChatWindow from "./components/ChatWindow/index";
import ContactList from "./components/ContactList/index";
import MessageList from "./components/MessageList/index";
import styles from "./index.module.scss";
-
+import { getContactList } from "./api";
const { Sider, Content } = Layout;
const { TabPane } = Tabs;
const CkboxPage: React.FC = () => {
+ const [messageApi, contextHolder] = message.useMessage();
const [contacts, setContacts] = useState([]);
const [chatSessions, setChatSessions] = useState([]);
const [currentChat, setCurrentChat] = useState(null);
@@ -57,52 +35,27 @@ const CkboxPage: React.FC = () => {
const fetchContacts = async () => {
try {
setLoading(true);
- // 模拟联系人数据
- const mockContacts: ContactData[] = [
- {
- id: "1",
- name: "张三",
- phone: "13800138001",
- avatar: "",
- online: true,
- status: "在线",
- department: "技术部",
- position: "前端工程师",
- },
- {
- id: "2",
- name: "李四",
- phone: "13800138002",
- avatar: "",
- online: false,
- status: "忙碌中",
- department: "产品部",
- position: "产品经理",
- },
- {
- id: "3",
- name: "王五",
- phone: "13800138003",
- avatar: "",
- online: true,
- status: "在线",
- department: "设计部",
- position: "UI设计师",
- },
- {
- id: "4",
- name: "赵六",
- phone: "13800138004",
- avatar: "",
- online: false,
- status: "离线",
- department: "运营部",
- position: "运营专员",
- },
- ];
- setContacts(mockContacts);
+ // 使用API获取联系人数据
+ const response = await getContactList({ prevId: 0, count: 500 });
+ console.log(response);
+
+ if (response && response.data) {
+ // 转换API返回的数据结构为组件所需的ContactData结构
+ const contactList: ContactData[] = response.data.map((item: any) => ({
+ id: item.id.toString(),
+ name: item.nickname || item.conRemark || item.alias || "",
+ phone: item.phone || "",
+ avatar: item.avatar || "",
+ online: true, // 假设所有联系人都在线,实际应根据API返回数据调整
+ status: "在线", // 假设状态,实际应根据API返回数据调整
+ department: "", // API中没有对应字段,可以根据需要添加
+ position: "", // API中没有对应字段,可以根据需要添加
+ }));
+ setContacts(contactList);
+ }
} catch (error) {
- message.error("获取联系人失败");
+ messageApi.error("获取联系人失败");
+ console.error("获取联系人失败:", error);
} finally {
setLoading(false);
}
@@ -145,7 +98,7 @@ const CkboxPage: React.FC = () => {
];
setChatSessions(sessions);
} catch (error) {
- message.error("获取聊天记录失败");
+ messageApi.error("获取聊天记录失败");
}
};
@@ -195,9 +148,9 @@ const CkboxPage: React.FC = () => {
);
setCurrentChat(updatedSession);
- message.success("消息发送成功");
+ messageApi.success("消息发送成功");
} catch (error) {
- message.error("消息发送失败");
+ messageApi.error("消息发送失败");
}
};
@@ -223,6 +176,7 @@ const CkboxPage: React.FC = () => {
return (
+ {contextHolder}
{/* 左侧边栏 */}
{/* 搜索栏 */}
diff --git a/Cunkebao/src/store/module/user.ts b/Cunkebao/src/store/module/user.ts
index aeb811a8..070978c1 100644
--- a/Cunkebao/src/store/module/user.ts
+++ b/Cunkebao/src/store/module/user.ts
@@ -80,6 +80,8 @@ export const useUserStore = createPersistStore(
},
login2: token2 => {
localStorage.setItem("token2", token2);
+ console.log(token2);
+
set({ token2, isLoggedIn: true });
},
logout: () => {
From 61e1ffc6c49e17223e1701cf79e473577303540f 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, 20 Aug 2025 18:44:54 +0800
Subject: [PATCH 20/78] =?UTF-8?q?style(=E7=BB=84=E4=BB=B6):=20=E4=BC=98?=
=?UTF-8?q?=E5=8C=96=E6=B5=81=E9=87=8F=E6=B1=A0=E9=80=89=E6=8B=A9=E7=BB=84?=
=?UTF-8?q?=E4=BB=B6=E5=A4=B4=E5=83=8F=E6=98=BE=E7=A4=BA=E6=A0=B7=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
将PoolSelection组件中的头像显示从Avatar组件改为显示名称首字母的div,提升视觉一致性
---
Cunkebao/src/components/PoolSelection/index.tsx | 4 +++-
Cunkebao/src/pages/mobile/test/select.tsx | 4 ++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/Cunkebao/src/components/PoolSelection/index.tsx b/Cunkebao/src/components/PoolSelection/index.tsx
index 715c1a13..184fd1e8 100644
--- a/Cunkebao/src/components/PoolSelection/index.tsx
+++ b/Cunkebao/src/components/PoolSelection/index.tsx
@@ -82,7 +82,9 @@ export default function PoolSelection({
{selectedOptions.map(item => (
-
+
+ {(item.nickname || item.name || "").charAt(0)}
+
{item.nickname || item.name}
{item.wechatId || item.mobile}
diff --git a/Cunkebao/src/pages/mobile/test/select.tsx b/Cunkebao/src/pages/mobile/test/select.tsx
index f586895e..a24e53ac 100644
--- a/Cunkebao/src/pages/mobile/test/select.tsx
+++ b/Cunkebao/src/pages/mobile/test/select.tsx
@@ -36,7 +36,7 @@ const ComponentTest: React.FC = () => {
const [selectedFriendsOptions, setSelectedFriendsOptions] = useState<
FriendSelectionItem[]
>([]);
-
+
// 流量池选择状态
const [selectedPools, setSelectedPools] = useState
([]);
return (
@@ -160,7 +160,7 @@ const ComponentTest: React.FC = () => {
-
+
PoolSelection 组件测试
From 8e89c5ba73632ed9e72f9574907c000d0a299fdb 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, 21 Aug 2025 10:51:23 +0800
Subject: [PATCH 21/78] =?UTF-8?q?feat(ckbox):=20=E5=AE=9E=E7=8E=B0?=
=?UTF-8?q?=E8=81=8A=E5=A4=A9=E7=95=8C=E9=9D=A2=E5=8A=9F=E8=83=BD=E5=B9=B6?=
=?UTF-8?q?=E9=9B=86=E6=88=90WebSocket?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
添加联系人列表、聊天会话和消息窗口组件
集成WebSocket连接实现实时聊天功能
重构API调用以获取聊天记录和联系人信息
添加样式文件美化聊天界面
---
Cunkebao/src/pages/login/Login.tsx | 12 +-
Cunkebao/src/pages/pc/ckbox/api.ts | 18 +-
.../pc/ckbox/components/ChatWindow/index.tsx | 91 +++---
.../SidebarMenu/ContactListSimple.module.scss | 66 +++++
.../SidebarMenu/ContactListSimple.tsx | 50 ++++
.../SidebarMenu/SidebarMenu.module.scss | 63 +++++
.../pc/ckbox/components/SidebarMenu/index.tsx | 125 +++++++++
Cunkebao/src/pages/pc/ckbox/index.tsx | 262 +++++++++---------
Cunkebao/src/pages/pc/ckbox/main.ts | 7 +
9 files changed, 518 insertions(+), 176 deletions(-)
create mode 100644 Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.module.scss
create mode 100644 Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.tsx
create mode 100644 Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss
create mode 100644 Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
create mode 100644 Cunkebao/src/pages/pc/ckbox/main.ts
diff --git a/Cunkebao/src/pages/login/Login.tsx b/Cunkebao/src/pages/login/Login.tsx
index 621e6bf2..197c410d 100644
--- a/Cunkebao/src/pages/login/Login.tsx
+++ b/Cunkebao/src/pages/login/Login.tsx
@@ -129,9 +129,19 @@ const Login: React.FC = () => {
});
//获取触客宝
- getToken2().then(() => {
+ getToken2().then((Token: string) => {
getChuKeBaoUserInfo().then(res => {
setUserInfo(res);
+ // 使用WebSocket store连接
+ const { connect } = useWebSocketStore.getState();
+ connect({
+ accessToken: Token,
+ accountId: getAccountId()?.toString() || "",
+ client: "kefu-client",
+ autoReconnect: true,
+ reconnectInterval: 3000,
+ maxReconnectAttempts: 5,
+ });
});
});
};
diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts
index f71abd9f..ef1da61a 100644
--- a/Cunkebao/src/pages/pc/ckbox/api.ts
+++ b/Cunkebao/src/pages/pc/ckbox/api.ts
@@ -19,16 +19,24 @@ export const getContactList = (params: { prevId: number; count: number }) => {
return request("/api/wechatFriend/list", params, "GET");
};
-// 搜索联系人
-export const searchContacts = (keyword: string): Promise => {
- return request("/v1/contacts/search", { keyword }, "GET");
-};
-
// 获取聊天会话列表
export const getChatSessions = (): Promise => {
return request("/v1/chats/sessions", {}, "GET");
};
+// 搜索联系人
+export const getChatMessage = (params: {
+ wechatAccountId: number;
+ wechatFriendId: number;
+ From: number;
+ To: number;
+ Count: number;
+ olderData: boolean;
+ keyword: string;
+}) => {
+ return request("/api/FriendMessage/SearchMessage", params, "GET");
+};
+
// 获取聊天历史
export const getChatHistory = (
chatId: string,
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
index f02bee3c..f3f76a2f 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
@@ -41,7 +41,7 @@ import {
} from "@ant-design/icons";
import dayjs from "dayjs";
import { ChatSession, MessageData, MessageType } from "../../data";
-// import { getChatHistory, sendMessage } from "../api";
+import { getChatMessage } from "../../api";
import styles from "./ChatWindow.module.scss";
const { Header, Content, Footer, Sider } = Layout;
@@ -78,38 +78,65 @@ const ChatWindow: React.FC = ({
const fetchChatHistory = async () => {
try {
setLoading(true);
- // 模拟聊天历史数据
- const mockMessages: MessageData[] = [
- {
- id: "1",
- senderId: "other",
- senderName: chat.name,
- content: "你好,请问有什么可以帮助您的吗?",
- type: MessageType.TEXT,
- timestamp: dayjs().subtract(10, "minute").toISOString(),
- isRead: true,
- },
- {
- id: "2",
- senderId: "me",
- senderName: "我",
- content: "我想了解一下你们的产品",
- type: MessageType.TEXT,
- timestamp: dayjs().subtract(8, "minute").toISOString(),
- isRead: true,
- },
- {
- id: "3",
- senderId: "other",
- senderName: chat.name,
- content: "好的,我来为您详细介绍",
- type: MessageType.TEXT,
- timestamp: dayjs().subtract(5, "minute").toISOString(),
- isRead: true,
- },
- ];
- setMessages(mockMessages);
+
+ // 从chat对象中提取wechatFriendId
+ // 假设chat.id存储的是wechatFriendId
+ const wechatFriendId = parseInt(chat.id);
+
+ if (isNaN(wechatFriendId)) {
+ messageApi.error("无效的聊天ID");
+ return;
+ }
+
+ // 调用API获取聊天历史
+ const response = await getChatMessage({
+ wechatAccountId: 32686452, // 使用实际的wechatAccountId
+ wechatFriendId: wechatFriendId,
+ From: 0,
+ To: 0,
+ Count: 50, // 获取最近的50条消息
+ olderData: false,
+ keyword: "",
+ });
+
+ console.log("聊天历史响应:", response);
+
+ if (response && Array.isArray(response)) {
+ // 将API返回的消息记录转换为MessageData格式
+ const chatMessages: MessageData[] = response.map(item => {
+ // 解析content字段,它是一个JSON字符串
+ let msgContent = "";
+ try {
+ const contentObj = JSON.parse(item.content);
+ msgContent = contentObj.content || "";
+ } catch (e) {
+ msgContent = item.content;
+ }
+
+ // 判断消息是发送还是接收
+ const isSend = item.isSend === true;
+
+ return {
+ id: item.id.toString(),
+ senderId: isSend ? "me" : "other",
+ senderName: isSend ? "我" : chat.name,
+ content: msgContent,
+ type: MessageType.TEXT, // 默认为文本类型,实际应根据msgType字段判断
+ timestamp: item.createTime || new Date(item.wechatTime).toISOString(),
+ isRead: true, // 默认已读
+ };
+ });
+
+ // 按时间排序
+ chatMessages.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
+
+ setMessages(chatMessages);
+ } else {
+ // 如果没有消息,显示空数组
+ setMessages([]);
+ }
} catch (error) {
+ console.error("获取聊天记录失败:", error);
messageApi.error("获取聊天记录失败");
} finally {
setLoading(false);
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.module.scss
new file mode 100644
index 00000000..c7a67ec9
--- /dev/null
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.module.scss
@@ -0,0 +1,66 @@
+.contactListSimple {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ background-color: #fff;
+ color: #333;
+
+ .header {
+ padding: 10px 15px;
+ font-weight: bold;
+ border-bottom: 1px solid #f0f0f0;
+ }
+
+ .list {
+ flex: 1;
+ overflow-y: auto;
+
+ :global(.ant-list-item) {
+ padding: 10px 15px;
+ border-bottom: none;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #f5f5f5;
+ }
+ }
+ }
+
+ .contactItem {
+ display: flex;
+ align-items: center;
+ padding: 8px 15px;
+
+ &.selected {
+ background-color: #f5f5f5;
+ }
+ }
+
+ .avatarContainer {
+ margin-right: 10px;
+ }
+
+ .avatar {
+ background-color: #1890ff;
+ }
+
+ .contactInfo {
+ flex: 1;
+ overflow: hidden;
+ }
+
+ .name {
+ font-size: 14px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .status {
+ font-size: 12px;
+ color: #aaa;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+}
\ No newline at end of file
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.tsx
new file mode 100644
index 00000000..9a5dfba3
--- /dev/null
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.tsx
@@ -0,0 +1,50 @@
+import React from "react";
+import { List, Avatar, Badge } from "antd";
+import { ContactData } from "../../data";
+import styles from "./ContactListSimple.module.scss";
+
+interface ContactListSimpleProps {
+ contacts: ContactData[];
+ onContactClick: (contact: ContactData) => void;
+ selectedContactId?: string;
+}
+
+const ContactListSimple: React.FC = ({
+ contacts,
+ onContactClick,
+ selectedContactId,
+}) => {
+ return (
+
+
全部好友
+
(
+ onContactClick(contact)}
+ className={`${styles.contactItem} ${contact.id === selectedContactId ? styles.selected : ""}`}
+ >
+
+
+ {contact.name.charAt(0)}
+ }
+ className={styles.avatar}
+ />
+
+
+
+
+ )}
+ />
+
+ );
+};
+
+export default ContactListSimple;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss
new file mode 100644
index 00000000..614120fb
--- /dev/null
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss
@@ -0,0 +1,63 @@
+.sidebar {
+ background: #fff;
+ border-right: 1px solid #f0f0f0;
+ display: flex;
+ flex-direction: column;
+
+ .searchBar {
+ padding: 16px;
+ border-bottom: 1px solid #f0f0f0;
+ background: #fff;
+
+ :global(.ant-input) {
+ border-radius: 20px;
+ background: #f5f5f5;
+ border: none;
+
+ &:focus {
+ background: #fff;
+ border: 1px solid #1890ff;
+ box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+ }
+ }
+ }
+
+ .tabs {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ :global(.ant-tabs-content) {
+ flex: 1;
+ overflow: hidden;
+ }
+
+ :global(.ant-tabs-tabpane) {
+ height: 100%;
+ overflow: hidden;
+ }
+
+ :global(.ant-tabs-nav) {
+ margin: 0;
+ padding: 0 16px;
+ background: #fff;
+ border-bottom: 1px solid #f0f0f0;
+
+ :global(.ant-tabs-tab) {
+ padding: 12px 0;
+ margin: 0 16px 0 0;
+ }
+ }
+ }
+
+ .emptyState {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ color: #999;
+ padding: 20px;
+ text-align: center;
+ }
+}
\ No newline at end of file
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
new file mode 100644
index 00000000..85b99318
--- /dev/null
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
@@ -0,0 +1,125 @@
+import React, { useState } from "react";
+import { Layout, Input, Tabs } from "antd";
+import {
+ SearchOutlined,
+ UserOutlined,
+ TeamOutlined,
+ MessageOutlined,
+} from "@ant-design/icons";
+import { ContactData, ChatSession } from "../../data";
+import ContactListSimple from "./ContactListSimple";
+import MessageList from "../MessageList/index";
+import styles from "./SidebarMenu.module.scss";
+
+const { Sider } = Layout;
+const { TabPane } = Tabs;
+
+interface SidebarMenuProps {
+ contacts: ContactData[];
+ chatSessions: ChatSession[];
+ currentChat: ChatSession | null;
+ onContactClick: (contact: ContactData) => void;
+ onChatSelect: (chat: ChatSession) => void;
+ loading?: boolean;
+}
+
+const SidebarMenu: React.FC = ({
+ contacts,
+ chatSessions,
+ currentChat,
+ onContactClick,
+ onChatSelect,
+ loading = false,
+}) => {
+ const [searchText, setSearchText] = useState("");
+ const [activeTab, setActiveTab] = useState("contacts");
+
+ const handleSearch = (value: string) => {
+ setSearchText(value);
+ };
+
+ const getFilteredContacts = () => {
+ if (!searchText) return contacts;
+ return contacts.filter(
+ contact =>
+ contact.name.toLowerCase().includes(searchText.toLowerCase()) ||
+ contact.phone.includes(searchText),
+ );
+ };
+
+ const getFilteredSessions = () => {
+ if (!searchText) return chatSessions;
+ return chatSessions.filter(session =>
+ session.name.toLowerCase().includes(searchText.toLowerCase()),
+ );
+ };
+
+ return (
+
+ {/* 搜索栏 */}
+
+ }
+ value={searchText}
+ onChange={e => handleSearch(e.target.value)}
+ allowClear
+ />
+
+
+ {/* 标签页 */}
+
+
+
+ 聊天
+
+ }
+ key="chats"
+ >
+
+
+
+
+ 联系人
+
+ }
+ key="contacts"
+ >
+
+
+
+
+ 群组
+
+ }
+ key="groups"
+ >
+
+
+
+
+ );
+};
+
+export default SidebarMenu;
diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx
index 0722949c..a70737e2 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/index.tsx
@@ -1,47 +1,66 @@
import React, { useState, useEffect, useRef } from "react";
-import { Layout, Input, Button, Tabs, Space, message, Tooltip } from "antd";
-import {
- SearchOutlined,
- UserOutlined,
- TeamOutlined,
- MessageOutlined,
- InfoCircleOutlined,
-} from "@ant-design/icons";
+import { Layout, Button, Space, message, Tooltip } from "antd";
+import { InfoCircleOutlined, MessageOutlined } from "@ant-design/icons";
import dayjs from "dayjs";
import { ContactData, MessageData, ChatSession } from "./data";
import ChatWindow from "./components/ChatWindow/index";
-import ContactList from "./components/ContactList/index";
-import MessageList from "./components/MessageList/index";
+import SidebarMenu from "./components/SidebarMenu/index";
import styles from "./index.module.scss";
-import { getContactList } from "./api";
-const { Sider, Content } = Layout;
-const { TabPane } = Tabs;
+import { getContactList, getChatMessage } from "./api";
+const { Content } = Layout;
+import { loginWithToken, getChuKeBaoUserInfo } from "@/pages/login/api";
+import { useCkChatStore } from "@/store/module/ckchat";
+import { useUserStore } from "@/store/module/user";
const CkboxPage: React.FC = () => {
const [messageApi, contextHolder] = message.useMessage();
const [contacts, setContacts] = useState([]);
const [chatSessions, setChatSessions] = useState([]);
const [currentChat, setCurrentChat] = useState(null);
- const [searchText, setSearchText] = useState("");
const [loading, setLoading] = useState(false);
- const [activeTab, setActiveTab] = useState("contacts");
const [showProfile, setShowProfile] = useState(true);
+ const { setUserInfo, getAccountId } = useCkChatStore();
+ const { login2 } = useUserStore();
useEffect(() => {
- fetchContacts();
- fetchChatSessions();
+ //获取触客宝
+ getToken2().then(() => {
+ getChuKeBaoUserInfo().then(res => {
+ setUserInfo(res);
+ setTimeout(() => {
+ fetchContacts();
+ fetchChatSessions();
+ });
+ });
+ });
}, []);
+ const getToken2 = () => {
+ return new Promise((resolve, reject) => {
+ const params = {
+ grant_type: "password",
+ password: "kr123456",
+ username: "kr_xf3",
+ };
+ loginWithToken(params)
+ .then(res => {
+ login2(res.access_token);
+ resolve(res.access_token);
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ };
+
const fetchContacts = async () => {
try {
setLoading(true);
// 使用API获取联系人数据
const response = await getContactList({ prevId: 0, count: 500 });
- console.log(response);
-
- if (response && response.data) {
+ if (response) {
// 转换API返回的数据结构为组件所需的ContactData结构
- const contactList: ContactData[] = response.data.map((item: any) => ({
+ const contactList: ContactData[] = response.map((item: any) => ({
id: item.id.toString(),
name: item.nickname || item.conRemark || item.alias || "",
phone: item.phone || "",
@@ -63,41 +82,83 @@ const CkboxPage: React.FC = () => {
const fetchChatSessions = async () => {
try {
- // 模拟聊天会话数据
- const sessions: ChatSession[] = [
- {
- id: "1",
- type: "private",
- name: "张三",
- avatar: "",
- lastMessage: "你好,请问有什么可以帮助您的吗?",
- lastTime: dayjs().subtract(5, "minute").toISOString(),
- unreadCount: 2,
- online: true,
- },
- {
- id: "2",
- type: "group",
- name: "技术支持群",
- avatar: "",
- lastMessage: "新版本已经发布,请大家及时更新",
- lastTime: dayjs().subtract(1, "hour").toISOString(),
- unreadCount: 0,
- online: false,
- },
- {
- id: "3",
- type: "private",
- name: "李四",
- avatar: "",
- lastMessage: "谢谢您的帮助!",
- lastTime: dayjs().subtract(2, "hour").toISOString(),
- unreadCount: 0,
- online: false,
- },
- ];
- setChatSessions(sessions);
+ // 先确保联系人列表已加载
+ if (contacts.length === 0) {
+ await fetchContacts();
+ }
+
+ const response = await getChatMessage({
+ wechatAccountId: 32686452, // 使用实际的wechatAccountId
+ wechatFriendId: 0, // 可以设置为0获取所有好友的消息
+ From: 0,
+ To: 0,
+ Count: 50, // 获取最近的50条消息
+ olderData: false,
+ keyword: "",
+ });
+
+ console.log("聊天消息响应:", response);
+
+ if (response && Array.isArray(response)) {
+ // 创建一个Map来存储每个好友ID对应的最新消息
+ const friendMessageMap = new Map();
+
+ // 遍历所有消息,只保留每个好友的最新消息
+ response.forEach(item => {
+ const friendId = item.wechatFriendId;
+ const existingMessage = friendMessageMap.get(friendId);
+
+ // 如果Map中没有这个好友的消息,或者当前消息比已存在的更新,则更新Map
+ if (
+ !existingMessage ||
+ new Date(item.createTime) > new Date(existingMessage.createTime)
+ ) {
+ friendMessageMap.set(friendId, item);
+ }
+ });
+
+ // 将Map转换为数组
+ const latestMessages = Array.from(friendMessageMap.values());
+
+ // 将API返回的消息记录转换为ChatSession格式
+ const sessions: ChatSession[] = latestMessages.map(item => {
+ // 解析content字段,它是一个JSON字符串
+ let msgContent = "";
+ try {
+ const contentObj = JSON.parse(item.content);
+ msgContent = contentObj.content || "";
+ } catch (e) {
+ msgContent = item.content;
+ }
+
+ // 尝试从联系人列表中找到对应的联系人信息
+ const contact = contacts.find(
+ c => c.id === item.wechatFriendId.toString(),
+ );
+
+ return {
+ id: item.id.toString(),
+ type: "private", // 假设都是私聊
+ name: contact ? contact.name : `联系人 ${item.wechatFriendId}`, // 使用联系人名称或默认名称
+ avatar: contact?.avatar || "", // 使用联系人头像或默认空字符串
+ lastMessage: msgContent,
+ lastTime:
+ item.createTime || new Date(item.wechatTime).toISOString(),
+ unreadCount: 0, // 未读消息数需要另外获取
+ online: contact?.online || false, // 使用联系人在线状态或默认为false
+ };
+ });
+
+ // 按最后消息时间排序
+ sessions.sort(
+ (a, b) =>
+ new Date(b.lastTime).getTime() - new Date(a.lastTime).getTime(),
+ );
+
+ setChatSessions(sessions);
+ }
} catch (error) {
+ console.error("获取聊天记录失败:", error);
messageApi.error("获取聊天记录失败");
}
};
@@ -154,93 +215,18 @@ const CkboxPage: React.FC = () => {
}
};
- const handleSearch = (value: string) => {
- setSearchText(value);
- };
-
- const getFilteredContacts = () => {
- if (!searchText) return contacts;
- return contacts.filter(
- contact =>
- contact.name.toLowerCase().includes(searchText.toLowerCase()) ||
- contact.phone.includes(searchText),
- );
- };
-
- const getFilteredSessions = () => {
- if (!searchText) return chatSessions;
- return chatSessions.filter(session =>
- session.name.toLowerCase().includes(searchText.toLowerCase()),
- );
- };
-
return (
{contextHolder}
{/* 左侧边栏 */}
-
- {/* 搜索栏 */}
-
- }
- value={searchText}
- onChange={e => handleSearch(e.target.value)}
- allowClear
- />
-
-
- {/* 标签页 */}
-
-
-
- 聊天
-
- }
- key="chats"
- >
-
-
-
-
- 联系人
-
- }
- key="contacts"
- >
-
-
-
-
- 群组
-
- }
- key="groups"
- >
-
-
-
-
+
{/* 主内容区 */}
diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts
new file mode 100644
index 00000000..da431b2f
--- /dev/null
+++ b/Cunkebao/src/pages/pc/ckbox/main.ts
@@ -0,0 +1,7 @@
+import { useCkChatStore } from "@/store/module/ckchat";
+import { useWebSocketStore } from "@/store/module/websocket";
+const { connect } = useWebSocketStore.getState();
+//获取微信账户组
+export const getWechatAccountGroup = () => {
+ console.log(connect);
+};
From cfb752509a55c300ece9dfad21cc357671bffa70 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, 21 Aug 2025 16:47:55 +0800
Subject: [PATCH 22/78] =?UTF-8?q?refactor(PoolSelection):=20=E9=87=8D?=
=?UTF-8?q?=E5=91=BD=E5=90=8D=E7=BB=84=E4=BB=B6=E5=8F=8A=E7=9B=B8=E5=85=B3?=
=?UTF-8?q?=E6=8E=A5=E5=8F=A3=E4=BB=8EGroup=E5=88=B0Pool?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
将组件及相关接口从GroupSelection重命名为PoolSelection以更准确反映其用途
更新相关引用和类型定义
调整流量池选择弹窗的样式和交互
---
Cunkebao/src/components/PoolSelection/data.ts | 12 +++----
.../PoolSelection/index.module.scss | 2 +-
.../src/components/PoolSelection/index.tsx | 5 ++-
.../PoolSelection/selectionPopup.tsx | 10 +++---
Cunkebao/src/pages/mobile/test/select.tsx | 28 +++++++++++++++-
.../form/components/ContentSelector.tsx | 33 ++++++++++++-------
.../workspace/auto-group/form/index.tsx | 13 ++++----
7 files changed, 69 insertions(+), 34 deletions(-)
diff --git a/Cunkebao/src/components/PoolSelection/data.ts b/Cunkebao/src/components/PoolSelection/data.ts
index e3aac263..95ff55d4 100644
--- a/Cunkebao/src/components/PoolSelection/data.ts
+++ b/Cunkebao/src/components/PoolSelection/data.ts
@@ -28,7 +28,7 @@ export interface PoolItem {
tags: any[];
}
-export interface GroupSelectionItem {
+export interface PoolSelectionItem {
id: string;
avatar?: string;
name: string;
@@ -42,10 +42,10 @@ export interface GroupSelectionItem {
}
// 组件属性接口
-export interface GroupSelectionProps {
- selectedOptions: GroupSelectionItem[];
- onSelect: (groups: GroupSelectionItem[]) => void;
- onSelectDetail?: (groups: PoolPackageItem[]) => void;
+export interface PoolSelectionProps {
+ selectedOptions: PoolSelectionItem[];
+ onSelect: (Pools: PoolSelectionItem[]) => void;
+ onSelectDetail?: (Pools: PoolPackageItem[]) => void;
placeholder?: string;
className?: string;
visible?: boolean;
@@ -56,6 +56,6 @@ export interface GroupSelectionProps {
readonly?: boolean;
onConfirm?: (
selectedIds: string[],
- selectedItems: GroupSelectionItem[],
+ selectedItems: PoolSelectionItem[],
) => void;
}
diff --git a/Cunkebao/src/components/PoolSelection/index.module.scss b/Cunkebao/src/components/PoolSelection/index.module.scss
index bedba3ef..f0421ca1 100644
--- a/Cunkebao/src/components/PoolSelection/index.module.scss
+++ b/Cunkebao/src/components/PoolSelection/index.module.scss
@@ -106,7 +106,7 @@
.groupAvatar {
width: 40px;
height: 40px;
- border-radius: 50%;
+ border-radius: 8px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
diff --git a/Cunkebao/src/components/PoolSelection/index.tsx b/Cunkebao/src/components/PoolSelection/index.tsx
index 184fd1e8..5118bacd 100644
--- a/Cunkebao/src/components/PoolSelection/index.tsx
+++ b/Cunkebao/src/components/PoolSelection/index.tsx
@@ -1,10 +1,9 @@
import React, { useState } from "react";
import { SearchOutlined, DeleteOutlined } from "@ant-design/icons";
import { Button, Input } from "antd";
-import { Avatar } from "antd-mobile";
import style from "./index.module.scss";
import SelectionPopup from "./selectionPopup";
-import { GroupSelectionProps } from "./data";
+import { PoolSelectionProps } from "./data";
export default function PoolSelection({
selectedOptions,
onSelect,
@@ -18,7 +17,7 @@ export default function PoolSelection({
showSelectedList = true,
readonly = false,
onConfirm,
-}: GroupSelectionProps) {
+}: PoolSelectionProps) {
const [popupVisible, setPopupVisible] = useState(false);
// 删除已选流量池项
diff --git a/Cunkebao/src/components/PoolSelection/selectionPopup.tsx b/Cunkebao/src/components/PoolSelection/selectionPopup.tsx
index 3d681ac3..d952efc8 100644
--- a/Cunkebao/src/components/PoolSelection/selectionPopup.tsx
+++ b/Cunkebao/src/components/PoolSelection/selectionPopup.tsx
@@ -6,19 +6,19 @@ import style from "./index.module.scss";
import Layout from "@/components/Layout/Layout";
import PopupHeader from "@/components/PopuLayout/header";
import PopupFooter from "@/components/PopuLayout/footer";
-import { GroupSelectionItem, PoolPackageItem } from "./data";
+import { PoolSelectionItem, PoolPackageItem } from "./data";
// 弹窗属性接口
interface SelectionPopupProps {
visible: boolean;
onVisibleChange: (visible: boolean) => void;
- selectedOptions: GroupSelectionItem[];
- onSelect: (items: GroupSelectionItem[]) => void;
+ selectedOptions: PoolSelectionItem[];
+ onSelect: (items: PoolSelectionItem[]) => void;
onSelectDetail?: (items: PoolPackageItem[]) => void;
readonly?: boolean;
onConfirm?: (
selectedIds: string[],
- selectedItems: GroupSelectionItem[],
+ selectedItems: PoolSelectionItem[],
) => void;
}
@@ -66,7 +66,7 @@ export default function SelectionPopup({
if (readonly) return;
// 将PoolPackageItem转换为GroupSelectionItem格式
- const selectionItem: GroupSelectionItem = {
+ const selectionItem: PoolSelectionItem = {
id: String(item.id),
name: item.name,
description: item.description,
diff --git a/Cunkebao/src/pages/mobile/test/select.tsx b/Cunkebao/src/pages/mobile/test/select.tsx
index a24e53ac..f7c1edd7 100644
--- a/Cunkebao/src/pages/mobile/test/select.tsx
+++ b/Cunkebao/src/pages/mobile/test/select.tsx
@@ -14,7 +14,7 @@ import { ContentItem } from "@/components/ContentSelection/data";
import { FriendSelectionItem } from "@/components/FriendSelection/data";
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
import { AccountItem } from "@/components/AccountSelection/data";
-import { GroupSelectionItem as PoolSelectionItem } from "@/components/PoolSelection/data";
+import { PoolSelectionItem } from "@/components/PoolSelection/data";
const ComponentTest: React.FC = () => {
const [activeTab, setActiveTab] = useState("pools");
@@ -161,6 +161,32 @@ const ComponentTest: React.FC = () => {
+
+
+
GroupSelection 组件测试
+
+
+ 已选群组: {selectedGroups.length} 个
+
+ 群组ID:{" "}
+ {selectedGroups.map(g => g.id).join(", ") || "无"}
+
+
+
+
PoolSelection 组件测试
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/ContentSelector.tsx b/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/ContentSelector.tsx
index a4cc905f..b045ae4b 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/ContentSelector.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/ContentSelector.tsx
@@ -1,13 +1,16 @@
import React, { useImperativeHandle, forwardRef } from "react";
import { Form, Card } from "antd";
-import ContentSelection from "@/components/ContentSelection";
-import { ContentItem } from "@/components/ContentSelection/data";
+import PoolSelection from "@/components/PoolSelection";
+import {
+ PoolSelectionItem,
+ PoolPackageItem,
+} from "@/components/PoolSelection/data";
interface ContentSelectorProps {
- selectedContent: ContentItem[];
+ selectedContent: PoolSelectionItem[];
onNext: (data: {
contentGroups: string[];
- contentGroupsOptions: ContentItem[];
+ contentGroupsOptions: PoolSelectionItem[];
}) => void;
}
@@ -37,7 +40,7 @@ const ContentSelector = forwardRef
(
}));
// 处理选择变化
- const handleContentChange = (contentGroupsOptions: ContentItem[]) => {
+ const handleContentChange = (contentGroupsOptions: PoolSelectionItem[]) => {
const contentGroups = contentGroupsOptions.map(c => c.id.toString());
form.setFieldValue("contentGroups", contentGroups);
onNext({
@@ -46,6 +49,11 @@ const ContentSelector = forwardRef(
});
};
+ // 处理详细选择数据
+ const handleSelectDetail = (poolPackages: PoolPackageItem[]) => {
+ // 如果需要处理原始流量池包数据,可以在这里添加逻辑
+ };
+
return (
@@ -58,25 +66,26 @@ const ContentSelector = forwardRef(
>
- 选择内容库
+ 选择流量池包
- 请选择要用于建群的内容库
+ 请选择要用于建群的流量池包
- {
DeviceSelectionItem[]
>([]);
const [contentGroupsOptions, setContentGroupsOptions] = useState<
- ContentItem[]
+ PoolSelectionItem[]
>([]);
// 创建子组件的ref
@@ -104,10 +105,10 @@ const AutoGroupForm: React.FC = () => {
setDeviceGroupsOptions(data.deveiceGroupsOptions);
};
- // 内容库选择
+ // 流量池包选择
const handleContentChange = (data: {
contentGroups: string[];
- contentGroupsOptions: ContentItem[];
+ contentGroupsOptions: PoolSelectionItem[];
}) => {
setFormData(prev => ({ ...prev, contentGroups: data.contentGroups }));
setContentGroupsOptions(data.contentGroupsOptions);
@@ -123,7 +124,7 @@ const AutoGroupForm: React.FC = () => {
return;
}
if (formData.contentGroups.length === 0) {
- Toast.show({ content: "请选择至少一个内容库" });
+ Toast.show({ content: "请选择至少一个流量池包" });
return;
}
From eadb7a8f3e95d550e6cc39ef96eb3dfa187060c3 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, 21 Aug 2025 16:58:12 +0800
Subject: [PATCH 23/78] =?UTF-8?q?refactor(auto-group):=20=E5=B0=86?=
=?UTF-8?q?=E5=86=85=E5=AE=B9=E5=BA=93=E9=80=89=E6=8B=A9=E5=99=A8=E6=9B=BF?=
=?UTF-8?q?=E6=8D=A2=E4=B8=BA=E6=B5=81=E9=87=8F=E6=B1=A0=E9=80=89=E6=8B=A9?=
=?UTF-8?q?=E5=99=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
将原有的内容库选择组件(ContentSelector)替换为流量池选择组件(PoolSelector),并更新相关类型定义和表单逻辑
---
.../{ContentSelector.tsx => PoolSelector.tsx} | 44 ++++++++-----------
.../workspace/auto-group/form/index.tsx | 41 ++++++++---------
.../mobile/workspace/auto-group/form/types.ts | 8 ++--
3 files changed, 42 insertions(+), 51 deletions(-)
rename Cunkebao/src/pages/mobile/workspace/auto-group/form/components/{ContentSelector.tsx => PoolSelector.tsx} (62%)
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/ContentSelector.tsx b/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/PoolSelector.tsx
similarity index 62%
rename from Cunkebao/src/pages/mobile/workspace/auto-group/form/components/ContentSelector.tsx
rename to Cunkebao/src/pages/mobile/workspace/auto-group/form/components/PoolSelector.tsx
index b045ae4b..33c164a8 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/ContentSelector.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/PoolSelector.tsx
@@ -6,21 +6,21 @@ import {
PoolPackageItem,
} from "@/components/PoolSelection/data";
-interface ContentSelectorProps {
- selectedContent: PoolSelectionItem[];
+interface PoolSelectorProps {
+ selectedPools: PoolSelectionItem[];
onNext: (data: {
- contentGroups: string[];
- contentGroupsOptions: PoolSelectionItem[];
+ poolGroups: string[];
+ poolGroupsOptions: PoolSelectionItem[];
}) => void;
}
-export interface ContentSelectorRef {
+export interface PoolSelectorRef {
validate: () => Promise;
getValues: () => any;
}
-const ContentSelector = forwardRef(
- ({ selectedContent, onNext }, ref) => {
+const PoolSelector = forwardRef(
+ ({ selectedPools, onNext }, ref) => {
const [form] = Form.useForm();
// 暴露方法给父组件
@@ -30,7 +30,7 @@ const ContentSelector = forwardRef(
await form.validateFields();
return true;
} catch (error) {
- console.log("ContentSelector 表单验证失败:", error);
+ console.log("PoolSelector 表单验证失败:", error);
return false;
}
},
@@ -40,12 +40,12 @@ const ContentSelector = forwardRef(
}));
// 处理选择变化
- const handleContentChange = (contentGroupsOptions: PoolSelectionItem[]) => {
- const contentGroups = contentGroupsOptions.map(c => c.id.toString());
- form.setFieldValue("contentGroups", contentGroups);
+ const handlePoolChange = (poolGroupsOptions: PoolSelectionItem[]) => {
+ const poolGroups = poolGroupsOptions.map(c => c.id.toString());
+ form.setFieldValue("poolGroups", poolGroups);
onNext({
- contentGroups,
- contentGroupsOptions,
+ poolGroups,
+ poolGroupsOptions,
});
};
@@ -61,7 +61,7 @@ const ContentSelector = forwardRef(
form={form}
layout="vertical"
initialValues={{
- contentGroups: selectedContent.map(c => c.id.toString()),
+ poolGroups: selectedPools.map(c => c.id.toString()),
}}
>
@@ -74,7 +74,7 @@ const ContentSelector = forwardRef(
(
]}
>
@@ -99,6 +93,6 @@ const ContentSelector = forwardRef(
},
);
-ContentSelector.displayName = "ContentSelector";
+PoolSelector.displayName = "PoolSelector";
-export default ContentSelector;
+export default PoolSelector;
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/form/index.tsx b/Cunkebao/src/pages/mobile/workspace/auto-group/form/index.tsx
index 19f11b63..ea0a7897 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-group/form/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/auto-group/form/index.tsx
@@ -8,14 +8,11 @@ import { AutoGroupFormData, StepItem } from "./types";
import StepIndicator from "@/components/StepIndicator";
import BasicSettings, { BasicSettingsRef } from "./components/BasicSettings";
import DeviceSelector, { DeviceSelectorRef } from "./components/DeviceSelector";
-import ContentSelector, {
- ContentSelectorRef,
-} from "./components/ContentSelector";
+import PoolSelector, { PoolSelectorRef } from "./components/PoolSelector";
import NavCommon from "@/components/NavCommon/index";
import dayjs from "dayjs";
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
import { PoolSelectionItem } from "@/components/PoolSelection/data";
-import { GroupSelectionItem } from "../../../../../components/GroupSelection/data";
const steps: StepItem[] = [
{ id: 1, title: "步骤 1", subtitle: "基础设置" },
@@ -28,8 +25,8 @@ const defaultForm: AutoGroupFormData = {
type: 4,
deveiceGroups: [], // 设备组
deveiceGroupsOptions: [], // 设备组选项
- contentGroups: [], // 内容库
- contentGroupsOptions: [], // 内容库选项
+ poolGroups: [], // 内容库
+ poolGroupsOptions: [], // 内容库选项
startTime: dayjs().format("HH:mm"), // 开始时间 (HH:mm)
endTime: dayjs().add(1, "hour").format("HH:mm"), // 结束时间 (HH:mm)
groupSizeMin: 20, // 群组最小人数
@@ -51,14 +48,14 @@ const AutoGroupForm: React.FC = () => {
const [deviceGroupsOptions, setDeviceGroupsOptions] = useState<
DeviceSelectionItem[]
>([]);
- const [contentGroupsOptions, setContentGroupsOptions] = useState<
+ const [poolGroupsOptions, setpoolGroupsOptions] = useState<
PoolSelectionItem[]
>([]);
// 创建子组件的ref
const basicSettingsRef = useRef(null);
const deviceSelectorRef = useRef(null);
- const contentSelectorRef = useRef(null);
+ const poolSelectorRef = useRef(null);
useEffect(() => {
if (!id) return;
@@ -69,8 +66,8 @@ const AutoGroupForm: React.FC = () => {
name: res.name,
deveiceGroups: res.config.deveiceGroups || [],
deveiceGroupsOptions: res.config.deveiceGroupsOptions || [],
- contentGroups: res.config.contentGroups || [],
- contentGroupsOptions: res.config.contentGroupsOptions || [],
+ poolGroups: res.config.poolGroups || [],
+ poolGroupsOptions: res.config.poolGroupsOptions || [],
startTime: res.config.startTime,
endTime: res.config.endTime,
groupSizeMin: res.config.groupSizeMin,
@@ -84,7 +81,7 @@ const AutoGroupForm: React.FC = () => {
};
setFormData(updatedForm);
setDeviceGroupsOptions(res.config.deveiceGroupsOptions || []);
- setContentGroupsOptions(res.config.contentGroupsOptions || []);
+ setpoolGroupsOptions(res.config.poolGroupsOptions || []);
setDataLoaded(true); // 标记数据已加载
});
}, [id]);
@@ -106,12 +103,12 @@ const AutoGroupForm: React.FC = () => {
};
// 流量池包选择
- const handleContentChange = (data: {
- contentGroups: string[];
- contentGroupsOptions: PoolSelectionItem[];
+ const handlePoolsChange = (data: {
+ poolGroups: string[];
+ poolGroupsOptions: PoolSelectionItem[];
}) => {
- setFormData(prev => ({ ...prev, contentGroups: data.contentGroups }));
- setContentGroupsOptions(data.contentGroupsOptions);
+ setFormData(prev => ({ ...prev, poolGroups: data.poolGroups }));
+ setpoolGroupsOptions(data.poolGroupsOptions);
};
const handleSave = async () => {
@@ -123,7 +120,7 @@ const AutoGroupForm: React.FC = () => {
Toast.show({ content: "请选择至少一个设备组" });
return;
}
- if (formData.contentGroups.length === 0) {
+ if (formData.poolGroups.length === 0) {
Toast.show({ content: "请选择至少一个流量池包" });
return;
}
@@ -133,7 +130,7 @@ const AutoGroupForm: React.FC = () => {
const submitData = {
...formData,
deveiceGroupsOptions: deviceGroupsOptions,
- contentGroupsOptions: contentGroupsOptions,
+ poolGroupsOptions: poolGroupsOptions,
};
if (isEdit) {
@@ -228,10 +225,10 @@ const AutoGroupForm: React.FC = () => {
);
case 3:
return (
-
);
default:
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/form/types.ts b/Cunkebao/src/pages/mobile/workspace/auto-group/form/types.ts
index f3754955..83ad789d 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-group/form/types.ts
+++ b/Cunkebao/src/pages/mobile/workspace/auto-group/form/types.ts
@@ -1,5 +1,5 @@
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
-import { ContentItem } from "@/components/ContentSelection/data";
+import { PoolSelectionItem } from "@/components/PoolSelection/data";
// 自动建群表单数据类型定义
export interface AutoGroupFormData {
@@ -8,8 +8,8 @@ export interface AutoGroupFormData {
name: string; // 任务名称
deveiceGroups: string[]; // 设备组
deveiceGroupsOptions: DeviceSelectionItem[]; // 设备组选项
- contentGroups: string[]; // 内容库
- contentGroupsOptions: ContentItem[]; // 内容库选项
+ poolGroups: string[]; // 流量池
+ poolGroupsOptions: PoolSelectionItem[]; // 流量池选项
startTime: string; // 开始时间 (YYYY-MM-DD HH:mm:ss)
endTime: string; // 结束时间 (YYYY-MM-DD HH:mm:ss)
groupSizeMin: number; // 群组最小人数
@@ -38,7 +38,7 @@ export const formValidationRules = {
{ required: true, message: "请选择设备组" },
{ type: "array", min: 1, message: "至少选择一个设备组" },
],
- contentGroups: [
+ poolGroups: [
{ required: true, message: "请选择内容库" },
{ type: "array", min: 1, message: "至少选择一个内容库" },
],
From d0bd7d4cd7c216b8ca6eb71aebd205dc8ee84709 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, 21 Aug 2025 17:44:47 +0800
Subject: [PATCH 24/78] =?UTF-8?q?feat(websocket):=20=E9=87=8D=E6=9E=84WebS?=
=?UTF-8?q?ocket=E8=BF=9E=E6=8E=A5=E9=80=BB=E8=BE=91=E5=B9=B6=E6=B7=BB?=
=?UTF-8?q?=E5=8A=A0=E5=88=9D=E5=A7=8B=E5=8C=96=E5=91=BD=E4=BB=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 重构WebSocket连接逻辑,使用新的URL参数构建方式
- 添加初始化连接时的CmdSignIn和CmdRequestWechatAccountsAliveStatus命令
- 修改WebSocket消息类型定义,简化消息结构
- 从CkChatStore获取accountId用于连接参数
- 移除Login页面中与WebSocket相关的冗余代码
---
Cunkebao/src/pages/login/Login.tsx | 48 +--------
Cunkebao/src/pages/pc/ckbox/api.ts | 25 ++++-
.../pc/ckbox/components/SidebarMenu/index.tsx | 97 ++++++++++---------
Cunkebao/src/pages/pc/ckbox/index.tsx | 18 ++--
Cunkebao/src/pages/pc/ckbox/main.ts | 62 +++++++++++-
Cunkebao/src/store/module/ckchat.ts | 2 +-
Cunkebao/src/store/module/user.ts | 1 -
Cunkebao/src/store/module/websocket.ts | 74 +++++++-------
8 files changed, 179 insertions(+), 148 deletions(-)
diff --git a/Cunkebao/src/pages/login/Login.tsx b/Cunkebao/src/pages/login/Login.tsx
index 197c410d..7cf8832c 100644
--- a/Cunkebao/src/pages/login/Login.tsx
+++ b/Cunkebao/src/pages/login/Login.tsx
@@ -8,14 +8,8 @@ import {
} from "antd-mobile-icons";
import { useUserStore } from "@/store/module/user";
import { useCkChatStore } from "@/store/module/ckchat";
-import { useWebSocketStore } from "@/store/module/websocket";
-import {
- loginWithPassword,
- loginWithCode,
- sendVerificationCode,
- loginWithToken,
- getChuKeBaoUserInfo,
-} from "./api";
+
+import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api";
import style from "./login.module.scss";
const Login: React.FC = () => {
@@ -26,8 +20,7 @@ const Login: React.FC = () => {
const [showPassword, setShowPassword] = useState(false);
const [agreeToTerms, setAgreeToTerms] = useState(false);
- const { login, login2 } = useUserStore();
- const { setUserInfo, getAccountId } = useCkChatStore();
+ const { login } = useUserStore();
// 倒计时效果
useEffect(() => {
@@ -97,24 +90,6 @@ const Login: React.FC = () => {
});
};
- const getToken2 = () => {
- return new Promise((resolve, reject) => {
- const params = {
- grant_type: "password",
- password: "kr123456",
- username: "kr_xf3",
- };
- loginWithToken(params)
- .then(res => {
- login2(res.access_token);
- resolve(res.access_token);
- })
- .catch(err => {
- reject(err);
- });
- });
- };
-
// 登录处理
const handleLogin = async (values: any) => {
if (!agreeToTerms) {
@@ -127,23 +102,6 @@ const Login: React.FC = () => {
.finally(() => {
setLoading(false);
});
-
- //获取触客宝
- getToken2().then((Token: string) => {
- getChuKeBaoUserInfo().then(res => {
- setUserInfo(res);
- // 使用WebSocket store连接
- const { connect } = useWebSocketStore.getState();
- connect({
- accessToken: Token,
- accountId: getAccountId()?.toString() || "",
- client: "kefu-client",
- autoReconnect: true,
- reconnectInterval: 3000,
- maxReconnectAttempts: 5,
- });
- });
- });
};
// 第三方登录处理
diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts
index ef1da61a..c79fe353 100644
--- a/Cunkebao/src/pages/pc/ckbox/api.ts
+++ b/Cunkebao/src/pages/pc/ckbox/api.ts
@@ -14,16 +14,31 @@ import {
ChatSettings,
} from "./data";
+//触客宝登陆
+export function loginWithToken(params: any) {
+ return request(
+ "/token",
+ params,
+ "POST",
+ {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ },
+ 1000,
+ );
+}
+
+// 获取触客宝用户信息
+export function getChuKeBaoUserInfo() {
+ return request("/api/account/self", {}, "GET");
+}
+
// 获取联系人列表
export const getContactList = (params: { prevId: number; count: number }) => {
return request("/api/wechatFriend/list", params, "GET");
};
-// 获取聊天会话列表
-export const getChatSessions = (): Promise => {
- return request("/v1/chats/sessions", {}, "GET");
-};
-
// 搜索联系人
export const getChatMessage = (params: {
wechatAccountId: number;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
index 85b99318..e4da3996 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
@@ -12,7 +12,6 @@ import MessageList from "../MessageList/index";
import styles from "./SidebarMenu.module.scss";
const { Sider } = Layout;
-const { TabPane } = Tabs;
interface SidebarMenuProps {
contacts: ContactData[];
@@ -72,52 +71,56 @@ const SidebarMenu: React.FC = ({
activeKey={activeTab}
onChange={setActiveTab}
className={styles.tabs}
- >
-
-
- 聊天
-
- }
- key="chats"
- >
-
-
-
-
- 联系人
-
- }
- key="contacts"
- >
-
-
-
-
- 群组
-
- }
- key="groups"
- >
-
-
-
+ items={[
+ {
+ key: "chats",
+ label: (
+
+
+ 聊天
+
+ ),
+ children: (
+
+ ),
+ },
+ {
+ key: "contacts",
+ label: (
+
+
+ 联系人
+
+ ),
+ children: (
+
+ ),
+ },
+ {
+ key: "groups",
+ label: (
+
+
+ 群组
+
+ ),
+ children: (
+
+ ),
+ },
+ ]}
+ />
);
};
diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx
index a70737e2..58533e02 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/index.tsx
@@ -12,9 +12,12 @@ const { Content } = Layout;
import { loginWithToken, getChuKeBaoUserInfo } from "@/pages/login/api";
import { useCkChatStore } from "@/store/module/ckchat";
import { useUserStore } from "@/store/module/user";
+import { useWebSocketStore } from "@/store/module/websocket";
+import { chatInitAPIdata, getChatInfo } from "./main";
+
const CkboxPage: React.FC = () => {
const [messageApi, contextHolder] = message.useMessage();
- const [contacts, setContacts] = useState([]);
+ const [contacts, setContacts] = useState([]);
const [chatSessions, setChatSessions] = useState([]);
const [currentChat, setCurrentChat] = useState(null);
const [loading, setLoading] = useState(false);
@@ -23,16 +26,8 @@ const CkboxPage: React.FC = () => {
const { login2 } = useUserStore();
useEffect(() => {
- //获取触客宝
- getToken2().then(() => {
- getChuKeBaoUserInfo().then(res => {
- setUserInfo(res);
- setTimeout(() => {
- fetchContacts();
- fetchChatSessions();
- });
- });
- });
+ const contactList = chatInitAPIdata();
+ console.log(contactList);
}, []);
const getToken2 = () => {
@@ -217,6 +212,7 @@ const CkboxPage: React.FC = () => {
return (
+ {/* */}
{contextHolder}
{/* 左侧边栏 */}
{
- console.log(connect);
+const { setUserInfo, getAccountId } = useCkChatStore.getState();
+//获取触客宝基础信息
+export const chatInitAPIdata = async () => {
+ //获取Token
+ const Token = await getToken();
+ //获取用户信息
+ const userInfo = await getChuKeBaoUserInfo();
+ setUserInfo(userInfo);
+
+ //获取用户账号Id
+ const accountId = getAccountId();
+
+ //发起链接
+ connect({
+ accessToken: String(Token),
+ accountId: accountId,
+ client: "kefu-client",
+ cmdType: "CmdSignIn",
+ seq: 1,
+ });
+ //获取联系人列表
+ const contactList = await getContactList({
+ prevId: userInfo.tenant.tenantType,
+ count: 100,
+ });
+
+ return contactList;
+};
+
+export const getChatInfo = () => {
+ //获取UserId
+ sendCommand("CmdRequestWechatAccountsAliveStatus", {
+ wechatAccountIds: ["300745", "4880930", "32686452"],
+ seq: 2,
+ });
+ console.log("发送链接信息");
+};
+//获取token
+const getToken = () => {
+ return new Promise((resolve, reject) => {
+ const params = {
+ grant_type: "password",
+ password: "kr123456",
+ username: "kr_xf3",
+ };
+ loginWithToken(params)
+ .then(res => {
+ login2(res.access_token);
+ resolve(res.access_token);
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
};
diff --git a/Cunkebao/src/store/module/ckchat.ts b/Cunkebao/src/store/module/ckchat.ts
index 7d4a9337..fccaf1ec 100644
--- a/Cunkebao/src/store/module/ckchat.ts
+++ b/Cunkebao/src/store/module/ckchat.ts
@@ -44,7 +44,7 @@ export const useCkChatStore = createPersistStore(
// 获取账户ID
getAccountId: () => {
const state = useCkChatStore.getState();
- return state.userInfo?.account?.id || null;
+ return Number(state.userInfo?.account?.id) || null;
},
// 获取租户ID
diff --git a/Cunkebao/src/store/module/user.ts b/Cunkebao/src/store/module/user.ts
index 070978c1..cf9b897e 100644
--- a/Cunkebao/src/store/module/user.ts
+++ b/Cunkebao/src/store/module/user.ts
@@ -80,7 +80,6 @@ export const useUserStore = createPersistStore(
},
login2: token2 => {
localStorage.setItem("token2", token2);
- console.log(token2);
set({ token2, isLoggedIn: true });
},
diff --git a/Cunkebao/src/store/module/websocket.ts b/Cunkebao/src/store/module/websocket.ts
index 910fc7a7..a000f1a3 100644
--- a/Cunkebao/src/store/module/websocket.ts
+++ b/Cunkebao/src/store/module/websocket.ts
@@ -1,15 +1,15 @@
import { createPersistStore } from "@/store/createPersistStore";
import { Toast } from "antd-mobile";
import { useUserStore } from "./user";
+import { useCkChatStore } from "@/store/module/ckchat";
+const { getAccountId } = useCkChatStore.getState();
// WebSocket消息类型
export interface WebSocketMessage {
- id: string;
- type: string;
- content: any;
- timestamp: number;
- sender?: string;
- receiver?: string;
+ cmdType?: string;
+ seq?: number;
+ wechatAccountIds?: string[];
+ [key: string]: any;
}
// WebSocket连接状态
@@ -25,9 +25,11 @@ export enum WebSocketStatus {
interface WebSocketConfig {
url: string;
client: string;
- accountId: string;
+ accountId: number;
accessToken: string;
autoReconnect: boolean;
+ cmdType: string;
+ seq: number;
reconnectInterval: number;
maxReconnectAttempts: number;
}
@@ -68,11 +70,13 @@ interface WebSocketState {
// 默认配置
const DEFAULT_CONFIG: WebSocketConfig = {
- url: (import.meta as any).env?.VITE_API_WS_URL || "ws://localhost:8080",
+ url: (import.meta as any).env?.VITE_API_WS_URL,
client: "kefu-client",
- accountId: "",
+ accountId: 0,
accessToken: "",
autoReconnect: true,
+ cmdType: "", // 添加默认的命令类型
+ seq: 0, // 添加默认的序列号
reconnectInterval: 3000,
maxReconnectAttempts: 5,
};
@@ -103,24 +107,24 @@ export const useWebSocketStore = createPersistStore(
};
// 获取用户信息
- const { token, token2, user } = useUserStore.getState();
- const accessToken = fullConfig.accessToken || token2 || token;
+ const { token2 } = useUserStore.getState();
- if (!accessToken) {
+ if (!token2) {
Toast.show({ content: "未找到有效的访问令牌", position: "top" });
return;
}
- // 构建WebSocket URL
- const params = {
- client: fullConfig.client,
- accountId: fullConfig.accountId || user?.s2_accountId || "",
- accessToken: accessToken,
- t: Date.now().toString(),
- };
+ console.log("获取connect参数", getAccountId());
- const wsUrl =
- fullConfig.url + "?" + new URLSearchParams(params).toString();
+ // 构建WebSocket URL
+ const params = new URLSearchParams({
+ client: fullConfig.client.toString(),
+ accountId: getAccountId().toString(),
+ accessToken: token2,
+ t: Date.now().toString(),
+ });
+
+ const wsUrl = fullConfig.url + "?" + params;
set({
status: WebSocketStatus.CONNECTING,
@@ -166,7 +170,7 @@ export const useWebSocketStore = createPersistStore(
},
// 发送消息
- sendMessage: (message: Omit) => {
+ sendMessage: message => {
const currentState = get();
if (
@@ -179,8 +183,6 @@ export const useWebSocketStore = createPersistStore(
const fullMessage: WebSocketMessage = {
...message,
- id: Date.now().toString(),
- timestamp: Date.now(),
};
try {
@@ -204,16 +206,8 @@ export const useWebSocketStore = createPersistStore(
return;
}
- const { user } = useUserStore.getState();
- const { token, token2 } = useUserStore.getState();
- const accessToken = token2 || token;
-
const command = {
- accessToken: accessToken,
- accountId: user?.s2_accountId,
- client: currentState.config?.client || "kefu-client",
- cmdType: cmdType,
- seq: Date.now(),
+ cmdType,
...data,
};
@@ -255,10 +249,20 @@ export const useWebSocketStore = createPersistStore(
});
console.log("WebSocket连接成功");
-
+ const { token2 } = useUserStore.getState();
// 发送登录命令
if (currentState.config) {
- currentState.sendCommand("CmdSignIn");
+ currentState.sendCommand("CmdSignIn", {
+ accessToken: token2,
+ accountId: Number(getAccountId()),
+ client: currentState.config?.client || "kefu-client",
+ seq: currentState.config?.seq || 1,
+ });
+ //获取UserId
+ currentState.sendCommand("CmdRequestWechatAccountsAliveStatus", {
+ wechatAccountIds: ["300745", "4880930", "32686452"],
+ seq: 2,
+ });
}
Toast.show({ content: "WebSocket连接成功", position: "top" });
From d726be7d66d49fad3ab6cad1fa8e72f9545f64e7 Mon Sep 17 00:00:00 2001
From: wong <106998207@qq.com>
Date: Fri, 22 Aug 2025 10:23:05 +0800
Subject: [PATCH 25/78] =?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
---
Server/application/ai/config/route.php | 22 +++
Server/application/ai/controller/DouBaoAI.php | 53 +++++++
Server/application/ai/controller/OpenAi.php | 141 ++++++++++++++++++
.../controller/PasswordLoginController.php | 37 +++--
Server/application/common/model/User.php | 2 +-
.../cozeai/controller/BaseController.php | 4 +-
.../controller/ConversationController.php | 8 +-
Server/application/cunkebao/config/route.php | 2 +
.../controller/WorkbenchController.php | 41 ++++-
.../plan/PosterWeChatMiniProgram.php | 67 +++++++++
.../cunkebao/validate/Workbench.php | 6 +-
Server/route/route.php | 3 +
12 files changed, 363 insertions(+), 23 deletions(-)
create mode 100644 Server/application/ai/config/route.php
create mode 100644 Server/application/ai/controller/DouBaoAI.php
create mode 100644 Server/application/ai/controller/OpenAi.php
diff --git a/Server/application/ai/config/route.php b/Server/application/ai/config/route.php
new file mode 100644
index 00000000..286b7c24
--- /dev/null
+++ b/Server/application/ai/config/route.php
@@ -0,0 +1,22 @@
+middleware(['jwt']);
\ No newline at end of file
diff --git a/Server/application/ai/controller/DouBaoAI.php b/Server/application/ai/controller/DouBaoAI.php
new file mode 100644
index 00000000..6885c0ee
--- /dev/null
+++ b/Server/application/ai/controller/DouBaoAI.php
@@ -0,0 +1,53 @@
+apiUrl = Env::get('doubaoAi.api_url');
+ $this->apiKey = Env::get('doubaoAi.api_key');
+
+ // 设置请求头
+ $this->headers = [
+ 'Content-Type: application/json',
+ 'Authorization: Bearer ' . $this->apiKey
+ ];
+
+ if (empty($this->apiKey) || empty($this->apiUrl)) {
+ return json_encode(['code' => 500, 'msg' => '参数缺失']);
+ }
+ }
+
+
+ public function text()
+ {
+ $this->__init();
+
+ // 发送请求
+ $params = [
+ 'model' => 'doubao-1-5-pro-32k-250115',
+ 'messages' => [
+ ['role' => 'system', 'content' => '你是人工智能助手.'],
+ ['role' => 'user', 'content' => '厦门天气'],
+ ],
+ /*'extra_headers' => [
+ 'x-is-encrypted' => true
+ ],
+ 'temperature' => 1,
+ 'top_p' => 0.7,
+ 'max_tokens' => 4096,
+ 'frequency_penalty' => 0,*/
+ ];
+ $result = requestCurl($this->apiUrl, $params, 'POST', $this->headers, 'json');
+ $result = json_decode($result, true);
+ return successJson($result);
+ }
+}
\ No newline at end of file
diff --git a/Server/application/ai/controller/OpenAi.php b/Server/application/ai/controller/OpenAi.php
new file mode 100644
index 00000000..3c4f3db4
--- /dev/null
+++ b/Server/application/ai/controller/OpenAi.php
@@ -0,0 +1,141 @@
+apiUrl = Env::get('openAi.apiUrl');
+ $this->apiKey = Env::get('openAi.apiKey');
+
+ // 设置请求头
+ $this->headers = [
+ 'Content-Type: application/json',
+ 'Authorization: Bearer '.$this->apiKey
+ ];
+ }
+
+
+ public function text()
+ {
+ $this->__init();
+ $params = [
+ 'model' => 'gpt-3.5-turbo-0125',
+ 'input' => 'DHA 从孕期到出生到老年都需要,助力大脑发育🧠/减缓脑压力有助记忆/给大脑动力#贝蒂喜藻油DHA 双标认证每粒 150毫克,高含量、高性价比从小吃到老,长期吃更健康 重写这条朋友圈 要求: 1、原本的字数和意思不要修改超过10% 2、出现品牌名或个人名字就去除'
+ ];
+ $result = $this->httpRequest( $this->apiUrl, 'POST', $params,$this->headers);
+ exit_data($result);
+ }
+
+ /**
+ * 示例:调用OpenAI API生成睡前故事
+ * 对应curl命令:
+ * curl "https://api.ai.com/v1/responses" \
+ * -H "Content-Type: application/json" \
+ * -H "Authorization: Bearer $OPENAI_API_KEY" \
+ * -d '{
+ * "model": "gpt-5",
+ * "input": "Write a one-sentence bedtime story about a unicorn."
+ * }'
+ */
+ public function bedtimeStory()
+ {
+ $this->__init();
+
+ // API请求参数
+ $params = [
+ 'model' => 'gpt-5',
+ 'input' => 'Write a one-sentence bedtime story about a unicorn.'
+ ];
+
+ // 发送请求到OpenAI API
+ $url = 'https://api.openai.com/v1/responses';
+ $result = $this->httpRequest($url, 'POST', $params, $this->headers);
+
+ // 返回结果
+ exit_data($result);
+ }
+
+
+
+
+ /**
+ * CURL请求 - 专门用于JSON API请求
+ *
+ * @param $url 请求url地址
+ * @param $method 请求方法 get post
+ * @param null $postfields post数据数组
+ * @param array $headers 请求header信息
+ * @param int $timeout 超时时间
+ * @param bool|false $debug 调试开启 默认false
+ * @return mixed
+ */
+ protected function httpRequest($url, $method = "GET", $postfields = null, $headers = array(), $timeout = 30, $debug = false)
+ {
+ $method = strtoupper($method);
+ $ci = curl_init();
+
+ /* Curl settings */
+ curl_setopt($ci, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0");
+ curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, 60); /* 在发起连接前等待的时间,如果设置为0,则无限等待 */
+ curl_setopt($ci, CURLOPT_TIMEOUT, $timeout); /* 设置cURL允许执行的最长秒数 */
+ curl_setopt($ci, CURLOPT_RETURNTRANSFER, true);
+
+ switch ($method) {
+ case "POST":
+ curl_setopt($ci, CURLOPT_POST, true);
+ if (!empty($postfields)) {
+ // 对于JSON API,直接将数组转换为JSON字符串
+ if (is_array($postfields)) {
+ $tmpdatastr = json_encode($postfields);
+ } else {
+ $tmpdatastr = $postfields;
+ }
+ curl_setopt($ci, CURLOPT_POSTFIELDS, $tmpdatastr);
+ }
+ break;
+ default:
+ curl_setopt($ci, CURLOPT_CUSTOMREQUEST, $method); /* //设置请求方式 */
+ break;
+ }
+
+ $ssl = preg_match('/^https:\/\//i', $url) ? TRUE : FALSE;
+ curl_setopt($ci, CURLOPT_URL, $url);
+ if ($ssl) {
+ curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, FALSE); // https请求 不验证证书和hosts
+ curl_setopt($ci, CURLOPT_SSL_VERIFYHOST, FALSE); // 不从证书中检查SSL加密算法是否存在
+ }
+
+ if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off')) {
+ curl_setopt($ci, CURLOPT_FOLLOWLOCATION, 1);
+ }
+ curl_setopt($ci, CURLOPT_MAXREDIRS, 2);/*指定最多的HTTP重定向的数量,这个选项是和CURLOPT_FOLLOWLOCATION一起使用的*/
+ curl_setopt($ci, CURLOPT_HTTPHEADER, $headers);
+ curl_setopt($ci, CURLINFO_HEADER_OUT, true);
+
+ $response = curl_exec($ci);
+ $requestinfo = curl_getinfo($ci);
+ $http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);
+
+ if ($debug) {
+ echo "=====post data======\r\n";
+ var_dump($postfields);
+ echo "=====info===== \r\n";
+ print_r($requestinfo);
+ echo "=====response=====\r\n";
+ print_r($response);
+ }
+
+ curl_close($ci);
+ return $response;
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/Server/application/common/controller/PasswordLoginController.php b/Server/application/common/controller/PasswordLoginController.php
index f4d12cf0..ce55ba2d 100644
--- a/Server/application/common/controller/PasswordLoginController.php
+++ b/Server/application/common/controller/PasswordLoginController.php
@@ -57,10 +57,7 @@ class PasswordLoginController extends BaseController
throw new \Exception('用户不存在或已禁用', 403);
}
-
$password = md5($password);
-
-
if ($user->passwordMd5 !== $password) {
throw new \Exception('账号或密码错误', 403);
}
@@ -119,8 +116,11 @@ class PasswordLoginController extends BaseController
// 生成JWT令牌
$token = JwtUtil::createToken($member, 86400 * 30);
$token_expired = time() + 86400 * 30;
-
- return compact('member', 'token', 'token_expired','deviceTotal');
+ $kefuData = [
+ 'token' => [],
+ 'self' => [],
+ ];
+ return compact('member', 'token', 'token_expired','deviceTotal','kefuData');
}
/**
@@ -131,15 +131,34 @@ class PasswordLoginController extends BaseController
public function index()
{
$params = $this->request->only(['account', 'password', 'typeId']);
-
try {
- $result = $this->dataValidate($params)->doLogin(
+ $userData = $this->dataValidate($params)->doLogin(
$params['account'],
$params['password'],
$params['typeId']
);
-
- return ResponseHelper::success($result, '登录成功');
+ //同时登录客服系统
+ if (!empty($userData['member']['passwordLocal'])){
+ $params = [
+ 'grant_type' => 'password',
+ 'username' => $userData['member']['account'],
+ 'password' => localDecrypt($userData['member']['passwordLocal'])
+ ];
+ // 调用登录接口获取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());
}
diff --git a/Server/application/common/model/User.php b/Server/application/common/model/User.php
index 9117d783..9bb3450b 100644
--- a/Server/application/common/model/User.php
+++ b/Server/application/common/model/User.php
@@ -34,5 +34,5 @@ class User extends Model
* 隐藏属性
* @var array
*/
- protected $hidden = ['passwordMd5', 'passwordLocal', 'deleteTime'];
+ protected $hidden = ['passwordMd5', 'deleteTime'];
}
\ No newline at end of file
diff --git a/Server/application/cozeai/controller/BaseController.php b/Server/application/cozeai/controller/BaseController.php
index 4336ba19..20d1dc70 100644
--- a/Server/application/cozeai/controller/BaseController.php
+++ b/Server/application/cozeai/controller/BaseController.php
@@ -20,8 +20,8 @@ class BaseController extends Controller
parent::__construct();
// 从环境变量获取配置
- $this->apiUrl = Env::get('ai.api_url');
- $this->accessToken = Env::get('ai.token');
+ $this->apiUrl = Env::get('cozeAi.api_url');
+ $this->accessToken = Env::get('cozeAi.token');
// 设置请求头
$this->headers = [
diff --git a/Server/application/cozeai/controller/ConversationController.php b/Server/application/cozeai/controller/ConversationController.php
index 6a04ffe1..59b69b5f 100644
--- a/Server/application/cozeai/controller/ConversationController.php
+++ b/Server/application/cozeai/controller/ConversationController.php
@@ -101,7 +101,7 @@ class ConversationController extends BaseController
public function create($is_internal = false)
{
try {
- $bot_id = Env::get('ai.bot_id');
+ $bot_id = Env::get('cozeAi.bot_id');
$userInfo = request()->userInfo;
$uid = $userInfo['id'];
$companyId = $userInfo['companyId'];
@@ -117,7 +117,7 @@ class ConversationController extends BaseController
];
$messages[] = [
'role' => 'assistant',
- 'content' => Env::get('ai.content'),
+ 'content' => Env::get('cozeAi.content'),
'type' => 'answer',
'content_type' => 'text',
];
@@ -145,7 +145,7 @@ class ConversationController extends BaseController
'chat_id' => $conversation['id'],
'conversation_id' => $conversation['id'],
'bot_id' => $bot_id,
- 'content' => Env::get('ai.content'),
+ 'content' => Env::get('cozeAi.content'),
'content_type' => 'text',
'role' => 'assistant',
'type' => 'answer',
@@ -177,7 +177,7 @@ class ConversationController extends BaseController
public function createChat()
{
try {
- $bot_id = Env::get('ai.bot_id');
+ $bot_id = Env::get('cozeAi.bot_id');
$conversation_id = input('conversation_id','');
$question = input('question','');
diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php
index 5b93d1c0..be31504f 100644
--- a/Server/application/cunkebao/config/route.php
+++ b/Server/application/cunkebao/config/route.php
@@ -96,6 +96,7 @@ Route::group('v1/', function () {
Route::get('device-labels', 'app\cunkebao\controller\WorkbenchController@getDeviceLabels'); // 获取设备微信好友标签统计
Route::get('group-list', 'app\cunkebao\controller\WorkbenchController@getGroupList'); // 获取群列表
Route::get('account-list', 'app\cunkebao\controller\WorkbenchController@getAccountList'); // 获取账号列表
+ Route::get('transfer-friends', 'app\cunkebao\controller\WorkbenchController@getTrafficList'); // 获取账号列表
Route::get('getJdSocialMedia', 'app\cunkebao\controller\WorkbenchController@getJdSocialMedia'); // 获取京东联盟导购媒体
Route::get('getJdPromotionSite', 'app\cunkebao\controller\WorkbenchController@getJdPromotionSite'); // 获取京东联盟广告位
@@ -155,6 +156,7 @@ Route::group('v1/frontend', function () {
Route::group('business/poster', function () {
Route::post('getone', 'app\cunkebao\controller\plan\PosterWeChatMiniProgram@getPosterTaskData');
Route::post('decryptphone', 'app\cunkebao\controller\plan\PosterWeChatMiniProgram@getPhoneNumber');
+ Route::post('decryptphones', 'app\cunkebao\controller\plan\PosterWeChatMiniProgram@decryptphones');
});
});
diff --git a/Server/application/cunkebao/controller/WorkbenchController.php b/Server/application/cunkebao/controller/WorkbenchController.php
index 599efbfa..4a699e02 100644
--- a/Server/application/cunkebao/controller/WorkbenchController.php
+++ b/Server/application/cunkebao/controller/WorkbenchController.php
@@ -58,7 +58,7 @@ class WorkbenchController extends Controller
$workbench = new Workbench;
$workbench->name = $param['name'];
$workbench->type = $param['type'];
- $workbench->status = 1;
+ $workbench->status = !empty($param['status']) ? 1 : 0;
$workbench->autoStart = !empty($param['autoStart']) ? 1 : 0;
$workbench->userId = $userInfo['id'];
$workbench->companyId = $userInfo['companyId'];
@@ -131,6 +131,8 @@ class WorkbenchController extends Controller
$config->maxGroupsPerDay = $param['maxGroupsPerDay'];
$config->groupNameTemplate = $param['groupNameTemplate'];
$config->groupDescription = $param['groupDescription'];
+ $config->poolGroups = json_encode($param['poolGroups'] ?? []);
+ $config->wechatGroups = json_encode($param['wechatGroups'] ?? []);
$config->createTime = time();
$config->updateTime = time();
$config->save();
@@ -201,7 +203,7 @@ class WorkbenchController extends Controller
$query->field('workbenchId,pushType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,contentLibraries');
},
'groupCreate' => function($query) {
- $query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription');
+ $query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription,poolGroups,wechatGroups');
},
'user' => function ($query) {
$query->field('id,username');
@@ -277,7 +279,7 @@ class WorkbenchController extends Controller
$item->config->status = $item->config->status;
$item->config->groups = json_decode($item->config->groups, true);
$item->config->contentLibraries = json_decode($item->config->contentLibraries, true);
- $item->config->lastPushTime = '22222';
+ $item->config->lastPushTime = '';
}
unset($item->groupPush, $item->group_push);
break;
@@ -285,6 +287,8 @@ class WorkbenchController extends Controller
if (!empty($item->groupCreate)) {
$item->config = $item->groupCreate;
$item->config->devices = json_decode($item->config->devices, true);
+ $item->config->poolGroups = json_decode($item->config->poolGroups, true);
+ $item->config->wechatGroups = json_decode($item->config->wechatGroups, true);
}
unset($item->groupCreate, $item->group_create);
break;
@@ -391,7 +395,7 @@ class WorkbenchController extends Controller
$query->field('workbenchId,pushType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,contentLibraries');
},
'groupCreate' => function($query) {
- $query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription');
+ $query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription,poolGroups,wechatGroups');
}
];
$workbench = Workbench::where([
@@ -466,6 +470,8 @@ class WorkbenchController extends Controller
if (!empty($workbench->groupCreate)) {
$workbench->config = $workbench->groupCreate;
$workbench->config->deveiceGroups = json_decode($workbench->config->devices, true);
+ $workbench->config->poolGroups = json_decode($workbench->config->poolGroups, true);
+ $workbench->config->wechatGroups = json_decode($workbench->config->wechatGroups, true);
unset($workbench->groupCreate, $workbench->group_create);
}
break;
@@ -600,6 +606,21 @@ class WorkbenchController extends Controller
$workbench->config->accountGroupsOptions = [];
}
+ if (!empty($workbench->config->poolGroups)){
+ $poolGroupsOptions = Db::name('traffic_source_package')->alias('tsp')
+ ->join('traffic_source_package_item tspi','tspi.packageId=tsp.id','left')
+ ->whereIn('tsp.companyId', [$this->request->userInfo['companyId'],0])
+ ->whereIn('tsp.id', $workbench->config->poolGroups)
+ ->field('tsp.id,tsp.name,tsp.description,tsp.createTime,count(tspi.id) as num')
+ ->group('tsp.id')
+ ->select();
+ $workbench->config->poolGroupsOptions = $poolGroupsOptions;
+ }else{
+ $workbench->config->poolGroupsOptions = [];
+ }
+
+
+
return json(['code' => 200, 'msg' => '获取成功', 'data' => $workbench]);
}
@@ -636,6 +657,7 @@ class WorkbenchController extends Controller
try {
// 更新工作台基本信息
$workbench->name = $param['name'];
+ $workbench->status = !empty($param['status']) ? 1 : 0;
$workbench->autoStart = !empty($param['autoStart']) ? 1 : 0;
$workbench->updateTime = time();
$workbench->save();
@@ -720,6 +742,8 @@ class WorkbenchController extends Controller
$config->maxGroupsPerDay = $param['maxGroupsPerDay'];
$config->groupNameTemplate = $param['groupNameTemplate'];
$config->groupDescription = $param['groupDescription'];
+ $config->poolGroups = json_encode($param['poolGroups'] ?? []);
+ $config->wechatGroups = json_encode($param['wechatGroups'] ?? []);
$config->updateTime = time();
$config->save();
}
@@ -859,7 +883,8 @@ class WorkbenchController extends Controller
$newConfig->contentTypes = $config->contentTypes;
$newConfig->devices = $config->devices;
$newConfig->friends = $config->friends;
- //$newConfig->targetGroups = $config->targetGroups;
+ $newConfig->createTime = time();
+ $newConfig->updateTime = time();
$newConfig->save();
}
break;
@@ -876,6 +901,8 @@ class WorkbenchController extends Controller
$newConfig->accountType = $config->accountType;
$newConfig->devices = $config->devices;
$newConfig->contentLibraries = $config->contentLibraries;
+ $newConfig->createTime = time();
+ $newConfig->updateTime = time();
$newConfig->save();
}
break;
@@ -893,6 +920,8 @@ class WorkbenchController extends Controller
$newConfig->status = $config->status;
$newConfig->groups = $config->groups;
$newConfig->contentLibraries = $config->contentLibraries;
+ $newConfig->createTime = time();
+ $newConfig->updateTime = time();
$newConfig->save();
}
break;
@@ -909,6 +938,8 @@ class WorkbenchController extends Controller
$newConfig->maxGroupsPerDay = $config->maxGroupsPerDay;
$newConfig->groupNameTemplate = $config->groupNameTemplate;
$newConfig->groupDescription = $config->groupDescription;
+ $newConfig->poolGroups = $config->poolGroups;
+ $newConfig->wechatGroups = $config->wechatGroups;
$newConfig->createTime = time();
$newConfig->updateTime = time();
$newConfig->save();
diff --git a/Server/application/cunkebao/controller/plan/PosterWeChatMiniProgram.php b/Server/application/cunkebao/controller/plan/PosterWeChatMiniProgram.php
index aa5c2b53..b07c7480 100644
--- a/Server/application/cunkebao/controller/plan/PosterWeChatMiniProgram.php
+++ b/Server/application/cunkebao/controller/plan/PosterWeChatMiniProgram.php
@@ -127,6 +127,73 @@ class PosterWeChatMiniProgram extends Controller
}
+
+
+ public function decryptphones() {
+
+ $taskId = request()->param('id');
+ $phone = request()->param('phone');
+ if (!$phone) {
+ return json([
+ 'code' => 400,
+ 'message' => '手机号不能为空'
+ ]);
+ }
+
+ $task = Db::name('customer_acquisition_task')->where('id', $taskId)->find();
+ if (!$task) {
+ return json([
+ 'code' => 400,
+ 'message' => '任务不存在'
+ ]);
+ }
+
+ if (!empty($phone)) {
+
+ // TODO 拿到手机号之后的后续操作:
+ // 1. 先写入 ck_traffic_pool 表 identifier mobile 都是 用 phone字段的值
+ $trafficPool = Db::name('traffic_pool')->where('identifier', $phone)->find();
+ if (!$trafficPool) {
+ Db::name('traffic_pool')->insert([
+ 'identifier' => $phone,
+ 'mobile' => $phone,
+ 'createTime' => time()
+ ]);
+ }
+
+ $taskCustomer = Db::name('task_customer')->where('task_id', $taskId)->where('phone', $phone)->find();
+ if (!$taskCustomer) {
+ Db::name('task_customer')->insert([
+ 'task_id' => $taskId,
+ // 'identifier' => $phone,
+ 'phone' => $phone,
+ 'source' => $task['name'],
+ 'createTime' => time(),
+ 'tags' => json_encode([]),
+ 'siteTags' => json_encode([]),
+ ]);
+ }
+ // return $phone;
+ return json([
+ 'code' => 200,
+ 'message' => '获取手机号成功',
+ 'data' => $phone
+ ]);
+ } else {
+ // return null;
+ return json([
+ 'code' => 400,
+ 'message' => '手机号失败:'
+ ]);
+ }
+
+ // return $result;
+
+ }
+
+
+
+
// todo 获取海报获客任务的任务/海报数据 -- 表还没设计好,不急 ck_customer_acquisition_task
public function getPosterTaskData() {
$id = request()->param('id');
diff --git a/Server/application/cunkebao/validate/Workbench.php b/Server/application/cunkebao/validate/Workbench.php
index 01fa360c..6796140f 100644
--- a/Server/application/cunkebao/validate/Workbench.php
+++ b/Server/application/cunkebao/validate/Workbench.php
@@ -48,8 +48,8 @@ class Workbench extends Validate
// 自动建群特有参数
'groupNameTemplate' => 'requireIf:type,4|max:50',
'maxGroupsPerDay' => 'requireIf:type,4|number|min:1',
- 'groupSizeMin' => 'requireIf:type,4|number|min:1',
- 'groupSizeMax' => 'requireIf:type,4|number|min:1',
+ 'groupSizeMin' => 'requireIf:type,4|number|min:1|max:50',
+ 'groupSizeMax' => 'requireIf:type,4|number|min:1|max:50',
// 流量分发特有参数
'distributeType' => 'requireIf:type,5|in:1,2',
'maxPerDay' => 'requireIf:type,5|number|min:1',
@@ -130,9 +130,11 @@ class Workbench extends Validate
'groupSizeMin.requireIf' => '请设置每个群的人数',
'groupSizeMin.number' => '每个群的人数必须为数字',
'groupSizeMin.min' => '每个群的人数必须大于0',
+ 'groupSizeMin.max' => '每个群的人数最大50人',
'groupSizeMax.requireIf' => '请设置每个群的人数',
'groupSizeMax.number' => '每个群的人数必须为数字',
'groupSizeMax.min' => '每个群的人数必须大于0',
+ 'groupSizeMax.max' => '每个群的人数最大50人',
// 流量分发相关提示
'distributeType.requireIf' => '请选择流量分发类型',
'distributeType.in' => '流量分发类型错误',
diff --git a/Server/route/route.php b/Server/route/route.php
index 13581e1e..a1adcf1b 100644
--- a/Server/route/route.php
+++ b/Server/route/route.php
@@ -36,4 +36,7 @@ include __DIR__ . '/../application/superadmin/config/route.php';
// 加载CozeAI模块路由配置
include __DIR__ . '/../application/cozeai/config/route.php';
+// 加载OpenAiAI模块路由配置
+include __DIR__ . '/../application/ai/config/route.php';
+
return [];
From 647a053cccda8b0a9f965829bec9476d5a38628e 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: Fri, 22 Aug 2025 10:36:33 +0800
Subject: [PATCH 26/78] =?UTF-8?q?fix(workspace):=20=E4=BF=AE=E6=AD=A3?=
=?UTF-8?q?=E8=87=AA=E5=8A=A8=E7=82=B9=E8=B5=9E=E4=BB=BB=E5=8A=A1=E4=B8=AD?=
=?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=BB=84=E5=AD=97=E6=AE=B5=E5=90=8D=E7=A7=B0?=
=?UTF-8?q?=E9=94=99=E8=AF=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
修复自动点赞任务创建页面中设备组字段名称拼写错误,将`deveicegroups`统一改为`deveiceGroups`,并更新相关逻辑判断和组件属性传递
---
Cunkebao/dist/.vite/manifest.json | 26 +++++++++----------
Cunkebao/dist/index.html | 8 +++---
.../mobile/workspace/auto-like/new/index.tsx | 8 +++---
3 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json
index 1fc771a8..454623cd 100644
--- a/Cunkebao/dist/.vite/manifest.json
+++ b/Cunkebao/dist/.vite/manifest.json
@@ -1,14 +1,18 @@
{
- "_charts-CLRTJ7Uf.js": {
- "file": "assets/charts-CLRTJ7Uf.js",
+ "_charts-DRkEUjsu.js": {
+ "file": "assets/charts-DRkEUjsu.js",
"name": "charts",
"imports": [
- "_ui-BFvqeNzU.js",
+ "_ui-ltFujOOi.js",
"_vendor-2vc8h_ct.js"
]
},
- "_ui-BFvqeNzU.js": {
- "file": "assets/ui-BFvqeNzU.js",
+ "_ui-D0C0OGrH.css": {
+ "file": "assets/ui-D0C0OGrH.css",
+ "src": "_ui-D0C0OGrH.css"
+ },
+ "_ui-ltFujOOi.js": {
+ "file": "assets/ui-ltFujOOi.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,18 +33,18 @@
"name": "vendor"
},
"index.html": {
- "file": "assets/index-C48GlG01.js",
+ "file": "assets/index-DV2QF8R9.js",
"name": "index",
"src": "index.html",
"isEntry": true,
"imports": [
"_vendor-2vc8h_ct.js",
- "_ui-BFvqeNzU.js",
+ "_ui-ltFujOOi.js",
"_utils-6WF66_dS.js",
- "_charts-CLRTJ7Uf.js"
+ "_charts-DRkEUjsu.js"
],
"css": [
- "assets/index-Ta4vyxDJ.css"
+ "assets/index-DqyE1UEk.css"
]
}
}
\ No newline at end of file
diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html
index 8a58e165..76889c66 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/new/index.tsx b/Cunkebao/src/pages/mobile/workspace/auto-like/new/index.tsx
index b7bc9ba4..5be27d57 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-like/new/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/auto-like/new/index.tsx
@@ -72,7 +72,7 @@ const NewAutoLike: React.FC = () => {
startTime: config.timeRange?.start || config.startTime || "08:00",
endTime: config.timeRange?.end || config.endTime || "22:00",
contentTypes: config.contentTypes || ["text", "image", "video"],
- deveiceGroups: config.deveicegroups || [],
+ deveiceGroups: config.deveiceGroups || [],
deveiceGroupsOptions: config.deveiceGroupsOptions || [],
friendsGroups: config.friendsgroups || [],
friendsGroupsOptions: config.friendsGroupsOptions || [],
@@ -121,7 +121,7 @@ const NewAutoLike: React.FC = () => {
message.warning("请输入任务名称");
return;
}
- if (!formData.deveicegroups || formData.deveicegroups.length === 0) {
+ if (!formData.deveiceGroups || formData.deveiceGroups.length === 0) {
message.warning("请选择执行设备");
return;
}
@@ -329,7 +329,7 @@ const NewAutoLike: React.FC = () => {
handleUpdateFormData({ devices })}
showInput={true}
showSelectedList={true}
@@ -348,7 +348,7 @@ const NewAutoLike: React.FC = () => {
onClick={handleNext}
className={style.nextBtn}
size="large"
- disabled={formData.deveicegroups.length === 0}
+ disabled={formData.deveiceGroups.length === 0}
>
下一步
From 5ce77e0adc93e9f72b45817bb894485a613a2a3b Mon Sep 17 00:00:00 2001
From: wong <106998207@qq.com>
Date: Fri, 22 Aug 2025 11:32:01 +0800
Subject: [PATCH 27/78] =?UTF-8?q?=E5=86=85=E5=AE=B9=E5=BA=93=E5=85=BC?=
=?UTF-8?q?=E5=AE=B9=E6=80=A7=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../cunkebao/controller/ContentLibraryController.php | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/Server/application/cunkebao/controller/ContentLibraryController.php b/Server/application/cunkebao/controller/ContentLibraryController.php
index 38fdb2d1..5a95f1a7 100644
--- a/Server/application/cunkebao/controller/ContentLibraryController.php
+++ b/Server/application/cunkebao/controller/ContentLibraryController.php
@@ -58,9 +58,9 @@ class ContentLibraryController extends Controller
$data = [
'name' => $param['name'],
// 数据来源配置
- 'sourceFriends' => $sourceType == 1 ? json_encode($param['friendsGroups']) : json_encode([]), // 选择的微信好友
- 'sourceGroups' => $sourceType == 2 ? json_encode($param['wechatGroups']) : json_encode([]), // 选择的微信群
- 'groupMembers' => $sourceType == 2 ? json_encode($param['groupMembers']) : json_encode([]), // 群组成员
+ 'sourceFriends' => $sourceType == 1 && isset($param['friendsGroups']) ? json_encode($param['friendsGroups']) : json_encode([]), // 选择的微信好友
+ 'sourceGroups' => $sourceType == 2 && isset($param['wechatGroups']) ? json_encode($param['wechatGroups']) : json_encode([]), // 选择的微信群
+ 'groupMembers' => $sourceType == 2 && isset($param['groupMembers']) ? json_encode($param['groupMembers']) : json_encode([]), // 群组成员
// 关键词配置
'keywordInclude' => $keywordInclude, // 包含的关键词
'keywordExclude' => $keywordExclude, // 排除的关键词
@@ -324,9 +324,9 @@ class ContentLibraryController extends Controller
// 更新内容库基本信息
$library->name = $param['name'];
$library->sourceType = isset($param['sourceType']) ? $param['sourceType'] : 1;
- $library->sourceFriends = $param['sourceType'] == 1 ? json_encode($param['friendsGroups']) : json_encode([]);
- $library->sourceGroups = $param['sourceType'] == 2 ? json_encode($param['wechatGroups']) : json_encode([]);
- $library->groupMembers = $param['sourceType'] == 2 ? json_encode($param['groupMembers']) : json_encode([]);
+ $library->sourceFriends = $param['sourceType'] == 1 && isset($param['friendsGroups']) ? json_encode($param['friendsGroups']) : json_encode([]);
+ $library->sourceGroups = $param['sourceType'] == 2 && isset($param['wechatGroups']) ? json_encode($param['wechatGroups']) : json_encode([]);
+ $library->groupMembers = $param['sourceType'] == 2 && isset($param['groupMembers']) ? json_encode($param['groupMembers']) : json_encode([]);
$library->keywordInclude = $keywordInclude;
$library->keywordExclude = $keywordExclude;
$library->aiEnabled = isset($param['aiEnabled']) ? $param['aiEnabled'] : 0;
From 32ea075e90112bbad8ecfe758781fe8b9a99749c 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: Fri, 22 Aug 2025 14:21:35 +0800
Subject: [PATCH 28/78] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?=
=?UTF-8?q?=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
---
.../pc/ckbox/components/ChatWindow/index.tsx | 26 +--
.../SidebarMenu/SidebarMenu.module.scss | 2 +-
...ListSimple.tsx => WechatFriendsModule.tsx} | 8 +-
.../pc/ckbox/components/SidebarMenu/data.ts | 47 ++++++
.../pc/ckbox/components/SidebarMenu/index.tsx | 103 ++++++------
Cunkebao/src/pages/pc/ckbox/data.ts | 72 ++++++---
Cunkebao/src/pages/pc/ckbox/index.tsx | 149 ++----------------
Cunkebao/src/pages/pc/ckbox/main.ts | 47 +++---
Cunkebao/src/store/module/websocket.ts | 2 -
9 files changed, 206 insertions(+), 250 deletions(-)
rename Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/{ContactListSimple.tsx => WechatFriendsModule.tsx} (85%)
create mode 100644 Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
index f3f76a2f..3ba0b11b 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
@@ -78,16 +78,16 @@ const ChatWindow: React.FC = ({
const fetchChatHistory = async () => {
try {
setLoading(true);
-
+
// 从chat对象中提取wechatFriendId
// 假设chat.id存储的是wechatFriendId
const wechatFriendId = parseInt(chat.id);
-
+
if (isNaN(wechatFriendId)) {
messageApi.error("无效的聊天ID");
return;
}
-
+
// 调用API获取聊天历史
const response = await getChatMessage({
wechatAccountId: 32686452, // 使用实际的wechatAccountId
@@ -98,9 +98,9 @@ const ChatWindow: React.FC = ({
olderData: false,
keyword: "",
});
-
+
console.log("聊天历史响应:", response);
-
+
if (response && Array.isArray(response)) {
// 将API返回的消息记录转换为MessageData格式
const chatMessages: MessageData[] = response.map(item => {
@@ -112,24 +112,28 @@ const ChatWindow: React.FC = ({
} catch (e) {
msgContent = item.content;
}
-
+
// 判断消息是发送还是接收
const isSend = item.isSend === true;
-
+
return {
id: item.id.toString(),
senderId: isSend ? "me" : "other",
senderName: isSend ? "我" : chat.name,
content: msgContent,
type: MessageType.TEXT, // 默认为文本类型,实际应根据msgType字段判断
- timestamp: item.createTime || new Date(item.wechatTime).toISOString(),
+ timestamp:
+ item.createTime || new Date(item.wechatTime).toISOString(),
isRead: true, // 默认已读
};
});
-
+
// 按时间排序
- chatMessages.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
-
+ chatMessages.sort(
+ (a, b) =>
+ new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),
+ );
+
setMessages(chatMessages);
} else {
// 如果没有消息,显示空数组
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss
index 614120fb..1b77fa4d 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss
@@ -60,4 +60,4 @@
padding: 20px;
text-align: center;
}
-}
\ No newline at end of file
+}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
similarity index 85%
rename from Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.tsx
rename to Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
index 9a5dfba3..c3fd5e2c 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
@@ -1,12 +1,12 @@
import React from "react";
import { List, Avatar, Badge } from "antd";
-import { ContactData } from "../../data";
+import { ContactData } from "./data";
import styles from "./ContactListSimple.module.scss";
interface ContactListSimpleProps {
contacts: ContactData[];
onContactClick: (contact: ContactData) => void;
- selectedContactId?: string;
+ selectedContactId?: number;
}
const ContactListSimple: React.FC = ({
@@ -31,14 +31,14 @@ const ContactListSimple: React.FC = ({
{contact.name.charAt(0)}
+ !contact.avatar && {contact.nickname.charAt(0)}
}
className={styles.avatar}
/>
-
{contact.name}
+
{contact.nickname}
)}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts
new file mode 100644
index 00000000..afe7cb81
--- /dev/null
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts
@@ -0,0 +1,47 @@
+// 联系人数据接口
+export interface ContactData {
+ id?: number;
+ wechatAccountId: number;
+ wechatId: string;
+ alias: string;
+ conRemark: string;
+ nickname: string;
+ quanPin: string;
+ avatar?: string;
+ gender: number;
+ region: string;
+ addFrom: number;
+ phone: string;
+ labels: string[];
+ signature: string;
+ accountId: number;
+ extendFields: null;
+ city?: string;
+ lastUpdateTime: string;
+ isPassed: boolean;
+ tenantId: number;
+ groupId: number;
+ thirdParty: null;
+ additionalPicture: string;
+ desc: string;
+ config: null;
+ lastMessageTime: number;
+ unreadCount: number;
+ duplicate: boolean;
+}
+//聊天会话类型
+export type ChatType = "private" | "group";
+// 聊天会话接口
+export interface ChatSession {
+ id: string;
+ type: ChatType;
+ name: string;
+ avatar?: string;
+ lastMessage: string;
+ lastTime: string;
+ unreadCount: number;
+ online: boolean;
+ members?: string[];
+ pinned?: boolean;
+ muted?: boolean;
+}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
index e4da3996..edfc2c74 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
@@ -6,12 +6,13 @@ import {
TeamOutlined,
MessageOutlined,
} from "@ant-design/icons";
-import { ContactData, ChatSession } from "../../data";
-import ContactListSimple from "./ContactListSimple";
+import { ContactData, ChatSession } from "./data";
+import WechatFriendsModule from "./WechatFriendsModule";
import MessageList from "../MessageList/index";
import styles from "./SidebarMenu.module.scss";
const { Sider } = Layout;
+const { TabPane } = Tabs;
interface SidebarMenuProps {
contacts: ContactData[];
@@ -41,7 +42,7 @@ const SidebarMenu: React.FC
= ({
if (!searchText) return contacts;
return contacts.filter(
contact =>
- contact.name.toLowerCase().includes(searchText.toLowerCase()) ||
+ contact.nickname.toLowerCase().includes(searchText.toLowerCase()) ||
contact.phone.includes(searchText),
);
};
@@ -71,56 +72,52 @@ const SidebarMenu: React.FC = ({
activeKey={activeTab}
onChange={setActiveTab}
className={styles.tabs}
- items={[
- {
- key: "chats",
- label: (
-
-
- 聊天
-
- ),
- children: (
-
- ),
- },
- {
- key: "contacts",
- label: (
-
-
- 联系人
-
- ),
- children: (
-
- ),
- },
- {
- key: "groups",
- label: (
-
-
- 群组
-
- ),
- children: (
-
- ),
- },
- ]}
- />
+ >
+
+
+ 聊天
+
+ }
+ key="chats"
+ >
+
+
+
+
+ 联系人
+
+ }
+ key="contacts"
+ >
+
+
+
+
+ 群组
+
+ }
+ key="groups"
+ >
+
+
+
);
};
diff --git a/Cunkebao/src/pages/pc/ckbox/data.ts b/Cunkebao/src/pages/pc/ckbox/data.ts
index f8f52b59..118b9fa0 100644
--- a/Cunkebao/src/pages/pc/ckbox/data.ts
+++ b/Cunkebao/src/pages/pc/ckbox/data.ts
@@ -1,14 +1,52 @@
// 联系人数据接口
export interface ContactData {
- id: string;
- name: string;
- phone: string;
+ id?: number;
+ wechatAccountId: number;
+ wechatId: string;
+ alias: string;
+ conRemark: string;
+ nickname: string;
+ quanPin: string;
avatar?: string;
- online: boolean;
- lastSeen?: string;
- status?: string;
- department?: string;
- position?: string;
+ gender: number;
+ region: string;
+ addFrom: number;
+ phone: string;
+ labels: string[];
+ signature: string;
+ accountId: number;
+ extendFields: null;
+ city?: string;
+ lastUpdateTime: string;
+ isPassed: boolean;
+ tenantId: number;
+ groupId: number;
+ thirdParty: null;
+ additionalPicture: string;
+ desc: string;
+ config: null;
+ lastMessageTime: number;
+ unreadCount: number;
+ duplicate: boolean;
+}
+
+/**
+ * 微信好友基本信息接口
+ * 包含主要字段和兼容性字段
+ */
+export interface WechatFriend {
+ // 主要字段
+ id: number; // 好友ID
+ wechatAccountId: number; // 微信账号ID
+ wechatId: string; // 微信ID
+ nickname: string; // 昵称
+ conRemark: string; // 备注名
+ avatar: string; // 头像URL
+ gender: number; // 性别:1-男,2-女,0-未知
+ region: string; // 地区
+ phone: string; // 电话
+ labels: string[]; // 标签列表
+ [key: string]: any;
}
// 消息类型枚举
@@ -52,18 +90,6 @@ export interface ChatSession {
muted?: boolean;
}
-// 群组信息接口
-export interface GroupData {
- id: string;
- name: string;
- avatar?: string;
- description?: string;
- members: ContactData[];
- adminIds: string[];
- createdAt: string;
- updatedAt: string;
-}
-
// 聊天历史响应接口
export interface ChatHistoryResponse {
messages: MessageData[];
@@ -79,12 +105,6 @@ export interface SendMessageRequest {
replyTo?: string;
}
-// 联系人列表响应接口
-export interface ContactListResponse {
- contacts: ContactData[];
- total: number;
-}
-
// 搜索联系人请求接口
export interface SearchContactRequest {
keyword: string;
diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx
index 58533e02..dd782295 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/index.tsx
@@ -1,19 +1,17 @@
-import React, { useState, useEffect, useRef } from "react";
+import React, { useState, useEffect } from "react";
import { Layout, Button, Space, message, Tooltip } from "antd";
import { InfoCircleOutlined, MessageOutlined } from "@ant-design/icons";
import dayjs from "dayjs";
-import { ContactData, MessageData, ChatSession } from "./data";
+import { ChatSession } from "./data";
+import { ContactData } from "./components/SidebarMenu/data";
import ChatWindow from "./components/ChatWindow/index";
import SidebarMenu from "./components/SidebarMenu/index";
import styles from "./index.module.scss";
-import { getContactList, getChatMessage } from "./api";
-const { Content } = Layout;
-import { loginWithToken, getChuKeBaoUserInfo } from "@/pages/login/api";
-import { useCkChatStore } from "@/store/module/ckchat";
+const { Content } = Layout;
+import { loginWithToken } from "@/pages/login/api";
import { useUserStore } from "@/store/module/user";
-import { useWebSocketStore } from "@/store/module/websocket";
-import { chatInitAPIdata, getChatInfo } from "./main";
+import { chatInitAPIdata } from "./main";
const CkboxPage: React.FC = () => {
const [messageApi, contextHolder] = message.useMessage();
@@ -22,12 +20,19 @@ const CkboxPage: React.FC = () => {
const [currentChat, setCurrentChat] = useState(null);
const [loading, setLoading] = useState(false);
const [showProfile, setShowProfile] = useState(true);
- const { setUserInfo, getAccountId } = useCkChatStore();
const { login2 } = useUserStore();
useEffect(() => {
- const contactList = chatInitAPIdata();
- console.log(contactList);
+ // 方法一:使用 Promise 链式调用处理异步函数
+ chatInitAPIdata()
+ .then(contactList => {
+ console.log(contactList);
+ // 如果需要可以设置联系人列表
+ setContacts(contactList);
+ })
+ .catch(error => {
+ console.error("获取联系人列表失败:", error);
+ });
}, []);
const getToken2 = () => {
@@ -48,116 +53,6 @@ const CkboxPage: React.FC = () => {
});
};
- const fetchContacts = async () => {
- try {
- setLoading(true);
- // 使用API获取联系人数据
- const response = await getContactList({ prevId: 0, count: 500 });
- if (response) {
- // 转换API返回的数据结构为组件所需的ContactData结构
- const contactList: ContactData[] = response.map((item: any) => ({
- id: item.id.toString(),
- name: item.nickname || item.conRemark || item.alias || "",
- phone: item.phone || "",
- avatar: item.avatar || "",
- online: true, // 假设所有联系人都在线,实际应根据API返回数据调整
- status: "在线", // 假设状态,实际应根据API返回数据调整
- department: "", // API中没有对应字段,可以根据需要添加
- position: "", // API中没有对应字段,可以根据需要添加
- }));
- setContacts(contactList);
- }
- } catch (error) {
- messageApi.error("获取联系人失败");
- console.error("获取联系人失败:", error);
- } finally {
- setLoading(false);
- }
- };
-
- const fetchChatSessions = async () => {
- try {
- // 先确保联系人列表已加载
- if (contacts.length === 0) {
- await fetchContacts();
- }
-
- const response = await getChatMessage({
- wechatAccountId: 32686452, // 使用实际的wechatAccountId
- wechatFriendId: 0, // 可以设置为0获取所有好友的消息
- From: 0,
- To: 0,
- Count: 50, // 获取最近的50条消息
- olderData: false,
- keyword: "",
- });
-
- console.log("聊天消息响应:", response);
-
- if (response && Array.isArray(response)) {
- // 创建一个Map来存储每个好友ID对应的最新消息
- const friendMessageMap = new Map();
-
- // 遍历所有消息,只保留每个好友的最新消息
- response.forEach(item => {
- const friendId = item.wechatFriendId;
- const existingMessage = friendMessageMap.get(friendId);
-
- // 如果Map中没有这个好友的消息,或者当前消息比已存在的更新,则更新Map
- if (
- !existingMessage ||
- new Date(item.createTime) > new Date(existingMessage.createTime)
- ) {
- friendMessageMap.set(friendId, item);
- }
- });
-
- // 将Map转换为数组
- const latestMessages = Array.from(friendMessageMap.values());
-
- // 将API返回的消息记录转换为ChatSession格式
- const sessions: ChatSession[] = latestMessages.map(item => {
- // 解析content字段,它是一个JSON字符串
- let msgContent = "";
- try {
- const contentObj = JSON.parse(item.content);
- msgContent = contentObj.content || "";
- } catch (e) {
- msgContent = item.content;
- }
-
- // 尝试从联系人列表中找到对应的联系人信息
- const contact = contacts.find(
- c => c.id === item.wechatFriendId.toString(),
- );
-
- return {
- id: item.id.toString(),
- type: "private", // 假设都是私聊
- name: contact ? contact.name : `联系人 ${item.wechatFriendId}`, // 使用联系人名称或默认名称
- avatar: contact?.avatar || "", // 使用联系人头像或默认空字符串
- lastMessage: msgContent,
- lastTime:
- item.createTime || new Date(item.wechatTime).toISOString(),
- unreadCount: 0, // 未读消息数需要另外获取
- online: contact?.online || false, // 使用联系人在线状态或默认为false
- };
- });
-
- // 按最后消息时间排序
- sessions.sort(
- (a, b) =>
- new Date(b.lastTime).getTime() - new Date(a.lastTime).getTime(),
- );
-
- setChatSessions(sessions);
- }
- } catch (error) {
- console.error("获取聊天记录失败:", error);
- messageApi.error("获取聊天记录失败");
- }
- };
-
const handleContactClick = (contact: ContactData) => {
// 查找或创建聊天会话
let session = chatSessions.find(s => s.id === contact.id);
@@ -165,7 +60,7 @@ const CkboxPage: React.FC = () => {
session = {
id: contact.id,
type: "private",
- name: contact.name,
+ name: contact.nickname,
avatar: contact.avatar,
lastMessage: "",
lastTime: dayjs().toISOString(),
@@ -181,16 +76,6 @@ const CkboxPage: React.FC = () => {
if (!currentChat || !message.trim()) return;
try {
- const newMessage: MessageData = {
- id: Date.now().toString(),
- senderId: "me",
- senderName: "我",
- content: message,
- type: "text" as any,
- timestamp: dayjs().toISOString(),
- isRead: false,
- };
-
// 更新当前聊天会话
const updatedSession = {
...currentChat,
diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts
index 986e5f6a..366f77b6 100644
--- a/Cunkebao/src/pages/pc/ckbox/main.ts
+++ b/Cunkebao/src/pages/pc/ckbox/main.ts
@@ -9,30 +9,35 @@ const { connect } = useWebSocketStore.getState();
const { setUserInfo, getAccountId } = useCkChatStore.getState();
//获取触客宝基础信息
export const chatInitAPIdata = async () => {
- //获取Token
- const Token = await getToken();
- //获取用户信息
- const userInfo = await getChuKeBaoUserInfo();
- setUserInfo(userInfo);
+ try {
+ //获取Token
+ const Token = await getToken();
+ //获取用户信息
+ const userInfo = await getChuKeBaoUserInfo();
+ setUserInfo(userInfo);
- //获取用户账号Id
- const accountId = getAccountId();
+ //获取用户账号Id
+ const accountId = getAccountId();
- //发起链接
- connect({
- accessToken: String(Token),
- accountId: accountId,
- client: "kefu-client",
- cmdType: "CmdSignIn",
- seq: 1,
- });
- //获取联系人列表
- const contactList = await getContactList({
- prevId: userInfo.tenant.tenantType,
- count: 100,
- });
+ //发起链接
+ connect({
+ accessToken: String(Token),
+ accountId: accountId,
+ client: "kefu-client",
+ cmdType: "CmdSignIn",
+ seq: 1,
+ });
+ //获取联系人列表
+ const contactList = await getContactList({
+ prevId: userInfo.tenant.tenantType,
+ count: 100,
+ });
- return contactList;
+ return contactList;
+ } catch (error) {
+ console.error("获取联系人列表失败:", error);
+ return [];
+ }
};
export const getChatInfo = () => {
diff --git a/Cunkebao/src/store/module/websocket.ts b/Cunkebao/src/store/module/websocket.ts
index a000f1a3..2d330f66 100644
--- a/Cunkebao/src/store/module/websocket.ts
+++ b/Cunkebao/src/store/module/websocket.ts
@@ -114,8 +114,6 @@ export const useWebSocketStore = createPersistStore(
return;
}
- console.log("获取connect参数", getAccountId());
-
// 构建WebSocket URL
const params = new URLSearchParams({
client: fullConfig.client.toString(),
From b3cf0243dd22d2a03f6989e4e9c1f7b56c63a59f 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: Fri, 22 Aug 2025 14:24:52 +0800
Subject: [PATCH 29/78] 1
---
Cunkebao/dist/.vite/manifest.json | 2 +-
Cunkebao/dist/index.html | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json
index 454623cd..9630fc98 100644
--- a/Cunkebao/dist/.vite/manifest.json
+++ b/Cunkebao/dist/.vite/manifest.json
@@ -33,7 +33,7 @@
"name": "vendor"
},
"index.html": {
- "file": "assets/index-DV2QF8R9.js",
+ "file": "assets/index-C1eWqLbB.js",
"name": "index",
"src": "index.html",
"isEntry": true,
diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html
index 76889c66..51b87117 100644
--- a/Cunkebao/dist/index.html
+++ b/Cunkebao/dist/index.html
@@ -11,7 +11,7 @@
-
+
From 0b1872cc029779eda18ac52f2a6661c27c979b30 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: Fri, 22 Aug 2025 14:47:39 +0800
Subject: [PATCH 30/78] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=AE=BE?=
=?UTF-8?q?=E5=A4=87ID=E7=B1=BB=E5=9E=8B=E9=94=99=E8=AF=AF=E5=B9=B6?=
=?UTF-8?q?=E4=BC=98=E5=8C=96=E9=80=89=E9=A1=B9=E5=A4=84=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将设备ID从string类型改为number类型以匹配后端接口
- 为packageOptions添加默认空数组防止undefined错误
- 优化选项获取逻辑,正确处理API返回的数据结构
- 修复自动点赞任务创建时的friendsGroups校验条件
---
Cunkebao/dist/.vite/manifest.json | 26 +++++++++----------
Cunkebao/dist/index.html | 8 +++---
.../src/components/FriendSelection/data.ts | 2 +-
.../FriendSelection/selectionPopup.tsx | 2 +-
.../mine/traffic-pool/list/BatchAddModal.tsx | 2 +-
.../mobile/mine/traffic-pool/list/api.ts | 4 +--
.../mine/traffic-pool/list/dataAnyx.tsx | 10 +++++--
.../mobile/mine/traffic-pool/list/index.tsx | 4 +--
.../mobile/workspace/auto-like/new/data.ts | 4 +--
.../mobile/workspace/auto-like/new/index.tsx | 11 +++++---
10 files changed, 42 insertions(+), 31 deletions(-)
diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json
index 9630fc98..0307e260 100644
--- a/Cunkebao/dist/.vite/manifest.json
+++ b/Cunkebao/dist/.vite/manifest.json
@@ -1,18 +1,14 @@
{
- "_charts-DRkEUjsu.js": {
- "file": "assets/charts-DRkEUjsu.js",
+ "_charts-BKZw5YGd.js": {
+ "file": "assets/charts-BKZw5YGd.js",
"name": "charts",
"imports": [
- "_ui-ltFujOOi.js",
+ "_ui-CQE-Ac3N.js",
"_vendor-2vc8h_ct.js"
]
},
- "_ui-D0C0OGrH.css": {
- "file": "assets/ui-D0C0OGrH.css",
- "src": "_ui-D0C0OGrH.css"
- },
- "_ui-ltFujOOi.js": {
- "file": "assets/ui-ltFujOOi.js",
+ "_ui-CQE-Ac3N.js": {
+ "file": "assets/ui-CQE-Ac3N.js",
"name": "ui",
"imports": [
"_vendor-2vc8h_ct.js"
@@ -21,6 +17,10 @@
"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,18 +33,18 @@
"name": "vendor"
},
"index.html": {
- "file": "assets/index-C1eWqLbB.js",
+ "file": "assets/index-Do1bKVHK.js",
"name": "index",
"src": "index.html",
"isEntry": true,
"imports": [
"_vendor-2vc8h_ct.js",
- "_ui-ltFujOOi.js",
+ "_ui-CQE-Ac3N.js",
"_utils-6WF66_dS.js",
- "_charts-DRkEUjsu.js"
+ "_charts-BKZw5YGd.js"
],
"css": [
- "assets/index-DqyE1UEk.css"
+ "assets/index-ZCB0Bf2x.css"
]
}
}
\ No newline at end of file
diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html
index 51b87117..40df896b 100644
--- a/Cunkebao/dist/index.html
+++ b/Cunkebao/dist/index.html
@@ -11,13 +11,13 @@
-
+
-
+
-
+
-
+
diff --git a/Cunkebao/src/components/FriendSelection/data.ts b/Cunkebao/src/components/FriendSelection/data.ts
index 30b9ec70..434aa641 100644
--- a/Cunkebao/src/components/FriendSelection/data.ts
+++ b/Cunkebao/src/components/FriendSelection/data.ts
@@ -10,7 +10,7 @@ export interface FriendSelectionItem {
export interface FriendSelectionProps {
selectedOptions?: FriendSelectionItem[];
onSelect: (friends: FriendSelectionItem[]) => void;
- deviceIds?: string[];
+ deviceIds?: number[];
enableDeviceFilter?: boolean;
placeholder?: string;
className?: string;
diff --git a/Cunkebao/src/components/FriendSelection/selectionPopup.tsx b/Cunkebao/src/components/FriendSelection/selectionPopup.tsx
index f5713c80..c527f755 100644
--- a/Cunkebao/src/components/FriendSelection/selectionPopup.tsx
+++ b/Cunkebao/src/components/FriendSelection/selectionPopup.tsx
@@ -12,7 +12,7 @@ interface SelectionPopupProps {
onVisibleChange: (visible: boolean) => void;
selectedOptions: FriendSelectionItem[];
onSelect: (friends: FriendSelectionItem[]) => void;
- deviceIds?: string[];
+ deviceIds?: number[];
enableDeviceFilter?: boolean;
readonly?: boolean;
onConfirm?: (
diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/BatchAddModal.tsx b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/BatchAddModal.tsx
index 9a034765..5c7eae5c 100644
--- a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/BatchAddModal.tsx
+++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/BatchAddModal.tsx
@@ -15,7 +15,7 @@ interface BatchAddModalProps {
const BatchAddModal: React.FC = ({
visible,
onClose,
- packageOptions,
+ packageOptions = [],
batchTarget,
setBatchTarget,
selectedCount,
diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/api.ts b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/api.ts
index a5b757ef..5f1509e5 100644
--- a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/api.ts
+++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/api.ts
@@ -9,10 +9,10 @@ export function fetchTrafficPoolList(params: {
return request("/v1/traffic/pool", params, "GET");
}
-export async function fetchScenarioOptions(): Promise {
+export async function fetchScenarioOptions() {
return request("/v1/plan/scenes", {}, "GET");
}
-export async function fetchPackageOptions(): Promise {
+export async function fetchPackageOptions() {
return request("/v1/traffic/pool/getPackage", {}, "GET");
}
diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx
index 6894ce1b..390e439b 100644
--- a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx
+++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx
@@ -78,8 +78,14 @@ export function useTrafficPoolListLogic() {
// 获取筛选项
useEffect(() => {
- fetchPackageOptions().then(setPackageOptions);
- fetchScenarioOptions().then(setScenarioOptions);
+ fetchPackageOptions().then(res => {
+ console.log("packageOptions", res);
+
+ setPackageOptions(res.list || []);
+ });
+ fetchScenarioOptions().then(res => {
+ setScenarioOptions(res.list || []);
+ });
}, []);
// 筛选条件变化时刷新列表
diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/index.tsx b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/index.tsx
index a2a2edec..e3bacf8f 100644
--- a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/index.tsx
+++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/index.tsx
@@ -173,8 +173,8 @@ const TrafficPoolList: React.FC = () => {
status: "offline" as const,
})),
);
- setPackageId(filters.packageId);
- setScenarioId(filters.scenarioId);
+ setPackageId(filters.packageId ? parseInt(filters.packageId) : 0);
+ setScenarioId(filters.scenarioId ? parseInt(filters.scenarioId) : 0);
setUserValue(filters.userValue);
setUserStatus(filters.userStatus);
// 重新获取列表
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-like/new/data.ts b/Cunkebao/src/pages/mobile/workspace/auto-like/new/data.ts
index 32201f84..43f1da6f 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-like/new/data.ts
+++ b/Cunkebao/src/pages/mobile/workspace/auto-like/new/data.ts
@@ -79,9 +79,9 @@ export interface CreateLikeTaskData {
startTime: string;
endTime: string;
contentTypes: ContentType[];
- deveiceGroups: string[];
+ deveiceGroups: number[];
deveiceGroupsOptions: DeviceSelectionItem[];
- friendsGroups: string[];
+ friendsGroups: number[];
friendsGroupsOptions: FriendSelectionItem[];
friendMaxLikes: number;
friendTags?: string;
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-like/new/index.tsx b/Cunkebao/src/pages/mobile/workspace/auto-like/new/index.tsx
index 5be27d57..3d0aca8d 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-like/new/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/auto-like/new/index.tsx
@@ -330,7 +330,12 @@ const NewAutoLike: React.FC = () => {
handleUpdateFormData({ devices })}
+ onSelect={devices =>
+ handleUpdateFormData({
+ deveiceGroups: devices.map(v => v.id),
+ deveiceGroupsOptions: devices,
+ })
+ }
showInput={true}
showSelectedList={true}
/>
@@ -363,7 +368,7 @@ const NewAutoLike: React.FC = () => {
selectedOptions={formData.friendsGroupsOptions || []}
onSelect={friends =>
handleUpdateFormData({
- friendsGroups: friends.map(f => String(f.id)),
+ friendsGroups: friends.map(f => f.id),
friendsGroupsOptions: friends,
})
}
@@ -385,7 +390,7 @@ const NewAutoLike: React.FC = () => {
size="large"
loading={isSubmitting}
disabled={
- !formData.friendsgroups || formData.friendsgroups.length === 0
+ !formData.friendsGroups || formData.friendsGroups.length === 0
}
>
{isEditMode ? "更新任务" : "创建任务"}
From f099bb5420fed2055602547b97bb0fb7a0eb6443 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: Fri, 22 Aug 2025 14:47:52 +0800
Subject: [PATCH 31/78] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E6=9E=84?=
=?UTF-8?q?=E5=BB=BA=E7=94=9F=E6=88=90=E7=9A=84=E8=B5=84=E6=BA=90=E6=96=87?=
=?UTF-8?q?=E4=BB=B6=E5=BC=95=E7=94=A8=E8=B7=AF=E5=BE=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
自动构建后更新manifest.json和index.html中的资源文件引用路径
---
Cunkebao/dist/.vite/manifest.json | 2 +-
Cunkebao/dist/index.html | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json
index 0307e260..57e7c8ae 100644
--- a/Cunkebao/dist/.vite/manifest.json
+++ b/Cunkebao/dist/.vite/manifest.json
@@ -33,7 +33,7 @@
"name": "vendor"
},
"index.html": {
- "file": "assets/index-Do1bKVHK.js",
+ "file": "assets/index-B86fnEyO.js",
"name": "index",
"src": "index.html",
"isEntry": true,
diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html
index 40df896b..ea8b5977 100644
--- a/Cunkebao/dist/index.html
+++ b/Cunkebao/dist/index.html
@@ -11,7 +11,7 @@
-
+
From 812cb977131d2e98a8393343c2a9904677898d8b 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: Fri, 22 Aug 2025 15:17:47 +0800
Subject: [PATCH 32/78] =?UTF-8?q?refactor(ckbox):=20=E7=BB=9F=E4=B8=80?=
=?UTF-8?q?=E8=81=94=E7=B3=BB=E4=BA=BA=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84?=
=?UTF-8?q?=E5=B9=B6=E7=AE=80=E5=8C=96=E8=81=8A=E5=A4=A9=E4=BC=9A=E8=AF=9D?=
=?UTF-8?q?=E5=A4=84=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将ContactData接口添加索引签名以支持动态属性
- 移除ChatSession接口,直接使用ContactData作为聊天会话类型
- 简化组件间数据传递和状态管理
- 更新相关组件props类型定义
---
.../pc/ckbox/components/MessageList/data.ts | 48 +++++++++++++++++++
.../pc/ckbox/components/MessageList/index.tsx | 23 ++++-----
.../SidebarMenu/WechatFriendsModule.tsx | 20 ++++----
.../pc/ckbox/components/SidebarMenu/data.ts | 1 +
.../pc/ckbox/components/SidebarMenu/index.tsx | 16 +++----
Cunkebao/src/pages/pc/ckbox/data.ts | 1 +
Cunkebao/src/pages/pc/ckbox/index.tsx | 19 ++------
7 files changed, 81 insertions(+), 47 deletions(-)
create mode 100644 Cunkebao/src/pages/pc/ckbox/components/MessageList/data.ts
diff --git a/Cunkebao/src/pages/pc/ckbox/components/MessageList/data.ts b/Cunkebao/src/pages/pc/ckbox/components/MessageList/data.ts
new file mode 100644
index 00000000..c1f83783
--- /dev/null
+++ b/Cunkebao/src/pages/pc/ckbox/components/MessageList/data.ts
@@ -0,0 +1,48 @@
+// 联系人数据接口
+export interface ContactData {
+ id?: number;
+ wechatAccountId: number;
+ wechatId: string;
+ alias: string;
+ conRemark: string;
+ nickname: string;
+ quanPin: string;
+ avatar?: string;
+ gender: number;
+ region: string;
+ addFrom: number;
+ phone: string;
+ labels: string[];
+ signature: string;
+ accountId: number;
+ extendFields: null;
+ city?: string;
+ lastUpdateTime: string;
+ isPassed: boolean;
+ tenantId: number;
+ groupId: number;
+ thirdParty: null;
+ additionalPicture: string;
+ desc: string;
+ config: null;
+ lastMessageTime: number;
+ unreadCount: number;
+ duplicate: boolean;
+ [key: string]: any;
+}
+//聊天会话类型
+export type ChatType = "private" | "group";
+// 聊天会话接口
+export interface ChatSession {
+ id: number;
+ type: ChatType;
+ name: string;
+ avatar?: string;
+ lastMessage: string;
+ lastTime: string;
+ unreadCount: number;
+ online: boolean;
+ members?: string[];
+ pinned?: boolean;
+ muted?: boolean;
+}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx
index fd0e0422..151c64d3 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx
@@ -2,17 +2,17 @@ import React from "react";
import { List, Avatar, Badge } from "antd";
import { UserOutlined, TeamOutlined } from "@ant-design/icons";
import dayjs from "dayjs";
-import { ChatSession } from "@/pages/pc/ckbox/data";
+import { ContactData } from "./data";
import styles from "./MessageList.module.scss";
interface MessageListProps {
- sessions: ChatSession[];
- currentChat: ChatSession | null;
- onChatSelect: (chat: ChatSession) => void;
+ chatSessions: ContactData[];
+ currentChat: ContactData;
+ onChatSelect: (chat: ContactData) => void;
}
const MessageList: React.FC = ({
- sessions,
+ chatSessions,
currentChat,
onChatSelect,
}) => {
@@ -35,7 +35,7 @@ const MessageList: React.FC = ({
return (
(
= ({
size={48}
src={session.avatar}
icon={
- session.type === "group" ? (
+ session?.type === "group" ? (
) : (
@@ -59,18 +59,15 @@ const MessageList: React.FC = ({
-
{session.name}
+
{session.nickname}
- {formatTime(session.lastTime)}
+ {formatTime(session?.lastTime || "")}
- {session.lastMessage}
+ {session?.lastMessage}
- {session.online && (
-
在线
- )}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
index c3fd5e2c..0ff78aec 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
@@ -6,7 +6,7 @@ import styles from "./ContactListSimple.module.scss";
interface ContactListSimpleProps {
contacts: ContactData[];
onContactClick: (contact: ContactData) => void;
- selectedContactId?: number;
+ selectedContactId?: ContactData;
}
const ContactListSimple: React.FC = ({
@@ -24,18 +24,16 @@ const ContactListSimple: React.FC = ({
onContactClick(contact)}
- className={`${styles.contactItem} ${contact.id === selectedContactId ? styles.selected : ""}`}
+ className={`${styles.contactItem} ${contact.id === selectedContactId?.id ? styles.selected : ""}`}
>
-
- {contact.nickname.charAt(0)}
- }
- className={styles.avatar}
- />
-
+
{contact.nickname.charAt(0)}
+ }
+ className={styles.avatar}
+ />
{contact.nickname}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts
index afe7cb81..d2ee86d4 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts
@@ -28,6 +28,7 @@ export interface ContactData {
lastMessageTime: number;
unreadCount: number;
duplicate: boolean;
+ [key: string]: any;
}
//聊天会话类型
export type ChatType = "private" | "group";
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
index edfc2c74..c93fd09a 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
@@ -6,7 +6,7 @@ import {
TeamOutlined,
MessageOutlined,
} from "@ant-design/icons";
-import { ContactData, ChatSession } from "./data";
+import { ContactData } from "./data";
import WechatFriendsModule from "./WechatFriendsModule";
import MessageList from "../MessageList/index";
import styles from "./SidebarMenu.module.scss";
@@ -16,10 +16,10 @@ const { TabPane } = Tabs;
interface SidebarMenuProps {
contacts: ContactData[];
- chatSessions: ChatSession[];
- currentChat: ChatSession | null;
+ chatSessions: ContactData[];
+ currentChat: ContactData;
onContactClick: (contact: ContactData) => void;
- onChatSelect: (chat: ChatSession) => void;
+ onChatSelect: (chat: ContactData) => void;
loading?: boolean;
}
@@ -50,7 +50,7 @@ const SidebarMenu: React.FC
= ({
const getFilteredSessions = () => {
if (!searchText) return chatSessions;
return chatSessions.filter(session =>
- session.name.toLowerCase().includes(searchText.toLowerCase()),
+ session.nickname.toLowerCase().includes(searchText.toLowerCase()),
);
};
@@ -83,9 +83,9 @@ const SidebarMenu: React.FC = ({
key="chats"
>
= ({
{
const [messageApi, contextHolder] = message.useMessage();
const [contacts, setContacts] = useState([]);
- const [chatSessions, setChatSessions] = useState([]);
- const [currentChat, setCurrentChat] = useState(null);
+ const [chatSessions, setChatSessions] = useState([]);
+ const [currentChat, setCurrentChat] = useState(null);
const [loading, setLoading] = useState(false);
const [showProfile, setShowProfile] = useState(true);
const { login2 } = useUserStore();
@@ -55,18 +54,8 @@ const CkboxPage: React.FC = () => {
const handleContactClick = (contact: ContactData) => {
// 查找或创建聊天会话
- let session = chatSessions.find(s => s.id === contact.id);
+ const session = chatSessions.find(s => s.id === contact.id);
if (!session) {
- session = {
- id: contact.id,
- type: "private",
- name: contact.nickname,
- avatar: contact.avatar,
- lastMessage: "",
- lastTime: dayjs().toISOString(),
- unreadCount: 0,
- online: contact.online,
- };
setChatSessions(prev => [session!, ...prev]);
}
setCurrentChat(session);
From 465c63ed1ce14774c05460b2315ac1350dd18c33 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: Fri, 22 Aug 2025 16:54:24 +0800
Subject: [PATCH 33/78] =?UTF-8?q?feat(ckbox):=20=E6=B7=BB=E5=8A=A0?=
=?UTF-8?q?=E6=B6=88=E6=81=AF=E6=B8=85=E9=99=A4=E6=9C=AA=E8=AF=BB=E5=92=8C?=
=?UTF-8?q?=E8=8E=B7=E5=8F=96=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在api.ts中添加clearUnreadCount和getMessages接口
- 实现联系人点击时清除未读并获取消息
- 优化websocket重连逻辑,添加认证失败处理
- 修改登录凭证为karuo账户
- 添加递归获取所有联系人列表功能
---
Cunkebao/src/pages/pc/ckbox/api.ts | 49 +++++++----------
.../SidebarMenu/ContactListSimple.module.scss | 2 +-
Cunkebao/src/pages/pc/ckbox/index.tsx | 49 ++++++++---------
Cunkebao/src/pages/pc/ckbox/main.ts | 55 +++++++++++++++++--
Cunkebao/src/store/module/websocket.ts | 36 ++++++++++++
5 files changed, 129 insertions(+), 62 deletions(-)
diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts
index c79fe353..76ae1312 100644
--- a/Cunkebao/src/pages/pc/ckbox/api.ts
+++ b/Cunkebao/src/pages/pc/ckbox/api.ts
@@ -1,11 +1,8 @@
import request from "@/api/request2";
import {
- ContactData,
- ChatSession,
MessageData,
ChatHistoryResponse,
MessageType,
- GroupData,
OnlineStatus,
MessageStatus,
FileUploadResponse,
@@ -14,6 +11,25 @@ import {
ChatSettings,
} from "./data";
+//读取聊天信息
+//kf.quwanzhi.com:9991/api/WechatFriend/clearUnreadCount
+
+//获取聊天记录-1 清除未读
+export function clearUnreadCount(params) {
+ return request("/api/WechatFriend/clearUnreadCount", params, "PUT");
+}
+//获取聊天记录-2 获取列表
+export function getMessages(params: {
+ wechatAccountId: number;
+ wechatFriendId: number;
+ From: number;
+ To: number;
+ Count: number;
+ olderData: boolean;
+}) {
+ return request("/api/FriendMessage/SearchMessage", params, "PUT");
+}
+
//触客宝登陆
export function loginWithToken(params: any) {
return request(
@@ -92,33 +108,6 @@ export const markChatAsRead = (chatId: string): Promise => {
return request(`/v1/chats/${chatId}/read`, {}, "PUT");
};
-// 获取群组列表
-export const getGroupList = (): Promise => {
- return request("/v1/groups", {}, "GET");
-};
-
-// 创建群组
-export const createGroup = (data: {
- name: string;
- description?: string;
- memberIds: string[];
-}): Promise => {
- return request("/v1/groups", data, "POST");
-};
-
-// 获取群组详情
-export const getGroupDetail = (groupId: string): Promise => {
- return request(`/v1/groups/${groupId}`, {}, "GET");
-};
-
-// 更新群组信息
-export const updateGroup = (
- groupId: string,
- data: Partial,
-): Promise => {
- return request(`/v1/groups/${groupId}`, data, "PUT");
-};
-
// 添加群组成员
export const addGroupMembers = (
groupId: string,
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.module.scss
index c7a67ec9..5de994a5 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.module.scss
@@ -63,4 +63,4 @@
overflow: hidden;
text-overflow: ellipsis;
}
-}
\ No newline at end of file
+}
diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx
index 7452ce41..32e7ced6 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/index.tsx
@@ -8,7 +8,7 @@ import SidebarMenu from "./components/SidebarMenu/index";
import styles from "./index.module.scss";
const { Content } = Layout;
-import { loginWithToken } from "@/pages/login/api";
+import { loginWithToken, clearUnreadCount, getMessages } from "./api";
import { useUserStore } from "@/store/module/user";
import { chatInitAPIdata } from "./main";
@@ -23,6 +23,7 @@ const CkboxPage: React.FC = () => {
useEffect(() => {
// 方法一:使用 Promise 链式调用处理异步函数
+ setLoading(true);
chatInitAPIdata()
.then(contactList => {
console.log(contactList);
@@ -31,34 +32,32 @@ const CkboxPage: React.FC = () => {
})
.catch(error => {
console.error("获取联系人列表失败:", error);
+ })
+ .finally(() => {
+ setLoading(false);
});
}, []);
- const getToken2 = () => {
- return new Promise((resolve, reject) => {
- const params = {
- grant_type: "password",
- password: "kr123456",
- username: "kr_xf3",
- };
- loginWithToken(params)
- .then(res => {
- login2(res.access_token);
- resolve(res.access_token);
- })
- .catch(err => {
- reject(err);
- });
- });
- };
-
const handleContactClick = (contact: ContactData) => {
- // 查找或创建聊天会话
- const session = chatSessions.find(s => s.id === contact.id);
- if (!session) {
- setChatSessions(prev => [session!, ...prev]);
- }
- setCurrentChat(session);
+ console.log(contact);
+ clearUnreadCount([contact.id]).then(() => {
+ getMessages({
+ wechatAccountId: contact.wechatAccountId,
+ wechatFriendId: contact.id,
+ From: 0,
+ To: 0,
+ Count: 100,
+ olderData: false,
+ }).then(res => {
+ console.log(res);
+ });
+ });
+ // // 查找或创建聊天会话
+ // const session = chatSessions.find(s => s.id === contact.id);
+ // if (!session) {
+ // setChatSessions(prev => [session!, ...prev]);
+ // }
+ // setCurrentChat(session);
};
const handleSendMessage = async (message: string) => {
diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts
index 366f77b6..9af65aa0 100644
--- a/Cunkebao/src/pages/pc/ckbox/main.ts
+++ b/Cunkebao/src/pages/pc/ckbox/main.ts
@@ -28,10 +28,7 @@ export const chatInitAPIdata = async () => {
seq: 1,
});
//获取联系人列表
- const contactList = await getContactList({
- prevId: userInfo.tenant.tenantType,
- count: 100,
- });
+ const contactList = await getAllContactList();
return contactList;
} catch (error) {
@@ -40,6 +37,50 @@ export const chatInitAPIdata = async () => {
}
};
+// 递归获取所有联系人列表
+export const getAllContactList = async () => {
+ try {
+ let allContacts = [];
+ let prevId = 0;
+ const count = 1000;
+ let hasMore = true;
+
+ while (hasMore) {
+ console.log(`获取联系人列表,prevId: ${prevId}, count: ${count}`);
+ const contactList = await getContactList({
+ prevId,
+ count,
+ });
+
+ if (
+ !contactList ||
+ !Array.isArray(contactList) ||
+ contactList.length === 0
+ ) {
+ hasMore = false;
+ break;
+ }
+
+ allContacts = [...allContacts, ...contactList];
+
+ // 如果返回的数据少于请求的数量,说明已经没有更多数据了
+ if (contactList.length < count) {
+ hasMore = false;
+ } else {
+ // 获取最后一条数据的id作为下一次请求的prevId
+ const lastContact = contactList[contactList.length - 1];
+ prevId = lastContact.id;
+ }
+ }
+
+ console.log(`总共获取到 ${allContacts.length} 条联系人数据`);
+ return allContacts;
+ } catch (error) {
+ console.error("获取所有联系人列表失败:", error);
+ return [];
+ }
+};
+
export const getChatInfo = () => {
//获取UserId
sendCommand("CmdRequestWechatAccountsAliveStatus", {
@@ -53,8 +94,10 @@ const getToken = () => {
return new Promise((resolve, reject) => {
const params = {
grant_type: "password",
- password: "kr123456",
- username: "kr_xf3",
+ // password: "kr123456",
+ // username: "kr_xf3",
+ username: "karuo",
+ password: "zhiqun1984",
};
loginWithToken(params)
.then(res => {
diff --git a/Cunkebao/src/store/module/websocket.ts b/Cunkebao/src/store/module/websocket.ts
index 2d330f66..8525040d 100644
--- a/Cunkebao/src/store/module/websocket.ts
+++ b/Cunkebao/src/store/module/websocket.ts
@@ -233,6 +233,11 @@ export const useWebSocketStore = createPersistStore(
const currentState = get();
if (currentState.config) {
+ // 检查是否允许重连
+ if (!currentState.config.autoReconnect) {
+ console.log("自动重连已禁用,不再尝试重连");
+ return;
+ }
currentState.connect(currentState.config);
}
},
@@ -272,6 +277,32 @@ export const useWebSocketStore = createPersistStore(
const data = JSON.parse(event.data);
console.log("收到WebSocket消息:", data);
+ // 处理特定的通知消息
+ if (data.cmdType === "CmdNotify") {
+ // 处理Auth failed通知
+ if (data.notify === "Auth failed" || data.notify === "Kicked out") {
+ console.error(`WebSocket ${data.notify},断开连接`);
+ Toast.show({
+ content: `WebSocket ${data.notify},断开连接`,
+ position: "top",
+ });
+
+ // 禁用自动重连
+ if (get().config) {
+ set({
+ config: {
+ ...get().config!,
+ autoReconnect: false,
+ },
+ });
+ }
+
+ // 断开连接
+ get().disconnect();
+ return;
+ }
+ }
+
const currentState = get();
const newMessage: WebSocketMessage = {
id: Date.now().toString(),
@@ -311,7 +342,12 @@ export const useWebSocketStore = createPersistStore(
currentState.reconnectAttempts <
(currentState.config?.maxReconnectAttempts || 5)
) {
+ console.log("尝试自动重连...");
currentState._startReconnectTimer();
+ } else if (!currentState.config?.autoReconnect) {
+ console.log("自动重连已禁用,不再尝试重连");
+ // 重置重连计数
+ set({ reconnectAttempts: 0 });
}
},
From 83ae92b2b679a1d258ab7377fa798d60c5fa3f0d 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: Fri, 22 Aug 2025 17:55:04 +0800
Subject: [PATCH 34/78] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?=
=?UTF-8?q?=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
---
.../src/components/Layout/LayoutFiexd.tsx | 48 ++++++++
.../SidebarMenu/CustomLayout.module.scss | 0
.../SidebarMenu/SidebarMenu.module.scss | 108 +++++++++---------
.../pc/ckbox/components/SidebarMenu/index.tsx | 94 ++++++++-------
Cunkebao/src/pages/pc/ckbox/index.module.scss | 21 +++-
Cunkebao/src/pages/pc/ckbox/index.tsx | 92 ++++++++-------
6 files changed, 222 insertions(+), 141 deletions(-)
create mode 100644 Cunkebao/src/components/Layout/LayoutFiexd.tsx
create mode 100644 Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/CustomLayout.module.scss
diff --git a/Cunkebao/src/components/Layout/LayoutFiexd.tsx b/Cunkebao/src/components/Layout/LayoutFiexd.tsx
new file mode 100644
index 00000000..9a5cefd5
--- /dev/null
+++ b/Cunkebao/src/components/Layout/LayoutFiexd.tsx
@@ -0,0 +1,48 @@
+import React from "react";
+import { SpinLoading } from "antd-mobile";
+import styles from "./layout.module.scss";
+
+interface LayoutProps {
+ loading?: boolean;
+ children?: React.ReactNode;
+ header?: React.ReactNode;
+ footer?: React.ReactNode;
+}
+
+const LayoutFiexd: React.FC = ({
+ header,
+ children,
+ footer,
+ loading = false,
+}) => {
+ return (
+
+
{header}
+
+ {loading ? (
+
+ ) : (
+ children
+ )}
+
+
{footer}
+
+ );
+};
+
+export default LayoutFiexd;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/CustomLayout.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/CustomLayout.module.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss
index 1b77fa4d..6a60b1ef 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss
@@ -1,63 +1,61 @@
-.sidebar {
+.headerContainer {
background: #fff;
- border-right: 1px solid #f0f0f0;
- display: flex;
- flex-direction: column;
+ border-bottom: 1px solid #f0f0f0;
+}
- .searchBar {
- padding: 16px;
- border-bottom: 1px solid #f0f0f0;
- background: #fff;
+.searchBar {
+ padding: 16px 16px 8px;
+ background: #fff;
- :global(.ant-input) {
- border-radius: 20px;
- background: #f5f5f5;
- border: none;
+ :global(.ant-input) {
+ border-radius: 20px;
+ background: #f5f5f5;
+ border: none;
- &:focus {
- background: #fff;
- border: 1px solid #1890ff;
- box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
- }
- }
- }
-
- .tabs {
- flex: 1;
- display: flex;
- flex-direction: column;
-
- :global(.ant-tabs-content) {
- flex: 1;
- overflow: hidden;
- }
-
- :global(.ant-tabs-tabpane) {
- height: 100%;
- overflow: hidden;
- }
-
- :global(.ant-tabs-nav) {
- margin: 0;
- padding: 0 16px;
+ &:focus {
background: #fff;
- border-bottom: 1px solid #f0f0f0;
-
- :global(.ant-tabs-tab) {
- padding: 12px 0;
- margin: 0 16px 0 0;
- }
+ border: 1px solid #1890ff;
+ box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
}
-
- .emptyState {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: 100%;
- color: #999;
- padding: 20px;
- text-align: center;
- }
+}
+
+.tabsContainer {
+ display: flex;
+ padding: 0 16px 8px;
+ border-bottom: 1px solid #f0f0f0;
+
+ .tabItem {
+ display: flex;
+ align-items: center;
+ padding: 8px 12px;
+ cursor: pointer;
+ border-radius: 4px;
+ transition: all 0.3s;
+
+ &:hover {
+ color: #1890ff;
+ background-color: rgba(24, 144, 255, 0.1);
+ }
+
+ &.active {
+ color: #1890ff;
+ background-color: rgba(24, 144, 255, 0.1);
+ }
+
+ span {
+ margin-left: 4px;
+ }
+ }
+}
+
+.emptyState {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ color: #999;
+ padding: 20px;
+ text-align: center;
}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
index c93fd09a..81560309 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
@@ -1,5 +1,5 @@
import React, { useState } from "react";
-import { Layout, Input, Tabs } from "antd";
+import { Layout as AntLayout, Input, Tabs } from "antd";
import {
SearchOutlined,
UserOutlined,
@@ -9,9 +9,10 @@ import {
import { ContactData } from "./data";
import WechatFriendsModule from "./WechatFriendsModule";
import MessageList from "../MessageList/index";
+import LayoutFiexd from "@/components/Layout/LayoutFiexd";
import styles from "./SidebarMenu.module.scss";
-const { Sider } = Layout;
+const { Sider } = AntLayout;
const { TabPane } = Tabs;
interface SidebarMenuProps {
@@ -54,8 +55,9 @@ const SidebarMenu: React.FC = ({
);
};
- return (
-
+ // 渲染Header部分,包含搜索框和标签页切换
+ const renderHeader = () => (
+
{/* 搜索栏 */}
= ({
/>
- {/* 标签页 */}
-
-
-
- 聊天
-
- }
- key="chats"
+ {/* 标签页切换 */}
+
+
setActiveTab("chats")}
>
+
+ 聊天
+
+
setActiveTab("contacts")}
+ >
+
+ 联系人
+
+
setActiveTab("groups")}
+ >
+
+ 群组
+
+
+
+ );
+
+ // 渲染内容部分
+ const renderContent = () => {
+ switch (activeTab) {
+ case "chats":
+ return (
-
-
-
- 联系人
-
- }
- key="contacts"
- >
+ );
+ case "contacts":
+ return (
-
-
-
- 群组
-
- }
- key="groups"
- >
+ );
+ case "groups":
+ return (
-
-
-
+ );
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+ {renderContent()}
+
);
};
diff --git a/Cunkebao/src/pages/pc/ckbox/index.module.scss b/Cunkebao/src/pages/pc/ckbox/index.module.scss
index 2237cce9..113abc01 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/index.module.scss
@@ -1,10 +1,27 @@
.ckboxLayout {
height: 100vh;
background: #fff;
+ display: flex;
+ flex-direction: column;
- .sidebar {
+ .header {
+ background: #1890ff;
+ color: #fff;
+ height: 64px;
+ line-height: 64px;
+ padding: 0 24px;
+ font-size: 18px;
+ font-weight: bold;
+ }
+
+ .sider {
background: #fff;
border-right: 1px solid #f0f0f0;
+ overflow: auto;
+ }
+
+ .sidebar {
+ height: 100%;
display: flex;
flex-direction: column;
@@ -87,6 +104,8 @@
background: #f5f5f5;
display: flex;
flex-direction: column;
+ padding: 16px;
+ overflow: auto;
.chatContainer {
height: 100%;
diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx
index 32e7ced6..fb5f392c 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/index.tsx
@@ -7,7 +7,7 @@ import ChatWindow from "./components/ChatWindow/index";
import SidebarMenu from "./components/SidebarMenu/index";
import styles from "./index.module.scss";
-const { Content } = Layout;
+const { Header, Content, Sider } = Layout;
import { loginWithToken, clearUnreadCount, getMessages } from "./api";
import { useUserStore } from "@/store/module/user";
import { chatInitAPIdata } from "./main";
@@ -85,53 +85,57 @@ const CkboxPage: React.FC = () => {
return (
- {/* */}
{contextHolder}
- {/* 左侧边栏 */}
-
+
+
+ {/* 左侧边栏 */}
+
+
+
- {/* 主内容区 */}
-
- {currentChat ? (
-
-
-
-
- }
- onClick={() => setShowProfile(!showProfile)}
- size="small"
- >
- {showProfile ? "隐藏资料" : "显示资料"}
-
-
-
+ {/* 主内容区 */}
+
+ {currentChat ? (
+
+
+
+
+ }
+ onClick={() => setShowProfile(!showProfile)}
+ size="small"
+ >
+ {showProfile ? "隐藏资料" : "显示资料"}
+
+
+
+
+
setShowProfile(!showProfile)}
+ />
- setShowProfile(!showProfile)}
- />
-
- ) : (
-
-
-
-
欢迎使用触客宝
-
选择一个联系人开始聊天
+ ) : (
+
+
+
+
欢迎使用触客宝
+
选择一个联系人开始聊天
+
-
- )}
-
+ )}
+
+
);
};
From c12633b2b14cc5c5861359f3b05513b9f7ba71c6 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: Mon, 25 Aug 2025 14:05:32 +0800
Subject: [PATCH 35/78] =?UTF-8?q?refactor(ckbox):=20=E9=87=8D=E6=9E=84?=
=?UTF-8?q?=E8=81=8A=E5=A4=A9=E6=A8=A1=E5=9D=97=E6=8E=A5=E5=8F=A3=E5=92=8C?=
=?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=AE=9A=E4=B9=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将HTTP方法从PUT改为GET以符合REST规范
- 移除测试用的硬编码凭证
- 新增ChatRecord接口并替换原有ChatSession
- 合并重复的样式文件并重命名
- 优化联系人点击处理逻辑
---
Cunkebao/src/pages/pc/ckbox/api.ts | 2 +-
.../pc/ckbox/components/ChatWindow/index.tsx | 5 +-
....module.scss => WechatFriends.module.scss} | 0
.../SidebarMenu/WechatFriendsModule.tsx | 12 +++--
.../pc/ckbox/components/SidebarMenu/data.ts | 48 -------------------
.../pc/ckbox/components/SidebarMenu/index.tsx | 7 +--
Cunkebao/src/pages/pc/ckbox/data.ts | 24 ++++++++++
Cunkebao/src/pages/pc/ckbox/index.tsx | 28 ++++-------
Cunkebao/src/pages/pc/ckbox/main.ts | 8 ++--
9 files changed, 49 insertions(+), 85 deletions(-)
rename Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/{ContactListSimple.module.scss => WechatFriends.module.scss} (100%)
delete mode 100644 Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts
diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts
index 76ae1312..58236f8b 100644
--- a/Cunkebao/src/pages/pc/ckbox/api.ts
+++ b/Cunkebao/src/pages/pc/ckbox/api.ts
@@ -27,7 +27,7 @@ export function getMessages(params: {
Count: number;
olderData: boolean;
}) {
- return request("/api/FriendMessage/SearchMessage", params, "PUT");
+ return request("/api/FriendMessage/SearchMessage", params, "GET");
}
//触客宝登陆
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
index 3ba0b11b..9c7b0bda 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
@@ -40,7 +40,8 @@ import {
MessageOutlined,
} from "@ant-design/icons";
import dayjs from "dayjs";
-import { ChatSession, MessageData, MessageType } from "../../data";
+import { MessageData, MessageType } from "../../data";
+import { ChatRecord } from "@/pages/pc/ckbox/data";
import { getChatMessage } from "../../api";
import styles from "./ChatWindow.module.scss";
@@ -48,7 +49,7 @@ const { Header, Content, Footer, Sider } = Layout;
const { TextArea } = Input;
interface ChatWindowProps {
- chat: ChatSession;
+ chat: ChatRecord;
onSendMessage: (message: string) => void;
showProfile?: boolean;
onToggleProfile?: () => void;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends.module.scss
similarity index 100%
rename from Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactListSimple.module.scss
rename to Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends.module.scss
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
index 0ff78aec..56782ac6 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
@@ -1,15 +1,15 @@
import React from "react";
-import { List, Avatar, Badge } from "antd";
+import { List, Avatar } from "antd";
import { ContactData } from "./data";
-import styles from "./ContactListSimple.module.scss";
+import styles from "./WechatFriends.module.scss";
-interface ContactListSimpleProps {
+interface WechatFriendsProps {
contacts: ContactData[];
onContactClick: (contact: ContactData) => void;
selectedContactId?: ContactData;
}
-const ContactListSimple: React.FC = ({
+const ContactListSimple: React.FC = ({
contacts,
onContactClick,
selectedContactId,
@@ -36,7 +36,9 @@ const ContactListSimple: React.FC = ({
/>
-
{contact.nickname}
+
+ {contact.conRemark || contact.nickname}
+
)}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts
deleted file mode 100644
index d2ee86d4..00000000
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-// 联系人数据接口
-export interface ContactData {
- id?: number;
- wechatAccountId: number;
- wechatId: string;
- alias: string;
- conRemark: string;
- nickname: string;
- quanPin: string;
- avatar?: string;
- gender: number;
- region: string;
- addFrom: number;
- phone: string;
- labels: string[];
- signature: string;
- accountId: number;
- extendFields: null;
- city?: string;
- lastUpdateTime: string;
- isPassed: boolean;
- tenantId: number;
- groupId: number;
- thirdParty: null;
- additionalPicture: string;
- desc: string;
- config: null;
- lastMessageTime: number;
- unreadCount: number;
- duplicate: boolean;
- [key: string]: any;
-}
-//聊天会话类型
-export type ChatType = "private" | "group";
-// 聊天会话接口
-export interface ChatSession {
- id: string;
- type: ChatType;
- name: string;
- avatar?: string;
- lastMessage: string;
- lastTime: string;
- unreadCount: number;
- online: boolean;
- members?: string[];
- pinned?: boolean;
- muted?: boolean;
-}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
index 81560309..1b784737 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
@@ -6,18 +6,15 @@ import {
TeamOutlined,
MessageOutlined,
} from "@ant-design/icons";
-import { ContactData } from "./data";
+import { ContactData, ChatRecord } from "@/pages/pc/ckbox/data";
import WechatFriendsModule from "./WechatFriendsModule";
import MessageList from "../MessageList/index";
import LayoutFiexd from "@/components/Layout/LayoutFiexd";
import styles from "./SidebarMenu.module.scss";
-const { Sider } = AntLayout;
-const { TabPane } = Tabs;
-
interface SidebarMenuProps {
contacts: ContactData[];
- chatSessions: ContactData[];
+ chatSessions: ChatRecord[];
currentChat: ContactData;
onContactClick: (contact: ContactData) => void;
onChatSelect: (chat: ContactData) => void;
diff --git a/Cunkebao/src/pages/pc/ckbox/data.ts b/Cunkebao/src/pages/pc/ckbox/data.ts
index 25132751..4390a714 100644
--- a/Cunkebao/src/pages/pc/ckbox/data.ts
+++ b/Cunkebao/src/pages/pc/ckbox/data.ts
@@ -31,6 +31,30 @@ export interface ContactData {
[key: string]: any;
}
+//聊天记录接口
+export interface ChatRecord {
+ 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;
+ [key: string]: any;
+}
+
/**
* 微信好友基本信息接口
* 包含主要字段和兼容性字段
diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx
index fb5f392c..6e0be50b 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/index.tsx
@@ -2,24 +2,22 @@ import React, { useState, useEffect } from "react";
import { Layout, Button, Space, message, Tooltip } from "antd";
import { InfoCircleOutlined, MessageOutlined } from "@ant-design/icons";
import dayjs from "dayjs";
-import { ContactData } from "./data";
+import { ContactData, ChatRecord } from "./data";
import ChatWindow from "./components/ChatWindow/index";
import SidebarMenu from "./components/SidebarMenu/index";
import styles from "./index.module.scss";
const { Header, Content, Sider } = Layout;
-import { loginWithToken, clearUnreadCount, getMessages } from "./api";
-import { useUserStore } from "@/store/module/user";
+import { clearUnreadCount, getMessages } from "./api";
import { chatInitAPIdata } from "./main";
const CkboxPage: React.FC = () => {
const [messageApi, contextHolder] = message.useMessage();
const [contacts, setContacts] = useState
([]);
- const [chatSessions, setChatSessions] = useState([]);
+ const [chatSessions, setChatSessions] = useState([]);
const [currentChat, setCurrentChat] = useState(null);
const [loading, setLoading] = useState(false);
const [showProfile, setShowProfile] = useState(true);
- const { login2 } = useUserStore();
useEffect(() => {
// 方法一:使用 Promise 链式调用处理异步函数
@@ -39,25 +37,18 @@ const CkboxPage: React.FC = () => {
}, []);
const handleContactClick = (contact: ContactData) => {
- console.log(contact);
clearUnreadCount([contact.id]).then(() => {
getMessages({
wechatAccountId: contact.wechatAccountId,
wechatFriendId: contact.id,
- From: 0,
- To: 0,
+ From: 1,
+ To: 1751344975916,
Count: 100,
- olderData: false,
- }).then(res => {
- console.log(res);
+ olderData: true,
+ }).then(session => {
+ setChatSessions(session);
});
});
- // // 查找或创建聊天会话
- // const session = chatSessions.find(s => s.id === contact.id);
- // if (!session) {
- // setChatSessions(prev => [session!, ...prev]);
- // }
- // setCurrentChat(session);
};
const handleSendMessage = async (message: string) => {
@@ -72,9 +63,6 @@ const CkboxPage: React.FC = () => {
unreadCount: 0,
};
- setChatSessions(prev =>
- prev.map(s => (s.id === currentChat.id ? updatedSession : s)),
- );
setCurrentChat(updatedSession);
messageApi.success("消息发送成功");
diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts
index 9af65aa0..ba05406e 100644
--- a/Cunkebao/src/pages/pc/ckbox/main.ts
+++ b/Cunkebao/src/pages/pc/ckbox/main.ts
@@ -94,10 +94,10 @@ const getToken = () => {
return new Promise((resolve, reject) => {
const params = {
grant_type: "password",
- // password: "kr123456",
- // username: "kr_xf3",
- username: "karuo",
- password: "zhiqun1984",
+ password: "kr123456",
+ username: "kr_xf3",
+ // username: "karuo",
+ // password: "zhiqun1984",
};
loginWithToken(params)
.then(res => {
From f7a27c7c6304053f51b7dde3399375ef4b551683 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, 26 Aug 2025 11:48:10 +0800
Subject: [PATCH 36/78] =?UTF-8?q?feat(ckbox):=20=E5=AE=9E=E7=8E=B0?=
=?UTF-8?q?=E8=81=8A=E5=A4=A9=E4=BC=9A=E8=AF=9D=E7=AE=A1=E7=90=86=E5=92=8C?=
=?UTF-8?q?=E5=A4=9A=E5=AA=92=E4=BD=93=E6=B6=88=E6=81=AF=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在ckchat store中添加聊天会话管理功能
- 支持多种类型消息渲染(图片、视频、音频、文件等)
- 优化聊天界面样式和时间分组显示
- 修复联系人数据结构命名问题
- 添加群聊列表获取功能
---
Cunkebao/src/pages/login/Login.tsx | 1 -
Cunkebao/src/pages/pc/ckbox/README.md | 4 +-
Cunkebao/src/pages/pc/ckbox/api.ts | 8 +
.../ChatWindow/ChatWindow.module.scss | 200 ++++-
.../pc/ckbox/components/ChatWindow/index.tsx | 704 ++++++++++++++----
.../pc/ckbox/components/ContactList/index.tsx | 6 +-
.../pc/ckbox/components/MessageList/data.ts | 2 +-
.../pc/ckbox/components/MessageList/index.tsx | 8 +-
.../SidebarMenu/WechatFriendsModule.tsx | 8 +-
.../pc/ckbox/components/SidebarMenu/index.tsx | 17 +-
Cunkebao/src/pages/pc/ckbox/data.ts | 25 +-
Cunkebao/src/pages/pc/ckbox/index.tsx | 44 +-
Cunkebao/src/pages/pc/ckbox/main.ts | 58 +-
Cunkebao/src/store/module/ckchat.ts | 38 +-
Cunkebao/src/store/module/websocket.ts | 1 +
15 files changed, 939 insertions(+), 185 deletions(-)
diff --git a/Cunkebao/src/pages/login/Login.tsx b/Cunkebao/src/pages/login/Login.tsx
index 7cf8832c..40cf8816 100644
--- a/Cunkebao/src/pages/login/Login.tsx
+++ b/Cunkebao/src/pages/login/Login.tsx
@@ -7,7 +7,6 @@ import {
UserOutline,
} from "antd-mobile-icons";
import { useUserStore } from "@/store/module/user";
-import { useCkChatStore } from "@/store/module/ckchat";
import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api";
import style from "./login.module.scss";
diff --git a/Cunkebao/src/pages/pc/ckbox/README.md b/Cunkebao/src/pages/pc/ckbox/README.md
index 8f09df32..e4450c69 100644
--- a/Cunkebao/src/pages/pc/ckbox/README.md
+++ b/Cunkebao/src/pages/pc/ckbox/README.md
@@ -89,10 +89,10 @@ ckbox/
## 数据结构
-### 联系人 (ContactData)
+### 联系人 (ContractData)
```typescript
-interface ContactData {
+interface ContractData {
id: string;
name: string;
phone: string;
diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts
index 58236f8b..c58cff74 100644
--- a/Cunkebao/src/pages/pc/ckbox/api.ts
+++ b/Cunkebao/src/pages/pc/ckbox/api.ts
@@ -29,6 +29,14 @@ export function getMessages(params: {
}) {
return request("/api/FriendMessage/SearchMessage", params, "GET");
}
+//获取群列表
+export function getChatRoomList(params: { prevId: number; count: number }) {
+ return request(
+ "/api/wechatChatroom/listExcludeMembersByPage?",
+ params,
+ "GET",
+ );
+}
//触客宝登陆
export function loginWithToken(params: any) {
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 076eaf59..8d7afa12 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss
@@ -373,13 +373,26 @@
.messageItem {
margin-bottom: 16px;
+ position: relative;
.messageContent {
display: flex;
align-items: flex-start;
gap: 8px;
max-width: 70%;
+ }
+}
+.messageTime {
+ text-align: center;
+ padding: 8px 0;
+ font-size: 12px;
+ color: #999;
+ margin: 16px 0;
+}
+
+.messageItem {
+ .messageContent {
.messageAvatar {
flex-shrink: 0;
}
@@ -389,6 +402,7 @@
border-radius: 12px;
padding: 8px 12px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+ max-width: 100%;
.messageSender {
font-size: 12px;
@@ -402,11 +416,189 @@
word-break: break-word;
}
+ .emojiMessage {
+ img {
+ max-width: 120px;
+ max-height: 120px;
+ border-radius: 4px;
+ display: block;
+ cursor: pointer;
+ }
+ }
+
+ .imageMessage {
+ img {
+ max-width: 100%;
+ border-radius: 8px;
+ display: block;
+ cursor: pointer;
+ }
+ }
+
+ .videoMessage {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+
+ video {
+ max-width: 100%;
+ border-radius: 8px;
+ display: block;
+ }
+
+ .videoContainer {
+ position: relative;
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ overflow: hidden;
+ border-radius: 8px;
+
+ &:hover .videoPlayIcon {
+ transform: scale(1.1);
+ }
+
+ .videoThumbnail {
+ width: 100%;
+ display: block;
+ border-radius: 8px;
+ }
+
+ .videoPlayIcon {
+ position: absolute;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: rgba(0, 0, 0, 0.5);
+ border-radius: 50%;
+ width: 60px;
+ height: 60px;
+ transition: transform 0.2s ease;
+
+ .loadingSpinner {
+ width: 32px;
+ height: 32px;
+ border: 3px solid rgba(255, 255, 255, 0.3);
+ border-radius: 50%;
+ border-top-color: #fff;
+ animation: spin 1s linear infinite;
+ }
+ }
+
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+ }
+
+ .downloadButton {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 18px;
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ transition: all 0.2s;
+
+ &:hover {
+ color: #40a9ff;
+ }
+ }
+ }
+
+ .audioMessage {
+ position: relative;
+ display: flex;
+ align-items: center;
+ background: #f5f5f5;
+ border-radius: 8px;
+ padding: 8px;
+
+ audio {
+ flex: 1;
+ min-width: 200px;
+ }
+
+ .downloadButton {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #1890ff;
+ font-size: 18px;
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ margin-left: 8px;
+ transition: all 0.2s;
+
+ &:hover {
+ color: #40a9ff;
+ }
+ }
+ }
+
+ .fileMessage {
+ background: #f5f5f5;
+ border-radius: 8px;
+ padding: 8px;
+ display: flex;
+ align-items: center;
+ position: relative;
+ transition: background-color 0.2s;
+ width: 240px;
+
+ &:hover {
+ background: #e6f7ff;
+ }
+
+ .fileInfo {
+ flex: 1;
+ margin-right: 8px;
+ cursor: pointer;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .downloadButton {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #1890ff;
+ font-size: 18px;
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ transition: all 0.2s;
+
+ &:hover {
+ color: #40a9ff;
+ }
+ }
+ }
+
+ .locationMessage {
+ background: #f5f5f5;
+ border-radius: 8px;
+ padding: 8px;
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ transition: background-color 0.2s;
+
+ &:hover {
+ background: #fff2e8;
+ }
+ }
+
.messageTime {
- font-size: 11px;
- color: #bfbfbf;
- margin-top: 4px;
- text-align: right;
+ display: none;
}
}
}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
index 9c7b0bda..9b275b10 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
@@ -9,12 +9,9 @@ import {
Menu,
message,
Tooltip,
- Divider,
Badge,
Card,
Tag,
- Row,
- Col,
Modal,
} from "antd";
import {
@@ -36,118 +33,65 @@ import {
EnvironmentOutlined as LocationOutlined,
AudioOutlined,
AudioOutlined as AudioHoldOutlined,
+ DownloadOutlined,
CodeSandboxOutlined,
MessageOutlined,
+ FileOutlined,
+ FilePdfOutlined,
+ FileWordOutlined,
+ FileExcelOutlined,
+ FilePptOutlined,
+ PlayCircleFilled,
} from "@ant-design/icons";
import dayjs from "dayjs";
-import { MessageData, MessageType } from "../../data";
-import { ChatRecord } from "@/pages/pc/ckbox/data";
-import { getChatMessage } from "../../api";
+import { ChatRecord, ContractData } from "@/pages/pc/ckbox/data";
+import { clearUnreadCount, getMessages } from "@/pages/pc/ckbox/api";
import styles from "./ChatWindow.module.scss";
+import { useWebSocketStore } from "@/store/module/websocket";
+const { sendCommand } = useWebSocketStore.getState();
const { Header, Content, Footer, Sider } = Layout;
const { TextArea } = Input;
interface ChatWindowProps {
- chat: ChatRecord;
+ contract: ContractData;
onSendMessage: (message: string) => void;
showProfile?: boolean;
onToggleProfile?: () => void;
}
const ChatWindow: React.FC = ({
- chat,
+ contract,
onSendMessage,
showProfile = true,
onToggleProfile,
}) => {
const [messageApi, contextHolder] = message.useMessage();
- const [messages, setMessages] = useState([]);
+ const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState("");
const [loading, setLoading] = useState(false);
const [showMaterialModal, setShowMaterialModal] = useState(false);
const messagesEndRef = useRef(null);
useEffect(() => {
- fetchChatHistory();
- }, [chat.id]);
+ clearUnreadCount([contract.id]).then(() => {
+ getMessages({
+ wechatAccountId: contract.wechatAccountId,
+ wechatFriendId: contract.id,
+ From: 1,
+ To: +new Date() + 1000,
+ Count: 100,
+ olderData: true,
+ }).then(msg => {
+ setMessages(msg);
+ });
+ });
+ }, [contract.id]);
useEffect(() => {
scrollToBottom();
}, [messages]);
- const fetchChatHistory = async () => {
- try {
- setLoading(true);
-
- // 从chat对象中提取wechatFriendId
- // 假设chat.id存储的是wechatFriendId
- const wechatFriendId = parseInt(chat.id);
-
- if (isNaN(wechatFriendId)) {
- messageApi.error("无效的聊天ID");
- return;
- }
-
- // 调用API获取聊天历史
- const response = await getChatMessage({
- wechatAccountId: 32686452, // 使用实际的wechatAccountId
- wechatFriendId: wechatFriendId,
- From: 0,
- To: 0,
- Count: 50, // 获取最近的50条消息
- olderData: false,
- keyword: "",
- });
-
- console.log("聊天历史响应:", response);
-
- if (response && Array.isArray(response)) {
- // 将API返回的消息记录转换为MessageData格式
- const chatMessages: MessageData[] = response.map(item => {
- // 解析content字段,它是一个JSON字符串
- let msgContent = "";
- try {
- const contentObj = JSON.parse(item.content);
- msgContent = contentObj.content || "";
- } catch (e) {
- msgContent = item.content;
- }
-
- // 判断消息是发送还是接收
- const isSend = item.isSend === true;
-
- return {
- id: item.id.toString(),
- senderId: isSend ? "me" : "other",
- senderName: isSend ? "我" : chat.name,
- content: msgContent,
- type: MessageType.TEXT, // 默认为文本类型,实际应根据msgType字段判断
- timestamp:
- item.createTime || new Date(item.wechatTime).toISOString(),
- isRead: true, // 默认已读
- };
- });
-
- // 按时间排序
- chatMessages.sort(
- (a, b) =>
- new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),
- );
-
- setMessages(chatMessages);
- } else {
- // 如果没有消息,显示空数组
- setMessages([]);
- }
- } catch (error) {
- console.error("获取聊天记录失败:", error);
- messageApi.error("获取聊天记录失败");
- } finally {
- setLoading(false);
- }
- };
-
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
@@ -156,14 +100,26 @@ const ChatWindow: React.FC = ({
if (!inputValue.trim()) return;
try {
- const newMessage: MessageData = {
- id: Date.now().toString(),
- senderId: "me",
- senderName: "我",
+ const newMessage: ChatRecord = {
+ id: contract.id,
+ wechatAccountId: contract.wechatAccountId,
+ wechatFriendId: contract.id,
+ tenantId: 0,
+ accountId: 0,
+ synergyAccountId: 0,
content: inputValue,
- type: MessageType.TEXT,
- timestamp: dayjs().toISOString(),
- isRead: false,
+ msgType: 0,
+ msgSubType: 0,
+ msgSvrId: "",
+ isSend: false,
+ createTime: "",
+ isDeleted: false,
+ deleteTime: "",
+ sendStatus: 0,
+ wechatTime: 0,
+ origin: 0,
+ msgId: 0,
+ recalled: false,
};
setMessages(prev => [...prev, newMessage]);
@@ -221,7 +177,498 @@ const ChatWindow: React.FC = ({
// 这里可以根据不同的素材类型显示不同的模态框
};
- const renderMessage = (msg: MessageData) => {
+ // 处理视频播放请求,发送socket请求获取真实视频地址
+ const handleVideoPlayRequest = (tencentUrl: string, messageId: string) => {
+ // 构建socket请求数据
+ sendCommand("CmdDownloadVideo", {
+ chatroomMessageId: contract.chatroomId ? contract.chatroomId : 0,
+ friendMessageId: contract.id,
+ seq: 9,
+ tencentUrl: tencentUrl,
+ wechatAccountId: contract.wechatAccountId,
+ });
+
+ // 更新消息状态为加载中
+ 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;
+ });
+ });
+
+ // TODO: 这里应该实现实际的socket请求发送逻辑
+ console.log("发送视频请求:", socketData);
+
+ // 模拟获取视频地址后的处理
+ // 实际应用中,这里应该是socket响应的回调处理
+ setTimeout(() => {
+ // 模拟获取到视频地址
+ const videoUrl = "https://example.com/video.mp4";
+
+ // 更新消息内容,将预览图替换为实际视频
+ setMessages(prevMessages => {
+ return prevMessages.map(msg => {
+ if (msg.id === messageId) {
+ // 创建新的视频消息内容
+ return {
+ ...msg,
+ content: videoUrl,
+ };
+ }
+ return msg;
+ });
+ });
+
+ messageApi.success("视频加载成功");
+ }, 1500);
+ };
+
+ // 解析消息内容,判断消息类型并返回对应的渲染内容
+ const parseMessageContent = (content: string, msg: ChatRecord) => {
+ // 检查是否为表情包
+ if (
+ typeof content === "string" &&
+ content.includes("ac-weremote-s2.oss-cn-shenzhen.aliyuncs.com") &&
+ content.includes("#")
+ ) {
+ return (
+
+

window.open(content, "_blank")}
+ />
+
+ );
+ }
+
+ // 检查是否为带预览图的视频消息
+ try {
+ if (
+ typeof content === "string" &&
+ content.trim().startsWith("{") &&
+ content.trim().endsWith("}")
+ ) {
+ const videoData = JSON.parse(content);
+ // 处理用户提供的JSON格式 {"previewImage":"https://...", "tencentUrl":"..."}
+ 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);
+ };
+
+ // 检查是否处于加载状态
+ if (videoData.isLoading) {
+ return (
+
+
+

+
+
+
+ );
+ }
+
+ return (
+
+
+

+
+
+
+ );
+ }
+ // 保留原有的视频处理逻辑
+ else if (
+ videoData.type === "video" &&
+ videoData.url &&
+ videoData.thumb
+ ) {
+ return (
+
+ );
+ }
+ }
+ } catch (e) {
+ // 解析JSON失败,不是视频消息
+ console.log("解析视频消息失败:", e);
+ }
+
+ // 检查是否为图片链接
+ if (
+ typeof content === "string" &&
+ (content.match(/\.(jpg|jpeg|png|gif)$/i) ||
+ (content.includes("oss-cn-shenzhen.aliyuncs.com") &&
+ content.includes(".jpg")))
+ ) {
+ return (
+
+

window.open(content, "_blank")}
+ />
+
+ );
+ }
+
+ // 检查是否为视频链接
+ if (
+ typeof content === "string" &&
+ (content.match(/\.(mp4|avi|mov|wmv|flv)$/i) ||
+ (content.includes("oss-cn-shenzhen.aliyuncs.com") &&
+ content.includes(".mp4")))
+ ) {
+ return (
+
+ );
+ }
+
+ // 检查是否为音频链接
+ if (
+ typeof content === "string" &&
+ (content.match(/\.(mp3|wav|ogg|m4a)$/i) ||
+ (content.includes("oss-cn-shenzhen.aliyuncs.com") &&
+ content.includes(".mp3")))
+ ) {
+ return (
+
+ );
+ }
+
+ // 检查是否为Office文件链接
+ if (
+ typeof content === "string" &&
+ content.match(/\.(doc|docx|xls|xlsx|ppt|pptx|pdf)$/i)
+ ) {
+ const fileName = content.split("/").pop() || "文件";
+ const fileExt = fileName.split(".").pop()?.toLowerCase();
+
+ // 根据文件类型选择不同的图标
+ let fileIcon = (
+
+ );
+
+ if (fileExt === "pdf") {
+ fileIcon = (
+
+ );
+ } else if (fileExt === "doc" || fileExt === "docx") {
+ fileIcon = (
+
+ );
+ } else if (fileExt === "xls" || fileExt === "xlsx") {
+ fileIcon = (
+
+ );
+ } else if (fileExt === "ppt" || fileExt === "pptx") {
+ fileIcon = (
+
+ );
+ }
+
+ return (
+
+ );
+ }
+
+ // 检查是否为文件消息(JSON格式)
+ try {
+ if (
+ typeof content === "string" &&
+ content.trim().startsWith("{") &&
+ content.trim().endsWith("}")
+ ) {
+ const fileData = JSON.parse(content);
+ if (fileData.type === "file" && fileData.title) {
+ // 检查是否为Office文件
+ const fileExt = fileData.title.split(".").pop()?.toLowerCase();
+ let fileIcon = (
+
+ );
+
+ if (fileExt === "pdf") {
+ fileIcon = (
+
+ );
+ } else if (fileExt === "doc" || fileExt === "docx") {
+ fileIcon = (
+
+ );
+ } else if (fileExt === "xls" || fileExt === "xlsx") {
+ fileIcon = (
+
+ );
+ } else if (fileExt === "ppt" || fileExt === "pptx") {
+ fileIcon = (
+
+ );
+ }
+
+ return (
+
+ );
+ }
+ }
+ } catch (e) {
+ // 解析JSON失败,不是文件消息
+ }
+
+ // 检查是否为位置信息
+ if (
+ typeof content === "string" &&
+ (content.includes("
+
+
+
{label}
+ {coordinates && (
+
+ {coordinates}
+
+ )}
+
+
+ );
+ }
+
+ // 默认为文本消息
+ return {content}
;
+ };
+
+ // 用于分组消息并添加时间戳的辅助函数
+ const groupMessagesByTime = (messages: ChatRecord[]) => {
+ const groups: { time: string; messages: ChatRecord[] }[] = [];
+ let currentDate = "";
+
+ messages.forEach(msg => {
+ const messageDate = dayjs(msg.timestamp).format("YYYY-MM-DD");
+ const messageTime = dayjs(msg.timestamp).format("HH:mm");
+
+ if (messageDate !== currentDate) {
+ currentDate = messageDate;
+ groups.push({ time: messageTime, messages: [msg] });
+ } else {
+ const lastGroup = groups[groups.length - 1];
+ // 如果时间相差超过5分钟,创建新的时间组
+ const lastMessageTime =
+ lastGroup.messages[lastGroup.messages.length - 1].timestamp;
+ const timeDiff = Math.abs(
+ dayjs(msg.timestamp).diff(dayjs(lastMessageTime), "minute"),
+ );
+
+ if (timeDiff >= 5) {
+ groups.push({ time: messageTime, messages: [msg] });
+ } else {
+ lastGroup.messages.push(msg);
+ }
+ }
+ });
+
+ return groups;
+ };
+
+ const renderMessage = (msg: ChatRecord) => {
const isOwn = msg.senderId === "me";
return (
= ({
{!isOwn && (
}
className={styles.messageAvatar}
/>
@@ -243,10 +690,7 @@ const ChatWindow: React.FC
= ({
{!isOwn && (
{msg.senderName}
)}
- {msg.content}
-
- {dayjs(msg.timestamp).format("HH:mm")}
-
+ {parseMessageContent(msg.content, msg)}
@@ -275,9 +719,9 @@ const ChatWindow: React.FC = ({
);
// 模拟联系人详细信息
- const contactInfo = {
- name: chat.name,
- avatar: chat.avatar,
+ const contractInfo = {
+ name: contract.name,
+ avatar: contract.avatar,
phone: "13800138001",
email: "zhangsan@example.com",
department: "技术部",
@@ -300,8 +744,10 @@ const ChatWindow: React.FC = ({
:
}
+ src={contract.avatar}
+ icon={
+ contract.type === "group" ?
:
+ }
/>
= ({
}}
>
- {chat.name}
- {chat.online && (
+ {contract.name}
+ {contract.online && (
在线
)}
@@ -351,7 +797,12 @@ const ChatWindow: React.FC
= ({
) : (
<>
- {messages.map(renderMessage)}
+ {groupMessagesByTime(messages).map((group, groupIndex) => (
+
+ {group.time}
+ {group.messages.map(renderMessage)}
+
+ ))}
>
)}
@@ -494,16 +945,16 @@ const ChatWindow: React.FC
= ({
}
/>
-
{contactInfo.name}
+
{contractInfo.name}
-
+
- {contactInfo.position} · {contactInfo.department}
+ {contractInfo.position} · {contractInfo.department}
@@ -511,35 +962,35 @@ const ChatWindow: React.FC = ({
{/* 联系信息 */}
-
-
+
+
-
- {contactInfo.phone}
+
+ {contractInfo.phone}
-
+
-
- {contactInfo.email}
+
+ {contractInfo.email}
-
+
-
- {contactInfo.location}
+
+ {contractInfo.location}
-
+
-
- {contactInfo.company}
+
+ {contractInfo.company}
-
+
-
- 入职时间:{contactInfo.joinDate}
+
+ 入职时间:{contractInfo.joinDate}
@@ -548,7 +999,7 @@ const ChatWindow: React.FC
= ({
{/* 标签 */}
- {contactInfo.tags.map((tag, index) => (
+ {contractInfo.tags.map((tag, index) => (
{tag}
@@ -558,7 +1009,7 @@ const ChatWindow: React.FC
= ({
{/* 个人简介 */}
- {contactInfo.bio}
+ {contractInfo.bio}
{/* 操作按钮 */}
@@ -597,7 +1048,6 @@ const ChatWindow: React.FC = ({
,
]}
width={800}
- bodyStyle={{ padding: 0 }}
>
{/* 左侧素材分类 */}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx
index 7bf0c898..aaadf2ba 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx
@@ -1,12 +1,12 @@
import React from "react";
import { List, Avatar, Badge } from "antd";
import { UserOutlined } from "@ant-design/icons";
-import { ContactData } from "../../data";
+import { ContractData } from "../../data";
import styles from "./ContactList.module.scss";
interface ContactListProps {
- contacts: ContactData[];
- onContactClick: (contact: ContactData) => void;
+ contacts: ContractData[];
+ onContactClick: (contact: ContractData) => void;
}
const ContactList: React.FC
= ({
diff --git a/Cunkebao/src/pages/pc/ckbox/components/MessageList/data.ts b/Cunkebao/src/pages/pc/ckbox/components/MessageList/data.ts
index c1f83783..f6840485 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/MessageList/data.ts
+++ b/Cunkebao/src/pages/pc/ckbox/components/MessageList/data.ts
@@ -1,5 +1,5 @@
// 联系人数据接口
-export interface ContactData {
+export interface ContractData {
id?: number;
wechatAccountId: number;
wechatId: string;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx
index 151c64d3..6cb74783 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx
@@ -2,13 +2,13 @@ import React from "react";
import { List, Avatar, Badge } from "antd";
import { UserOutlined, TeamOutlined } from "@ant-design/icons";
import dayjs from "dayjs";
-import { ContactData } from "./data";
+import { ContractData } from "./data";
import styles from "./MessageList.module.scss";
interface MessageListProps {
- chatSessions: ContactData[];
- currentChat: ContactData;
- onChatSelect: (chat: ContactData) => void;
+ chatSessions: ContractData[];
+ currentChat: ContractData;
+ onChatSelect: (chat: ContractData) => void;
}
const MessageList: React.FC = ({
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
index 56782ac6..8d3f04fb 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
@@ -1,12 +1,12 @@
import React from "react";
import { List, Avatar } from "antd";
-import { ContactData } from "./data";
+import { ContractData } from "./data";
import styles from "./WechatFriends.module.scss";
interface WechatFriendsProps {
- contacts: ContactData[];
- onContactClick: (contact: ContactData) => void;
- selectedContactId?: ContactData;
+ contacts: ContractData[];
+ onContactClick: (contact: ContractData) => void;
+ selectedContactId?: ContractData;
}
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 1b784737..2a120454 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
@@ -1,34 +1,35 @@
import React, { useState } from "react";
-import { Layout as AntLayout, Input, Tabs } from "antd";
+import { Input } from "antd";
import {
SearchOutlined,
UserOutlined,
TeamOutlined,
MessageOutlined,
} from "@ant-design/icons";
-import { ContactData, ChatRecord } from "@/pages/pc/ckbox/data";
+import { ContractData } from "@/pages/pc/ckbox/data";
import WechatFriendsModule from "./WechatFriendsModule";
import MessageList from "../MessageList/index";
import LayoutFiexd from "@/components/Layout/LayoutFiexd";
import styles from "./SidebarMenu.module.scss";
+import { getChatSessions } from "@/store/module/ckchat";
interface SidebarMenuProps {
- contacts: ContactData[];
- chatSessions: ChatRecord[];
- currentChat: ContactData;
- onContactClick: (contact: ContactData) => void;
- onChatSelect: (chat: ContactData) => void;
+ contacts: ContractData[];
+ currentChat: ContractData;
+ onContactClick: (contact: ContractData) => void;
+ onChatSelect: (chat: ContractData) => void;
loading?: boolean;
}
const SidebarMenu: React.FC = ({
contacts,
- chatSessions,
currentChat,
onContactClick,
onChatSelect,
loading = false,
}) => {
+ const chatSessions = getChatSessions();
+
const [searchText, setSearchText] = useState("");
const [activeTab, setActiveTab] = useState("contacts");
diff --git a/Cunkebao/src/pages/pc/ckbox/data.ts b/Cunkebao/src/pages/pc/ckbox/data.ts
index 4390a714..63d6fffc 100644
--- a/Cunkebao/src/pages/pc/ckbox/data.ts
+++ b/Cunkebao/src/pages/pc/ckbox/data.ts
@@ -1,5 +1,24 @@
+//群聊数据接口
+export interface GroupData {
+ id?: number;
+ wechatAccountId: number;
+ tenantId: number;
+ accountId: number;
+ chatroomId: string;
+ chatroomOwner: string;
+ conRemark: string;
+ nickname: string;
+ chatroomAvatar: string;
+ groupId: number;
+ config: any;
+ unreadCount: number;
+ notice: string;
+ selfDisplyName: string;
+ [key: string]: any;
+}
+
// 联系人数据接口
-export interface ContactData {
+export interface ContractData {
id?: number;
wechatAccountId: number;
wechatId: string;
@@ -24,7 +43,9 @@ export interface ContactData {
thirdParty: null;
additionalPicture: string;
desc: string;
- config: null;
+ config?: {
+ chat: boolean;
+ };
lastMessageTime: number;
unreadCount: number;
duplicate: boolean;
diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx
index 6e0be50b..7a54e1fe 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/index.tsx
@@ -2,20 +2,18 @@ import React, { useState, useEffect } from "react";
import { Layout, Button, Space, message, Tooltip } from "antd";
import { InfoCircleOutlined, MessageOutlined } from "@ant-design/icons";
import dayjs from "dayjs";
-import { ContactData, ChatRecord } from "./data";
+import { ContractData, GroupData } from "./data";
import ChatWindow from "./components/ChatWindow/index";
import SidebarMenu from "./components/SidebarMenu/index";
import styles from "./index.module.scss";
-
+import { addChatSession } from "@/store/module/ckchat";
const { Header, Content, Sider } = Layout;
-import { clearUnreadCount, getMessages } from "./api";
import { chatInitAPIdata } from "./main";
-
const CkboxPage: React.FC = () => {
const [messageApi, contextHolder] = message.useMessage();
const [contacts, setContacts] = useState([]);
- const [chatSessions, setChatSessions] = useState([]);
- const [currentChat, setCurrentChat] = useState(null);
+ const [currentChat, setCurrentChat] = useState(null);
+
const [loading, setLoading] = useState(false);
const [showProfile, setShowProfile] = useState(true);
@@ -23,10 +21,17 @@ const CkboxPage: React.FC = () => {
// 方法一:使用 Promise 链式调用处理异步函数
setLoading(true);
chatInitAPIdata()
- .then(contactList => {
- console.log(contactList);
- // 如果需要可以设置联系人列表
- setContacts(contactList);
+ .then((response: { contactList: any[]; chatRoomList: any[] }) => {
+ const { contactList, chatRoomList } = response;
+ //找出已经在聊天的
+ const isChatList = contactList.filter(
+ v => (v?.config && v.config?.chat) || false,
+ );
+ isChatList.forEach(v => {
+ addChatSession(v);
+ });
+
+ setContacts(isChatList);
})
.catch(error => {
console.error("获取联系人列表失败:", error);
@@ -36,19 +41,9 @@ const CkboxPage: React.FC = () => {
});
}, []);
- const handleContactClick = (contact: ContactData) => {
- clearUnreadCount([contact.id]).then(() => {
- getMessages({
- wechatAccountId: contact.wechatAccountId,
- wechatFriendId: contact.id,
- From: 1,
- To: 1751344975916,
- Count: 100,
- olderData: true,
- }).then(session => {
- setChatSessions(session);
- });
- });
+ const handleContactClick = (contact: ContractData) => {
+ addChatSession(contact);
+ setCurrentChat(contact);
};
const handleSendMessage = async (message: string) => {
@@ -80,7 +75,6 @@ const CkboxPage: React.FC = () => {
{
setShowProfile(!showProfile)}
diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts
index ba05406e..6fc17e3a 100644
--- a/Cunkebao/src/pages/pc/ckbox/main.ts
+++ b/Cunkebao/src/pages/pc/ckbox/main.ts
@@ -1,7 +1,12 @@
import { useCkChatStore } from "@/store/module/ckchat";
import { useWebSocketStore } from "@/store/module/websocket";
-import { loginWithToken, getChuKeBaoUserInfo, getContactList } from "./api";
+import {
+ loginWithToken,
+ getChuKeBaoUserInfo,
+ getContactList,
+ getChatRoomList,
+} from "./api";
const { sendCommand } = useWebSocketStore.getState();
import { useUserStore } from "@/store/module/user";
const { login2 } = useUserStore.getState();
@@ -29,8 +34,12 @@ export const chatInitAPIdata = async () => {
});
//获取联系人列表
const contactList = await getAllContactList();
-
- return contactList;
+ //获取群列表
+ const chatRoomList = await getAllChatRoomList();
+ return {
+ contactList,
+ chatRoomList,
+ };
} catch (error) {
console.error("获取联系人列表失败:", error);
return [];
@@ -80,6 +89,49 @@ export const getAllContactList = async () => {
return [];
}
};
+// 递归获取所有群列表
+export const getAllChatRoomList = async () => {
+ try {
+ let allContacts = [];
+ let prevId = 0;
+ const count = 1000;
+ let hasMore = true;
+
+ while (hasMore) {
+ console.log(`获取群列表,prevId: ${prevId}, count: ${count}`);
+ const contactList = await getChatRoomList({
+ prevId,
+ count,
+ });
+
+ if (
+ !contactList ||
+ !Array.isArray(contactList) ||
+ contactList.length === 0
+ ) {
+ hasMore = false;
+ break;
+ }
+
+ allContacts = [...allContacts, ...contactList];
+
+ // 如果返回的数据少于请求的数量,说明已经没有更多数据了
+ if (contactList.length < count) {
+ hasMore = false;
+ } else {
+ // 获取最后一条数据的id作为下一次请求的prevId
+ const lastContact = contactList[contactList.length - 1];
+ prevId = lastContact.id;
+ }
+ }
+
+ console.log(`总共获取到 ${allContacts.length} 条群数据`);
+ return allContacts;
+ } catch (error) {
+ console.error("获取所有群列表失败:", error);
+ return [];
+ }
+};
export const getChatInfo = () => {
//获取UserId
diff --git a/Cunkebao/src/store/module/ckchat.ts b/Cunkebao/src/store/module/ckchat.ts
index fccaf1ec..d0a6f983 100644
--- a/Cunkebao/src/store/module/ckchat.ts
+++ b/Cunkebao/src/store/module/ckchat.ts
@@ -1,12 +1,45 @@
import { createPersistStore } from "@/store/createPersistStore";
import { CkChatState, CkUserInfo, CkAccount, CkTenant } from "./ckchat.data";
-
+import { ContractData, GroupData } from "@/pages/pc/ckbox/data";
export const useCkChatStore = createPersistStore(
set => ({
userInfo: null,
isLoggedIn: false,
+ chatSessions: [],
+ // 获取聊天会话
+ 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 });
@@ -87,3 +120,6 @@ export const getCkTenantId = () => useCkChatStore.getState().getTenantId();
export const getCkAccountName = () =>
useCkChatStore.getState().getAccountName();
export const getCkTenantName = () => useCkChatStore.getState().getTenantName();
+export const getChatSessions = () => useCkChatStore.getState().chatSessions;
+export const addChatSession = (session: ContractData | GroupData) =>
+ useCkChatStore.getState().addChatSession(session);
diff --git a/Cunkebao/src/store/module/websocket.ts b/Cunkebao/src/store/module/websocket.ts
index 8525040d..2d042aea 100644
--- a/Cunkebao/src/store/module/websocket.ts
+++ b/Cunkebao/src/store/module/websocket.ts
@@ -32,6 +32,7 @@ interface WebSocketConfig {
seq: number;
reconnectInterval: number;
maxReconnectAttempts: number;
+ [key: string]: any;
}
interface WebSocketState {
From c7b15896acde97da3efab13c63e9892565364722 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, 26 Aug 2025 11:50:26 +0800
Subject: [PATCH 37/78] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=B8=8B?=
=?UTF-8?q?=E8=BD=BD=E8=A7=86=E9=A2=91=E6=97=B6friendMessageId=E7=9A=84?=
=?UTF-8?q?=E9=94=99=E8=AF=AF=E8=B5=8B=E5=80=BC=E9=80=BB=E8=BE=91?=
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, 1 insertion(+), 1 deletion(-)
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
index 9b275b10..e38ad119 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
@@ -182,7 +182,7 @@ const ChatWindow: React.FC = ({
// 构建socket请求数据
sendCommand("CmdDownloadVideo", {
chatroomMessageId: contract.chatroomId ? contract.chatroomId : 0,
- friendMessageId: contract.id,
+ friendMessageId: contract.chatroomId ? 0 : contract.id,
seq: 9,
tencentUrl: tencentUrl,
wechatAccountId: contract.wechatAccountId,
From a26b465ff6a2f9aaa73491c36dc4c5f0ab7b52df 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, 26 Aug 2025 14:19:19 +0800
Subject: [PATCH 38/78] =?UTF-8?q?refactor(ckbox):=20=E7=BB=9F=E4=B8=80?=
=?UTF-8?q?=E5=B0=86contact=E9=87=8D=E5=91=BD=E5=90=8D=E4=B8=BAcontract?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
将代码中所有的contact变量和类名统一改为contract,包括API路径、组件属性、CSS类名等
更新相关路由配置和文档中的API接口说明
---
.../src/pages/mobile/mine/setting/About.tsx | 4 +-
Cunkebao/src/pages/pc/ckbox/README.md | 2 +-
.../ChatWindow/ChatWindow.module.scss | 22 ++--
.../pc/ckbox/components/ChatWindow/index.tsx | 100 ++++++++++++------
.../ContactList/ContactList.module.scss | 26 ++---
.../pc/ckbox/components/ContactList/index.tsx | 32 +++---
.../pc/ckbox/components/MessageList/index.tsx | 4 +-
.../SidebarMenu/WechatFriends.module.scss | 6 +-
.../SidebarMenu/WechatFriendsModule.tsx | 28 ++---
.../pc/ckbox/components/SidebarMenu/index.tsx | 26 ++---
Cunkebao/src/pages/pc/ckbox/index.tsx | 18 ++--
Cunkebao/src/pages/pc/ckbox/main.ts | 32 +++---
Cunkebao/src/router/config.ts | 6 +-
Cunkebao/src/store/module/ckchat.ts | 7 +-
14 files changed, 181 insertions(+), 132 deletions(-)
diff --git a/Cunkebao/src/pages/mobile/mine/setting/About.tsx b/Cunkebao/src/pages/mobile/mine/setting/About.tsx
index 7bcfcf40..432d15aa 100644
--- a/Cunkebao/src/pages/mobile/mine/setting/About.tsx
+++ b/Cunkebao/src/pages/mobile/mine/setting/About.tsx
@@ -46,7 +46,7 @@ const About: React.FC = () => {
];
// 联系信息
- const contactInfo = [
+ const contractInfo = [
{
id: "email",
title: "邮箱支持",
@@ -125,7 +125,7 @@ const About: React.FC = () => {
{/*
联系我们
- {contactInfo.map(item => (
+ {contractInfo.map(item => (
= ({
const [inputValue, setInputValue] = useState("");
const [loading, setLoading] = useState(false);
const [showMaterialModal, setShowMaterialModal] = useState(false);
+ const [pendingVideoRequests, setPendingVideoRequests] = useState<
+ Record
+ >({});
const messagesEndRef = useRef(null);
useEffect(() => {
@@ -92,6 +93,64 @@ const ChatWindow: React.FC = ({
scrollToBottom();
}, [messages]);
+ // 添加 WebSocket 消息订阅
+ useEffect(() => {
+ // 订阅 WebSocket 消息变化
+ const unsubscribe = useWebSocketStore.subscribe(
+ state => state.messages,
+ (messages, previousMessages) => {
+ // 只处理新消息
+ if (messages.length > previousMessages.length) {
+ // 获取最新的消息
+ const newMessages = messages.slice(previousMessages.length);
+
+ // 处理新消息
+ newMessages.forEach(message => {
+ const content = message.content;
+
+ // 检查是否是视频下载响应
+ if (content && content.cmdType === "CmdDownloadVideoResponse") {
+ // 获取视频URL
+ const videoUrl = content.videoUrl;
+ const requestId = content.requestId || content.seq;
+
+ // 查找对应的消息ID
+ const messageId = pendingVideoRequests[requestId];
+ if (messageId && videoUrl) {
+ // 更新消息内容,将预览图替换为实际视频
+ setMessages(prevMessages => {
+ return prevMessages.map(msg => {
+ if (msg.id === messageId) {
+ return {
+ ...msg,
+ content: videoUrl,
+ };
+ }
+ return msg;
+ });
+ });
+
+ // 从待处理请求中移除
+ setPendingVideoRequests(prev => {
+ const newRequests = { ...prev };
+ delete newRequests[requestId];
+ return newRequests;
+ });
+
+ messageApi.success("视频加载成功");
+ }
+ }
+ });
+ }
+ },
+ );
+
+ // 组件卸载时取消订阅
+ return () => {
+ unsubscribe();
+ };
+ }, [pendingVideoRequests, messageApi]);
+
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
@@ -179,15 +238,22 @@ const ChatWindow: React.FC = ({
// 处理视频播放请求,发送socket请求获取真实视频地址
const handleVideoPlayRequest = (tencentUrl: string, messageId: string) => {
+ // 生成请求ID (可以使用 seq 或其他唯一标识)
+
// 构建socket请求数据
- sendCommand("CmdDownloadVideo", {
+ useWebSocketStore.getState().sendCommand("CmdDownloadVideo", {
chatroomMessageId: contract.chatroomId ? contract.chatroomId : 0,
friendMessageId: contract.chatroomId ? 0 : contract.id,
- seq: 9,
+ seq: 9, // 使用唯一的请求ID
tencentUrl: tencentUrl,
wechatAccountId: contract.wechatAccountId,
});
+ // 记录待处理的视频请求
+ setPendingVideoRequests(prev => ({
+ ...prev,
+ }));
+
// 更新消息状态为加载中
setMessages(prevMessages => {
return prevMessages.map(msg => {
@@ -205,32 +271,6 @@ const ChatWindow: React.FC = ({
return msg;
});
});
-
- // TODO: 这里应该实现实际的socket请求发送逻辑
- console.log("发送视频请求:", socketData);
-
- // 模拟获取视频地址后的处理
- // 实际应用中,这里应该是socket响应的回调处理
- setTimeout(() => {
- // 模拟获取到视频地址
- const videoUrl = "https://example.com/video.mp4";
-
- // 更新消息内容,将预览图替换为实际视频
- setMessages(prevMessages => {
- return prevMessages.map(msg => {
- if (msg.id === messageId) {
- // 创建新的视频消息内容
- return {
- ...msg,
- content: videoUrl,
- };
- }
- return msg;
- });
- });
-
- messageApi.success("视频加载成功");
- }, 1500);
};
// 解析消息内容,判断消息类型并返回对应的渲染内容
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ContactList/ContactList.module.scss b/Cunkebao/src/pages/pc/ckbox/components/ContactList/ContactList.module.scss
index d868d744..28ed1451 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ContactList/ContactList.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/components/ContactList/ContactList.module.scss
@@ -1,8 +1,8 @@
-.contactList {
+.contractList {
height: 100%;
overflow-y: auto;
- .contactItem {
+ .contractItem {
padding: 12px 16px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
@@ -16,17 +16,17 @@
border-bottom: none;
}
- .contactInfo {
+ .contractInfo {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
- .contactDetails {
+ .contractDetails {
flex: 1;
min-width: 0;
- .contactName {
+ .contractName {
font-size: 14px;
font-weight: 500;
color: #262626;
@@ -36,13 +36,13 @@
white-space: nowrap;
}
- .contactPhone {
+ .contractPhone {
font-size: 12px;
color: #8c8c8c;
margin-bottom: 2px;
}
- .contactStatus {
+ .contractStatus {
font-size: 11px;
color: #bfbfbf;
overflow: hidden;
@@ -56,19 +56,19 @@
// 响应式设计
@media (max-width: 768px) {
- .contactList {
- .contactItem {
+ .contractList {
+ .contractItem {
padding: 10px 12px;
- .contactInfo {
+ .contractInfo {
gap: 10px;
- .contactDetails {
- .contactName {
+ .contractDetails {
+ .contractName {
font-size: 13px;
}
- .contactPhone {
+ .contractPhone {
font-size: 11px;
}
}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx
index aaadf2ba..38caf58a 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx
@@ -5,36 +5,36 @@ import { ContractData } from "../../data";
import styles from "./ContactList.module.scss";
interface ContactListProps {
- contacts: ContractData[];
- onContactClick: (contact: ContractData) => void;
+ contracts: ContractData[];
+ onContactClick: (contract: ContractData) => void;
}
const ContactList: React.FC = ({
- contacts,
+ contracts,
onContactClick,
}) => {
return (
-
+
(
+ dataSource={contracts}
+ renderItem={contract => (
onContactClick(contact)}
+ className={styles.contractItem}
+ onClick={() => onContactClick(contract)}
>
-
-
+
+
}
/>
-
-
{contact.name}
-
{contact.phone}
- {contact.status && (
-
{contact.status}
+
+
{contract.name}
+
{contract.phone}
+ {contract.status && (
+
{contract.status}
)}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx
index 6cb74783..f00994de 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx
@@ -2,13 +2,13 @@ import React from "react";
import { List, Avatar, Badge } from "antd";
import { UserOutlined, TeamOutlined } from "@ant-design/icons";
import dayjs from "dayjs";
-import { ContractData } from "./data";
+import { ContractData, GroupData } from "@/pages/pc/ckbox/data";
import styles from "./MessageList.module.scss";
interface MessageListProps {
chatSessions: ContractData[];
currentChat: ContractData;
- onChatSelect: (chat: ContractData) => void;
+ onChatSelect: (chat: ContractData | GroupData) => void;
}
const MessageList: React.FC
= ({
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends.module.scss
index 5de994a5..bb90641b 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends.module.scss
@@ -1,4 +1,4 @@
-.contactListSimple {
+.contractListSimple {
display: flex;
flex-direction: column;
height: 100%;
@@ -26,7 +26,7 @@
}
}
- .contactItem {
+ .contractItem {
display: flex;
align-items: center;
padding: 8px 15px;
@@ -44,7 +44,7 @@
background-color: #1890ff;
}
- .contactInfo {
+ .contractInfo {
flex: 1;
overflow: hidden;
}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
index 8d3f04fb..303d9bb6 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx
@@ -1,43 +1,43 @@
import React from "react";
import { List, Avatar } from "antd";
-import { ContractData } from "./data";
+import { ContractData } from "@/pages/pc/ckbox/data";
import styles from "./WechatFriends.module.scss";
interface WechatFriendsProps {
- contacts: ContractData[];
- onContactClick: (contact: ContractData) => void;
+ contracts: ContractData[];
+ onContactClick: (contract: ContractData) => void;
selectedContactId?: ContractData;
}
const ContactListSimple: React.FC = ({
- contacts,
+ contracts,
onContactClick,
selectedContactId,
}) => {
return (
-
+
全部好友
(
+ dataSource={contracts}
+ renderItem={contract => (
onContactClick(contact)}
- className={`${styles.contactItem} ${contact.id === selectedContactId?.id ? styles.selected : ""}`}
+ key={contract.id}
+ onClick={() => onContactClick(contract)}
+ className={`${styles.contractItem} ${contract.id === selectedContactId?.id ? styles.selected : ""}`}
>
{contact.nickname.charAt(0)}
+ !contract.avatar && {contract.nickname.charAt(0)}
}
className={styles.avatar}
/>
-
+
- {contact.conRemark || contact.nickname}
+ {contract.conRemark || contract.nickname}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
index 2a120454..20adfd63 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
@@ -14,15 +14,15 @@ import styles from "./SidebarMenu.module.scss";
import { getChatSessions } from "@/store/module/ckchat";
interface SidebarMenuProps {
- contacts: ContractData[];
+ contracts: ContractData[];
currentChat: ContractData;
- onContactClick: (contact: ContractData) => void;
+ onContactClick: (contract: ContractData) => void;
onChatSelect: (chat: ContractData) => void;
loading?: boolean;
}
const SidebarMenu: React.FC
= ({
- contacts,
+ contracts,
currentChat,
onContactClick,
onChatSelect,
@@ -31,18 +31,18 @@ const SidebarMenu: React.FC = ({
const chatSessions = getChatSessions();
const [searchText, setSearchText] = useState("");
- const [activeTab, setActiveTab] = useState("contacts");
+ const [activeTab, setActiveTab] = useState("contracts");
const handleSearch = (value: string) => {
setSearchText(value);
};
const getFilteredContacts = () => {
- if (!searchText) return contacts;
- return contacts.filter(
- contact =>
- contact.nickname.toLowerCase().includes(searchText.toLowerCase()) ||
- contact.phone.includes(searchText),
+ if (!searchText) return contracts;
+ return contracts.filter(
+ contract =>
+ contract.nickname.toLowerCase().includes(searchText.toLowerCase()) ||
+ contract.phone.includes(searchText),
);
};
@@ -77,8 +77,8 @@ const SidebarMenu: React.FC = ({
聊天
setActiveTab("contacts")}
+ className={`${styles.tabItem} ${activeTab === "contracts" ? styles.active : ""}`}
+ onClick={() => setActiveTab("contracts")}
>
联系人
@@ -105,10 +105,10 @@ const SidebarMenu: React.FC
= ({
currentChat={currentChat}
/>
);
- case "contacts":
+ case "contracts":
return (
diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx
index 7a54e1fe..ab19ab07 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/index.tsx
@@ -11,7 +11,7 @@ const { Header, Content, Sider } = Layout;
import { chatInitAPIdata } from "./main";
const CkboxPage: React.FC = () => {
const [messageApi, contextHolder] = message.useMessage();
- const [contacts, setContacts] = useState([]);
+ const [contracts, setContacts] = useState([]);
const [currentChat, setCurrentChat] = useState(null);
const [loading, setLoading] = useState(false);
@@ -21,10 +21,10 @@ const CkboxPage: React.FC = () => {
// 方法一:使用 Promise 链式调用处理异步函数
setLoading(true);
chatInitAPIdata()
- .then((response: { contactList: any[]; chatRoomList: any[] }) => {
- const { contactList, chatRoomList } = response;
+ .then((response: { contractList: any[]; chatRoomList: any[] }) => {
+ const { contractList, chatRoomList } = response;
//找出已经在聊天的
- const isChatList = contactList.filter(
+ const isChatList = contractList.filter(
v => (v?.config && v.config?.chat) || false,
);
isChatList.forEach(v => {
@@ -41,9 +41,9 @@ const CkboxPage: React.FC = () => {
});
}, []);
- const handleContactClick = (contact: ContractData) => {
- addChatSession(contact);
- setCurrentChat(contact);
+ const handleContactClick = (contract: ContractData) => {
+ addChatSession(contract);
+ setCurrentChat(contract);
};
const handleSendMessage = async (message: string) => {
@@ -74,7 +74,7 @@ const CkboxPage: React.FC = () => {
{/* 左侧边栏 */}
{
setShowProfile(!showProfile)}
diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts
index 6fc17e3a..3d195926 100644
--- a/Cunkebao/src/pages/pc/ckbox/main.ts
+++ b/Cunkebao/src/pages/pc/ckbox/main.ts
@@ -33,11 +33,11 @@ export const chatInitAPIdata = async () => {
seq: 1,
});
//获取联系人列表
- const contactList = await getAllContactList();
+ const contractList = await getAllContactList();
//获取群列表
const chatRoomList = await getAllChatRoomList();
return {
- contactList,
+ contractList,
chatRoomList,
};
} catch (error) {
@@ -56,28 +56,28 @@ export const getAllContactList = async () => {
while (hasMore) {
console.log(`获取联系人列表,prevId: ${prevId}, count: ${count}`);
- const contactList = await getContactList({
+ const contractList = await getContactList({
prevId,
count,
});
if (
- !contactList ||
- !Array.isArray(contactList) ||
- contactList.length === 0
+ !contractList ||
+ !Array.isArray(contractList) ||
+ contractList.length === 0
) {
hasMore = false;
break;
}
- allContacts = [...allContacts, ...contactList];
+ allContacts = [...allContacts, ...contractList];
// 如果返回的数据少于请求的数量,说明已经没有更多数据了
- if (contactList.length < count) {
+ if (contractList.length < count) {
hasMore = false;
} else {
// 获取最后一条数据的id作为下一次请求的prevId
- const lastContact = contactList[contactList.length - 1];
+ const lastContact = contractList[contractList.length - 1];
prevId = lastContact.id;
}
}
@@ -99,28 +99,28 @@ export const getAllChatRoomList = async () => {
while (hasMore) {
console.log(`获取群列表,prevId: ${prevId}, count: ${count}`);
- const contactList = await getChatRoomList({
+ const contractList = await getChatRoomList({
prevId,
count,
});
if (
- !contactList ||
- !Array.isArray(contactList) ||
- contactList.length === 0
+ !contractList ||
+ !Array.isArray(contractList) ||
+ contractList.length === 0
) {
hasMore = false;
break;
}
- allContacts = [...allContacts, ...contactList];
+ allContacts = [...allContacts, ...contractList];
// 如果返回的数据少于请求的数量,说明已经没有更多数据了
- if (contactList.length < count) {
+ if (contractList.length < count) {
hasMore = false;
} else {
// 获取最后一条数据的id作为下一次请求的prevId
- const lastContact = contactList[contactList.length - 1];
+ const lastContact = contractList[contractList.length - 1];
prevId = lastContact.id;
}
}
diff --git a/Cunkebao/src/router/config.ts b/Cunkebao/src/router/config.ts
index 7ad14ac5..b6a8f394 100644
--- a/Cunkebao/src/router/config.ts
+++ b/Cunkebao/src/router/config.ts
@@ -95,7 +95,7 @@ export const routeGroups = {
"/plans",
"/plans/:planId",
"/orders",
- "/contact-import",
+ "/contract-import",
],
},
};
@@ -126,7 +126,7 @@ export const routePermissions = {
"/plans",
"/plans/:planId",
"/orders",
- "/contact-import",
+ "/contract-import",
],
// 访客权限
@@ -150,7 +150,7 @@ export const routeTitles: Record = {
"/profile": "个人中心",
"/plans": "计划管理",
"/orders": "订单管理",
- "/contact-import": "联系人导入",
+ "/contract-import": "联系人导入",
};
// 获取路由标题
diff --git a/Cunkebao/src/store/module/ckchat.ts b/Cunkebao/src/store/module/ckchat.ts
index d0a6f983..d25b5960 100644
--- a/Cunkebao/src/store/module/ckchat.ts
+++ b/Cunkebao/src/store/module/ckchat.ts
@@ -120,6 +120,11 @@ export const getCkTenantId = () => useCkChatStore.getState().getTenantId();
export const getCkAccountName = () =>
useCkChatStore.getState().getAccountName();
export const getCkTenantName = () => useCkChatStore.getState().getTenantName();
-export const getChatSessions = () => useCkChatStore.getState().chatSessions;
+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);
From 3645075473d7d30073b0387011f920cd717f15da 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, 26 Aug 2025 16:18:37 +0800
Subject: [PATCH 39/78] =?UTF-8?q?fix(websocket):=20=E4=BF=AE=E5=A4=8DWebSo?=
=?UTF-8?q?cket=E8=BF=9E=E6=8E=A5=E5=92=8C=E8=A7=86=E9=A2=91=E6=B6=88?=
=?UTF-8?q?=E6=81=AF=E5=A4=84=E7=90=86=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 显式指定WebSocket URL确保连接到正确服务器
- 添加本地地址连接拦截逻辑防止错误连接
- 优化视频消息处理逻辑,包括加载状态管理和下载响应处理
- 使用时间戳作为唯一序列号避免重复
- 修复消息订阅逻辑,仅在有待处理视频请求时监听
---
Cunkebao/src/components/WebSocketExample.tsx | 1 +
.../pc/ckbox/components/ChatWindow/index.tsx | 175 ++++++++++++------
Cunkebao/src/pages/pc/ckbox/main.ts | 22 ++-
Cunkebao/src/store/module/websocket.ts | 17 +-
4 files changed, 148 insertions(+), 67 deletions(-)
diff --git a/Cunkebao/src/components/WebSocketExample.tsx b/Cunkebao/src/components/WebSocketExample.tsx
index 060bd98b..67867431 100644
--- a/Cunkebao/src/components/WebSocketExample.tsx
+++ b/Cunkebao/src/components/WebSocketExample.tsx
@@ -116,6 +116,7 @@ const WebSocketExample: React.FC = () => {
color="primary"
onClick={() =>
connect({
+ url: "wss://kf.quwanzhi.com:9993", // 显式指定WebSocket URL,确保使用正确的服务器地址
client: "kefu-client",
autoReconnect: true,
})
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
index f7fe94f7..39434565 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
@@ -47,7 +47,7 @@ import dayjs from "dayjs";
import { ChatRecord, ContractData } from "@/pages/pc/ckbox/data";
import { clearUnreadCount, getMessages } from "@/pages/pc/ckbox/api";
import styles from "./ChatWindow.module.scss";
-import { useWebSocketStore } from "@/store/module/websocket";
+import { useWebSocketStore, WebSocketMessage } from "@/store/module/websocket";
const { Header, Content, Footer, Sider } = Layout;
const { TextArea } = Input;
@@ -76,6 +76,7 @@ const ChatWindow: React.FC = ({
useEffect(() => {
clearUnreadCount([contract.id]).then(() => {
+ setLoading(true);
getMessages({
wechatAccountId: contract.wechatAccountId,
wechatFriendId: contract.id,
@@ -83,73 +84,106 @@ const ChatWindow: React.FC = ({
To: +new Date() + 1000,
Count: 100,
olderData: true,
- }).then(msg => {
- setMessages(msg);
- });
+ })
+ .then(msg => {
+ setMessages(msg);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
});
}, [contract.id]);
useEffect(() => {
- scrollToBottom();
+ // 只有在非视频加载操作时才自动滚动到底部
+ // 检查是否有视频正在加载中
+ const hasLoadingVideo = messages.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) {
+ scrollToBottom();
+ }
}, [messages]);
- // 添加 WebSocket 消息订阅
+ // 添加 WebSocket 消息订阅 - 监听视频下载响应消息
useEffect(() => {
+ // 只有当有待处理的视频请求时才订阅WebSocket消息
+ if (Object.keys(pendingVideoRequests).length === 0) {
+ return;
+ }
+
+ console.log("开始监听视频下载响应,当前待处理请求:", pendingVideoRequests);
+
// 订阅 WebSocket 消息变化
- const unsubscribe = useWebSocketStore.subscribe(
- state => state.messages,
- (messages, previousMessages) => {
- // 只处理新消息
- if (messages.length > previousMessages.length) {
- // 获取最新的消息
- const newMessages = messages.slice(previousMessages.length);
+ const unsubscribe = useWebSocketStore.subscribe(state => {
+ // 只处理新增的消息
+ const messages = state.messages as WebSocketMessage[];
- // 处理新消息
- newMessages.forEach(message => {
- const content = message.content;
+ // 筛选出视频下载响应消息
+ messages.forEach(message => {
+ if (message?.content?.cmdType === "CmdDownloadVideoResult") {
+ console.log("收到视频下载响应:", message.content);
- // 检查是否是视频下载响应
- if (content && content.cmdType === "CmdDownloadVideoResponse") {
- // 获取视频URL
- const videoUrl = content.videoUrl;
- const requestId = content.requestId || content.seq;
+ // 检查是否是我们正在等待的视频响应
+ const messageId = Object.keys(pendingVideoRequests).find(
+ id => pendingVideoRequests[id] === message.content.friendMessageId,
+ );
- // 查找对应的消息ID
- const messageId = pendingVideoRequests[requestId];
- if (messageId && videoUrl) {
- // 更新消息内容,将预览图替换为实际视频
- setMessages(prevMessages => {
- return prevMessages.map(msg => {
- if (msg.id === messageId) {
- return {
- ...msg,
- content: videoUrl,
- };
- }
- return msg;
- });
- });
+ if (messageId) {
+ console.log("找到对应的消息ID:", messageId);
- // 从待处理请求中移除
- setPendingVideoRequests(prev => {
- const newRequests = { ...prev };
- delete newRequests[requestId];
- return newRequests;
- });
+ // 从待处理队列中移除
+ setPendingVideoRequests(prev => {
+ const newRequests = { ...prev };
+ delete newRequests[messageId];
+ return newRequests;
+ });
- messageApi.success("视频加载成功");
- }
- }
- });
+ // 更新消息内容,将视频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, messageApi]);
+ }, [pendingVideoRequests]); // 依赖于pendingVideoRequests,当队列变化时重新设置订阅
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
@@ -237,21 +271,24 @@ const ChatWindow: React.FC = ({
};
// 处理视频播放请求,发送socket请求获取真实视频地址
- const handleVideoPlayRequest = (tencentUrl: string, messageId: string) => {
- // 生成请求ID (可以使用 seq 或其他唯一标识)
+ const handleVideoPlayRequest = (tencentUrl: string, messageId: number) => {
+ // 生成请求ID (使用当前时间戳作为唯一标识)
+ const requestSeq = `${+new Date()}`;
+ console.log("发送视频下载请求:", { messageId, requestSeq });
// 构建socket请求数据
useWebSocketStore.getState().sendCommand("CmdDownloadVideo", {
- chatroomMessageId: contract.chatroomId ? contract.chatroomId : 0,
- friendMessageId: contract.chatroomId ? 0 : contract.id,
- seq: 9, // 使用唯一的请求ID
+ chatroomMessageId: contract.chatroomId ? messageId : 0,
+ friendMessageId: contract.chatroomId ? 0 : messageId,
+ seq: requestSeq, // 使用唯一的请求ID
tencentUrl: tencentUrl,
wechatAccountId: contract.wechatAccountId,
});
- // 记录待处理的视频请求
+ // 将消息ID和请求序列号添加到待处理队列
setPendingVideoRequests(prev => ({
...prev,
+ [messageId]: messageId,
}));
// 更新消息状态为加载中
@@ -313,6 +350,31 @@ const ChatWindow: React.FC = ({
handleVideoPlayRequest(videoData.tencentUrl, msg.id);
};
+ // 检查是否已下载视频URL
+ if (videoData.videoUrl) {
+ // 已获取到视频URL,显示视频播放器
+ return (
+
+ );
+ }
+
// 检查是否处于加载状态
if (videoData.isLoading) {
return (
@@ -336,6 +398,7 @@ const ChatWindow: React.FC = ({
);
}
+ // 默认显示预览图和播放按钮
return (
@@ -709,7 +772,7 @@ const ChatWindow: React.FC
= ({
};
const renderMessage = (msg: ChatRecord) => {
- const isOwn = msg.senderId === "me";
+ const isOwn = msg.isSend;
return (
{
const accountId = getAccountId();
//发起链接
- connect({
- accessToken: String(Token),
- accountId: accountId,
- client: "kefu-client",
- cmdType: "CmdSignIn",
- seq: 1,
- });
+ if (Token && accountId) {
+ connect({
+ url: "wss://kf.quwanzhi.com:9993", // 显式指定WebSocket URL,确保使用正确的服务器地址
+ accessToken: String(Token),
+ accountId: accountId,
+ client: "kefu-client",
+ cmdType: "CmdSignIn",
+ seq: +new Date(),
+ });
+ console.log("WebSocket连接已初始化");
+ } else {
+ console.error("WebSocket连接初始化失败:缺少Token或accountId");
+ }
//获取联系人列表
const contractList = await getAllContactList();
//获取群列表
@@ -137,7 +143,7 @@ export const getChatInfo = () => {
//获取UserId
sendCommand("CmdRequestWechatAccountsAliveStatus", {
wechatAccountIds: ["300745", "4880930", "32686452"],
- seq: 2,
+ seq: +new Date(),
});
console.log("发送链接信息");
};
diff --git a/Cunkebao/src/store/module/websocket.ts b/Cunkebao/src/store/module/websocket.ts
index 2d042aea..397733c9 100644
--- a/Cunkebao/src/store/module/websocket.ts
+++ b/Cunkebao/src/store/module/websocket.ts
@@ -9,6 +9,7 @@ export interface WebSocketMessage {
cmdType?: string;
seq?: number;
wechatAccountIds?: string[];
+ content?: any;
[key: string]: any;
}
@@ -77,7 +78,7 @@ const DEFAULT_CONFIG: WebSocketConfig = {
accessToken: "",
autoReconnect: true,
cmdType: "", // 添加默认的命令类型
- seq: 0, // 添加默认的序列号
+ seq: +new Date(), // 添加默认的序列号
reconnectInterval: 3000,
maxReconnectAttempts: 5,
};
@@ -123,7 +124,17 @@ export const useWebSocketStore = createPersistStore
(
t: Date.now().toString(),
});
+ // 检查URL是否为localhost,如果是则不连接
const wsUrl = fullConfig.url + "?" + params;
+ if (wsUrl.includes("localhost") || wsUrl.includes("127.0.0.1")) {
+ console.error("WebSocket连接被拦截:不允许连接到本地地址", wsUrl);
+ Toast.show({
+ content: "WebSocket连接被拦截:不允许连接到本地地址",
+ position: "top",
+ });
+ set({ status: WebSocketStatus.ERROR });
+ return;
+ }
set({
status: WebSocketStatus.CONNECTING,
@@ -260,12 +271,12 @@ export const useWebSocketStore = createPersistStore(
accessToken: token2,
accountId: Number(getAccountId()),
client: currentState.config?.client || "kefu-client",
- seq: currentState.config?.seq || 1,
+ seq: +new Date(),
});
//获取UserId
currentState.sendCommand("CmdRequestWechatAccountsAliveStatus", {
wechatAccountIds: ["300745", "4880930", "32686452"],
- seq: 2,
+ seq: +new Date(),
});
}
From 08dc74cb67ad6937056327ca23d1a37c358d4063 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, 26 Aug 2025 16:34:17 +0800
Subject: [PATCH 40/78] =?UTF-8?q?feat(=E8=81=8A=E5=A4=A9=E7=AA=97=E5=8F=A3?=
=?UTF-8?q?):=20=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF=E6=97=B6=E9=97=B4?=
=?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=A0=B7=E5=BC=8F=E5=92=8C=E6=A0=BC=E5=BC=8F?=
=?UTF-8?q?=E5=8C=96=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在消息时间前后添加分割线并调整间距
- 根据消息时间范围显示不同格式的时间(当天、昨天、一周内、超过一周)
- 使用微信时间或创建时间作为消息时间基准
- 当相邻消息时间差超过5分钟时创建新时间组
---
.../ChatWindow/ChatWindow.module.scss | 23 ++++++++-
.../pc/ckbox/components/ChatWindow/index.tsx | 49 ++++++++++++++++---
2 files changed, 63 insertions(+), 9 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 1f4487de..f9ff1339 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss
@@ -385,10 +385,29 @@
.messageTime {
text-align: center;
- padding: 8px 0;
+ padding: 4px 0;
font-size: 12px;
color: #999;
- margin: 16px 0;
+ margin: 10px 0;
+ position: relative;
+
+ &::before,
+ &::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ width: calc(50% - 60px);
+ height: 1px;
+ background-color: rgba(0, 0, 0, 0.05);
+ }
+
+ &::before {
+ left: 0;
+ }
+
+ &::after {
+ right: 0;
+ }
}
.messageItem {
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
index 39434565..d3bc97c4 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
@@ -745,23 +745,58 @@ const ChatWindow: React.FC = ({
let currentDate = "";
messages.forEach(msg => {
- const messageDate = dayjs(msg.timestamp).format("YYYY-MM-DD");
- const messageTime = dayjs(msg.timestamp).format("HH:mm");
+ // 使用消息的创建时间或微信时间
+ const msgTime = msg.wechatTime
+ ? msg.wechatTime * 1000
+ : new Date(msg.createTime).getTime();
+ const messageDate = dayjs(msgTime).format("YYYY-MM-DD");
+
+ // 根据不同时间范围格式化时间戳
+ let formattedTime = "";
+ const now = dayjs();
+ const msgDayjs = dayjs(msgTime);
+
+ // 当天消息:显示为 "时:分",例如 "14:30"
+ if (msgDayjs.format("YYYY-MM-DD") === now.format("YYYY-MM-DD")) {
+ formattedTime = msgDayjs.format("HH:mm");
+ }
+ // 昨天消息:显示为 "昨天 时:分",如 "昨天 19:25"
+ else if (
+ msgDayjs.format("YYYY-MM-DD") ===
+ now.subtract(1, "day").format("YYYY-MM-DD")
+ ) {
+ formattedTime = `昨天 ${msgDayjs.format("HH:mm")}`;
+ }
+ // 前天至一周内消息:显示为 "星期 X 时:分",例如 "周三 16:10"
+ else if (msgDayjs.isAfter(now.subtract(7, "day"))) {
+ const weekDayMap = ["日", "一", "二", "三", "四", "五", "六"];
+ const weekDay = weekDayMap[msgDayjs.day()];
+ formattedTime = `周${weekDay} ${msgDayjs.format("HH:mm")}`;
+ }
+ // 超过一周的消息:显示为 "YYYY 年 X 月 X 日 时:分",如 "2025 年 8 月 15 日 10:35"
+ else {
+ formattedTime = msgDayjs.format("YYYY 年 M 月 D 日 HH:mm");
+ }
if (messageDate !== currentDate) {
currentDate = messageDate;
- groups.push({ time: messageTime, messages: [msg] });
+ groups.push({ time: formattedTime, messages: [msg] });
} else {
const lastGroup = groups[groups.length - 1];
// 如果时间相差超过5分钟,创建新的时间组
- const lastMessageTime =
- lastGroup.messages[lastGroup.messages.length - 1].timestamp;
+ const lastMessageTime = lastGroup.messages[
+ lastGroup.messages.length - 1
+ ].wechatTime
+ ? lastGroup.messages[lastGroup.messages.length - 1].wechatTime * 1000
+ : new Date(
+ lastGroup.messages[lastGroup.messages.length - 1].createTime,
+ ).getTime();
const timeDiff = Math.abs(
- dayjs(msg.timestamp).diff(dayjs(lastMessageTime), "minute"),
+ dayjs(msgTime).diff(dayjs(lastMessageTime), "minute"),
);
if (timeDiff >= 5) {
- groups.push({ time: messageTime, messages: [msg] });
+ groups.push({ time: formattedTime, messages: [msg] });
} else {
lastGroup.messages.push(msg);
}
From 1334942fc094632719cfa0a76bd1c76aa8384c1d 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, 27 Aug 2025 10:22:51 +0800
Subject: [PATCH 41/78] =?UTF-8?q?feat(=E8=81=8A=E5=A4=A9=E7=AA=97=E5=8F=A3?=
=?UTF-8?q?):=20=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF=E6=97=B6=E9=97=B4?=
=?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=A0=BC=E5=BC=8F=E5=B9=B6=E8=B0=83=E6=95=B4?=
=?UTF-8?q?=E6=A0=B7=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
重构消息时间格式化逻辑,提取为公共工具函数 formatWechatTime
移除聊天窗口中的分隔线样式,调整消息间距
更新构建产物文件引用路径
---
Cunkebao/dist/.vite/manifest.json | 18 +++---
Cunkebao/dist/index.html | 8 +--
.../ChatWindow/ChatWindow.module.scss | 21 +------
.../pc/ckbox/components/ChatWindow/index.tsx | 62 ++-----------------
Cunkebao/src/utils/common.ts | 48 ++++++++++++++
5 files changed, 66 insertions(+), 91 deletions(-)
diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json
index 454623cd..42480c71 100644
--- a/Cunkebao/dist/.vite/manifest.json
+++ b/Cunkebao/dist/.vite/manifest.json
@@ -1,9 +1,9 @@
{
- "_charts-DRkEUjsu.js": {
- "file": "assets/charts-DRkEUjsu.js",
+ "_charts-DDGb1us0.js": {
+ "file": "assets/charts-DDGb1us0.js",
"name": "charts",
"imports": [
- "_ui-ltFujOOi.js",
+ "_ui-rFvxQTWo.js",
"_vendor-2vc8h_ct.js"
]
},
@@ -11,8 +11,8 @@
"file": "assets/ui-D0C0OGrH.css",
"src": "_ui-D0C0OGrH.css"
},
- "_ui-ltFujOOi.js": {
- "file": "assets/ui-ltFujOOi.js",
+ "_ui-rFvxQTWo.js": {
+ "file": "assets/ui-rFvxQTWo.js",
"name": "ui",
"imports": [
"_vendor-2vc8h_ct.js"
@@ -33,18 +33,18 @@
"name": "vendor"
},
"index.html": {
- "file": "assets/index-DV2QF8R9.js",
+ "file": "assets/index-C3xy08Hg.js",
"name": "index",
"src": "index.html",
"isEntry": true,
"imports": [
"_vendor-2vc8h_ct.js",
- "_ui-ltFujOOi.js",
+ "_ui-rFvxQTWo.js",
"_utils-6WF66_dS.js",
- "_charts-DRkEUjsu.js"
+ "_charts-DDGb1us0.js"
],
"css": [
- "assets/index-DqyE1UEk.css"
+ "assets/index-D4Jt-UDy.css"
]
}
}
\ No newline at end of file
diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html
index 76889c66..f4b62be3 100644
--- a/Cunkebao/dist/index.html
+++ b/Cunkebao/dist/index.html
@@ -11,13 +11,13 @@
-
+
-
+
-
+
-
+
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 f9ff1339..6565fbb9 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss
@@ -388,26 +388,7 @@
padding: 4px 0;
font-size: 12px;
color: #999;
- margin: 10px 0;
- position: relative;
-
- &::before,
- &::after {
- content: '';
- position: absolute;
- top: 50%;
- width: calc(50% - 60px);
- height: 1px;
- background-color: rgba(0, 0, 0, 0.05);
- }
-
- &::before {
- left: 0;
- }
-
- &::after {
- right: 0;
- }
+ margin: 20px 0;
}
.messageItem {
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
index d3bc97c4..af49755b 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
@@ -48,6 +48,7 @@ import { ChatRecord, ContractData } from "@/pages/pc/ckbox/data";
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";
const { Header, Content, Footer, Sider } = Layout;
const { TextArea } = Input;
@@ -742,65 +743,10 @@ const ChatWindow: React.FC = ({
// 用于分组消息并添加时间戳的辅助函数
const groupMessagesByTime = (messages: ChatRecord[]) => {
const groups: { time: string; messages: ChatRecord[] }[] = [];
- let currentDate = "";
-
messages.forEach(msg => {
- // 使用消息的创建时间或微信时间
- const msgTime = msg.wechatTime
- ? msg.wechatTime * 1000
- : new Date(msg.createTime).getTime();
- const messageDate = dayjs(msgTime).format("YYYY-MM-DD");
-
- // 根据不同时间范围格式化时间戳
- let formattedTime = "";
- const now = dayjs();
- const msgDayjs = dayjs(msgTime);
-
- // 当天消息:显示为 "时:分",例如 "14:30"
- if (msgDayjs.format("YYYY-MM-DD") === now.format("YYYY-MM-DD")) {
- formattedTime = msgDayjs.format("HH:mm");
- }
- // 昨天消息:显示为 "昨天 时:分",如 "昨天 19:25"
- else if (
- msgDayjs.format("YYYY-MM-DD") ===
- now.subtract(1, "day").format("YYYY-MM-DD")
- ) {
- formattedTime = `昨天 ${msgDayjs.format("HH:mm")}`;
- }
- // 前天至一周内消息:显示为 "星期 X 时:分",例如 "周三 16:10"
- else if (msgDayjs.isAfter(now.subtract(7, "day"))) {
- const weekDayMap = ["日", "一", "二", "三", "四", "五", "六"];
- const weekDay = weekDayMap[msgDayjs.day()];
- formattedTime = `周${weekDay} ${msgDayjs.format("HH:mm")}`;
- }
- // 超过一周的消息:显示为 "YYYY 年 X 月 X 日 时:分",如 "2025 年 8 月 15 日 10:35"
- else {
- formattedTime = msgDayjs.format("YYYY 年 M 月 D 日 HH:mm");
- }
-
- if (messageDate !== currentDate) {
- currentDate = messageDate;
- groups.push({ time: formattedTime, messages: [msg] });
- } else {
- const lastGroup = groups[groups.length - 1];
- // 如果时间相差超过5分钟,创建新的时间组
- const lastMessageTime = lastGroup.messages[
- lastGroup.messages.length - 1
- ].wechatTime
- ? lastGroup.messages[lastGroup.messages.length - 1].wechatTime * 1000
- : new Date(
- lastGroup.messages[lastGroup.messages.length - 1].createTime,
- ).getTime();
- const timeDiff = Math.abs(
- dayjs(msgTime).diff(dayjs(lastMessageTime), "minute"),
- );
-
- if (timeDiff >= 5) {
- groups.push({ time: formattedTime, messages: [msg] });
- } else {
- lastGroup.messages.push(msg);
- }
- }
+ // 使用 formatWechatTime 函数格式化时间戳
+ const formattedTime = formatWechatTime(msg.wechatTime);
+ groups.push({ time: formattedTime, messages: [msg] });
});
return groups;
diff --git a/Cunkebao/src/utils/common.ts b/Cunkebao/src/utils/common.ts
index 995b9c05..65db3b71 100644
--- a/Cunkebao/src/utils/common.ts
+++ b/Cunkebao/src/utils/common.ts
@@ -1,5 +1,53 @@
import { Modal } from "antd-mobile";
import { getSetting } from "@/store/module/settings";
+export function formatWechatTime(timestamp) {
+ // 处理时间戳(兼容秒级/毫秒级)
+ const date = new Date(
+ timestamp.toString().length === 10 ? timestamp * 1000 : timestamp,
+ );
+ const now = new Date();
+
+ // 获取消息时间的年月日时分
+ const messageYear = date.getFullYear();
+ const messageMonth = date.getMonth();
+ const messageDate = date.getDate();
+ const messageHour = date.getHours().toString().padStart(2, "0");
+ const messageMinute = date.getMinutes().toString().padStart(2, "0");
+
+ // 获取当前时间的年月日
+ const nowYear = now.getFullYear();
+ const nowMonth = now.getMonth();
+ const nowDate = now.getDate();
+
+ // 创建当天0点的时间对象,用于比较是否同一天
+ const today = new Date(nowYear, nowMonth, nowDate, 0, 0, 0);
+ const yesterday = new Date(today);
+ yesterday.setDate(yesterday.getDate() - 1);
+ const weekAgo = new Date(today);
+ weekAgo.setDate(weekAgo.getDate() - 6); // 7天前(包括今天)
+
+ // 消息日期(不含时间)
+ const messageDay = new Date(messageYear, messageMonth, messageDate, 0, 0, 0);
+
+ // 当天消息:只显示时分
+ if (messageDay.getTime() === today.getTime()) {
+ return `${messageHour}:${messageMinute}`;
+ }
+
+ // 昨天消息:显示"昨天 时分"
+ if (messageDay.getTime() === yesterday.getTime()) {
+ return `昨天 ${messageHour}:${messageMinute}`;
+ }
+
+ // 一周内消息:显示"星期X 时分"
+ if (messageDay.getTime() >= weekAgo.getTime()) {
+ const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
+ return `${weekdays[date.getDay()]} ${messageHour}:${messageMinute}`;
+ }
+
+ // 超过一周:显示"年月日 时分"
+ return `${messageYear}年${messageMonth + 1}月${messageDate}日 ${messageHour}:${messageMinute}`;
+}
/**
* 通用js调用弹窗,Promise风格
* @param content 弹窗内容
From 968480669e4d345f5f9adb5074d7eabbe13175eb 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, 27 Aug 2025 14:29:37 +0800
Subject: [PATCH 42/78] =?UTF-8?q?refactor(ckbox):=20=E9=87=8D=E6=9E=84?=
=?UTF-8?q?=E4=BE=A7=E8=BE=B9=E6=A0=8F=E7=BB=84=E4=BB=B6=E7=BB=93=E6=9E=84?=
=?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E7=BB=84=E7=BB=87?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
feat(ckbox): 添加垂直用户列表组件用于终端用户展示
feat(ckbox): 实现控制终端用户数据管理功能
fix(ckbox): 修复联系人列表样式问题
fix(ckbox): 解决消息列表数据同步问题
style(ckbox): 调整侧边栏样式和布局
style(ckbox): 优化组件样式文件结构
chore(ckbox): 移除未使用的客户相关组件和样式
---
Cunkebao/src/pages/login/Login.tsx | 49 +--
Cunkebao/src/pages/pc/ckbox/api.ts | 5 +
.../pc/ckbox/components/ChatWindow/index.tsx | 1 -
.../CustomerDetailModal.module.scss | 101 -----
.../ckbox/components/CustomerDetailModal.tsx | 370 ------------------
.../components/CustomerFilter.module.scss | 36 --
.../pc/ckbox/components/CustomerFilter.tsx | 297 --------------
.../components/CustomerFormModal.module.scss | 62 ---
.../pc/ckbox/components/CustomerFormModal.tsx | 327 ----------------
.../components/CustomerTagModal.module.scss | 106 -----
.../pc/ckbox/components/CustomerTagModal.tsx | 325 ---------------
.../ContactList/ContactList.module.scss | 0
.../{ => SidebarMenu}/ContactList/index.tsx | 0
.../MessageList/MessageList.module.scss | 0
.../{ => SidebarMenu}/MessageList/api.ts | 0
.../{ => SidebarMenu}/MessageList/data.ts | 0
.../{ => SidebarMenu}/MessageList/index.tsx | 0
.../pc/ckbox/components/SidebarMenu/index.tsx | 2 +-
.../VerticalUserList.module.scss | 95 +++++
.../components/VerticalUserList/index.tsx | 77 ++++
Cunkebao/src/pages/pc/ckbox/index.module.scss | 16 +-
Cunkebao/src/pages/pc/ckbox/index.tsx | 74 +++-
Cunkebao/src/pages/pc/ckbox/main.ts | 90 +++--
Cunkebao/src/store/module/ckchat.data.ts | 62 +++
Cunkebao/src/store/module/ckchat.ts | 42 +-
25 files changed, 441 insertions(+), 1696 deletions(-)
delete mode 100644 Cunkebao/src/pages/pc/ckbox/components/CustomerDetailModal.module.scss
delete mode 100644 Cunkebao/src/pages/pc/ckbox/components/CustomerDetailModal.tsx
delete mode 100644 Cunkebao/src/pages/pc/ckbox/components/CustomerFilter.module.scss
delete mode 100644 Cunkebao/src/pages/pc/ckbox/components/CustomerFilter.tsx
delete mode 100644 Cunkebao/src/pages/pc/ckbox/components/CustomerFormModal.module.scss
delete mode 100644 Cunkebao/src/pages/pc/ckbox/components/CustomerFormModal.tsx
delete mode 100644 Cunkebao/src/pages/pc/ckbox/components/CustomerTagModal.module.scss
delete mode 100644 Cunkebao/src/pages/pc/ckbox/components/CustomerTagModal.tsx
rename Cunkebao/src/pages/pc/ckbox/components/{ => SidebarMenu}/ContactList/ContactList.module.scss (100%)
rename Cunkebao/src/pages/pc/ckbox/components/{ => SidebarMenu}/ContactList/index.tsx (100%)
rename Cunkebao/src/pages/pc/ckbox/components/{ => SidebarMenu}/MessageList/MessageList.module.scss (100%)
rename Cunkebao/src/pages/pc/ckbox/components/{ => SidebarMenu}/MessageList/api.ts (100%)
rename Cunkebao/src/pages/pc/ckbox/components/{ => SidebarMenu}/MessageList/data.ts (100%)
rename Cunkebao/src/pages/pc/ckbox/components/{ => SidebarMenu}/MessageList/index.tsx (100%)
create mode 100644 Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/VerticalUserList.module.scss
create mode 100644 Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx
diff --git a/Cunkebao/src/pages/login/Login.tsx b/Cunkebao/src/pages/login/Login.tsx
index 40cf8816..73701a38 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 { Form, Input, Button, Toast, Checkbox } from "antd-mobile";
import {
EyeInvisibleOutline,
@@ -18,8 +18,8 @@ const Login: React.FC = () => {
const [countdown, setCountdown] = useState(0);
const [showPassword, setShowPassword] = useState(false);
const [agreeToTerms, setAgreeToTerms] = useState(false);
-
- const { login } = useUserStore();
+ const { setUserInfo } = useCkChatStore.getState();
+ const { login, login2 } = useUserStore();
// 倒计时效果
useEffect(() => {
@@ -62,30 +62,23 @@ const Login: React.FC = () => {
};
const getToken = (values: any) => {
- return new Promise((resolve, reject) => {
- // 添加typeId参数
- const loginParams = {
- ...values,
- typeId: activeTab as number,
- };
+ // 添加typeId参数
+ const loginParams = {
+ ...values,
+ typeId: activeTab as number,
+ };
- const response =
- activeTab === 1
- ? loginWithPassword(loginParams)
- : loginWithCode(loginParams);
+ const response =
+ activeTab === 1
+ ? loginWithPassword(loginParams)
+ : loginWithCode(loginParams);
- response
- .then(res => {
- // 获取设备总数
- const deviceTotal = res.deviceTotal || 0;
-
- // 更新状态管理(token会自动存储到localStorage,用户信息存储在状态管理中)
- login(res.token, res.member, deviceTotal);
- resolve(res);
- })
- .catch(err => {
- reject(err);
- });
+ response.then(res => {
+ const { member, kefuData, deviceTotal } = res;
+ login(res.token, member, deviceTotal);
+ const { self, token } = kefuData;
+ login2(token.access_token);
+ setUserInfo(self);
});
};
@@ -96,11 +89,7 @@ const Login: React.FC = () => {
return;
}
//获取存客宝
- getToken(values)
- .then(() => {})
- .finally(() => {
- setLoading(false);
- });
+ getToken(values);
};
// 第三方登录处理
diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts
index c58cff74..c642da36 100644
--- a/Cunkebao/src/pages/pc/ckbox/api.ts
+++ b/Cunkebao/src/pages/pc/ckbox/api.ts
@@ -63,6 +63,11 @@ export const getContactList = (params: { prevId: number; count: number }) => {
return request("/api/wechatFriend/list", params, "GET");
};
+//获取控制终端列表
+export const getControlTerminalList = params => {
+ return request("/api/wechataccount", params, "GET");
+};
+
// 搜索联系人
export const getChatMessage = (params: {
wechatAccountId: number;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
index af49755b..f2880c69 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
@@ -43,7 +43,6 @@ import {
FilePptOutlined,
PlayCircleFilled,
} from "@ant-design/icons";
-import dayjs from "dayjs";
import { ChatRecord, ContractData } from "@/pages/pc/ckbox/data";
import { clearUnreadCount, getMessages } from "@/pages/pc/ckbox/api";
import styles from "./ChatWindow.module.scss";
diff --git a/Cunkebao/src/pages/pc/ckbox/components/CustomerDetailModal.module.scss b/Cunkebao/src/pages/pc/ckbox/components/CustomerDetailModal.module.scss
deleted file mode 100644
index 26c9c6e4..00000000
--- a/Cunkebao/src/pages/pc/ckbox/components/CustomerDetailModal.module.scss
+++ /dev/null
@@ -1,101 +0,0 @@
-.customerDetailModal {
- :global(.ant-modal-body) {
- padding: 0;
- max-height: 70vh;
- overflow-y: auto;
- }
-}
-
-.modalTitle {
- display: flex;
- align-items: center;
- gap: 12px;
- font-size: 16px;
- font-weight: 600;
-}
-
-.modalContent {
- padding: 24px;
-}
-
-.infoCard {
- margin-bottom: 16px;
- border-radius: 6px;
-
- &:last-child {
- margin-bottom: 0;
- }
-
- :global(.ant-card-head) {
- background: #fafafa;
- border-bottom: 1px solid #f0f0f0;
- min-height: 40px;
-
- .ant-card-head-title {
- font-size: 14px;
- font-weight: 600;
- }
- }
-}
-
-.tagsContainer {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
-}
-
-.remarkText {
- color: #666;
- line-height: 1.6;
- white-space: pre-wrap;
-}
-
-.followUpItem {
- .followUpHeader {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
-
- .followUpType {
- font-weight: 500;
- color: #1890ff;
- background: #e6f7ff;
- padding: 2px 8px;
- border-radius: 4px;
- font-size: 12px;
- }
-
- .followUpDate {
- color: #8c8c8c;
- font-size: 12px;
- }
- }
-
- .followUpContent {
- color: #262626;
- line-height: 1.6;
- margin-bottom: 4px;
- }
-
- .nextFollowUp {
- color: #fa8c16;
- font-size: 12px;
- font-style: italic;
- }
-}
-
-// 响应式设计
-@media (max-width: 768px) {
- .modalContent {
- padding: 16px;
- }
-
- .followUpItem {
- .followUpHeader {
- flex-direction: column;
- align-items: flex-start;
- gap: 4px;
- }
- }
-}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/CustomerDetailModal.tsx b/Cunkebao/src/pages/pc/ckbox/components/CustomerDetailModal.tsx
deleted file mode 100644
index 9eace86e..00000000
--- a/Cunkebao/src/pages/pc/ckbox/components/CustomerDetailModal.tsx
+++ /dev/null
@@ -1,370 +0,0 @@
-import React, { useState, useEffect } from "react";
-import {
- Modal,
- Descriptions,
- Tag,
- Avatar,
- Button,
- Space,
- Divider,
- List,
- Card,
- Timeline,
- Empty,
- Spin,
-} from "antd";
-import {
- UserOutlined,
- PhoneOutlined,
- MailOutlined,
- WechatOutlined,
- EditOutlined,
- CalendarOutlined,
- EnvironmentOutlined,
- BankOutlined,
- TagOutlined,
-} from "@ant-design/icons";
-import dayjs from "dayjs";
-import { CustomerData, CustomerStatus, CustomerSource } from "../data";
-import { getCustomerDetail, getCustomerFollowUps } from "../api";
-import styles from "./CustomerDetailModal.module.scss";
-
-interface CustomerDetailModalProps {
- visible: boolean;
- customer: CustomerData | null;
- onCancel: () => void;
- onEdit: () => void;
-}
-
-const CustomerDetailModal: React.FC = ({
- visible,
- customer,
- onCancel,
- onEdit,
-}) => {
- const [loading, setLoading] = useState(false);
- const [customerDetail, setCustomerDetail] = useState(
- null,
- );
- const [followUps, setFollowUps] = useState([]);
-
- useEffect(() => {
- if (visible && customer) {
- fetchCustomerDetail();
- fetchFollowUps();
- }
- }, [visible, customer]);
-
- const fetchCustomerDetail = async () => {
- if (!customer) return;
- try {
- setLoading(true);
- const detail = await getCustomerDetail(customer.id);
- setCustomerDetail(detail);
- } catch (error) {
- console.error("获取客户详情失败:", error);
- } finally {
- setLoading(false);
- }
- };
-
- const fetchFollowUps = async () => {
- if (!customer) return;
- try {
- const data = await getCustomerFollowUps(customer.id);
- setFollowUps(data);
- } catch (error) {
- console.error("获取跟进记录失败:", error);
- }
- };
-
- const getStatusTag = (status: CustomerStatus) => {
- const statusConfig = {
- [CustomerStatus.ACTIVE]: { color: "green", text: "活跃" },
- [CustomerStatus.INACTIVE]: { color: "red", text: "非活跃" },
- [CustomerStatus.POTENTIAL]: { color: "blue", text: "潜在" },
- [CustomerStatus.LOST]: { color: "gray", text: "流失" },
- };
- const config = statusConfig[status];
- return {config.text};
- };
-
- const getSourceTag = (source: CustomerSource) => {
- const sourceConfig = {
- [CustomerSource.WECHAT]: {
- color: "green",
- text: "微信",
- icon: ,
- },
- [CustomerSource.WEBSITE]: {
- color: "blue",
- text: "官网",
- icon: ,
- },
- [CustomerSource.REFERRAL]: {
- color: "orange",
- text: "推荐",
- icon: ,
- },
- [CustomerSource.OTHER]: {
- color: "gray",
- text: "其他",
- icon: ,
- },
- };
- const config = sourceConfig[source];
- return (
-
- {config.text}
-
- );
- };
-
- const getLevelTag = (level?: string) => {
- const levelConfig = {
- vip: { color: "gold", text: "VIP客户" },
- normal: { color: "blue", text: "普通客户" },
- potential: { color: "orange", text: "潜在客户" },
- };
- const config = levelConfig[level as keyof typeof levelConfig];
- return config ? {config.text} : null;
- };
-
- const getGenderText = (gender?: string) => {
- const genderMap = {
- male: "男",
- female: "女",
- unknown: "未知",
- };
- return genderMap[gender as keyof typeof genderMap] || "未知";
- };
-
- if (!customerDetail) {
- return (
-
- 关闭
- ,
- }
- onClick={onEdit}
- >
- 编辑
- ,
- ]}
- width={800}
- >
-
-
-
-
- );
- }
-
- return (
-
- }
- />
- {customerDetail.name}
-
- }
- open={visible}
- onCancel={onCancel}
- footer={[
- ,
- }
- onClick={onEdit}
- >
- 编辑
- ,
- ]}
- width={900}
- className={styles.customerDetailModal}
- >
-
- {/* 基本信息 */}
-
-
-
- {customerDetail.name}
-
-
-
-
- {customerDetail.phone}
-
-
-
- {customerDetail.email ? (
-
-
- {customerDetail.email}
-
- ) : (
- "-"
- )}
-
-
- {customerDetail.wechat ? (
-
-
- {customerDetail.wechat}
-
- ) : (
- "-"
- )}
-
-
- {getGenderText(customerDetail.gender)}
-
-
- {customerDetail.birthday ? (
-
-
- {dayjs(customerDetail.birthday).format("YYYY-MM-DD")}
-
- ) : (
- "-"
- )}
-
-
- {getSourceTag(customerDetail.source)}
-
-
- {getStatusTag(customerDetail.status)}
-
-
- {getLevelTag(customerDetail.level)}
-
-
- {customerDetail.lastContact ? (
-
-
- {dayjs(customerDetail.lastContact).format("YYYY-MM-DD HH:mm")}
-
- ) : (
- "-"
- )}
-
-
-
-
- {/* 公司信息 */}
- {(customerDetail.company || customerDetail.position) && (
-
-
-
- {customerDetail.company ? (
-
-
- {customerDetail.company}
-
- ) : (
- "-"
- )}
-
-
- {customerDetail.position || "-"}
-
-
- {customerDetail.address ? (
-
-
- {customerDetail.address}
-
- ) : (
- "-"
- )}
-
-
-
- )}
-
- {/* 标签信息 */}
- {customerDetail.tags && customerDetail.tags.length > 0 && (
-
-
- {customerDetail.tags.map((tag, index) => (
- }>
- {tag}
-
- ))}
-
-
- )}
-
- {/* 备注信息 */}
- {customerDetail.remark && (
-
- {customerDetail.remark}
-
- )}
-
-
-
- {/* 跟进记录 */}
-
- {followUps.length > 0 ? (
-
- {followUps.map((followUp, index) => (
-
-
-
-
- {followUp.type}
-
-
- {dayjs(followUp.createdAt).format("YYYY-MM-DD HH:mm")}
-
-
-
- {followUp.content}
-
- {followUp.nextFollowUpDate && (
-
- 下次跟进:{" "}
- {dayjs(followUp.nextFollowUpDate).format("YYYY-MM-DD")}
-
- )}
-
-
- ))}
-
- ) : (
-
- )}
-
-
- {/* 时间信息 */}
-
-
-
- {dayjs(customerDetail.createdAt).format("YYYY-MM-DD HH:mm:ss")}
-
-
- {dayjs(customerDetail.updatedAt).format("YYYY-MM-DD HH:mm:ss")}
-
-
-
-
-
- );
-};
-
-export default CustomerDetailModal;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/CustomerFilter.module.scss b/Cunkebao/src/pages/pc/ckbox/components/CustomerFilter.module.scss
deleted file mode 100644
index 5581de1b..00000000
--- a/Cunkebao/src/pages/pc/ckbox/components/CustomerFilter.module.scss
+++ /dev/null
@@ -1,36 +0,0 @@
-.filterContent {
- .activeFilters {
- margin-bottom: 24px;
- padding: 16px;
- background: #f6ffed;
- border: 1px solid #b7eb8f;
- border-radius: 6px;
-
- h4 {
- margin: 0 0 12px 0;
- font-size: 14px;
- font-weight: 600;
- color: #52c41a;
- }
-
- .filterTags {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- }
- }
-}
-
-// 响应式设计
-@media (max-width: 768px) {
- .filterContent {
- .activeFilters {
- margin-bottom: 16px;
- padding: 12px;
-
- .filterTags {
- gap: 6px;
- }
- }
- }
-}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/CustomerFilter.tsx b/Cunkebao/src/pages/pc/ckbox/components/CustomerFilter.tsx
deleted file mode 100644
index d6e0f8e0..00000000
--- a/Cunkebao/src/pages/pc/ckbox/components/CustomerFilter.tsx
+++ /dev/null
@@ -1,297 +0,0 @@
-import React, { useState, useEffect } from "react";
-import {
- Modal,
- Form,
- Select,
- DatePicker,
- Button,
- Row,
- Col,
- Space,
- Tag,
- Input,
-} from "antd";
-import { FilterOutlined, ClearOutlined, TagOutlined } from "@ant-design/icons";
-import { CustomerStatus, CustomerSource, CustomerFilters } from "../data";
-import { getAllTags } from "../api";
-import styles from "./CustomerFilter.module.scss";
-
-const { RangePicker } = DatePicker;
-const { Option } = Select;
-
-interface CustomerFilterProps {
- visible: boolean;
- filters: CustomerFilters;
- onCancel: () => void;
- onOk: (filters: CustomerFilters) => void;
-}
-
-const CustomerFilter: React.FC = ({
- visible,
- filters,
- onCancel,
- onOk,
-}) => {
- const [form] = Form.useForm();
- const [allTags, setAllTags] = useState([]);
- const [loading, setLoading] = useState(false);
-
- useEffect(() => {
- if (visible) {
- fetchAllTags();
- // 设置表单初始值
- form.setFieldsValue({
- status: filters.status,
- source: filters.source,
- dateRange: filters.dateRange
- ? [
- filters.dateRange[0] ? new Date(filters.dateRange[0]) : null,
- filters.dateRange[1] ? new Date(filters.dateRange[1]) : null,
- ]
- : undefined,
- tags: filters.tags,
- level: filters.level,
- gender: filters.gender,
- });
- }
- }, [visible, filters, form]);
-
- const fetchAllTags = async () => {
- try {
- const tags = await getAllTags();
- setAllTags(tags);
- } catch (error) {
- console.error("获取标签失败:", error);
- }
- };
-
- const handleOk = async () => {
- try {
- const values = await form.validateFields();
- const newFilters: CustomerFilters = {
- status: values.status,
- source: values.source,
- dateRange: values.dateRange
- ? [
- values.dateRange[0]?.toISOString().split("T")[0] || "",
- values.dateRange[1]?.toISOString().split("T")[0] || "",
- ]
- : undefined,
- tags: values.tags || [],
- level: values.level,
- gender: values.gender,
- };
- onOk(newFilters);
- } catch (error) {
- console.error("表单验证失败:", error);
- }
- };
-
- const handleReset = () => {
- form.resetFields();
- };
-
- const getActiveFilterCount = () => {
- let count = 0;
- if (filters.status) count++;
- if (filters.source) count++;
- if (filters.dateRange) count++;
- if (filters.tags && filters.tags.length > 0) count++;
- if (filters.level) count++;
- if (filters.gender) count++;
- return count;
- };
-
- const renderFilterSummary = () => {
- const activeFilters = [];
-
- if (filters.status) {
- const statusText = {
- [CustomerStatus.ACTIVE]: "活跃",
- [CustomerStatus.INACTIVE]: "非活跃",
- [CustomerStatus.POTENTIAL]: "潜在",
- [CustomerStatus.LOST]: "流失",
- }[filters.status];
- activeFilters.push(
-
- 状态: {statusText}
- ,
- );
- }
-
- if (filters.source) {
- const sourceText = {
- [CustomerSource.WECHAT]: "微信",
- [CustomerSource.WEBSITE]: "官网",
- [CustomerSource.REFERRAL]: "推荐",
- [CustomerSource.OTHER]: "其他",
- }[filters.source];
- activeFilters.push(
-
- 来源: {sourceText}
- ,
- );
- }
-
- if (filters.dateRange) {
- activeFilters.push(
-
- 日期: {filters.dateRange[0]} ~ {filters.dateRange[1]}
- ,
- );
- }
-
- if (filters.tags && filters.tags.length > 0) {
- activeFilters.push(
-
- 标签: {filters.tags.join(", ")}
- ,
- );
- }
-
- if (filters.level) {
- const levelText = {
- vip: "VIP客户",
- normal: "普通客户",
- potential: "潜在客户",
- }[filters.level];
- activeFilters.push(
-
- 等级: {levelText}
- ,
- );
- }
-
- if (filters.gender) {
- const genderText = {
- male: "男",
- female: "女",
- unknown: "未知",
- }[filters.gender];
- activeFilters.push(
-
- 性别: {genderText}
- ,
- );
- }
-
- return activeFilters;
- };
-
- return (
-
-
- 筛选条件
- {getActiveFilterCount() > 0 && (
- {getActiveFilterCount()} 个筛选条件
- )}
-
- }
- open={visible}
- onCancel={onCancel}
- footer={[
- } onClick={handleReset}>
- 重置
- ,
- ,
- ,
- ]}
- width={600}
- >
-
- {/* 当前筛选条件 */}
- {getActiveFilterCount() > 0 && (
-
-
当前筛选条件:
-
{renderFilterSummary()}
-
- )}
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default CustomerFilter;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/CustomerFormModal.module.scss b/Cunkebao/src/pages/pc/ckbox/components/CustomerFormModal.module.scss
deleted file mode 100644
index 19fe4008..00000000
--- a/Cunkebao/src/pages/pc/ckbox/components/CustomerFormModal.module.scss
+++ /dev/null
@@ -1,62 +0,0 @@
-.customerForm {
- .avatarSection {
- text-align: center;
- margin-bottom: 24px;
- padding: 16px;
- background: #fafafa;
- border-radius: 8px;
- }
-
- .avatarUpload {
- display: inline-block;
- cursor: pointer;
- transition: all 0.3s;
-
- &:hover {
- opacity: 0.8;
- }
- }
-
- .avatarPlaceholder {
- width: 80px;
- height: 80px;
- border: 2px dashed #d9d9d9;
- border-radius: 50%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- color: #8c8c8c;
- font-size: 12px;
- transition: all 0.3s;
-
- &:hover {
- border-color: #1890ff;
- color: #1890ff;
- }
-
- .anticon {
- font-size: 24px;
- margin-bottom: 4px;
- }
- }
-}
-
-// 响应式设计
-@media (max-width: 768px) {
- .customerForm {
- .avatarSection {
- margin-bottom: 16px;
- padding: 12px;
- }
-
- .avatarPlaceholder {
- width: 60px;
- height: 60px;
-
- .anticon {
- font-size: 20px;
- }
- }
- }
-}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/CustomerFormModal.tsx b/Cunkebao/src/pages/pc/ckbox/components/CustomerFormModal.tsx
deleted file mode 100644
index 60bf9f43..00000000
--- a/Cunkebao/src/pages/pc/ckbox/components/CustomerFormModal.tsx
+++ /dev/null
@@ -1,327 +0,0 @@
-import React, { useState, useEffect } from "react";
-import {
- Modal,
- Form,
- Input,
- Select,
- DatePicker,
- Button,
- Row,
- Col,
- message,
- Upload,
- Avatar,
-} from "antd";
-import {
- UserOutlined,
- PhoneOutlined,
- MailOutlined,
- WechatOutlined,
- UploadOutlined,
- PlusOutlined,
-} from "@ant-design/icons";
-import dayjs from "dayjs";
-import {
- CustomerData,
- CustomerFormData,
- CustomerStatus,
- CustomerSource,
-} from "../data";
-import { createCustomer, updateCustomer } from "../api";
-import styles from "./CustomerFormModal.module.scss";
-
-const { Option } = Select;
-const { TextArea } = Input;
-
-interface CustomerFormModalProps {
- visible: boolean;
- customer: CustomerData | null;
- onCancel: () => void;
- onOk: () => void;
-}
-
-const CustomerFormModal: React.FC = ({
- visible,
- customer,
- onCancel,
- onOk,
-}) => {
- const [form] = Form.useForm();
- const [loading, setLoading] = useState(false);
- const [avatarUrl, setAvatarUrl] = useState("");
-
- const isEdit = !!customer;
-
- useEffect(() => {
- if (visible) {
- if (customer) {
- // 编辑模式,填充表单数据
- form.setFieldsValue({
- name: customer.name,
- phone: customer.phone,
- email: customer.email,
- wechat: customer.wechat,
- source: customer.source,
- status: customer.status,
- tags: customer.tags,
- remark: customer.remark,
- company: customer.company,
- position: customer.position,
- address: customer.address,
- birthday: customer.birthday ? dayjs(customer.birthday) : undefined,
- gender: customer.gender,
- level: customer.level,
- });
- setAvatarUrl(customer.avatar || "");
- } else {
- // 新增模式,重置表单
- form.resetFields();
- setAvatarUrl("");
- }
- }
- }, [visible, customer, form]);
-
- const handleSubmit = async () => {
- try {
- const values = await form.validateFields();
- setLoading(true);
-
- const formData: CustomerFormData = {
- ...values,
- birthday: values.birthday
- ? values.birthday.format("YYYY-MM-DD")
- : undefined,
- avatar: avatarUrl,
- };
-
- if (isEdit) {
- await updateCustomer(customer!.id, formData);
- message.success("客户信息更新成功");
- } else {
- await createCustomer(formData);
- message.success("客户创建成功");
- }
-
- onOk();
- } catch (error) {
- console.error("提交失败:", error);
- message.error(isEdit ? "更新失败" : "创建失败");
- } finally {
- setLoading(false);
- }
- };
-
- const handleAvatarChange = (info: any) => {
- if (info.file.status === "done") {
- setAvatarUrl(info.file.response.url);
- message.success("头像上传成功");
- } else if (info.file.status === "error") {
- message.error("头像上传失败");
- }
- };
-
- const uploadProps = {
- name: "file",
- action: "/api/upload",
- headers: {
- authorization: "authorization-text",
- },
- onChange: handleAvatarChange,
- showUploadList: false,
- };
-
- return (
-
- 取消
- ,
- ,
- ]}
- width={800}
- destroyOnClose
- >
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default CustomerFormModal;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/CustomerTagModal.module.scss b/Cunkebao/src/pages/pc/ckbox/components/CustomerTagModal.module.scss
deleted file mode 100644
index 9a52c599..00000000
--- a/Cunkebao/src/pages/pc/ckbox/components/CustomerTagModal.module.scss
+++ /dev/null
@@ -1,106 +0,0 @@
-.tagModalContent {
- .createTagSection {
- margin-bottom: 24px;
- padding: 16px;
- background: #fafafa;
- border-radius: 6px;
-
- h4 {
- margin: 0 0 12px 0;
- font-size: 14px;
- font-weight: 600;
- color: #262626;
- }
- }
-
- .tagListSection {
- margin-bottom: 24px;
-
- h4 {
- margin: 0 0 12px 0;
- font-size: 14px;
- font-weight: 600;
- color: #262626;
- }
-
- .tagGrid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
- gap: 12px;
- }
-
- .tagItem {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 8px 12px;
- border: 1px solid #f0f0f0;
- border-radius: 6px;
- cursor: pointer;
- transition: all 0.3s;
-
- &:hover {
- border-color: #1890ff;
- background: #f6ffed;
- }
-
- &.selected {
- border-color: #1890ff;
- background: #e6f7ff;
- }
-
- .tagActions {
- display: flex;
- gap: 4px;
- opacity: 0;
- transition: opacity 0.3s;
- }
-
- &:hover .tagActions {
- opacity: 1;
- }
- }
- }
-
- .selectedTagsSection {
- h4 {
- margin: 0 0 12px 0;
- font-size: 14px;
- font-weight: 600;
- color: #262626;
- }
-
- .selectedTags {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- }
- }
-}
-
-// 响应式设计
-@media (max-width: 768px) {
- .tagModalContent {
- .createTagSection {
- margin-bottom: 16px;
- padding: 12px;
- }
-
- .tagListSection {
- margin-bottom: 16px;
-
- .tagGrid {
- grid-template-columns: 1fr;
- gap: 8px;
- }
-
- .tagItem {
- padding: 6px 8px;
-
- .tagActions {
- opacity: 1;
- }
- }
- }
- }
-}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/CustomerTagModal.tsx b/Cunkebao/src/pages/pc/ckbox/components/CustomerTagModal.tsx
deleted file mode 100644
index 703393f8..00000000
--- a/Cunkebao/src/pages/pc/ckbox/components/CustomerTagModal.tsx
+++ /dev/null
@@ -1,325 +0,0 @@
-import React, { useState, useEffect } from "react";
-import {
- Modal,
- Tag,
- Button,
- Input,
- Space,
- List,
- Empty,
- message,
- Popconfirm,
- ColorPicker,
- Form,
- Row,
- Col,
-} from "antd";
-import {
- PlusOutlined,
- DeleteOutlined,
- TagOutlined,
- EditOutlined,
-} from "@ant-design/icons";
-import { CustomerData, TagData } from "../data";
-import {
- getCustomerTags,
- updateCustomerTags,
- getAllTags,
- createTag,
- deleteTag,
-} from "../api";
-import styles from "./CustomerTagModal.module.scss";
-
-interface CustomerTagModalProps {
- visible: boolean;
- customer: CustomerData | null;
- onCancel: () => void;
- onOk: () => void;
-}
-
-const CustomerTagModal: React.FC = ({
- visible,
- customer,
- onCancel,
- onOk,
-}) => {
- const [loading, setLoading] = useState(false);
- const [customerTags, setCustomerTags] = useState([]);
- const [allTags, setAllTags] = useState([]);
- const [selectedTags, setSelectedTags] = useState([]);
- const [newTagName, setNewTagName] = useState("");
- const [newTagColor, setNewTagColor] = useState("#1890ff");
- const [editingTag, setEditingTag] = useState(null);
-
- useEffect(() => {
- if (visible && customer) {
- fetchCustomerTags();
- fetchAllTags();
- }
- }, [visible, customer]);
-
- useEffect(() => {
- if (customerTags.length > 0) {
- setSelectedTags(customerTags);
- }
- }, [customerTags]);
-
- const fetchCustomerTags = async () => {
- if (!customer) return;
- try {
- const tags = await getCustomerTags(customer.id);
- setCustomerTags(tags.map(tag => tag.name));
- } catch (error) {
- console.error("获取客户标签失败:", error);
- }
- };
-
- const fetchAllTags = async () => {
- try {
- const tags = await getAllTags();
- setAllTags(tags);
- } catch (error) {
- console.error("获取所有标签失败:", error);
- }
- };
-
- const handleTagToggle = (tagName: string) => {
- setSelectedTags(prev => {
- if (prev.includes(tagName)) {
- return prev.filter(tag => tag !== tagName);
- } else {
- return [...prev, tagName];
- }
- });
- };
-
- const handleSave = async () => {
- if (!customer) return;
- try {
- setLoading(true);
- await updateCustomerTags(customer.id, selectedTags);
- message.success("标签更新成功");
- onOk();
- } catch (error) {
- message.error("标签更新失败");
- } finally {
- setLoading(false);
- }
- };
-
- const handleCreateTag = async () => {
- if (!newTagName.trim()) {
- message.warning("请输入标签名称");
- return;
- }
-
- try {
- await createTag({
- name: newTagName.trim(),
- color: newTagColor,
- });
- message.success("标签创建成功");
- setNewTagName("");
- setNewTagColor("#1890ff");
- fetchAllTags();
- } catch (error) {
- message.error("标签创建失败");
- }
- };
-
- const handleDeleteTag = async (tagId: string) => {
- try {
- await deleteTag(tagId);
- message.success("标签删除成功");
- fetchAllTags();
- // 如果删除的标签在客户标签中,也要移除
- const tagToDelete = allTags.find(tag => tag.id === tagId);
- if (tagToDelete && selectedTags.includes(tagToDelete.name)) {
- setSelectedTags(prev => prev.filter(tag => tag !== tagToDelete.name));
- }
- } catch (error) {
- message.error("标签删除失败");
- }
- };
-
- const handleEditTag = (tag: TagData) => {
- setEditingTag(tag);
- setNewTagName(tag.name);
- setNewTagColor(tag.color);
- };
-
- const handleUpdateTag = async () => {
- if (!editingTag || !newTagName.trim()) {
- message.warning("请输入标签名称");
- return;
- }
-
- try {
- // 这里需要后端提供更新标签的接口
- // await updateTag(editingTag.id, { name: newTagName.trim(), color: newTagColor });
- message.success("标签更新成功");
- setEditingTag(null);
- setNewTagName("");
- setNewTagColor("#1890ff");
- fetchAllTags();
- } catch (error) {
- message.error("标签更新失败");
- }
- };
-
- const handleCancelEdit = () => {
- setEditingTag(null);
- setNewTagName("");
- setNewTagColor("#1890ff");
- };
-
- return (
-
- 取消
- ,
- ,
- ]}
- width={600}
- >
-
- {/* 创建新标签 */}
-
-
创建新标签
-
-
- setNewTagName(e.target.value)}
- onPressEnter={editingTag ? handleUpdateTag : handleCreateTag}
- />
-
-
- setNewTagColor(color.toHexString())}
- />
-
-
-
- {editingTag ? (
- <>
-
-
- >
- ) : (
- }
- onClick={handleCreateTag}
- >
- 创建
-
- )}
-
-
-
-
-
- {/* 标签列表 */}
-
-
选择标签
- {allTags.length > 0 ? (
-
- {allTags.map(tag => (
-
handleTagToggle(tag.name)}
- >
-
}
- style={{ cursor: "pointer" }}
- >
- {tag.name}
-
-
-
}
- onClick={e => {
- e.stopPropagation();
- handleEditTag(tag);
- }}
- />
-
handleDeleteTag(tag.id)}
- okText="确定"
- cancelText="取消"
- >
- }
- danger
- onClick={e => e.stopPropagation()}
- />
-
-
-
- ))}
-
- ) : (
-
- )}
-
-
- {/* 已选标签 */}
- {selectedTags.length > 0 && (
-
-
已选标签
-
- {selectedTags.map(tagName => {
- const tag = allTags.find(t => t.name === tagName);
- return (
- }
- closable
- onClose={() => handleTagToggle(tagName)}
- >
- {tagName}
-
- );
- })}
-
-
- )}
-
-
- );
-};
-
-export default CustomerTagModal;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ContactList/ContactList.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactList/ContactList.module.scss
similarity index 100%
rename from Cunkebao/src/pages/pc/ckbox/components/ContactList/ContactList.module.scss
rename to Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactList/ContactList.module.scss
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactList/index.tsx
similarity index 100%
rename from Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx
rename to Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/ContactList/index.tsx
diff --git a/Cunkebao/src/pages/pc/ckbox/components/MessageList/MessageList.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/MessageList.module.scss
similarity index 100%
rename from Cunkebao/src/pages/pc/ckbox/components/MessageList/MessageList.module.scss
rename to Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/MessageList.module.scss
diff --git a/Cunkebao/src/pages/pc/ckbox/components/MessageList/api.ts b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/api.ts
similarity index 100%
rename from Cunkebao/src/pages/pc/ckbox/components/MessageList/api.ts
rename to Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/api.ts
diff --git a/Cunkebao/src/pages/pc/ckbox/components/MessageList/data.ts b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/data.ts
similarity index 100%
rename from Cunkebao/src/pages/pc/ckbox/components/MessageList/data.ts
rename to Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/data.ts
diff --git a/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx
similarity index 100%
rename from Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx
rename to Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
index 20adfd63..36cbf763 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
@@ -8,7 +8,7 @@ import {
} from "@ant-design/icons";
import { ContractData } from "@/pages/pc/ckbox/data";
import WechatFriendsModule from "./WechatFriendsModule";
-import MessageList from "../MessageList/index";
+import MessageList from "./MessageList/index";
import LayoutFiexd from "@/components/Layout/LayoutFiexd";
import styles from "./SidebarMenu.module.scss";
import { getChatSessions } from "@/store/module/ckchat";
diff --git a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/VerticalUserList.module.scss b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/VerticalUserList.module.scss
new file mode 100644
index 00000000..bd853ae5
--- /dev/null
+++ b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/VerticalUserList.module.scss
@@ -0,0 +1,95 @@
+.verticalUserList {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ background-color: #2e2e2e;
+ color: #fff;
+ width: 60px;
+
+ .userListHeader {
+ padding: 10px 0;
+ text-align: center;
+ border-bottom: 1px solid #3a3a3a;
+
+ .allFriends {
+ font-size: 12px;
+ color: #ccc;
+ }
+ }
+
+ .userList {
+ flex: 1;
+ overflow-y: auto;
+ padding: 10px 0;
+
+ &::-webkit-scrollbar {
+ width: 4px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: #555;
+ border-radius: 2px;
+ }
+ }
+
+ .userItem {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 10px 0;
+ position: relative;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #3a3a3a;
+ }
+
+ &.active {
+ background-color: #3a3a3a;
+
+ &::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 3px;
+ height: 20px;
+ background-color: #1890ff;
+ }
+ }
+ }
+
+ .userAvatar {
+ border: 2px solid transparent;
+
+ .active & {
+ border-color: #1890ff;
+ }
+ }
+
+ .messageBadge {
+ :global(.ant-badge-count) {
+ background-color: #ff4d4f;
+ box-shadow: none;
+ font-size: 10px;
+ min-width: 16px;
+ height: 16px;
+ line-height: 16px;
+ padding: 0 4px;
+ border-radius: 8px;
+ }
+ }
+
+ .onlineIndicator {
+ position: absolute;
+ bottom: 10px;
+ right: 10px;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background-color: #52c41a;
+ border: 1px solid #2e2e2e;
+ }
+}
\ No newline at end of file
diff --git a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx
new file mode 100644
index 00000000..bc10cfa1
--- /dev/null
+++ b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx
@@ -0,0 +1,77 @@
+import React, { useEffect, useState } from "react";
+import { Avatar, Badge, Tooltip } from "antd";
+import styles from "./VerticalUserList.module.scss";
+import { CkChatCtrlUserData } from "@/store/module/ckchat.data";
+interface UserItem {
+ id: string;
+ name: string;
+ avatar: string;
+ messageCount?: number;
+ isOnline?: boolean;
+}
+
+interface VerticalUserListProps {
+ onUserSelect: (userId: string) => void;
+}
+import { getCtrlUserList, useCkChatStore } from "@/store/module/ckchat";
+const VerticalUserList: React.FC = ({
+ onUserSelect,
+}) => {
+ // 直接从store获取ctrlUserList,这样当store中的数据更新时,组件会自动重新渲染
+ const ctrlUserList = useCkChatStore(state => state.ctrlUserList);
+ const [activeUserId, setActiveUserId] = useState();
+
+ useEffect(() => {
+ console.log("控制终端用户列表更新:", ctrlUserList);
+ }, [ctrlUserList]);
+ // 格式化消息数量显示
+ const formatMessageCount = (count: number) => {
+ if (count > 99) return "99+";
+ return count.toString();
+ };
+
+ const handleUserSelect = (userId: number) => {
+ setActiveUserId(userId);
+ onUserSelect(userId.toString());
+ };
+
+ return (
+
+
+
+ {ctrlUserList.map(user => (
+
+ handleUserSelect(user.id)}
+ >
+
+
+ {!user.avatar && user.name.charAt(0)}
+
+
+ {user.isOnline &&
}
+
+
+ ))}
+
+
+ );
+};
+
+export default VerticalUserList;
diff --git a/Cunkebao/src/pages/pc/ckbox/index.module.scss b/Cunkebao/src/pages/pc/ckbox/index.module.scss
index 113abc01..fec9c9a5 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/index.module.scss
@@ -14,11 +14,17 @@
font-weight: bold;
}
- .sider {
- background: #fff;
- border-right: 1px solid #f0f0f0;
- overflow: auto;
- }
+ .verticalSider {
+ background: #2e2e2e;
+ border-right: 1px solid #3a3a3a;
+ overflow: hidden;
+}
+
+.sider {
+ background: #fff;
+ border-right: 1px solid #f0f0f0;
+ overflow: auto;
+}
.sidebar {
height: 100%;
diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx
index ab19ab07..b56fbf7c 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/index.tsx
@@ -2,17 +2,71 @@ import React, { useState, useEffect } from "react";
import { Layout, Button, Space, message, Tooltip } from "antd";
import { InfoCircleOutlined, MessageOutlined } from "@ant-design/icons";
import dayjs from "dayjs";
-import { ContractData, GroupData } from "./data";
+import { ContractData } from "./data";
import ChatWindow from "./components/ChatWindow/index";
import SidebarMenu from "./components/SidebarMenu/index";
+import VerticalUserList from "./components/VerticalUserList";
import styles from "./index.module.scss";
import { addChatSession } from "@/store/module/ckchat";
const { Header, Content, Sider } = Layout;
import { chatInitAPIdata } from "./main";
+// 垂直侧边栏用户数据
+const verticalUsers = [
+ {
+ id: "all",
+ name: "全部好友",
+ avatar: "",
+ messageCount: 99,
+ },
+ {
+ id: "game",
+ name: "游戏好友",
+ avatar: "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
+ messageCount: 99,
+ },
+ {
+ id: "card",
+ name: "卡片好友",
+ avatar: "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
+ messageCount: 99,
+ },
+ {
+ id: "dh",
+ name: "DH好友",
+ avatar: "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
+ },
+ {
+ id: "yp",
+ name: "用户多",
+ avatar: "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
+ },
+ {
+ id: "dq",
+ name: "当前",
+ avatar: "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
+ },
+ {
+ id: "rq",
+ name: "人气榜",
+ avatar: "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
+ },
+ {
+ id: "ai",
+ name: "AI助手",
+ avatar: "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
+ },
+ {
+ id: "hf",
+ name: "回复号码",
+ avatar: "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png",
+ },
+];
+
const CkboxPage: React.FC = () => {
const [messageApi, contextHolder] = message.useMessage();
const [contracts, setContacts] = useState([]);
const [currentChat, setCurrentChat] = useState(null);
+ const [activeVerticalUserId, setActiveVerticalUserId] = useState("all");
const [loading, setLoading] = useState(false);
const [showProfile, setShowProfile] = useState(true);
@@ -66,12 +120,28 @@ const CkboxPage: React.FC = () => {
}
};
+ // 处理垂直侧边栏用户选择
+ const handleVerticalUserSelect = (userId: string) => {
+ setActiveVerticalUserId(userId);
+ // 这里可以根据选择的用户类别筛选不同的联系人列表
+ // 例如:根据userId加载不同分类的联系人
+ };
+
return (
{contextHolder}
- {/* 左侧边栏 */}
+ {/* 垂直侧边栏 */}
+
+
+
+
+ {/* 左侧联系人边栏 */}
{
try {
- //获取Token
- const Token = await getToken();
- //获取用户信息
- const userInfo = await getChuKeBaoUserInfo();
- setUserInfo(userInfo);
-
- //获取用户账号Id
- const accountId = getAccountId();
-
- //发起链接
- if (Token && accountId) {
- connect({
- url: "wss://kf.quwanzhi.com:9993", // 显式指定WebSocket URL,确保使用正确的服务器地址
- accessToken: String(Token),
- accountId: accountId,
- client: "kefu-client",
- cmdType: "CmdSignIn",
- seq: +new Date(),
- });
- console.log("WebSocket连接已初始化");
- } else {
- console.error("WebSocket连接初始化失败:缺少Token或accountId");
- }
+ // //发起链接
+ // if (Token && accountId) {
+ // connect({
+ // url: "wss://kf.quwanzhi.com:9993", // 显式指定WebSocket URL,确保使用正确的服务器地址
+ // accessToken: String(Token),
+ // accountId: accountId,
+ // client: "kefu-client",
+ // cmdType: "CmdSignIn",
+ // seq: +new Date(),
+ // });
+ // console.log("WebSocket连接已初始化");
+ // } else {
+ // console.error("WebSocket连接初始化失败:缺少Token或accountId");
+ // }
//获取联系人列表
const contractList = await getAllContactList();
+
+ // 提取不重复的wechatAccountId组
+ const uniqueWechatAccountIds: number[] =
+ getUniqueWechatAccountIds(contractList);
+
+ //获取控制终端列表
+ const controlTerminalList: CkChatCtrlUserData[] =
+ await getControlTerminalListByWechatAccountIds(uniqueWechatAccountIds);
+
+ //添加控制终端用户
+ controlTerminalList.forEach(item => {
+ addCtrlUser(item);
+ });
+
//获取群列表
const chatRoomList = await getAllChatRoomList();
return {
@@ -51,7 +57,14 @@ export const chatInitAPIdata = async () => {
return [];
}
};
-
+//获取控制终端列表
+export const getControlTerminalListByWechatAccountIds = (
+ WechatAccountIds: number[],
+) => {
+ return Promise.all(
+ WechatAccountIds.map(id => getControlTerminalList({ id: id })),
+ );
+};
// 递归获取所有联系人列表
export const getAllContactList = async () => {
try {
@@ -61,7 +74,6 @@ export const getAllContactList = async () => {
let hasMore = true;
while (hasMore) {
- console.log(`获取联系人列表,prevId: ${prevId}, count: ${count}`);
const contractList = await getContactList({
prevId,
count,
@@ -87,14 +99,32 @@ export const getAllContactList = async () => {
prevId = lastContact.id;
}
}
-
- console.log(`总共获取到 ${allContacts.length} 条联系人数据`);
return allContacts;
} catch (error) {
console.error("获取所有联系人列表失败:", error);
return [];
}
};
+
+// 提取不重复的wechatAccountId组
+export const getUniqueWechatAccountIds = contacts => {
+ if (!contacts || !Array.isArray(contacts) || contacts.length === 0) {
+ return [];
+ }
+
+ // 使用Set来存储不重复的wechatAccountId
+ const uniqueAccountIdsSet = new Set();
+
+ // 遍历联系人列表,将每个wechatAccountId添加到Set中
+ contacts.forEach(contact => {
+ if (contact && contact.wechatAccountId) {
+ uniqueAccountIdsSet.add(contact.wechatAccountId);
+ }
+ });
+
+ // 将Set转换为数组并返回
+ return Array.from(uniqueAccountIdsSet);
+};
// 递归获取所有群列表
export const getAllChatRoomList = async () => {
try {
@@ -104,7 +134,6 @@ export const getAllChatRoomList = async () => {
let hasMore = true;
while (hasMore) {
- console.log(`获取群列表,prevId: ${prevId}, count: ${count}`);
const contractList = await getChatRoomList({
prevId,
count,
@@ -131,7 +160,6 @@ export const getAllChatRoomList = async () => {
}
}
- console.log(`总共获取到 ${allContacts.length} 条群数据`);
return allContacts;
} catch (error) {
console.error("获取所有群列表失败:", error);
diff --git a/Cunkebao/src/store/module/ckchat.data.ts b/Cunkebao/src/store/module/ckchat.data.ts
index 97c046a0..8d026158 100644
--- a/Cunkebao/src/store/module/ckchat.data.ts
+++ b/Cunkebao/src/store/module/ckchat.data.ts
@@ -1,3 +1,65 @@
+//终端用户数据接口
+export interface CkChatCtrlUserData {
+ /** 用户唯一标识ID */
+ id: number;
+ /** 租户ID(多租户系统中用于区分租户) */
+ tenantId: number;
+ /** 微信ID(用户微信账号唯一标识) */
+ wechatId: string;
+ /** 用户昵称 */
+ nickname: string;
+ /** 用户别名/备注名(自定义标识) */
+ alias: string;
+ /** 头像图片URL(阿里云OSS存储地址) */
+ avatar: string;
+ /**
+ * 性别标识(0:未知/未设置,1:男,2:女,符合微信性别字段定义)
+ * 原始数据中为0,此处标注可能的枚举值以便后续扩展
+ */
+ gender: 0 | 1 | 2;
+ /** 地区信息(原始数据为空字符串,未填写) */
+ region: string;
+ /** 个性签名(原始数据为空字符串,未填写) */
+ signature: string;
+ /** 绑定的QQ号(0表示未绑定) */
+ bindQQ: string;
+ /** 绑定的邮箱(原始数据为空字符串,未绑定) */
+ bindEmail: string;
+ /** 绑定的手机号(原始数据为空字符串,未绑定) */
+ bindMobile: string;
+ /**
+ * 用户创建时间(注册时间)
+ * 格式:ISO 8601标准时间字符串(YYYY-MM-DDTHH:mm:ss.fffffff)
+ */
+ createTime: string;
+ /** 当前登录设备ID(关联用户使用的设备唯一标识) */
+ currentDeviceId: number;
+ /** 是否删除(逻辑删除标识,false:未删除,true:已删除) */
+ isDeleted: boolean;
+ /**
+ * 删除时间(逻辑删除时记录,未删除时为默认初始时间"0001-01-01T00:00:00")
+ * 格式:ISO 8601标准时间字符串
+ */
+ deleteTime: string;
+ /** 用户所属群组ID(用于群组分类管理) */
+ groupId: number;
+ /** 系统内对用户的备注信息(补充标识) */
+ memo: string;
+ /** 用户当前使用的微信版本号 */
+ wechatVersion: string;
+ /**
+ * 用户标签列表(用于分类、筛选,涵盖:业务类型、互动记录、身份标识等维度)
+ * 例如:"团队"(身份)、"未成交"(业务状态)、"抖音"(渠道)等
+ */
+ labels: string[];
+ /**
+ * 最后更新时间(用户信息最近修改时间)
+ * 格式:ISO 8601标准时间字符串
+ */
+ lastUpdateTime: string;
+ [key: string]: any;
+}
+
// 账户信息接口
export interface CkAccount {
id: number;
diff --git a/Cunkebao/src/store/module/ckchat.ts b/Cunkebao/src/store/module/ckchat.ts
index d25b5960..b7f9a26e 100644
--- a/Cunkebao/src/store/module/ckchat.ts
+++ b/Cunkebao/src/store/module/ckchat.ts
@@ -1,13 +1,44 @@
import { createPersistStore } from "@/store/createPersistStore";
-import { CkChatState, CkUserInfo, CkAccount, CkTenant } from "./ckchat.data";
+import {
+ CkChatState,
+ CkUserInfo,
+ CkAccount,
+ CkTenant,
+ CkChatCtrlUserData,
+} from "./ckchat.data";
import { ContractData, GroupData } from "@/pages/pc/ckbox/data";
export const useCkChatStore = createPersistStore(
set => ({
userInfo: null,
isLoggedIn: false,
chatSessions: [],
-
+ ctrlUserList: [],
+ // 控制终端用户列表
+ getCtrlUserList: () => {
+ const state = useCkChatStore.getState();
+ return state.ctrlUserList;
+ },
+ // 添加控制终端用户
+ addCtrlUser: (user: CkChatCtrlUserData) => {
+ set(state => ({
+ ctrlUserList: [...state.ctrlUserList, user],
+ }));
+ },
+ // 删除控制终端用户
+ deleteCtrlUser: (userId: number) => {
+ set(state => ({
+ ctrlUserList: state.ctrlUserList.filter(item => item.id !== userId),
+ }));
+ },
+ // 更新控制终端用户
+ updateCtrlUser: (user: CkChatCtrlUserData) => {
+ set(state => ({
+ ctrlUserList: state.ctrlUserList.map(item =>
+ item.id === user.id ? user : item,
+ ),
+ }));
+ },
// 获取聊天会话
getChatSessions: () => {
const state = useCkChatStore.getState();
@@ -128,3 +159,10 @@ export const updateChatSession = (session: ContractData | GroupData) =>
useCkChatStore.getState().updateChatSession(session);
export const deleteChatSession = (sessionId: string) =>
useCkChatStore.getState().deleteChatSession(sessionId);
+export const getCtrlUserList = () => useCkChatStore.getState().ctrlUserList;
+export const addCtrlUser = (user: CkChatCtrlUserData) =>
+ useCkChatStore.getState().addCtrlUser(user);
+export const deleteCtrlUser = (userId: number) =>
+ useCkChatStore.getState().deleteCtrlUser(userId);
+export const updateCtrlUser = (user: CkChatCtrlUserData) =>
+ useCkChatStore.getState().updateCtrlUser(user);
From 79ccd8580ee93972dfae4120bb1982d71aa46d20 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, 27 Aug 2025 14:30:20 +0800
Subject: [PATCH 43/78] =?UTF-8?q?refactor(=E6=B5=81=E9=87=8F=E6=B1=A0):=20?=
=?UTF-8?q?=E4=BC=98=E5=8C=96=E6=89=B9=E9=87=8F=E5=8A=A0=E5=85=A5=E5=88=86?=
=?UTF-8?q?=E7=BB=84=E5=BC=B9=E7=AA=97=E7=BB=84=E4=BB=B6=E5=B9=B6=E6=B8=85?=
=?UTF-8?q?=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
将Modal组件替换为Popup组件以改善移动端用户体验,移除调试日志和未使用的类型导入
---
.../mine/traffic-pool/list/BatchAddModal.tsx | 28 +++++++++++++------
.../mine/traffic-pool/list/dataAnyx.tsx | 11 +-------
2 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/BatchAddModal.tsx b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/BatchAddModal.tsx
index 5c7eae5c..86dda202 100644
--- a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/BatchAddModal.tsx
+++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/BatchAddModal.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { Modal, Selector } from "antd-mobile";
+import { Popup, Selector } from "antd-mobile";
import type { PackageOption } from "./data";
interface BatchAddModalProps {
@@ -21,14 +21,24 @@ const BatchAddModal: React.FC = ({
selectedCount,
onConfirm,
}) => (
-
+ //
+ //
选择目标分组
+ //
({ label: p.name, value: p.id }))}
+ // value={[batchTarget]}
+ // onChange={v => setBatchTarget(v[0])}
+ // />
+ //
+ //
+ // 将选中的{selectedCount}个用户加入所选分组
+ //
+ //
+ onClose()}
+ position="bottom"
+ bodyStyle={{ height: "80vh" }}
>
选择目标分组
@@ -41,7 +51,7 @@ const BatchAddModal: React.FC
= ({
将选中的{selectedCount}个用户加入所选分组
-
+
);
export default BatchAddModal;
diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx
index 390e439b..e0c7514f 100644
--- a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx
+++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx
@@ -4,14 +4,7 @@ import {
fetchPackageOptions,
fetchScenarioOptions,
} from "./api";
-import type {
- TrafficPoolUser,
- DeviceOption,
- PackageOption,
- ValueLevel,
- UserStatus,
- ScenarioOption,
-} from "./data";
+import type { TrafficPoolUser, PackageOption, ScenarioOption } from "./data";
import { Toast } from "antd-mobile";
export function useTrafficPoolListLogic() {
@@ -79,8 +72,6 @@ export function useTrafficPoolListLogic() {
// 获取筛选项
useEffect(() => {
fetchPackageOptions().then(res => {
- console.log("packageOptions", res);
-
setPackageOptions(res.list || []);
});
fetchScenarioOptions().then(res => {
From 29e6bf8582873804f9a71230af0211f549628d04 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, 27 Aug 2025 15:35:58 +0800
Subject: [PATCH 44/78] =?UTF-8?q?feat(=E7=BE=A4=E6=8E=A8=E4=BB=BB=E5=8A=A1?=
=?UTF-8?q?):=20=E6=B7=BB=E5=8A=A0=E6=9B=B4=E6=96=B0=E7=BE=A4=E6=8E=A8?=
=?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=8E=A5=E5=8F=A3=E5=B9=B6=E4=BF=AE=E6=94=B9?=
=?UTF-8?q?=E5=88=9B=E5=BB=BA=E6=8E=A5=E5=8F=A3=E8=B7=AF=E5=BE=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
修改创建群推任务的接口路径为/v1/workbench/create,并添加type参数
新增更新群推任务的接口/v1/workbench/update
---
.../pages/mobile/workspace/group-push/form/index.api.ts | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/Cunkebao/src/pages/mobile/workspace/group-push/form/index.api.ts b/Cunkebao/src/pages/mobile/workspace/group-push/form/index.api.ts
index e6fe977b..819926da 100644
--- a/Cunkebao/src/pages/mobile/workspace/group-push/form/index.api.ts
+++ b/Cunkebao/src/pages/mobile/workspace/group-push/form/index.api.ts
@@ -1,8 +1,12 @@
import request from "@/api/request";
-export function createGroupPushTask(taskData) {
- return request("/v1/workspace/group-push/tasks", taskData, "POST");
+
+export function createGroupPushTask(data) {
+ return request("/v1/workbench/create", { ...data, type: 3 }, "POST");
}
+export function updateGroupPushTask(data) {
+ return request("/v1/workbench/update", { ...data, type: 3 }, "POST");
+}
// 获取京东社交媒体列表
export const fetchSocialMediaList = async () => {
return request("/v1/workbench/getJdSocialMedia", {}, "GET");
From 0d5e34cfdd0166de4320a6874bbbd88dd83f4aea 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, 27 Aug 2025 17:04:39 +0800
Subject: [PATCH 45/78] =?UTF-8?q?refactor(=E7=BE=A4=E6=8E=A8=E8=A1=A8?=
=?UTF-8?q?=E5=8D=95):=20=E9=87=8D=E6=9E=84=E8=A1=A8=E5=8D=95=E6=95=B0?=
=?UTF-8?q?=E6=8D=AE=E7=BB=93=E6=9E=84=E5=B9=B6=E6=95=B4=E5=90=88=E4=BA=AC?=
=?UTF-8?q?=E4=B8=9C=E8=81=94=E7=9B=9F=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将布尔类型字段改为数字枚举类型以增强可读性
- 移除独立的京东联盟组件,将其功能整合到基础设置中
- 简化表单步骤,从4步减少到3步
- 优化表单字段命名使其更语义化
---
.../form/components/BasicSettings.tsx | 282 +++++++++++++-----
.../form/components/JingDongLink.tsx | 148 ---------
.../workspace/group-push/form/index.data.ts | 14 +-
.../workspace/group-push/form/index.tsx | 119 ++++----
4 files changed, 261 insertions(+), 302 deletions(-)
delete mode 100644 Cunkebao/src/pages/mobile/workspace/group-push/form/components/JingDongLink.tsx
diff --git a/Cunkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx b/Cunkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx
index e83332ad..ad951714 100644
--- a/Cunkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx
@@ -1,16 +1,33 @@
-import React, { useImperativeHandle, forwardRef } from "react";
-import { Input, Button, Card, Switch, Form, InputNumber } from "antd";
+import React, {
+ useImperativeHandle,
+ forwardRef,
+ useState,
+ useEffect,
+} from "react";
+import {
+ Input,
+ Button,
+ Card,
+ Switch,
+ Form,
+ InputNumber,
+ Select,
+ Radio,
+} from "antd";
+import { fetchSocialMediaList, fetchPromotionSiteList } from "../index.api";
interface BasicSettingsProps {
defaultValues?: {
name: string;
- pushTimeStart: string;
- pushTimeEnd: string;
- dailyPushCount: number;
- pushOrder: "earliest" | "latest";
- isLoopPush: boolean;
- isImmediatePush: boolean;
- isEnabled: boolean;
+ startTime: string; // 允许推送的开始时间
+ endTime: string; // 允许推送的结束时间
+ maxPerDay: number;
+ pushOrder: number; // 1: 按最早, 2: 按最新
+ isLoop: number; // 0: 否, 1: 是
+ pushType: number; // 0: 定时推送, 1: 立即推送
+ status: number; // 0: 否, 1: 是
+ socialMediaId?: string;
+ promotionSiteId?: string;
};
onNext: (values: any) => void;
onSave: (values: any) => void;
@@ -27,18 +44,61 @@ const BasicSettings = forwardRef(
{
defaultValues = {
name: "",
- pushTimeStart: "06:00",
- pushTimeEnd: "23:59",
- dailyPushCount: 20,
- pushOrder: "latest",
- isLoopPush: false,
- isImmediatePush: false,
- isEnabled: false,
+ startTime: "06:00", // 允许推送的开始时间
+ endTime: "23:59", // 允许推送的结束时间
+ maxPerDay: 20,
+ pushOrder: 1,
+ isLoop: 0, // 0: 否, 1: 是
+ pushType: 0, // 0: 定时推送, 1: 立即推送
+ status: 0, // 0: 否, 1: 是
+ socialMediaId: undefined,
+ promotionSiteId: undefined,
},
},
ref,
) => {
const [form] = Form.useForm();
+ const [, forceUpdate] = useState({});
+ const [socialMediaList, setSocialMediaList] = useState([]);
+ const [promotionSiteList, setPromotionSiteList] = useState([]);
+ const [loadingSocialMedia, setLoadingSocialMedia] = useState(false);
+ const [loadingPromotionSite, setLoadingPromotionSite] = useState(false);
+
+ // 确保组件初始化时能正确显示按钮状态
+ useEffect(() => {
+ forceUpdate({});
+ }, []);
+
+ // 组件挂载时获取社交媒体列表
+ useEffect(() => {
+ setLoadingSocialMedia(true);
+ fetchSocialMediaList()
+ .then(res => {
+ setSocialMediaList(res);
+ })
+ .finally(() => {
+ setLoadingSocialMedia(false);
+ });
+ }, []);
+
+ // 监听社交媒体选择变化
+ const handleSocialMediaChange = value => {
+ form.setFieldsValue({ socialMediaId: value });
+ // 清空推广站点选择
+ form.setFieldsValue({ promotionSiteId: undefined });
+ setPromotionSiteList([]);
+
+ if (value) {
+ setLoadingPromotionSite(true);
+ fetchPromotionSiteList(value)
+ .then(res => {
+ setPromotionSiteList(res);
+ })
+ .finally(() => {
+ setLoadingPromotionSite(false);
+ });
+ }
+ };
// 暴露方法给父组件
useImperativeHandle(ref, () => ({
@@ -55,7 +115,10 @@ const BasicSettings = forwardRef(
return form.getFieldsValue();
},
}));
-
+ const handlePushOrderChange = (value: number) => {
+ form.setFieldsValue({ pushOrder: value });
+ forceUpdate({}); // 强制组件重新渲染
+ };
return (
@@ -64,7 +127,10 @@ const BasicSettings = forwardRef(
layout="vertical"
initialValues={defaultValues}
onValuesChange={(changedValues, allValues) => {
- // 可以在这里处理表单值变化
+ // 当pushOrder值变化时,强制更新组件
+ if ("pushOrder" in changedValues) {
+ forceUpdate({});
+ }
}}
>
{/* 任务名称 */}
@@ -78,32 +144,56 @@ const BasicSettings = forwardRef(
>
-
- {/* 允许推送的时间段 */}
-
-
-
-
-
- 至
-
-
-
-
+ {/* 推送类型 */}
+
+
+ 定时推送
+ 立即推送
+
+
+ {/* 允许推送的时间段 - 只在定时推送时显示 */}
+
+ prevValues.pushType !== currentValues.pushType
+ }
+ >
+ {({ getFieldValue }) => {
+ // 只在pushType为0(定时推送)时显示时间段设置
+ return getFieldValue("pushType") === 0 ? (
+
+
+
+
+
+ 至
+
+
+
+
+
+ ) : null;
+ }}
{/* 每日推送 */}
(
>
+ {/* 京东联盟 */}
+
+
+
+
+
+
+
+
+
+
{/* 是否循环推送 */}
-
-
-
- {/* 是否立即推送 */}
- (checked ? 1 : 0)}
+ getValueProps={value => ({ checked: value === 1 })}
>
@@ -177,30 +285,40 @@ const BasicSettings = forwardRef(
{/* 是否启用 */}
(checked ? 1 : 0)}
+ getValueProps={value => ({ checked: value === 1 })}
>
- {/* 立即推送提示 */}
-
- {() => {
- const isImmediatePush = form.getFieldValue("isImmediatePush");
- return isImmediatePush ? (
-
- 如果启用,系统会把内容库里所有的内容按顺序推送到指定的社群
-
- ) : null;
+ {/* 推送类型提示 */}
+
+ prevValues.pushType !== currentValues.pushType
+ }
+ >
+ {({ getFieldValue }) => {
+ const pushType = getFieldValue("pushType");
+ if (pushType === 1) {
+ return (
+
+ 如果启用立即推送,系统会把内容库里所有的内容按顺序推送到指定的社群
+
+ );
+ }
+ return null;
}}
diff --git a/Cunkebao/src/pages/mobile/workspace/group-push/form/components/JingDongLink.tsx b/Cunkebao/src/pages/mobile/workspace/group-push/form/components/JingDongLink.tsx
deleted file mode 100644
index 969666ce..00000000
--- a/Cunkebao/src/pages/mobile/workspace/group-push/form/components/JingDongLink.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import React, {
- useState,
- useEffect,
- useImperativeHandle,
- forwardRef,
-} from "react";
-import { Form, Select, Card } from "antd";
-import { fetchSocialMediaList, fetchPromotionSiteList } from "../index.api";
-
-// 京东社交媒体接口
-interface JdSocialMedia {
- id: string;
- name: string;
- [key: string]: any;
-}
-
-// 京东推广站点接口
-interface JdPromotionSite {
- id: string;
- name: string;
- [key: string]: any;
-}
-
-interface JingDongLinkProps {
- defaultValues?: {
- socialMediaId?: string;
- promotionSiteId?: string;
- };
- onNext?: (values: any) => void;
- onSave?: (values: any) => void;
- loading?: boolean;
-}
-
-export interface JingDongLinkRef {
- validate: () => Promise;
- getValues: () => any;
-}
-
-const JingDongLink = forwardRef(
- (
- {
- defaultValues = {
- socialMediaId: undefined,
- promotionSiteId: undefined,
- },
- onNext,
- onSave,
- loading = false,
- },
- ref,
- ) => {
- const [form] = Form.useForm();
- const [socialMediaList, setSocialMediaList] = useState([]);
- const [promotionSiteList, setPromotionSiteList] = useState<
- JdPromotionSite[]
- >([]);
- const [loadingSocialMedia, setLoadingSocialMedia] = useState(false);
- const [loadingPromotionSite, setLoadingPromotionSite] = useState(false);
-
- // 暴露方法给父组件
- useImperativeHandle(ref, () => ({
- validate: async () => {
- try {
- await form.validateFields();
- return true;
- } catch (error) {
- console.log("JingDongLink 表单验证失败:", error);
- return false;
- }
- },
- getValues: () => {
- return form.getFieldsValue();
- },
- }));
-
- // 组件挂载时获取社交媒体列表
- useEffect(() => {
- fetchSocialMediaList().then(res => {
- setSocialMediaList(res);
- });
- }, []);
-
- // 监听社交媒体选择变化
- const handleSocialMediaChange = (value: number) => {
- form.setFieldsValue({ socialMediaId: value });
- // 清空推广站点选择
- form.setFieldsValue({ promotionSiteId: undefined });
- setPromotionSiteList([]);
-
- if (value) {
- fetchPromotionSiteList(value).then(res => {
- setPromotionSiteList(res);
- });
- }
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- },
-);
-
-JingDongLink.displayName = "JingDongLink";
-
-export default JingDongLink;
diff --git a/Cunkebao/src/pages/mobile/workspace/group-push/form/index.data.ts b/Cunkebao/src/pages/mobile/workspace/group-push/form/index.data.ts
index a158470a..8a5fdc68 100644
--- a/Cunkebao/src/pages/mobile/workspace/group-push/form/index.data.ts
+++ b/Cunkebao/src/pages/mobile/workspace/group-push/form/index.data.ts
@@ -21,13 +21,13 @@ export interface ContentLibrary {
export interface FormData {
name: string;
- pushTimeStart: string;
- pushTimeEnd: string;
- dailyPushCount: number;
- pushOrder: "earliest" | "latest";
- isLoopPush: boolean;
- isImmediatePush: boolean;
- isEnabled: boolean;
+ startTime: string; // 允许推送的开始时间
+ endTime: string; // 允许推送的结束时间
+ maxPerDay: number;
+ pushOrder: number; // 1: 按最早, 2: 按最新
+ isLoop: number; // 0: 否, 1: 是
+ pushType: number; // 0: 定时推送, 1: 立即推送
+ status: number; // 0: 否, 1: 是
contentGroups: string[];
wechatGroups: string[];
// 京东联盟相关字段
diff --git a/Cunkebao/src/pages/mobile/workspace/group-push/form/index.tsx b/Cunkebao/src/pages/mobile/workspace/group-push/form/index.tsx
index 24469531..30025dd1 100644
--- a/Cunkebao/src/pages/mobile/workspace/group-push/form/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/group-push/form/index.tsx
@@ -9,7 +9,6 @@ import GroupSelector, { GroupSelectorRef } from "./components/GroupSelector";
import ContentSelector, {
ContentSelectorRef,
} from "./components/ContentSelector";
-import JingDongLink, { JingDongLinkRef } from "./components/JingDongLink";
import type { FormData } from "./index.data";
import NavCommon from "@/components/NavCommon";
import { GroupSelectionItem } from "@/components/GroupSelection/data";
@@ -18,7 +17,6 @@ const steps = [
{ id: 1, title: "步骤 1", subtitle: "基础设置" },
{ id: 2, title: "步骤 2", subtitle: "选择社群" },
{ id: 3, title: "步骤 3", subtitle: "选择内容库" },
- { id: 4, title: "步骤 4", subtitle: "京东联盟" },
];
const NewGroupPush: React.FC = () => {
@@ -35,13 +33,13 @@ const NewGroupPush: React.FC = () => {
const [formData, setFormData] = useState({
name: "",
- pushTimeStart: "06:00",
- pushTimeEnd: "23:59",
- dailyPushCount: 20,
- pushOrder: "latest",
- isLoopPush: false,
- isImmediatePush: false,
- isEnabled: false,
+ startTime: "06:00", // 允许推送的开始时间
+ endTime: "23:59", // 允许推送的结束时间
+ maxPerDay: 20,
+ pushOrder: 2, // 2: 按最新
+ isLoop: 0, // 0: 否, 1: 是
+ pushType: 0, // 0: 定时推送, 1: 立即推送
+ status: 0, // 0: 否, 1: 是
wechatGroups: [],
contentGroups: [],
});
@@ -51,7 +49,6 @@ const NewGroupPush: React.FC = () => {
const basicSettingsRef = useRef(null);
const groupSelectorRef = useRef(null);
const contentSelectorRef = useRef(null);
- const jingDongLinkRef = useRef(null);
useEffect(() => {
if (!id) return;
@@ -83,56 +80,59 @@ const NewGroupPush: React.FC = () => {
};
const handleSave = async () => {
- if (!formData.name.trim()) {
- window.alert("请输入任务名称");
- return;
- }
- if (formData.wechatGroups.length === 0) {
- window.alert("请选择至少一个社群");
- return;
- }
- if (formData.contentGroups.length === 0) {
- window.alert("请选择至少一个内容库");
- return;
- }
-
- // 获取京东联盟数据
- const jingDongLinkValues = jingDongLinkRef.current?.getValues();
-
- setLoading(true);
try {
+ // 调用 ContentSelector 的表单校验
+ const isValid = (await contentSelectorRef.current?.validate()) || false;
+ if (!isValid) return;
+
+ setLoading(true);
+
+ // 获取基础设置中的京东联盟数据
+ const basicSettingsValues = basicSettingsRef.current?.getValues() || {};
+
+ // 构建 API 请求数据
const apiData = {
name: formData.name,
- timeRange: {
- start: formData.pushTimeStart,
- end: formData.pushTimeEnd,
- },
- maxPushPerDay: formData.dailyPushCount,
+ startTime: formData.startTime, // 允许推送的开始时间
+ endTime: formData.endTime, // 允许推送的结束时间
+ maxPushPerDay: formData.maxPerDay,
pushOrder: formData.pushOrder,
- isLoopPush: formData.isLoopPush,
- isImmediatePush: formData.isImmediatePush,
- isEnabled: formData.isEnabled,
+ isLoop: formData.isLoop, // 0: 否, 1: 是
+ pushType: formData.pushType, // 0: 定时推送, 1: 立即推送
+ status: formData.status, // 0: 否, 1: 是
wechatGroups: formData.wechatGroups,
contentGroups: formData.contentGroups,
- // 京东联盟数据
- socialMediaId: jingDongLinkValues?.socialMediaId,
- promotionSiteId: jingDongLinkValues?.promotionSiteId,
- pushMode: formData.isImmediatePush
- ? ("immediate" as const)
- : ("scheduled" as const),
+ // 京东联盟数据从基础设置中获取
+ socialMediaId: basicSettingsValues.socialMediaId,
+ promotionSiteId: basicSettingsValues.promotionSiteId,
+ pushMode:
+ formData.pushType === 1
+ ? ("immediate" as const)
+ : ("scheduled" as const),
messageType: "text" as const,
messageContent: "",
targetTags: [],
pushInterval: 60,
};
- const response = await createGroupPushTask(apiData);
- if (response.code === 200) {
- window.alert("保存成功");
- navigate("/workspace/group-push");
+
+ // 打印API请求数据,用于调试
+ console.log("发送到API的数据:", apiData);
+
+ // 调用创建或更新 API
+ if (id) {
+ // 更新逻辑将在这里实现
+ window.alert("更新成功");
} else {
- window.alert("保存失败,请稍后重试");
+ const response = await createGroupPushTask(apiData);
+ if (response.code === 200) {
+ window.alert("创建成功");
+ navigate("/workspace/group-push");
+ } else {
+ window.alert("保存失败,请稍后重试");
+ }
}
} catch (error) {
+ console.error("保存失败:", error);
window.alert("保存失败,请稍后重试");
} finally {
setLoading(false);
@@ -146,7 +146,7 @@ const NewGroupPush: React.FC = () => {
};
const handleNext = async () => {
- if (currentStep < 4) {
+ if (currentStep < 3) {
try {
let isValid = false;
@@ -171,14 +171,6 @@ const NewGroupPush: React.FC = () => {
}
break;
- case 3:
- // 调用 ContentSelector 的表单校验
- isValid = (await contentSelectorRef.current?.validate()) || false;
- if (isValid) {
- setCurrentStep(4);
- }
- break;
-
default:
setCurrentStep(currentStep + 1);
}
@@ -196,7 +188,7 @@ const NewGroupPush: React.FC = () => {
上一步
)}
- {currentStep === 4 ? (
+ {currentStep === 3 ? (
@@ -224,13 +216,13 @@ const NewGroupPush: React.FC = () => {
ref={basicSettingsRef}
defaultValues={{
name: formData.name,
- pushTimeStart: formData.pushTimeStart,
- pushTimeEnd: formData.pushTimeEnd,
- dailyPushCount: formData.dailyPushCount,
+ startTime: formData.startTime,
+ endTime: formData.endTime,
+ maxPerDay: formData.maxPerDay,
pushOrder: formData.pushOrder,
- isLoopPush: formData.isLoopPush,
- isImmediatePush: formData.isImmediatePush,
- isEnabled: formData.isEnabled,
+ isLoop: formData.isLoop,
+ status: formData.status,
+ pushType: formData.pushType,
}}
onNext={handleBasicSettingsChange}
onSave={handleSave}
@@ -253,9 +245,6 @@ const NewGroupPush: React.FC = () => {
onNext={handleLibrariesChange}
/>
)}
- {currentStep === 4 && (
-
- )}
From 35c5cf60d4fe1c9e047a9779afcbaa0579c7a30d 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, 27 Aug 2025 17:26:00 +0800
Subject: [PATCH 46/78] =?UTF-8?q?refactor(=E7=BE=A4=E6=8E=A8=E4=BB=BB?=
=?UTF-8?q?=E5=8A=A1):=20=E7=BB=9F=E4=B8=80API=E6=8E=A5=E5=8F=A3=E5=8F=82?=
=?UTF-8?q?=E6=95=B0=E5=92=8C=E5=93=8D=E5=BA=94=E5=A4=84=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
重构群推任务相关API接口,将参数改为对象形式并统一响应处理
更新任务状态切换接口,添加type参数区分任务类型
修改字段名maxPushPerDay为maxPerDay保持一致性
使用Toast组件替代window.alert进行提示
---
.../mobile/workspace/auto-like/list/api.ts | 4 ++--
.../workspace/group-push/detail/groupPush.ts | 5 ++--
.../workspace/group-push/detail/index.tsx | 8 +++----
.../workspace/group-push/form/index.tsx | 24 ++++++++++---------
.../workspace/group-push/list/index.api.ts | 17 +++++--------
.../workspace/group-push/list/index.tsx | 2 +-
6 files changed, 28 insertions(+), 32 deletions(-)
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 7f828d56..615cb7ff 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-like/list/api.ts
+++ b/Cunkebao/src/pages/mobile/workspace/auto-like/list/api.ts
@@ -35,8 +35,8 @@ export function deleteAutoLikeTask(id: string): Promise {
}
// 切换任务状态
-export function toggleAutoLikeTask(id: string, status: string): Promise {
- return request("/v1/workbench/update-status", { id, status }, "POST");
+export function toggleAutoLikeTask(data): Promise {
+ return request("/v1/workbench/update-status", { ...data, type: 1 }, "POST");
}
// 复制自动点赞任务
diff --git a/Cunkebao/src/pages/mobile/workspace/group-push/detail/groupPush.ts b/Cunkebao/src/pages/mobile/workspace/group-push/detail/groupPush.ts
index 0295e2f9..b5f685c9 100644
--- a/Cunkebao/src/pages/mobile/workspace/group-push/detail/groupPush.ts
+++ b/Cunkebao/src/pages/mobile/workspace/group-push/detail/groupPush.ts
@@ -12,8 +12,9 @@ export interface GroupPushTask {
createTime: string;
creator: string;
pushInterval: number;
- maxPushPerDay: number;
- timeRange: { start: string; end: string };
+ maxPerDay: number;
+ startTime: string; // 允许推送的开始时间
+ endTime: string; // 允许推送的结束时间
messageType: "text" | "image" | "video" | "link";
messageContent: string;
targetTags: string[];
diff --git a/Cunkebao/src/pages/mobile/workspace/group-push/detail/index.tsx b/Cunkebao/src/pages/mobile/workspace/group-push/detail/index.tsx
index c38ba67e..188321fe 100644
--- a/Cunkebao/src/pages/mobile/workspace/group-push/detail/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/group-push/detail/index.tsx
@@ -179,7 +179,7 @@ const Detail: React.FC = () => {
基本设置
推送间隔:{task.pushInterval} 秒
-
每日最大推送数:{task.maxPushPerDay} 条
+
每日最大推送数:{task.maxPerDay} 条
执行时间段:{task.timeRange.start} - {task.timeRange.end}
@@ -221,12 +221,10 @@ const Detail: React.FC = () => {
执行进度
- 今日已推送:{task.pushCount} / {task.maxPushPerDay}
+ 今日已推送:{task.pushCount} / {task.maxPerDay}
{task.targetTags.length > 0 && (
diff --git a/Cunkebao/src/pages/mobile/workspace/group-push/form/index.tsx b/Cunkebao/src/pages/mobile/workspace/group-push/form/index.tsx
index 30025dd1..0a07fa08 100644
--- a/Cunkebao/src/pages/mobile/workspace/group-push/form/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/group-push/form/index.tsx
@@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Button } from "antd";
+import { Toast } from "antd-mobile";
import { createGroupPushTask } from "./index.api";
import Layout from "@/components/Layout/Layout";
import StepIndicator from "@/components/StepIndicator";
@@ -95,7 +96,7 @@ const NewGroupPush: React.FC = () => {
name: formData.name,
startTime: formData.startTime, // 允许推送的开始时间
endTime: formData.endTime, // 允许推送的结束时间
- maxPushPerDay: formData.maxPerDay,
+ maxPerDay: formData.maxPerDay,
pushOrder: formData.pushOrder,
isLoop: formData.isLoop, // 0: 否, 1: 是
pushType: formData.pushType, // 0: 定时推送, 1: 立即推送
@@ -121,19 +122,20 @@ const NewGroupPush: React.FC = () => {
// 调用创建或更新 API
if (id) {
// 更新逻辑将在这里实现
- window.alert("更新成功");
+ Toast.show({ content: "更新成功", position: "top" });
+ navigate("/workspace/group-push");
} else {
- const response = await createGroupPushTask(apiData);
- if (response.code === 200) {
- window.alert("创建成功");
- navigate("/workspace/group-push");
- } else {
- window.alert("保存失败,请稍后重试");
- }
+ createGroupPushTask(apiData)
+ .then(() => {
+ Toast.show({ content: "创建成功", position: "top" });
+ navigate("/workspace/group-push");
+ })
+ .catch(() => {
+ Toast.show({ content: "创建失败,请稍后重试", position: "top" });
+ });
}
} catch (error) {
- console.error("保存失败:", error);
- window.alert("保存失败,请稍后重试");
+ Toast.show({ content: "保存失败,请稍后重试", position: "top" });
} finally {
setLoading(false);
}
diff --git a/Cunkebao/src/pages/mobile/workspace/group-push/list/index.api.ts b/Cunkebao/src/pages/mobile/workspace/group-push/list/index.api.ts
index 2a6c1153..c11a7e3b 100644
--- a/Cunkebao/src/pages/mobile/workspace/group-push/list/index.api.ts
+++ b/Cunkebao/src/pages/mobile/workspace/group-push/list/index.api.ts
@@ -1,4 +1,5 @@
import request from "@/api/request";
+import { GroupPushTask } from "../detail/groupPush";
interface ApiResponse
{
code: number;
@@ -11,22 +12,16 @@ export async function fetchGroupPushTasks() {
}
export async function deleteGroupPushTask(id: string): Promise {
- return request(`/v1/workspace/group-push/tasks/${id}`, {}, "DELETE");
+ return request("/v1/workbench/delete", { id }, "DELETE");
}
-export async function toggleGroupPushTask(
- id: string,
- status: string,
-): Promise {
- return request(
- `/v1/workspace/group-push/tasks/${id}/toggle`,
- { status },
- "POST",
- );
+// 切换任务状态
+export function toggleGroupPushTask(data): Promise {
+ return request("/v1/workbench/update-status", { ...data, type: 3 }, "POST");
}
export async function copyGroupPushTask(id: string): Promise {
- return request(`/v1/workspace/group-push/tasks/${id}/copy`, {}, "POST");
+ return request("/v1/workbench/copy", { id }, "POST");
}
export async function createGroupPushTask(
diff --git a/Cunkebao/src/pages/mobile/workspace/group-push/list/index.tsx b/Cunkebao/src/pages/mobile/workspace/group-push/list/index.tsx
index 8aa3fca3..d4e9f362 100644
--- a/Cunkebao/src/pages/mobile/workspace/group-push/list/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/group-push/list/index.tsx
@@ -133,7 +133,7 @@ const GroupPush: React.FC = () => {
const task = tasks.find(t => t.id === taskId);
if (!task) return;
const newStatus = task.status === 1 ? 2 : 1;
- await toggleGroupPushTask(taskId, String(newStatus));
+ await toggleGroupPushTask({ id: taskId, status: newStatus });
fetchTasks();
};
From 3bf91cedc97fabf337aa7426807a36a8bc1ef376 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, 27 Aug 2025 17:48:15 +0800
Subject: [PATCH 47/78] =?UTF-8?q?refactor(ui):=20=E7=A7=BB=E9=99=A4?=
=?UTF-8?q?=E6=89=8B=E5=8A=A8=E6=B7=BB=E5=8A=A0=E9=80=89=E9=A1=B9=E5=B9=B6?=
=?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8D=E6=8E=A7=E5=88=B6=E7=BB=88=E7=AB=AF?=
=?UTF-8?q?=E5=88=97=E8=A1=A8=E5=8F=98=E9=87=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 删除两个页面中的"手动添加"标签页选项
- 将controlTerminalList重命名为kfUserList以提高可读性
- 更新构建文件引用路径
---
Cunkebao/dist/.vite/manifest.json | 18 +++++++++---------
Cunkebao/dist/index.html | 8 ++++----
Cunkebao/src/pages/guide/index.tsx | 1 -
.../src/pages/mobile/mine/devices/index.tsx | 1 -
Cunkebao/src/pages/pc/ckbox/main.ts | 8 ++++++--
5 files changed, 19 insertions(+), 17 deletions(-)
diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json
index 42480c71..f5df3f32 100644
--- a/Cunkebao/dist/.vite/manifest.json
+++ b/Cunkebao/dist/.vite/manifest.json
@@ -1,9 +1,9 @@
{
- "_charts-DDGb1us0.js": {
- "file": "assets/charts-DDGb1us0.js",
+ "_charts-BY5IGS3y.js": {
+ "file": "assets/charts-BY5IGS3y.js",
"name": "charts",
"imports": [
- "_ui-rFvxQTWo.js",
+ "_ui-g-9w80lh.js",
"_vendor-2vc8h_ct.js"
]
},
@@ -11,8 +11,8 @@
"file": "assets/ui-D0C0OGrH.css",
"src": "_ui-D0C0OGrH.css"
},
- "_ui-rFvxQTWo.js": {
- "file": "assets/ui-rFvxQTWo.js",
+ "_ui-g-9w80lh.js": {
+ "file": "assets/ui-g-9w80lh.js",
"name": "ui",
"imports": [
"_vendor-2vc8h_ct.js"
@@ -33,18 +33,18 @@
"name": "vendor"
},
"index.html": {
- "file": "assets/index-C3xy08Hg.js",
+ "file": "assets/index-D1PYRHiS.js",
"name": "index",
"src": "index.html",
"isEntry": true,
"imports": [
"_vendor-2vc8h_ct.js",
- "_ui-rFvxQTWo.js",
"_utils-6WF66_dS.js",
- "_charts-DDGb1us0.js"
+ "_ui-g-9w80lh.js",
+ "_charts-BY5IGS3y.js"
],
"css": [
- "assets/index-D4Jt-UDy.css"
+ "assets/index-ejYsXKTB.css"
]
}
}
\ No newline at end of file
diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html
index f4b62be3..abe21cc7 100644
--- a/Cunkebao/dist/index.html
+++ b/Cunkebao/dist/index.html
@@ -11,13 +11,13 @@
-
+
-
-
+
+
-
+
diff --git a/Cunkebao/src/pages/guide/index.tsx b/Cunkebao/src/pages/guide/index.tsx
index da42ed05..1610bdbc 100644
--- a/Cunkebao/src/pages/guide/index.tsx
+++ b/Cunkebao/src/pages/guide/index.tsx
@@ -282,7 +282,6 @@ const Guide: React.FC = () => {
style={{ marginBottom: 16 }}
>
-
{addTab === "scan" && (
diff --git a/Cunkebao/src/pages/mobile/mine/devices/index.tsx b/Cunkebao/src/pages/mobile/mine/devices/index.tsx
index c4199a43..62fe17f2 100644
--- a/Cunkebao/src/pages/mobile/mine/devices/index.tsx
+++ b/Cunkebao/src/pages/mobile/mine/devices/index.tsx
@@ -379,7 +379,6 @@ const Devices: React.FC = () => {
style={{ marginBottom: 16 }}
>
-
{addTab === "scan" && (
diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts
index 55bb7176..47f14209 100644
--- a/Cunkebao/src/pages/pc/ckbox/main.ts
+++ b/Cunkebao/src/pages/pc/ckbox/main.ts
@@ -38,19 +38,23 @@ export const chatInitAPIdata = async () => {
getUniqueWechatAccountIds(contractList);
//获取控制终端列表
- const controlTerminalList: CkChatCtrlUserData[] =
+ const kfUserList: CkChatCtrlUserData[] =
await getControlTerminalListByWechatAccountIds(uniqueWechatAccountIds);
//添加控制终端用户
- controlTerminalList.forEach(item => {
+ kfUserList.forEach(item => {
addCtrlUser(item);
});
//获取群列表
const chatRoomList = await getAllChatRoomList();
+
+ //存入store
+
return {
contractList,
chatRoomList,
+ kfUserList,
};
} catch (error) {
console.error("获取联系人列表失败:", error);
From 3c1337b2130eb2d0784dbeb728f1cb0bb2d7d82d 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, 27 Aug 2025 17:51:24 +0800
Subject: [PATCH 48/78] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?=
=?UTF-8?q?=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
---
ckApp/manifest.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ckApp/manifest.json b/ckApp/manifest.json
index 297a2ced..b8ab3182 100644
--- a/ckApp/manifest.json
+++ b/ckApp/manifest.json
@@ -1,5 +1,5 @@
{
- "name" : "ckApp",
+ "name" : "存客宝",
"appid" : "__UNI__2B34F1A",
"description" : "",
"versionName" : "1.0.0",
From 640daf243104459b1ebfcdc7ca357972987ae055 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, 28 Aug 2025 15:18:18 +0800
Subject: [PATCH 49/78] =?UTF-8?q?refactor(ckbox):=20=E9=87=8D=E6=9E=84?=
=?UTF-8?q?=E5=AE=A2=E6=9C=8D=E5=88=97=E8=A1=A8=E5=92=8C=E8=81=94=E7=B3=BB?=
=?UTF-8?q?=E4=BA=BA=E7=AE=A1=E7=90=86=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将ctrlUserList重命名为kfUserList并优化相关接口
- 新增异步更新客服列表、联系人列表和会话列表的方法
- 重构联系人分组逻辑,支持按标签分组
- 优化会话列表排序规则,按最后更新时间降序排列
- 移除无用代码并简化组件逻辑
---
.../workspace/group-push/form/index.data.ts | 2 +-
Cunkebao/src/pages/pc/ckbox/api.ts | 6 +-
.../SidebarMenu/MessageList/index.tsx | 22 +--
.../pc/ckbox/components/SidebarMenu/index.tsx | 2 +-
.../components/VerticalUserList/index.tsx | 25 +--
Cunkebao/src/pages/pc/ckbox/index.tsx | 64 +------
Cunkebao/src/pages/pc/ckbox/main.ts | 176 +++++++++++++++---
Cunkebao/src/store/module/ckchat.data.ts | 56 ++----
Cunkebao/src/store/module/ckchat.ts | 52 ++++--
Cunkebao/src/utils/common.ts | 3 +
10 files changed, 226 insertions(+), 182 deletions(-)
diff --git a/Cunkebao/src/pages/mobile/workspace/group-push/form/index.data.ts b/Cunkebao/src/pages/mobile/workspace/group-push/form/index.data.ts
index 8a5fdc68..02188e84 100644
--- a/Cunkebao/src/pages/mobile/workspace/group-push/form/index.data.ts
+++ b/Cunkebao/src/pages/mobile/workspace/group-push/form/index.data.ts
@@ -23,7 +23,7 @@ export interface FormData {
name: string;
startTime: string; // 允许推送的开始时间
endTime: string; // 允许推送的结束时间
- maxPerDay: number;
+ dailyPushCount: number;
pushOrder: number; // 1: 按最早, 2: 按最新
isLoop: number; // 0: 否, 1: 是
pushType: number; // 0: 定时推送, 1: 立即推送
diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts
index c642da36..3ea80e5b 100644
--- a/Cunkebao/src/pages/pc/ckbox/api.ts
+++ b/Cunkebao/src/pages/pc/ckbox/api.ts
@@ -14,6 +14,10 @@ import {
//读取聊天信息
//kf.quwanzhi.com:9991/api/WechatFriend/clearUnreadCount
+export function WechatGroup(params) {
+ return request("/api/WechatGroup/list", params, "GET");
+}
+
//获取聊天记录-1 清除未读
export function clearUnreadCount(params) {
return request("/api/WechatFriend/clearUnreadCount", params, "PUT");
@@ -30,7 +34,7 @@ export function getMessages(params: {
return request("/api/FriendMessage/SearchMessage", params, "GET");
}
//获取群列表
-export function getChatRoomList(params: { prevId: number; count: number }) {
+export function getGroupList(params: { prevId: number; count: number }) {
return request(
"/api/wechatChatroom/listExcludeMembersByPage?",
params,
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 f00994de..3a4058ae 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx
@@ -4,7 +4,7 @@ import { UserOutlined, TeamOutlined } from "@ant-design/icons";
import dayjs from "dayjs";
import { ContractData, GroupData } from "@/pages/pc/ckbox/data";
import styles from "./MessageList.module.scss";
-
+import { formatWechatTime } from "@/utils/common";
interface MessageListProps {
chatSessions: ContractData[];
currentChat: ContractData;
@@ -16,22 +16,6 @@ const MessageList: React.FC = ({
currentChat,
onChatSelect,
}) => {
- const formatTime = (timestamp: string) => {
- const now = dayjs();
- const messageTime = dayjs(timestamp);
- const diffDays = now.diff(messageTime, "day");
-
- if (diffDays === 0) {
- return messageTime.format("HH:mm");
- } else if (diffDays === 1) {
- return "昨天";
- } else if (diffDays < 7) {
- return messageTime.format("ddd");
- } else {
- return messageTime.format("MM-DD");
- }
- };
-
return (
= ({
@@ -61,7 +45,7 @@ const MessageList: React.FC = ({
{session.nickname}
- {formatTime(session?.lastTime || "")}
+ {formatWechatTime(session?.lastUpdateTime)}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
index 36cbf763..41bd4da7 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
@@ -31,7 +31,7 @@ const SidebarMenu: React.FC
= ({
const chatSessions = getChatSessions();
const [searchText, setSearchText] = useState("");
- const [activeTab, setActiveTab] = useState("contracts");
+ const [activeTab, setActiveTab] = useState("chats");
const handleSearch = (value: string) => {
setSearchText(value);
diff --git a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx
index bc10cfa1..31112010 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx
@@ -1,29 +1,17 @@
-import React, { useEffect, useState } from "react";
+import React, { useState } from "react";
import { Avatar, Badge, Tooltip } from "antd";
import styles from "./VerticalUserList.module.scss";
-import { CkChatCtrlUserData } from "@/store/module/ckchat.data";
-interface UserItem {
- id: string;
- name: string;
- avatar: string;
- messageCount?: number;
- isOnline?: boolean;
-}
-
+import { useCkChatStore } from "@/store/module/ckchat";
interface VerticalUserListProps {
+ activeKfUserId: number;
onUserSelect: (userId: string) => void;
}
-import { getCtrlUserList, useCkChatStore } from "@/store/module/ckchat";
const VerticalUserList: React.FC = ({
+ activeKfUserId,
onUserSelect,
}) => {
- // 直接从store获取ctrlUserList,这样当store中的数据更新时,组件会自动重新渲染
- const ctrlUserList = useCkChatStore(state => state.ctrlUserList);
- const [activeUserId, setActiveUserId] = useState();
+ const [activeUserId, setActiveUserId] = useState(activeKfUserId);
- useEffect(() => {
- console.log("控制终端用户列表更新:", ctrlUserList);
- }, [ctrlUserList]);
// 格式化消息数量显示
const formatMessageCount = (count: number) => {
if (count > 99) return "99+";
@@ -34,6 +22,7 @@ const VerticalUserList: React.FC = ({
setActiveUserId(userId);
onUserSelect(userId.toString());
};
+ const kfUserList = useCkChatStore(state => state.kfUserList);
return (
@@ -41,7 +30,7 @@ const VerticalUserList: React.FC
= ({
全部好友
- {ctrlUserList.map(user => (
+ {kfUserList.map(user => (
{
const [messageApi, contextHolder] = message.useMessage();
const [contracts, setContacts] = useState
([]);
const [currentChat, setCurrentChat] = useState(null);
- const [activeVerticalUserId, setActiveVerticalUserId] = useState("all");
+ const [activeVerticalUserId, setActiveVerticalUserId] = useState(0);
const [loading, setLoading] = useState(false);
const [showProfile, setShowProfile] = useState(true);
@@ -75,8 +24,8 @@ const CkboxPage: React.FC = () => {
// 方法一:使用 Promise 链式调用处理异步函数
setLoading(true);
chatInitAPIdata()
- .then((response: { contractList: any[]; chatRoomList: any[] }) => {
- const { contractList, chatRoomList } = response;
+ .then(response => {
+ const { contractList, chatRoomList, kfUserList } = response;
//找出已经在聊天的
const isChatList = contractList.filter(
v => (v?.config && v.config?.chat) || false,
@@ -133,10 +82,10 @@ const CkboxPage: React.FC = () => {
{/* 垂直侧边栏 */}
+
@@ -148,7 +97,6 @@ const CkboxPage: React.FC = () => {
currentChat={currentChat}
onContactClick={handleContactClick}
onChatSelect={setCurrentChat}
- loading={loading}
/>
diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts
index 55bb7176..a0e7ab7c 100644
--- a/Cunkebao/src/pages/pc/ckbox/main.ts
+++ b/Cunkebao/src/pages/pc/ckbox/main.ts
@@ -1,62 +1,182 @@
-import { addCtrlUser, useCkChatStore } from "@/store/module/ckchat";
+import {
+ useCkChatStore,
+ asyncKfUserList,
+ asyncContractList,
+ asyncChatSessions,
+} from "@/store/module/ckchat";
import { useWebSocketStore } from "@/store/module/websocket";
import {
loginWithToken,
getControlTerminalList,
getContactList,
- getChatRoomList,
+ getGroupList,
+ WechatGroup,
} from "./api";
const { sendCommand } = useWebSocketStore.getState();
import { useUserStore } from "@/store/module/user";
-import { CkChatCtrlUserData } from "@/store/module/ckchat.data";
+import { KfUserListData } from "@/store/module/ckchat.data";
const { login2 } = useUserStore.getState();
-const { connect } = useWebSocketStore.getState();
-const { setUserInfo, getAccountId } = useCkChatStore.getState();
//获取触客宝基础信息
export const chatInitAPIdata = async () => {
try {
- // //发起链接
- // if (Token && accountId) {
- // connect({
- // url: "wss://kf.quwanzhi.com:9993", // 显式指定WebSocket URL,确保使用正确的服务器地址
- // accessToken: String(Token),
- // accountId: accountId,
- // client: "kefu-client",
- // cmdType: "CmdSignIn",
- // seq: +new Date(),
- // });
- // console.log("WebSocket连接已初始化");
- // } else {
- // console.error("WebSocket连接初始化失败:缺少Token或accountId");
- // }
//获取联系人列表
const contractList = await getAllContactList();
+ //构建联系人列表标签
+ const newContractList = await createContractList(contractList);
+
+ //获取联系人列表
+ asyncContractList(contractList);
+
// 提取不重复的wechatAccountId组
const uniqueWechatAccountIds: number[] =
getUniqueWechatAccountIds(contractList);
//获取控制终端列表
- const controlTerminalList: CkChatCtrlUserData[] =
+ const kfUserList: KfUserListData[] =
await getControlTerminalListByWechatAccountIds(uniqueWechatAccountIds);
- //添加控制终端用户
- controlTerminalList.forEach(item => {
- addCtrlUser(item);
- });
+ //获取用户列表
+ asyncKfUserList(kfUserList);
//获取群列表
- const chatRoomList = await getAllChatRoomList();
+ const groupList = await getAllGroupList();
+
+ //获取消息会话列表并按lastUpdateTime排序
+ const filterUserSessions = contractList?.filter(
+ v => v?.config && v.config?.chat,
+ );
+ const filterGroupSessions = groupList?.filter(
+ v => v?.config && v.config?.chat,
+ );
+ //排序功能
+ const sortedSessions = [...filterUserSessions, ...filterGroupSessions].sort(
+ (a, b) => {
+ // 如果lastUpdateTime不存在,则将其排在最后
+ if (!a.lastUpdateTime) return 1;
+ if (!b.lastUpdateTime) return -1;
+
+ // 首先按时间降序排列(最新的在前面)
+ const timeCompare =
+ new Date(b.lastUpdateTime).getTime() -
+ new Date(a.lastUpdateTime).getTime();
+
+ // 如果时间相同,则按未读消息数量降序排列
+ if (timeCompare === 0) {
+ // 如果unreadCount不存在,则将其排在后面
+ const aUnread = a.unreadCount || 0;
+ const bUnread = b.unreadCount || 0;
+ return bUnread - aUnread; // 未读消息多的排在前面
+ }
+
+ return timeCompare;
+ },
+ );
+ //会话数据同步
+ asyncChatSessions(sortedSessions);
+
return {
contractList,
- chatRoomList,
+ groupList,
+ kfUserList,
+ newContractList,
};
} catch (error) {
console.error("获取联系人列表失败:", error);
return [];
}
};
+
+//构建联系人列表标签
+export const createContractList = async (contractList: any[]) => {
+ const LablesRes = await Promise.all(
+ [1, 2].map(item =>
+ WechatGroup({
+ groupType: item,
+ }),
+ ),
+ );
+ const [friend, group] = LablesRes;
+
+ const countLables = [...friend, ...group];
+
+ // 根据countLables中的groupName整理contractList数据
+ // 返回按标签分组的联系人数组,包括未分组标签(在数组最后)
+ return organizeContactsByLabels(contractList, countLables);
+};
+
+/**
+ * 根据标签组织联系人
+ * @param contractList 联系人列表
+ * @param countLables 标签列表
+ * @returns 按标签分组的联系人
+ */
+export const organizeContactsByLabels = (
+ contractList: any[],
+ countLables: any[],
+) => {
+ // 创建结果对象,用于存储按标签分组的联系人
+ const result: { [key: string]: any[] } = {};
+
+ // 初始化结果对象,为每个标签创建一个空数组
+ countLables.forEach(label => {
+ if (label && label.groupName) {
+ result[label.groupName] = [];
+ }
+ });
+
+ // 创建未分组标签,用于存放没有匹配到任何标签的联系人
+ const ungroupedLabel = "未分组";
+ result[ungroupedLabel] = [];
+
+ // 遍历联系人列表
+ contractList.forEach(contact => {
+ // 确保联系人有labels字段且是数组
+ if (contact && Array.isArray(contact.labels)) {
+ // 标记联系人是否已被分配到某个组
+ let isAssigned = false;
+
+ // 遍历标签列表
+ countLables.forEach(label => {
+ if (label && label.groupName) {
+ // 检查联系人的labels是否包含当前标签的groupName
+ if (contact.labels.includes(label.groupName)) {
+ // 将联系人添加到对应标签的数组中
+ result[label.groupName].push(contact);
+ isAssigned = true;
+ }
+ }
+ });
+
+ // 如果联系人没有被分配到任何组,则添加到未分组
+ if (!isAssigned) {
+ result[ungroupedLabel].push(contact);
+ }
+ } else {
+ // 如果联系人没有labels字段或不是数组,也添加到未分组
+ result[ungroupedLabel].push(contact);
+ }
+ });
+
+ // 将结果转换为数组格式,确保未分组在最后
+ const resultArray = Object.entries(result).map(([groupName, contacts]) => ({
+ groupName,
+ contacts,
+ }));
+
+ // 将未分组移到数组末尾
+ const ungroupedIndex = resultArray.findIndex(
+ item => item.groupName === ungroupedLabel,
+ );
+ if (ungroupedIndex !== -1) {
+ const ungrouped = resultArray.splice(ungroupedIndex, 1)[0];
+ resultArray.push(ungrouped);
+ }
+
+ return resultArray;
+};
+
//获取控制终端列表
export const getControlTerminalListByWechatAccountIds = (
WechatAccountIds: number[],
@@ -126,7 +246,7 @@ export const getUniqueWechatAccountIds = contacts => {
return Array.from(uniqueAccountIdsSet);
};
// 递归获取所有群列表
-export const getAllChatRoomList = async () => {
+export const getAllGroupList = async () => {
try {
let allContacts = [];
let prevId = 0;
@@ -134,7 +254,7 @@ export const getAllChatRoomList = async () => {
let hasMore = true;
while (hasMore) {
- const contractList = await getChatRoomList({
+ const contractList = await getGroupList({
prevId,
count,
});
diff --git a/Cunkebao/src/store/module/ckchat.data.ts b/Cunkebao/src/store/module/ckchat.data.ts
index 8d026158..28c1c708 100644
--- a/Cunkebao/src/store/module/ckchat.data.ts
+++ b/Cunkebao/src/store/module/ckchat.data.ts
@@ -1,61 +1,27 @@
+import { ContractData } from "../../pages/pc/ckbox/data";
+
//终端用户数据接口
-export interface CkChatCtrlUserData {
- /** 用户唯一标识ID */
+export interface KfUserListData {
id: number;
- /** 租户ID(多租户系统中用于区分租户) */
tenantId: number;
- /** 微信ID(用户微信账号唯一标识) */
wechatId: string;
- /** 用户昵称 */
nickname: string;
- /** 用户别名/备注名(自定义标识) */
alias: string;
- /** 头像图片URL(阿里云OSS存储地址) */
avatar: string;
- /**
- * 性别标识(0:未知/未设置,1:男,2:女,符合微信性别字段定义)
- * 原始数据中为0,此处标注可能的枚举值以便后续扩展
- */
- gender: 0 | 1 | 2;
- /** 地区信息(原始数据为空字符串,未填写) */
+ gender: number;
region: string;
- /** 个性签名(原始数据为空字符串,未填写) */
signature: string;
- /** 绑定的QQ号(0表示未绑定) */
bindQQ: string;
- /** 绑定的邮箱(原始数据为空字符串,未绑定) */
bindEmail: string;
- /** 绑定的手机号(原始数据为空字符串,未绑定) */
bindMobile: string;
- /**
- * 用户创建时间(注册时间)
- * 格式:ISO 8601标准时间字符串(YYYY-MM-DDTHH:mm:ss.fffffff)
- */
createTime: string;
- /** 当前登录设备ID(关联用户使用的设备唯一标识) */
currentDeviceId: number;
- /** 是否删除(逻辑删除标识,false:未删除,true:已删除) */
isDeleted: boolean;
- /**
- * 删除时间(逻辑删除时记录,未删除时为默认初始时间"0001-01-01T00:00:00")
- * 格式:ISO 8601标准时间字符串
- */
deleteTime: string;
- /** 用户所属群组ID(用于群组分类管理) */
groupId: number;
- /** 系统内对用户的备注信息(补充标识) */
memo: string;
- /** 用户当前使用的微信版本号 */
wechatVersion: string;
- /**
- * 用户标签列表(用于分类、筛选,涵盖:业务类型、互动记录、身份标识等维度)
- * 例如:"团队"(身份)、"未成交"(业务状态)、"抖音"(渠道)等
- */
labels: string[];
- /**
- * 最后更新时间(用户信息最近修改时间)
- * 格式:ISO 8601标准时间字符串
- */
lastUpdateTime: string;
[key: string]: any;
}
@@ -102,6 +68,20 @@ export interface CkUserInfo {
export interface CkChatState {
userInfo: CkUserInfo | null;
isLoggedIn: boolean;
+ contractList: ContractData[];
+ chatSessions: any[];
+ kfUserList: KfUserListData[];
+ getkfUserList: () => KfUserListData[];
+ asyncKfUserList: (data: KfUserListData[]) => void;
+ asyncContractList: (data: ContractData[]) => void;
+ asyncChatSessions: (data: any[]) => 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;
updateAccount: (account: Partial) => void;
diff --git a/Cunkebao/src/store/module/ckchat.ts b/Cunkebao/src/store/module/ckchat.ts
index b7f9a26e..13289ac5 100644
--- a/Cunkebao/src/store/module/ckchat.ts
+++ b/Cunkebao/src/store/module/ckchat.ts
@@ -5,40 +5,50 @@ import {
CkUserInfo,
CkAccount,
CkTenant,
- CkChatCtrlUserData,
+ KfUserListData,
} from "./ckchat.data";
import { ContractData, GroupData } from "@/pages/pc/ckbox/data";
export const useCkChatStore = createPersistStore(
set => ({
userInfo: null,
isLoggedIn: false,
- chatSessions: [],
- ctrlUserList: [],
- // 控制终端用户列表
- getCtrlUserList: () => {
- const state = useCkChatStore.getState();
- return state.ctrlUserList;
+ contractList: [], //联系人列表
+ chatSessions: [], //聊天会话
+ kfUserList: [], //客服列表
+ // 异步设置会话列表
+ asyncChatSessions: data => {
+ set({ chatSessions: data });
},
- // 添加控制终端用户
- addCtrlUser: (user: CkChatCtrlUserData) => {
- set(state => ({
- ctrlUserList: [...state.ctrlUserList, user],
- }));
+ // 异步设置联系人列表
+ asyncContractList: data => {
+ set({ contractList: data });
+ },
+ // 控制终端用户列表
+ getkfUserList: () => {
+ const state = useCkChatStore.getState();
+ return state.kfUserList;
+ },
+ asyncKfUserList: data => {
+ set({ kfUserList: data });
},
// 删除控制终端用户
deleteCtrlUser: (userId: number) => {
set(state => ({
- ctrlUserList: state.ctrlUserList.filter(item => item.id !== userId),
+ kfUserList: state.kfUserList.filter(item => item.id !== userId),
}));
},
// 更新控制终端用户
- updateCtrlUser: (user: CkChatCtrlUserData) => {
+ updateCtrlUser: (user: KfUserListData) => {
set(state => ({
- ctrlUserList: state.ctrlUserList.map(item =>
+ kfUserList: state.kfUserList.map(item =>
item.id === user.id ? user : item,
),
}));
},
+ // 清空控制终端用户列表
+ clearkfUserList: () => {
+ set({ kfUserList: [] });
+ },
// 获取聊天会话
getChatSessions: () => {
const state = useCkChatStore.getState();
@@ -159,10 +169,16 @@ export const updateChatSession = (session: ContractData | GroupData) =>
useCkChatStore.getState().updateChatSession(session);
export const deleteChatSession = (sessionId: string) =>
useCkChatStore.getState().deleteChatSession(sessionId);
-export const getCtrlUserList = () => useCkChatStore.getState().ctrlUserList;
-export const addCtrlUser = (user: CkChatCtrlUserData) =>
+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: CkChatCtrlUserData) =>
+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);
diff --git a/Cunkebao/src/utils/common.ts b/Cunkebao/src/utils/common.ts
index 65db3b71..fd740223 100644
--- a/Cunkebao/src/utils/common.ts
+++ b/Cunkebao/src/utils/common.ts
@@ -1,6 +1,9 @@
import { Modal } from "antd-mobile";
import { getSetting } from "@/store/module/settings";
export function formatWechatTime(timestamp) {
+ if (!timestamp) {
+ return "";
+ }
// 处理时间戳(兼容秒级/毫秒级)
const date = new Date(
timestamp.toString().length === 10 ? timestamp * 1000 : timestamp,
From fdc6c15d889cd78aaf66d23027b1c7fe456ce325 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, 28 Aug 2025 15:51:32 +0800
Subject: [PATCH 50/78] =?UTF-8?q?feat(ckchat):=20=E6=B7=BB=E5=8A=A0?=
=?UTF-8?q?=E8=81=94=E7=B3=BB=E4=BA=BA=E5=88=86=E7=BB=84=E5=8A=9F=E8=83=BD?=
=?UTF-8?q?=E5=B9=B6=E5=AE=9E=E7=8E=B0=E9=AA=A8=E6=9E=B6=E5=B1=8F=E4=BC=98?=
=?UTF-8?q?=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
添加新的联系人分组状态 newContractList 和对应的异步设置方法 asyncNewContractList
实现页面加载时的骨架屏效果,优化用户体验
重构 SidebarMenu 组件样式,分离骨架屏逻辑
新增 PageSkeleton 组件用于统一管理骨架屏
---
Cunkebao/dist/.vite/manifest.json | 18 +--
Cunkebao/dist/index.html | 8 +-
.../SidebarMenu/SidebarMenu.module.scss | 129 +++++++++++++-----
.../pc/ckbox/components/SidebarMenu/index.tsx | 59 +++++++-
.../components/Skeleton/index.module.scss | 111 +++++++++++++++
.../pc/ckbox/components/Skeleton/index.tsx | 91 ++++++++++++
Cunkebao/src/pages/pc/ckbox/index.tsx | 127 +++++++++--------
Cunkebao/src/store/module/ckchat.ts | 8 ++
8 files changed, 444 insertions(+), 107 deletions(-)
create mode 100644 Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.module.scss
create mode 100644 Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.tsx
diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json
index 42480c71..17e97181 100644
--- a/Cunkebao/dist/.vite/manifest.json
+++ b/Cunkebao/dist/.vite/manifest.json
@@ -1,9 +1,9 @@
{
- "_charts-DDGb1us0.js": {
- "file": "assets/charts-DDGb1us0.js",
+ "_charts-M0qaf_ew.js": {
+ "file": "assets/charts-M0qaf_ew.js",
"name": "charts",
"imports": [
- "_ui-rFvxQTWo.js",
+ "_ui-D5qYGnLz.js",
"_vendor-2vc8h_ct.js"
]
},
@@ -11,8 +11,8 @@
"file": "assets/ui-D0C0OGrH.css",
"src": "_ui-D0C0OGrH.css"
},
- "_ui-rFvxQTWo.js": {
- "file": "assets/ui-rFvxQTWo.js",
+ "_ui-D5qYGnLz.js": {
+ "file": "assets/ui-D5qYGnLz.js",
"name": "ui",
"imports": [
"_vendor-2vc8h_ct.js"
@@ -33,18 +33,18 @@
"name": "vendor"
},
"index.html": {
- "file": "assets/index-C3xy08Hg.js",
+ "file": "assets/index-BQxyt58_.js",
"name": "index",
"src": "index.html",
"isEntry": true,
"imports": [
"_vendor-2vc8h_ct.js",
- "_ui-rFvxQTWo.js",
"_utils-6WF66_dS.js",
- "_charts-DDGb1us0.js"
+ "_ui-D5qYGnLz.js",
+ "_charts-M0qaf_ew.js"
],
"css": [
- "assets/index-D4Jt-UDy.css"
+ "assets/index-B6B8u-1D.css"
]
}
}
\ No newline at end of file
diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html
index f4b62be3..dc0fa190 100644
--- a/Cunkebao/dist/index.html
+++ b/Cunkebao/dist/index.html
@@ -11,13 +11,13 @@
-
+
-
-
+
+
-
+
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss
index 6a60b1ef..c973b4bc 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss
@@ -1,50 +1,100 @@
-.headerContainer {
- background: #fff;
- border-bottom: 1px solid #f0f0f0;
-}
-
-.searchBar {
- padding: 16px 16px 8px;
+.sidebarMenu {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
background: #fff;
- :global(.ant-input) {
- border-radius: 20px;
- background: #f5f5f5;
- border: none;
+ .headerContainer {
+ padding: 16px;
+ background: #fff;
+ border-bottom: 1px solid #f0f0f0;
- &:focus {
+ .searchBar {
+ margin-bottom: 16px;
+ padding: 0;
background: #fff;
- border: 1px solid #1890ff;
- box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+
+ :global(.ant-input) {
+ border-radius: 20px;
+ background: #f5f5f5;
+ border: none;
+
+ &:focus {
+ background: #fff;
+ border: 1px solid #1890ff;
+ box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+ }
+ }
+ }
+
+ .tabsContainer {
+ display: flex;
+ justify-content: space-around;
+ border-bottom: 1px solid #f0f0f0;
+ padding: 0 0 8px;
+
+ .tabItem {
+ padding: 8px 0;
+ flex: 1;
+ text-align: center;
+ cursor: pointer;
+ color: #999;
+ transition: all 0.3s;
+
+ &:hover {
+ color: #1890ff;
+ }
+
+ &.active {
+ color: #1890ff;
+ border-bottom: 2px solid #1890ff;
+ }
+
+ span {
+ margin-left: 4px;
+ }
+ }
}
}
}
-.tabsContainer {
+// 骨架屏样式
+.skeletonContainer {
+ height: 100%;
+ padding: 16px;
display: flex;
- padding: 0 16px 8px;
- border-bottom: 1px solid #f0f0f0;
+ flex-direction: column;
- .tabItem {
+ .searchBarSkeleton {
+ margin-bottom: 16px;
+ }
+
+ .tabsContainerSkeleton {
display: flex;
- align-items: center;
- padding: 8px 12px;
- cursor: pointer;
- border-radius: 4px;
- transition: all 0.3s;
+ justify-content: space-around;
+ margin-bottom: 16px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid #f0f0f0;
+ gap: 8px;
+ }
- &:hover {
- color: #1890ff;
- background-color: rgba(24, 144, 255, 0.1);
- }
+ .contactListSkeleton {
+ flex: 1;
+ overflow-y: auto;
- &.active {
- color: #1890ff;
- background-color: rgba(24, 144, 255, 0.1);
- }
+ .contactItemSkeleton {
+ display: flex;
+ align-items: center;
+ padding: 12px 0;
+ border-bottom: 1px solid #f5f5f5;
- span {
- margin-left: 4px;
+ .contactInfoSkeleton {
+ margin-left: 12px;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
}
}
}
@@ -59,3 +109,16 @@
padding: 20px;
text-align: center;
}
+
+.contentContainer {
+ flex: 1;
+ overflow-y: auto;
+}
+
+.footer {
+ padding: 10px;
+ text-align: center;
+ border-top: 1px solid #f0f0f0;
+ background: #fff;
+ display: none; /* 默认隐藏底部,如果需要显示可以移除此行 */
+}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
index 41bd4da7..64723fcc 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
@@ -1,5 +1,5 @@
import React, { useState } from "react";
-import { Input } from "antd";
+import { Input, Skeleton } from "antd";
import {
SearchOutlined,
UserOutlined,
@@ -9,7 +9,6 @@ import {
import { ContractData } from "@/pages/pc/ckbox/data";
import WechatFriendsModule from "./WechatFriendsModule";
import MessageList from "./MessageList/index";
-import LayoutFiexd from "@/components/Layout/LayoutFiexd";
import styles from "./SidebarMenu.module.scss";
import { getChatSessions } from "@/store/module/ckchat";
@@ -53,6 +52,51 @@ const SidebarMenu: React.FC = ({
);
};
+ // 渲染骨架屏
+ const renderSkeleton = () => (
+
+
+
+
+
+
+
+
+
+
+ {Array(8)
+ .fill(null)
+ .map((_, index) => (
+
+ ))}
+
+
+ );
+
// 渲染Header部分,包含搜索框和标签页切换
const renderHeader = () => (
@@ -125,10 +169,15 @@ const SidebarMenu: React.FC
= ({
}
};
+ if (loading) {
+ return renderSkeleton();
+ }
+
return (
-
- {renderContent()}
-
+
+ {renderHeader()}
+
{renderContent()}
+
);
};
diff --git a/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.module.scss b/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.module.scss
new file mode 100644
index 00000000..3c0329a0
--- /dev/null
+++ b/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.module.scss
@@ -0,0 +1,111 @@
+.skeletonLayout {
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+
+ .skeletonHeader {
+ height: 64px;
+ padding: 0 24px;
+ display: flex;
+ align-items: center;
+ background-color: #fff;
+ border-bottom: 1px solid #f0f0f0;
+ }
+
+ .skeletonVerticalSider {
+ background-color: #fff;
+ border-right: 1px solid #f0f0f0;
+
+ .verticalUserList {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 16px 0;
+
+ .verticalUserItem {
+ margin-bottom: 16px;
+ }
+ }
+ }
+
+ .skeletonSider {
+ background-color: #fff;
+ border-right: 1px solid #f0f0f0;
+ padding: 16px;
+
+ .searchSkeleton {
+ margin-bottom: 16px;
+ }
+
+ .tabsSkeleton {
+ display: flex;
+ justify-content: space-around;
+ margin-bottom: 16px;
+ }
+
+ .contactListSkeleton {
+ .contactItemSkeleton {
+ display: flex;
+ align-items: center;
+ padding: 12px 0;
+ border-bottom: 1px solid #f5f5f5;
+
+ .contactInfoSkeleton {
+ margin-left: 12px;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+ }
+ }
+ }
+
+ .skeletonMainContent {
+ background-color: #f5f5f5;
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+
+ .chatHeaderSkeleton {
+ background-color: #fff;
+ padding: 16px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ border-radius: 8px 8px 0 0;
+ }
+
+ .chatContentSkeleton {
+ flex: 1;
+ background-color: #fff;
+ padding: 16px;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ .messageSkeleton {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+
+ &.leftMessage {
+ align-self: flex-start;
+ }
+
+ &.rightMessage {
+ align-self: flex-end;
+ flex-direction: row-reverse;
+ }
+ }
+ }
+
+ .inputAreaSkeleton {
+ background-color: #fff;
+ padding: 16px;
+ border-radius: 0 0 8px 8px;
+ border-top: 1px solid #f0f0f0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.tsx
new file mode 100644
index 00000000..f4ea1f1d
--- /dev/null
+++ b/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.tsx
@@ -0,0 +1,91 @@
+import React from 'react';
+import { Skeleton, Layout } from 'antd';
+import styles from './index.module.scss';
+import pageStyles from '../../index.module.scss';
+
+const { Header, Content, Sider } = Layout;
+
+interface PageSkeletonProps {
+ loading: boolean;
+ children: React.ReactNode;
+}
+
+/**
+ * 页面骨架屏组件
+ * 在数据加载完成前显示骨架屏
+ */
+const PageSkeleton: React.FC = ({ loading, children }) => {
+ if (!loading) return <>{children}>;
+
+ return (
+
+
+
+ {/* 垂直侧边栏骨架 */}
+
+
+ {Array(5)
+ .fill(null)
+ .map((_, index) => (
+
+
+
+ ))}
+
+
+
+ {/* 左侧联系人边栏骨架 */}
+
+
+
+
+
+
+
+
+
+ {Array(8)
+ .fill(null)
+ .map((_, index) => (
+
+ ))}
+
+
+
+ {/* 主内容区骨架 */}
+
+
+
+
+
+
+ {Array(5)
+ .fill(null)
+ .map((_, index) => (
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+ );
+};
+
+export default PageSkeleton;
\ No newline at end of file
diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx
index 1c318615..8b4ba820 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/index.tsx
@@ -1,15 +1,17 @@
import React, { useState, useEffect } from "react";
-import { Layout, Button, Space, message, Tooltip, Spin } from "antd";
+import { Layout, Button, Space, message, Tooltip } from "antd";
import { InfoCircleOutlined, MessageOutlined } from "@ant-design/icons";
import dayjs from "dayjs";
import { ContractData } from "./data";
import ChatWindow from "./components/ChatWindow/index";
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";
const { Header, Content, Sider } = Layout;
import { chatInitAPIdata } from "./main";
+import { KfUserListData } from "@/store/module/ckchat.data";
const CkboxPage: React.FC = () => {
const [messageApi, contextHolder] = message.useMessage();
@@ -25,7 +27,17 @@ const CkboxPage: React.FC = () => {
setLoading(true);
chatInitAPIdata()
.then(response => {
- const { contractList, chatRoomList, kfUserList } = response;
+ const data = response as {
+ contractList: any[];
+ groupList: any[];
+ kfUserList: KfUserListData[];
+ newContractList: { groupName: string; contacts: any[] }[];
+ };
+ const { contractList, groupList, kfUserList, newContractList } = data;
+ response;
+
+ console.log(contractList, groupList, kfUserList, newContractList);
+
//找出已经在聊天的
const isChatList = contractList.filter(
v => (v?.config && v.config?.chat) || false,
@@ -77,66 +89,69 @@ const CkboxPage: React.FC = () => {
};
return (
-
- {contextHolder}
-
-
- {/* 垂直侧边栏 */}
+
+
+ {contextHolder}
+
+
+ {/* 垂直侧边栏 */}
-
-
-
+
+
+
- {/* 左侧联系人边栏 */}
-
-
-
+ {/* 左侧联系人边栏 */}
+
+
+
- {/* 主内容区 */}
-
- {currentChat ? (
-
-
-
-
- }
- onClick={() => setShowProfile(!showProfile)}
- size="small"
- >
- {showProfile ? "隐藏资料" : "显示资料"}
-
-
-
+ {/* 主内容区 */}
+
+ {currentChat ? (
+
+
+
+
+ }
+ onClick={() => setShowProfile(!showProfile)}
+ size="small"
+ >
+ {showProfile ? "隐藏资料" : "显示资料"}
+
+
+
+
+
setShowProfile(!showProfile)}
+ />
- setShowProfile(!showProfile)}
- />
-
- ) : (
-
-
-
-
欢迎使用触客宝
-
选择一个联系人开始聊天
+ ) : (
+
+
+
+
欢迎使用触客宝
+
选择一个联系人开始聊天
+
-
- )}
-
+ )}
+
+
-
+
);
};
diff --git a/Cunkebao/src/store/module/ckchat.ts b/Cunkebao/src/store/module/ckchat.ts
index 13289ac5..22d66f04 100644
--- a/Cunkebao/src/store/module/ckchat.ts
+++ b/Cunkebao/src/store/module/ckchat.ts
@@ -15,6 +15,11 @@ export const useCkChatStore = createPersistStore
(
contractList: [], //联系人列表
chatSessions: [], //聊天会话
kfUserList: [], //客服列表
+ newContractList: [], //联系人分组
+ // 异步设置会话列表
+ asyncNewContractList: data => {
+ set({ newContractList: data });
+ },
// 异步设置会话列表
asyncChatSessions: data => {
set({ chatSessions: data });
@@ -182,3 +187,6 @@ 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);
From 1053d4125f96c5f8d001aa0a8b6dd8b55ac815aa 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, 28 Aug 2025 15:58:50 +0800
Subject: [PATCH 51/78] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E8=AE=BE?=
=?UTF-8?q?=E5=A4=87=E7=BB=84=E5=AD=97=E6=AE=B5=E5=90=8D=E6=8B=BC=E5=86=99?=
=?UTF-8?q?=E9=94=99=E8=AF=AF=E5=8F=8A=E7=8A=B6=E6=80=81=E5=80=BC=E9=94=99?=
=?UTF-8?q?=E8=AF=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
修复接口字段名拼写错误,将`devices`改为`deveiceGroups`以保持前后端一致
同时修正状态值映射错误,将启用状态对应的值从2改为0
---
.../src/pages/mobile/workspace/moments-sync/new/index.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Cunkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx b/Cunkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx
index e2484e90..2fc1176d 100644
--- a/Cunkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx
@@ -70,7 +70,7 @@ const NewMomentsSync: React.FC = () => {
syncType: res.accountType === 1 ? 1 : 2,
accountType: res.accountType === 1 ? "business" : "personal",
enabled: res.status === 1,
- deveiceGroups: res.config?.devices || [],
+ deveiceGroups: res.config?.deveiceGroups || [],
// 关键:用id字符串数组回填
contentGroups: res.config?.contentGroups || [], // 直接用对象数组
contentTypes: res.config?.contentTypes || ["text", "image", "video"],
@@ -134,7 +134,7 @@ const NewMomentsSync: React.FC = () => {
try {
const params = {
name: formData.taskName,
- devices: formData.deveiceGroups,
+ deveiceGroups: formData.deveiceGroups,
contentLibraries: formData.contentGroups.map((lib: any) => lib.id),
syncInterval: formData.syncInterval,
syncCount: formData.syncCount,
@@ -146,7 +146,7 @@ const NewMomentsSync: React.FC = () => {
targetTags: formData.targetTags,
filterKeywords: formData.filterKeywords,
type: 2,
- status: formData.enabled ? 1 : 2,
+ status: formData.enabled ? 1 : 0,
};
if (isEditMode && id) {
await updateMomentsSync({ id, ...params });
From 31d75be68e7c9bd6c7e9c48e82e5410e8d8c880b 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, 28 Aug 2025 16:06:16 +0800
Subject: [PATCH 52/78] =?UTF-8?q?fix(AccountListModal):=20=E6=9B=B4?=
=?UTF-8?q?=E6=96=B0=E8=B4=A6=E5=8F=B7=E7=8A=B6=E6=80=81=E6=98=BE=E7=A4=BA?=
=?UTF-8?q?=E9=80=BB=E8=BE=91=E5=92=8C=E9=A2=9C=E8=89=B2=E6=A0=87=E8=AF=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
修改账号状态显示逻辑,将字符串状态改为数字类型,并更新对应的颜色标识和状态文本。同时更新构建产物的文件引用路径。
---
Cunkebao/dist/.vite/manifest.json | 16 +++----
Cunkebao/dist/index.html | 6 +--
.../plan/list/components/AccountListModal.tsx | 47 ++++++++++++-------
3 files changed, 40 insertions(+), 29 deletions(-)
diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json
index f5df3f32..050321b1 100644
--- a/Cunkebao/dist/.vite/manifest.json
+++ b/Cunkebao/dist/.vite/manifest.json
@@ -1,9 +1,9 @@
{
- "_charts-BY5IGS3y.js": {
- "file": "assets/charts-BY5IGS3y.js",
+ "_charts-62ymwWYF.js": {
+ "file": "assets/charts-62ymwWYF.js",
"name": "charts",
"imports": [
- "_ui-g-9w80lh.js",
+ "_ui-DUe_gloh.js",
"_vendor-2vc8h_ct.js"
]
},
@@ -11,8 +11,8 @@
"file": "assets/ui-D0C0OGrH.css",
"src": "_ui-D0C0OGrH.css"
},
- "_ui-g-9w80lh.js": {
- "file": "assets/ui-g-9w80lh.js",
+ "_ui-DUe_gloh.js": {
+ "file": "assets/ui-DUe_gloh.js",
"name": "ui",
"imports": [
"_vendor-2vc8h_ct.js"
@@ -33,15 +33,15 @@
"name": "vendor"
},
"index.html": {
- "file": "assets/index-D1PYRHiS.js",
+ "file": "assets/index-BmpchxsT.js",
"name": "index",
"src": "index.html",
"isEntry": true,
"imports": [
"_vendor-2vc8h_ct.js",
"_utils-6WF66_dS.js",
- "_ui-g-9w80lh.js",
- "_charts-BY5IGS3y.js"
+ "_ui-DUe_gloh.js",
+ "_charts-62ymwWYF.js"
],
"css": [
"assets/index-ejYsXKTB.css"
diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html
index abe21cc7..e1fc8350 100644
--- a/Cunkebao/dist/index.html
+++ b/Cunkebao/dist/index.html
@@ -11,11 +11,11 @@
-
+
-
-
+
+
diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx
index dd8cff30..734536f2 100644
--- a/Cunkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx
+++ b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx
@@ -61,29 +61,40 @@ const AccountListModal: React.FC = ({
}, [visible, ruleId]);
const title = ruleName ? `${ruleName} - 已添加账号列表` : "已添加账号列表";
- const getStatusColor = (status?: string) => {
- switch (status) {
- case "normal":
- return "#52c41a";
- case "limited":
- return "#faad14";
- case "blocked":
- return "#ff4d4f";
+ const getStatusColor = (status?: string | number) => {
+ // 确保status是数字类型
+ const statusNum = Number(status);
+
+ switch (statusNum) {
+ case 0:
+ return "#faad14"; // 待添加 - 黄色警告色
+ case 1:
+ return "#1890ff"; // 添加中 - 蓝色进行中
+ case 2:
+ return "#ff4d4f"; // 添加失败 - 红色错误色
+ case 3:
+ return "#ff4d4f"; // 添加失败 - 红色错误色
+ case 4:
+ return "#52c41a"; // 已添加 - 绿色成功色
default:
- return "#d9d9d9";
+ return "#d9d9d9"; // 未知状态 - 灰色
}
};
- const getStatusText = (status?: string) => {
+ const getStatusText = (status?: number) => {
switch (status) {
- case "normal":
- return "正常";
- case "limited":
- return "受限";
- case "blocked":
- return "封禁";
+ case 0:
+ return "待添加";
+ case 1:
+ return "添加中";
+ case 2:
+ return "添加失败";
+ case 3:
+ return "添加失败";
+ case 4:
+ return "已添加";
default:
- return "未知";
+ return "未知状态";
}
};
@@ -149,7 +160,7 @@ const AccountListModal: React.FC = ({
style={{ backgroundColor: getStatusColor(account.status) }}
/>
- {getStatusText(account.status)}
+ {getStatusText(Number(account.status))}
From b0496e309eefe5e5b2bc4f6f9edb5eabc19ace39 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, 28 Aug 2025 16:06:39 +0800
Subject: [PATCH 53/78] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?=
=?UTF-8?q?=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/dist/.vite/manifest.json | 2 +-
Cunkebao/dist/index.html | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json
index 050321b1..d5e8389c 100644
--- a/Cunkebao/dist/.vite/manifest.json
+++ b/Cunkebao/dist/.vite/manifest.json
@@ -33,7 +33,7 @@
"name": "vendor"
},
"index.html": {
- "file": "assets/index-BmpchxsT.js",
+ "file": "assets/index-v5mFyoXq.js",
"name": "index",
"src": "index.html",
"isEntry": true,
diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html
index e1fc8350..18b071ca 100644
--- a/Cunkebao/dist/index.html
+++ b/Cunkebao/dist/index.html
@@ -11,7 +11,7 @@
-
+
From f7b28d876d6ee1ee8383f71d7927762b9caec775 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, 28 Aug 2025 16:09:58 +0800
Subject: [PATCH 54/78] =?UTF-8?q?fix(AccountListModal):=20=E4=BF=AE?=
=?UTF-8?q?=E6=94=B9=E7=8A=B6=E6=80=812=E7=9A=84=E6=98=BE=E7=A4=BA?=
=?UTF-8?q?=E6=96=87=E6=9C=AC=E4=B8=BA"=E8=AF=B7=E6=B1=82=E5=B7=B2?=
=?UTF-8?q?=E5=8F=91=E9=80=81=E5=BE=85=E9=80=9A=E8=BF=87"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
构建产物已更新,反映最新的代码变更
---
Cunkebao/dist/.vite/manifest.json | 2 +-
Cunkebao/dist/index.html | 2 +-
.../mobile/scenarios/plan/list/components/AccountListModal.tsx | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json
index d5e8389c..24c694d0 100644
--- a/Cunkebao/dist/.vite/manifest.json
+++ b/Cunkebao/dist/.vite/manifest.json
@@ -33,7 +33,7 @@
"name": "vendor"
},
"index.html": {
- "file": "assets/index-v5mFyoXq.js",
+ "file": "assets/index-C4WYi_u0.js",
"name": "index",
"src": "index.html",
"isEntry": true,
diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html
index 18b071ca..b1a6a34d 100644
--- a/Cunkebao/dist/index.html
+++ b/Cunkebao/dist/index.html
@@ -11,7 +11,7 @@
-
+
diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx
index 734536f2..7d16ad61 100644
--- a/Cunkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx
+++ b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx
@@ -88,7 +88,7 @@ const AccountListModal: React.FC = ({
case 1:
return "添加中";
case 2:
- return "添加失败";
+ return "请求已发送待通过";
case 3:
return "添加失败";
case 4:
From 0a6f892ba8dbe4f89e59790f04215b06ed10e5b8 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, 28 Aug 2025 16:26:14 +0800
Subject: [PATCH 55/78] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?=
=?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9AcontentLibraries?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Cunkebao/src/pages/mobile/scenarios/plan/new/index.data.ts | 2 ++
Cunkebao/src/pages/mobile/scenarios/plan/new/index.tsx | 2 ++
Cunkebao/src/pages/mobile/scenarios/plan/new/steps/base.data.ts | 2 ++
Cunkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx | 2 +-
4 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/new/index.data.ts b/Cunkebao/src/pages/mobile/scenarios/plan/new/index.data.ts
index d7c42eb7..34489536 100644
--- a/Cunkebao/src/pages/mobile/scenarios/plan/new/index.data.ts
+++ b/Cunkebao/src/pages/mobile/scenarios/plan/new/index.data.ts
@@ -54,4 +54,6 @@ export const defFormData: FormData = {
deveiceGroupsOptions: [],
wechatGroups: [],
wechatGroupsOptions: [],
+ contentGroups: [],
+ contentGroupsOptions: [],
};
diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/new/index.tsx b/Cunkebao/src/pages/mobile/scenarios/plan/new/index.tsx
index 708d29cb..acce83fa 100644
--- a/Cunkebao/src/pages/mobile/scenarios/plan/new/index.tsx
+++ b/Cunkebao/src/pages/mobile/scenarios/plan/new/index.tsx
@@ -71,6 +71,8 @@ export default function NewPlan() {
deveiceGroupsOptions: detail.deveiceGroupsOptions ?? [],
wechatGroups: detail.wechatGroups ?? [],
wechatGroupsOptions: detail.wechatGroupsOptions ?? [],
+ contentGroups: detail.contentGroups ?? [],
+ contentGroupsOptions: detail.contentGroupsOptions ?? [],
status: detail.status ?? 0,
messagePlans: detail.messagePlans ?? [],
}));
diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/new/steps/base.data.ts b/Cunkebao/src/pages/mobile/scenarios/plan/new/steps/base.data.ts
index d702bcb1..dd5e5a6f 100644
--- a/Cunkebao/src/pages/mobile/scenarios/plan/new/steps/base.data.ts
+++ b/Cunkebao/src/pages/mobile/scenarios/plan/new/steps/base.data.ts
@@ -58,6 +58,8 @@ export interface MessageContentItem {
groupIds?: string[]; // 改为数组以支持GroupSelection组件
groupOptions?: any[]; // 添加群选项数组
linkUrl?: string;
+ coverImage?: string;
+ [key: string]: any;
}
export interface MessageContentGroup {
diff --git a/Cunkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx b/Cunkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx
index 2fc1176d..984698a1 100644
--- a/Cunkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx
@@ -135,7 +135,7 @@ const NewMomentsSync: React.FC = () => {
const params = {
name: formData.taskName,
deveiceGroups: formData.deveiceGroups,
- contentLibraries: formData.contentGroups.map((lib: any) => lib.id),
+ contentGroups: formData.contentGroups.map((lib: any) => lib.id),
syncInterval: formData.syncInterval,
syncCount: formData.syncCount,
syncType: formData.syncType, // 账号类型真实传参
From e050da41e5efea66d70628ec78e787f3f5306951 Mon Sep 17 00:00:00 2001
From: wong <106998207@qq.com>
Date: Thu, 28 Aug 2025 16:35:10 +0800
Subject: [PATCH 56/78] =?UTF-8?q?=E9=AA=8C=E8=AF=81=E4=BF=AE=E6=94=B9?=
=?UTF-8?q?=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../cunkebao/validate/Workbench.php | 55 +++++++++----------
1 file changed, 27 insertions(+), 28 deletions(-)
diff --git a/Server/application/cunkebao/validate/Workbench.php b/Server/application/cunkebao/validate/Workbench.php
index 6796140f..664178be 100644
--- a/Server/application/cunkebao/validate/Workbench.php
+++ b/Server/application/cunkebao/validate/Workbench.php
@@ -33,18 +33,18 @@ class Workbench extends Validate
'syncType' => 'requireIf:type,2|in:1,2,3,4',
'startTime' => 'requireIf:type,2|dateFormat:H:i',
'endTime' => 'requireIf:type,2|dateFormat:H:i',
- 'accountType' => 'requireIf:type,2|in:1,2',
- 'contentLibraries' => 'requireIf:type,2|array',
+ 'accountGroups' => 'requireIf:type,2|in:1,2',
+ 'contentGroups' => 'requireIf:type,2|array',
// 群消息推送特有参数
- 'pushType' => 'requireIf:type,3|in:1,2', // 推送方式 1定时 2立即
+ 'pushType' => 'requireIf:type,3|in:0,1', // 推送方式 0定时 1立即
'startTime' => 'requireIf:type,3|dateFormat:H:i',
'endTime' => 'requireIf:type,3|dateFormat:H:i',
'maxPerDay' => 'requireIf:type,3|number|min:1',
'pushOrder' => 'requireIf:type,3|in:1,2', // 1最早 2最新
'isLoop' => 'requireIf:type,3|in:0,1',
'status' => 'requireIf:type,3|in:0,1',
- 'groups' => 'requireIf:type,3|array|min:1',
- 'contentLibraries' => 'requireIf:type,3|array|min:1',
+ 'wechatGroups' => 'requireIf:type,3|array|min:1',
+ 'contentGroups' => 'requireIf:type,3|array|min:1',
// 自动建群特有参数
'groupNameTemplate' => 'requireIf:type,4|max:50',
'maxGroupsPerDay' => 'requireIf:type,4|number|min:1',
@@ -56,9 +56,9 @@ class Workbench extends Validate
'timeType' => 'requireIf:type,5|in:1,2',
'startTime' => 'requireIf:type,5|dateFormat:H:i',
'endTime' => 'requireIf:type,5|dateFormat:H:i',
- 'account' => 'requireIf:type,5|array|min:1',
+ 'accountGroups' => 'requireIf:type,5|array|min:1',
// 通用参数
- 'devices' => 'requireIf:type,1,2,5|array',
+ 'deveiceGroups' => 'requireIf:type,1,2,5|array',
];
/**
@@ -98,13 +98,12 @@ class Workbench extends Validate
'startTime.dateFormat' => '发布开始时间格式错误',
'endTime.requireIf' => '请设置发布结束时间',
'endTime.dateFormat' => '发布结束时间格式错误',
- 'accountType.requireIf' => '请选择账号类型',
- 'accountType.in' => '账号类型错误',
- 'contentLibraries.requireIf' => '请选择内容库',
- 'contentLibraries.array' => '内容库格式错误',
+ 'accountGroups.requireIf' => '请选择账号类型',
+ 'accountGroups.in' => '账号类型错误',
+ 'contentGroups.requireIf' => '请选择内容库',
+ 'contentGroups.array' => '内容库格式错误',
// 群消息推送相关提示
'pushType.requireIf' => '请选择推送方式',
- 'pushType.in' => '推送方式错误',
'startTime.requireIf' => '请设置推送开始时间',
'startTime.dateFormat' => '推送开始时间格式错误',
'endTime.requireIf' => '请设置推送结束时间',
@@ -116,11 +115,9 @@ class Workbench extends Validate
'pushOrder.in' => '推送顺序错误',
'isLoop.requireIf' => '请选择是否循环推送',
'isLoop.in' => '循环推送参数错误',
- 'status.requireIf' => '请选择推送状态',
- 'status.in' => '推送状态错误',
- 'groups.requireIf' => '请选择推送群组',
- 'groups.array' => '推送群组格式错误',
- 'groups.min' => '至少选择一个推送群组',
+ 'wechatGroups.requireIf' => '请选择推送群组',
+ 'wechatGroups.array' => '推送群组格式错误',
+ 'wechatGroups.min' => '至少选择一个推送群组',
// 自动建群相关提示
'groupNameTemplate.requireIf' => '请设置群名称前缀',
'groupNameTemplate.max' => '群名称前缀最多50个字符',
@@ -144,31 +141,33 @@ class Workbench extends Validate
'timeType.requireIf' => '请选择时间类型',
// 通用提示
- 'devices.require' => '请选择设备',
- 'devices.array' => '设备格式错误',
+ 'deveiceGroups.require' => '请选择设备',
+ 'deveiceGroups.array' => '设备格式错误',
'targetGroups.require' => '请选择目标用户组',
'targetGroups.array' => '目标用户组格式错误',
- 'account.requireIf' => '流量分发时必须选择分发账号',
- 'account.array' => '分发账号格式错误',
- 'account.min' => '至少选择一个分发账号',
+ 'accountGroups.requireIf' => '流量分发时必须选择分发账号',
+ 'accountGroups.array' => '分发账号格式错误',
+ 'accountGroups.min' => '至少选择一个分发账号',
];
/**
* 验证场景
*/
protected $scene = [
- 'create' => ['name', 'type', 'autoStart', 'devices', 'targetGroups',
+ 'create' => ['name', 'type', 'autoStart', 'deveiceGroups', 'targetGroups',
'interval', 'maxLikes', 'startTime', 'endTime', 'contentTypes',
'syncInterval', 'syncCount', 'syncType',
- 'pushType', 'startTime', 'endTime', 'maxPerDay', 'pushOrder', 'isLoop', 'status', 'groups', 'contentLibraries',
- 'groupNamePrefix', 'maxGroups', 'membersPerGroup'
+ 'pushType', 'startTime', 'endTime', 'maxPerDay', 'pushOrder', 'isLoop', 'status', 'wechatGroups', 'contentGroups',
+ 'groupNamePrefix', 'maxGroups', 'membersPerGroup',
+ 'groupNameTemplate', 'maxGroupsPerDay', 'groupSizeMin', 'groupSizeMax',
],
'update_status' => ['id', 'status'],
- 'edit' => ['name', 'type', 'autoStart', 'devices', 'targetGroups',
+ 'edit' => ['name', 'type', 'autoStart', 'deveiceGroups', 'targetGroups',
'interval', 'maxLikes', 'startTime', 'endTime', 'contentTypes',
'syncInterval', 'syncCount', 'syncType',
- 'pushType', 'startTime', 'endTime', 'maxPerDay', 'pushOrder', 'isLoop', 'status', 'groups', 'contentLibraries',
- 'groupNamePrefix', 'maxGroups', 'membersPerGroup'
+ 'pushType', 'startTime', 'endTime', 'maxPerDay', 'pushOrder', 'isLoop', 'status', 'wechatGroups', 'contentGroups',
+ 'groupNamePrefix', 'maxGroups', 'membersPerGroup',
+ 'groupNameTemplate', 'maxGroupsPerDay', 'groupSizeMin', 'groupSizeMax',
]
];
From 53a78e6744ac0f8bec7b16c7edc2b4933b95964a 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, 28 Aug 2025 17:04:54 +0800
Subject: [PATCH 57/78] =?UTF-8?q?feat(ContentSelection):=20=E6=B7=BB?=
=?UTF-8?q?=E5=8A=A0=E4=B8=B4=E6=97=B6=E9=80=89=E4=B8=AD=E9=80=89=E9=A1=B9?=
=?UTF-8?q?=E4=BB=A5=E4=BC=98=E5=8C=96=E9=80=89=E6=8B=A9=E4=BA=A4=E4=BA=92?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
在内容选择组件中引入临时选中选项(tempSelectedOptions),避免直接修改选中状态。用户确认时才更新实际选中选项,提供更流畅的选择体验并防止误操作。
---
.../src/components/ContentSelection/index.tsx | 23 +++++++++++++------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/Cunkebao/src/components/ContentSelection/index.tsx b/Cunkebao/src/components/ContentSelection/index.tsx
index c5d996e3..457dc923 100644
--- a/Cunkebao/src/components/ContentSelection/index.tsx
+++ b/Cunkebao/src/components/ContentSelection/index.tsx
@@ -53,6 +53,9 @@ export default function ContentSelection({
const [totalPages, setTotalPages] = useState(1);
const [totalLibraries, setTotalLibraries] = useState(0);
const [loading, setLoading] = useState(false);
+ const [tempSelectedOptions, setTempSelectedOptions] = useState(
+ [],
+ );
// 删除已选内容库
const handleRemoveLibrary = (id: number) => {
@@ -72,6 +75,8 @@ export default function ContentSelection({
if (readonly) return;
setCurrentPage(1);
setSearchQuery("");
+ // 复制一份selectedOptions到临时变量
+ setTempSelectedOptions([...selectedOptions]);
setRealVisible(true);
fetchLibraries(1, "");
};
@@ -120,10 +125,10 @@ export default function ContentSelection({
// 处理内容库选择
const handleLibraryToggle = (library: ContentItem) => {
if (readonly) return;
- const newSelected = selectedOptions.some(c => c.id === library.id)
- ? selectedOptions.filter(c => c.id !== library.id)
- : [...selectedOptions, library];
- onSelect(newSelected);
+ const newSelected = tempSelectedOptions.some(c => c.id === library.id)
+ ? tempSelectedOptions.filter(c => c.id !== library.id)
+ : [...tempSelectedOptions, library];
+ setTempSelectedOptions(newSelected);
};
// 获取显示文本
@@ -134,8 +139,10 @@ export default function ContentSelection({
// 确认选择
const handleConfirm = () => {
+ // 用户点击确认时,才更新实际的selectedOptions
+ onSelect(tempSelectedOptions);
if (onConfirm) {
- onConfirm(selectedOptions);
+ onConfirm(tempSelectedOptions);
}
setRealVisible(false);
};
@@ -243,7 +250,7 @@ export default function ContentSelection({
currentPage={currentPage}
totalPages={totalPages}
loading={loading}
- selectedCount={selectedOptions.length}
+ selectedCount={tempSelectedOptions.length}
onPageChange={setCurrentPage}
onCancel={() => setRealVisible(false)}
onConfirm={handleConfirm}
@@ -260,7 +267,9 @@ export default function ContentSelection({
{libraries.map(item => (