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..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 @@ -1,43 +1,355 @@ .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; + 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%; + 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: 175px; + } + + .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..477b8def 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/customer-management/index.tsx @@ -1,24 +1,338 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import PowerNavigation from "@/components/PowerNavtion"; +import { + SearchOutlined, + FilterOutlined, + MessageOutlined, + PhoneOutlined, +} from "@ant-design/icons"; import styles from "./index.module.scss"; +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"; +import Layout from "@/components/Layout/LayoutFiexd"; +// 头像组件 +const Avatar: React.FC<{ name: string; avatar?: string; size?: number }> = ({ + name, + avatar, + size = 40, +}) => { + const getInitials = (name: string) => { + return name.charAt(0).toUpperCase(); + }; -const CustomerManagement: React.FC = () => { - return ( -
- { + 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("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 [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 loadContacts = async (page: number = 1, pageSize: number = 12) => { + try { + setLoading(true); + + // 构建请求参数 + 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]); + + const filteredContacts = contacts; + + return ( + +
+ 添加好友} + /> + {/* 搜索和筛选 */} +
+ setSearchValue(e.target.value)} + prefix={} + allowClear + size="large" + /> + +
+ {/* 标签页 */} +
+ {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.signature && ( +
+ {contact.signature} +
+ )} + +
+ +
+
+ + ))} +
+ + )} +
+
+
+
+ ); +}; + export default CustomerManagement; 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; }; /** * 根据标签组织联系人 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; +}