From daf504d5fd27bddb37df9f0df9c720943dab770f 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: Sat, 8 Nov 2025 17:00:40 +0800 Subject: [PATCH] Add dropdown menu in SidebarMenu for adding friends and creating group chats, including modals for each action --- .../components/SidebarMenu/AddFriends/api.ts | 4 + .../SidebarMenu/AddFriends/index.module.scss | 102 ++++++ .../SidebarMenu/AddFriends/index.tsx | 174 +++++++++++ .../SidebarMenu/PopChatRoom/index.module.scss | 153 +++++++++ .../SidebarMenu/PopChatRoom/index.tsx | 293 ++++++++++++++++++ .../weChat/components/SidebarMenu/index.tsx | 54 +++- 6 files changed, 777 insertions(+), 3 deletions(-) create mode 100644 Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/AddFriends/api.ts create mode 100644 Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/AddFriends/index.module.scss create mode 100644 Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/AddFriends/index.tsx create mode 100644 Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.module.scss create mode 100644 Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.tsx diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/AddFriends/api.ts b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/AddFriends/api.ts new file mode 100644 index 00000000..fa59fe28 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/AddFriends/api.ts @@ -0,0 +1,4 @@ +import request from "@/api/request2"; +export const getWechatAccountInfo = (params: { id?: string }) => { + return request("/api/wechataccount", params, "GET"); +}; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/AddFriends/index.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/AddFriends/index.module.scss new file mode 100644 index 00000000..a651d6c9 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/AddFriends/index.module.scss @@ -0,0 +1,102 @@ +.addFriendModal { + .ant-modal-body { + padding: 24px; + } + + .ant-modal-header { + display: none; + } +} + +.modalContent { + display: flex; + flex-direction: column; + gap: 16px; +} + +.searchInputWrapper { + .ant-input { + height: 40px; + border-radius: 4px; + } +} + +.tipText { + font-size: 14px; + color: #666; + margin-top: 8px; + margin-bottom: 8px; +} + +.greetingWrapper { + margin-bottom: 20px; + .ant-input { + resize: none; + border-radius: 4px; + } +} + +.formRow { + display: flex; + align-items: center; + margin-bottom: 16px; + + .label { + width: 60px; + font-size: 14px; + color: #333; + flex-shrink: 0; + } + + .inputField { + flex: 1; + height: 36px; + border-radius: 4px; + } + + .selectField { + flex: 1; + border-radius: 4px; + + .ant-select-selector { + height: 36px; + border-radius: 4px; + } + } +} + +.buttonGroup { + display: flex; + gap: 12px; + margin-top: 8px; + justify-content: flex-start; + + .addButton { + background-color: #52c41a; + border-color: #52c41a; + color: #fff; + height: 36px; + padding: 0 24px; + border-radius: 4px; + font-size: 14px; + + &:hover { + background-color: #73d13d; + border-color: #73d13d; + } + } + + .cancelButton { + height: 36px; + padding: 0 24px; + border-radius: 4px; + font-size: 14px; + border-color: #d9d9d9; + color: #333; + + &:hover { + border-color: #40a9ff; + color: #40a9ff; + } + } +} diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/AddFriends/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/AddFriends/index.tsx new file mode 100644 index 00000000..c1a291a8 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/AddFriends/index.tsx @@ -0,0 +1,174 @@ +import React, { useState } from "react"; +import { Modal, Input, Button, message, Select } from "antd"; +import { SearchOutlined, DownOutlined } from "@ant-design/icons"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; +import { useCustomerStore } from "@/store/module/weChat/customer"; +import styles from "./index.module.scss"; + +interface AddFriendsProps { + visible: boolean; + onCancel: () => void; +} + +const AddFriends: React.FC = ({ visible, onCancel }) => { + const [searchValue, setSearchValue] = useState(""); + const [greeting, setGreeting] = useState("我是老坑爹-解放双手,释放时间"); + const [remark, setRemark] = useState(""); + const [selectedTag, setSelectedTag] = useState(undefined); + const [loading, setLoading] = useState(false); + const { sendCommand } = useWebSocketStore(); + const currentCustomer = useCustomerStore(state => state.currentCustomer); + + // 获取标签列表(从 currentCustomer.labels 字符串数组) + const tags = currentCustomer?.labels || []; + + // 重置表单 + const handleReset = () => { + setSearchValue(""); + setGreeting("我是老坑爹-解放双手,释放时间"); + setRemark(""); + setSelectedTag(undefined); + }; + + // 处理取消 + const handleCancel = () => { + handleReset(); + onCancel(); + }; + + // 判断是否为手机号(11位数字) + const isPhoneNumber = (value: string): boolean => { + return /^1[3-9]\d{9}$/.test(value.trim()); + }; + + // 处理添加好友 + const handleAddFriend = async () => { + if (!searchValue.trim()) { + message.warning("请输入微信号或手机号"); + return; + } + + if (!currentCustomer?.id) { + message.error("请先选择客服账号"); + return; + } + + setLoading(true); + try { + const trimmedValue = searchValue.trim(); + const isPhone = isPhoneNumber(trimmedValue); + + // 发送添加好友命令 + sendCommand("CmdSendFriendRequest", { + WechatAccountId: currentCustomer.id, + TargetWechatId: isPhone ? "" : trimmedValue, + Phone: isPhone ? trimmedValue : "", + Message: greeting.trim() || "我是老坑爹-解放双手,释放时间", + Remark: remark.trim() || "", + Labels: selectedTag || "", + }); + + message.success("好友请求已发送"); + handleCancel(); + } catch (error) { + console.error("添加好友失败:", error); + message.error("添加好友失败,请重试"); + } finally { + setLoading(false); + } + }; + + return ( + +
+ {/* 搜索输入框 */} +
+ } + value={searchValue} + onChange={e => setSearchValue(e.target.value)} + onPressEnter={handleAddFriend} + disabled={loading} + allowClear + /> +
+ + {/* 提示文字 */} +
你需要发送验证申请,等待对方通过
+ + {/* 验证消息文本区域 */} +
+ setGreeting(e.target.value)} + rows={4} + disabled={loading} + maxLength={200} + /> +
+ + {/* 备注输入框 */} +
+ 备注: + setRemark(e.target.value)} + placeholder="请输入备注" + disabled={loading} + className={styles.inputField} + /> +
+ + {/* 标签选择器 */} +
+ 标签: + +
+ + {/* 底部按钮 */} +
+ + +
+
+
+ ); +}; + +export default AddFriends; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.module.scss new file mode 100644 index 00000000..20d9a3d3 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.module.scss @@ -0,0 +1,153 @@ +.popChatRoomModal { + .ant-modal-content { + height: 500px; + } + + .ant-modal-body { + height: calc(500px - 110px); + overflow: hidden; + padding: 16px; + } +} + +.modalContent { + height: 100%; + display: flex; + flex-direction: column; +} + +.searchContainer { + margin-bottom: 16px; + + .searchInput { + width: 100%; + } +} + +.contentBody { + flex: 1; + display: flex; + gap: 16px; + min-height: 0; +} + +.contactList, +.selectedList { + flex: 1; + display: flex; + flex-direction: column; + border: 1px solid #d9d9d9; + border-radius: 6px; + overflow: hidden; +} + +.listHeader { + padding: 12px 16px; + background-color: #fafafa; + border-bottom: 1px solid #d9d9d9; + font-weight: 500; + font-size: 14px; + color: #262626; +} + +.listContent { + flex: 1; + overflow-y: auto; + padding: 8px; + min-height: 0; +} + +.contactItem, +.selectedItem { + display: flex; + align-items: center; + padding: 8px 12px; + border-radius: 4px; + margin-bottom: 4px; + transition: background-color 0.2s; + + &:hover { + background-color: #f5f5f5; + } + + &:last-child { + margin-bottom: 0; + } +} + +.paginationContainer { + padding: 12px; + border-top: 1px solid #d9d9d9; + background-color: #fafafa; +} + +.selectedItem { + justify-content: space-between; +} + +.contactInfo { + display: flex; + align-items: center; + gap: 8px; + flex: 1; + min-width: 0; +} + +.contactName { + font-size: 14px; + color: #262626; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.conRemark { + font-size: 12px; + color: #8c8c8c; +} + +.removeIcon { + color: #8c8c8c; + cursor: pointer; + padding: 4px; + border-radius: 2px; + transition: all 0.2s; + + &:hover { + color: #ff4d4f; + background-color: #fff2f0; + } +} + +.loadingContainer { + display: flex; + justify-content: center; + align-items: center; + height: 200px; + flex-direction: column; + gap: 12px; + color: #8c8c8c; +} + +// 响应式设计 +@media (max-width: 768px) { + .popChatRoomModal { + .ant-modal-content { + max-height: 600px; + } + + .ant-modal-body { + max-height: calc(600px - 110px); + } + } + + .contentBody { + flex-direction: column; + gap: 12px; + } + + .contactList, + .selectedList { + min-height: 200px; + } +} diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.tsx new file mode 100644 index 00000000..9826a6e5 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.tsx @@ -0,0 +1,293 @@ +import React, { useState, useEffect, useMemo } from "react"; +import { + Modal, + Input, + Button, + Avatar, + Checkbox, + Empty, + Spin, + message, + Pagination, +} from "antd"; +import { SearchOutlined, CloseOutlined, UserOutlined } from "@ant-design/icons"; +import styles from "./index.module.scss"; +import { ContactManager } from "@/utils/dbAction"; +import { useUserStore } from "@/store/module/user"; +import { useCustomerStore } from "@/store/module/weChat/customer"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; +import { Contact } from "@/utils/db"; + +interface PopChatRoomProps { + visible: boolean; + onCancel: () => void; +} + +const PopChatRoom: React.FC = ({ visible, onCancel }) => { + const [searchValue, setSearchValue] = useState(""); + const [allContacts, setAllContacts] = useState([]); + const [selectedContacts, setSelectedContacts] = useState([]); + const [loading, setLoading] = useState(false); + const [page, setPage] = useState(1); + const pageSize = 20; + const { sendCommand } = useWebSocketStore(); + const currentUserId = useUserStore(state => state.user?.id) || 0; + const currentCustomer = useCustomerStore(state => state.currentCustomer); + + // 加载联系人数据(只加载好友,不包含群聊) + const loadContacts = async () => { + setLoading(true); + try { + const allContactsData = + await ContactManager.getUserContacts(currentUserId); + // 过滤出好友类型,排除群聊 + const friendsOnly = (allContactsData as Contact[]).filter( + contact => contact.type === "friend", + ); + setAllContacts(friendsOnly); + } catch (err) { + console.error("加载联系人数据失败:", err); + message.error("加载联系人数据失败"); + } finally { + setLoading(false); + } + }; + + // 重置状态 + useEffect(() => { + if (visible) { + setSearchValue(""); + setSelectedContacts([]); + setPage(1); + loadContacts(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [visible]); + + // 过滤联系人 - 支持名称和拼音搜索 + const filteredContacts = useMemo(() => { + if (!searchValue.trim()) return allContacts; + + const keyword = searchValue.toLowerCase(); + return allContacts.filter(contact => { + const name = (contact.nickname || "").toLowerCase(); + const remark = (contact.conRemark || "").toLowerCase(); + const quanPin = (contact as any).quanPin?.toLowerCase?.() || ""; + const pinyin = (contact as any).pinyin?.toLowerCase?.() || ""; + return ( + name.includes(keyword) || + remark.includes(keyword) || + quanPin.includes(keyword) || + pinyin.includes(keyword) + ); + }); + }, [allContacts, searchValue]); + + const paginatedContacts = useMemo(() => { + const start = (page - 1) * pageSize; + const end = start + pageSize; + return filteredContacts.slice(start, end); + }, [filteredContacts, page]); + + // 处理联系人选择 + const handleContactSelect = (contact: Contact) => { + setSelectedContacts(prev => { + if (isContactSelected(contact.id)) { + return prev.filter(item => item.id !== contact.id); + } + return [...prev, contact]; + }); + }; + + // 移除已选择的联系人 + const handleRemoveSelected = (contactId: number) => { + setSelectedContacts(prev => + prev.filter(contact => contact.id !== contactId), + ); + }; + + // 检查联系人是否已选择 + const isContactSelected = (contactId: number) => { + return selectedContacts.some(contact => contact.id === contactId); + }; + + // 处理取消 + const handleCancel = () => { + setSearchValue(""); + setSelectedContacts([]); + setPage(1); + onCancel(); + }; + + // 创建群聊 + const handleCreateGroup = () => { + if (selectedContacts.length === 0) { + message.warning("请至少选择一个联系人"); + return; + } + + if (!currentCustomer?.id) { + message.error("请先选择客服账号"); + return; + } + + // 获取选中的好友ID列表 + const friendIds = selectedContacts.map(contact => contact.id); + + try { + // 发送创建群聊命令 + sendCommand("CmdCreateChatroom", { + wechatAccountId: currentCustomer.id, + wechatFriendIds: friendIds, + }); + + message.success("群聊创建请求已发送"); + handleCancel(); + } catch (error) { + console.error("创建群聊失败:", error); + message.error("创建群聊失败,请重试"); + } + }; + + return ( + + 取消 + , + , + ]} + > +
+ {/* 搜索框 */} +
+ } + value={searchValue} + onChange={e => setSearchValue(e.target.value)} + className={styles.searchInput} + disabled={loading} + allowClear + /> +
+ +
+ {/* 左侧联系人列表 */} +
+
+ 联系人 ({filteredContacts.length}) +
+
+ {loading ? ( +
+ + 加载联系人中... +
+ ) : filteredContacts.length > 0 ? ( + paginatedContacts.map(contact => ( +
+ handleContactSelect(contact)} + > +
+ } + /> +
+
{contact.nickname}
+ {contact.conRemark && ( +
+ {contact.conRemark} +
+ )} +
+
+
+
+ )) + ) : ( + + )} +
+ {filteredContacts.length > 0 && ( +
+ setPage(p)} + showSizeChanger={false} + /> +
+ )} +
+ + {/* 右侧已选择列表 */} +
+
+ 已选联系人 ({selectedContacts.length}) +
+
+ {selectedContacts.length > 0 ? ( + selectedContacts.map(contact => ( +
+
+ } + /> +
+
{contact.nickname}
+ {contact.conRemark && ( +
+ {contact.conRemark} +
+ )} +
+
+ handleRemoveSelected(contact.id)} + /> +
+ )) + ) : ( + + )} +
+
+
+
+
+ ); +}; + +export default PopChatRoom; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/index.tsx index b8d088ed..3699b995 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/index.tsx @@ -1,9 +1,16 @@ import React, { useState, useEffect } from "react"; -import { Input, Skeleton, Button } from "antd"; -import { SearchOutlined, PlusOutlined } from "@ant-design/icons"; +import { Input, Skeleton, Button, Dropdown, MenuProps } from "antd"; +import { + SearchOutlined, + PlusOutlined, + UserAddOutlined, + TeamOutlined, +} from "@ant-design/icons"; import WechatFriends from "./WechatFriends"; import MessageList from "./MessageList/index"; import FriendsCircle from "./FriendsCicle"; +import AddFriends from "./AddFriends"; +import PopChatRoom from "./PopChatRoom"; import styles from "./SidebarMenu.module.scss"; import { useContactStore } from "@/store/module/weChat/contacts"; import { useCustomerStore } from "@/store/module/weChat/customer"; @@ -28,6 +35,9 @@ const SidebarMenu: React.FC = ({ loading = false }) => { const [activeTab, setActiveTab] = useState("chats"); const [switchingTab, setSwitchingTab] = useState(false); // tab切换加载状态 + const [isAddFriendModalVisible, setIsAddFriendModalVisible] = useState(false); + const [isCreateGroupModalVisible, setIsCreateGroupModalVisible] = + useState(false); // 监听 currentContact 变化,自动切换到聊天tab并选中会话 useEffect(() => { @@ -68,6 +78,26 @@ const SidebarMenu: React.FC = ({ loading = false }) => { clearSearchKeyword(); }; + // 下拉菜单项 + const menuItems: MenuProps["items"] = [ + { + key: "addFriend", + label: "添加好友", + icon: , + onClick: () => { + setIsAddFriendModalVisible(true); + }, + }, + { + key: "createGroup", + label: "发起群聊", + icon: , + onClick: () => { + setIsCreateGroupModalVisible(true); + }, + }, + ]; + // 渲染骨架屏 const renderSkeleton = () => (
@@ -126,7 +156,15 @@ const SidebarMenu: React.FC = ({ loading = false }) => { onClear={handleClearSearch} allowClear /> - + {currentCustomer && ( + + + + )}
{/* 标签页切换 */} @@ -182,6 +220,16 @@ const SidebarMenu: React.FC = ({ loading = false }) => {
{renderHeader()}
{renderContent()}
+ {/* 添加好友弹窗 */} + setIsAddFriendModalVisible(false)} + /> + {/* 发起群聊弹窗 */} + setIsCreateGroupModalVisible(false)} + />
); };