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 {
StarOutlined,
SearchOutlined,
FilterOutlined,
MessageOutlined,
@@ -9,24 +8,9 @@ import {
} from "@ant-design/icons";
import styles from "./index.module.scss";
import { Button, Input, Row, Col } from "antd";
// 联系人数据类型
interface Contact {
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";
}
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
import { ContractData } from "@/pages/pc/ckbox/data";
// 直接使用 ContractData 类型
// 头像组件
const Avatar: React.FC<{ name: string; avatar?: string; size?: number }> = ({
@@ -83,68 +67,46 @@ const CustomerManagement: React.FC = () => {
const [activeTab, setActiveTab] = useState("customer");
const [searchValue, setSearchValue] = useState("");
// 模拟数据
const contacts: Contact[] = [
{
id: "1",
name: "李先生",
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=li",
role: "技术总监",
company: "某科技公司",
phone: "138****8888",
email: "li@company.com",
location: "北京",
tags: ["客户", "重要客户", "AI产品", "意向客户"],
source: "朋友推荐",
lastContact: "2024/3/5",
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 getContractList = useCkChatStore(state => state.getContractList);
const contacts: ContractData[] = getContractList();
console.log(contacts, "contacts");
// 动态计算各分类的联系人数量
const tabCounts = useMemo(() => {
return {
customer: contacts.filter(c => c.isPassed).length,
potential: contacts.filter(c => !c.isPassed).length,
partner: 0, // 可以根据业务逻辑调整
friend: 0, // 可以根据业务逻辑调整
};
}, [contacts]);
const tabs = [
{ key: "customer", label: "客户", count: 2 },
{ key: "potential", label: "潜在客户", count: 1 },
{ key: "partner", label: "合作伙伴", count: 0 },
{ key: "friend", label: "朋友", count: 0 },
{ key: "customer", label: "客户", count: tabCounts.customer },
{ key: "potential", label: "潜在客户", count: tabCounts.potential },
{ key: "partner", label: "合作伙伴", count: tabCounts.partner },
{ key: "friend", label: "朋友", count: tabCounts.friend },
];
const filteredContacts = contacts.filter(
contact =>
contact.category === activeTab &&
(searchValue === "" ||
contact.name.includes(searchValue) ||
contact.company.includes(searchValue) ||
contact.tags.some(tag => tag.includes(searchValue))),
);
// const filteredContacts = contacts.filter(contact => {
// const isCategoryMatch =
// (activeTab === "customer" && contact.isPassed) ||
// (activeTab === "potential" && !contact.isPassed) ||
// activeTab === "partner" ||
// activeTab === "friend";
// 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 (
<div className={styles.container}>
@@ -160,7 +122,7 @@ const CustomerManagement: React.FC = () => {
{/* 搜索和筛选 */}
<div className={styles.searchBar}>
<Input
placeholder="搜索计划名称"
placeholder="搜索好友姓名、公司或标签..."
value={searchValue}
onChange={e => setSearchValue(e.target.value)}
prefix={<SearchOutlined />}
@@ -191,83 +153,95 @@ const CustomerManagement: React.FC = () => {
{/* 联系人卡片列表 */}
<div className={styles.contactsList}>
<Row gutter={16}>
{filteredContacts.map(contact => (
<Col span={8} key={contact.id}>
<div className={styles.contactCard}>
<div className={styles.cardHeader}>
<div className={styles.contactInfo}>
<Avatar
name={contact.name}
avatar={contact.avatar}
size={48}
/>
<div className={styles.nameSection}>
<h3 className={styles.contactName}>
{contact.name}
{contact.isStarred && (
<StarOutlined className={styles.starIcon} />
)}
</h3>
<p className={styles.roleCompany}>
{contact.role} {"·"} {contact.company}
{filteredContacts.length === 0 ? (
<div style={{ textAlign: "center", padding: "50px" }}>
<p style={{ color: "#999" }}></p>
</div>
) : (
<Row gutter={16}>
{filteredContacts.map(contact => (
<Col span={8} key={contact.id || contact.serverId}>
<div className={styles.contactCard}>
<div className={styles.cardHeader}>
<div className={styles.contactInfo}>
<Avatar
name={
contact.conRemark ||
contact.nickname ||
contact.alias ||
"未知用户"
}
avatar={contact.avatar}
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>
</div>
</div>
</div>
<div className={styles.contactDetails}>
<div className={styles.contactInfo}>
<p className={styles.contactItem}>
<span className={styles.label}>:</span>{" "}
{contact.phone}
</p>
{contact.email && (
<p className={styles.contactItem}>
<span className={styles.label}>:</span>{" "}
{contact.email}
</p>
)}
<p className={styles.contactItem}>
<span className={styles.label}>:</span>{" "}
{contact.location}
</p>
<div className={styles.tagsSection}>
<div className={styles.tags}>
{contact?.labels?.map((tag: string, index: number) => (
<span key={index} className={styles.tag}>
{tag}
</span>
))}
</div>
<span className={styles.source}></span>
</div>
<div className={styles.lastContact}>
:{" "}
{contact.lastMessageTime
? new Date(contact.lastMessageTime).toLocaleDateString()
: "未联系"}
</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 className={styles.tagsSection}>
<div className={styles.tags}>
{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>
</Col>
))}
</Row>
)}
</div>
</div>
</div>

View File

@@ -37,6 +37,11 @@ export function getContactList(params) {
export function getGroupList(params) {
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 }) => {

View File

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