From 6c6d0f328e03a1501c5da295454fcc2ccdbc0b77 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, 8 Sep 2025 17:54:41 +0800 Subject: [PATCH 1/6] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E8=A7=A6?= =?UTF-8?q?=E5=AE=A2=E5=AE=9D=E6=A8=A1=E5=9D=97=E6=B5=8B=E8=AF=95=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/src/pages/pc/ckbox/README.md | 236 -------------------------- Cunkebao/src/pages/pc/ckbox/test.tsx | 224 ------------------------ 2 files changed, 460 deletions(-) delete mode 100644 Cunkebao/src/pages/pc/ckbox/README.md delete mode 100644 Cunkebao/src/pages/pc/ckbox/test.tsx diff --git a/Cunkebao/src/pages/pc/ckbox/README.md b/Cunkebao/src/pages/pc/ckbox/README.md deleted file mode 100644 index a7cb0127..00000000 --- a/Cunkebao/src/pages/pc/ckbox/README.md +++ /dev/null @@ -1,236 +0,0 @@ -# 触客宝模块 (CKBox) - -## 功能概述 - -触客宝模块是一个类似微信的聊天系统,提供实时消息、联系人管理、群组聊天等功能,专为企业客户服务场景设计。 - -## 主要功能 - -### 1. 聊天功能 - -- 实时消息发送和接收 -- 支持文本、图片、文件等多种消息类型 -- 消息状态显示(发送中、已发送、已读) -- 消息撤回和转发 -- 表情包和快捷回复 - -### 2. 联系人管理 - -- 联系人列表展示 -- 在线状态显示 -- 联系人搜索和筛选 -- 联系人资料查看 -- 添加/删除联系人 - -### 3. 群组功能 - -- 群组创建和管理 -- 群成员管理 -- 群组消息 -- 群组设置 - -### 4. 聊天会话 - -- 聊天会话列表 -- 未读消息提醒 -- 会话置顶和静音 -- 聊天记录管理 -- 会话搜索 - -### 5. 通话功能 - -- 语音通话 -- 视频通话 -- 通话记录 - -## 界面布局 - -``` -┌─────────────────────────────────────────┐ -│ [搜索栏] │ -├─────────────────────────────────────────┤ -│ [聊天] [联系人] [群组] │ -├─────────────────────────────────────────┤ -│ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ 聊天列表 │ │ │ │ -│ │ • 张三 (2) │ │ │ │ -│ │ • 技术支持群 │ │ 聊天窗口 │ │ -│ │ • 李四 │ │ │ │ -│ │ │ │ │ │ -│ └─────────────────┘ └─────────────────┘ │ -└─────────────────────────────────────────┘ -``` - -## 文件结构 - -``` -ckbox/ -├── index.tsx # 主页面 -├── data.ts # 数据类型定义 -├── api.ts # API接口 -├── index.module.scss # 主页面样式 -├── test.tsx # 测试页面 -├── README.md # 说明文档 -└── components/ # 组件目录 - ├── ChatWindow.tsx # 聊天窗口 - ├── ContactList.tsx # 联系人列表 - ├── MessageList.tsx # 消息列表 - └── *.module.scss # 组件样式文件 -``` - -## 技术栈 - -- **React 18** - 前端框架 -- **TypeScript** - 类型安全 -- **Ant Design** - UI组件库 -- **SCSS** - 样式预处理器 -- **Day.js** - 日期处理 -- **WebSocket** - 实时通信 - -## 数据结构 - -### 联系人 (ContractData) - -```typescript -interface ContractData { - id: string; - name: string; - phone: string; - avatar?: string; - online: boolean; - lastSeen?: string; - status?: string; - department?: string; - position?: string; -} -``` - -### 消息 (MessageData) - -```typescript -interface MessageData { - id: string; - senderId: string; - senderName: string; - content: string; - type: MessageType; - timestamp: string; - isRead: boolean; - replyTo?: string; - forwardFrom?: string; -} -``` - -### 聊天会话 (ChatSession) - -```typescript -interface ChatSession { - id: string; - type: "private" | "group"; - name: string; - avatar?: string; - lastMessage: string; - lastTime: string; - unreadCount: number; - online: boolean; - members?: string[]; - pinned?: boolean; - muted?: boolean; -} -``` - -## 使用方法 - -### 1. 路由配置 - -在路由配置中添加触客宝模块: - -```typescript -// router/module/ckbox.tsx -import React from "react"; -import { lazy } from "react"; -import type { RouteObject } from "react-router-dom"; - -const CkboxPage = lazy(() => import("@/pages/pc/ckbox")); - -const ckboxRoutes: (RouteObject & { auth?: boolean; requiredRole?: string })[] = [ - { - path: "/ckbox", - element: , - auth: true, - requiredRole: "user", - }, -]; - -export default ckboxRoutes; -``` - -### 2. API接口 - -确保后端提供以下API接口: - -- `GET /v1/contracts` - 获取联系人列表 -- `GET /v1/chats/sessions` - 获取聊天会话列表 -- `GET /v1/chats/:id/messages` - 获取聊天历史 -- `POST /v1/chats/:id/messages` - 发送消息 -- `GET /v1/groups` - 获取群组列表 -- `POST /v1/groups` - 创建群组 - -### 3. 访问页面 - -访问 `/ckbox` 路径即可使用触客宝模块。 - -## 组件说明 - -### ChatWindow - -聊天窗口组件,包含: - -- 聊天头部(联系人信息、通话按钮、更多操作) -- 消息内容区域(消息气泡、时间显示) -- 输入区域(文本输入、表情、附件、发送按钮) - -### ContactList - -联系人列表组件,包含: - -- 联系人头像和在线状态 -- 联系人姓名和状态 -- 点击进入聊天 - -### MessageList - -消息列表组件,包含: - -- 聊天会话列表 -- 最后消息和时间 -- 未读消息数量 -- 在线状态显示 - -## 样式特点 - -- 类似微信的界面设计 -- 响应式布局,支持移动端 -- 现代化的UI风格 -- 良好的用户体验 - -## 开发规范 - -- 使用TypeScript进行类型检查 -- 遵循ESLint代码规范 -- 组件按功能拆分,保持单一职责 -- API接口统一管理 -- 错误处理和用户反馈 - -## 扩展功能 - -可以根据需要扩展以下功能: - -- 消息加密 -- 语音消息 -- 视频消息 -- 消息搜索 -- 消息备份 -- 多端同步 -- 消息提醒 -- 自动回复 diff --git a/Cunkebao/src/pages/pc/ckbox/test.tsx b/Cunkebao/src/pages/pc/ckbox/test.tsx deleted file mode 100644 index d089775e..00000000 --- a/Cunkebao/src/pages/pc/ckbox/test.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import React from "react"; -import { Card, Typography, Space, Button, Tag } from "antd"; -import { - MessageOutlined, - UserOutlined, - TeamOutlined, - PhoneOutlined, - VideoCameraOutlined, -} from "@ant-design/icons"; - -const { Title, Paragraph } = Typography; - -const CkboxTestPage: React.FC = () => { - return ( -
- - <MessageOutlined style={{ marginRight: "8px" }} /> - 触客宝模块测试页面 - - - - 这是一个类似微信的聊天系统,提供实时消息、联系人管理、群组聊天等功能。 - - - - {/* 功能特性 */} - - - }> - 实时聊天 - - }> - 联系人管理 - - }> - 群组聊天 - - }> - 语音通话 - - }> - 视频通话 - - - - - {/* 界面布局 */} - -
-
- {/* 左侧边栏 */} -
-
- 左侧边栏 -
-
• 搜索栏
-
• 聊天标签页
-
• 联系人标签页
-
• 群组标签页
-
- - {/* 主聊天区域 */} -
- {/* 聊天头部 */} -
- 聊天头部 -
- - {/* 聊天内容 */} -
- 聊天内容区域 -
- • 消息气泡显示 -
- • 支持文本、图片、文件 -
- • 消息时间显示 -
• 在线状态显示 -
-
- - {/* 输入区域 */} -
- 输入区域 -
-
- - {/* 右侧个人资料 */} -
-
- 右侧个人资料 -
-
• 基本信息
-
• 联系信息
-
• 标签
-
• 个人简介
-
• 操作按钮
-
-
-
-
- - {/* 使用说明 */} - - - 1. 开始聊天: - 在左侧联系人列表中选择一个联系人,点击进入聊天界面。 - - - 2. 发送消息: - 在聊天窗口底部的输入框中输入消息,按Enter键或点击发送按钮发送消息。 - - - 3. 查看资料: - 聊天时右侧会显示联系人的详细资料,包括基本信息、联系方式等。 - - - 4. 搜索功能: - 使用顶部搜索栏可以快速搜索联系人或群组。 - - - 5. 切换标签: - 在左侧可以切换聊天、联系人、群组等不同标签页。 - - - - {/* 技术特点 */} - - -
- • React 18 + TypeScript: - 现代化的前端开发技术栈 -
-
- • Ant Design: - 企业级UI组件库,提供丰富的组件 -
-
- • SCSS Modules: - 样式隔离和模块化,避免样式冲突 -
-
- • 响应式设计: - 支持不同屏幕尺寸,移动端友好 -
-
- • 组件化架构: - 功能模块独立成组件,便于维护和扩展 -
-
-
- - {/* 操作按钮 */} - - - - - - - -
-
- ); -}; - -export default CkboxTestPage; From fe5e7b16333d41ffbb29d6ae25adca15e3acf4e0 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, 8 Sep 2025 18:07:12 +0800 Subject: [PATCH 2/6] =?UTF-8?q?refactor(wechat):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E8=81=8A=E5=A4=A9=E7=95=8C=E9=9D=A2=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E7=BB=93=E6=9E=84=EF=BC=8C=E5=B0=86=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=A7=BB=E5=8A=A8=E5=88=B0weChat=E5=AD=90?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构微信聊天界面的组件结构,将原有分散的组件文件整理到weChat子目录中,包括消息列表、联系人列表、垂直用户列表等组件。同时优化了样式文件和类型定义,保持代码结构清晰。修改路由配置以支持新的目录结构。 --- Cunkebao/src/pages/pc/ckbox/index.tsx | 105 +----- .../pc/ckbox/monitoring/index.module.scss | 153 +++++++++ .../src/pages/pc/ckbox/monitoring/index.tsx | 186 ++++++++++ Cunkebao/src/pages/pc/ckbox/weChat/api.ts | 246 +++++++++++++ .../ChatWindow/ChatWindow.module.scss | 0 .../MessageEnter/MessageEnter.module.scss | 0 .../components/MessageEnter/index.tsx | 0 .../MessageRecord/MessageRecord.module.scss | 0 .../components/MessageRecord/index.tsx | 0 .../components/ProfileCard/Person.module.scss | 0 .../components/ProfileCard/index.tsx | 0 .../components/ChatWindow/demo.tsx | 0 .../components/ChatWindow/index.tsx | 0 .../components/NavCommon/index.module.scss | 258 ++++++++++++++ .../weChat/components/NavCommon/index.tsx | 120 +++++++ .../MessageList/MessageList.module.scss | 0 .../components/SidebarMenu/MessageList/api.ts | 0 .../SidebarMenu/MessageList/data.ts | 0 .../SidebarMenu/MessageList/index.tsx | 0 .../SidebarMenu/SidebarMenu.module.scss | 0 .../WechatFriends/WechatFriends.module.scss | 0 .../SidebarMenu/WechatFriends/index.tsx | 0 .../components/SidebarMenu/index.tsx | 0 .../components/Skeleton/index.module.scss | 0 .../components/Skeleton/index.tsx | 0 .../VerticalUserList.module.scss | 0 .../components/VerticalUserList/index.tsx | 0 Cunkebao/src/pages/pc/ckbox/weChat/data.ts | 323 ++++++++++++++++++ .../pages/pc/ckbox/weChat/index.module.scss | 198 +++++++++++ Cunkebao/src/pages/pc/ckbox/weChat/index.tsx | 106 ++++++ Cunkebao/src/pages/pc/ckbox/weChat/main.ts | 307 +++++++++++++++++ Cunkebao/src/router/module/ckbox.tsx | 12 +- 32 files changed, 1912 insertions(+), 102 deletions(-) create mode 100644 Cunkebao/src/pages/pc/ckbox/monitoring/index.module.scss create mode 100644 Cunkebao/src/pages/pc/ckbox/monitoring/index.tsx create mode 100644 Cunkebao/src/pages/pc/ckbox/weChat/api.ts rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/ChatWindow/ChatWindow.module.scss (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/ChatWindow/components/MessageEnter/index.tsx (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/ChatWindow/components/MessageRecord/MessageRecord.module.scss (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/ChatWindow/components/MessageRecord/index.tsx (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/ChatWindow/components/ProfileCard/Person.module.scss (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/ChatWindow/components/ProfileCard/index.tsx (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/ChatWindow/demo.tsx (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/ChatWindow/index.tsx (100%) create mode 100644 Cunkebao/src/pages/pc/ckbox/weChat/components/NavCommon/index.module.scss create mode 100644 Cunkebao/src/pages/pc/ckbox/weChat/components/NavCommon/index.tsx rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/SidebarMenu/MessageList/MessageList.module.scss (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/SidebarMenu/MessageList/api.ts (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/SidebarMenu/MessageList/data.ts (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/SidebarMenu/MessageList/index.tsx (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/SidebarMenu/SidebarMenu.module.scss (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/SidebarMenu/WechatFriends/WechatFriends.module.scss (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/SidebarMenu/WechatFriends/index.tsx (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/SidebarMenu/index.tsx (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/Skeleton/index.module.scss (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/Skeleton/index.tsx (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/VerticalUserList/VerticalUserList.module.scss (100%) rename Cunkebao/src/pages/pc/ckbox/{ => weChat}/components/VerticalUserList/index.tsx (100%) create mode 100644 Cunkebao/src/pages/pc/ckbox/weChat/data.ts create mode 100644 Cunkebao/src/pages/pc/ckbox/weChat/index.module.scss create mode 100644 Cunkebao/src/pages/pc/ckbox/weChat/index.tsx create mode 100644 Cunkebao/src/pages/pc/ckbox/weChat/main.ts diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx index 2129769c..5eebff47 100644 --- a/Cunkebao/src/pages/pc/ckbox/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/index.tsx @@ -1,105 +1,14 @@ -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 ChatWindow from "./components/ChatWindow/index"; -import SidebarMenu from "./components/SidebarMenu/index"; -import VerticalUserList from "./components/VerticalUserList"; -import PageSkeleton from "./components/Skeleton"; +import React from "react"; +import { Layout } from "antd"; +import { Outlet } from "react-router-dom"; import NavCommon from "./components/NavCommon"; import styles from "./index.module.scss"; -import { addChatSession } from "@/store/module/ckchat/ckchat"; -const { Content, Sider } = Layout; -import { chatInitAPIdata, initSocket } from "./main"; -import { useWeChatStore } from "@/store/module/weChat/weChat"; - -import { KfUserListData } from "@/pages/pc/ckbox/data"; - const CkboxPage: React.FC = () => { - // 不要在组件初始化时获取sendCommand,而是在需要时动态获取 - const [loading, setLoading] = useState(false); - const currentContract = useWeChatStore(state => state.currentContract); - useEffect(() => { - // 方法一:使用 Promise 链式调用处理异步函数 - setLoading(true); - chatInitAPIdata() - .then(response => { - const data = response as { - contractList: any[]; - groupList: any[]; - kfUserList: KfUserListData[]; - newContractList: { groupName: string; contacts: any[] }[]; - }; - const { contractList } = data; - - //找出已经在聊天的 - const isChatList = contractList.filter( - v => (v?.config && v.config?.chat) || false, - ); - isChatList.forEach(v => { - addChatSession(v); - }); - - // 数据加载完成后初始化WebSocket连接 - initSocket(); - }) - .catch(error => { - console.error("获取联系人列表失败:", error); - }) - .finally(() => { - setLoading(false); - }); - }, []); - return ( - - - - - {/* 垂直侧边栏 */} - - - - - - {/* 左侧联系人边栏 */} - - - - - {/* 主内容区 */} - - {currentContract ? ( -
- {/*
- - - - - -
*/} - -
- ) : ( -
-
- -

欢迎使用触客宝

-

选择一个联系人开始聊天

-
-
- )} -
-
-
-
+ + + + ); }; diff --git a/Cunkebao/src/pages/pc/ckbox/monitoring/index.module.scss b/Cunkebao/src/pages/pc/ckbox/monitoring/index.module.scss new file mode 100644 index 00000000..07a906b5 --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/monitoring/index.module.scss @@ -0,0 +1,153 @@ +.monitoring { + padding: 24px; + background-color: #f5f5f5; + min-height: 100vh; + + .header { + margin-bottom: 24px; + + h2 { + margin: 0 0 8px 0; + color: #262626; + font-size: 24px; + font-weight: 600; + } + + p { + margin: 0; + color: #8c8c8c; + font-size: 14px; + } + } + + .statsRow { + margin-bottom: 24px; + + .statCard { + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + transition: all 0.3s ease; + + &:hover { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); + transform: translateY(-2px); + } + } + } + + .progressRow { + margin-bottom: 24px; + + .progressCard, + .metricsCard { + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + height: 280px; + + .ant-card-body { + height: calc(100% - 57px); + display: flex; + flex-direction: column; + justify-content: space-between; + } + } + + .progressItem { + margin-bottom: 20px; + + &:last-child { + margin-bottom: 0; + } + + span { + display: block; + margin-bottom: 8px; + color: #595959; + font-size: 14px; + } + } + + .metricItem { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 0; + border-bottom: 1px solid #f0f0f0; + + &:last-child { + border-bottom: none; + } + + span { + color: #595959; + font-size: 14px; + } + + .metricValue { + display: flex; + align-items: center; + gap: 8px; + + span { + font-weight: 600; + font-size: 16px; + color: #262626; + } + } + } + } + + .tableRow { + .tableCard { + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + + .ant-table { + .ant-table-thead > tr > th { + background-color: #fafafa; + border-bottom: 1px solid #f0f0f0; + font-weight: 600; + } + + .ant-table-tbody > tr { + &:hover { + background-color: #f5f5f5; + } + } + } + } + } +} + +// 响应式设计 +@media (max-width: 768px) { + .monitoring { + padding: 16px; + + .header { + h2 { + font-size: 20px; + } + } + + .progressRow { + .progressCard, + .metricsCard { + height: auto; + margin-bottom: 16px; + } + } + } +} + +@media (max-width: 576px) { + .monitoring { + padding: 12px; + + .statsRow { + .statCard { + margin-bottom: 12px; + } + } + } +} \ No newline at end of file diff --git a/Cunkebao/src/pages/pc/ckbox/monitoring/index.tsx b/Cunkebao/src/pages/pc/ckbox/monitoring/index.tsx new file mode 100644 index 00000000..21efed53 --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/monitoring/index.tsx @@ -0,0 +1,186 @@ +import React from 'react'; +import { Card, Row, Col, Statistic, Progress, Table, Tag } from 'antd'; +import { + UserOutlined, + MessageOutlined, + TeamOutlined, + TrophyOutlined, + ArrowUpOutlined, + ArrowDownOutlined +} from '@ant-design/icons'; +import styles from './index.module.scss'; + +interface MonitoringProps { + // 预留接口属性 +} + +const Monitoring: React.FC = () => { + // 模拟数据 + const statsData = [ + { + title: '在线设备数', + value: 128, + prefix: , + suffix: '台', + valueStyle: { color: '#3f8600' }, + }, + { + title: '今日消息量', + value: 2456, + prefix: , + suffix: '条', + valueStyle: { color: '#1890ff' }, + }, + { + title: '活跃群组', + value: 89, + prefix: , + suffix: '个', + valueStyle: { color: '#722ed1' }, + }, + { + title: '成功率', + value: 98.5, + prefix: , + suffix: '%', + valueStyle: { color: '#f5222d' }, + }, + ]; + + const tableColumns = [ + { + title: '设备名称', + dataIndex: 'deviceName', + key: 'deviceName', + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + render: (status: string) => ( + {status} + ), + }, + { + title: '消息数', + dataIndex: 'messageCount', + key: 'messageCount', + }, + { + title: '最后活跃时间', + dataIndex: 'lastActive', + key: 'lastActive', + }, + ]; + + const tableData = [ + { + key: '1', + deviceName: '设备001', + status: '在线', + messageCount: 245, + lastActive: '2024-01-15 14:30:25', + }, + { + key: '2', + deviceName: '设备002', + status: '离线', + messageCount: 156, + lastActive: '2024-01-15 12:15:10', + }, + { + key: '3', + deviceName: '设备003', + status: '在线', + messageCount: 389, + lastActive: '2024-01-15 14:28:45', + }, + ]; + + return ( +
+
+

数据监控看板

+

实时监控系统运行状态和数据指标

+
+ + {/* 统计卡片 */} + + {statsData.map((stat, index) => ( + + + + + + ))} + + + {/* 进度指标 */} + + + +
+ CPU使用率 + +
+
+ 内存使用率 + +
+
+ 磁盘使用率 + +
+
+ + + +
+ 消息处理速度 +
+ 1,245 + +
+
+
+ 错误率 +
+ 0.2% + +
+
+
+ 响应时间 +
+ 125ms + +
+
+
+ +
+ + {/* 设备状态表格 */} + + + + + + + + + ); +}; + +export default Monitoring; \ No newline at end of file diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/api.ts b/Cunkebao/src/pages/pc/ckbox/weChat/api.ts new file mode 100644 index 00000000..98bc336a --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/weChat/api.ts @@ -0,0 +1,246 @@ +import request from "@/api/request2"; +import { + MessageData, + ChatHistoryResponse, + MessageType, + OnlineStatus, + MessageStatus, + FileUploadResponse, + EmojiData, + QuickReply, + ChatSettings, +} from "./data"; + +//读取聊天信息 +//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"); +} + +//更新配置 +export function updateConfig(params) { + return request("/api/WechatFriend/updateConfig", params, "PUT"); +} +//获取聊天记录-2 获取列表 +export function getChatMessages(params: { + wechatAccountId: number; + wechatFriendId?: number; + wechatChatroomId?: number; + From: number; + To: number; + Count: number; + olderData: boolean; +}) { + return request("/api/FriendMessage/SearchMessage", params, "GET"); +} +export function getChatroomMessages(params: { + wechatAccountId: number; + wechatFriendId?: number; + wechatChatroomId?: number; + From: number; + To: number; + Count: number; + olderData: boolean; +}) { + return request("/api/ChatroomMessage/SearchMessage", params, "GET"); +} + +//获取群列表 +export function getGroupList(params: { prevId: number; count: number }) { + return request( + "/api/wechatChatroom/listExcludeMembersByPage?", + params, + "GET", + ); +} + +//获取群成员 +export function getGroupMembers(params: { id: number }) { + return request( + "/api/WechatChatroom/listMembersByWechatChatroomId", + params, + "GET", + ); +} + +//触客宝登陆 +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 getControlTerminalList = params => { + return request("/api/wechataccount", params, "GET"); +}; + +// 获取聊天历史 +export const getChatHistory = ( + chatId: string, + page: number = 1, + pageSize: number = 50, +): Promise => { + return request(`/v1/chats/${chatId}/messages`, { page, pageSize }, "GET"); +}; + +// 发送消息 +export const sendMessage = ( + chatId: string, + content: string, + type: MessageType = MessageType.TEXT, +): Promise => { + return request(`/v1/chats/${chatId}/messages`, { content, type }, "POST"); +}; + +// 发送文件消息 +export const sendFileMessage = ( + chatId: string, + file: File, + type: MessageType, +): Promise => { + const formData = new FormData(); + formData.append("file", file); + formData.append("type", type); + return request(`/v1/chats/${chatId}/messages/file`, formData, "POST"); +}; + +// 标记消息为已读 +export const markMessageAsRead = (messageId: string): Promise => { + return request(`/v1/messages/${messageId}/read`, {}, "PUT"); +}; + +// 标记聊天为已读 +export const markChatAsRead = (chatId: string): Promise => { + return request(`/v1/chats/${chatId}/read`, {}, "PUT"); +}; + +// 添加群组成员 +export const addGroupMembers = ( + groupId: string, + memberIds: string[], +): Promise => { + return request(`/v1/groups/${groupId}/members`, { memberIds }, "POST"); +}; + +// 移除群组成员 +export const removeGroupMembers = ( + groupId: string, + memberIds: string[], +): Promise => { + return request(`/v1/groups/${groupId}/members`, { memberIds }, "DELETE"); +}; + +// 获取在线状态 +export const getOnlineStatus = (userId: string): Promise => { + return request(`/v1/users/${userId}/status`, {}, "GET"); +}; + +// 获取消息状态 +export const getMessageStatus = (messageId: string): Promise => { + return request(`/v1/messages/${messageId}/status`, {}, "GET"); +}; + +// 上传文件 +export const uploadFile = (file: File): Promise => { + const formData = new FormData(); + formData.append("file", file); + return request("/v1/upload", formData, "POST"); +}; + +// 获取表情包列表 +export const getEmojiList = (): Promise => { + return request("/v1/emojis", {}, "GET"); +}; + +// 获取快捷回复列表 +export const getQuickReplies = (): Promise => { + return request("/v1/quick-replies", {}, "GET"); +}; + +// 添加快捷回复 +export const addQuickReply = (data: { + content: string; + category: string; +}): Promise => { + return request("/v1/quick-replies", data, "POST"); +}; + +// 删除快捷回复 +export const deleteQuickReply = (id: string): Promise => { + return request(`/v1/quick-replies/${id}`, {}, "DELETE"); +}; + +// 获取聊天设置 +export const getChatSettings = (): Promise => { + return request("/v1/chat/settings", {}, "GET"); +}; + +// 更新聊天设置 +export const updateChatSettings = ( + settings: Partial, +): Promise => { + return request("/v1/chat/settings", settings, "PUT"); +}; + +// 删除聊天会话 +export const deleteChatSession = (chatId: string): Promise => { + return request(`/v1/chats/${chatId}`, {}, "DELETE"); +}; + +// 置顶聊天会话 +export const pinChatSession = (chatId: string): Promise => { + return request(`/v1/chats/${chatId}/pin`, {}, "PUT"); +}; + +// 取消置顶聊天会话 +export const unpinChatSession = (chatId: string): Promise => { + return request(`/v1/chats/${chatId}/unpin`, {}, "PUT"); +}; + +// 静音聊天会话 +export const muteChatSession = (chatId: string): Promise => { + return request(`/v1/chats/${chatId}/mute`, {}, "PUT"); +}; + +// 取消静音聊天会话 +export const unmuteChatSession = (chatId: string): Promise => { + return request(`/v1/chats/${chatId}/unmute`, {}, "PUT"); +}; + +// 转发消息 +export const forwardMessage = ( + messageId: string, + targetChatIds: string[], +): Promise => { + return request("/v1/messages/forward", { messageId, targetChatIds }, "POST"); +}; + +// 撤回消息 +export const recallMessage = (messageId: string): Promise => { + return request(`/v1/messages/${messageId}/recall`, {}, "PUT"); +}; diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/ChatWindow.module.scss similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss rename to Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/ChatWindow.module.scss diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss rename to Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx rename to Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageRecord/MessageRecord.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/MessageRecord.module.scss similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageRecord/MessageRecord.module.scss rename to Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/MessageRecord.module.scss diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageRecord/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageRecord/index.tsx rename to Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/Person.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/Person.module.scss similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/Person.module.scss rename to Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/Person.module.scss diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/index.tsx similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx rename to Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/index.tsx diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/demo.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/demo.tsx similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/ChatWindow/demo.tsx rename to Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/demo.tsx diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/index.tsx similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx rename to Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/index.tsx diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/components/NavCommon/index.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/components/NavCommon/index.module.scss new file mode 100644 index 00000000..75333cfb --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/weChat/components/NavCommon/index.module.scss @@ -0,0 +1,258 @@ +.header { + background: #fff; + padding: 0 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + justify-content: space-between; + height: 64px; + border-bottom: 1px solid #f0f0f0; +} + +.headerLeft { + display: flex; + align-items: center; + gap: 12px; +} + +.menuButton { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 6px; + transition: all 0.3s; + + &:hover { + background-color: #f5f5f5; + } + + .anticon { + font-size: 18px; + color: #666; + } +} + +.title { + font-size: 18px; + color: #333; + margin: 0; +} + +.headerRight { + display: flex; + align-items: center; +} + +.userInfo { + cursor: pointer; + padding: 8px 12px; + border-radius: 6px; + transition: all 0.3s; + + &:hover { + background-color: #f5f5f5; + } + .suanli { + font-size: 16px; + color: #666; + .suanliIcon { + font-size: 24px; + } + } +} + +.avatar { + border: 2px solid #f0f0f0; + transition: all 0.3s; + + &:hover { + border-color: #1890ff; + } +} + +.username { + font-size: 14px; + color: #333; + font-weight: 500; + margin-left: 8px; +} + +// 抽屉样式 +.drawer { + :global(.ant-drawer-header) { + background: #fafafa; + border-bottom: 1px solid #f0f0f0; + } + + :global(.ant-drawer-body) { + padding: 0; + } +} + +.drawerContent { + height: 100%; + display: flex; + flex-direction: column; + background: #f8f9fa; +} + +.drawerHeader { + padding: 20px; + background: #fff; + border-bottom: none; +} + +.logoSection { + display: flex; + align-items: center; + gap: 12px; +} + +.logoIcon { + width: 48px; + height: 48px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + color: white; +} + +.logoText { + display: flex; + flex-direction: column; +} + +.appName { + font-size: 18px; + font-weight: 600; + color: #333; + margin: 0; +} + +.appDesc { + font-size: 12px; + color: #999; + margin: 2px 0 0 0; +} + +.drawerBody { + flex: 1; + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.primaryButton { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 12px; + padding: 16px 20px; + display: flex; + align-items: center; + gap: 12px; + cursor: pointer; + transition: all 0.3s; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); + } + + .buttonIcon { + font-size: 20px; + } + + span { + font-size: 16px; + font-weight: 600; + color: white; + } +} + +.menuSection { + margin-top: 8px; +} + +.menuItem { + display: flex; + align-items: center; + padding: 16px 20px; + cursor: pointer; + transition: all 0.3s; + border-radius: 8px; + + &:hover { + background-color: #f0f0f0; + } + + span { + font-size: 16px; + color: #333; + font-weight: 500; + } +} + +.menuIcon { + font-size: 20px; + margin-right: 12px; + width: 20px; + text-align: center; +} + +.drawerFooter { + padding: 20px; + background: #fff; + border-top: 1px solid #f0f0f0; + .balanceSection { + display: flex; + justify-content: space-between; + align-items: center; + .balanceIcon { + color: #666; + .suanliIcon { + font-size: 20px; + } + } + + .balanceText { + color: #3d9c0d; + } + } +} + +.balanceLabel { + font-size: 12px; + color: #999; + margin: 0; +} + +.balanceAmount { + font-size: 18px; + font-weight: 600; + color: #52c41a; + margin: 2px 0 0 0; +} + +// 响应式设计 +@media (max-width: 768px) { + .header { + padding: 0 12px; + } + + .title { + font-size: 16px; + } + + .username { + display: none; + } + + .drawer { + width: 280px !important; + } +} diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/components/NavCommon/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/NavCommon/index.tsx new file mode 100644 index 00000000..c8892960 --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/weChat/components/NavCommon/index.tsx @@ -0,0 +1,120 @@ +import React, { useState } from "react"; +import { Layout, Drawer, Avatar, Dropdown, Space, Button } from "antd"; +import { + MenuOutlined, + UserOutlined, + LogoutOutlined, + SettingOutlined, +} from "@ant-design/icons"; +import type { MenuProps } from "antd"; +import { useCkChatStore } from "@/store/module/ckchat/ckchat"; +import { useNavigate } from "react-router-dom"; +import styles from "./index.module.scss"; + +const { Header } = Layout; + +interface NavCommonProps { + title?: string; + onMenuClick?: () => void; + drawerContent?: React.ReactNode; +} + +const NavCommon: React.FC = ({ + title = "触客宝", + onMenuClick, + drawerContent, +}) => { + const [drawerVisible, setDrawerVisible] = useState(false); + + const { userInfo } = useCkChatStore(); + + // 处理菜单图标点击 + const handleMenuClick = () => { + setDrawerVisible(true); + onMenuClick?.(); + }; + + // 处理抽屉关闭 + const handleDrawerClose = () => { + setDrawerVisible(false); + }; + + // 默认抽屉内容 + const defaultDrawerContent = ( +
+
+
+
+
+
触客宝
+
AI智能营销系统
+
+
+
+
+
+
🔒
+ AI智能客服 +
+
+
+
📊
+ 功能中心 +
+
+
+
+
+
+ 算力余额 +
+
9307.423
+
+
+
+ ); + + return ( + <> +
+
+
+ +
+ + + + 9307.423 + + } + src={userInfo?.account?.avatar} + className={styles.avatar} + /> + +
+
+ + + {drawerContent || defaultDrawerContent} + + + ); +}; + +export default NavCommon; diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/MessageList.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/MessageList.module.scss similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/MessageList.module.scss rename to Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/MessageList.module.scss diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/api.ts b/Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/api.ts similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/api.ts rename to Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/api.ts diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/data.ts b/Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/data.ts similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/data.ts rename to Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/data.ts diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx rename to Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/SidebarMenu.module.scss similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss rename to Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/SidebarMenu.module.scss diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/WechatFriends.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/WechatFriends.module.scss similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/WechatFriends.module.scss rename to Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/WechatFriends.module.scss diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/index.tsx similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx rename to Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/index.tsx diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/index.tsx similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx rename to Cunkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/index.tsx diff --git a/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/components/Skeleton/index.module.scss similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.module.scss rename to Cunkebao/src/pages/pc/ckbox/weChat/components/Skeleton/index.module.scss diff --git a/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/Skeleton/index.tsx similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.tsx rename to Cunkebao/src/pages/pc/ckbox/weChat/components/Skeleton/index.tsx diff --git a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/VerticalUserList.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/components/VerticalUserList/VerticalUserList.module.scss similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/VerticalUserList.module.scss rename to Cunkebao/src/pages/pc/ckbox/weChat/components/VerticalUserList/VerticalUserList.module.scss diff --git a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/VerticalUserList/index.tsx similarity index 100% rename from Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx rename to Cunkebao/src/pages/pc/ckbox/weChat/components/VerticalUserList/index.tsx diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/data.ts b/Cunkebao/src/pages/pc/ckbox/weChat/data.ts new file mode 100644 index 00000000..0444a312 --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/weChat/data.ts @@ -0,0 +1,323 @@ +// 消息列表数据接口 - 支持weChatGroup和contracts两种数据类型 +export interface MessageListData { + serverId: number | string; // 服务器ID作为主键 + id?: number; // 接口数据的原始ID字段 + + // 数据类型标识 + dataType: "weChatGroup" | "contracts"; // 数据类型:微信群组或联系人 + + // 通用字段(两种类型都有的字段) + wechatAccountId: number; // 微信账号ID + tenantId: number; // 租户ID + accountId: number; // 账号ID + nickname: string; // 昵称 + avatar?: string; // 头像 + groupId: number; // 分组ID + config?: { + chat: boolean; + }; // 配置信息 + labels?: string[]; // 标签列表 + unreadCount: number; // 未读消息数 + + // 联系人特有字段(当dataType为'contracts'时使用) + wechatId?: string; // 微信ID + alias?: string; // 别名 + conRemark?: string; // 备注 + quanPin?: string; // 全拼 + gender?: number; // 性别 + region?: string; // 地区 + addFrom?: number; // 添加来源 + phone?: string; // 电话 + signature?: string; // 签名 + extendFields?: any; // 扩展字段 + city?: string; // 城市 + lastUpdateTime?: string; // 最后更新时间 + isPassed?: boolean; // 是否通过 + thirdParty?: any; // 第三方 + additionalPicture?: string; // 附加图片 + desc?: string; // 描述 + lastMessageTime?: number; // 最后消息时间 + duplicate?: boolean; // 是否重复 + + // 微信群组特有字段(当dataType为'weChatGroup'时使用) + chatroomId?: string; // 群聊ID + chatroomOwner?: string; // 群主 + chatroomAvatar?: string; // 群头像 + notice?: string; // 群公告 + selfDisplyName?: string; // 自己在群里的显示名称 + + [key: string]: any; // 兼容其他字段 +} + +//联系人标签分组 +export interface ContactGroupByLabel { + id: number; + accountId?: number; + groupName: string; + tenantId?: number; + count: number; + [key: string]: any; +} +//终端用户数据接口 +export interface KfUserListData { + id: number; + tenantId: number; + wechatId: string; + nickname: string; + alias: string; + avatar: string; + gender: number; + region: string; + signature: string; + bindQQ: string; + bindEmail: string; + bindMobile: string; + createTime: string; + currentDeviceId: number; + isDeleted: boolean; + deleteTime: string; + groupId: number; + memo: string; + wechatVersion: string; + labels: string[]; + lastUpdateTime: string; + isOnline?: boolean; + [key: string]: any; +} + +// 账户信息接口 +export interface CkAccount { + id: number; + realName: string; + nickname: string | null; + memo: string | null; + avatar: string; + userName: string; + secret: string; + accountType: number; + departmentId: number; + useGoogleSecretKey: boolean; + hasVerifyGoogleSecret: boolean; +} + +//群聊数据接口 +export interface weChatGroup { + id?: number; + wechatAccountId: number; + tenantId: number; + accountId: number; + chatroomId: string; + chatroomOwner: string; + conRemark: string; + nickname: string; + chatroomAvatar: string; + groupId: number; + config?: { + chat: boolean; + }; + labels?: string[]; + unreadCount: number; + notice: string; + selfDisplyName: string; + wechatChatroomId: number; + serverId?: number; + [key: string]: any; +} + +// 联系人数据接口 +export interface ContractData { + id?: number; + serverId?: 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?: { + chat: boolean; + }; + lastMessageTime: number; + unreadCount: number; + duplicate: boolean; + [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; + sender?: { + chatroomNickname: string; + isAdmin: boolean; + isDeleted: boolean; + nickname: string; + ownerWechatId: string; + wechatId: string; + [key: string]: any; + }; + [key: string]: any; +} + +/** + * 微信好友基本信息接口 + * 包含主要字段和兼容性字段 + */ +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; +} + +// 消息类型枚举 +export enum MessageType { + TEXT = "text", + IMAGE = "image", + VOICE = "voice", + VIDEO = "video", + FILE = "file", + LOCATION = "location", +} + +// 消息数据接口 +export interface MessageData { + id: string; + senderId: string; + senderName: string; + content: string; + type: MessageType; + timestamp: string; + isRead: boolean; + replyTo?: string; + forwardFrom?: string; +} + +// 聊天会话类型 +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; +} + +// 聊天历史响应接口 +export interface ChatHistoryResponse { + messages: MessageData[]; + hasMore: boolean; + total: number; +} + +// 发送消息请求接口 +export interface SendMessageRequest { + chatId: string; + content: string; + type: MessageType; + replyTo?: string; +} + +// 搜索联系人请求接口 +export interface SearchContactRequest { + keyword: string; + limit?: number; +} + +// 在线状态接口 +export interface OnlineStatus { + userId: string; + online: boolean; + lastSeen: string; +} + +// 消息状态接口 +export interface MessageStatus { + messageId: string; + status: "sending" | "sent" | "delivered" | "read" | "failed"; + timestamp: string; +} + +// 文件上传响应接口 +export interface FileUploadResponse { + url: string; + filename: string; + size: number; + type: string; +} + +// 表情包接口 +export interface EmojiData { + id: string; + name: string; + url: string; + category: string; +} + +// 快捷回复接口 +export interface QuickReply { + id: string; + content: string; + category: string; + useCount: number; +} + +// 聊天设置接口 +export interface ChatSettings { + autoReply: boolean; + autoReplyMessage: string; + notification: boolean; + sound: boolean; + theme: "light" | "dark"; + fontSize: "small" | "medium" | "large"; +} diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/index.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/index.module.scss new file mode 100644 index 00000000..9b37437e --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/weChat/index.module.scss @@ -0,0 +1,198 @@ +.ckboxLayout { + height: 100vh; + background: #fff; + display: flex; + flex-direction: column; + + .header { + background: #1890ff; + color: #fff; + height: 64px; + line-height: 64px; + padding: 0 24px; + font-size: 18px; + font-weight: bold; + } + + .verticalSider { + background: #2e2e2e; + border-right: 1px solid #f0f0f0; + overflow: hidden; + } + + .sider { + background: #fff; + border-right: 1px solid #f0f0f0; + overflow: auto; + } + + .sidebar { + height: 100%; + 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 16px; + margin: 0; + + &:hover { + color: #1890ff; + } + + &.ant-tabs-tab-active { + .ant-tabs-tab-btn { + color: #1890ff; + } + } + } + + :global(.ant-tabs-ink-bar) { + background: #1890ff; + } + } + } + + .emptyState { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 200px; + color: #8c8c8c; + + p { + margin-top: 16px; + font-size: 14px; + } + } + } + + .mainContent { + background: #f5f5f5; + display: flex; + flex-direction: column; + overflow: auto; + + .chatContainer { + height: 100%; + display: flex; + flex-direction: column; + + .chatToolbar { + background: #fff; + border-bottom: 1px solid #f0f0f0; + padding: 8px 16px; + display: flex; + justify-content: flex-end; + align-items: center; + min-height: 48px; + } + } + + .welcomeScreen { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + background: #fff; + + .welcomeContent { + text-align: center; + color: #8c8c8c; + + h2 { + margin: 24px 0 12px 0; + color: #262626; + font-size: 24px; + font-weight: 600; + } + + p { + font-size: 16px; + margin: 0; + } + } + } + } +} + +// 响应式设计 +@media (max-width: 768px) { + .ckboxLayout { + .sidebar { + .searchBar { + padding: 12px; + } + + .tabs { + :global(.ant-tabs-nav) { + padding: 0 12px; + + :global(.ant-tabs-tab) { + padding: 10px 12px; + } + } + } + } + + .mainContent { + .chatContainer { + .chatToolbar { + padding: 6px 12px; + min-height: 40px; + } + } + + .welcomeScreen { + .welcomeContent { + h2 { + font-size: 20px; + } + + p { + font-size: 14px; + } + } + } + } + } +} diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/index.tsx new file mode 100644 index 00000000..2129769c --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/weChat/index.tsx @@ -0,0 +1,106 @@ +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 ChatWindow from "./components/ChatWindow/index"; +import SidebarMenu from "./components/SidebarMenu/index"; +import VerticalUserList from "./components/VerticalUserList"; +import PageSkeleton from "./components/Skeleton"; +import NavCommon from "./components/NavCommon"; +import styles from "./index.module.scss"; +import { addChatSession } from "@/store/module/ckchat/ckchat"; +const { Content, Sider } = Layout; +import { chatInitAPIdata, initSocket } from "./main"; +import { useWeChatStore } from "@/store/module/weChat/weChat"; + +import { KfUserListData } from "@/pages/pc/ckbox/data"; + +const CkboxPage: React.FC = () => { + // 不要在组件初始化时获取sendCommand,而是在需要时动态获取 + const [loading, setLoading] = useState(false); + const currentContract = useWeChatStore(state => state.currentContract); + useEffect(() => { + // 方法一:使用 Promise 链式调用处理异步函数 + setLoading(true); + chatInitAPIdata() + .then(response => { + const data = response as { + contractList: any[]; + groupList: any[]; + kfUserList: KfUserListData[]; + newContractList: { groupName: string; contacts: any[] }[]; + }; + const { contractList } = data; + + //找出已经在聊天的 + const isChatList = contractList.filter( + v => (v?.config && v.config?.chat) || false, + ); + isChatList.forEach(v => { + addChatSession(v); + }); + + // 数据加载完成后初始化WebSocket连接 + initSocket(); + }) + .catch(error => { + console.error("获取联系人列表失败:", error); + }) + .finally(() => { + setLoading(false); + }); + }, []); + + return ( + + + + + {/* 垂直侧边栏 */} + + + + + + {/* 左侧联系人边栏 */} + + + + + {/* 主内容区 */} + + {currentContract ? ( +
+ {/*
+ + + + + +
*/} + +
+ ) : ( +
+
+ +

欢迎使用触客宝

+

选择一个联系人开始聊天

+
+
+ )} +
+
+
+
+ ); +}; + +export default CkboxPage; diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/main.ts b/Cunkebao/src/pages/pc/ckbox/weChat/main.ts new file mode 100644 index 00000000..19f6cf62 --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/weChat/main.ts @@ -0,0 +1,307 @@ +import { + asyncKfUserList, + asyncContractList, + asyncChatSessions, + asyncWeChatGroup, + asyncCountLables, + useCkChatStore, +} from "@/store/module/ckchat/ckchat"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; + +import { + loginWithToken, + getControlTerminalList, + getContactList, + getGroupList, +} from "./api"; + +import { useUserStore } from "@/store/module/user"; + +import { + KfUserListData, + ContractData, + weChatGroup, +} from "@/pages/pc/ckbox/data"; + +import { WechatGroup } from "./api"; +const { login2 } = useUserStore.getState(); +//获取触客宝基础信息 +export const chatInitAPIdata = async () => { + try { + //获取联系人列表 + const contractList = await getAllContactList(); + + //获取联系人列表 + asyncContractList(contractList); + + //获取群列表 + const groupList = await getAllGroupList(); + + await asyncWeChatGroup(groupList); + + // 提取不重复的wechatAccountId组 + const uniqueWechatAccountIds: number[] = getUniqueWechatAccountIds( + contractList, + groupList, + ); + + //获取控制终端列表 + const kfUserList: KfUserListData[] = + await getControlTerminalListByWechatAccountIds(uniqueWechatAccountIds); + + //获取用户列表 + await asyncKfUserList(kfUserList); + + //获取标签列表 + const countLables = await getCountLables(); + await asyncCountLables(countLables); + + //获取消息会话列表并按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, + groupList, + kfUserList, + }; + } catch (error) { + console.error("获取联系人列表失败:", error); + return []; + } +}; +//发起soket连接 +export const initSocket = () => { + // 检查WebSocket是否已经连接 + const { status } = useWebSocketStore.getState(); + + // 如果已经连接或正在连接,则不重复连接 + if (["connected", "connecting"].includes(status)) { + console.log("WebSocket已连接或正在连接,跳过重复连接", { status }); + return; + } + + // 从store获取token和accountId + const { token2 } = useUserStore.getState(); + const { getAccountId } = useCkChatStore.getState(); + const Token = token2; + const accountId = getAccountId(); + // 使用WebSocket store初始化连接 + const { connect } = useWebSocketStore.getState(); + + // 连接WebSocket + connect({ + accessToken: Token, + accountId: Number(accountId), + client: "kefu-client", + cmdType: "CmdSignIn", + seq: +new Date(), + }); +}; + +export const getCountLables = async () => { + const LablesRes = await Promise.all( + [1, 2].map(item => + WechatGroup({ + groupType: item, + }), + ), + ); + const [friend, group] = LablesRes; + const countLables = [ + ...[ + { + id: 0, + groupName: "默认群分组", + groupType: 2, + }, + ], + ...group, + ...friend, + ...[ + { + id: 0, + groupName: "未分组", + groupType: 1, + }, + ], + ]; + + return countLables; +}; +/** + * 根据标签组织联系人 + * @param contractList 联系人列表 + * @param countLables 标签列表 + * @returns 按标签分组的联系人 + */ + +//获取控制终端列表 +export const getControlTerminalListByWechatAccountIds = ( + WechatAccountIds: number[], +) => { + return Promise.all( + WechatAccountIds.map(id => getControlTerminalList({ id: id })), + ); +}; +// 递归获取所有联系人列表 +export const getAllContactList = async () => { + try { + let allContacts = []; + let prevId = 0; + const count = 1000; + let hasMore = true; + + while (hasMore) { + const contractList = await getContactList({ + prevId, + count, + }); + + if ( + !contractList || + !Array.isArray(contractList) || + contractList.length === 0 + ) { + hasMore = false; + break; + } + + allContacts = [...allContacts, ...contractList]; + + // 如果返回的数据少于请求的数量,说明已经没有更多数据了 + if (contractList.length < count) { + hasMore = false; + } else { + // 获取最后一条数据的id作为下一次请求的prevId + const lastContact = contractList[contractList.length - 1]; + prevId = lastContact.id; + } + } + return allContacts; + } catch (error) { + console.error("获取所有联系人列表失败:", error); + return []; + } +}; + +// 提取不重复的wechatAccountId组 +export const getUniqueWechatAccountIds = ( + contacts: ContractData[], + groupList: weChatGroup[], +) => { + 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); + } + }); + + // 遍历联系人列表,将每个wechatAccountId添加到Set中 + groupList.forEach(group => { + if (group && group.wechatAccountId) { + uniqueAccountIdsSet.add(group.wechatAccountId); + } + }); + + // 将Set转换为数组并返回 + return Array.from(uniqueAccountIdsSet); +}; +// 递归获取所有群列表 +export const getAllGroupList = async () => { + try { + let allContacts = []; + let prevId = 0; + const count = 1000; + let hasMore = true; + + while (hasMore) { + const contractList = await getGroupList({ + prevId, + count, + }); + + if ( + !contractList || + !Array.isArray(contractList) || + contractList.length === 0 + ) { + hasMore = false; + break; + } + + allContacts = [...allContacts, ...contractList]; + + // 如果返回的数据少于请求的数量,说明已经没有更多数据了 + if (contractList.length < count) { + hasMore = false; + } else { + // 获取最后一条数据的id作为下一次请求的prevId + const lastContact = contractList[contractList.length - 1]; + prevId = lastContact.id; + } + } + + return allContacts; + } catch (error) { + console.error("获取所有群列表失败:", error); + return []; + } +}; + +//获取token +const getToken = () => { + return new Promise((resolve, reject) => { + const params = { + grant_type: "password", + password: "kr123456", + username: "kr_xf3", + // username: "karuo", + // password: "zhiqun1984", + }; + loginWithToken(params) + .then(res => { + login2(res.access_token); + resolve(res.access_token); + }) + .catch(err => { + reject(err); + }); + }); +}; diff --git a/Cunkebao/src/router/module/ckbox.tsx b/Cunkebao/src/router/module/ckbox.tsx index 6d64437f..7a5ae294 100644 --- a/Cunkebao/src/router/module/ckbox.tsx +++ b/Cunkebao/src/router/module/ckbox.tsx @@ -1,8 +1,6 @@ -import React from "react"; -import { lazy } from "react"; import type { RouteObject } from "react-router-dom"; - -const CkboxPage = lazy(() => import("@/pages/pc/ckbox")); +import CkboxPage from "@/pages/pc/ckbox"; +import WeChatPage from "@/pages/pc/ckbox/weChat"; const ckboxRoutes: (RouteObject & { auth?: boolean; requiredRole?: string })[] = [ @@ -11,6 +9,12 @@ const ckboxRoutes: (RouteObject & { auth?: boolean; requiredRole?: string })[] = element: , auth: true, requiredRole: "user", + children: [ + { + path: "weChat", + element: , + }, + ], }, ]; From 9fa647e40e0321b3f209519d09a6d9d261c77064 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, 8 Sep 2025 18:19:03 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat(ckbox):=20=E9=87=8D=E6=9E=84=E7=9B=91?= =?UTF-8?q?=E6=8E=A7=E9=A1=B5=E9=9D=A2=E4=B8=BA=E4=BB=AA=E8=A1=A8=E7=9B=98?= =?UTF-8?q?=E5=B9=B6=E6=B7=BB=E5=8A=A0=E5=9B=BE=E8=A1=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将监控页面重命名为仪表盘,保留原有功能组件的同时新增多个图表展示模块 使用echarts实现消息趋势、功能分布等数据可视化 优化布局和样式,提升用户体验 --- .../index.module.scss | 65 ++- .../src/pages/pc/ckbox/dashboard/index.tsx | 473 ++++++++++++++++++ Cunkebao/src/pages/pc/ckbox/index.tsx | 9 +- .../src/pages/pc/ckbox/monitoring/index.tsx | 186 ------- Cunkebao/src/router/module/ckbox.tsx | 9 + 5 files changed, 539 insertions(+), 203 deletions(-) rename Cunkebao/src/pages/pc/ckbox/{monitoring => dashboard}/index.module.scss (70%) create mode 100644 Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx delete mode 100644 Cunkebao/src/pages/pc/ckbox/monitoring/index.tsx diff --git a/Cunkebao/src/pages/pc/ckbox/monitoring/index.module.scss b/Cunkebao/src/pages/pc/ckbox/dashboard/index.module.scss similarity index 70% rename from Cunkebao/src/pages/pc/ckbox/monitoring/index.module.scss rename to Cunkebao/src/pages/pc/ckbox/dashboard/index.module.scss index 07a906b5..88a1b2e6 100644 --- a/Cunkebao/src/pages/pc/ckbox/monitoring/index.module.scss +++ b/Cunkebao/src/pages/pc/ckbox/dashboard/index.module.scss @@ -97,22 +97,59 @@ } } + .chartsRow { + margin-top: 24px; + } + + .chartCard { + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; + + &:hover { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); + transform: translateY(-2px); + } + + .ant-card-body { + padding: 16px; + } + + // ECharts容器样式 + .echarts-for-react { + width: 100% !important; + } +} + .tableRow { - .tableCard { - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + margin-top: 24px; + } + + .tableCard { + .ant-card-head { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; - .ant-table { - .ant-table-thead > tr > th { - background-color: #fafafa; - border-bottom: 1px solid #f0f0f0; - font-weight: 600; - } - - .ant-table-tbody > tr { - &:hover { - background-color: #f5f5f5; - } + .ant-card-head-title { + color: white; + } + } + } + + .tableCard { + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + + .ant-table { + .ant-table-thead > tr > th { + background-color: #fafafa; + border-bottom: 1px solid #f0f0f0; + font-weight: 600; + } + + .ant-table-tbody > tr { + &:hover { + background-color: #f5f5f5; } } } diff --git a/Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx b/Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx new file mode 100644 index 00000000..5d72bb4a --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx @@ -0,0 +1,473 @@ +import React from "react"; +import { Card, Row, Col, Statistic, Progress, Table, Tag } from "antd"; +import { + UserOutlined, + MessageOutlined, + TeamOutlined, + TrophyOutlined, + ArrowUpOutlined, + ArrowDownOutlined, +} from "@ant-design/icons"; +import * as echarts from "echarts"; +import ReactECharts from "echarts-for-react"; +import styles from "./index.module.scss"; + +interface DashboardProps { + // 预留接口属性 +} + +const Dashboard: React.FC = () => { + // 模拟数据 + const statsData = [ + { + title: "在线设备数", + value: 128, + prefix: , + suffix: "台", + valueStyle: { color: "#3f8600" }, + }, + { + title: "今日消息量", + value: 2456, + prefix: , + suffix: "条", + valueStyle: { color: "#1890ff" }, + }, + { + title: "活跃群组", + value: 89, + prefix: , + suffix: "个", + valueStyle: { color: "#722ed1" }, + }, + { + title: "成功率", + value: 98.5, + prefix: , + suffix: "%", + valueStyle: { color: "#f5222d" }, + }, + ]; + + const tableColumns = [ + { + title: "设备名称", + dataIndex: "deviceName", + key: "deviceName", + }, + { + title: "状态", + dataIndex: "status", + key: "status", + render: (status: string) => ( + {status} + ), + }, + { + title: "消息数", + dataIndex: "messageCount", + key: "messageCount", + }, + { + title: "最后活跃时间", + dataIndex: "lastActive", + key: "lastActive", + }, + ]; + + const tableData = [ + { + key: "1", + deviceName: "设备001", + status: "在线", + messageCount: 245, + lastActive: "2024-01-15 14:30:25", + }, + { + key: "2", + deviceName: "设备002", + status: "离线", + messageCount: 156, + lastActive: "2024-01-15 12:15:10", + }, + { + key: "3", + deviceName: "设备003", + status: "在线", + messageCount: 389, + lastActive: "2024-01-15 14:28:45", + }, + ]; + + // 图表数据 + const lineData = [ + { time: "00:00", value: 120 }, + { time: "02:00", value: 132 }, + { time: "04:00", value: 101 }, + { time: "06:00", value: 134 }, + { time: "08:00", value: 190 }, + { time: "10:00", value: 230 }, + { time: "12:00", value: 210 }, + { time: "14:00", value: 220 }, + { time: "16:00", value: 165 }, + { time: "18:00", value: 127 }, + { time: "20:00", value: 82 }, + { time: "22:00", value: 91 }, + ]; + + const columnData = [ + { type: "消息发送", value: 27 }, + { type: "消息接收", value: 25 }, + { type: "群组管理", value: 18 }, + { type: "设备监控", value: 15 }, + { type: "数据同步", value: 10 }, + { type: "其他", value: 5 }, + ]; + + const pieData = [ + { type: "在线设备", value: 128 }, + { type: "离线设备", value: 32 }, + { type: "维护中", value: 8 }, + ]; + + const areaData = [ + { time: "1月", value: 3000 }, + { time: "2月", value: 4000 }, + { time: "3月", value: 3500 }, + { time: "4月", value: 5000 }, + { time: "5月", value: 4900 }, + { time: "6月", value: 6000 }, + ]; + + // ECharts配置 + const lineOption = { + title: { + text: "24小时消息趋势", + left: "center", + textStyle: { + color: "#333", + fontSize: 16, + }, + }, + tooltip: { + trigger: "axis", + backgroundColor: "rgba(0,0,0,0.8)", + textStyle: { + color: "#fff", + }, + }, + grid: { + left: "3%", + right: "4%", + bottom: "3%", + containLabel: true, + }, + xAxis: { + type: "category", + data: lineData.map(item => item.time), + axisLine: { + lineStyle: { + color: "#ccc", + }, + }, + }, + yAxis: { + type: "value", + axisLine: { + lineStyle: { + color: "#ccc", + }, + }, + }, + series: [ + { + data: lineData.map(item => item.value), + type: "line", + smooth: true, + lineStyle: { + color: "#1890ff", + width: 3, + }, + itemStyle: { + color: "#1890ff", + }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: "rgba(24, 144, 255, 0.3)" }, + { offset: 1, color: "rgba(24, 144, 255, 0.1)" }, + ]), + }, + }, + ], + }; + + const columnOption = { + title: { + text: "功能使用分布", + left: "center", + textStyle: { + color: "#333", + fontSize: 16, + }, + }, + tooltip: { + trigger: "axis", + backgroundColor: "rgba(0,0,0,0.8)", + textStyle: { + color: "#fff", + }, + }, + grid: { + left: "3%", + right: "4%", + bottom: "3%", + containLabel: true, + }, + xAxis: { + type: "category", + data: columnData.map(item => item.type), + axisLabel: { + rotate: 45, + }, + axisLine: { + lineStyle: { + color: "#ccc", + }, + }, + }, + yAxis: { + type: "value", + axisLine: { + lineStyle: { + color: "#ccc", + }, + }, + }, + series: [ + { + data: columnData.map(item => item.value), + type: "bar", + itemStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: "#52c41a" }, + { offset: 1, color: "#389e0d" }, + ]), + }, + }, + ], + }; + + const pieOption = { + title: { + text: "设备状态分布", + left: "center", + textStyle: { + color: "#333", + fontSize: 16, + }, + }, + tooltip: { + trigger: "item", + backgroundColor: "rgba(0,0,0,0.8)", + textStyle: { + color: "#fff", + }, + }, + legend: { + orient: "vertical", + left: "left", + }, + series: [ + { + name: "设备状态", + type: "pie", + radius: "50%", + data: pieData.map(item => ({ name: item.type, value: item.value })), + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: "rgba(0, 0, 0, 0.5)", + }, + }, + itemStyle: { + borderRadius: 5, + borderColor: "#fff", + borderWidth: 2, + }, + }, + ], + }; + + const areaOption = { + title: { + text: "月度数据趋势", + left: "center", + textStyle: { + color: "#333", + fontSize: 16, + }, + }, + tooltip: { + trigger: "axis", + backgroundColor: "rgba(0,0,0,0.8)", + textStyle: { + color: "#fff", + }, + }, + grid: { + left: "3%", + right: "4%", + bottom: "3%", + containLabel: true, + }, + xAxis: { + type: "category", + data: areaData.map(item => item.time), + axisLine: { + lineStyle: { + color: "#ccc", + }, + }, + }, + yAxis: { + type: "value", + axisLine: { + lineStyle: { + color: "#ccc", + }, + }, + }, + series: [ + { + data: areaData.map(item => item.value), + type: "line", + smooth: true, + lineStyle: { + color: "#722ed1", + width: 3, + }, + itemStyle: { + color: "#722ed1", + }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: "rgba(114, 46, 209, 0.6)" }, + { offset: 1, color: "rgba(114, 46, 209, 0.1)" }, + ]), + }, + }, + ], + }; + + return ( +
+
+

数据监控看板

+

实时监控系统运行状态和数据指标

+
+ + {/* 统计卡片 */} + + {statsData.map((stat, index) => ( +
+ + + + + ))} + + + {/* 进度指标 */} + + + +
+ CPU使用率 + +
+
+ 内存使用率 + +
+
+ 磁盘使用率 + +
+
+ + + +
+ 消息处理速度 +
+ 1,245 + +
+
+
+ 错误率 +
+ 0.2% + +
+
+
+ 响应时间 +
+ 125ms + +
+
+
+ + + + {/* 图表区域 - 四栏布局 */} + + + + + + + + + + + + + + + + + + + + + + + + {/* 设备状态表格 */} + + + +
+ + + + + ); +}; + +export default Dashboard; diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx index 5eebff47..2ff2022a 100644 --- a/Cunkebao/src/pages/pc/ckbox/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/index.tsx @@ -1,12 +1,15 @@ import React from "react"; -import { Layout } from "antd"; +import Layout from "@/components/Layout/Layout"; import { Outlet } from "react-router-dom"; import NavCommon from "./components/NavCommon"; import styles from "./index.module.scss"; const CkboxPage: React.FC = () => { return ( - - + + } + > ); diff --git a/Cunkebao/src/pages/pc/ckbox/monitoring/index.tsx b/Cunkebao/src/pages/pc/ckbox/monitoring/index.tsx deleted file mode 100644 index 21efed53..00000000 --- a/Cunkebao/src/pages/pc/ckbox/monitoring/index.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import React from 'react'; -import { Card, Row, Col, Statistic, Progress, Table, Tag } from 'antd'; -import { - UserOutlined, - MessageOutlined, - TeamOutlined, - TrophyOutlined, - ArrowUpOutlined, - ArrowDownOutlined -} from '@ant-design/icons'; -import styles from './index.module.scss'; - -interface MonitoringProps { - // 预留接口属性 -} - -const Monitoring: React.FC = () => { - // 模拟数据 - const statsData = [ - { - title: '在线设备数', - value: 128, - prefix: , - suffix: '台', - valueStyle: { color: '#3f8600' }, - }, - { - title: '今日消息量', - value: 2456, - prefix: , - suffix: '条', - valueStyle: { color: '#1890ff' }, - }, - { - title: '活跃群组', - value: 89, - prefix: , - suffix: '个', - valueStyle: { color: '#722ed1' }, - }, - { - title: '成功率', - value: 98.5, - prefix: , - suffix: '%', - valueStyle: { color: '#f5222d' }, - }, - ]; - - const tableColumns = [ - { - title: '设备名称', - dataIndex: 'deviceName', - key: 'deviceName', - }, - { - title: '状态', - dataIndex: 'status', - key: 'status', - render: (status: string) => ( - {status} - ), - }, - { - title: '消息数', - dataIndex: 'messageCount', - key: 'messageCount', - }, - { - title: '最后活跃时间', - dataIndex: 'lastActive', - key: 'lastActive', - }, - ]; - - const tableData = [ - { - key: '1', - deviceName: '设备001', - status: '在线', - messageCount: 245, - lastActive: '2024-01-15 14:30:25', - }, - { - key: '2', - deviceName: '设备002', - status: '离线', - messageCount: 156, - lastActive: '2024-01-15 12:15:10', - }, - { - key: '3', - deviceName: '设备003', - status: '在线', - messageCount: 389, - lastActive: '2024-01-15 14:28:45', - }, - ]; - - return ( -
-
-

数据监控看板

-

实时监控系统运行状态和数据指标

-
- - {/* 统计卡片 */} - - {statsData.map((stat, index) => ( -
- - - - - ))} - - - {/* 进度指标 */} - - - -
- CPU使用率 - -
-
- 内存使用率 - -
-
- 磁盘使用率 - -
-
- - - -
- 消息处理速度 -
- 1,245 - -
-
-
- 错误率 -
- 0.2% - -
-
-
- 响应时间 -
- 125ms - -
-
-
- - - - {/* 设备状态表格 */} - - - -
- - - - - ); -}; - -export default Monitoring; \ No newline at end of file diff --git a/Cunkebao/src/router/module/ckbox.tsx b/Cunkebao/src/router/module/ckbox.tsx index 7a5ae294..4ca69087 100644 --- a/Cunkebao/src/router/module/ckbox.tsx +++ b/Cunkebao/src/router/module/ckbox.tsx @@ -1,6 +1,7 @@ import type { RouteObject } from "react-router-dom"; import CkboxPage from "@/pages/pc/ckbox"; import WeChatPage from "@/pages/pc/ckbox/weChat"; +import Dashboard from "@/pages/pc/ckbox/dashboard"; const ckboxRoutes: (RouteObject & { auth?: boolean; requiredRole?: string })[] = [ @@ -10,6 +11,14 @@ const ckboxRoutes: (RouteObject & { auth?: boolean; requiredRole?: string })[] = auth: true, requiredRole: "user", children: [ + { + path: "", + element: , + }, + { + path: "dashboard", + element: , + }, { path: "weChat", element: , From 2b48dd87220fc8d9f782c57e6d4d8b86662bccdd 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, 8 Sep 2025 18:21:14 +0800 Subject: [PATCH 4/6] =?UTF-8?q?refactor(PlaceholderPage):=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E6=9C=AA=E4=BD=BF=E7=94=A8=E7=9A=84showFooter?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E5=92=8CMeauMobile=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit style(dashboard): 统一图表组件中的引号为双引号 --- Cunkebao/src/components/PlaceholderPage.tsx | 6 +----- Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Cunkebao/src/components/PlaceholderPage.tsx b/Cunkebao/src/components/PlaceholderPage.tsx index 4eec7f2f..69a8ddde 100644 --- a/Cunkebao/src/components/PlaceholderPage.tsx +++ b/Cunkebao/src/components/PlaceholderPage.tsx @@ -2,14 +2,12 @@ import React from "react"; import { NavBar, Button } from "antd-mobile"; import { PlusOutlined } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; -import MeauMobile from "@/components/MeauMobile/MeauMoible"; interface PlaceholderPageProps { title: string; showBack?: boolean; showAddButton?: boolean; addButtonText?: string; - showFooter?: boolean; } const PlaceholderPage: React.FC = ({ @@ -17,13 +15,12 @@ const PlaceholderPage: React.FC = ({ showBack = true, showAddButton = false, addButtonText = "新建", - showFooter = true, }) => { return ( window.history.back() : undefined} left={ @@ -43,7 +40,6 @@ const PlaceholderPage: React.FC = ({ } /> } - footer={showFooter ? : undefined} >

{title}页面

diff --git a/Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx b/Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx index 5d72bb4a..0e6ceacf 100644 --- a/Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx @@ -433,22 +433,22 @@ const Dashboard: React.FC = () => {
- + - + - + - + From eb073b04ac42e1f0e08626f2230df31a0fea55c5 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, 8 Sep 2025 18:52:06 +0800 Subject: [PATCH 5/6] =?UTF-8?q?refactor(ckbox):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=AF=BC=E8=88=AA=E7=BB=84=E4=BB=B6=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将导航菜单数据抽离到独立文件,简化路由配置 移除未使用的NavCommon组件属性,优化微信管理页面结构 --- Cunkebao/src/pages/mobile/mine/main/index.tsx | 2 +- .../ckbox/components/NavCommon/index.data.ts | 44 +++++++++++++++ .../pc/ckbox/components/NavCommon/index.tsx | 53 +++++++++++++------ Cunkebao/src/pages/pc/ckbox/weChat/index.tsx | 7 +-- Cunkebao/src/router/module/ckbox.tsx | 45 ++++++++-------- 5 files changed, 106 insertions(+), 45 deletions(-) create mode 100644 Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.data.ts diff --git a/Cunkebao/src/pages/mobile/mine/main/index.tsx b/Cunkebao/src/pages/mobile/mine/main/index.tsx index 45358e3f..66450792 100644 --- a/Cunkebao/src/pages/mobile/mine/main/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/main/index.tsx @@ -90,7 +90,7 @@ const Mine: React.FC = () => { description: "触客宝", icon: , count: 0, - path: "/mine/ckbox", + path: "/ckbox/weChat", bgColor: "#fff7e6", iconColor: "#fa8c16", }, diff --git a/Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.data.ts b/Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.data.ts new file mode 100644 index 00000000..f42e11af --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.data.ts @@ -0,0 +1,44 @@ +// 菜单项接口 +export interface MenuItem { + id: string; + title: string; + icon: string; + path?: string; +} + +// 菜单列表数据 +export const menuList: MenuItem[] = [ + { + id: "dashboard", + title: "数据面板", + icon: "📊", + path: "/ckbox/dashboard", + }, + { + id: "wechat", + title: "微信管理", + icon: "💬", + path: "/ckbox/weChat", + }, +]; + +// 抽屉菜单配置数据 +export const drawerMenuData = { + header: { + logoIcon: "✨", + appName: "触客宝", + appDesc: "AI智能营销系统", + }, + primaryButton: { + title: "AI智能客服", + icon: "🔒", + }, + footer: { + balanceIcon: "⚡", + balanceLabel: "算力余额", + balanceValue: "9307.423", + }, +}; + +// 导出默认配置 +export default drawerMenuData; diff --git a/Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx index c8892960..500099fa 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx @@ -9,6 +9,7 @@ import { import type { MenuProps } from "antd"; import { useCkChatStore } from "@/store/module/ckchat/ckchat"; import { useNavigate } from "react-router-dom"; +import { drawerMenuData, menuList } from "./index.data"; import styles from "./index.module.scss"; const { Header } = Layout; @@ -16,16 +17,14 @@ const { Header } = Layout; interface NavCommonProps { title?: string; onMenuClick?: () => void; - drawerContent?: React.ReactNode; } const NavCommon: React.FC = ({ title = "触客宝", onMenuClick, - drawerContent, }) => { const [drawerVisible, setDrawerVisible] = useState(false); - + const navigate = useNavigate(); const { userInfo } = useCkChatStore(); // 处理菜单图标点击 @@ -44,31 +43,55 @@ const NavCommon: React.FC = ({
-
+
+ {drawerMenuData.header.logoIcon} +
-
触客宝
-
AI智能营销系统
+
+ {drawerMenuData.header.appName} +
+
+ {drawerMenuData.header.appDesc} +
-
🔒
- AI智能客服 +
+ {drawerMenuData.primaryButton.icon} +
+ {drawerMenuData.primaryButton.title}
-
-
📊
- 功能中心 -
+ {menuList.map((item, index) => ( +
{ + if (item.path) { + navigate(item.path); + setDrawerVisible(false); + } + }} + > +
{item.icon}
+ {item.title} +
+ ))}
- 算力余额 + + {drawerMenuData.footer.balanceIcon} + + {drawerMenuData.footer.balanceLabel} +
+
+ {drawerMenuData.footer.balanceValue}
-
9307.423
@@ -111,7 +134,7 @@ const NavCommon: React.FC = ({ width={300} className={styles.drawer} > - {drawerContent || defaultDrawerContent} + {defaultDrawerContent} ); diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/index.tsx index 2129769c..2c6a8db6 100644 --- a/Cunkebao/src/pages/pc/ckbox/weChat/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/weChat/index.tsx @@ -1,12 +1,10 @@ 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 { Layout } from "antd"; +import { MessageOutlined } from "@ant-design/icons"; import ChatWindow from "./components/ChatWindow/index"; import SidebarMenu from "./components/SidebarMenu/index"; import VerticalUserList from "./components/VerticalUserList"; import PageSkeleton from "./components/Skeleton"; -import NavCommon from "./components/NavCommon"; import styles from "./index.module.scss"; import { addChatSession } from "@/store/module/ckchat/ckchat"; const { Content, Sider } = Layout; @@ -54,7 +52,6 @@ const CkboxPage: React.FC = () => { return ( - {/* 垂直侧边栏 */} diff --git a/Cunkebao/src/router/module/ckbox.tsx b/Cunkebao/src/router/module/ckbox.tsx index 4ca69087..1fbb8b61 100644 --- a/Cunkebao/src/router/module/ckbox.tsx +++ b/Cunkebao/src/router/module/ckbox.tsx @@ -1,30 +1,27 @@ -import type { RouteObject } from "react-router-dom"; import CkboxPage from "@/pages/pc/ckbox"; import WeChatPage from "@/pages/pc/ckbox/weChat"; import Dashboard from "@/pages/pc/ckbox/dashboard"; -const ckboxRoutes: (RouteObject & { auth?: boolean; requiredRole?: string })[] = - [ - { - path: "/ckbox", - element: , - auth: true, - requiredRole: "user", - children: [ - { - path: "", - element: , - }, - { - path: "dashboard", - element: , - }, - { - path: "weChat", - element: , - }, - ], - }, - ]; +const ckboxRoutes = [ + { + path: "/ckbox", + element: , + auth: true, + children: [ + { + path: "", + element: , + }, + { + path: "dashboard", + element: , + }, + { + path: "weChat", + element: , + }, + ], + }, +]; export default ckboxRoutes; From cb44735eecd98e467a47fb6bba2a5008e03af064 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, 9 Sep 2025 09:51:07 +0800 Subject: [PATCH 6/6] =?UTF-8?q?style:=20=E7=BB=9F=E4=B8=80=E5=9B=BE?= =?UTF-8?q?=E8=A1=A8=E5=8D=A1=E7=89=87=E4=B8=AD=E7=9A=84=E5=BC=95=E5=8F=B7?= =?UTF-8?q?=E4=B8=BA=E5=8D=95=E5=BC=95=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx b/Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx index 0e6ceacf..5d72bb4a 100644 --- a/Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx @@ -433,22 +433,22 @@ const Dashboard: React.FC = () => {
- + - + - + - +