From fdc6c15d889cd78aaf66d23027b1c7fe456ce325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 28 Aug 2025 15:51:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(ckchat):=20=E6=B7=BB=E5=8A=A0=E8=81=94?= =?UTF-8?q?=E7=B3=BB=E4=BA=BA=E5=88=86=E7=BB=84=E5=8A=9F=E8=83=BD=E5=B9=B6?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E9=AA=A8=E6=9E=B6=E5=B1=8F=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加新的联系人分组状态 newContractList 和对应的异步设置方法 asyncNewContractList 实现页面加载时的骨架屏效果,优化用户体验 重构 SidebarMenu 组件样式,分离骨架屏逻辑 新增 PageSkeleton 组件用于统一管理骨架屏 --- Cunkebao/dist/.vite/manifest.json | 18 +-- Cunkebao/dist/index.html | 8 +- .../SidebarMenu/SidebarMenu.module.scss | 129 +++++++++++++----- .../pc/ckbox/components/SidebarMenu/index.tsx | 59 +++++++- .../components/Skeleton/index.module.scss | 111 +++++++++++++++ .../pc/ckbox/components/Skeleton/index.tsx | 91 ++++++++++++ Cunkebao/src/pages/pc/ckbox/index.tsx | 127 +++++++++-------- Cunkebao/src/store/module/ckchat.ts | 8 ++ 8 files changed, 444 insertions(+), 107 deletions(-) create mode 100644 Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.module.scss create mode 100644 Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.tsx diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json index 42480c71..17e97181 100644 --- a/Cunkebao/dist/.vite/manifest.json +++ b/Cunkebao/dist/.vite/manifest.json @@ -1,9 +1,9 @@ { - "_charts-DDGb1us0.js": { - "file": "assets/charts-DDGb1us0.js", + "_charts-M0qaf_ew.js": { + "file": "assets/charts-M0qaf_ew.js", "name": "charts", "imports": [ - "_ui-rFvxQTWo.js", + "_ui-D5qYGnLz.js", "_vendor-2vc8h_ct.js" ] }, @@ -11,8 +11,8 @@ "file": "assets/ui-D0C0OGrH.css", "src": "_ui-D0C0OGrH.css" }, - "_ui-rFvxQTWo.js": { - "file": "assets/ui-rFvxQTWo.js", + "_ui-D5qYGnLz.js": { + "file": "assets/ui-D5qYGnLz.js", "name": "ui", "imports": [ "_vendor-2vc8h_ct.js" @@ -33,18 +33,18 @@ "name": "vendor" }, "index.html": { - "file": "assets/index-C3xy08Hg.js", + "file": "assets/index-BQxyt58_.js", "name": "index", "src": "index.html", "isEntry": true, "imports": [ "_vendor-2vc8h_ct.js", - "_ui-rFvxQTWo.js", "_utils-6WF66_dS.js", - "_charts-DDGb1us0.js" + "_ui-D5qYGnLz.js", + "_charts-M0qaf_ew.js" ], "css": [ - "assets/index-D4Jt-UDy.css" + "assets/index-B6B8u-1D.css" ] } } \ No newline at end of file diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html index f4b62be3..dc0fa190 100644 --- a/Cunkebao/dist/index.html +++ b/Cunkebao/dist/index.html @@ -11,13 +11,13 @@ - + - - + + - +
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss index 6a60b1ef..c973b4bc 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/SidebarMenu.module.scss @@ -1,50 +1,100 @@ -.headerContainer { - background: #fff; - border-bottom: 1px solid #f0f0f0; -} - -.searchBar { - padding: 16px 16px 8px; +.sidebarMenu { + height: 100%; + display: flex; + flex-direction: column; background: #fff; - :global(.ant-input) { - border-radius: 20px; - background: #f5f5f5; - border: none; + .headerContainer { + padding: 16px; + background: #fff; + border-bottom: 1px solid #f0f0f0; - &:focus { + .searchBar { + margin-bottom: 16px; + padding: 0; background: #fff; - border: 1px solid #1890ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); + + :global(.ant-input) { + border-radius: 20px; + background: #f5f5f5; + border: none; + + &:focus { + background: #fff; + border: 1px solid #1890ff; + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); + } + } + } + + .tabsContainer { + display: flex; + justify-content: space-around; + border-bottom: 1px solid #f0f0f0; + padding: 0 0 8px; + + .tabItem { + padding: 8px 0; + flex: 1; + text-align: center; + cursor: pointer; + color: #999; + transition: all 0.3s; + + &:hover { + color: #1890ff; + } + + &.active { + color: #1890ff; + border-bottom: 2px solid #1890ff; + } + + span { + margin-left: 4px; + } + } } } } -.tabsContainer { +// 骨架屏样式 +.skeletonContainer { + height: 100%; + padding: 16px; display: flex; - padding: 0 16px 8px; - border-bottom: 1px solid #f0f0f0; + flex-direction: column; - .tabItem { + .searchBarSkeleton { + margin-bottom: 16px; + } + + .tabsContainerSkeleton { display: flex; - align-items: center; - padding: 8px 12px; - cursor: pointer; - border-radius: 4px; - transition: all 0.3s; + justify-content: space-around; + margin-bottom: 16px; + padding-bottom: 8px; + border-bottom: 1px solid #f0f0f0; + gap: 8px; + } - &:hover { - color: #1890ff; - background-color: rgba(24, 144, 255, 0.1); - } + .contactListSkeleton { + flex: 1; + overflow-y: auto; - &.active { - color: #1890ff; - background-color: rgba(24, 144, 255, 0.1); - } + .contactItemSkeleton { + display: flex; + align-items: center; + padding: 12px 0; + border-bottom: 1px solid #f5f5f5; - span { - margin-left: 4px; + .contactInfoSkeleton { + margin-left: 12px; + flex: 1; + display: flex; + flex-direction: column; + gap: 8px; + } } } } @@ -59,3 +109,16 @@ padding: 20px; text-align: center; } + +.contentContainer { + flex: 1; + overflow-y: auto; +} + +.footer { + padding: 10px; + text-align: center; + border-top: 1px solid #f0f0f0; + background: #fff; + display: none; /* 默认隐藏底部,如果需要显示可以移除此行 */ +} diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx index 41bd4da7..64723fcc 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import { Input } from "antd"; +import { Input, Skeleton } from "antd"; import { SearchOutlined, UserOutlined, @@ -9,7 +9,6 @@ import { import { ContractData } from "@/pages/pc/ckbox/data"; import WechatFriendsModule from "./WechatFriendsModule"; import MessageList from "./MessageList/index"; -import LayoutFiexd from "@/components/Layout/LayoutFiexd"; import styles from "./SidebarMenu.module.scss"; import { getChatSessions } from "@/store/module/ckchat"; @@ -53,6 +52,51 @@ const SidebarMenu: React.FC = ({ ); }; + // 渲染骨架屏 + const renderSkeleton = () => ( +
+
+ +
+
+ + + +
+
+ {Array(8) + .fill(null) + .map((_, index) => ( +
+ +
+ + +
+
+ ))} +
+
+ ); + // 渲染Header部分,包含搜索框和标签页切换 const renderHeader = () => (
@@ -125,10 +169,15 @@ const SidebarMenu: React.FC = ({ } }; + if (loading) { + return renderSkeleton(); + } + return ( - - {renderContent()} - +
+ {renderHeader()} +
{renderContent()}
+
); }; diff --git a/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.module.scss b/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.module.scss new file mode 100644 index 00000000..3c0329a0 --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.module.scss @@ -0,0 +1,111 @@ +.skeletonLayout { + height: 100vh; + display: flex; + flex-direction: column; + + .skeletonHeader { + height: 64px; + padding: 0 24px; + display: flex; + align-items: center; + background-color: #fff; + border-bottom: 1px solid #f0f0f0; + } + + .skeletonVerticalSider { + background-color: #fff; + border-right: 1px solid #f0f0f0; + + .verticalUserList { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 0; + + .verticalUserItem { + margin-bottom: 16px; + } + } + } + + .skeletonSider { + background-color: #fff; + border-right: 1px solid #f0f0f0; + padding: 16px; + + .searchSkeleton { + margin-bottom: 16px; + } + + .tabsSkeleton { + display: flex; + justify-content: space-around; + margin-bottom: 16px; + } + + .contactListSkeleton { + .contactItemSkeleton { + display: flex; + align-items: center; + padding: 12px 0; + border-bottom: 1px solid #f5f5f5; + + .contactInfoSkeleton { + margin-left: 12px; + flex: 1; + display: flex; + flex-direction: column; + gap: 8px; + } + } + } + } + + .skeletonMainContent { + background-color: #f5f5f5; + padding: 16px; + display: flex; + flex-direction: column; + + .chatHeaderSkeleton { + background-color: #fff; + padding: 16px; + display: flex; + align-items: center; + gap: 12px; + border-radius: 8px 8px 0 0; + } + + .chatContentSkeleton { + flex: 1; + background-color: #fff; + padding: 16px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 16px; + + .messageSkeleton { + display: flex; + align-items: flex-start; + gap: 8px; + + &.leftMessage { + align-self: flex-start; + } + + &.rightMessage { + align-self: flex-end; + flex-direction: row-reverse; + } + } + } + + .inputAreaSkeleton { + background-color: #fff; + padding: 16px; + border-radius: 0 0 8px 8px; + border-top: 1px solid #f0f0f0; + } + } +} \ No newline at end of file diff --git a/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.tsx new file mode 100644 index 00000000..f4ea1f1d --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/components/Skeleton/index.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { Skeleton, Layout } from 'antd'; +import styles from './index.module.scss'; +import pageStyles from '../../index.module.scss'; + +const { Header, Content, Sider } = Layout; + +interface PageSkeletonProps { + loading: boolean; + children: React.ReactNode; +} + +/** + * 页面骨架屏组件 + * 在数据加载完成前显示骨架屏 + */ +const PageSkeleton: React.FC = ({ loading, children }) => { + if (!loading) return <>{children}; + + return ( + +
+ +
+ + {/* 垂直侧边栏骨架 */} + +
+ {Array(5) + .fill(null) + .map((_, index) => ( +
+ +
+ ))} +
+
+ + {/* 左侧联系人边栏骨架 */} + +
+ +
+
+ + +
+
+ {Array(8) + .fill(null) + .map((_, index) => ( +
+ +
+ + +
+
+ ))} +
+
+ + {/* 主内容区骨架 */} + +
+ + +
+
+ {Array(5) + .fill(null) + .map((_, index) => ( +
+ + +
+ ))} +
+
+ +
+
+
+
+ ); +}; + +export default PageSkeleton; \ No newline at end of file diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx index 1c318615..8b4ba820 100644 --- a/Cunkebao/src/pages/pc/ckbox/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/index.tsx @@ -1,15 +1,17 @@ import React, { useState, useEffect } from "react"; -import { Layout, Button, Space, message, Tooltip, Spin } from "antd"; +import { Layout, Button, Space, message, Tooltip } from "antd"; import { InfoCircleOutlined, MessageOutlined } from "@ant-design/icons"; import dayjs from "dayjs"; import { ContractData } from "./data"; import ChatWindow from "./components/ChatWindow/index"; import SidebarMenu from "./components/SidebarMenu/index"; import VerticalUserList from "./components/VerticalUserList"; +import PageSkeleton from "./components/Skeleton"; import styles from "./index.module.scss"; import { addChatSession } from "@/store/module/ckchat"; const { Header, Content, Sider } = Layout; import { chatInitAPIdata } from "./main"; +import { KfUserListData } from "@/store/module/ckchat.data"; const CkboxPage: React.FC = () => { const [messageApi, contextHolder] = message.useMessage(); @@ -25,7 +27,17 @@ const CkboxPage: React.FC = () => { setLoading(true); chatInitAPIdata() .then(response => { - const { contractList, chatRoomList, kfUserList } = response; + const data = response as { + contractList: any[]; + groupList: any[]; + kfUserList: KfUserListData[]; + newContractList: { groupName: string; contacts: any[] }[]; + }; + const { contractList, groupList, kfUserList, newContractList } = data; + response; + + console.log(contractList, groupList, kfUserList, newContractList); + //找出已经在聊天的 const isChatList = contractList.filter( v => (v?.config && v.config?.chat) || false, @@ -77,66 +89,69 @@ const CkboxPage: React.FC = () => { }; return ( - - {contextHolder} -
触客宝
- - {/* 垂直侧边栏 */} + + + {contextHolder} +
触客宝
+ + {/* 垂直侧边栏 */} - - - + + + - {/* 左侧联系人边栏 */} - - - + {/* 左侧联系人边栏 */} + + + - {/* 主内容区 */} - - {currentChat ? ( -
-
- - - - - + {/* 主内容区 */} + + {currentChat ? ( +
+
+ + + + + +
+ setShowProfile(!showProfile)} + />
- setShowProfile(!showProfile)} - /> -
- ) : ( -
-
- -

欢迎使用触客宝

-

选择一个联系人开始聊天

+ ) : ( +
+
+ +

欢迎使用触客宝

+

选择一个联系人开始聊天

+
-
- )} - + )} + + - + ); }; diff --git a/Cunkebao/src/store/module/ckchat.ts b/Cunkebao/src/store/module/ckchat.ts index 13289ac5..22d66f04 100644 --- a/Cunkebao/src/store/module/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat.ts @@ -15,6 +15,11 @@ export const useCkChatStore = createPersistStore( contractList: [], //联系人列表 chatSessions: [], //聊天会话 kfUserList: [], //客服列表 + newContractList: [], //联系人分组 + // 异步设置会话列表 + asyncNewContractList: data => { + set({ newContractList: data }); + }, // 异步设置会话列表 asyncChatSessions: data => { set({ chatSessions: data }); @@ -182,3 +187,6 @@ export const asyncContractList = (data: ContractData[]) => useCkChatStore.getState().asyncContractList(data); export const asyncChatSessions = (data: ContractData[]) => useCkChatStore.getState().asyncChatSessions(data); +export const asyncNewContractList = ( + data: { groupName: string; contacts: any[] }[], +) => useCkChatStore.getState().asyncNewContractList(data);