Merge branch 'yongpxu-dev' into develop

This commit is contained in:
超级老白兔
2025-09-05 17:11:55 +08:00
13 changed files with 452 additions and 68 deletions

View File

@@ -1,9 +1,9 @@
{
"_charts-C4aL5mHM.js": {
"file": "assets/charts-C4aL5mHM.js",
"_charts-DmoeDXY2.js": {
"file": "assets/charts-DmoeDXY2.js",
"name": "charts",
"imports": [
"_ui-DJLY-TX6.js",
"_ui-D66ihimQ.js",
"_vendor-2vc8h_ct.js"
]
},
@@ -11,8 +11,8 @@
"file": "assets/ui-D0C0OGrH.css",
"src": "_ui-D0C0OGrH.css"
},
"_ui-DJLY-TX6.js": {
"file": "assets/ui-DJLY-TX6.js",
"_ui-D66ihimQ.js": {
"file": "assets/ui-D66ihimQ.js",
"name": "ui",
"imports": [
"_vendor-2vc8h_ct.js"
@@ -33,18 +33,18 @@
"name": "vendor"
},
"index.html": {
"file": "assets/index-BaRKPU0c.js",
"file": "assets/index-bW2KwNBi.js",
"name": "index",
"src": "index.html",
"isEntry": true,
"imports": [
"_vendor-2vc8h_ct.js",
"_utils-6WF66_dS.js",
"_ui-DJLY-TX6.js",
"_charts-C4aL5mHM.js"
"_ui-D66ihimQ.js",
"_charts-DmoeDXY2.js"
],
"css": [
"assets/index-bDMGkYaC.css"
"assets/index-BeKt58rz.css"
]
}
}

View File

@@ -11,13 +11,13 @@
</style>
<!-- 引入 uni-app web-view SDK必须 -->
<script type="text/javascript" src="/websdk.js"></script>
<script type="module" crossorigin src="/assets/index-BaRKPU0c.js"></script>
<script type="module" crossorigin src="/assets/index-bW2KwNBi.js"></script>
<link rel="modulepreload" crossorigin href="/assets/vendor-2vc8h_ct.js">
<link rel="modulepreload" crossorigin href="/assets/utils-6WF66_dS.js">
<link rel="modulepreload" crossorigin href="/assets/ui-DJLY-TX6.js">
<link rel="modulepreload" crossorigin href="/assets/charts-C4aL5mHM.js">
<link rel="modulepreload" crossorigin href="/assets/ui-D66ihimQ.js">
<link rel="modulepreload" crossorigin href="/assets/charts-DmoeDXY2.js">
<link rel="stylesheet" crossorigin href="/assets/ui-D0C0OGrH.css">
<link rel="stylesheet" crossorigin href="/assets/index-bDMGkYaC.css">
<link rel="stylesheet" crossorigin href="/assets/index-BeKt58rz.css">
</head>
<body>
<div id="root"></div>

View File

@@ -110,8 +110,10 @@
.chatContent {
flex: 1;
overflow: hidden;
overflow: visible;
background: #f5f5f5;
display: flex;
flex-direction: column;
.messagesContainer {
height: 100%;

View File

@@ -34,6 +34,11 @@
margin: 8px 0;
position: relative;
}
.loadMore {
text-align: center;
color: #1890ff;
cursor: pointer;
}
// 消息项
.messageItem {

View File

@@ -192,6 +192,7 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
return (
<div className={styles.messagesContainer}>
<div className={styles.loadMore}></div>
{groupMessagesByTime(currentMessages).map((group, groupIndex) => (
<React.Fragment key={`group-${groupIndex}`}>
<Divider>

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef } from "react";
import React, { useState } from "react";
import { Layout, Button, Avatar, Space, Dropdown, Menu, Tooltip } from "antd";
import {
PhoneOutlined,
@@ -6,6 +6,7 @@ import {
MoreOutlined,
UserOutlined,
TeamOutlined,
InfoCircleOutlined,
} from "@ant-design/icons";
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import styles from "./ChatWindow.module.scss";
@@ -18,15 +19,13 @@ const { Header, Content } = Layout;
interface ChatWindowProps {
contract: ContractData | weChatGroup;
showProfile?: boolean;
onToggleProfile?: () => void;
}
const ChatWindow: React.FC<ChatWindowProps> = ({
contract,
showProfile = true,
onToggleProfile,
}) => {
const ChatWindow: React.FC<ChatWindowProps> = ({ contract }) => {
const [showProfile, setShowProfile] = useState(true);
const onToggleProfile = () => {
setShowProfile(!showProfile);
};
const chatMenu = (
<Menu>
<Menu.Item key="profile" icon={<UserOutlined />}>
@@ -76,6 +75,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
className={styles.headerButton}
/>
</Tooltip>
<Tooltip title="视频通话">
<Button
type="text"
@@ -83,6 +83,14 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
className={styles.headerButton}
/>
</Tooltip>
<Tooltip title="个人资料">
<Button
onClick={onToggleProfile}
type="text"
icon={<InfoCircleOutlined />}
className={styles.headerButton}
/>
</Tooltip>
<Dropdown overlay={chatMenu} trigger={["click"]}>
<Button
type="text"

View File

@@ -0,0 +1,258 @@
.header {
background: #fff;
padding: 0 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
border-bottom: 1px solid #f0f0f0;
}
.headerLeft {
display: flex;
align-items: center;
gap: 12px;
}
.menuButton {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 6px;
transition: all 0.3s;
&:hover {
background-color: #f5f5f5;
}
.anticon {
font-size: 18px;
color: #666;
}
}
.title {
font-size: 18px;
color: #333;
margin: 0;
}
.headerRight {
display: flex;
align-items: center;
}
.userInfo {
cursor: pointer;
padding: 8px 12px;
border-radius: 6px;
transition: all 0.3s;
&:hover {
background-color: #f5f5f5;
}
.suanli {
font-size: 16px;
color: #666;
.suanliIcon {
font-size: 24px;
}
}
}
.avatar {
border: 2px solid #f0f0f0;
transition: all 0.3s;
&:hover {
border-color: #1890ff;
}
}
.username {
font-size: 14px;
color: #333;
font-weight: 500;
margin-left: 8px;
}
// 抽屉样式
.drawer {
:global(.ant-drawer-header) {
background: #fafafa;
border-bottom: 1px solid #f0f0f0;
}
:global(.ant-drawer-body) {
padding: 0;
}
}
.drawerContent {
height: 100%;
display: flex;
flex-direction: column;
background: #f8f9fa;
}
.drawerHeader {
padding: 20px;
background: #fff;
border-bottom: none;
}
.logoSection {
display: flex;
align-items: center;
gap: 12px;
}
.logoIcon {
width: 48px;
height: 48px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: white;
}
.logoText {
display: flex;
flex-direction: column;
}
.appName {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0;
}
.appDesc {
font-size: 12px;
color: #999;
margin: 2px 0 0 0;
}
.drawerBody {
flex: 1;
padding: 20px;
display: flex;
flex-direction: column;
gap: 16px;
}
.primaryButton {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
padding: 16px 20px;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
transition: all 0.3s;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
}
.buttonIcon {
font-size: 20px;
}
span {
font-size: 16px;
font-weight: 600;
color: white;
}
}
.menuSection {
margin-top: 8px;
}
.menuItem {
display: flex;
align-items: center;
padding: 16px 20px;
cursor: pointer;
transition: all 0.3s;
border-radius: 8px;
&:hover {
background-color: #f0f0f0;
}
span {
font-size: 16px;
color: #333;
font-weight: 500;
}
}
.menuIcon {
font-size: 20px;
margin-right: 12px;
width: 20px;
text-align: center;
}
.drawerFooter {
padding: 20px;
background: #fff;
border-top: 1px solid #f0f0f0;
.balanceSection {
display: flex;
justify-content: space-between;
align-items: center;
.balanceIcon {
color: #666;
.suanliIcon {
font-size: 20px;
}
}
.balanceText {
color: #3d9c0d;
}
}
}
.balanceLabel {
font-size: 12px;
color: #999;
margin: 0;
}
.balanceAmount {
font-size: 18px;
font-weight: 600;
color: #52c41a;
margin: 2px 0 0 0;
}
// 响应式设计
@media (max-width: 768px) {
.header {
padding: 0 12px;
}
.title {
font-size: 16px;
}
.username {
display: none;
}
.drawer {
width: 280px !important;
}
}

View File

@@ -0,0 +1,120 @@
import React, { useState } from "react";
import { Layout, Drawer, Avatar, Dropdown, Space, Button } from "antd";
import {
MenuOutlined,
UserOutlined,
LogoutOutlined,
SettingOutlined,
} from "@ant-design/icons";
import type { MenuProps } from "antd";
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
import { useNavigate } from "react-router-dom";
import styles from "./index.module.scss";
const { Header } = Layout;
interface NavCommonProps {
title?: string;
onMenuClick?: () => void;
drawerContent?: React.ReactNode;
}
const NavCommon: React.FC<NavCommonProps> = ({
title = "触客宝",
onMenuClick,
drawerContent,
}) => {
const [drawerVisible, setDrawerVisible] = useState(false);
const { userInfo } = useCkChatStore();
// 处理菜单图标点击
const handleMenuClick = () => {
setDrawerVisible(true);
onMenuClick?.();
};
// 处理抽屉关闭
const handleDrawerClose = () => {
setDrawerVisible(false);
};
// 默认抽屉内容
const defaultDrawerContent = (
<div className={styles.drawerContent}>
<div className={styles.drawerHeader}>
<div className={styles.logoSection}>
<div className={styles.logoIcon}></div>
<div className={styles.logoText}>
<div className={styles.appName}></div>
<div className={styles.appDesc}>AI智能营销系统</div>
</div>
</div>
</div>
<div className={styles.drawerBody}>
<div className={styles.primaryButton}>
<div className={styles.buttonIcon}>🔒</div>
<span>AI智能客服</span>
</div>
<div className={styles.menuSection}>
<div className={styles.menuItem}>
<div className={styles.menuIcon}>📊</div>
<span></span>
</div>
</div>
</div>
<div className={styles.drawerFooter}>
<div className={styles.balanceSection}>
<div className={styles.balanceIcon}>
<span className={styles.suanliIcon}></span>
</div>
<div className={styles.balanceText}>9307.423</div>
</div>
</div>
</div>
);
return (
<>
<Header className={styles.header}>
<div className={styles.headerLeft}>
<Button
type="text"
icon={<MenuOutlined />}
onClick={handleMenuClick}
className={styles.menuButton}
/>
<span className={styles.title}>{title}</span>
</div>
<div className={styles.headerRight}>
<Space className={styles.userInfo}>
<span className={styles.suanli}>
<span className={styles.suanliIcon}></span>
9307.423
</span>
<Avatar
size={40}
icon={<UserOutlined />}
src={userInfo?.account?.avatar}
className={styles.avatar}
/>
</Space>
</div>
</Header>
<Drawer
title="菜单"
placement="left"
onClose={handleDrawerClose}
open={drawerVisible}
width={300}
className={styles.drawer}
>
{drawerContent || defaultDrawerContent}
</Drawer>
</>
);
};
export default NavCommon;

View File

@@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect, useState } from "react";
import { List, Avatar, Badge } from "antd";
import { UserOutlined, TeamOutlined } from "@ant-design/icons";
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
@@ -11,11 +11,25 @@ interface MessageListProps {}
const MessageList: React.FC<MessageListProps> = () => {
const { setCurrentContact, currentContract } = useWeChatStore();
const chatSessions = useCkChatStore(state => state.getChatSessions());
const getChatSessions = useCkChatStore(state => state.chatSessions);
const kfSelected = useCkChatStore(state => state.kfSelected);
const onContactClick = (session: ContractData | weChatGroup) => {
setCurrentContact(session, true);
};
const [chatSessions, setChatSessions] = useState<
(ContractData | weChatGroup)[]
>([]);
useEffect(() => {
if (kfSelected == 0) {
setChatSessions(getChatSessions);
} else {
const newChatSessions = getChatSessions.filter(
v => v.wechatAccountId === kfSelected && kfSelected != 0,
);
setChatSessions(newChatSessions);
}
}, [getChatSessions, kfSelected]);
return (
<div className={styles.messageList}>
<List
@@ -64,7 +78,6 @@ const MessageList: React.FC<MessageListProps> = () => {
</List.Item>
)}
/>
<div className={styles.lastDayMessage}></div>
</div>
);
};

View File

@@ -2,18 +2,15 @@
display: flex;
flex-direction: column;
height: 100%;
background-color: #2e2e2e;
color: #fff;
width: 60px;
background-color: #ffffff;
.userListHeader {
padding: 10px 0;
text-align: center;
border-bottom: 1px solid #3a3a3a;
cursor: pointer;
.allFriends {
font-size: 12px;
color: #ccc;
color: #333333;
}
}
@@ -40,35 +37,19 @@
padding: 10px 0;
position: relative;
cursor: pointer;
&:hover {
background-color: #3a3a3a;
}
background: #ffffff;
&.active {
background-color: #3a3a3a;
.userAvatar {
border: 4px solid #1890ff;
&::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 20px;
background-color: #1890ff;
.active & {
border-color: #1890ff;
}
}
}
}
.userAvatar {
border: 2px solid transparent;
.active & {
border-color: #1890ff;
}
}
.messageBadge {
:global(.ant-badge-count) {
background-color: #ff4d4f;
@@ -90,11 +71,11 @@
height: 8px;
border-radius: 50%;
border: 1px solid #2e2e2e;
&.online {
background-color: #52c41a; // 绿色表示在线
}
&.offline {
background-color: #8c8c8c; // 灰色表示离线
}

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React from "react";
import { Avatar, Badge, Tooltip } from "antd";
import styles from "./VerticalUserList.module.scss";
import { useCkChatStore, asyncKfSelected } from "@/store/module/ckchat/ckchat";
@@ -42,7 +42,7 @@ const VerticalUserList: React.FC = () => {
>
<Avatar
src={user.avatar}
size={40}
size={50}
className={styles.userAvatar}
style={{
backgroundColor: !user.avatar ? "#1890ff" : undefined,

View File

@@ -16,7 +16,7 @@
.verticalSider {
background: #2e2e2e;
border-right: 1px solid #3a3a3a;
border-right: 1px solid #f0f0f0;
overflow: hidden;
}

View File

@@ -6,9 +6,10 @@ import ChatWindow from "./components/ChatWindow/index";
import SidebarMenu from "./components/SidebarMenu/index";
import VerticalUserList from "./components/VerticalUserList";
import PageSkeleton from "./components/Skeleton";
import NavCommon from "./components/NavCommon";
import styles from "./index.module.scss";
import { addChatSession } from "@/store/module/ckchat/ckchat";
const { Header, Content, Sider } = Layout;
const { Content, Sider } = Layout;
import { chatInitAPIdata, initSocket } from "./main";
import { useWeChatStore } from "@/store/module/weChat/weChat";
@@ -17,7 +18,6 @@ import { KfUserListData } from "@/pages/pc/ckbox/data";
const CkboxPage: React.FC = () => {
// 不要在组件初始化时获取sendCommand而是在需要时动态获取
const [loading, setLoading] = useState(false);
const [showProfile, setShowProfile] = useState(true);
const currentContract = useWeChatStore(state => state.currentContract);
useEffect(() => {
// 方法一:使用 Promise 链式调用处理异步函数
@@ -54,11 +54,11 @@ const CkboxPage: React.FC = () => {
return (
<PageSkeleton loading={loading}>
<Layout className={styles.ckboxLayout}>
<Header className={styles.header}></Header>
<NavCommon title="AI自动聊天懂业务会引导客户不停地聊不停" />
<Layout>
{/* 垂直侧边栏 */}
<Sider width={60} className={styles.verticalSider}>
<Sider width={80} className={styles.verticalSider}>
<VerticalUserList />
</Sider>
@@ -71,7 +71,7 @@ const CkboxPage: React.FC = () => {
<Content className={styles.mainContent}>
{currentContract ? (
<div className={styles.chatContainer}>
<div className={styles.chatToolbar}>
{/* <div className={styles.chatToolbar}>
<Space>
<Tooltip title={showProfile ? "隐藏资料" : "显示资料"}>
<Button
@@ -84,12 +84,8 @@ const CkboxPage: React.FC = () => {
</Button>
</Tooltip>
</Space>
</div>
<ChatWindow
contract={currentContract}
showProfile={showProfile}
onToggleProfile={() => setShowProfile(!showProfile)}
/>
</div> */}
<ChatWindow contract={currentContract} />
</div>
) : (
<div className={styles.welcomeScreen}>