This commit is contained in:
Your Name
2026-03-11 09:49:47 +08:00
parent 02ae537b4c
commit 38ad60f4bb
290 changed files with 36917 additions and 123 deletions
@@ -0,0 +1,89 @@
import type { IAppStore, ITasks } from '../../interface/store';
export default class AppStore implements IAppStore {
public store: {
enabledMessageReadReceipt: boolean;
enabledEmojiPlugin: boolean;
enabledOnlineStatus: boolean;
enabledCustomerServicePlugin: boolean;
enabledTranslationPlugin: boolean;
enabledVoiceToText: boolean;
enableTyping: boolean;
enableConversationDraft: boolean;
enableAutoMessageRead: boolean;
isOfficial: boolean;
SDKVersion: string;
tasks: ITasks;
[key: string]: any;
};
public defaultStore = {
enabledMessageReadReceipt: false,
enabledEmojiPlugin: false,
enabledOnlineStatus: false,
enabledCustomerServicePlugin: false,
enabledTranslationPlugin: false,
enabledVoiceToText: false,
enableTyping: true,
enableConversationDraft: true,
enableAutoMessageRead: true,
isOfficial: false,
SDKVersion: '3.0.0',
tasks: {
sendMessage: false,
revokeMessage: false,
modifyNickName: false,
groupChat: false,
muteGroup: false,
dismissGroup: false,
call: false,
searchCloudMessage: false,
customerService: false,
translateTextMessage: false,
},
} as any;
constructor() {
this.store = {
enabledEmojiPlugin: false,
enabledMessageReadReceipt: false,
enabledOnlineStatus: false,
enabledCustomerServicePlugin: false,
enabledTranslationPlugin: false,
enabledVoiceToText: false,
enableTyping: true,
enableConversationDraft: true,
enableAutoMessageRead: true,
isOfficial: false,
SDKVersion: '3.0.0',
tasks: {
sendMessage: false,
revokeMessage: false,
modifyNickName: false,
groupChat: false,
muteGroup: false,
dismissGroup: false,
call: false,
searchCloudMessage: false,
customerService: false,
translateTextMessage: false,
},
};
}
update(key: string, data: any) {
this.store[key] = data;
}
getData(key: string) {
return this.store[key];
}
reset(keyList: string[] = []) {
this.store = {
...this.defaultStore,
...this.store,
...keyList.reduce((acc, key) => ({ ...acc, [key]: this.defaultStore[key] }), {}),
};
}
}
@@ -0,0 +1,135 @@
import type { Message } from '@tencentcloud/lite-chat/basic';
import type { IChatStore } from '../../interface/store';
import type { IMessageModel } from '../../interface/model';
import MessageModel from '../../model/message';
import { CHAT_IGNORE_KEYS } from '../../config';
interface ITextInfo {
messageID: string;
visible: boolean;
}
export default class ChatStore implements IChatStore {
public store: {
messageList: IMessageModel[];
isCompleted: boolean;
nextReqMessageID: string;
quoteMessage: Message | any;
newMessageList: Message[];
typingStatus: boolean;
messageSource: IMessageModel | undefined;
translateTextInfo: Map<string, ITextInfo[]> | undefined;
voiceToTextInfo: Map<string, ITextInfo[]> | undefined;
userInfo: Record<string, any>;
[key: string]: any;
};
public defaultStore = {
messageList: [],
isCompleted: false,
nextReqMessageID: '',
quoteMessage: {},
newMessageList: [],
typingStatus: false,
messageSource: undefined,
translateTextInfo: undefined,
voiceToTextInfo: undefined,
userInfo: {},
} as any;
constructor() {
this.store = {
messageList: [],
isCompleted: false,
nextReqMessageID: '',
quoteMessage: {},
newMessageList: [],
typingStatus: false,
messageSource: undefined,
translateTextInfo: undefined,
voiceToTextInfo: undefined,
userInfo: {},
};
}
update(key: string, data: any): void {
switch (key) {
case 'messageList':
this.updateMessageList(data);
break;
case 'translateTextInfo':
this.updateTranslateTextInfo(data);
break;
case 'voiceToTextInfo':
this.updateVoiceToTextInfo(data);
break;
default:
this.store[key] = data;
}
}
getData(key: string) {
return this.store[key];
}
getModel(messageID: string) {
return this.store.messageList.find((message: IMessageModel) => message.ID === messageID);
}
reset(keyList: string[] = []) {
const resetkeyList = keyList.filter((key: string) => !CHAT_IGNORE_KEYS.includes(key));
this.store = {
...this.defaultStore,
...this.store,
...resetkeyList?.reduce((acc, key) => ({ ...acc, [key]: this.defaultStore[key] }), {}),
};
}
private updateMessageList(list: any[]) {
const toBeUpdatedList: IMessageModel[] = [];
list.forEach((item) => {
let messageModel: Message | IMessageModel | undefined = item;
if (!(item instanceof MessageModel)) {
messageModel = this.getModel(item.ID);
if (messageModel) {
(messageModel as MessageModel).updateProperties(item);
} else {
messageModel = new MessageModel(item);
}
}
toBeUpdatedList.push(messageModel as IMessageModel);
});
this.store.messageList = toBeUpdatedList;
}
private updateTranslateTextInfo(data: Record<string, any>) {
this.updateBykey('translateTextInfo', data);
}
private updateVoiceToTextInfo(data: Record<string, any>) {
this.updateBykey('voiceToTextInfo', data);
}
private updateBykey(key: string, data: Record<string, any>) {
const { conversationID, messageID, visible = false } = data;
if (!this.store[key]) {
this.store[key] = new Map();
}
if (!this.store[key].has(conversationID)) {
this.store[key].set(conversationID, []);
}
const list = this.store[key].get(conversationID) || [];
let isAdded = true;
for (let i = 0; i < list.length; i++) {
if (list[i].messageID === messageID) {
list[i].visible = visible;
isAdded = false;
break;
}
}
if (isAdded) {
list.push({ messageID, visible });
}
this.store[key].set(conversationID, list);
}
}
@@ -0,0 +1,88 @@
import type { Conversation } from '@tencentcloud/lite-chat/basic';
import type { IConversationStore } from '../../interface/store';
import type { IConversationModel } from '../../interface/model';
import ConversationModel from '../../model/conversation';
export default class ConversationStore implements IConversationStore {
public store: {
currentConversationID: string;
currentConversation: IConversationModel | null;
totalUnreadCount: number;
conversationList: IConversationModel[];
operationTypeMap: Map<string, number>; // 记录退群、群解散、被踢的群
[key: string]: any;
};
public defaultStore = {
currentConversationID: '',
totalUnreadCount: 0,
conversationList: [],
currentConversation: null,
operationTypeMap: new Map(),
} as any;
constructor() {
this.store = {
currentConversationID: '',
totalUnreadCount: 0,
conversationList: [],
currentConversation: null,
operationTypeMap: new Map(),
};
}
update(key: string, data: any) {
switch (key) {
case 'conversationList':
this.updateConversationList(data);
break;
case 'operationTypeMap':
this.updateOperationTypeMap(data);
break;
default:
this.store[key] = data;
}
}
getData(key: string) {
return this.store[key];
}
getModel(conversationID: string) {
return this.store.conversationList.find((conversation: IConversationModel) => conversation.conversationID === conversationID);
}
reset(keyList: string[] = []) {
this.store = {
...this.defaultStore,
...this.store,
...keyList.reduce((acc, key) => ({ ...acc, [key]: this.defaultStore[key] }), {}),
};
}
private updateConversationList(list: Conversation[] | ConversationModel[]) {
const toBeUpdatedList: any[] = [];
list.forEach((item: Conversation | ConversationModel) => {
let conversationModel: Conversation | IConversationModel | undefined = item;
if (!(item instanceof ConversationModel)) {
conversationModel = new ConversationModel(item);
} else {
(conversationModel as ConversationModel).updateProperties(item);
}
const optType = this.getOperationType(item);
(conversationModel as ConversationModel).updateOperationType(optType);
toBeUpdatedList.push(conversationModel);
});
this.store.conversationList = toBeUpdatedList;
}
private updateOperationTypeMap(data: Record<string, any>) {
const { conversationID, operationType = 0 } = data;
this.store.operationTypeMap.set(conversationID, operationType);
}
private getOperationType(conversation: any) {
const { conversationID } = conversation;
return this.store.operationTypeMap.get(conversationID) || 0;
}
}
@@ -0,0 +1,27 @@
import type { ICustomStore } from '../../interface/store';
export default class CustomStore implements ICustomStore {
public store: any;
constructor() {
this.store = {};
}
update(key: string, data: any) {
this.store[key] = data;
}
getData(key: string) {
return this.store[key];
}
reset(keyList: string[] = []) {
if (keyList.length === 0) {
this.store = {};
}
this.store = {
...this.store,
...keyList.reduce((acc, key) => ({ ...acc, [key]: undefined }), {}),
};
}
}
@@ -0,0 +1,89 @@
import type { Group, Message } from '@tencentcloud/lite-chat/basic';
import type { GroupMember, IGroupStore } from '../../interface/store';
import type { IGroupModel } from '../../interface/model';
import GroupModel from '../../model/group';
export default class GroupStore implements IGroupStore {
public store: {
currentGroupID: string;
currentGroup: IGroupModel | undefined;
currentGroupAttributes: Record<string, any>;
currentGroupCounters: Record<string, number>;
currentGroupMemberList: GroupMember[];
groupList: IGroupModel[];
groupSystemNoticeList: Message[]; // 下发给个人的群系统通知
isCompleted: boolean;
offset: number;
[key: string]: any;
};
public defaultStore = {
currentGroupID: '',
currentGroup: {},
currentGroupAttributes: {},
currentGroupCounters: {},
currentGroupMemberList: [],
groupList: [],
groupSystemNoticeList: [],
isCompleted: false,
offset: 0,
} as any;
constructor() {
this.store = {
currentGroupID: '',
currentGroup: undefined,
currentGroupAttributes: {},
currentGroupCounters: {},
currentGroupMemberList: [],
groupList: [],
groupSystemNoticeList: [],
isCompleted: false,
offset: 0,
};
}
update(key: string, data: any) {
switch (key) {
case 'groupList':
this.updateGroupList(data);
break;
case 'currentGroup':
if (data instanceof GroupModel) {
this.store.currentGroup = data;
} else {
this.store.currentGroup = new GroupModel(data);
}
break;
default:
this.store[key] = data;
}
}
getData(key: string) {
if (key === 'groupSystemNoticeList') {
const groupSystemNoticeList = [...this.store.groupSystemNoticeList];
// 群系统通知给 UI 后立即清空
this.store.groupSystemNoticeList.length = 0;
return groupSystemNoticeList;
}
return this.store[key];
}
reset(keyList: string[] = []) {
this.store = {
...this.defaultStore,
...this.store,
...keyList.reduce((acc, key) => ({ ...acc, [key]: this.defaultStore[key] }), {}),
};
}
private updateGroupList(list: any[]) {
this.store.groupList = list.map((item: Group | GroupModel) => {
if (item instanceof GroupModel) {
return item;
}
return new GroupModel(item);
});
}
}
@@ -0,0 +1,87 @@
import type { IUserStore } from '../../interface/store';
import { UserStatus } from '../../const';
interface IBlackUser {
userID: string;
nick: string;
avatar: string;
}
export default class UserStore implements IUserStore {
public store: {
userProfile: object;
displayOnlineStatus: boolean;
displayMessageReadReceipt: boolean;
userStatusList: Map<string, {
statusType: number;
customStatus: string;
}>;
kickedOut: string;
netStateChange: string;
userBlacklist: IBlackUser[];
targetLanguage: string;
[key: string]: any;
};
public defaultStore = {
userProfile: {},
displayOnlineStatus: false,
displayMessageReadReceipt: true,
userStatusList: new Map(),
kickedOut: '',
netStateChange: '',
userBlacklist: [],
targetLanguage: 'zh',
} as any;
constructor() {
this.store = {
userProfile: {},
displayOnlineStatus: false, // 默认关闭在线状态功能,防止专业版集成引起报错
displayMessageReadReceipt: true, // 默认在用户纬度开启消息阅读状态功能
userStatusList: new Map(), // 获取/订阅/取消订阅用户状态的 userID 列表
kickedOut: '',
netStateChange: '',
userBlacklist: [],
targetLanguage: 'zh',
};
}
update(key: string, data: any) {
switch (key) {
case 'userStatusList':
this.updateUserStatusList(data);
break;
default:
this.store[key] = data;
}
}
getData(key: string) {
return this.store[key];
}
reset(keyList: string[] = []) {
this.store = {
...this.defaultStore,
...this.store,
...keyList.reduce((acc, key) => ({ ...acc, [key]: this.defaultStore[key] }), {}),
};
}
private updateUserStatusList(list: any[]) {
if (list.length === 0) {
this.store.userStatusList.clear();
return;
}
list.forEach((item: { userID: string; statusType: number; customStatus: string }) => {
const { userID, statusType = 0, customStatus = '' } = item;
// statusType = -1 表示该用户已被取消订阅,内部约定规则
if (statusType === UserStatus.UNSUB_USER) {
this.store.userStatusList.delete(userID);
} else {
this.store.userStatusList.set(userID, { statusType, customStatus });
}
});
}
}
@@ -0,0 +1,155 @@
import type { ITUIStore, IOptions, Task } from '../interface/store';
import { StoreName } from '../const';
import { notNotifyList } from '../config';
import AppStore from './store/app';
import UserStore from './store/user';
import ConversationStore from './store/conversation';
import ChatStore from './store/chat';
import GroupStore from './store/group';
import CustomStore from './store/custom';
import type { func } from '../type';
export default class TUIStore implements ITUIStore {
static instance: TUIStore;
public task: Task;
private storeMap: Partial<Record<StoreName, any>>;
constructor() {
this.storeMap = {
[StoreName.APP]: new AppStore(),
[StoreName.USER]: new UserStore(),
[StoreName.CONV]: new ConversationStore(),
[StoreName.CHAT]: new ChatStore(),
[StoreName.GRP]: new GroupStore(),
};
// todo 此处后续优化结构后调整
this.task = {} as Task; // 保存监听回调列表
}
/**
* 获取 TUIStore 实例
*/
static getInstance() {
if (!TUIStore.instance) {
TUIStore.instance = new TUIStore();
}
return TUIStore.instance;
}
/**
* UI 组件注册监听回调
* @param {StoreName} storeName store 名称
* @param {IOptions} options 监听信息
*/
watch(storeName: StoreName, options: IOptions) {
if (!this.task[storeName]) {
this.task[storeName] = {};
}
const watcher = this.task[storeName];
Object.keys(options).forEach((key) => {
const callback = options[key];
if (!watcher[key]) {
watcher[key] = new Map();
}
watcher[key].set(callback, 1);
// 注册监听后立即通知一次默认数据,避免UI层初始化调用 getData
this.notifyOnWatch(storeName, key, callback);
});
}
/**
* UI 取消组件监听回调
* @param {StoreName} storeName store 名称
* @param {IOptions} options 监听信息,包含需要取消的回掉等
*/
unwatch(storeName: StoreName, options: IOptions) {
// todo 该接口暂未支持,unwatch掉同一类,如仅传入store注销掉该store下的所有callback,同样options仅传入key注销掉该key下的所有callback
// options的callback function为必填参数,后续修改
if (!this.task[storeName]) {
return;
}
const watcher = this.task[storeName];
Object.keys(options).forEach((key) => {
watcher[key]?.delete(options[key]);
});
}
/**
* 通用 store 数据更新,messageList 的变更需要单独处理
* @param {StoreName} storeName store 名称
* @param {string} key 变更的 key
* @param {any} data 变更的数据
*/
update(storeName: StoreName, key: string, data: any) {
if (storeName === StoreName.CUSTOM && !this.storeMap[storeName]) {
this.storeMap[storeName] = new CustomStore();
}
this.storeMap[storeName]?.update(key, data);
this.notify(storeName, key);
}
/**
* 获取 Store 数据
* @param {StoreName} storeName store 名称
* @param {string} key 待获取的 key
*/
getData(storeName: StoreName, key: string) {
if (storeName === StoreName.CUSTOM && !this.storeMap[storeName]) {
this.storeMap[storeName] = new CustomStore();
}
return this.storeMap[storeName]?.getData(key);
}
getConversationModel(conversationID: string) {
return this.storeMap[StoreName.CONV]?.getModel(conversationID);
}
getMessageModel(messageID: string) {
return this.storeMap[StoreName.CHAT]?.getModel(messageID);
}
reset(storeName: StoreName, keyList: string[] = [], isNotificationNeeded = false) {
if (storeName in this.storeMap) {
const store = this.storeMap[storeName];
if (keyList.length === 0) {
// reset all
keyList = Object.keys(store?.store);
}
store.reset(keyList);
if (isNotificationNeeded) {
keyList.forEach((key) => {
this.notify(storeName, key);
});
}
}
}
// 注册监听时默认给当前 callback 通知一次,notNotifyList 中配置的 key 如果数据为空就不触发 callback
private notifyOnWatch(storeName: StoreName, key: string, callback: func) {
const data = this.getData(storeName, key);
if (notNotifyList.indexOf(key) > -1 && data.length === 0) {
return;
}
callback && callback.call(this, data);
}
/**
* UI 组件注册监听回调
* @param {StoreName} storeName store 名称
* @param {string} key 变更的 key
*/
private notify(storeName: StoreName, key: string) {
if (!this.task[storeName]) {
return;
}
const watcher = this.task[storeName];
if (watcher[key]) {
const callbackMap = watcher[key];
const data = this.getData(storeName, key);
for (const [callback] of callbackMap.entries()) {
callback.call(this, data);
}
}
}
}