diff --git a/Cunkebao/.gitignore b/Cunkebao/.gitignore index dcf55aed..a2bf4a07 100644 --- a/Cunkebao/.gitignore +++ b/Cunkebao/.gitignore @@ -3,4 +3,5 @@ dist/ build/ yarn.lock .env -.DS_Store \ No newline at end of file +.DS_Store +dist/* diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json index 2289b6a9..994665e2 100644 --- a/Cunkebao/dist/.vite/manifest.json +++ b/Cunkebao/dist/.vite/manifest.json @@ -1,50 +1,50 @@ { - "_charts-aNYyX7D2.js": { - "file": "assets/charts-aNYyX7D2.js", + "_charts-B449e2xS.js": { + "file": "assets/charts-B449e2xS.js", "name": "charts", "imports": [ - "_ui-DZwp85UP.js", - "_vendor-Bq99rrm8.js" + "_ui-DDu9FCjt.js", + "_vendor-0WYR1k4q.js" ] }, "_ui-D0C0OGrH.css": { "file": "assets/ui-D0C0OGrH.css", "src": "_ui-D0C0OGrH.css" }, - "_ui-DZwp85UP.js": { - "file": "assets/ui-DZwp85UP.js", + "_ui-DDu9FCjt.js": { + "file": "assets/ui-DDu9FCjt.js", "name": "ui", "imports": [ - "_vendor-Bq99rrm8.js" + "_vendor-0WYR1k4q.js" ], "css": [ "assets/ui-D0C0OGrH.css" ] }, - "_utils-Ft3ushmX.js": { - "file": "assets/utils-Ft3ushmX.js", + "_utils-DC06x9DY.js": { + "file": "assets/utils-DC06x9DY.js", "name": "utils", "imports": [ - "_vendor-Bq99rrm8.js" + "_vendor-0WYR1k4q.js" ] }, - "_vendor-Bq99rrm8.js": { - "file": "assets/vendor-Bq99rrm8.js", + "_vendor-0WYR1k4q.js": { + "file": "assets/vendor-0WYR1k4q.js", "name": "vendor" }, "index.html": { - "file": "assets/index-CCIZs36L.js", + "file": "assets/index-DzNmnMYg.js", "name": "index", "src": "index.html", "isEntry": true, "imports": [ - "_vendor-Bq99rrm8.js", - "_ui-DZwp85UP.js", - "_utils-Ft3ushmX.js", - "_charts-aNYyX7D2.js" + "_vendor-0WYR1k4q.js", + "_ui-DDu9FCjt.js", + "_utils-DC06x9DY.js", + "_charts-B449e2xS.js" ], "css": [ - "assets/index-DRrzDMi4.css" + "assets/index-QrS4Cvyc.css" ] } } \ No newline at end of file diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html index 9b257a9d..0eea1718 100644 --- a/Cunkebao/dist/index.html +++ b/Cunkebao/dist/index.html @@ -11,13 +11,13 @@ - - - - - + + + + + - +
diff --git a/Cunkebao/src/pages/login/Login.tsx b/Cunkebao/src/pages/login/Login.tsx index 972710b2..d1db021e 100644 --- a/Cunkebao/src/pages/login/Login.tsx +++ b/Cunkebao/src/pages/login/Login.tsx @@ -1,5 +1,4 @@ import React, { useState, useEffect } from "react"; -import { useCkChatStore } from "@/store/module/ckchat/ckchat"; import { Form, Input, Button, Toast, Checkbox } from "antd-mobile"; import { EyeInvisibleOutline, @@ -7,7 +6,6 @@ import { UserOutline, } from "antd-mobile-icons"; import { useUserStore } from "@/store/module/user"; -import { useWebSocketStore } from "@/store/module/websocket/websocket"; import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api"; import style from "./login.module.scss"; @@ -19,7 +17,6 @@ const Login: React.FC = () => { const [countdown, setCountdown] = useState(0); const [showPassword, setShowPassword] = useState(false); const [agreeToTerms, setAgreeToTerms] = useState(false); - const { setUserInfo } = useCkChatStore.getState(); const { login, login2 } = useUserStore(); // 倒计时效果 @@ -75,13 +72,9 @@ const Login: React.FC = () => { : loginWithCode(loginParams); response.then(res => { - const { member, kefuData, deviceTotal } = res; + const { member, deviceTotal } = res; // 清空WebSocket连接状态 - useWebSocketStore.getState().clearConnectionState(); login(res.token, member, deviceTotal); - const { self, token } = kefuData; - login2(token.access_token); - setUserInfo(self); }); }; diff --git a/Cunkebao/src/pages/mobile/mine/devices/index.tsx b/Cunkebao/src/pages/mobile/mine/devices/index.tsx index 62fe17f2..f84b30c7 100644 --- a/Cunkebao/src/pages/mobile/mine/devices/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/devices/index.tsx @@ -291,7 +291,10 @@ const Devices: React.FC = () => { {/* 主要内容区域:头像和详细信息 */} -
+
goDetail(device.id!)} + > {/* 头像 */}
{device.avatar ? ( @@ -339,12 +342,6 @@ const Devices: React.FC = () => {
- - {/* 箭头图标 */} - goDetail(device.id!)} - /> ))} diff --git a/Cunkebao/src/pages/mobile/mine/main/index.tsx b/Cunkebao/src/pages/mobile/mine/main/index.tsx index 66450792..d2e56cea 100644 --- a/Cunkebao/src/pages/mobile/mine/main/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/main/index.tsx @@ -84,16 +84,6 @@ const Mine: React.FC = () => { bgColor: "#fff7e6", iconColor: "#fa8c16", }, - { - id: "ckb", - title: "触客宝", - description: "触客宝", - icon: , - count: 0, - path: "/ckbox/weChat", - bgColor: "#fff7e6", - iconColor: "#fa8c16", - }, ]; // 加载统计数据 diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/api.ts b/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/api.ts index 6d4c4b83..3f3341ae 100644 --- a/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/api.ts +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/api.ts @@ -2,7 +2,7 @@ import request from "@/api/request"; import type { UserTagsResponse } from "./data"; export function getTrafficPoolDetail(wechatId: string) { - return request("/v1/wechats/getWechatInfo", { wechatId }, "GET"); + return request("/v1/traffic/pool/getUserInfo", { wechatId }, "GET"); } // 获取用户旅程记录 diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/data.ts b/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/data.ts index 43615992..f32ec073 100644 --- a/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/data.ts +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/data.ts @@ -1,39 +1,64 @@ +// 设备信息类型 +export interface DeviceInfo { + id: number; + memo: string; + imei: string; + brand: string; + alive: number; + address: string; +} + +// 来源信息类型 +export interface SourceInfo { + nickname: string; + avatar: string; + gender: number; + phone: string; + wechatId: string; + alias: string; + createTime: string; + friendId: number; + wechatAccountId: number; + lastMsgTime: string; + device: DeviceInfo; +} + +// 统计总计类型 +export interface TotalStats { + msg: number; + money: number; + isFriend: boolean; + percentage: string; +} + +// RMM评分类型 +export interface RmmScore { + r: number; + f: number; + m: number; +} + // 用户详情类型 export interface TrafficPoolUserDetail { id: number; + identifier: string; + wechatId: string; nickname: string; avatar: string; - wechatId: string; - status: number | string; - addTime: string; - lastInteraction: string; - deviceName?: string; - wechatAccountName?: string; - customerServiceName?: string; - poolNames?: string[]; - rfmScore?: { - recency: number; - frequency: number; - monetary: number; - segment?: string; - }; - totalSpent?: number; - interactionCount?: number; - conversionRate?: number; - tags?: string[]; - packages?: string[]; - interactions?: Array<{ - id: string; - type: string; - content: string; - timestamp: string; - value?: number; - }>; + gender: number; + phone: string; + alias: string; + lastMsgTime: string; + source: SourceInfo[]; + packages: any[]; + total: TotalStats; + rmm: RmmScore; } // 扩展的用户详情类型 export interface ExtendedUserDetail extends TrafficPoolUserDetail { - userInfo: { + // 保留原有的扩展字段用于向后兼容 + userInfo?: { nickname: string; avatar: string; wechatId: string; @@ -44,23 +69,23 @@ export interface ExtendedUserDetail extends TrafficPoolUserDetail { unknowFriend: number; }; }; - rfmScore: { + rfmScore?: { recency: number; frequency: number; monetary: number; totalScore: number; }; - trafficPools: { + trafficPools?: { currentPool: string; availablePools: string[]; }; - userTags: Array<{ + userTags?: Array<{ id: string; name: string; color: string; type: string; }>; - valueTags: Array<{ + valueTags?: Array<{ id: string; name: string; color: string; diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/index.module.scss b/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/index.module.scss index 6b896216..23fd44d2 100644 --- a/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/index.module.scss +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/index.module.scss @@ -1,9 +1,3 @@ -.container { - padding: 0; - background: #f5f5f5; - min-height: 100vh; -} - // 头部样式 .header { display: flex; @@ -123,7 +117,7 @@ // 内容区域 .content { - padding: 16px; + padding: 10px 10px 10px 16px; } .tabContent { diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/index.tsx b/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/index.tsx index 4c7ce51b..786669ed 100644 --- a/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/detail/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -import { useParams, useNavigate } from "react-router-dom"; +import { useParams } from "react-router-dom"; import { Card, Button, Avatar, Tag, List, SpinLoading } from "antd-mobile"; import { UserOutlined, @@ -22,9 +22,23 @@ import type { } from "./data"; import styles from "./index.module.scss"; +// RMM评分辅助函数 +const getRmmValueLevel = (totalScore: number): string => { + if (totalScore >= 12) return "高价值客户"; + if (totalScore >= 8) return "中等价值客户"; + if (totalScore >= 4) return "低价值客户"; + return "潜在客户"; +}; + +const getRmmColor = (totalScore: number): string => { + if (totalScore >= 12) return "danger"; + if (totalScore >= 8) return "warning"; + if (totalScore >= 4) return "primary"; + return "default"; +}; + const TrafficPoolDetail: React.FC = () => { const { wxid, userId } = useParams(); - const navigate = useNavigate(); const [loading, setLoading] = useState(true); const [user, setUser] = useState(null); const [activeTab, setActiveTab] = useState("basic"); @@ -46,43 +60,89 @@ const TrafficPoolDetail: React.FC = () => { setLoading(true); getTrafficPoolDetail(wxid as string) .then(res => { - // 将API数据转换为扩展的用户详情数据 + // 直接使用API返回的数据结构 const extendedUser: ExtendedUserDetail = { ...res, - // 添加userInfo属性 - userInfo: res.userInfo, - // 模拟RFM评分数据 + // 根据新数据结构构建userInfo + userInfo: { + nickname: res.nickname, + avatar: res.avatar, + wechatId: res.wechatId, + friendShip: { + totalFriend: res.source?.length || 0, + maleFriend: res.source?.filter(s => s.gender === 1).length || 0, + femaleFriend: res.source?.filter(s => s.gender === 2).length || 0, + unknowFriend: res.source?.filter(s => s.gender === 0).length || 0, + }, + }, + // 使用API返回的RMM数据 rfmScore: { - recency: 5, - frequency: 5, - monetary: 5, - totalScore: 15, + recency: res.rmm.r, + frequency: res.rmm.f, + monetary: res.rmm.m, + totalScore: res.rmm.r + res.rmm.f + res.rmm.m, }, - // 模拟流量池数据 + // 根据数据推断流量池信息 trafficPools: { - currentPool: "新用户池", - availablePools: ["高价值客户池", "活跃用户池"], + currentPool: res.total.isFriend ? "已添加好友池" : "待添加池", + availablePools: ["高价值客户池", "活跃用户池", "新用户池"], }, - // 模拟用户标签数据 + // 基于数据生成用户标签 userTags: [ - { id: "1", name: "近期活跃", color: "success", type: "user" }, - { id: "2", name: "高频互动", color: "primary", type: "user" }, - { id: "3", name: "高消费", color: "warning", type: "user" }, - { id: "4", name: "老客户", color: "danger", type: "user" }, + ...(res.total.isFriend + ? [ + { + id: "friend", + name: "已添加好友", + color: "success", + type: "status", + }, + ] + : []), + ...(res.total.money > 0 + ? [ + { + id: "paid", + name: "付费用户", + color: "warning", + type: "value", + }, + ] + : []), + ...(res.total.msg > 10 + ? [ + { + id: "active", + name: "高频互动", + color: "primary", + type: "behavior", + }, + ] + : []), + ...(res.source?.length > 1 + ? [ + { + id: "multi", + name: "多设备用户", + color: "danger", + type: "device", + }, + ] + : []), ], - // 模拟价值标签数据 + // 基于RMM评分生成价值标签 valueTags: [ { - id: "1", - name: "重要保持客户", - color: "primary", + id: "rmm", + name: getRmmValueLevel(res.rmm.r + res.rmm.f + res.rmm.m), + color: getRmmColor(res.rmm.r + res.rmm.f + res.rmm.m), icon: "crown", - rfmScore: 14, - valueLevel: "高价值", + rfmScore: res.rmm.r + res.rmm.f + res.rmm.m, + valueLevel: getRmmValueLevel(res.rmm.r + res.rmm.f + res.rmm.m), }, ], }; - console.log(extendedUser); + console.log("用户详情数据:", extendedUser); setUser(extendedUser); }) @@ -258,7 +318,7 @@ const TrafficPoolDetail: React.FC = () => {
@@ -267,20 +327,29 @@ const TrafficPoolDetail: React.FC = () => { } />
-
{user.userInfo.nickname}
-
{user.userInfo.wechatId}
+
{user.nickname}
+
{user.wechatId}
- - - 重要价值客户 - - - 优先添加 - + {user.valueTags?.map(tag => ( + + + {tag.name} + + ))} + {user.total.isFriend && ( + + 已添加好友 + + )}
@@ -323,11 +392,29 @@ const TrafficPoolDetail: React.FC = () => { {/* 关联信息 */} - 设备 - 微信号 - 客服 - 添加时间 - 最近互动 + + 设备 + + 微信号 + 别名 + + 添加时间 + + + 最近互动 + @@ -405,7 +492,7 @@ const TrafficPoolDetail: React.FC = () => { className={styles.statValue} style={{ color: "#52c41a" }} > - ¥9561 + ¥{user.total.money || 0}
总消费
@@ -414,7 +501,7 @@ const TrafficPoolDetail: React.FC = () => { className={styles.statValue} style={{ color: "#1677ff" }} > - 6 + {user.total.msg || 0}
互动次数
@@ -423,13 +510,18 @@ const TrafficPoolDetail: React.FC = () => { className={styles.statValue} style={{ color: "#722ed1" }} > - 3% + {user.total.percentage || "0"}%
转化率
-
- 未添加 +
+ {user.total.isFriend ? "已添加" : "未添加"}
添加状态
@@ -444,7 +536,7 @@ const TrafficPoolDetail: React.FC = () => { className={styles.statValue} style={{ color: "#1677ff" }} > - {user.userInfo.friendShip.totalFriend} + {user.userInfo?.friendShip.totalFriend || 0}
总好友
@@ -453,7 +545,7 @@ const TrafficPoolDetail: React.FC = () => { className={styles.statValue} style={{ color: "#1677ff" }} > - {user.userInfo.friendShip.maleFriend} + {user.userInfo?.friendShip.maleFriend || 0}
男性好友
@@ -462,13 +554,13 @@ const TrafficPoolDetail: React.FC = () => { className={styles.statValue} style={{ color: "#eb2f96" }} > - {user.userInfo.friendShip.femaleFriend} + {user.userInfo?.friendShip.femaleFriend || 0}
女性好友
- {user.userInfo.friendShip.unknowFriend} + {user.userInfo?.friendShip.unknowFriend || 0}
未知性别
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 0c08b5e6..84fefee2 100644 --- a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/index.tsx @@ -209,7 +209,7 @@ const TrafficPoolList: React.FC = () => { style={{ cursor: "pointer" }} onClick={() => navigate( - `/mine/traffic-pool/detail/${item.sourceId}/${item.id}`, + `/mine/traffic-pool/detail/${item.wechatId}/${item.id}`, ) } > diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/data.ts b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/data.ts index f2ff3f2f..20d7a2c9 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/data.ts +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/data.ts @@ -17,7 +17,7 @@ export interface WechatAccountSummary { }; restrictions: { id: number; - level: string; + level: number; reason: string; date: string; }[]; diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss index 944ab4ff..c77c7dd4 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss @@ -738,3 +738,74 @@ min-width: 70px; margin-right: 8px; } + +.risk-content { + padding: 16px 0; + height: 500px; + overflow-y: auto; + + .restrictions-list { + .restriction-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + margin-bottom: 12px; + background: #fff; + border-radius: 8px; + border: 1px solid #f0f0f0; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); + + &:last-child { + margin-bottom: 0; + } + + .restriction-info { + flex: 1; + + .restriction-reason { + font-size: 14px; + color: #333; + font-weight: 500; + margin-bottom: 4px; + } + + .restriction-date { + font-size: 12px; + color: #999; + } + } + + .restriction-level { + .level-badge { + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + + &.level-1 { + background: #e8f5e8; + color: #52c41a; + } + + &.level-2 { + background: #fff7e6; + color: #fa8c16; + } + + &.level-3 { + background: #fff2f0; + color: #ff4d4f; + } + } + } + } + } + + .empty { + text-align: center; + color: #999; + font-size: 14px; + padding: 40px 0; + } + } diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx index 25271b23..12eb5e0f 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx @@ -17,10 +17,6 @@ import { SearchOutlined, ReloadOutlined, UserOutlined, - ClockCircleOutlined, - MessageOutlined, - StarOutlined, - ExclamationCircleOutlined, } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; import style from "./detail.module.scss"; @@ -39,7 +35,6 @@ const WechatAccountDetail: React.FC = () => { const [showTransferConfirm, setShowTransferConfirm] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [activeTab, setActiveTab] = useState("overview"); - const [isLoading, setIsLoading] = useState(false); const [loadingInfo, setLoadingInfo] = useState(true); // 好友列表相关状态 @@ -91,7 +86,7 @@ const WechatAccountDetail: React.FC = () => { const response = await getWechatFriends({ wechatAccount: id, page: page, - limit: 20, + limit: 5, keyword: keyword, }); @@ -185,35 +180,6 @@ const WechatAccountDetail: React.FC = () => { return colors[Math.floor(Math.random() * colors.length)]; }; - const calculateAccountAge = (registerTime: string) => { - const registerDate = new Date(registerTime); - const now = new Date(); - const diffTime = Math.abs(now.getTime() - registerDate.getTime()); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - const years = Math.floor(diffDays / 365); - const months = Math.floor((diffDays % 365) / 30); - return { years, months }; - }; - - const formatAccountAge = (age: { years: number; months: number }) => { - if (age.years > 0) { - return `${age.years}年${age.months}个月`; - } - return `${age.months}个月`; - }; - - const getWeightColor = (weight: number) => { - if (weight >= 80) return "text-green-600"; - if (weight >= 60) return "text-yellow-600"; - return "text-red-600"; - }; - - const getWeightDescription = (weight: number) => { - if (weight >= 80) return "账号质量优秀,可以正常使用"; - if (weight >= 60) return "账号质量良好,需要注意使用频率"; - return "账号质量较差,建议谨慎使用"; - }; - const handleTransferFriends = () => { setShowTransferConfirm(true); }; @@ -227,11 +193,11 @@ const WechatAccountDetail: React.FC = () => { navigate("/scenarios"); }; - const getRestrictionLevelColor = (level: string) => { + const getRestrictionLevelColor = (level: number) => { switch (level) { - case "high": + case 3: return "text-red-600"; - case "medium": + case 2: return "text-yellow-600"; default: return "text-gray-600"; @@ -257,6 +223,10 @@ const WechatAccountDetail: React.FC = () => { setActiveTab(value); }; + const handleFriendClick = (friend: Friend) => { + navigate(`/mine/traffic-pool/detail/${friend.wechatId}/${friend.id}`); + }; + return ( } loading={loadingInfo}>
@@ -415,7 +385,11 @@ const WechatAccountDetail: React.FC = () => { ) : ( <> {friends.map(friend => ( -
+
handleFriendClick(friend)} + > { total={Math.ceil(friendsTotal / 20)} current={friendsPage} onChange={handlePageChange} - showText={true} />
)}
+ + +
+ {accountSummary?.restrictions && + accountSummary.restrictions.length > 0 ? ( +
+ {accountSummary.restrictions.map(restriction => ( +
+
+
+ {restriction.reason} +
+
+ {restriction.date + ? formatDateTime(restriction.date) + : "暂无时间"} +
+
+
+ + {restriction.level === 1 + ? "低风险" + : restriction.level === 2 + ? "中风险" + : "高风险"} + +
+
+ ))} +
+ ) : ( +
暂无风险记录
+ )} +
+
@@ -506,9 +519,9 @@ const WechatAccountDetail: React.FC = () => { - {restriction.level === "high" + {restriction.level === 3 ? "高风险" - : restriction.level === "medium" + : restriction.level === 2 ? "中风险" : "低风险"} diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts deleted file mode 100644 index 98bc336a..00000000 --- a/Cunkebao/src/pages/pc/ckbox/api.ts +++ /dev/null @@ -1,246 +0,0 @@ -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/NavCommon/index.data.ts b/Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.data.ts deleted file mode 100644 index f42e11af..00000000 --- a/Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.data.ts +++ /dev/null @@ -1,44 +0,0 @@ -// 菜单项接口 -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.module.scss b/Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.module.scss deleted file mode 100644 index 6bf58b79..00000000 --- a/Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.module.scss +++ /dev/null @@ -1,316 +0,0 @@ -.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; - gap: 16px; -} - -.userInfo { - display: flex; - align-items: center; - gap: 16px; - padding: 8px 0; - - .suanli { - display: flex; - align-items: center; - gap: 4px; - font-size: 14px; - color: #666; - font-weight: 500; - padding: 6px 12px; - background: #f8f9fa; - border-radius: 20px; - border: 1px solid #e9ecef; - - .suanliIcon { - font-size: 16px; - color: #ffc107; - } - } -} - -.messageButton { - display: flex; - align-items: center; - justify-content: center; - width: 44px; - height: 44px; - border-radius: 50%; - transition: all 0.3s; - border: 1px solid #e9ecef; - background: #fff; - - &:hover { - background-color: #f8f9fa; - border-color: #1890ff; - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(24, 144, 255, 0.15); - } - - .anticon { - font-size: 18px; - color: #666; - } -} - -.userSection { - display: flex; - align-items: center; - gap: 12px; - cursor: pointer; - padding: 8px 16px; - border-radius: 24px; - transition: all 0.3s; - border: 1px solid #e9ecef; - background: #fff; - - &:hover { - background-color: #f8f9fa; - border-color: #1890ff; - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(24, 144, 255, 0.15); - } -} - -.userNickname { - font-size: 14px; - color: #333; - font-weight: 600; - max-width: 100px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.avatar { - border: 2px solid #e9ecef; - transition: all 0.3s; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); -} - -.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/components/NavCommon/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx deleted file mode 100644 index 500099fa..00000000 --- a/Cunkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx +++ /dev/null @@ -1,143 +0,0 @@ -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 { drawerMenuData, menuList } from "./index.data"; -import styles from "./index.module.scss"; - -const { Header } = Layout; - -interface NavCommonProps { - title?: string; - onMenuClick?: () => void; -} - -const NavCommon: React.FC = ({ - title = "触客宝", - onMenuClick, -}) => { - const [drawerVisible, setDrawerVisible] = useState(false); - const navigate = useNavigate(); - const { userInfo } = useCkChatStore(); - - // 处理菜单图标点击 - const handleMenuClick = () => { - setDrawerVisible(true); - onMenuClick?.(); - }; - - // 处理抽屉关闭 - const handleDrawerClose = () => { - setDrawerVisible(false); - }; - - // 默认抽屉内容 - const defaultDrawerContent = ( -
-
-
-
- {drawerMenuData.header.logoIcon} -
-
-
- {drawerMenuData.header.appName} -
-
- {drawerMenuData.header.appDesc} -
-
-
-
-
-
-
- {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} -
-
-
-
- ); - - return ( - <> -
-
-
- -
- - - - 9307.423 - - } - src={userInfo?.account?.avatar} - className={styles.avatar} - /> - -
-
- - - {defaultDrawerContent} - - - ); -}; - -export default NavCommon; diff --git a/Cunkebao/src/pages/pc/ckbox/dashboard/index.module.scss b/Cunkebao/src/pages/pc/ckbox/dashboard/index.module.scss deleted file mode 100644 index 96fdbab6..00000000 --- a/Cunkebao/src/pages/pc/ckbox/dashboard/index.module.scss +++ /dev/null @@ -1,193 +0,0 @@ -.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; - } - } - } - } - - .chartsRow { - margin-top: 24px; - } - - .chartCard { - .ant-card-head { - background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); - color: white; - - .ant-card-head-title { - color: white; - font-weight: 600; - } - } - - .ant-card-body { - padding: 20px; - } - - // 图表容器样式 - .g2-tooltip { - background: rgba(0, 0, 0, 0.8); - border-radius: 6px; - color: white; - } - } - - .tableRow { - margin-top: 24px; - } - - .tableCard { - .ant-card-head { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - - .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; - } - } - } - } -} - -// 响应式设计 -@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/dashboard/index.tsx b/Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx deleted file mode 100644 index b6f0c923..00000000 --- a/Cunkebao/src/pages/pc/ckbox/dashboard/index.tsx +++ /dev/null @@ -1,476 +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 * 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/data.ts b/Cunkebao/src/pages/pc/ckbox/data.ts deleted file mode 100644 index 0444a312..00000000 --- a/Cunkebao/src/pages/pc/ckbox/data.ts +++ /dev/null @@ -1,323 +0,0 @@ -// 消息列表数据接口 - 支持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/index.module.scss b/Cunkebao/src/pages/pc/ckbox/index.module.scss deleted file mode 100644 index 9b37437e..00000000 --- a/Cunkebao/src/pages/pc/ckbox/index.module.scss +++ /dev/null @@ -1,198 +0,0 @@ -.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/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx deleted file mode 100644 index 2ff2022a..00000000 --- a/Cunkebao/src/pages/pc/ckbox/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; -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 ( - - } - > - - - ); -}; - -export default CkboxPage; diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts deleted file mode 100644 index 19f6cf62..00000000 --- a/Cunkebao/src/pages/pc/ckbox/main.ts +++ /dev/null @@ -1,307 +0,0 @@ -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/pages/pc/ckbox/weChat/api.ts b/Cunkebao/src/pages/pc/ckbox/weChat/api.ts deleted file mode 100644 index af476f26..00000000 --- a/Cunkebao/src/pages/pc/ckbox/weChat/api.ts +++ /dev/null @@ -1,287 +0,0 @@ -import request from "@/api/request2"; -import { - MessageData, - ChatHistoryResponse, - MessageType, - OnlineStatus, - MessageStatus, - FileUploadResponse, - EmojiData, - QuickReply, - ChatSettings, -} from "./data"; - -//读取聊天信息 -//kf.quwanzhi.com:9991/api/WechatFriend/clearUnreadCount -function jsonToQueryString(json) { - const params = new URLSearchParams(); - for (const key in json) { - if (Object.prototype.hasOwnProperty.call(json, key)) { - params.append(key, json[key]); - } - } - return params.toString(); -} -//转移客户 -export function WechatFriendAllot(params: { - wechatFriendId?: number; - wechatChatroomId?: number; - toAccountId: number; - notifyReceiver: boolean; - comment: string; -}) { - return request( - "/api/wechatFriend/allot?" + jsonToQueryString(params), - undefined, - "PUT", - ); -} - -//获取可转移客服列表 -export function getTransferableAgentList() { - return request("/api/account/myDepartmentAccountsForTransfer", {}, "GET"); -} - -// 微信好友列表 -export function WechatFriendRebackAllot(params: { - wechatFriendId?: number; - wechatChatroomId?: number; -}) { - return request( - "/api/wechatFriend/rebackAllot?" + jsonToQueryString(params), - undefined, - "PUT", - ); -} - -// 微信群列表 -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/weChat/components/ChatWindow/ChatWindow.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/ChatWindow.module.scss deleted file mode 100644 index ceeff66e..00000000 --- a/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/ChatWindow.module.scss +++ /dev/null @@ -1,404 +0,0 @@ -.chatWindow { - height: 100%; - display: flex; - flex-direction: row; -} - -.chatMain { - flex: 1; - display: flex; - flex-direction: column; - min-width: 0; -} - -.chatHeader { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 16px; - background: #fff; - border-bottom: 1px solid #f0f0f0; - height: 64px; - min-height: 64px; - flex-shrink: 0; - gap: 16px; // 确保信息区域和按钮区域有足够间距 - - .chatHeaderInfo { - display: flex; - align-items: center; - gap: 12px; - flex: 1; - min-width: 0; // 防止flex子元素溢出 - - :global(.ant-avatar) { - flex-shrink: 0; - width: 40px; - height: 40px; - } - - .chatHeaderDetails { - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - min-width: 0; - - .chatHeaderName { - font-size: 16px; - font-weight: 600; - color: #262626; - display: flex; - align-items: center; - gap: 8px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-right: 30px; - - .chatHeaderOnlineStatus { - font-size: 12px; - color: #52c41a; - font-weight: normal; - flex-shrink: 0; // 防止在线状态被压缩 - } - } - - .chatHeaderType { - font-size: 12px; - color: #8c8c8c; - margin-top: 2px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .chatHeaderSubInfo { - display: flex; - gap: 12px; - margin-top: 2px; - font-size: 12px; - overflow: hidden; - - .chatHeaderRemark { - color: #1890ff; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .chatHeaderWechatId { - color: #8c8c8c; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } - } - } - - .headerButton { - color: #8c8c8c; - border: none; - padding: 8px; - - &:hover { - color: #1890ff; - background: #f5f5f5; - } - } -} - -.chatContent { - flex: 1; - overflow: visible; - background: #f5f5f5; - display: flex; - flex-direction: column; - - .messagesContainer { - height: 100%; - overflow-y: auto; - padding: 16px; - - .loadingContainer { - display: flex; - justify-content: center; - align-items: center; - height: 100px; - color: #8c8c8c; - } - } -} - -// 右侧个人资料卡片 -.profileSider { - background: #fff; - border-left: 1px solid #f0f0f0; - display: flex; - flex-direction: column; - height: 100%; - flex-shrink: 0; - - .profileSiderContent { - display: flex; - flex-direction: column; - height: 100%; - - .profileHeader { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px; - border-bottom: 1px solid #f0f0f0; - background: #fafafa; - flex-shrink: 0; - - h3 { - margin: 0; - font-size: 16px; - font-weight: 600; - color: #262626; - } - - .closeButton { - color: #8c8c8c; - border: none; - padding: 4px; - - &:hover { - color: #1890ff; - background: #f5f5f5; - } - } - } - - .profileContent { - flex: 1; - overflow-y: auto; - padding: 16px; - height: 0; // 确保flex子元素能够正确计算高度 - - .profileCard { - margin-bottom: 16px; - border-radius: 8px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - - &:last-child { - margin-bottom: 0; - } - - :global(.ant-card-head) { - border-bottom: 1px solid #f0f0f0; - padding: 0 16px; - - :global(.ant-card-head-title) { - font-size: 14px; - font-weight: 600; - color: #262626; - } - } - - :global(.ant-card-body) { - padding: 16px; - } - } - - .profileBasic { - display: flex; - align-items: center; - gap: 16px; - - :global(.ant-avatar) { - flex-shrink: 0; - width: 48px; - height: 48px; - } - - .profileInfo { - flex: 1; - min-width: 0; - overflow: hidden; - - h4 { - margin: 0 0 8px 0; - font-size: 18px; - font-weight: 600; - color: #262626; - } - - .profileNickname { - margin: 0 0 8px 0; - font-size: 18px; - font-weight: 600; - color: #262626; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 100%; - cursor: pointer; - } - - .profileStatus { - margin: 0 0 4px 0; - font-size: 12px; - color: #52c41a; - } - - .profilePosition { - margin: 0; - font-size: 12px; - color: #8c8c8c; - } - - .profileRemark { - margin: 0 0 4px 0; - font-size: 12px; - color: #1890ff; - - :global(.ant-input) { - font-size: 12px; - } - - :global(.ant-btn) { - padding: 0; - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - } - } - - .profileWechatId { - margin: 0; - font-size: 12px; - color: #8c8c8c; - } - } - } - - .contractInfo { - .contractItem { - align-items: center; - margin-bottom: 12px; - font-size: 14px; - color: #262626; - - &:last-child { - margin-bottom: 0; - } - - :global(.anticon) { - color: #8c8c8c; - font-size: 16px; - width: 16px; - } - - .contractItemText { - padding-left: 10px; - } - } - } - - .tagsContainer { - display: flex; - flex-wrap: wrap; - gap: 6px; - - :global(.ant-tag) { - margin: 0; - border-radius: 12px; - font-size: 12px; - } - } - - .bioText { - margin: 0; - font-size: 14px; - line-height: 1.6; - color: #595959; - } - - .profileActions { - margin-top: 24px; - } - } - } -} - -.messageTime { - text-align: center; - padding: 4px 0; - font-size: 12px; - color: #999; - margin: 20px 0; -} - -// 响应式设计 -@media (max-width: 1200px) { - .profileSider { - width: 260px !important; - } -} - -@media (max-width: 768px) { - .chatWindow { - flex-direction: column; - } - - .profileSider { - width: 100% !important; - height: 300px; - border-left: none; - border-top: 1px solid #f0f0f0; - } - - .chatHeader { - padding: 0 12px; - height: 56px; - min-height: 56px; - - .chatHeaderInfo { - .chatHeaderDetails { - .chatHeaderName { - font-size: 14px; - } - } - } - } - - .chatContent { - .messagesContainer { - padding: 12px; - } - } - - .profileContent { - padding: 12px; - - .profileCard { - margin-bottom: 12px; - - :global(.ant-card-body) { - padding: 12px; - } - } - - .profileBasic { - gap: 12px; - - .profileInfo { - h4 { - font-size: 16px; - } - } - } - - .contractInfo { - .contractItem { - font-size: 13px; - margin-bottom: 10px; - } - } - } -} diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss deleted file mode 100644 index 36977bd1..00000000 --- a/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss +++ /dev/null @@ -1,181 +0,0 @@ -// MessageEnter 组件样式 - 微信风格 -.chatFooter { - background: #f7f7f7; - border-top: 1px solid #e1e1e1; - padding: 0; - height: auto; - min-height: 100px; -} - -.inputContainer { - padding: 8px 12px; - display: flex; - flex-direction: column; - gap: 6px; -} - -.inputToolbar { - display: flex; - justify-content: space-between; - align-items: center; - padding: 4px 0; - border-bottom: none; -} - -.leftTool { - display: flex; - gap: 2px; - align-items: center; -} - -.toolbarButton { - width: 28px; - height: 28px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; - color: #666; - font-size: 16px; - transition: all 0.15s; - border: none; - background: transparent; - - &:hover { - background: #e6e6e6; - color: #333; - } - - &:active { - background: #d9d9d9; - } -} - -.rightTool { - display: flex; - gap: 12px; - align-items: center; -} - -.rightToolItem { - display: flex; - align-items: center; - gap: 3px; - color: #666; - font-size: 11px; - cursor: pointer; - padding: 3px 6px; - border-radius: 3px; - transition: all 0.15s; - - &:hover { - background: #e6e6e6; - color: #333; - } -} - -.inputArea { - display: flex; - flex-direction: column; - padding: 4px 0; -} - -.inputWrapper { - border: 1px solid #d1d1d1; - border-radius: 4px; - background: #fff; - overflow: hidden; - - &:focus-within { - border-color: #07c160; - } -} - -.messageInput { - width: 100%; - border: none; - resize: none; - font-size: 13px; - line-height: 1.4; - padding: 8px 10px; - background: transparent; - - &:focus { - box-shadow: none; - outline: none; - } - - &::placeholder { - color: #b3b3b3; - } -} - -.sendButtonArea { - padding: 8px 10px; - display: flex; - justify-content: flex-end; -} - -.sendButton { - height: 32px; - border-radius: 4px; - font-weight: normal; - min-width: 60px; - font-size: 13px; - background: #07c160; - border-color: #07c160; - - &:hover { - background: #06ad56; - border-color: #06ad56; - } - - &:active { - background: #059748; - border-color: #059748; - } - - &:disabled { - background: #b3b3b3; - border-color: #b3b3b3; - opacity: 1; - } -} - -.inputHint { - font-size: 11px; - color: #999; - text-align: right; - margin-top: 2px; -} - -// 响应式设计 -@media (max-width: 768px) { - .inputContainer { - padding: 8px 12px; - } - - .inputToolbar { - flex-wrap: wrap; - gap: 8px; - } - - .rightTool { - gap: 8px; - } - - .rightToolItem { - font-size: 11px; - padding: 2px 6px; - } - - .inputArea { - flex-direction: column; - gap: 8px; - } - - .sendButton { - align-self: flex-end; - min-width: 60px; - } -} diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/chatRecord/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/chatRecord/index.tsx deleted file mode 100644 index 3068e42d..00000000 --- a/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/chatRecord/index.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import React, { useState } from "react"; -import { Button, Modal, Input, DatePicker, message } from "antd"; -import { MessageOutlined } from "@ant-design/icons"; -import dayjs from "dayjs"; -import { useWeChatStore } from "@/store/module/weChat/weChat"; - -const { RangePicker } = DatePicker; - -interface ChatRecordProps { - className?: string; - disabled?: boolean; -} - -const ChatRecord: React.FC = ({ - className, - disabled = false, -}) => { - const [visible, setVisible] = useState(false); - const [searchContent, setSearchContent] = useState(""); - const [dateRange, setDateRange] = useState<[dayjs.Dayjs, dayjs.Dayjs] | null>( - null, - ); - const [loading, setLoading] = useState(false); - const SearchMessage = useWeChatStore(state => state.SearchMessage); - - // 打开弹窗 - const openModal = () => { - setVisible(true); - }; - - // 关闭弹窗并重置状态 - const closeModal = () => { - setVisible(false); - setSearchContent(""); - setDateRange(null); - setLoading(false); - }; - - // 执行查找 - const handleSearch = async () => { - if (!dateRange) { - message.warning("请选择时间范围"); - return; - } - - try { - setLoading(true); - const [From, To] = dateRange; - const searchData = { - From: From.unix() * 1000, - To: To.unix() * 1000, - keyword: searchContent.trim(), - }; - await SearchMessage(searchData); - - message.success("查找完成"); - closeModal(); - } catch (error) { - console.error("查找失败:", error); - message.error("查找失败,请重试"); - } finally { - setLoading(false); - } - }; - - return ( - <> -
- - 聊天记录 -
- - - - - , - ]} - > -
- {/* 时间范围选择 */} -
-
- 时间范围 -
- -
- - {/* 查找内容输入 */} -
-
- 查找内容 -
- setSearchContent(e.target.value)} - size="large" - maxLength={100} - showCount - disabled={loading} - /> -
-
-
- - ); -}; - -export default ChatRecord; diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx deleted file mode 100644 index be2f9cb2..00000000 --- a/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import React, { useState } from "react"; -import { Button, Modal, Select, Input, message } from "antd"; -import { ShareAltOutlined } from "@ant-design/icons"; -import { - getTransferableAgentList, - WechatFriendAllot, - WechatFriendRebackAllot, -} from "@/pages/pc/ckbox/weChat/api"; -import { useCurrentContact } from "@/store/module/weChat/weChat"; -import { useCkChatStore } from "@/store/module/ckchat/ckchat"; -import { contractService, weChatGroupService } from "@/utils/db"; -const { TextArea } = Input; -const { Option } = Select; - -interface ToContractProps { - className?: string; - disabled?: boolean; -} -interface DepartItem { - id: number; - userName: string; - realName: string; - nickname: string; - avatar: string; - memo: string; - departmentId: number; - alive: boolean; -} - -const ToContract: React.FC = ({ - className, - disabled = false, -}) => { - const currentContact = useCurrentContact(); - const [visible, setVisible] = useState(false); - const [selectedTarget, setSelectedTarget] = useState(null); - const [comment, setComment] = useState(""); - const [loading, setLoading] = useState(false); - const [customerServiceList, setCustomerServiceList] = useState( - [], - ); - const deleteChatSession = useCkChatStore(state => state.deleteChatSession); - // 打开弹窗 - const openModal = () => { - setVisible(true); - getTransferableAgentList().then(data => { - setCustomerServiceList(data); - }); - }; - - // 关闭弹窗并重置状态 - const closeModal = () => { - setVisible(false); - setSelectedTarget(null); - setComment(""); - setLoading(false); - }; - - // 确定转给他人 - const handleConfirm = async () => { - if (!selectedTarget) { - message.warning("请选择目标客服"); - return; - } - - try { - setLoading(true); - - console.log(currentContact); - - // 调用转接接口 - if (currentContact) { - if ("chatroomId" in currentContact && currentContact.chatroomId) { - await WechatFriendAllot({ - wechatChatroomId: currentContact.id, - toAccountId: selectedTarget as number, - notifyReceiver: true, - comment: comment.trim(), - }); - } else { - await WechatFriendAllot({ - wechatFriendId: currentContact.id, - toAccountId: selectedTarget as number, - notifyReceiver: true, - comment: comment.trim(), - }); - } - } - - message.success("转接成功"); - try { - // 删除聊天会话 - deleteChatSession(currentContact.id); - // 删除本地数据库记录 - if ("chatroomId" in currentContact) { - await weChatGroupService.delete(currentContact.id); - } else { - await contractService.delete(currentContact.id); - } - } catch (deleteError) { - console.error("删除本地数据失败:", deleteError); - } - closeModal(); - } catch (error) { - console.error("转接失败:", error); - message.error("转接失败,请重试"); - } finally { - setLoading(false); - } - }; - - // 一键转回 - const handleReturn = async () => { - try { - setLoading(true); - - // 调用转回接口 - if (currentContact) { - if ("chatroomId" in currentContact && currentContact.chatroomId) { - await WechatFriendRebackAllot({ - wechatChatroomId: currentContact.id, - }); - } else { - await WechatFriendRebackAllot({ - wechatFriendId: currentContact.id, - }); - } - } - - message.success("转回成功"); - try { - // 删除聊天会话 - deleteChatSession(currentContact.id); - // 删除本地数据库记录 - if ("chatroomId" in currentContact) { - await weChatGroupService.delete(currentContact.id); - } else { - await contractService.delete(currentContact.id); - } - } catch (deleteError) { - console.error("删除本地数据失败:", deleteError); - } - closeModal(); - } catch (error) { - console.error("转回失败:", error); - message.error("转回失败,请重试"); - } finally { - setLoading(false); - } - }; - - return ( - <> -
- - 转给他人 -
- - - -
- - -
- , - ]} - > -
- {/* 目标客服选择 */} -
-
- 目标客服 -
- -
- - {/* 附言输入 */} -
-
- 附言 -
-