From 27dfb17e706b148a5bc11dc64d1a72f039864c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 25 Sep 2025 14:32:42 +0800 Subject: [PATCH 1/4] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PowerNavtion/index.module.scss | 1 + .../src/components/PowerNavtion/index.tsx | 3 + .../customer-management/index.module.scss | 378 ++++++++++++++++-- .../powerCenter/customer-management/index.tsx | 263 +++++++++++- 4 files changed, 608 insertions(+), 37 deletions(-) diff --git a/Touchkebao/src/components/PowerNavtion/index.module.scss b/Touchkebao/src/components/PowerNavtion/index.module.scss index b57617ad..04110530 100644 --- a/Touchkebao/src/components/PowerNavtion/index.module.scss +++ b/Touchkebao/src/components/PowerNavtion/index.module.scss @@ -11,6 +11,7 @@ align-items: center; min-height: 64px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + justify-content: space-between; .headerLeft { display: flex; diff --git a/Touchkebao/src/components/PowerNavtion/index.tsx b/Touchkebao/src/components/PowerNavtion/index.tsx index 42b9b002..b2255d1d 100644 --- a/Touchkebao/src/components/PowerNavtion/index.tsx +++ b/Touchkebao/src/components/PowerNavtion/index.tsx @@ -13,6 +13,7 @@ export interface PowerNavigationProps { onBackClick?: () => void; className?: string; style?: React.CSSProperties; + rightContent?: React.ReactNode; } const PowerNavigation: React.FC = ({ @@ -23,6 +24,7 @@ const PowerNavigation: React.FC = ({ onBackClick, className, style, + rightContent, }) => { const navigate = useNavigate(); @@ -57,6 +59,7 @@ const PowerNavigation: React.FC = ({ {subtitle && {subtitle}} +
{rightContent}
); }; diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.module.scss b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.module.scss index bf1ac1e6..a290c73b 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.module.scss @@ -1,43 +1,357 @@ .container { padding: 24px; - background: #fff; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } - -.header { - margin-bottom: 24px; - - h1 { - font-size: 24px; - font-weight: 600; - color: #262626; - margin: 0 0 8px 0; - } - - p { - font-size: 14px; - color: #8c8c8c; - margin: 0; - } +.searchBar { + display: flex; + align-items: center; + gap: 8px; + padding: 16px 0 8px 0; } .content { min-height: 400px; } -.placeholder { +// 页面头部 +.header { display: flex; - align-items: center; - justify-content: center; - height: 300px; - background: #fafafa; - border: 1px dashed #d9d9d9; - border-radius: 6px; - - p { - font-size: 16px; - color: #8c8c8c; - margin: 0; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 24px; + + .headerLeft { + .title { + font-size: 24px; + font-weight: 600; + color: #262626; + margin: 0 0 8px 0; + } + + .subtitle { + font-size: 14px; + color: #8c8c8c; + margin: 0; + } } -} \ No newline at end of file + + .headerRight { + .addButton { + background: #1890ff; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + cursor: pointer; + transition: background-color 0.3s; + + &:hover { + background: #40a9ff; + } + } + } +} + +// 搜索和筛选区域 +.searchSection { + display: flex; + gap: 12px; + margin-bottom: 24px; + + .searchBox { + flex: 1; + position: relative; + display: flex; + align-items: center; + + .searchIcon { + position: absolute; + left: 12px; + color: #8c8c8c; + font-size: 16px; + } + + .searchInput { + width: 100%; + height: 40px; + padding: 0 12px 0 40px; + border: 1px solid #d9d9d9; + border-radius: 6px; + font-size: 14px; + outline: none; + transition: border-color 0.3s; + + &:focus { + border-color: #1890ff; + } + + &::placeholder { + color: #8c8c8c; + } + } + } + + .filterButton { + display: flex; + align-items: center; + gap: 6px; + height: 40px; + padding: 0 16px; + background: white; + border: 1px solid #d9d9d9; + border-radius: 6px; + font-size: 14px; + color: #262626; + cursor: pointer; + transition: all 0.3s; + + &:hover { + border-color: #1890ff; + color: #1890ff; + } + } +} + +// 标签页 +.tabs { + display: flex; + gap: 0; + margin-bottom: 24px; + border-bottom: 1px solid #f0f0f0; + + .tab { + padding: 12px 24px; + background: none; + border: none; + border-bottom: 2px solid transparent; + font-size: 14px; + color: #8c8c8c; + cursor: pointer; + transition: all 0.3s; + + &:hover { + color: #1890ff; + } + + &.activeTab { + color: #1890ff; + border-bottom-color: #1890ff; + } + } +} + +// 联系人列表 +.contactsList { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 20px; +} + +// 联系人卡片 +.contactCard { + background: white; + border: 1px solid #f0f0f0; + border-radius: 8px; + padding: 20px; + transition: all 0.3s; + height: 100%; + min-height: 380px; + display: flex; + flex-direction: column; + + &:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + border-color: #d9d9d9; + } + + .cardHeader { + margin-bottom: 16px; + + .contactInfo { + display: flex; + align-items: flex-start; + gap: 12px; + } + + .nameSection { + flex: 1; + + .contactName { + display: flex; + align-items: center; + gap: 8px; + font-size: 16px; + font-weight: 600; + color: #262626; + margin: 0 0 4px 0; + + .starIcon { + color: #faad14; + font-size: 14px; + } + } + + .roleCompany { + font-size: 14px; + color: #8c8c8c; + margin: 0; + } + } + } + + // 头像样式 + .avatar { + border-radius: 50%; + object-fit: cover; + border: 2px solid #f0f0f0; + } + + .avatarPlaceholder { + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-weight: 600; + border: 2px solid #f0f0f0; + } + + .contactDetails { + margin-bottom: 16px; + flex: 1; + + .contactItem { + display: flex; + margin: 0 0 8px 0; + font-size: 14px; + + .label { + color: #8c8c8c; + margin-right: 8px; + min-width: 40px; + } + } + } + + .tagsSection { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + + .tags { + display: flex; + flex-wrap: wrap; + gap: 6px; + + .tag { + background: #f5f5f5; + color: #595959; + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; + } + } + + .source { + font-size: 12px; + color: #8c8c8c; + } + } + + .lastContact { + font-size: 12px; + color: #8c8c8c; + margin-bottom: 12px; + } + + .notes { + background: #f9f9f9; + padding: 8px 12px; + border-radius: 4px; + font-size: 12px; + color: #595959; + margin-bottom: 16px; + line-height: 1.4; + } + + .actions { + display: flex; + gap: 12px; + margin-top: auto; + + .actionButton { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + height: 32px; + background: white; + border: 1px solid #d9d9d9; + border-radius: 4px; + font-size: 12px; + color: #595959; + cursor: pointer; + transition: all 0.3s; + + &:hover { + border-color: #1890ff; + color: #1890ff; + } + + &:first-child:hover { + background: #e6f7ff; + } + + &:last-child:hover { + background: #f6ffed; + border-color: #52c41a; + color: #52c41a; + } + } + } +} + +// 响应式设计 +@media (max-width: 768px) { + .contactsList { + grid-template-columns: 1fr; + } + + .contactCard { + min-height: 350px; + } + + .header { + flex-direction: column; + gap: 16px; + align-items: flex-start; + + .headerRight { + width: 100%; + + .addButton { + width: 100%; + } + } + } + + .searchSection { + flex-direction: column; + + .filterButton { + width: 100%; + justify-content: center; + } + } + + .tabs { + overflow-x: auto; + white-space: nowrap; + + .tab { + flex-shrink: 0; + } + } +} diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx index 5ac24b2f..475d9cdb 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx @@ -1,20 +1,273 @@ -import React from "react"; +import React, { useState } from "react"; import PowerNavigation from "@/components/PowerNavtion"; +import { + StarOutlined, + SearchOutlined, + FilterOutlined, + MessageOutlined, + PhoneOutlined, +} 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"; +} + +// 头像组件 +const Avatar: React.FC<{ name: string; avatar?: string; size?: number }> = ({ + name, + avatar, + size = 40, +}) => { + const getInitials = (name: string) => { + return name.charAt(0).toUpperCase(); + }; + + const getAvatarColor = (name: string) => { + const colors = [ + "#1890ff", + "#52c41a", + "#faad14", + "#f5222d", + "#722ed1", + "#13c2c2", + "#eb2f96", + "#fa8c16", + ]; + const index = name.charCodeAt(0) % colors.length; + return colors[index]; + }; + + if (avatar) { + return ( + {name} + ); + } + + return ( +
+ {getInitials(name)} +
+ ); +}; 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 tabs = [ + { key: "customer", label: "客户", count: 2 }, + { key: "potential", label: "潜在客户", count: 1 }, + { key: "partner", label: "合作伙伴", count: 0 }, + { key: "friend", label: "朋友", count: 0 }, + ]; + + const filteredContacts = contacts.filter( + contact => + contact.category === activeTab && + (searchValue === "" || + contact.name.includes(searchValue) || + contact.company.includes(searchValue) || + contact.tags.some(tag => tag.includes(searchValue))), + ); + return (
添加好友} /> +
- {/* 功能内容待开发 */} -
-

客户好友管理功能正在开发中...

+ {/* 搜索和筛选 */} +
+ setSearchValue(e.target.value)} + prefix={} + allowClear + size="large" + /> + +
+ {/* 标签页 */} +
+ {tabs.map(tab => ( + + ))} +
+ + {/* 联系人卡片列表 */} +
+ + {filteredContacts.map(contact => ( + +
+
+
+ +
+

+ {contact.name} + {contact.isStarred && ( + + )} +

+

+ {contact.role} {"·"} {contact.company} +

+
+
+
+ +
+
+

+ 电话:{" "} + {contact.phone} +

+ {contact.email && ( +

+ 邮箱:{" "} + {contact.email} +

+ )} +

+ 地区:{" "} + {contact.location} +

+
+
+ +
+
+ {contact.tags.map((tag, index) => ( + + {tag} + + ))} +
+ {contact.source} +
+ +
+ 最后联系: {contact.lastContact} +
+ + {contact.notes && ( +
{contact.notes}
+ )} + +
+ + +
+
+ + ))} +
From b3bf342320b9e15885ef56451806f2f237bc016e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 25 Sep 2025 15:00:17 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat(=E5=AE=A2=E6=88=B6=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E8=88=87=E5=BE=AE=E4=BF=A1=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=B7?= =?UTF-8?q?):=20=E5=9C=A8=E5=AE=A2=E6=88=B6=E7=AE=A1=E7=90=86=E9=A0=81?= =?UTF-8?q?=E9=9D=A2=E4=B8=AD=EF=BC=8C=E6=95=B4=E5=90=88=E4=BA=86=E5=BE=9E?= =?UTF-8?q?=E7=8B=80=E6=85=8B=E7=AE=A1=E7=90=86=E7=8D=B2=E5=8F=96=E7=9A=84?= =?UTF-8?q?=E8=81=AF=E7=B5=A1=E4=BA=BA=E6=95=B8=E6=93=9A=EF=BC=8C=E4=B8=A6?= =?UTF-8?q?=E5=8B=95=E6=85=8B=E8=A8=88=E7=AE=97=E5=90=84=E5=88=86=E9=A1=9E?= =?UTF-8?q?=E7=9A=84=E8=81=AF=E7=B5=A1=E4=BA=BA=E6=95=B8=E9=87=8F=E3=80=82?= =?UTF-8?q?=E6=AD=A4=E5=A4=96=EF=BC=8C=E6=96=B0=E5=A2=9E=E4=BA=86=E7=8D=B2?= =?UTF-8?q?=E5=8F=96=E5=88=86=E7=B5=84=E5=88=97=E8=A1=A8=E7=9A=84API?= =?UTF-8?q?=EF=BC=8C=E5=84=AA=E5=8C=96=E4=BA=86=E8=81=AF=E7=B5=A1=E4=BA=BA?= =?UTF-8?q?=E9=A1=AF=E7=A4=BA=E9=82=8F=E8=BC=AF=EF=BC=8C=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E4=BA=86=E7=94=A8=E6=88=B6=E9=AB=94=E9=A9=97=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../powerCenter/customer-management/index.tsx | 274 ++++++++---------- Touchkebao/src/pages/pc/ckbox/weChat/api.ts | 5 + Touchkebao/src/pages/pc/ckbox/weChat/main.ts | 30 +- 3 files changed, 140 insertions(+), 169 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx index 475d9cdb..733332b3 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx @@ -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 (
@@ -160,7 +122,7 @@ const CustomerManagement: React.FC = () => { {/* 搜索和筛选 */}
setSearchValue(e.target.value)} prefix={} @@ -191,83 +153,95 @@ const CustomerManagement: React.FC = () => { {/* 联系人卡片列表 */}
- - {filteredContacts.map(contact => ( - -
-
-
- -
-

- {contact.name} - {contact.isStarred && ( - - )} -

-

- {contact.role} {"·"} {contact.company} + {filteredContacts.length === 0 ? ( +

+

暂无联系人数据

+
+ ) : ( + + {filteredContacts.map(contact => ( + +
+
+
+ +
+

+ {contact.conRemark || + contact.nickname || + contact.alias || + "未知用户"} +

+

+ 客户 {"·"} {contact.desc || "未设置公司"} +

+
+
+
+ +
+
+

+ 电话:{" "} + {contact.phone || "未设置电话"} +

+

+ 地区:{" "} + {contact.region || contact.city || "未设置地区"} +

+

+ 微信ID:{" "} + {contact.wechatId}

-
-
-
-

- 电话:{" "} - {contact.phone} -

- {contact.email && ( -

- 邮箱:{" "} - {contact.email} -

- )} -

- 地区:{" "} - {contact.location} -

+
+
+ {contact?.labels?.map((tag: string, index: number) => ( + + {tag} + + ))} +
+ 微信好友 +
+ +
+ 最后联系:{" "} + {contact.lastMessageTime + ? new Date(contact.lastMessageTime).toLocaleDateString() + : "未联系"} +
+ + {contact.signature && ( +
{contact.signature}
+ )} + +
+ +
- -
-
- {contact.tags.map((tag, index) => ( - - {tag} - - ))} -
- {contact.source} -
- -
- 最后联系: {contact.lastContact} -
- - {contact.notes && ( -
{contact.notes}
- )} - -
- - -
-
- - ))} -
+ + ))} + + )}
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/api.ts b/Touchkebao/src/pages/pc/ckbox/weChat/api.ts index 74b84462..4bdac18d 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/api.ts +++ b/Touchkebao/src/pages/pc/ckbox/weChat/api.ts @@ -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 }) => { diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/main.ts b/Touchkebao/src/pages/pc/ckbox/weChat/main.ts index 08f91c03..3dee0af5 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/main.ts +++ b/Touchkebao/src/pages/pc/ckbox/weChat/main.ts @@ -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; }; /** * 根据标签组织联系人 From 5acdd13966a00a1befb16144f8b9ccbac7e464d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 25 Sep 2025 15:39:44 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat(=E5=AE=A2=E6=88=B6=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E9=A0=81=E9=9D=A2=E5=84=AA=E5=8C=96):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=AE=A2=E6=88=B6=E7=AE=A1=E7=90=86=E9=A0=81=E9=9D=A2=EF=BC=8C?= =?UTF-8?q?=E8=AA=BF=E6=95=B4=E8=81=AF=E7=B5=A1=E4=BA=BA=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E7=9A=84=E6=9C=80=E5=B0=8F=E9=AB=98=E5=BA=A6=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84=E6=A8=A3=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E4=B8=A6=E6=95=B4=E5=90=88=E8=81=AF=E7=B5=A1=E4=BA=BA?= =?UTF-8?q?=E6=95=B8=E6=93=9A=E5=8A=A0=E8=BC=89=E9=82=8F=E8=BC=AF=EF=BC=8C?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E9=A0=81=E9=9D=A2=E6=80=A7=E8=83=BD=E8=88=87?= =?UTF-8?q?=E7=94=A8=E6=88=B6=E9=AB=94=E9=A9=97=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../customer-management/index.module.scss | 4 +- .../powerCenter/customer-management/index.tsx | 432 +++++++++++------- Touchkebao/src/styles/global.scss | 8 + 3 files changed, 275 insertions(+), 169 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.module.scss b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.module.scss index a290c73b..2eb4278c 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.module.scss @@ -116,7 +116,6 @@ .tabs { display: flex; gap: 0; - margin-bottom: 24px; border-bottom: 1px solid #f0f0f0; .tab { @@ -155,7 +154,6 @@ padding: 20px; transition: all 0.3s; height: 100%; - min-height: 380px; display: flex; flex-direction: column; @@ -320,7 +318,7 @@ } .contactCard { - min-height: 350px; + min-height: 175px; } .header { diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx index 733332b3..53914ab0 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo } from "react"; +import React, { useState, useEffect } from "react"; import PowerNavigation from "@/components/PowerNavtion"; import { SearchOutlined, @@ -7,11 +7,10 @@ import { PhoneOutlined, } from "@ant-design/icons"; import styles from "./index.module.scss"; -import { Button, Input, Row, Col } from "antd"; -import { useCkChatStore } from "@/store/module/ckchat/ckchat"; +import { Button, Input, Row, Col, Pagination, Spin, message } from "antd"; +import { getContactList } from "@/pages/pc/ckbox/weChat/api"; import { ContractData } from "@/pages/pc/ckbox/data"; -// 直接使用 ContractData 类型 - +import Layout from "@/components/Layout/LayoutFiexd"; // 头像组件 const Avatar: React.FC<{ name: string; avatar?: string; size?: number }> = ({ name, @@ -64,187 +63,288 @@ const Avatar: React.FC<{ name: string; avatar?: string; size?: number }> = ({ }; const CustomerManagement: React.FC = () => { - const [activeTab, setActiveTab] = useState("customer"); + const [activeTab, setActiveTab] = useState("all"); const [searchValue, setSearchValue] = useState(""); + const [contacts, setContacts] = useState([]); + const [loading, setLoading] = useState(false); + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 12, + total: 0, + }); - // 获取联系人数据 - 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 [tabCounts, setTabCounts] = useState({ + all: 0, + customer: 0, + potential: 0, + partner: 0, + friend: 0, + }); const tabs = [ + { key: "all", label: "全部", count: tabCounts.all }, { 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 => { - // const isCategoryMatch = - // (activeTab === "customer" && contact.isPassed) || - // (activeTab === "potential" && !contact.isPassed) || - // activeTab === "partner" || - // activeTab === "friend"; + // 加载联系人数据 + const loadContacts = async (page: number = 1, pageSize: number = 12) => { + try { + setLoading(true); - // 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)); + // 构建请求参数 + const params: any = { + page, + limit: pageSize, + }; + + // 添加搜索条件 + if (searchValue.trim()) { + params.keyword = searchValue; + } + + // 添加分类筛选 + if (activeTab === "customer") { + params.isPassed = true; + } else if (activeTab === "potential") { + params.isPassed = false; + } + // "全部"、"partner" 和 "friend" 不添加额外筛选条件 + + const response = await getContactList(params); + + // 假设接口返回格式为 { data: Contact[], total: number, page: number, limit: number } + setContacts(response.data || response.list || []); + setPagination(prev => ({ + ...prev, + current: response.page || page, + pageSize: response.limit || pageSize, + total: response.total || 0, + })); + + // 更新分类统计 + if (page === 1) { + // 只在第一页时更新统计,避免重复请求 + const allResponse = await getContactList({ page: 1, limit: 1 }); + const customerResponse = await getContactList({ + page: 1, + limit: 1, + isPassed: true, + }); + const potentialResponse = await getContactList({ + page: 1, + limit: 1, + isPassed: false, + }); + + setTabCounts({ + all: allResponse.total || 0, + customer: customerResponse.total || 0, + potential: potentialResponse.total || 0, + partner: 0, // 可以根据业务逻辑调整 + friend: 0, // 可以根据业务逻辑调整 + }); + } + } catch (error) { + console.error("加载联系人数据失败:", error); + message.error("加载联系人数据失败,请稍后重试"); + } finally { + setLoading(false); + } + }; + + // 当分类或搜索条件改变时重新加载数据 + useEffect(() => { + loadContacts(1, pagination.pageSize); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [activeTab, searchValue, pagination.pageSize]); - // return isCategoryMatch && isSearchMatch; - // }); const filteredContacts = contacts; return ( -
- 添加好友} - /> - -
- {/* 搜索和筛选 */} -
- setSearchValue(e.target.value)} - prefix={} - allowClear - size="large" - /> - -
- {/* 标签页 */} -
- {tabs.map(tab => ( - - ))} -
- - {/* 联系人卡片列表 */} -
- {filteredContacts.length === 0 ? ( -
-

暂无联系人数据

+ +
+ 添加好友} + /> + {/* 搜索和筛选 */} +
+ setSearchValue(e.target.value)} + prefix={} + allowClear + size="large" + /> +
- ) : ( - - {filteredContacts.map(contact => ( - -
-
-
- -
-

- {contact.conRemark || - contact.nickname || - contact.alias || - "未知用户"} -

-

- 客户 {"·"} {contact.desc || "未设置公司"} -

+ {/* 标签页 */} +
+ {tabs.map(tab => ( + + ))} +
+
+ + } + footer={ +
+ + `第 ${range[0]}-${range[1]} 条,共 ${total} 条` + } + onChange={(page, pageSize) => { + loadContacts(page, pageSize || pagination.pageSize); + }} + onShowSizeChange={(current, size) => { + loadContacts(1, size); + }} + pageSizeOptions={["6", "12", "24", "48"]} + /> +
+ } + > +
+
+ {/* 联系人卡片列表 */} +
+ {loading ? ( +
+ +

+ 正在加载联系人数据... +

+
+ ) : filteredContacts.length === 0 ? ( +
+

暂无联系人数据

+
+ ) : ( + <> + + {filteredContacts.map(contact => ( + +
+
+
+ +
+

+ {contact.conRemark || + contact.nickname || + contact.alias || + "未知用户"} +

+

+ 客户 {"·"} {contact.desc || "未设置公司"} +

+
+
+
+ +
+
+

+ 电话:{" "} + {contact.phone || "未设置电话"} +

+

+ 地区:{" "} + {contact.region || contact.city || "未设置地区"} +

+

+ 微信ID:{" "} + {contact.wechatId} +

+
+
+ +
+
+ {contact?.labels?.map( + (tag: string, index: number) => ( + + {tag} + + ), + )} +
+ 微信好友 +
+ +
+ 最后联系:{" "} + {contact.lastMessageTime + ? new Date( + contact.lastMessageTime, + ).toLocaleDateString() + : "未联系"} +
+ + {contact.signature && ( +
+ {contact.signature} +
+ )} + +
+ +
-
- -
-
-

- 电话:{" "} - {contact.phone || "未设置电话"} -

-

- 地区:{" "} - {contact.region || contact.city || "未设置地区"} -

-

- 微信ID:{" "} - {contact.wechatId} -

-
-
- -
-
- {contact?.labels?.map((tag: string, index: number) => ( - - {tag} - - ))} -
- 微信好友 -
- -
- 最后联系:{" "} - {contact.lastMessageTime - ? new Date(contact.lastMessageTime).toLocaleDateString() - : "未联系"} -
- - {contact.signature && ( -
{contact.signature}
- )} - -
- - -
-
- - ))} - - )} + + ))} + + + )} +
-
+ ); }; diff --git a/Touchkebao/src/styles/global.scss b/Touchkebao/src/styles/global.scss index 0b681cc6..16c25c87 100644 --- a/Touchkebao/src/styles/global.scss +++ b/Touchkebao/src/styles/global.scss @@ -315,3 +315,11 @@ button { flex: 1; } } +.pagination-wrapper { + display: flex; + justify-content: center; + align-items: center; + padding: 16px; + background: white; + border-top: 1px solid #f0f0f0; +} From d2ab632f2fdbfb32183e56dbe2c222d710a0b093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 25 Sep 2025 15:47:45 +0800 Subject: [PATCH 4/4] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../powerCenter/customer-management/index.tsx | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx index 53914ab0..477b8def 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx @@ -310,15 +310,6 @@ const CustomerManagement: React.FC = () => { 微信好友
-
- 最后联系:{" "} - {contact.lastMessageTime - ? new Date( - contact.lastMessageTime, - ).toLocaleDateString() - : "未联系"} -
- {contact.signature && (
{contact.signature} @@ -326,14 +317,10 @@ const CustomerManagement: React.FC = () => { )}
- - +