feat(联系人列表): 实现分组展示功能并重构组件结构
添加新的联系人分组功能,支持按分组展示联系人列表 重构联系人列表组件结构,将原有组件拆分为更清晰的模块 新增状态管理逻辑用于存储和获取分组联系人数据 移除不再使用的旧样式文件和组件
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user