feat(客戶管理與微信功能增強): 在客戶管理頁面中,整合了從狀態管理獲取的聯絡人數據,並動態計算各分類的聯絡人數量。此外,新增了獲取分組列表的API,優化了聯絡人顯示邏輯,提升了用戶體驗。
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
/**
|
||||
* 根据标签组织联系人
|
||||
|
||||
Reference in New Issue
Block a user