feat(客戶管理與微信功能增強): 在客戶管理頁面中,整合了從狀態管理獲取的聯絡人數據,並動態計算各分類的聯絡人數量。此外,新增了獲取分組列表的API,優化了聯絡人顯示邏輯,提升了用戶體驗。

This commit is contained in:
2025-09-25 15:00:17 +08:00
parent 27dfb17e70
commit b3bf342320
3 changed files with 140 additions and 169 deletions

View File

@@ -1,7 +1,6 @@
import React, { useState } from "react"; import React, { useState, useMemo } from "react";
import PowerNavigation from "@/components/PowerNavtion"; import PowerNavigation from "@/components/PowerNavtion";
import { import {
StarOutlined,
SearchOutlined, SearchOutlined,
FilterOutlined, FilterOutlined,
MessageOutlined, MessageOutlined,
@@ -9,24 +8,9 @@ import {
} from "@ant-design/icons"; } from "@ant-design/icons";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
import { Button, Input, Row, Col } from "antd"; import { Button, Input, Row, Col } from "antd";
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
// 联系人数据类型 import { ContractData } from "@/pages/pc/ckbox/data";
interface Contact { // 直接使用 ContractData 类型
id: string;
name: string;
avatar?: string;
role: string;
company: string;
phone: string;
email?: string;
location: string;
tags: string[];
source: string;
lastContact: string;
notes?: string;
isStarred?: boolean;
category: "customer" | "potential" | "partner" | "friend";
}
// 头像组件 // 头像组件
const Avatar: React.FC<{ name: string; avatar?: string; size?: number }> = ({ const Avatar: React.FC<{ name: string; avatar?: string; size?: number }> = ({
@@ -83,68 +67,46 @@ const CustomerManagement: React.FC = () => {
const [activeTab, setActiveTab] = useState("customer"); const [activeTab, setActiveTab] = useState("customer");
const [searchValue, setSearchValue] = useState(""); const [searchValue, setSearchValue] = useState("");
// 模拟数据 // 获取联系人数据
const contacts: Contact[] = [ const getContractList = useCkChatStore(state => state.getContractList);
{ const contacts: ContractData[] = getContractList();
id: "1", console.log(contacts, "contacts");
name: "李先生",
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=li", // 动态计算各分类的联系人数量
role: "技术总监", const tabCounts = useMemo(() => {
company: "某科技公司", return {
phone: "138****8888", customer: contacts.filter(c => c.isPassed).length,
email: "li@company.com", potential: contacts.filter(c => !c.isPassed).length,
location: "北京", partner: 0, // 可以根据业务逻辑调整
tags: ["客户", "重要客户", "AI产品", "意向客户"], friend: 0, // 可以根据业务逻辑调整
source: "朋友推荐", };
lastContact: "2024/3/5", }, [contacts]);
notes: "对AI产品很感兴趣,预算充足",
isStarred: true,
category: "customer",
},
{
id: "2",
name: "张总",
role: "CEO",
company: "大型企业集团",
phone: "139****9999",
email: "zhang@enterprise.com",
location: "上海",
tags: ["潜在客户", "决策者", "大客户"],
source: "展会获客",
lastContact: "2024/3/4",
notes: "需要详细的ROI分析报告",
category: "potential",
},
{
id: "3",
name: "王女士",
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=wang",
role: "市场经理",
company: "中型贸易公司",
phone: "137****7777",
location: "深圳",
tags: ["客户", "中小企业", "价格敏感"],
source: "网络广告",
lastContact: "2024/3/3",
category: "customer",
},
];
const tabs = [ const tabs = [
{ key: "customer", label: "客户", count: 2 }, { key: "customer", label: "客户", count: tabCounts.customer },
{ key: "potential", label: "潜在客户", count: 1 }, { key: "potential", label: "潜在客户", count: tabCounts.potential },
{ key: "partner", label: "合作伙伴", count: 0 }, { key: "partner", label: "合作伙伴", count: tabCounts.partner },
{ key: "friend", label: "朋友", count: 0 }, { key: "friend", label: "朋友", count: tabCounts.friend },
]; ];
const filteredContacts = contacts.filter( // const filteredContacts = contacts.filter(contact => {
contact => // const isCategoryMatch =
contact.category === activeTab && // (activeTab === "customer" && contact.isPassed) ||
(searchValue === "" || // (activeTab === "potential" && !contact.isPassed) ||
contact.name.includes(searchValue) || // activeTab === "partner" ||
contact.company.includes(searchValue) || // activeTab === "friend";
contact.tags.some(tag => tag.includes(searchValue))),
); // const isSearchMatch =
// searchValue === "" ||
// contact.nickname?.includes(searchValue) ||
// contact.conRemark?.includes(searchValue) ||
// contact.alias?.includes(searchValue) ||
// contact.desc?.includes(searchValue) ||
// contact.labels?.some(tag => tag.includes(searchValue));
// return isCategoryMatch && isSearchMatch;
// });
const filteredContacts = contacts;
return ( return (
<div className={styles.container}> <div className={styles.container}>
@@ -160,7 +122,7 @@ const CustomerManagement: React.FC = () => {
{/* 搜索和筛选 */} {/* 搜索和筛选 */}
<div className={styles.searchBar}> <div className={styles.searchBar}>
<Input <Input
placeholder="搜索计划名称" placeholder="搜索好友姓名、公司或标签..."
value={searchValue} value={searchValue}
onChange={e => setSearchValue(e.target.value)} onChange={e => setSearchValue(e.target.value)}
prefix={<SearchOutlined />} prefix={<SearchOutlined />}
@@ -191,83 +153,95 @@ const CustomerManagement: React.FC = () => {
{/* 联系人卡片列表 */} {/* 联系人卡片列表 */}
<div className={styles.contactsList}> <div className={styles.contactsList}>
<Row gutter={16}> {filteredContacts.length === 0 ? (
{filteredContacts.map(contact => ( <div style={{ textAlign: "center", padding: "50px" }}>
<Col span={8} key={contact.id}> <p style={{ color: "#999" }}></p>
<div className={styles.contactCard}> </div>
<div className={styles.cardHeader}> ) : (
<div className={styles.contactInfo}> <Row gutter={16}>
<Avatar {filteredContacts.map(contact => (
name={contact.name} <Col span={8} key={contact.id || contact.serverId}>
avatar={contact.avatar} <div className={styles.contactCard}>
size={48} <div className={styles.cardHeader}>
/> <div className={styles.contactInfo}>
<div className={styles.nameSection}> <Avatar
<h3 className={styles.contactName}> name={
{contact.name} contact.conRemark ||
{contact.isStarred && ( contact.nickname ||
<StarOutlined className={styles.starIcon} /> contact.alias ||
)} "未知用户"
</h3> }
<p className={styles.roleCompany}> avatar={contact.avatar}
{contact.role} {"·"} {contact.company} size={48}
/>
<div className={styles.nameSection}>
<h3 className={styles.contactName}>
{contact.conRemark ||
contact.nickname ||
contact.alias ||
"未知用户"}
</h3>
<p className={styles.roleCompany}>
{"·"} {contact.desc || "未设置公司"}
</p>
</div>
</div>
</div>
<div className={styles.contactDetails}>
<div className={styles.contactInfo}>
<p className={styles.contactItem}>
<span className={styles.label}>:</span>{" "}
{contact.phone || "未设置电话"}
</p>
<p className={styles.contactItem}>
<span className={styles.label}>:</span>{" "}
{contact.region || contact.city || "未设置地区"}
</p>
<p className={styles.contactItem}>
<span className={styles.label}>ID:</span>{" "}
{contact.wechatId}
</p> </p>
</div> </div>
</div> </div>
</div>
<div className={styles.contactDetails}> <div className={styles.tagsSection}>
<div className={styles.contactInfo}> <div className={styles.tags}>
<p className={styles.contactItem}> {contact?.labels?.map((tag: string, index: number) => (
<span className={styles.label}>:</span>{" "} <span key={index} className={styles.tag}>
{contact.phone} {tag}
</p> </span>
{contact.email && ( ))}
<p className={styles.contactItem}> </div>
<span className={styles.label}>:</span>{" "} <span className={styles.source}></span>
{contact.email} </div>
</p>
)} <div className={styles.lastContact}>
<p className={styles.contactItem}> :{" "}
<span className={styles.label}>:</span>{" "} {contact.lastMessageTime
{contact.location} ? new Date(contact.lastMessageTime).toLocaleDateString()
</p> : "未联系"}
</div>
{contact.signature && (
<div className={styles.notes}>{contact.signature}</div>
)}
<div className={styles.actions}>
<button className={styles.actionButton}>
<MessageOutlined />
</button>
<button className={styles.actionButton}>
<PhoneOutlined />
</button>
</div> </div>
</div> </div>
</Col>
<div className={styles.tagsSection}> ))}
<div className={styles.tags}> </Row>
{contact.tags.map((tag, index) => ( )}
<span key={index} className={styles.tag}>
{tag}
</span>
))}
</div>
<span className={styles.source}>{contact.source}</span>
</div>
<div className={styles.lastContact}>
: {contact.lastContact}
</div>
{contact.notes && (
<div className={styles.notes}>{contact.notes}</div>
)}
<div className={styles.actions}>
<button className={styles.actionButton}>
<MessageOutlined />
</button>
<button className={styles.actionButton}>
<PhoneOutlined />
</button>
</div>
</div>
</Col>
))}
</Row>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -37,6 +37,11 @@ export function getContactList(params) {
export function getGroupList(params) { export function getGroupList(params) {
return request("/v1/kefu/wechatChatroom/list", params, "GET"); return request("/v1/kefu/wechatChatroom/list", params, "GET");
} }
// 分组列表
export function getLabelsListByGroup(params) {
return request("/v1/kefu/wechatGroup/list", params, "GET");
}
//==============-原接口================= //==============-原接口=================
// 获取联系人列表 // 获取联系人列表
// export const getContactList = (params: { prevId: number; count: number }) => { // export const getContactList = (params: { prevId: number; count: number }) => {

View File

@@ -14,6 +14,7 @@ import {
getContactList, getContactList,
getGroupList, getGroupList,
getAgentList, getAgentList,
getLabelsListByGroup,
} from "./api"; } from "./api";
import { useUserStore } from "@/store/module/user"; import { useUserStore } from "@/store/module/user";
@@ -41,10 +42,10 @@ export const chatInitAPIdata = async () => {
await asyncWeChatGroup(groupList); await asyncWeChatGroup(groupList);
// 提取不重复的wechatAccountId组 // 提取不重复的wechatAccountId组
const uniqueWechatAccountIds: number[] = getUniqueWechatAccountIds( // const uniqueWechatAccountIds: number[] = getUniqueWechatAccountIds(
contractList, // contractList,
groupList, // groupList,
); // );
//获取控制终端列表 //获取控制终端列表
const kfUserList: KfUserListData[] = await getAgentList(); const kfUserList: KfUserListData[] = await getAgentList();
@@ -53,8 +54,8 @@ export const chatInitAPIdata = async () => {
await asyncKfUserList(kfUserList); await asyncKfUserList(kfUserList);
//获取标签列表 //获取标签列表
// const countLables = await getCountLables(); const countLables = await getCountLables();
// await asyncCountLables(countLables); await asyncCountLables(countLables);
//获取消息会话列表并按lastUpdateTime排序 //获取消息会话列表并按lastUpdateTime排序
const filterUserSessions = contractList?.filter( const filterUserSessions = contractList?.filter(
@@ -121,15 +122,9 @@ export const initSocket = () => {
}; };
export const getCountLables = async () => { export const getCountLables = async () => {
const LablesRes = await Promise.all( const Result = await getLabelsListByGroup({});
[1, 2].map(item => const LablesRes = Result.list;
WechatGroup({ return [
groupType: item,
}),
),
);
const [friend, group] = LablesRes;
const countLables = [
...[ ...[
{ {
id: 0, id: 0,
@@ -137,8 +132,7 @@ export const getCountLables = async () => {
groupType: 2, groupType: 2,
}, },
], ],
...group, ...LablesRes,
...friend,
...[ ...[
{ {
id: 0, id: 0,
@@ -147,8 +141,6 @@ export const getCountLables = async () => {
}, },
], ],
]; ];
return countLables;
}; };
/** /**
* 根据标签组织联系人 * 根据标签组织联系人