question_uniapp/static/im/mattermost.js

1069 lines
34 KiB
JavaScript
Raw Normal View History

2025-06-06 03:08:19 +08:00
import store from '@/config/store';
import base from '@/config/baseUrl';
class MattermostClient {
constructor() {
// 基础属性
this.ready = false;
2025-06-07 04:59:49 +08:00
this.userBaseUrl = "https://im.dxmt.io"; // 用户 API
this.adminBaseUrl = "https://api.dxmt.io"; // 管理员 API
2025-06-06 03:08:19 +08:00
this.ws = null;
this.wsUrl = '';
this.userToken = ''; // 用户令牌
this.adminToken = ''; // 管理员令牌
this.connected = false;
this.authenticated = false;
this.sequence = 1;
// 重连相关
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectInterval = 3000;
this.reconnectTimer = null;
// 心跳相关
this.heartbeatInterval = 30000; // 30秒
this.heartbeatTimer = null;
this.lastHeartbeatResponse = Date.now();
// 消息处理
this.messageHandlers = new Map(); // 修改为 Map 类型key 为事件名value 为 Set 类型的处理器集合
this.pendingMessages = new Map(); // 存储等待响应的消息
this.messageTimeoutEnabled = false; // 消息响应超时开关,默认关闭
this.messageTimeout = 10000; // 消息响应超时时间(毫秒)
// 状态回调
this.statusChangeCallbacks = new Set();
// 频道相关
this.channels = new Map(); // 存储频道信息
this.directChannels = new Map(); // 存储私聊频道
this.unreadCounts = new Map(); // 存储未读消息数
// 用户相关
this.currentUser = null;
this.userStatuses = new Map(); // 存储用户在线状态
this.userRoles = new Map(); // 存储用户角色
// 消息相关
this.messageCache = new Map(); // 缓存消息
this.deletedMessages = new Set(); // 记录已删除的消息
this.kown_users={}; // 存储已知的用户信息
}
// 初始化连接
async init() {
if(this.ready){
return;
}
if (!store.state.userInfo || !store.state.userInfo.im_token || !store.state.userInfo.token) {
console.log('用户令牌是必需的');
return ;
}
this.userToken = store.state.userInfo.im_token;
this.adminToken = store.state.userInfo.token;
this.currentUser = store.state.userInfo.im_user;
this.kown_users[this.currentUser.id] = this.currentUser;
this.getCurrentUser();
this.ready = true;
// 根据环境选择 ws 或 wss
const protocol = process.env.NODE_ENV === 'development' ? 'ws' : 'wss';
// 构建 WebSocket URL使用用户 API 域名
const connectionId = Date.now().toString(36) + Math.random().toString(36).substr(2);
2025-06-09 00:34:46 +08:00
this.wsUrl = this.userBaseUrl.replace('https',protocol) + `/api/v4/websocket?connection_id=${connectionId}&sequence_number=0`;
2025-06-06 03:08:19 +08:00
// 重置状态
this.sequence = 1;
this.reconnectAttempts = 0;
this.authenticated = false;
// 建立新连接
await this.connect();
// 启动心跳检测
this.startHeartbeat();
}
// 建立 WebSocket 连接
connect() {
return new Promise((resolve, reject) => {
try {
// 清理可能存在的旧连接
if (this.ws) {
return ;
//this.ws.close();
//this.ws = null;
}
this.ws = uni.connectSocket({
url: this.wsUrl,
header: {
'Authorization': `Bearer ${this.userToken}`
},
multiple: true,
success: () => {
console.log('WebSocket 连接创建成功');
},
fail: (error) => {
console.error('WebSocket 连接创建失败:', error);
this.handleConnectionError(error);
reject(error);
}
});
this.ws.onOpen(() => {
console.log('WebSocket 已打开,准备认证');
this.connected = true;
this.notifyStatusChange('connected');
// 发送认证信息,使用正确的认证消息格式
const authMessage = {
action: 'authentication_challenge',
seq: this.sequence,
data: {
token: this.userToken
}
};
this.sequence++;
this.ws.send({
data: JSON.stringify(authMessage),
success: () => {
console.log('认证信息发送成功:', authMessage);
// 注意:这里不立即设置 authenticated 为 true
// 等待服务器的认证响应
},
fail: (error) => {
console.error('认证信息发送失败:', error);
this.handleConnectionError(error);
reject(error);
}
});
});
this.ws.onClose(() => {
console.log('WebSocket 已关闭');
this.handleConnectionClose();
});
this.ws.onError((error) => {
console.error('WebSocket 错误:', error);
this.handleConnectionError(error);
});
this.ws.onMessage((res) => {
try {
const message = JSON.parse(res.data);
console.log('收到消息:', message);
// 处理认证响应
if (message.event === 'hello') {
console.log('认证成功');
this.authenticated = true;
this.notifyStatusChange('authenticated');
this.reconnectAttempts = 0;
resolve();
return;
}
// 处理认证失败
if (message.event === 'authentication_challenge' && message.data && message.data.status !== 'OK') {
console.error('认证失败:', message);
this.handleConnectionError(new Error('认证失败'));
reject(new Error('认证失败'));
return;
}
this.handleMessage(message);
} catch (error) {
console.error('消息解析错误:', error);
}
});
} catch (error) {
console.error('创建 WebSocket 连接失败:', error);
this.handleConnectionError(error);
reject(error);
}
});
}
// 处理连接错误
handleConnectionError(error) {
this.connected = false;
this.authenticated = false;
this.notifyStatusChange('error', error);
this.handleReconnect();
}
// 处理连接关闭
handleConnectionClose() {
this.connected = false;
this.authenticated = false;
this.notifyStatusChange('disconnected');
this.stopHeartbeat();
this.handleReconnect();
}
// 处理重连
handleReconnect() {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
this.notifyStatusChange('reconnecting', this.reconnectAttempts);
this.reconnectTimer = setTimeout(() => {
this.connect().catch(error => {
console.error('重连失败:', error);
});
}, this.reconnectInterval);
} else {
console.error('达到最大重连次数,停止重连');
this.notifyStatusChange('failed');
}
}
// 启动心跳检测
startHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
}
this.heartbeatTimer = setInterval(() => {
if (this.connected && this.authenticated) {
const now = Date.now();
if (now - this.lastHeartbeatResponse > this.heartbeatInterval * 2) {
console.log('心跳超时,准备重连');
this.handleConnectionClose();
return;
}
// 发送 ping 消息
this.sendWebSocketMessage('ping').catch(error => {
console.error('心跳消息发送失败:', error);
});
}
}, this.heartbeatInterval);
}
// 停止心跳检测
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
// 设置消息响应超时开关
setMessageTimeoutEnabled(enabled) {
this.messageTimeoutEnabled = enabled;
console.log(`消息响应超时已${enabled ? '启用' : '禁用'}`);
}
// 设置消息响应超时时间
setMessageTimeout(timeout) {
if (timeout < 1000) {
console.warn('消息响应超时时间不能小于1000毫秒');
return;
}
this.messageTimeout = timeout;
console.log(`消息响应超时时间已设置为${timeout}毫秒`);
}
// 获取消息响应超时设置
getMessageTimeoutSettings() {
return {
enabled: this.messageTimeoutEnabled,
timeout: this.messageTimeout
};
}
// 发送 WebSocket 消息
sendWebSocketMessage(action, data = {}) {
if (!this.connected) {
return Promise.reject(new Error('WebSocket 未连接'));
}
// 构建符合 Mattermost 格式的消息
const message = {
action,
seq: this.sequence
};
// 只有在非 ping 消息时才添加其他数据
if (action !== 'ping' && Object.keys(data).length > 0) {
Object.assign(message, data);
}
// 增加序列号
this.sequence++;
return new Promise((resolve, reject) => {
// 存储等待响应的消息
this.pendingMessages.set(message.seq, { resolve, reject });
// 设置超时处理
let timeout;
if (this.messageTimeoutEnabled) {
timeout = setTimeout(() => {
if (this.pendingMessages.has(message.seq)) {
this.pendingMessages.delete(message.seq);
reject(new Error(`消息 ${message.seq} 响应超时`));
}
}, this.messageTimeout);
}
this.ws.send({
data: JSON.stringify(message),
success: () => {
console.log('WebSocket 消息发送成功:', message);
},
fail: (error) => {
console.error('WebSocket 消息发送失败:', error);
this.pendingMessages.delete(message.seq);
if (timeout) {
clearTimeout(timeout);
}
reject(error);
}
});
});
}
// 处理接收到的消息
handleMessage(message) {
console.log('处理消息:', message);
// 更新心跳时间戳
if (message.action === 'pong') {
this.lastHeartbeatResponse = Date.now();
return;
}
// 处理消息响应
// if (message.seq && this.pendingMessages.has(message.seq)) {
// const { resolve } = this.pendingMessages.get(message.seq);
// this.pendingMessages.delete(message.seq);
// resolve(message);
// return;
// }
// 更新序列号
if (message.seq) {
this.sequence = message.seq + 1;
}
// 处理不同类型的消息
if (message.event === 'posted') {
const post = message.data;
// 更新未读消息数
if (post.channel_id && !this.isCurrentChannel(post.channel_id)) {
const currentCount = this.unreadCounts.get(post.channel_id) || 0;
this.unreadCounts.set(post.channel_id, currentCount + 1);
}
// 缓存消息
this.messageCache.set(post.id, post);
// 如果当前不在聊天页面,增加未读数
const currentPage = getCurrentPages();
const isInChatPage = currentPage.some(page =>
page.route === 'pages/im/chat' &&
((page.$page.options.target_id == this.getCurrentUserId() &&
page.$page.options.target_type === 'private') ||
(page.$page.options.target_id == '3awtqn6b17d4bp8ro68qmzo96o' &&
page.$page.options.target_type === 'group'))
);
if(!isInChatPage){
uni.createPushMessage({
title:post.channel_display_name.substr(0,1).replace("_","@"),
content:post.message,
payload:post,
//icon:'',
//sound:'',
cover:false,
complete(){
}
});
}
} else if (message.event === 'post_deleted') {
// 处理消息删除
const postId = message.data.post_id;
this.deletedMessages.add(postId);
this.messageCache.delete(postId);
} else if (message.event === 'user_updated') {
// 处理用户信息更新
const user = message.data;
if (user.roles) {
this.userRoles.set(user.id, user.roles);
}
} else if (message.event === 'status_change') {
// 处理用户状态变化
const status = message.data;
this.userStatuses.set(status.user_id, status);
}
// 触发消息处理器
if (message.event && this.messageHandlers.has(message.event)) {
console.log(`触发消息处理器: ${message.event}, 处理器数量: ${this.messageHandlers.get(message.event).size}`);
this.messageHandlers.get(message.event).forEach(handler => {
try {
handler(message.data);
} catch (error) {
console.error('消息处理器错误:', error);
}
});
}
}
// 注册状态变化回调
onStatusChange(callback) {
this.statusChangeCallbacks.add(callback);
}
// 移除状态变化回调
offStatusChange(callback) {
this.statusChangeCallbacks.delete(callback);
}
// 通知状态变化
notifyStatusChange(status, data = null) {
this.statusChangeCallbacks.forEach(callback => {
try {
callback(status, data);
} catch (error) {
console.error('状态变化回调错误:', error);
}
});
}
// 注册消息处理器
on(event, handler) {
if (!this.messageHandlers.has(event)) {
this.messageHandlers.set(event, new Set());
}
this.messageHandlers.get(event).add(handler);
console.log(`注册消息处理器: ${event}, 当前处理器数量: ${this.messageHandlers.get(event).size}`);
}
// 移除消息处理器
off(event, handler) {
if (this.messageHandlers.has(event)) {
this.messageHandlers.get(event).delete(handler);
console.log(`移除消息处理器: ${event}, 剩余处理器数量: ${this.messageHandlers.get(event).size}`);
}
}
// 获取连接状态
getConnectionStatus() {
return {
connected: this.connected,
authenticated: this.authenticated,
reconnectAttempts: this.reconnectAttempts,
maxReconnectAttempts: this.maxReconnectAttempts,
wsUrl: this.wsUrl,
lastHeartbeat: this.lastHeartbeatResponse
};
}
// 断开连接
async disconnect() {
this.stopHeartbeat();
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
if (this.ws) {
this.ws.close();
this.ws = null;
}
this.connected = false;
this.authenticated = false;
this.notifyStatusChange('disconnected');
// 清理所有等待响应的消息
this.pendingMessages.forEach(({ reject }) => {
reject(new Error('连接已断开'));
});
this.pendingMessages.clear();
}
async getRecentContacts(){
const channels = await this.request({
url: `/api/v4/users/me/channels`,
method: 'GET'
});
let result = {};
let unkown_user_ids = [];
if (channels && channels.length > 0) {
const dmChannels = channels
.filter(c => c.type === 'D')
.sort((a, b) => b.last_post_at - a.last_post_at); // 按最后消息时间降序
dmChannels.forEach(channel => {
var member_ids = channel.name.replace(this.currentUser.id,'').replace('__','');
if(!this.kown_users[member_ids]){
result[member_ids]={};
unkown_user_ids.push(member_ids);
}else{
result[member_ids]=this.kown_users[member_ids];
}
});
if(unkown_user_ids.length>0){
const users = await this.getUsersByIds(unkown_user_ids);
users.data.forEach(user => {
this.kown_users[user.id]=user;
result[user.id]=user;
});
}
return result;
}
return [];
}
// 获取私聊频道
async getDirectChannels() {
//return [];
try {
const channels = await this.request({
url: `/api/v4/users/me/channels`,
method: 'GET'
});
console.log(channels);
if (channels && channels.length > 0) {
channels.forEach(channel => {
this.directChannels.set(channel.id, channel);
});
return channels;
}
return [];
} catch (error) {
console.error('获取私聊频道错误:', error);
throw error;
}
}
// HTTP 请求封装
async request(options) {
let { url, method = 'GET', data = {}, headers = {},to_server='user' } = options;
let requestHeaders ={};
if(to_server=='user'){
// 添加认证头
requestHeaders = {
'Authorization': `Bearer ${this.userToken}`,
...headers
};
url = this.userBaseUrl + url;
}else{
// 添加认证头
requestHeaders = {
'token': this.adminToken,
...headers
};
url = this.adminBaseUrl + url;
}
const [error, response] = await uni.request({
url,
method,
data,
header: requestHeaders
}).catch(err => [err, null]);
if (error) {
console.error(`请求失败 [${method} ${url}]:`, error);
throw error;
}
if (response.statusCode >= 200 && response.statusCode < 300) {
return response.data;
}
console.error(`请求失败 [${method} ${url}]:`, response);
throw new Error(`请求失败: ${response.statusCode}`);
}
// 文件上传封装
async uploadFile(options) {
const { url, filePath, name = 'files', headers = {} } = options;
// 添加认证头
const requestHeaders = {
'Authorization': `Bearer ${this.userToken}`,
...headers
};
const [error, response] = await uni.uploadFile({
url,
filePath,
name,
header: requestHeaders
}).catch(err => [err, null]);
if (error) {
console.error(`文件上传失败 [${url}]:`, error);
throw error;
}
if (response.statusCode >= 200 && response.statusCode < 300) {
return response.data;
}
console.error(`文件上传失败 [${url}]:`, response);
throw new Error(`文件上传失败: ${response.statusCode}`);
}
// 获取频道消息
async getChannelPosts(channelId, page = 0, perPage = 30) {
try {
const response = await this.request({
url: `/api/v4/channels/${channelId}/posts`,
method: 'GET',
data: {
page,
per_page: perPage,
include_deleted: false
}
});
if (!response || !response.order || !response.posts) {
console.error('无效的消息数据格式:', response);
return [];
}
return response.order.reverse().map(id => response.posts[id]);
} catch (error) {
console.error('获取消息失败:', error);
return [];
}
}
// 发送文本消息
async sendTextMessage(channelId, message) {
return this.request({
url: `/api/v4/posts`,
method: 'POST',
data: {
channel_id: channelId,
message: message,
root_id: '',
file_ids: [],
props: {}
}
});
}
// 发送图片消息
async sendImageMessage(channelId, filePath) {
try {
// 1. 上传文件
const fileData = await this.uploadFile({
url: `${this.userBaseUrl}/api/v4/files`,
filePath
});
const fileInfo = JSON.parse(fileData);
const fileId = fileInfo.file_infos[0].id;
// 2. 发送带图片的消息
return this.request({
url: `${this.userBaseUrl}/api/v4/posts`,
method: 'POST',
data: {
channel_id: channelId,
message: '',
file_ids: [fileId],
props: {}
}
});
} catch (error) {
console.error('发送图片消息失败:', error);
throw error;
}
}
// 标记消息为已读
async markChannelAsViewed(channelId) {
if (!this.authenticated) {
throw new Error('未认证');
}
const response = await this.request({
url: `/api/v4/channels/${channelId}/view`,
method: 'POST',
data: {
channel_id: channelId
}
});
this.unreadCounts.set(channelId, 0);
return response;
}
// 检查是否是当前查看的频道
isCurrentChannel(channelId) {
return this.currentChannelId === channelId;
}
// 获取频道的未读消息数
getUnreadCount(channelId) {
return this.unreadCounts.get(channelId) || 0;
}
// 设置当前查看的频道
setCurrentChannel(channelId) {
this.currentChannelId = channelId;
// 自动标记为已读
if (channelId) {
this.markChannelAsViewed(channelId).catch(console.error);
}
}
// 获取当前用户信息
async getCurrentUser() {
// if (!this.authenticated) {
// throw new Error('未认证');
// }
const user = await this.request({
url: `/api/v4/users/me`,
method: 'GET'
});
this.currentUser = user;
this.userRoles.set(user.id, user.roles);
return user;
}
// 检查用户是否是管理员
isAdmin(userId) {
const roles = this.userRoles.get(userId);
return roles && roles.includes('system_admin');
}
// 检查用户是否是客服
isKefu(userId) {
const roles = this.userRoles.get(userId);
return roles && roles.includes('system_user');
}
// 获取用户状态
async getUserStatus(userId) {
if (!this.authenticated) {
throw new Error('未认证');
}
try {
// 使用用户 API 获取状态
const response = await this.request({
to_server:'admin',
url: `/api/v4/users/${userId}/status`,
method: 'GET'
});
if (response.statusCode === 200) {
this.userStatuses.set(userId, response.data);
return response.data;
}
throw new Error('获取用户状态失败');
} catch (error) {
console.error('获取用户状态错误:', error);
throw error;
}
}
// 获取多个用户状态
async getUsersStatus(userIds) {
if (!this.authenticated) {
throw new Error('未认证');
}
try {
const response = await this.request({
to_server:'admin',
url: `/api/v4/users/status/ids`,
method: 'POST',
data: userIds
});
if (response.statusCode === 200) {
response.data.forEach(status => {
this.userStatuses.set(status.user_id, status);
});
return response.data;
}
throw new Error('获取用户状态失败');
} catch (error) {
console.error('获取用户状态错误:', error);
throw error;
}
}
// 搜索消息
async searchPosts(terms, isOrSearch = false) {
if (!this.authenticated) {
throw new Error('未认证');
}
try {
const response = await this.request({
url: `/api/v4/posts/search`,
method: 'POST',
data: {
terms,
is_or_search: isOrSearch
}
});
if (response.statusCode === 200) {
return response.data;
}
throw new Error('搜索消息失败');
} catch (error) {
console.error('搜索消息错误:', error);
throw error;
}
}
// 删除消息(管理员功能)
async deletePost(postId) {
if (!this.authenticated) {
throw new Error('未认证');
}
if (!this.isAdmin(this.currentUser?.id)) {
throw new Error('没有权限删除消息');
}
try {
const response = await this.request({
to_server:'admin',
url: `/api/v4/posts/${postId}`,
method: 'DELETE',
});
if (response.statusCode === 200) {
this.deletedMessages.add(postId);
return response.data;
}
throw new Error('删除消息失败');
} catch (error) {
console.error('删除消息错误:', error);
throw error;
}
}
// 撤回消息
async editPost(postId, message) {
if (!this.authenticated) {
throw new Error('未认证');
}
try {
const response = await this.request({
url: `/api/v4/posts/${postId}`,
method: 'PUT',
data: {
message,
has_reactions: false
}
});
if (response.statusCode === 200) {
return response.data;
}
throw new Error('撤回消息失败');
} catch (error) {
console.error('撤回消息错误:', error);
throw error;
}
}
// 禁言用户(管理员功能)
async muteUser(userId, channelId) {
if (!this.authenticated) {
throw new Error('未认证');
}
if (!this.isAdmin(this.currentUser?.id)) {
throw new Error('没有权限禁言用户');
}
try {
const response = await this.request({
to_server:'admin',
url: `/api/v4/channels/${channelId}/members/${userId}/roles`,
method: 'PUT',
data: {
roles: 'channel_user channel_muted'
}
});
if (response.statusCode === 200) {
return response.data;
}
throw new Error('禁言用户失败');
} catch (error) {
console.error('禁言用户错误:', error);
throw error;
}
}
// 解除禁言(管理员功能)
async unmuteUser(userId, channelId) {
if (!this.authenticated) {
throw new Error('未认证');
}
if (!this.isAdmin(this.currentUser?.id)) {
throw new Error('没有权限解除禁言');
}
try {
const response = await this.request({
to_server:'admin',
url: `/api/v4/channels/${channelId}/members/${userId}/roles`,
method: 'PUT',
data: {
roles: 'channel_user'
}
});
if (response.statusCode === 200) {
return response.data;
}
throw new Error('解除禁言失败');
} catch (error) {
console.error('解除禁言错误:', error);
throw error;
}
}
// 获取被禁言用户列表(管理员功能)
async getMutedUsers(channelId) {
if (!this.authenticated) {
throw new Error('未认证');
}
if (!this.isAdmin(this.currentUser?.id)) {
throw new Error('没有权限查看禁言列表');
}
try {
const response = await this.request({
to_server:'admin',
url: `/api/v4/channels/${channelId}/members`,
method: 'GET'
});
if (response.statusCode === 200) {
return response.data.filter(member =>
member.roles.includes('channel_muted')
);
}
throw new Error('获取禁言列表失败');
} catch (error) {
console.error('获取禁言列表错误:', error);
throw error;
}
}
// 检查消息是否被删除
isMessageDeleted(postId) {
return this.deletedMessages.has(postId);
}
// 获取缓存的消息
getCachedMessage(postId) {
return this.messageCache.get(postId);
}
// 清理消息缓存
clearMessageCache() {
this.messageCache.clear();
this.deletedMessages.clear();
}
// 获取当前用户ID
getCurrentUserId() {
return this.currentUser?.id;
}
// 检查是否已认证
isAuthenticated() {
return this.authenticated;
}
async getUserInfo(username){
if(username.indexOf('@')!==-1){
return await this.request({
to_server:'admin',
url: `/api/v4/email/${username}`,
method: 'GET'
});
}else{
return await this.request({
to_server:'admin',
url: `/api/v4/users/${username}`,
method: 'GET'
});
}
}
2025-06-09 00:34:46 +08:00
async getUserById(user_id){
if(this.kown_users[user_id]){
return this.kown_users[user_id];
}
var result = await this.getUsersByIds([user_id]);
return result[0];
}
2025-06-06 03:08:19 +08:00
async getUsersByIds(user_ids){
return await this.request({
to_server:'admin',
url: `/api/v4/users/ids`,
method: 'POST',
data:JSON.stringify(user_ids)
});
}
async getChannelById(channel_id){
return await this.request({
url:`/api/v4/channels/${channel_id}`,
method: 'GET'
})
}
async getChannelMembers(channel_id){
return await this.request({
url:`/api/v4/channels/${channel_id}/members`,
method: 'GET'
})
}
async talk_to_user(item){
if (!item.type || item.type!== 'O') {
// 获取当前用户ID
const currentUserId = await this.getCurrentUserId();
// 获取目标用户ID
const targetUserId = item.id;
// 查询直接消息频道
const response = await this.request({
url: `/api/v4/channels/direct`,
method: 'POST',
data: [currentUserId, targetUserId]
});
if (response.id) {
// 使用返回的频道ID进行跳转
return `/pages/im/chat?target_id=${response.id}&target_type=private`;
}
}
// 如果不是私聊或查询失败使用原始ID跳转
return `/pages/im/chat?target_id=${item.id}&target_type=group`;
}
}
// 创建单例
const mattermost = new MattermostClient();
export default mattermost;