refactor(TransmitModal): 重构转发模态框组件并集成到消息记录

- 删除旧版转发模态框组件及相关文件
- 实现新版转发模态框组件,支持联系人搜索和多选
- 集成转发功能到消息记录组件
- 更新状态管理以支持转发功能
- 优化样式和响应式设计
This commit is contained in:
超级老白兔
2025-09-19 11:30:46 +08:00
parent 593e6c4670
commit b916180ccd
8 changed files with 571 additions and 530 deletions

View File

@@ -1,95 +0,0 @@
# 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. 达到最大选择数量时,未选择的联系人会被禁用

View File

@@ -1,26 +1,12 @@
// TransmitModal 组件样式 - 微信风格
.transmitModal {
:global(.ant-modal-content) {
padding: 0;
border-radius: 8px;
.ant-modal-content {
height: 500px;
}
.ant-modal-body {
height: calc(500px - 110px);
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;
padding: 16px;
}
}
@@ -31,96 +17,65 @@
}
.searchContainer {
padding: 16px 20px;
border-bottom: 1px solid #e8e8e8;
background: #fafafa;
}
margin-bottom: 16px;
.searchInput {
border-radius: 20px;
:global(.ant-input) {
border-radius: 20px;
background: #ffffff;
.searchInput {
width: 100%;
}
}
.contentBody {
flex: 1;
display: flex;
height: calc(100% - 60px);
}
.contactList {
flex: 1;
border-right: 1px solid #e8e8e8;
display: flex;
flex-direction: column;
gap: 16px;
min-height: 0;
}
.contactList,
.selectedList {
width: 200px;
flex: 1;
display: flex;
flex-direction: column;
background: #f8f8f8;
border: 1px solid #d9d9d9;
border-radius: 6px;
overflow: hidden;
}
.listHeader {
padding: 12px 16px;
background: #f0f0f0;
border-bottom: 1px solid #e8e8e8;
font-size: 14px;
background-color: #fafafa;
border-bottom: 1px solid #d9d9d9;
font-weight: 500;
color: #333;
font-size: 14px;
color: #262626;
}
.listContent {
flex: 1;
height: 60vh;
overflow-y: auto;
padding: 8px 0;
padding: 8px;
}
&::-webkit-scrollbar {
width: 6px;
.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;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: #d9d9d9;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: #bfbfbf;
&:last-child {
margin-bottom: 0;
}
}
.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;
}
}
.selectedItem {
justify-content: space-between;
}
.contactInfo {
@@ -128,109 +83,75 @@
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
}
.contactName {
font-size: 14px;
color: #333;
flex: 1;
color: #262626;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.groupIcon {
color: #1890ff;
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);
}
margin-left: 4px;
}
.removeIcon {
font-size: 12px;
color: #999;
color: #8c8c8c;
cursor: pointer;
padding: 4px;
border-radius: 50%;
transition: all 0.2s ease;
border-radius: 2px;
transition: all 0.2s;
&:hover {
color: #ff4d4f;
background: #fff2f0;
background-color: #fff2f0;
}
}
// 空状态样式
:global(.ant-empty) {
margin: 40px 0;
:global(.ant-empty-description) {
color: #999;
font-size: 13px;
}
.loadingContainer {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
flex-direction: column;
gap: 12px;
color: #8c8c8c;
}
.errorContainer {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
flex-direction: column;
gap: 12px;
color: #ff4d4f;
}
// 响应式设计
@media (max-width: 768px) {
.transmitModal {
:global(.ant-modal) {
width: 90% !important;
max-width: none;
.ant-modal-content {
max-height: 600px;
}
.ant-modal-body {
max-height: calc(600px - 110px);
}
}
.contentBody {
flex-direction: column;
gap: 12px;
}
.contactList,
.selectedList {
width: 160px;
}
.contactName {
font-size: 13px;
min-height: 200px;
}
}
// 按钮样式优化
: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;
}
}

View File

@@ -1,218 +0,0 @@
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;

View File

@@ -1,2 +0,0 @@
export { default } from "./TransmitModal";
export type { TransmitModalProps, Contact } from "./TransmitModal";

View File

@@ -0,0 +1,315 @@
import React, { useState, useEffect, useMemo } from "react";
import {
Modal,
Input,
Button,
Avatar,
Checkbox,
Empty,
Spin,
message,
} from "antd";
import {
SearchOutlined,
CloseOutlined,
UserOutlined,
TeamOutlined,
} from "@ant-design/icons";
import styles from "./TransmitModal.module.scss";
import { weChatGroupService, contractService } from "@/utils/db";
import { useWeChatStore } from "@/store/module/weChat/weChat";
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
export interface Contact {
id: string;
name: string;
avatar?: string;
type: "user" | "group";
pinyin?: string; // 用于搜索
originalData?: ContractData | weChatGroup; // 保存原始数据
}
export interface TransmitModalProps {
onConfirm?: (selectedTransmitContact: ContractData[] | weChatGroup[]) => void; // 可选因为会自动更新到store
}
const TransmitModal: React.FC<TransmitModalProps> = ({ onConfirm }) => {
const [searchValue, setSearchValue] = useState("");
const [allContacts, setAllContacts] = useState<Contact[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string>("");
const selectedTransmitContact = useWeChatStore(
state => state.selectedTransmitContact,
);
// 从 Zustand store 获取更新方法
const { updateSelectedTransmitContact } = useWeChatStore();
const openTransmitModal = useWeChatStore(state => state.openTransmitModal);
const updateTransmitModal = useWeChatStore(
state => state.updateTransmitModal,
);
// 加载联系人数据
const loadContacts = async () => {
setLoading(true);
setError("");
try {
// 并行加载联系人和群组数据
const [contractsData, groupsData] = await Promise.all([
contractService.findAll(),
weChatGroupService.findAll(),
]);
// 转换联系人数据格式
const contacts: Contact[] = contractsData.map(
(contract: ContractData) => ({
id: contract.id.toString(),
name:
contract.nickname ||
contract.alias ||
contract.wechatId ||
"未知联系人",
avatar: contract.avatar,
type: "user" as const,
pinyin: contract.quanPin,
originalData: contract,
}),
);
// 转换群组数据格式
const groups: Contact[] = groupsData.map((group: weChatGroup) => ({
id: group.id.toString(),
name: group.nickname || group.conRemark || "未知群组",
avatar: group.chatroomAvatar,
type: "group" as const,
pinyin: group.nickname, // 群组可能没有拼音,使用昵称
originalData: group,
}));
// 合并并排序(联系人在前,群组在后)
const allContactsData = [...contacts, ...groups];
setAllContacts(allContactsData);
} catch (err) {
console.error("加载联系人数据失败:", err);
setError("加载联系人数据失败,请重试");
message.error("加载联系人数据失败");
} finally {
setLoading(false);
}
};
// 重置状态
useEffect(() => {
if (openTransmitModal) {
setSearchValue("");
updateSelectedTransmitContact([]);
setError("");
loadContacts();
}
}, [openTransmitModal]);
// 过滤联系人 - 支持名称和拼音搜索
const filteredContacts = useMemo(() => {
if (!searchValue.trim()) return allContacts;
const keyword = searchValue.toLowerCase();
return allContacts.filter(
contact =>
contact.name.toLowerCase().includes(keyword) ||
contact.pinyin?.toLowerCase().includes(keyword) ||
contact.id.toLowerCase().includes(keyword),
);
}, [allContacts, searchValue]);
// 处理联系人选择
const handleContactSelect = (contact: Contact, checked: boolean) => {
console.log(contact);
console.log(checked);
};
// 移除已选择的联系人
const handleRemoveSelected = (contactId: string) => {
// updateSelectedTransmitContact(prev =>
// selectedTransmitContact.filter(item => item.id !== contactId),
// );
};
// 确认转发
const handleConfirm = () => {
if (selectedTransmitContact.length === 0) {
message.warning("请至少选择一个联系人");
return;
}
// 更新到 Zustand store
const originalDataList = selectedTransmitContact
.map(contact => contact.originalData)
.filter(Boolean) as (ContractData | weChatGroup)[];
// updateSelectedTransmitContact(originalDataList);
// 如果有外部回调,也调用它
if (onConfirm) {
onConfirm(selectedTransmitContact);
}
message.success(`已选择${selectedTransmitContact.length}个联系人`);
};
// 检查联系人是否已选择
const isContactSelected = (contactId: string) => {
console.log(contactId);
return selectedTransmitContact.some(contact => contact.id === contactId);
};
// 重试加载
const handleRetry = () => {
loadContacts();
};
return (
<Modal
title="转发消息"
open={openTransmitModal}
onCancel={() => updateTransmitModal(false)}
width={800}
className={styles.transmitModal}
footer={[
<Button key="cancel" onClick={() => updateTransmitModal(false)}>
</Button>,
<Button
key="confirm"
type="primary"
onClick={handleConfirm}
disabled={selectedTransmitContact.length === 0}
loading={loading}
>
{selectedTransmitContact.length > 0 &&
` (${selectedTransmitContact.length})`}
</Button>,
]}
>
<div className={styles.modalContent}>
{/* 搜索框 */}
<div className={styles.searchContainer}>
<Input
placeholder="输入联系人或群名"
prefix={<SearchOutlined />}
value={searchValue}
onChange={e => setSearchValue(e.target.value)}
className={styles.searchInput}
disabled={loading}
/>
</div>
<div className={styles.contentBody}>
{/* 左侧联系人列表 */}
<div className={styles.contactList}>
<div className={styles.listHeader}>
<span> ({filteredContacts.length})</span>
</div>
<div className={styles.listContent}>
{loading ? (
<div className={styles.loadingContainer}>
<Spin size="large" />
<span>...</span>
</div>
) : error ? (
<div className={styles.errorContainer}>
<span>{error}</span>
<Button size="small" onClick={handleRetry}>
</Button>
</div>
) : 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)}
>
<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={
searchValue ? "未找到匹配的联系人" : "暂无联系人"
}
image={Empty.PRESENTED_IMAGE_SIMPLE}
/>
)}
</div>
</div>
{/* 右侧已选择列表 */}
<div className={styles.selectedList}>
<div className={styles.listHeader}>
<span> {selectedTransmitContact.length} </span>
</div>
<div className={styles.listContent}>
{selectedTransmitContact.length > 0 ? (
selectedTransmitContact.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>
{contact.type === "group" && (
<TeamOutlined className={styles.groupIcon} />
)}
</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;

View File

@@ -12,6 +12,8 @@ import styles from "./MessageRecord.module.scss";
import { useWeChatStore } from "@/store/module/weChat/weChat";
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
import { fetchReCallApi } from "./api";
import TransmitModal from "./components/TransmitModal";
interface MessageRecordProps {
contract: ContractData | weChatGroup;
}
@@ -44,6 +46,11 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
state.kfUserList.find(kf => kf.id === contract.wechatAccountId),
);
const openTransmitModal = useWeChatStore(state => state.openTransmitModal);
const updateTransmitModal = useWeChatStore(
state => state.updateTransmitModal,
);
// 判断是否为表情包URL的工具函数
const isEmojiUrl = (content: string): boolean => {
return (
@@ -662,6 +669,7 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
};
const handleForwardMessage = (messageData: ChatRecord) => {
updateTransmitModal(true);
// 转发消息的处理逻辑
console.log("转发消息:", messageData);
};
@@ -702,6 +710,13 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
}
};
const handleConfirmTransmit = (
selectedContacts: ContractData[] | weChatGroup[],
) => {
// 确认转发逻辑
console.log("确认转发:", selectedContacts);
};
return (
<div className={styles.messagesContainer}>
<div className={styles.loadMore} onClick={() => loadMoreMessages()}>
@@ -727,7 +742,6 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
</React.Fragment>
))}
<div ref={messagesEndRef} />
{/* 右键菜单组件 */}
<ClickMenu
visible={contextMenu.visible}
@@ -738,6 +752,12 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
onClose={handleCloseContextMenu}
onCommad={handCommad}
/>
{/* 转发模态框 */}
<TransmitModal
onConfirm={(selectedContacts: ContractData[] | weChatGroup[]) =>
handleConfirmTransmit(selectedContacts)
}
/>
</div>
);
};

View File

@@ -4,64 +4,104 @@ import {
likeListItem,
FriendsCircleItem,
} 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;
/**
* 微信聊天状态管理接口定义
* 包含聊天消息、联系人管理、朋友圈等功能的状态和方法
*/
export interface WeChatState {
// ==================== Transmit Module =========Start===========
/** 选中的聊天记录列表 */
selectedChatRecords: ChatRecord[];
/** 更新选中的聊天记录 */
updateSelectedChatRecords: (message: ChatRecord[]) => void;
/** 选中的联系人或群组列表 */
selectedTransmitContact: ContractData[] | weChatGroup[];
/** 更新选中的联系人或群组 */
updateSelectedTransmitContact: (
contact: ContractData[] | weChatGroup[],
) => void;
/** 转发弹窗开启状态 */
openTransmitModal: boolean;
/** 更新转发弹窗状态 */
updateTransmitModal: (open: boolean) => void;
// 当前选中的联系人/群组
// ==================== Transmit Module =========END===========
// ==================== 当前联系人管理 ====================
/** 当前选中的联系人/群组 */
currentContract: ContractData | weChatGroup | null;
// CurrentContact 相关方法
/** 清空当前联系人 */
clearCurrentContact: () => void;
/** 设置当前联系人 */
setCurrentContact: (
contract: ContractData | weChatGroup,
isExist?: boolean,
) => void;
// 当前聊天用户的消息列表(只存储当前聊天用户的消息)
// ==================== 聊天消息管理 ====================
/** 当前聊天的消息列表 */
currentMessages: ChatRecord[];
// 添加消息
/** 添加消息 */
addMessage: (message: ChatRecord) => void;
// 替换消息
/** 更新指定消息 */
updateMessage: (messageId: number, updates: Partial<ChatRecord>) => void;
// 撤回消息
/** 撤回指定消息 */
recallMessage: (messageId: number) => void;
// 消息加载状态
/** 消息加载状态 */
messagesLoading: boolean;
/** 数据初始化加载状态 */
isLoadingData: boolean;
/** 当前群组成员列表 */
currentGroupMembers: any[];
showCheckbox: boolean;
updateShowCheckbox: (show: boolean) => void;
EnterModule: string;
// EnterModule 相关方法
updateEnterModule: (module: string) => void;
MomentCommon: FriendsCircleItem[];
// MomentCommon 相关方法
clearMomentCommon: () => void;
addMomentCommon: (moment: FriendsCircleItem[]) => void;
updateMomentCommon: (moments: FriendsCircleItem[]) => void;
// ==================== 界面状态管理 ====================
/** 是否显示复选框 */
showCheckbox: boolean;
/** 更新复选框显示状态 */
updateShowCheckbox: (show: boolean) => void;
/** 进入模块类型 (common | multipleForwarding) */
EnterModule: string;
/** 更新进入模块类型 */
updateEnterModule: (module: string) => void;
// ==================== 朋友圈相关 ====================
/** 朋友圈数据列表 */
MomentCommon: FriendsCircleItem[];
/** 朋友圈数据加载状态 */
MomentCommonLoading: boolean;
// MomentCommon 相关方法
/** 清空朋友圈数据 */
clearMomentCommon: () => void;
/** 添加朋友圈数据 */
addMomentCommon: (moment: FriendsCircleItem[]) => void;
/** 更新朋友圈数据 */
updateMomentCommon: (moments: FriendsCircleItem[]) => void;
/** 更新朋友圈加载状态 */
updateMomentCommonLoading: (loading: boolean) => void;
/** 更新朋友圈点赞 */
updateLikeMoment: (snsId: string, likeList: likeListItem[]) => void;
/** 更新朋友圈评论 */
updateComment: (snsId: string, commentList: CommentItem[]) => void;
// ==================== 消息加载方法 ====================
/** 加载聊天消息 */
loadChatMessages: (Init: boolean, To?: number) => Promise<void>;
/** 搜索消息 */
SearchMessage: (params: {
From: number;
To: number;
keyword: string;
Count?: number;
}) => Promise<void>;
// 视频消息处理方法
// ==================== 视频消息处理 ====================
/** 设置视频消息加载状态 */
setVideoLoading: (messageId: number, isLoading: boolean) => void;
/** 设置视频消息URL */
setVideoUrl: (messageId: number, videoUrl: string) => void;
// ==================== 消息接收处理 ====================
/** 接收新消息处理 */
receivedMsg: (message: ChatRecord) => void;
}

View File

@@ -20,36 +20,52 @@ import {
useCkChatStore,
} from "@/store/module/ckchat/ckchat";
/**
* 微信聊天状态管理 Store
* 使用 Zustand 管理微信聊天相关的状态和操作
*/
export const useWeChatStore = create<WeChatState>()(
persist(
(set, get) => ({
//选择聊天记录
selectedMessage: [],
updateSelectedMessage: (message: ChatRecord[]) => {
set({ selectedMessage: message });
// ==================== Transmit Module =========Start===========
/** 选中的聊天记录列表 */
selectedChatRecords: [],
/** 更新选中的聊天记录 */
updateSelectedChatRecords: (message: ChatRecord[]) => {
set({ selectedChatRecords: message });
},
//选择用户或群
selectedContact: [],
updateSelectedContact: (contact: ContractData[] | weChatGroup[]) => {
set({ selectedContact: contact });
},
//打开转发弹窗
openTransmitModal: false,
/** 选中的联系人或群组列表 */
selectedTransmitContact: [],
/** 更新选中的联系人或群组 */
updateSelectedTransmitContact: (
contact: ContractData[] | weChatGroup[],
) => {
set({ selectedTransmitContact: contact });
},
/** 转发弹窗开启状态 */
openTransmitModal: false,
/** 更新转发弹窗状态 */
updateTransmitModal: (open: boolean) => {
set({ openTransmitModal: open });
},
// ==================== Transmit Module =========END===========
// 初始状态
// ==================== 当前联系人管理状态 ====================
/** 当前选中的联系人/群组 */
currentContract: null,
/** 当前聊天的消息列表 */
currentMessages: [],
//添加消息
// ==================== 聊天消息管理方法 ====================
/** 添加新消息到当前聊天 */
addMessage: message => {
set(state => ({
currentMessages: [...state.currentMessages, message],
}));
},
//替换消息
/** 更新指定消息内容 */
updateMessage: (messageId, updates) => {
set(state => ({
currentMessages: state.currentMessages.map(msg =>
@@ -57,7 +73,7 @@ export const useWeChatStore = create<WeChatState>()(
),
}));
},
//撤回消息
/** 撤回指定消息 */
recallMessage: (messageId: number) => {
set(state => ({
currentMessages: state.currentMessages.filter(
@@ -66,31 +82,44 @@ export const useWeChatStore = create<WeChatState>()(
}));
},
// ==================== 加载状态管理 ====================
/** 消息加载状态 */
messagesLoading: false,
/** 数据初始化加载状态 */
isLoadingData: false,
/** 当前群组成员列表 */
currentGroupMembers: [],
// ==================== 界面状态管理 ====================
/** 是否显示复选框 */
showCheckbox: false,
/** 更新复选框显示状态 */
updateShowCheckbox: (show: boolean) => {
set({ showCheckbox: show });
},
EnterModule: "common", //common | multipleForwarding
/** 进入模块类型 (common | multipleForwarding) */
EnterModule: "common",
/** 更新进入模块类型 */
updateEnterModule: (module: string) => {
set({ EnterModule: module });
},
MomentCommon: [], //朋友圈数据
MomentCommonLoading: false, //朋友圈数据是否正在加载
// MomentCommon 相关方法
// ==================== 朋友圈相关状态 ====================
/** 朋友圈数据列表 */
MomentCommon: [],
/** 朋友圈数据加载状态 */
MomentCommonLoading: false,
/** 更新朋友圈加载状态 */
updateMomentCommonLoading: (loading: boolean) => {
set({ MomentCommonLoading: loading });
},
//============方法列表============
//清空当前联系人
// ==================== 当前联系人管理方法 ====================
/** 清空当前联系人和消息 */
clearCurrentContact: () => {
set({ currentContract: null, currentMessages: [] });
},
// Actions
/** 设置当前联系人并加载相关数据 */
setCurrentContact: (
contract: ContractData | weChatGroup,
isExist?: boolean,
@@ -112,6 +141,9 @@ export const useWeChatStore = create<WeChatState>()(
state.loadChatMessages(true, 4704624000000);
});
},
// ==================== 消息加载方法 ====================
/** 加载聊天消息 */
loadChatMessages: async (Init: boolean, To?: number) => {
const state = useWeChatStore.getState();
const contact = state.currentContract;
@@ -127,6 +159,7 @@ export const useWeChatStore = create<WeChatState>()(
};
if ("chatroomId" in contact && contact.chatroomId) {
// 群聊消息加载
params.wechatChatroomId = contact.id;
const messages = await getChatroomMessages(params);
const currentGroupMembers = await getGroupMembers({
@@ -143,6 +176,7 @@ export const useWeChatStore = create<WeChatState>()(
});
}
} else {
// 私聊消息加载
params.wechatFriendId = contact.id;
const messages = await getChatMessages(params);
if (Init) {
@@ -163,6 +197,8 @@ export const useWeChatStore = create<WeChatState>()(
set({ messagesLoading: false });
}
},
/** 搜索消息 */
SearchMessage: async ({
From = 1,
To = 4704624000000,
@@ -189,6 +225,7 @@ export const useWeChatStore = create<WeChatState>()(
};
if ("chatroomId" in contact && contact.chatroomId) {
// 群聊消息搜索
params.wechatChatroomId = contact.id;
const messages = await getChatroomMessages(params);
const currentGroupMembers = await getGroupMembers({
@@ -196,6 +233,7 @@ export const useWeChatStore = create<WeChatState>()(
});
set({ currentMessages: messages || [], currentGroupMembers });
} else {
// 私聊消息搜索
params.wechatFriendId = contact.id;
const messages = await getChatMessages(params);
set({ currentMessages: messages || [] });
@@ -208,29 +246,34 @@ export const useWeChatStore = create<WeChatState>()(
}
},
/** 设置消息加载状态 */
setMessageLoading: loading => {
set({ messagesLoading: Boolean(loading) });
},
// ==================== 消息接收处理 ====================
/** 接收新消息处理 */
receivedMsg: async message => {
const currentContract = useWeChatStore.getState().currentContract;
//判断群还是好友
// 判断是群聊还是私聊
const getMessageId =
message?.wechatChatroomId || message.wechatFriendId;
const isWechatGroup = message?.wechatChatroomId;
//当前选中聊天的群或好友
// 如果是当前选中的聊天,直接添加到消息列表
if (currentContract && currentContract.id == getMessageId) {
set(state => ({
currentMessages: [...state.currentMessages, message],
}));
} else {
//更新消息列表unread数值根据接收的++1 这样
// 更新其他聊天的未读消息数
const chatSessions = useCkChatStore.getState().chatSessions;
const session = chatSessions.find(item => item.id == getMessageId);
if (session) {
session.unreadCount = Number(session.unreadCount) + 1;
updateChatSession(session);
} else {
// 如果会话不存在,创建新会话
if (isWechatGroup) {
const [group] = await weChatGroupService.findByIds(getMessageId);
if (group) {
@@ -250,12 +293,16 @@ export const useWeChatStore = create<WeChatState>()(
}
},
// 便捷选择器
// ==================== 便捷选择器方法 ====================
/** 获取当前联系人 */
getCurrentContact: () => get().currentContract,
/** 获取当前消息列表 */
getCurrentMessages: () => get().currentMessages,
/** 获取消息加载状态 */
getMessagesLoading: () => get().messagesLoading,
// 视频消息处理方法
// ==================== 视频消息处理方法 ====================
/** 设置视频消息加载状态 */
setVideoLoading: (messageId: number, isLoading: boolean) => {
set(state => ({
currentMessages: state.currentMessages.map(msg => {
@@ -278,6 +325,7 @@ export const useWeChatStore = create<WeChatState>()(
}));
},
/** 设置视频消息URL */
setVideoUrl: (messageId: number, videoUrl: string) => {
set(state => ({
currentMessages: state.currentMessages.map(msg => {
@@ -309,6 +357,9 @@ export const useWeChatStore = create<WeChatState>()(
}),
}));
},
// ==================== 数据清理方法 ====================
/** 清空所有数据 */
clearAllData: () => {
set({
currentContract: null,
@@ -317,21 +368,25 @@ export const useWeChatStore = create<WeChatState>()(
});
},
// MomentCommon 相关方法
// ==================== 朋友圈管理方法 ====================
/** 清空朋友圈数据 */
clearMomentCommon: () => {
set({ MomentCommon: [] });
},
/** 添加朋友圈数据 */
addMomentCommon: moment => {
set(state => ({
MomentCommon: [...state.MomentCommon, ...moment],
}));
},
/** 更新朋友圈数据 */
updateMomentCommon: moments => {
set({ MomentCommon: moments });
},
/** 更新朋友圈点赞 */
updateLikeMoment: (snsId: string, likeList: likeListItem[]) => {
set(state => ({
MomentCommon: state.MomentCommon.map(moment =>
@@ -340,6 +395,7 @@ export const useWeChatStore = create<WeChatState>()(
}));
},
/** 更新朋友圈评论 */
updateComment: (snsId: string, commentList: CommentItem[]) => {
set(state => ({
MomentCommon: state.MomentCommon.map(moment =>
@@ -357,12 +413,16 @@ export const useWeChatStore = create<WeChatState>()(
),
);
// 导出便捷选择器函数
// ==================== 便捷选择器导出 ====================
/** 获取当前联系人的 Hook */
export const useCurrentContact = () =>
useWeChatStore(state => state.currentContract);
/** 获取当前消息列表的 Hook */
export const useCurrentMessages = () =>
useWeChatStore(state => state.currentMessages);
/** 获取消息加载状态的 Hook */
export const useMessagesLoading = () =>
useWeChatStore(state => state.messagesLoading);
/** 获取复选框显示状态的 Hook */
export const useShowCheckbox = () =>
useWeChatStore(state => state.showCheckbox);