更新TypeScript配置以支持新的模块路径别名,重命名获取客服列表的API函数,替换相关组件,移除不再使用的垂直用户列表组件及其样式,提升代码结构和可读性。

This commit is contained in:
超级老白兔
2025-10-23 12:35:30 +08:00
parent ef45bedf83
commit 81f225d9cb
15 changed files with 292 additions and 87 deletions

View File

@@ -0,0 +1,5 @@
import request from "@/api/request";
//获取客服列表
export function getCustomerList() {
return request("/v1/kefu/customerService/list", {}, "GET");
}

View File

@@ -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");
}

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>
{/* 左侧联系人边栏 */}

View File

@@ -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);

View 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;
}

View 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);

View 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"]

View File

@@ -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: {