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 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>
|
||||||
|
|||||||
@@ -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 }) => {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 根据标签组织联系人
|
* 根据标签组织联系人
|
||||||
|
|||||||
Reference in New Issue
Block a user