feat(联系人列表): 实现分组展示功能并重构组件结构

添加新的联系人分组功能,支持按分组展示联系人列表
重构联系人列表组件结构,将原有组件拆分为更清晰的模块
新增状态管理逻辑用于存储和获取分组联系人数据
移除不再使用的旧样式文件和组件
This commit is contained in:
超级老白兔
2025-08-28 18:17:40 +08:00
parent 64b5518309
commit b4eb2919b1
10 changed files with 134 additions and 178 deletions

View File

@@ -1,78 +0,0 @@
.contractList {
height: 100%;
overflow-y: auto;
.contractItem {
padding: 12px 16px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
transition: background-color 0.3s;
&:hover {
background-color: #f5f5f5;
}
&:last-child {
border-bottom: none;
}
.contractInfo {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
.contractDetails {
flex: 1;
min-width: 0;
.contractName {
font-size: 14px;
font-weight: 500;
color: #262626;
margin-bottom: 2px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.contractPhone {
font-size: 12px;
color: #8c8c8c;
margin-bottom: 2px;
}
.contractStatus {
font-size: 11px;
color: #bfbfbf;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
// 响应式设计
@media (max-width: 768px) {
.contractList {
.contractItem {
padding: 10px 12px;
.contractInfo {
gap: 10px;
.contractDetails {
.contractName {
font-size: 13px;
}
.contractPhone {
font-size: 11px;
}
}
}
}
}
}

View File

@@ -1,48 +0,0 @@
import React from "react";
import { List, Avatar, Badge } from "antd";
import { UserOutlined } from "@ant-design/icons";
import { ContractData } from "../../data";
import styles from "./ContactList.module.scss";
interface ContactListProps {
contracts: ContractData[];
onContactClick: (contract: ContractData) => void;
}
const ContactList: React.FC<ContactListProps> = ({
contracts,
onContactClick,
}) => {
return (
<div className={styles.contractList}>
<List
dataSource={contracts}
renderItem={contract => (
<List.Item
className={styles.contractItem}
onClick={() => onContactClick(contract)}
>
<div className={styles.contractInfo}>
<Badge dot={contract.online} color="#52c41a" offset={[-2, 2]}>
<Avatar
size={40}
src={contract.avatar}
icon={<UserOutlined />}
/>
</Badge>
<div className={styles.contractDetails}>
<div className={styles.contractName}>{contract.name}</div>
<div className={styles.contractPhone}>{contract.phone}</div>
{contract.status && (
<div className={styles.contractStatus}>{contract.status}</div>
)}
</div>
</div>
</List.Item>
)}
/>
</div>
);
};
export default ContactList;

View File

@@ -11,6 +11,42 @@
border-bottom: 1px solid #f0f0f0;
}
.groupCollapse {
width: 100%;
background-color: transparent;
border: none;
:global(.ant-collapse-item) {
border-bottom: 1px solid #f0f0f0;
}
:global(.ant-collapse-header) {
padding: 10px 15px !important;
font-weight: bold;
}
:global(.ant-collapse-content-box) {
padding: 0 !important;
}
}
.groupHeader {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.contactCount {
font-size: 12px;
color: #999;
font-weight: normal;
}
.groupPanel {
background-color: transparent;
}
.list {
flex: 1;
overflow-y: auto;

View File

@@ -0,0 +1,88 @@
import React, { useState } from "react";
import { List, Avatar, Collapse } from "antd";
import { ContractData } from "@/pages/pc/ckbox/data";
import styles from "./WechatFriends.module.scss";
import { useCkChatStore } from "@/store/module/ckchat";
interface WechatFriendsProps {
contracts: ContractData[];
onContactClick: (contract: ContractData) => void;
selectedContactId?: ContractData;
}
const { Panel } = Collapse;
const ContactListSimple: React.FC<WechatFriendsProps> = ({
contracts,
onContactClick,
selectedContactId,
}) => {
const newContractList = useCkChatStore(state => state.newContractList);
const [activeKey, setActiveKey] = useState<string[]>(["0"]); // 默认展开第一个分组
// 渲染联系人项
const renderContactItem = (contact: ContractData) => (
<List.Item
key={contact.id}
onClick={() => onContactClick(contact)}
className={`${styles.contractItem} ${contact.id === selectedContactId?.id ? styles.selected : ""}`}
>
<div className={styles.avatarContainer}>
<Avatar
src={contact.avatar}
icon={!contact.avatar && <span>{contact.nickname.charAt(0)}</span>}
className={styles.avatar}
/>
</div>
<div className={styles.contractInfo}>
<div className={styles.name}>
{contact.conRemark || contact.nickname}
</div>
</div>
</List.Item>
);
return (
<div className={styles.contractListSimple}>
{newContractList && newContractList.length > 0 ? (
<Collapse
className={styles.groupCollapse}
activeKey={activeKey}
onChange={keys => setActiveKey(keys as string[])}
>
{newContractList.map((group, index) => (
<Panel
header={
<div className={styles.groupHeader}>
<span>{group.groupName}</span>
<span className={styles.contactCount}>
{group.contacts.length}
</span>
</div>
}
key={index.toString()}
className={styles.groupPanel}
>
<List
className={styles.list}
dataSource={group.contacts}
renderItem={renderContactItem}
/>
</Panel>
))}
</Collapse>
) : (
<>
<div className={styles.header}></div>
<List
className={styles.list}
dataSource={contracts}
renderItem={renderContactItem}
/>
</>
)}
</div>
);
};
export default ContactListSimple;

View File

@@ -1,50 +0,0 @@
import React from "react";
import { List, Avatar } from "antd";
import { ContractData } from "@/pages/pc/ckbox/data";
import styles from "./WechatFriends.module.scss";
interface WechatFriendsProps {
contracts: ContractData[];
onContactClick: (contract: ContractData) => void;
selectedContactId?: ContractData;
}
const ContactListSimple: React.FC<WechatFriendsProps> = ({
contracts,
onContactClick,
selectedContactId,
}) => {
return (
<div className={styles.contractListSimple}>
<div className={styles.header}></div>
<List
className={styles.list}
dataSource={contracts}
renderItem={contract => (
<List.Item
key={contract.id}
onClick={() => onContactClick(contract)}
className={`${styles.contractItem} ${contract.id === selectedContactId?.id ? styles.selected : ""}`}
>
<div className={styles.avatarContainer}>
<Avatar
src={contract.avatar}
icon={
!contract.avatar && <span>{contract.nickname.charAt(0)}</span>
}
className={styles.avatar}
/>
</div>
<div className={styles.contractInfo}>
<div className={styles.name}>
{contract.conRemark || contract.nickname}
</div>
</div>
</List.Item>
)}
/>
</div>
);
};
export default ContactListSimple;

View File

@@ -7,7 +7,7 @@ import {
MessageOutlined,
} from "@ant-design/icons";
import { ContractData } from "@/pages/pc/ckbox/data";
import WechatFriendsModule from "./WechatFriendsModule";
import WechatFriends from "./WechatFriends";
import MessageList from "./MessageList/index";
import styles from "./SidebarMenu.module.scss";
import { getChatSessions } from "@/store/module/ckchat";
@@ -151,7 +151,7 @@ const SidebarMenu: React.FC<SidebarMenuProps> = ({
);
case "contracts":
return (
<WechatFriendsModule
<WechatFriends
contracts={getFilteredContacts()}
onContactClick={onContactClick}
selectedContactId={currentChat}

View File

@@ -3,6 +3,7 @@ import {
asyncKfUserList,
asyncContractList,
asyncChatSessions,
asyncNewContractList,
} from "@/store/module/ckchat";
import { useWebSocketStore } from "@/store/module/websocket";
@@ -26,6 +27,8 @@ export const chatInitAPIdata = async () => {
//构建联系人列表标签
const newContractList = await createContractList(contractList);
// 会话列表分组
asyncNewContractList(newContractList);
//获取联系人列表
asyncContractList(contractList);

View File

@@ -71,6 +71,7 @@ export interface CkChatState {
contractList: ContractData[];
chatSessions: any[];
kfUserList: KfUserListData[];
newContractList: { groupName: string; contacts: any[] }[];
getkfUserList: () => KfUserListData[];
asyncKfUserList: (data: KfUserListData[]) => void;
asyncContractList: (data: ContractData[]) => void;

View File

@@ -20,6 +20,10 @@ export const useCkChatStore = createPersistStore<CkChatState>(
asyncNewContractList: data => {
set({ newContractList: data });
},
getNewContractList: () => {
const state = useCkChatStore.getState();
return state.newContractList;
},
// 异步设置会话列表
asyncChatSessions: data => {
set({ chatSessions: data });