feat(微信聊天): 添加消息转发功能
实现消息多选转发功能,包括: 1. 新增转发模态框组件,支持联系人搜索和多选 2. 在消息记录组件中添加转发操作逻辑 3. 在消息输入区域添加转发操作栏 4. 在微信状态管理中新增转发相关状态和方法
This commit is contained in:
@@ -4,7 +4,55 @@
|
|||||||
border-top: 1px solid #e1e1e1;
|
border-top: 1px solid #e1e1e1;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: auto;
|
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 {
|
.inputContainer {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import {
|
|||||||
SendOutlined,
|
SendOutlined,
|
||||||
FolderOutlined,
|
FolderOutlined,
|
||||||
PictureOutlined,
|
PictureOutlined,
|
||||||
|
ExportOutlined,
|
||||||
|
CloseOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
|
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
|
||||||
import { useWebSocketStore } from "@/store/module/websocket/websocket";
|
import { useWebSocketStore } from "@/store/module/websocket/websocket";
|
||||||
@@ -28,6 +30,8 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
|
|||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
const [showMaterialModal, setShowMaterialModal] = useState(false);
|
const [showMaterialModal, setShowMaterialModal] = useState(false);
|
||||||
const EnterModule = useWeChatStore(state => state.EnterModule);
|
const EnterModule = useWeChatStore(state => state.EnterModule);
|
||||||
|
const updateShowCheckbox = useWeChatStore(state => state.updateShowCheckbox);
|
||||||
|
const updateEnterModule = useWeChatStore(state => state.updateEnterModule);
|
||||||
|
|
||||||
const handleSend = async () => {
|
const handleSend = async () => {
|
||||||
if (!inputValue.trim()) return;
|
if (!inputValue.trim()) return;
|
||||||
@@ -131,6 +135,14 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
|
|||||||
sendCommand("CmdSendMessage", params);
|
sendCommand("CmdSendMessage", params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCancelAction = () => {
|
||||||
|
updateShowCheckbox(false);
|
||||||
|
updateEnterModule("common");
|
||||||
|
};
|
||||||
|
const handTurnRignt = () => {
|
||||||
|
console.log("转发");
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* 聊天输入 */}
|
{/* 聊天输入 */}
|
||||||
@@ -209,7 +221,19 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{["multipleForwarding"].includes(EnterModule)}
|
{["multipleForwarding"].includes(EnterModule) && (
|
||||||
|
<div className={styles.multipleForwardingBar}>
|
||||||
|
<div className={styles.actionButton} onClick={handTurnRignt}>
|
||||||
|
<ExportOutlined className={styles.actionIcon} />
|
||||||
|
<span className={styles.actionText}>转发</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.actionButton} onClick={handleCancelAction}>
|
||||||
|
<CloseOutlined className={styles.actionIcon} />
|
||||||
|
<span className={styles.actionText}>取消</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Footer>
|
</Footer>
|
||||||
|
|
||||||
{/* 素材选择模态框 */}
|
{/* 素材选择模态框 */}
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<TransmitModal
|
||||||
|
open={showModal}
|
||||||
|
onCancel={() => 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. 达到最大选择数量时,未选择的联系人会被禁用
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<TransmitModalProps> = ({
|
||||||
|
open,
|
||||||
|
onCancel,
|
||||||
|
onConfirm,
|
||||||
|
contacts,
|
||||||
|
title = "转发",
|
||||||
|
confirmText = "确定",
|
||||||
|
cancelText = "取消",
|
||||||
|
maxSelection = 9,
|
||||||
|
}) => {
|
||||||
|
const [searchValue, setSearchValue] = useState("");
|
||||||
|
const [selectedContacts, setSelectedContacts] = useState<Contact[]>([]);
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
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 (
|
||||||
|
<Modal
|
||||||
|
title={title}
|
||||||
|
open={open}
|
||||||
|
onCancel={onCancel}
|
||||||
|
width={600}
|
||||||
|
height={500}
|
||||||
|
className={styles.transmitModal}
|
||||||
|
footer={[
|
||||||
|
<Button key="cancel" onClick={onCancel}>
|
||||||
|
{cancelText}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key="confirm"
|
||||||
|
type="primary"
|
||||||
|
onClick={handleConfirm}
|
||||||
|
disabled={selectedContacts.length === 0}
|
||||||
|
>
|
||||||
|
{confirmText}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div className={styles.modalContent}>
|
||||||
|
{/* 搜索框 */}
|
||||||
|
<div className={styles.searchContainer}>
|
||||||
|
<Input
|
||||||
|
placeholder="输入联系人或群名"
|
||||||
|
prefix={<SearchOutlined />}
|
||||||
|
value={searchValue}
|
||||||
|
onChange={e => setSearchValue(e.target.value)}
|
||||||
|
className={styles.searchInput}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.contentBody}>
|
||||||
|
{/* 左侧联系人列表 */}
|
||||||
|
<div className={styles.contactList}>
|
||||||
|
<div className={styles.listHeader}>
|
||||||
|
<span>联系人</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.listContent}>
|
||||||
|
{filteredContacts.length > 0 ? (
|
||||||
|
filteredContacts.map(contact => (
|
||||||
|
<div key={contact.id} className={styles.contactItem}>
|
||||||
|
<Checkbox
|
||||||
|
checked={isContactSelected(contact.id)}
|
||||||
|
onChange={e =>
|
||||||
|
handleContactSelect(contact, e.target.checked)
|
||||||
|
}
|
||||||
|
disabled={
|
||||||
|
!isContactSelected(contact.id) &&
|
||||||
|
selectedContacts.length >= maxSelection
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={styles.contactInfo}>
|
||||||
|
<Avatar
|
||||||
|
size={32}
|
||||||
|
src={contact.avatar}
|
||||||
|
icon={
|
||||||
|
contact.type === "group" ? (
|
||||||
|
<TeamOutlined />
|
||||||
|
) : (
|
||||||
|
<UserOutlined />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className={styles.contactName}>
|
||||||
|
{contact.name}
|
||||||
|
</span>
|
||||||
|
{contact.type === "group" && (
|
||||||
|
<TeamOutlined className={styles.groupIcon} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Empty
|
||||||
|
description="暂无联系人"
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧已选择列表 */}
|
||||||
|
<div className={styles.selectedList}>
|
||||||
|
<div className={styles.listHeader}>
|
||||||
|
<span>已选择 {selectedContacts.length} 个联系人</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.listContent}>
|
||||||
|
{selectedContacts.length > 0 ? (
|
||||||
|
selectedContacts.map(contact => (
|
||||||
|
<div key={contact.id} className={styles.selectedItem}>
|
||||||
|
<div className={styles.contactInfo}>
|
||||||
|
<Avatar
|
||||||
|
size={32}
|
||||||
|
src={contact.avatar}
|
||||||
|
icon={
|
||||||
|
contact.type === "group" ? (
|
||||||
|
<TeamOutlined />
|
||||||
|
) : (
|
||||||
|
<UserOutlined />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className={styles.contactName}>{contact.name}</span>
|
||||||
|
</div>
|
||||||
|
<CloseOutlined
|
||||||
|
className={styles.removeIcon}
|
||||||
|
onClick={() => handleRemoveSelected(contact.id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Empty
|
||||||
|
description="请选择联系人"
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TransmitModal;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { default } from "./TransmitModal";
|
||||||
|
export type { TransmitModalProps, Contact } from "./TransmitModal";
|
||||||
@@ -686,8 +686,9 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
|
|||||||
break;
|
break;
|
||||||
case "multipleForwarding":
|
case "multipleForwarding":
|
||||||
// 多条转发逻辑
|
// 多条转发逻辑
|
||||||
|
updateEnterModule(!showCheckbox ? "multipleForwarding" : "common");
|
||||||
updateShowCheckbox(!showCheckbox);
|
updateShowCheckbox(!showCheckbox);
|
||||||
updateEnterModule(!showCheckbox ? "common" : "multipleForwarding");
|
|
||||||
break;
|
break;
|
||||||
case "quote":
|
case "quote":
|
||||||
// 引用逻辑
|
// 引用逻辑
|
||||||
|
|||||||
@@ -6,6 +6,15 @@ import {
|
|||||||
} from "@/pages/pc/ckbox/weChat/components/SidebarMenu/FriendsCicle/index.data";
|
} from "@/pages/pc/ckbox/weChat/components/SidebarMenu/FriendsCicle/index.data";
|
||||||
// 微信聊天相关的类型定义
|
// 微信聊天相关的类型定义
|
||||||
export interface WeChatState {
|
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;
|
currentContract: ContractData | weChatGroup | null;
|
||||||
// CurrentContact 相关方法
|
// CurrentContact 相关方法
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import {
|
|||||||
getGroupMembers,
|
getGroupMembers,
|
||||||
} from "@/pages/pc/ckbox/api";
|
} from "@/pages/pc/ckbox/api";
|
||||||
import { WeChatState } from "./weChat.data";
|
import { WeChatState } from "./weChat.data";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
likeListItem,
|
likeListItem,
|
||||||
CommentItem,
|
CommentItem,
|
||||||
} from "@/pages/pc/ckbox/weChat/components/SidebarMenu/FriendsCicle/index.data";
|
} from "@/pages/pc/ckbox/weChat/components/SidebarMenu/FriendsCicle/index.data";
|
||||||
import { clearUnreadCount, updateConfig } from "@/pages/pc/ckbox/api";
|
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 { weChatGroupService, contractService } from "@/utils/db";
|
||||||
import {
|
import {
|
||||||
addChatSession,
|
addChatSession,
|
||||||
@@ -22,6 +23,23 @@ import {
|
|||||||
export const useWeChatStore = create<WeChatState>()(
|
export const useWeChatStore = create<WeChatState>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(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,
|
currentContract: null,
|
||||||
currentMessages: [],
|
currentMessages: [],
|
||||||
|
|||||||
Reference in New Issue
Block a user