diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss index 36977bd1..95c26fd0 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss @@ -4,7 +4,55 @@ border-top: 1px solid #e1e1e1; padding: 0; height: auto; - min-height: 100px; + height: 200px; +} + +// 多条转发操作栏样式 +.multipleForwardingBar { + display: flex; + align-items: center; + justify-content: center; + gap: 24px; + padding: 12px 16px; + background: #ffffff; + border-top: 1px solid #e1e1e1; + box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06); + height: 200px; +} + +.actionButton { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 8px 12px; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; + min-width: 60px; + + &:hover { + background: #f5f5f5; + transform: translateY(-1px); + } + + &:active { + background: #e8e8e8; + transform: translateY(0); + } +} + +.actionIcon { + font-size: 20px; + color: #576b95; + margin-bottom: 2px; +} + +.actionText { + font-size: 12px; + color: #333; + font-weight: 400; + white-space: nowrap; } .inputContainer { diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx index 97b81daf..4b876726 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx @@ -4,6 +4,8 @@ import { SendOutlined, FolderOutlined, PictureOutlined, + ExportOutlined, + CloseOutlined, } from "@ant-design/icons"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { useWebSocketStore } from "@/store/module/websocket/websocket"; @@ -28,6 +30,8 @@ const MessageEnter: React.FC = ({ contract }) => { const [inputValue, setInputValue] = useState(""); const [showMaterialModal, setShowMaterialModal] = useState(false); const EnterModule = useWeChatStore(state => state.EnterModule); + const updateShowCheckbox = useWeChatStore(state => state.updateShowCheckbox); + const updateEnterModule = useWeChatStore(state => state.updateEnterModule); const handleSend = async () => { if (!inputValue.trim()) return; @@ -131,6 +135,14 @@ const MessageEnter: React.FC = ({ contract }) => { sendCommand("CmdSendMessage", params); }; + const handleCancelAction = () => { + updateShowCheckbox(false); + updateEnterModule("common"); + }; + const handTurnRignt = () => { + console.log("转发"); + }; + return ( <> {/* 聊天输入 */} @@ -209,7 +221,19 @@ const MessageEnter: React.FC = ({ contract }) => { )} - {["multipleForwarding"].includes(EnterModule)} + {["multipleForwarding"].includes(EnterModule) && ( +
+
+ + 转发 +
+ +
+ + 取消 +
+
+ )} {/* 素材选择模态框 */} diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/README.md b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/README.md new file mode 100644 index 00000000..74a15a0d --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/README.md @@ -0,0 +1,95 @@ +# TransmitModal 转发模态框组件 + +## 功能特性 + +- 🔍 支持联系人搜索(姓名和拼音) +- 👥 支持个人和群组联系人 +- ✅ 多选功能,可设置最大选择数量 +- 📱 响应式设计,适配移动端 +- 🎨 微信风格UI设计 +- ♿ 无障碍支持 + +## 使用方法 + +```tsx +import TransmitModal, { Contact } from './components/TransmitModal'; + +const contacts: Contact[] = [ + { + id: '1', + name: '张三', + avatar: 'https://example.com/avatar1.jpg', + type: 'user', + pinyin: 'zhangsan' + }, + { + id: '2', + name: '开发群', + type: 'group', + pinyin: 'kaifaqun' + } +]; + +function App() { + const [showModal, setShowModal] = useState(false); + + const handleConfirm = (selectedContacts: Contact[]) => { + console.log('选择的联系人:', selectedContacts); + // 执行转发逻辑 + setShowModal(false); + }; + + return ( + setShowModal(false)} + onConfirm={handleConfirm} + contacts={contacts} + title="转发" + maxSelection={9} + /> + ); +} +``` + +## Props + +| 属性 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| open | boolean | - | 是否显示模态框 | +| onCancel | () => void | - | 取消回调 | +| onConfirm | (contacts: Contact[]) => void | - | 确认回调 | +| contacts | Contact[] | - | 联系人列表 | +| title | string | '转发' | 模态框标题 | +| confirmText | string | '确定' | 确认按钮文本 | +| cancelText | string | '取消' | 取消按钮文本 | +| maxSelection | number | 9 | 最大选择数量 | + +## Contact 接口 + +```tsx +interface Contact { + id: string; // 唯一标识 + name: string; // 显示名称 + avatar?: string; // 头像URL + type: 'user' | 'group'; // 类型:用户或群组 + pinyin?: string; // 拼音,用于搜索 +} +``` + +## 样式定制 + +组件使用 CSS Modules,可以通过覆盖以下类名来定制样式: + +- `.transmitModal` - 模态框容器 +- `.contactList` - 左侧联系人列表 +- `.selectedList` - 右侧已选择列表 +- `.contactItem` - 联系人项 +- `.selectedItem` - 已选择项 + +## 注意事项 + +1. 确保传入的 `contacts` 数组中每个联系人的 `id` 唯一 +2. `pinyin` 字段可选,但建议提供以支持拼音搜索 +3. 组件会自动处理选择状态,无需外部维护 +4. 达到最大选择数量时,未选择的联系人会被禁用 \ No newline at end of file diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/TransmitModal.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/TransmitModal.module.scss new file mode 100644 index 00000000..57bd53c6 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/TransmitModal.module.scss @@ -0,0 +1,236 @@ +// TransmitModal 组件样式 - 微信风格 +.transmitModal { + :global(.ant-modal-content) { + padding: 0; + border-radius: 8px; + overflow: hidden; + } + + :global(.ant-modal-header) { + padding: 16px 20px; + border-bottom: 1px solid #e8e8e8; + margin: 0; + } + + :global(.ant-modal-body) { + padding: 0; + height: 400px; + } + + :global(.ant-modal-footer) { + padding: 12px 20px; + border-top: 1px solid #e8e8e8; + text-align: right; + } +} + +.modalContent { + height: 100%; + display: flex; + flex-direction: column; +} + +.searchContainer { + padding: 16px 20px; + border-bottom: 1px solid #e8e8e8; + background: #fafafa; +} + +.searchInput { + border-radius: 20px; + + :global(.ant-input) { + border-radius: 20px; + background: #ffffff; + } +} + +.contentBody { + flex: 1; + display: flex; + height: calc(100% - 60px); +} + +.contactList { + flex: 1; + border-right: 1px solid #e8e8e8; + display: flex; + flex-direction: column; +} + +.selectedList { + width: 200px; + display: flex; + flex-direction: column; + background: #f8f8f8; +} + +.listHeader { + padding: 12px 16px; + background: #f0f0f0; + border-bottom: 1px solid #e8e8e8; + font-size: 14px; + font-weight: 500; + color: #333; +} + +.listContent { + flex: 1; + overflow-y: auto; + padding: 8px 0; + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: #d9d9d9; + border-radius: 3px; + } + + &::-webkit-scrollbar-thumb:hover { + background: #bfbfbf; + } +} + +.contactItem { + padding: 8px 16px; + cursor: pointer; + transition: background-color 0.2s ease; + + &:hover { + background: #f5f5f5; + } + + :global(.ant-checkbox-wrapper) { + width: 100%; + margin: 0; + } + + :global(.ant-checkbox) { + margin-right: 12px; + } + + :global(.ant-checkbox-disabled) { + :global(.ant-checkbox-inner) { + background-color: #f5f5f5; + border-color: #d9d9d9; + } + } +} + +.contactInfo { + display: flex; + align-items: center; + gap: 8px; + flex: 1; +} + +.contactName { + font-size: 14px; + color: #333; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.groupIcon { + font-size: 12px; + color: #999; + margin-left: auto; +} + +.selectedItem { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 12px; + margin: 4px 8px; + background: #ffffff; + border-radius: 6px; + border: 1px solid #e8e8e8; + transition: all 0.2s ease; + + &:hover { + border-color: #d9d9d9; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } +} + +.removeIcon { + font-size: 12px; + color: #999; + cursor: pointer; + padding: 4px; + border-radius: 50%; + transition: all 0.2s ease; + + &:hover { + color: #ff4d4f; + background: #fff2f0; + } +} + +// 空状态样式 +:global(.ant-empty) { + margin: 40px 0; + + :global(.ant-empty-description) { + color: #999; + font-size: 13px; + } +} + +// 响应式设计 +@media (max-width: 768px) { + .transmitModal { + :global(.ant-modal) { + width: 90% !important; + max-width: none; + } + } + + .selectedList { + width: 160px; + } + + .contactName { + font-size: 13px; + } +} + +// 按钮样式优化 +:global(.ant-btn-primary) { + background: #07c160; + border-color: #07c160; + + &:hover { + background: #06ad56; + border-color: #06ad56; + } + + &:disabled { + background: #f5f5f5; + border-color: #d9d9d9; + color: #bfbfbf; + } +} + +// 复选框样式优化 +:global(.ant-checkbox-checked) { + :global(.ant-checkbox-inner) { + background-color: #07c160; + border-color: #07c160; + } +} + +:global(.ant-checkbox-wrapper:hover) { + :global(.ant-checkbox-inner) { + border-color: #07c160; + } +} \ No newline at end of file diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/TransmitModal.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/TransmitModal.tsx new file mode 100644 index 00000000..51bce619 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/TransmitModal.tsx @@ -0,0 +1,218 @@ +import React, { useState, useEffect, useMemo } from "react"; +import { Modal, Input, Button, Avatar, Checkbox, Empty } from "antd"; +import { + SearchOutlined, + CloseOutlined, + UserOutlined, + TeamOutlined, +} from "@ant-design/icons"; +import styles from "./TransmitModal.module.scss"; + +export interface Contact { + id: string; + name: string; + avatar?: string; + type: "user" | "group"; + pinyin?: string; // 用于搜索 +} + +export interface TransmitModalProps { + open: boolean; + onCancel: () => void; + onConfirm: (selectedContacts: Contact[]) => void; + contacts: Contact[]; + title?: string; + confirmText?: string; + cancelText?: string; + maxSelection?: number; +} + +const TransmitModal: React.FC = ({ + open, + onCancel, + onConfirm, + contacts, + title = "转发", + confirmText = "确定", + cancelText = "取消", + maxSelection = 9, +}) => { + const [searchValue, setSearchValue] = useState(""); + const [selectedContacts, setSelectedContacts] = useState([]); + + // 重置状态 + useEffect(() => { + if (!open) { + setSearchValue(""); + setSelectedContacts([]); + } + }, [open]); + + // 过滤联系人 + const filteredContacts = useMemo(() => { + if (!searchValue.trim()) return contacts; + + const keyword = searchValue.toLowerCase(); + return contacts.filter( + contact => + contact.name.toLowerCase().includes(keyword) || + contact.pinyin?.toLowerCase().includes(keyword), + ); + }, [contacts, searchValue]); + + // 处理联系人选择 + const handleContactSelect = (contact: Contact, checked: boolean) => { + if (checked) { + if (selectedContacts.length >= maxSelection) { + return; // 达到最大选择数量 + } + setSelectedContacts(prev => [...prev, contact]); + } else { + setSelectedContacts(prev => prev.filter(item => item.id !== contact.id)); + } + }; + + // 移除已选择的联系人 + const handleRemoveSelected = (contactId: string) => { + setSelectedContacts(prev => prev.filter(item => item.id !== contactId)); + }; + + // 确认转发 + const handleConfirm = () => { + onConfirm(selectedContacts); + }; + + // 检查联系人是否已选择 + const isContactSelected = (contactId: string) => { + return selectedContacts.some(contact => contact.id === contactId); + }; + + return ( + + {cancelText} + , + , + ]} + > +
+ {/* 搜索框 */} +
+ } + value={searchValue} + onChange={e => setSearchValue(e.target.value)} + className={styles.searchInput} + /> +
+ +
+ {/* 左侧联系人列表 */} +
+
+ 联系人 +
+
+ {filteredContacts.length > 0 ? ( + filteredContacts.map(contact => ( +
+ + handleContactSelect(contact, e.target.checked) + } + disabled={ + !isContactSelected(contact.id) && + selectedContacts.length >= maxSelection + } + > +
+ + ) : ( + + ) + } + /> + + {contact.name} + + {contact.type === "group" && ( + + )} +
+
+
+ )) + ) : ( + + )} +
+
+ + {/* 右侧已选择列表 */} +
+
+ 已选择 {selectedContacts.length} 个联系人 +
+
+ {selectedContacts.length > 0 ? ( + selectedContacts.map(contact => ( +
+
+ + ) : ( + + ) + } + /> + {contact.name} +
+ handleRemoveSelected(contact.id)} + /> +
+ )) + ) : ( + + )} +
+
+
+
+
+ ); +}; + +export default TransmitModal; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/index.ts b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/index.ts new file mode 100644 index 00000000..fb2de0a0 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/index.ts @@ -0,0 +1,2 @@ +export { default } from "./TransmitModal"; +export type { TransmitModalProps, Contact } from "./TransmitModal"; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx index 4cbb4bde..502e6e1f 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx @@ -686,8 +686,9 @@ const MessageRecord: React.FC = ({ contract }) => { break; case "multipleForwarding": // 多条转发逻辑 + updateEnterModule(!showCheckbox ? "multipleForwarding" : "common"); updateShowCheckbox(!showCheckbox); - updateEnterModule(!showCheckbox ? "common" : "multipleForwarding"); + break; case "quote": // 引用逻辑 diff --git a/Touchkebao/src/store/module/weChat/weChat.data.ts b/Touchkebao/src/store/module/weChat/weChat.data.ts index f49e4b6c..14bf3506 100644 --- a/Touchkebao/src/store/module/weChat/weChat.data.ts +++ b/Touchkebao/src/store/module/weChat/weChat.data.ts @@ -6,6 +6,15 @@ import { } from "@/pages/pc/ckbox/weChat/components/SidebarMenu/FriendsCicle/index.data"; // 微信聊天相关的类型定义 export interface WeChatState { + //选择聊天记录 + selectedMessage: ChatRecord[]; + updateSelectedMessage: (message: ChatRecord[]) => void; + //选择用户或群 + selectedContact: ContractData[] | weChatGroup[]; + updateSelectedContact: (contact: ContractData[] | weChatGroup[]) => void; + + openTransmitModal: boolean; + updateTransmitModal: (open: boolean) => void; // 当前选中的联系人/群组 currentContract: ContractData | weChatGroup | null; // CurrentContact 相关方法 diff --git a/Touchkebao/src/store/module/weChat/weChat.ts b/Touchkebao/src/store/module/weChat/weChat.ts index b229bf3d..6ae89b95 100644 --- a/Touchkebao/src/store/module/weChat/weChat.ts +++ b/Touchkebao/src/store/module/weChat/weChat.ts @@ -6,12 +6,13 @@ import { getGroupMembers, } from "@/pages/pc/ckbox/api"; import { WeChatState } from "./weChat.data"; + import { likeListItem, CommentItem, } from "@/pages/pc/ckbox/weChat/components/SidebarMenu/FriendsCicle/index.data"; import { clearUnreadCount, updateConfig } from "@/pages/pc/ckbox/api"; -import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { weChatGroupService, contractService } from "@/utils/db"; import { addChatSession, @@ -22,6 +23,23 @@ import { export const useWeChatStore = create()( persist( (set, get) => ({ + //选择聊天记录 + selectedMessage: [], + updateSelectedMessage: (message: ChatRecord[]) => { + set({ selectedMessage: message }); + }, + //选择用户或群 + selectedContact: [], + updateSelectedContact: (contact: ContractData[] | weChatGroup[]) => { + set({ selectedContact: contact }); + }, + //打开转发弹窗 + openTransmitModal: false, + + updateTransmitModal: (open: boolean) => { + set({ openTransmitModal: open }); + }, + // 初始状态 currentContract: null, currentMessages: [],