更新TypeScript配置以支持新的模块路径别名,重命名获取客服列表的API函数,替换相关组件,移除不再使用的垂直用户列表组件及其样式,提升代码结构和可读性。
This commit is contained in:
5
Touchkebao/src/api/module/wechat.ts
Normal file
5
Touchkebao/src/api/module/wechat.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import request from "@/api/request";
|
||||
//获取客服列表
|
||||
export function getCustomerList() {
|
||||
return request("/v1/kefu/customerService/list", {}, "GET");
|
||||
}
|
||||
@@ -63,7 +63,7 @@ export function getMessageList(params: { page: number; limit: number }) {
|
||||
}
|
||||
|
||||
//获取客服列表
|
||||
export function getAgentList() {
|
||||
export function getCustomerList() {
|
||||
return request("/v1/kefu/customerService/list", {}, "GET");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.verticalUserList {
|
||||
.customerList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
@@ -91,4 +91,52 @@
|
||||
background-color: #8c8c8c; // 灰色表示离线
|
||||
}
|
||||
}
|
||||
|
||||
// 骨架屏样式
|
||||
.skeletonContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.skeletonItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.skeletonAvatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s infinite;
|
||||
}
|
||||
|
||||
.skeletonIndicator {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Avatar, Badge } from "antd";
|
||||
import styles from "./com.module.scss";
|
||||
import {
|
||||
useCustomerStore,
|
||||
updateCurrentCustomer,
|
||||
updateCustomerList,
|
||||
} from "@weChatStore/customer";
|
||||
import { getChatSessions } from "@storeModule/ckchat/ckchat";
|
||||
import { getCustomerList } from "@apiModule/wechat";
|
||||
const CustomerList: React.FC = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
//初始化获取客服列表
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
getCustomerList()
|
||||
.then(res => {
|
||||
updateCustomerList(res);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
const handleUserSelect = (userId: number) => {
|
||||
updateCurrentCustomer(
|
||||
customerList.find(customer => customer.id === userId) || null,
|
||||
);
|
||||
};
|
||||
const customerList = useCustomerStore(state => state.customerList);
|
||||
const currentCustomer = useCustomerStore(state => state.currentCustomer);
|
||||
|
||||
const chatSessions = getChatSessions();
|
||||
const getUnreadCount = (customerId: number) => {
|
||||
if (currentCustomer?.id != 0) {
|
||||
const session = chatSessions.filter(
|
||||
v => v.wechatAccountId === customerId,
|
||||
);
|
||||
return session.reduce((pre, cur) => pre + cur.config.unreadCount, 0);
|
||||
} else {
|
||||
return chatSessions.reduce((pre, cur) => pre + cur.config.unreadCount, 0);
|
||||
}
|
||||
};
|
||||
|
||||
// 骨架屏组件
|
||||
const SkeletonItem = () => (
|
||||
<div className={styles.skeletonItem}>
|
||||
<div className={styles.skeletonAvatar} />
|
||||
<div className={styles.skeletonIndicator} />
|
||||
</div>
|
||||
);
|
||||
|
||||
// 骨架屏列表
|
||||
const SkeletonList = () => (
|
||||
<div className={styles.skeletonContainer}>
|
||||
<SkeletonItem />
|
||||
<SkeletonItem />
|
||||
<SkeletonItem />
|
||||
<SkeletonItem />
|
||||
<SkeletonItem />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.customerList}>
|
||||
<div className={styles.userListHeader}>
|
||||
<div className={styles.allFriends}>微信号</div>
|
||||
</div>
|
||||
<div className={styles.userList}>
|
||||
{loading ? (
|
||||
<SkeletonList />
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className={styles.userItem}
|
||||
onClick={() => handleUserSelect(0)}
|
||||
>
|
||||
<Badge
|
||||
count={getUnreadCount(0)}
|
||||
overflowCount={99}
|
||||
className={styles.messageBadge}
|
||||
>
|
||||
<div className={styles.allUser}>全部</div>
|
||||
</Badge>
|
||||
<div className={`${styles.onlineIndicator} ${styles.online}`} />
|
||||
</div>
|
||||
{customerList.map(customer => (
|
||||
<div
|
||||
key={customer.id}
|
||||
className={`${styles.userItem} ${currentCustomer?.id === customer.id ? styles.active : ""}`}
|
||||
onClick={() => handleUserSelect(customer.id)}
|
||||
>
|
||||
<Badge
|
||||
count={getUnreadCount(customer.id)}
|
||||
overflowCount={99}
|
||||
className={styles.messageBadge}
|
||||
>
|
||||
<Avatar
|
||||
src={customer.avatar}
|
||||
size={50}
|
||||
className={styles.userAvatar}
|
||||
style={{
|
||||
backgroundColor: !customer.avatar ? "#1890ff" : undefined,
|
||||
}}
|
||||
>
|
||||
{!customer.avatar && customer.name.charAt(0)}
|
||||
</Avatar>
|
||||
</Badge>
|
||||
<div
|
||||
className={`${styles.onlineIndicator} ${customer.isOnline ? styles.online : styles.offline}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomerList;
|
||||
@@ -1,72 +0,0 @@
|
||||
import React from "react";
|
||||
import { Avatar, Badge } from "antd";
|
||||
import styles from "./VerticalUserList.module.scss";
|
||||
import { useCkChatStore, asyncKfSelected } from "@/store/module/ckchat/ckchat";
|
||||
|
||||
const VerticalUserList: React.FC = () => {
|
||||
const handleUserSelect = (userId: number) => {
|
||||
asyncKfSelected(userId);
|
||||
};
|
||||
const kfUserList = useCkChatStore(state => state.kfUserList);
|
||||
const kfSelected = useCkChatStore(state => state.kfSelected);
|
||||
const chatSessions = useCkChatStore(state => state.chatSessions);
|
||||
const getUnreadCount = (wechatAccountId: number) => {
|
||||
if (wechatAccountId != 0) {
|
||||
const session = chatSessions.filter(
|
||||
v => v.wechatAccountId === wechatAccountId,
|
||||
);
|
||||
return session.reduce((pre, cur) => pre + cur.config.unreadCount, 0);
|
||||
} else {
|
||||
return chatSessions.reduce((pre, cur) => pre + cur.config.unreadCount, 0);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.verticalUserList}>
|
||||
<div className={styles.userListHeader}>
|
||||
<div className={styles.allFriends}>微信号</div>
|
||||
</div>
|
||||
<div className={styles.userList}>
|
||||
<div className={styles.userItem} onClick={() => handleUserSelect(0)}>
|
||||
<Badge
|
||||
count={getUnreadCount(0)}
|
||||
overflowCount={99}
|
||||
className={styles.messageBadge}
|
||||
>
|
||||
<div className={styles.allUser}>全部</div>
|
||||
</Badge>
|
||||
<div className={`${styles.onlineIndicator} ${styles.online}`} />
|
||||
</div>
|
||||
{kfUserList.map(user => (
|
||||
<div
|
||||
key={user.id}
|
||||
className={`${styles.userItem} ${kfSelected === user.id ? styles.active : ""}`}
|
||||
onClick={() => handleUserSelect(user.id)}
|
||||
>
|
||||
<Badge
|
||||
count={getUnreadCount(user.id)}
|
||||
overflowCount={99}
|
||||
className={styles.messageBadge}
|
||||
>
|
||||
<Avatar
|
||||
src={user.avatar}
|
||||
size={50}
|
||||
className={styles.userAvatar}
|
||||
style={{
|
||||
backgroundColor: !user.avatar ? "#1890ff" : undefined,
|
||||
}}
|
||||
>
|
||||
{!user.avatar && user.name.charAt(0)}
|
||||
</Avatar>
|
||||
</Badge>
|
||||
<div
|
||||
className={`${styles.onlineIndicator} ${user.isOnline ? styles.online : styles.offline}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerticalUserList;
|
||||
@@ -3,7 +3,7 @@ import { Layout } from "antd";
|
||||
import { MessageOutlined } from "@ant-design/icons";
|
||||
import ChatWindow from "./components/ChatWindow/index";
|
||||
import SidebarMenu from "./components/SidebarMenu/index";
|
||||
import VerticalUserList from "./components/VerticalUserList";
|
||||
import CustomerList from "./components/CustomerList";
|
||||
import PageSkeleton from "./components/Skeleton";
|
||||
import styles from "./index.module.scss";
|
||||
const { Content, Sider } = Layout;
|
||||
@@ -16,9 +16,9 @@ const CkboxPage: React.FC = () => {
|
||||
const currentContract = useWeChatStore(state => state.currentContract);
|
||||
useEffect(() => {
|
||||
// 方法一:使用 Promise 链式调用处理异步函数
|
||||
if (!getIsLoadWeChat()) {
|
||||
setLoading(true);
|
||||
}
|
||||
// if (!getIsLoadWeChat()) {
|
||||
// setLoading(true);
|
||||
// }
|
||||
chatInitAPIdata()
|
||||
.then(() => {
|
||||
// 数据加载完成后初始化WebSocket连接
|
||||
@@ -39,7 +39,7 @@ const CkboxPage: React.FC = () => {
|
||||
{/* 垂直侧边栏 */}
|
||||
|
||||
<Sider width={80} className={styles.verticalSider}>
|
||||
<VerticalUserList />
|
||||
<CustomerList />
|
||||
</Sider>
|
||||
|
||||
{/* 左侧联系人边栏 */}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
asyncKfUserList,
|
||||
asyncContractList,
|
||||
asyncChatSessions,
|
||||
asyncWeChatGroup,
|
||||
@@ -16,7 +15,7 @@ import {
|
||||
getControlTerminalList,
|
||||
getContactList,
|
||||
getGroupList,
|
||||
getAgentList,
|
||||
getCustomerList,
|
||||
getLabelsListByGroup,
|
||||
getMessageList,
|
||||
} from "./api";
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
weChatGroup,
|
||||
} from "@/pages/pc/ckbox/data";
|
||||
|
||||
import { updateCustomerList } from "@weChatStore/customer";
|
||||
//获取触客宝基础信息
|
||||
export const chatInitAPIdata = async () => {
|
||||
try {
|
||||
@@ -52,12 +52,11 @@ export const chatInitAPIdata = async () => {
|
||||
|
||||
await asyncWeChatGroup(groupList);
|
||||
|
||||
//获取控制终端列表
|
||||
const kfUserList: KfUserListData[] = await getAgentList();
|
||||
|
||||
//获取用户列表
|
||||
await asyncKfUserList(kfUserList);
|
||||
// //获取控制终端列表
|
||||
// const kfUserList: KfUserListData[] = await getCustomerList();
|
||||
|
||||
// //获取用户列表
|
||||
// updateCustomerList(kfUserList);
|
||||
//获取标签列表
|
||||
const countLables = await getCountLables();
|
||||
await asyncCountLables(countLables);
|
||||
|
||||
0
Touchkebao/src/store/module/weChat/chatRecord.ts
Normal file
0
Touchkebao/src/store/module/weChat/chatRecord.ts
Normal file
0
Touchkebao/src/store/module/weChat/contacts.ts
Normal file
0
Touchkebao/src/store/module/weChat/contacts.ts
Normal file
0
Touchkebao/src/store/module/weChat/currentChat.ts
Normal file
0
Touchkebao/src/store/module/weChat/currentChat.ts
Normal file
41
Touchkebao/src/store/module/weChat/customer.data.ts
Normal file
41
Touchkebao/src/store/module/weChat/customer.data.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
export interface Customer {
|
||||
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;
|
||||
momentsMax: number;
|
||||
momentsNum: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
//Store State
|
||||
export interface CustomerState {
|
||||
//客服列表
|
||||
customerList: Customer[];
|
||||
//当前选中的客服
|
||||
currentCustomer: Customer | null;
|
||||
//更新客服列表
|
||||
updateCustomerList: (customerList: Customer[]) => void;
|
||||
//更新客服状态
|
||||
updateCustomerStatus: (customerId: number, status: string) => void;
|
||||
//更新当前选中的客服
|
||||
updateCurrentCustomer: (customer: Customer) => void;
|
||||
}
|
||||
56
Touchkebao/src/store/module/weChat/customer.ts
Normal file
56
Touchkebao/src/store/module/weChat/customer.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { Customer, CustomerState } from "./customer.data";
|
||||
export const useCustomerStore = create<CustomerState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
customerList: [], //客服列表
|
||||
currentCustomer: null, //当前选中的客服
|
||||
updateCustomerList: (customerList: Customer[]) => set({ customerList }), //更新客服列表
|
||||
updateCurrentCustomer: (customer: Customer) =>
|
||||
set({ currentCustomer: customer }), //更新当前选中的客服
|
||||
updateCustomerStatus: (customerId: number, status: string) =>
|
||||
set({
|
||||
customerList: get().customerList.map(customer =>
|
||||
customer.id === customerId ? { ...customer, status } : customer,
|
||||
),
|
||||
}), //更新客服状态
|
||||
}),
|
||||
{
|
||||
name: "customer-storage",
|
||||
partialize: state => ({
|
||||
customerList: [],
|
||||
currentCustomer: null,
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
/**
|
||||
* 更新当前选中的客服
|
||||
* @param customer 客服
|
||||
* @returns void
|
||||
*/
|
||||
export const updateCurrentCustomer = (customer: Customer) =>
|
||||
useCustomerStore.getState().updateCurrentCustomer(customer);
|
||||
/**
|
||||
* 更新客服列表
|
||||
* @param customerList 客服列表
|
||||
* @returns void
|
||||
*/
|
||||
export const updateCustomerList = (customerList: Customer[]) =>
|
||||
useCustomerStore.getState().updateCustomerList(customerList);
|
||||
/**
|
||||
* 获取当前选中的客服
|
||||
* @returns Customer | null
|
||||
*/
|
||||
export const getCurrentCustomer = () =>
|
||||
useCustomerStore.getState().currentCustomer;
|
||||
|
||||
/**
|
||||
* 更新客服状态
|
||||
* @param customerId 客服ID
|
||||
* @param status 状态
|
||||
* @returns void
|
||||
*/
|
||||
export const updateCustomerStatus = (customerId: number, status: string) =>
|
||||
useCustomerStore.getState().updateCustomerStatus(customerId, status);
|
||||
0
Touchkebao/src/store/module/weChat/message.ts
Normal file
0
Touchkebao/src/store/module/weChat/message.ts
Normal file
@@ -17,7 +17,10 @@
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
"@/*": ["src/*"],
|
||||
"@weChatStore/*": ["src/store/module/weChat/*"],
|
||||
"@storeModule/*": ["src/store/module/*"],
|
||||
"@apiModule/*": ["src/api/module/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
|
||||
@@ -7,6 +7,9 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve("src"),
|
||||
"@storeModule": path.resolve("src/store/module/"),
|
||||
"@weChatStore": path.resolve("src/store/module/weChat"),
|
||||
"@apiModule": path.resolve("src/api/module/"),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
|
||||
Reference in New Issue
Block a user