2025-06-06 03:08:19 +08:00
|
|
|
<template>
|
|
|
|
<view class="container" :style="pageHeight">
|
|
|
|
<uni-nav-bar @clickLeft="goto(1, 1)" left-icon="back" :title="channel.name || $t('Chat')" :border="false"
|
2025-06-10 17:10:40 +08:00
|
|
|
backgroundColor="#F5F5F5" :statusBar="true">
|
2025-06-10 20:19:45 +08:00
|
|
|
<!-- <template v-slot:right>
|
2025-06-06 03:08:19 +08:00
|
|
|
<view class="nav-right">
|
|
|
|
<uni-icons v-if="channel.type === 'O'" type="more-filled" size="24"
|
|
|
|
@click="showGroupMenu"></uni-icons>
|
|
|
|
</view>
|
2025-06-10 20:19:45 +08:00
|
|
|
</template> -->
|
2025-06-06 03:08:19 +08:00
|
|
|
</uni-nav-bar>
|
|
|
|
|
2025-06-10 03:06:54 +08:00
|
|
|
<view class="box-1" @click="emoji_show=addons_show=false">
|
2025-06-06 03:08:19 +08:00
|
|
|
<scroll-view scroll-y refresher-background="transparent" style="height: 100%;"
|
|
|
|
@refresherrefresh="refresherrefresh" :refresher-enabled="hasMoreMessages" :scroll-with-animation="false"
|
|
|
|
:refresher-triggered="scrollView.refresherTriggered" :scroll-into-view="scrollView.intoView"
|
2025-06-10 03:06:54 +08:00
|
|
|
@scrolltoupper="loadMoreMessages">
|
2025-06-06 03:08:19 +08:00
|
|
|
<view class="talk-list">
|
|
|
|
<!-- 加载更多提示 -->
|
|
|
|
<view v-if="isLoading" class="loading-more">
|
|
|
|
<uni-icons type="spinner-cycle" size="20" color="#999"></uni-icons>
|
2025-06-10 20:19:45 +08:00
|
|
|
<text>{{$t('im.loading')}}</text>
|
2025-06-06 03:08:19 +08:00
|
|
|
</view>
|
|
|
|
|
|
|
|
<!-- 消息列表 -->
|
|
|
|
<view v-for="item in talkList" :key="item.id" :id="'msg-' + item.id" class="message-item">
|
|
|
|
<view class="item flex-col"
|
|
|
|
:class="[item.user_id == currentUserId ? 'push' : 'pull', item.status === 'failed' ? 'failed' : '']">
|
|
|
|
<image :src="getAvatarUrl(item)" mode="aspectFill" class="pic"
|
|
|
|
@tap="showUserInfo(item.from_user)">
|
|
|
|
</image>
|
|
|
|
<view class="body">
|
2025-06-10 03:06:54 +08:00
|
|
|
<view class="nickname">{{ item.from_user.nickname || item.from_user.username || 'Unknown'}}</view>
|
2025-06-06 03:08:19 +08:00
|
|
|
<view class="content" :class="{ 'image-content': item.type === 'image' }">
|
|
|
|
<template v-if="item.type === 'image'">
|
2025-06-10 03:06:54 +08:00
|
|
|
<image :src="item.message.thumbnail" mode="widthFix"
|
|
|
|
style="max-width: 400rpx;" @tap="previewImage(item)"
|
2025-06-06 03:08:19 +08:00
|
|
|
@load="onImageLoad(item.id)"></image>
|
|
|
|
</template>
|
|
|
|
<template v-else>
|
|
|
|
<text>{{ item.message }}</text>
|
|
|
|
</template>
|
|
|
|
<!-- 消息状态 -->
|
|
|
|
<view v-if="item.user_id == currentUserId" class="message-status">
|
|
|
|
<uni-icons v-if="item.status === 'sending'" type="spinner-cycle" size="14"
|
|
|
|
color="#999"></uni-icons>
|
|
|
|
<uni-icons v-else-if="item.status === 'failed'" type="closeempty" size="14"
|
|
|
|
color="#ff4d4f" @tap="resendMessage(item)"></uni-icons>
|
|
|
|
<uni-icons v-else-if="item.status === 'sent'" type="checkmarkempty" size="14"
|
|
|
|
color="#999"></uni-icons>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
<view class="time">{{ formatTime(item.create_time) }}</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</scroll-view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="box-2">
|
|
|
|
<view class="input-area flex-col">
|
|
|
|
<view class="flex-grow">
|
|
|
|
<uni-easyinput type="text" class="input" v-model="content" :placeholder="$t('Type a message')"
|
|
|
|
placeholder-style="color:#999;" :cursor-spacing="6" trim="both" confirmType="send"
|
|
|
|
:inputBorder="false" @confirm="sendMessage" @focus="onInputFocus"
|
|
|
|
@blur="onInputBlur"></uni-easyinput>
|
|
|
|
</view>
|
|
|
|
<view class="action-buttons">
|
2025-06-10 03:06:54 +08:00
|
|
|
<button @tap="showemoji" style="display: flex;align-items: center;">
|
|
|
|
<img src="@/static/im/emoji.png" style="height:48rpx" mode="widthFix" />
|
2025-06-06 03:08:19 +08:00
|
|
|
</button>
|
2025-06-10 03:06:54 +08:00
|
|
|
<button @tap="showaddons" style="display: flex;align-items: center;">
|
2025-06-06 03:08:19 +08:00
|
|
|
<uni-icons type="plus" size="24" color="#666"></uni-icons>
|
|
|
|
</button>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
<view class="addons" v-show="addons_show">
|
|
|
|
<button @tap="sendphoto">
|
|
|
|
<uni-icons type="image" size="24" color="#666"></uni-icons>
|
2025-06-10 20:19:45 +08:00
|
|
|
{{$t('im.gallery')}}
|
2025-06-06 03:08:19 +08:00
|
|
|
</button>
|
|
|
|
<button @tap="sendcamera">
|
|
|
|
<uni-icons type="camera-filled" size="24" color="#666"></uni-icons>
|
2025-06-10 20:19:45 +08:00
|
|
|
{{$t('im.camera')}}
|
2025-06-06 03:08:19 +08:00
|
|
|
</button>
|
|
|
|
</view>
|
|
|
|
<view class="emoji" v-show="emoji_show">
|
|
|
|
<view v-for="emoji in emojiList" :key="emoji" @click="addEmoji(emoji)">{{ emoji }}</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<!-- 群组菜单弹窗 -->
|
|
|
|
<uni-popup ref="groupMenu" type="bottom">
|
|
|
|
<view class="group-menu">
|
|
|
|
<view class="menu-item" @tap="showGroupMembers">
|
|
|
|
<uni-icons type="person" size="20"></uni-icons>
|
|
|
|
<text>{{ $t('Group Members') }}</text>
|
|
|
|
</view>
|
|
|
|
<view class="menu-item" @tap="showGroupInfo">
|
|
|
|
<uni-icons type="info" size="20"></uni-icons>
|
|
|
|
<text>{{ $t('Group Info') }}</text>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</uni-popup>
|
|
|
|
</view>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
import { mapState, mapMutations } from 'vuex';
|
|
|
|
import mattermost from '@/static/im/mattermost.js';
|
|
|
|
import emojiList from '@/static/im/emoji.js';
|
|
|
|
|
|
|
|
// 日期格式化函数
|
|
|
|
function formatTime(timestamp) {
|
|
|
|
if (!timestamp) return '';
|
|
|
|
|
|
|
|
const date = new Date(timestamp);
|
|
|
|
const now = new Date();
|
|
|
|
const diff = now - date;
|
|
|
|
|
|
|
|
// 今天的消息只显示时间
|
|
|
|
if (diff < 24 * 60 * 60 * 1000 && date.getDate() === now.getDate()) {
|
|
|
|
return date.toLocaleTimeString('zh-CN', {
|
|
|
|
hour: '2-digit',
|
|
|
|
minute: '2-digit',
|
|
|
|
hour12: false
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// 昨天的消息显示"昨天"和时间
|
|
|
|
if (diff < 48 * 60 * 60 * 1000 && date.getDate() === now.getDate() - 1) {
|
|
|
|
return `昨天 ${date.toLocaleTimeString('zh-CN', {
|
|
|
|
hour: '2-digit',
|
|
|
|
minute: '2-digit',
|
|
|
|
hour12: false
|
|
|
|
})}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 一周内的消息显示星期几和时间
|
|
|
|
if (diff < 7 * 24 * 60 * 60 * 1000) {
|
|
|
|
const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
|
|
|
|
return `星期${weekdays[date.getDay()]} ${date.toLocaleTimeString('zh-CN', {
|
|
|
|
hour: '2-digit',
|
|
|
|
minute: '2-digit',
|
|
|
|
hour12: false
|
|
|
|
})}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 其他消息显示完整日期和时间
|
|
|
|
return date.toLocaleString('zh-CN', {
|
|
|
|
year: 'numeric',
|
|
|
|
month: '2-digit',
|
|
|
|
day: '2-digit',
|
|
|
|
hour: '2-digit',
|
|
|
|
minute: '2-digit',
|
|
|
|
hour12: false
|
|
|
|
});
|
|
|
|
}
|
|
|
|
let users = {};
|
|
|
|
|
|
|
|
export default {
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
init: {
|
2025-06-10 03:06:54 +08:00
|
|
|
cdnurl: 'http://api.dxmt.io'
|
2025-06-06 03:08:19 +08:00
|
|
|
},
|
|
|
|
channel:{},
|
|
|
|
// 滚动容器
|
|
|
|
scrollView: {
|
|
|
|
refresherTriggered: false,
|
|
|
|
intoView: '',
|
|
|
|
safeAreaHeight: 0
|
|
|
|
},
|
|
|
|
// 聊天列表数据
|
|
|
|
talkList: [],
|
|
|
|
// 请求参数
|
|
|
|
ajax: {
|
|
|
|
limit: 20,
|
|
|
|
last_page: 1,
|
|
|
|
page: 0,
|
|
|
|
flag: false,
|
|
|
|
loading: false
|
|
|
|
},
|
|
|
|
// 发送内容
|
|
|
|
content: '',
|
|
|
|
// 图片上传状态
|
|
|
|
uploading: false,
|
|
|
|
// 消息状态
|
|
|
|
messageStatus: new Map(),
|
|
|
|
// 图片加载状态
|
|
|
|
imageLoading: new Map(),
|
|
|
|
// 当前用户ID
|
|
|
|
currentUserId: '',
|
|
|
|
emojiList:emojiList,
|
|
|
|
emoji_show:false,
|
|
|
|
addons_show:false,
|
2025-06-10 03:06:54 +08:00
|
|
|
// 图片加载队列
|
|
|
|
imageLoadQueue: [],
|
|
|
|
isProcessingQueue: false,
|
|
|
|
// 占位图
|
|
|
|
placeholderImage: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
|
2025-06-06 03:08:19 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
...mapState(['userInfo']),
|
|
|
|
// 页面高度
|
|
|
|
pageHeight() {
|
|
|
|
const safeAreaHeight = this.scrollView.safeAreaHeight;
|
|
|
|
return safeAreaHeight > 0 ? `height: calc(${safeAreaHeight}px - var(--window-top));` : '';
|
|
|
|
},
|
|
|
|
// 是否有更多消息
|
|
|
|
hasMoreMessages() {
|
|
|
|
return this.ajax.last_page > this.ajax.page;
|
|
|
|
},
|
|
|
|
// 是否正在加载
|
|
|
|
isLoading() {
|
|
|
|
return this.ajax.loading;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async onLoad(options) {
|
|
|
|
try {
|
|
|
|
if(this.userInfo.im_token){
|
|
|
|
this.currentUserId = this.userInfo.im_user.id;
|
|
|
|
|
|
|
|
mattermost.init();
|
|
|
|
// 初始化目标信息
|
|
|
|
await this.initChannel(options);
|
|
|
|
|
|
|
|
// 获取系统信息
|
|
|
|
this.initSystemInfo();
|
|
|
|
|
|
|
|
// 获取历史消息
|
|
|
|
await this.loadMessages();
|
|
|
|
|
|
|
|
// 注册消息处理器
|
|
|
|
this.registerMessageHandlers();
|
|
|
|
}
|
|
|
|
// 标记频道为已读
|
|
|
|
//await this.markChannelAsRead();
|
|
|
|
} catch (error) {
|
|
|
|
console.error('页面加载失败:', error);
|
|
|
|
uni.showToast({
|
2025-06-10 20:19:45 +08:00
|
|
|
title: this.$t('im.loading_fail'),
|
2025-06-06 03:08:19 +08:00
|
|
|
icon: 'none'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onUnload() {
|
|
|
|
// 移除消息处理器
|
|
|
|
this.unregisterMessageHandlers();
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
...mapMutations(['setUnreadCount']),
|
|
|
|
|
|
|
|
// 初始化目标信息
|
|
|
|
async initChannel(options) {
|
|
|
|
let channel = await mattermost.getChannelById(options.target_id);
|
|
|
|
if(channel.type=='O'){
|
2025-06-10 03:06:54 +08:00
|
|
|
channel.name = channel.display_name || channel.name;
|
2025-06-06 03:08:19 +08:00
|
|
|
}
|
|
|
|
if(channel.type=='D'){
|
|
|
|
var target_user_id = channel.name.replace(mattermost.getCurrentUserId(),'').replace('__','')
|
|
|
|
var target_user={};
|
|
|
|
if(mattermost.kown_users[target_user_id]){
|
|
|
|
target_user = mattermost.kown_users[target_user_id];
|
|
|
|
}else{
|
|
|
|
let channelMembers = await mattermost.getUsersByIds([target_user_id]);
|
|
|
|
target_user = channelMembers.data[0];
|
|
|
|
}
|
|
|
|
channel.name = target_user.nickname || target_user.username;
|
|
|
|
}
|
|
|
|
this.channel = channel;
|
|
|
|
},
|
|
|
|
|
|
|
|
// 初始化系统信息
|
|
|
|
initSystemInfo() {
|
|
|
|
// #ifdef H5
|
|
|
|
this.scrollView.safeAreaHeight = uni.getSystemInfoSync().safeArea.height;
|
|
|
|
// #endif
|
|
|
|
},
|
|
|
|
|
|
|
|
// 注册消息处理器
|
|
|
|
registerMessageHandlers() {
|
|
|
|
console.log('注册消息处理器');
|
|
|
|
mattermost.on('posted', this.handleNewMessage);
|
|
|
|
mattermost.on('post_edited', this.handleMessageEdited);
|
|
|
|
mattermost.on('post_deleted', this.handleMessageDeleted);
|
|
|
|
mattermost.on('status_change', this.handleStatusChange);
|
|
|
|
},
|
|
|
|
|
|
|
|
// 移除消息处理器
|
|
|
|
unregisterMessageHandlers() {
|
|
|
|
console.log('移除消息处理器');
|
|
|
|
mattermost.off('posted', this.handleNewMessage);
|
|
|
|
mattermost.off('post_edited', this.handleMessageEdited);
|
|
|
|
mattermost.off('post_deleted', this.handleMessageDeleted);
|
|
|
|
mattermost.off('status_change', this.handleStatusChange);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 处理消息数据
|
2025-06-10 03:06:54 +08:00
|
|
|
async processMessageData(post, senderName = '') {
|
|
|
|
console.log('处理消息数据:', post);
|
2025-06-06 03:08:19 +08:00
|
|
|
// 构建用户信息
|
2025-06-10 03:06:54 +08:00
|
|
|
const fromUser = await mattermost.getUserById(post.user_id);
|
2025-06-06 03:08:19 +08:00
|
|
|
|
|
|
|
// 处理消息类型
|
|
|
|
let messageType = 'text';
|
|
|
|
let messageContent = post.message;
|
|
|
|
let imageUrl = '';
|
|
|
|
|
2025-06-10 03:06:54 +08:00
|
|
|
let imageInfo = null;
|
|
|
|
// 检查是否是图片消息
|
|
|
|
if (post.file_ids && post.file_ids.length > 0) {
|
|
|
|
messageType = 'image';
|
|
|
|
// 从 metadata.files 中获取图片信息
|
|
|
|
if (post.metadata && post.metadata.files && post.metadata.files.length > 0) {
|
|
|
|
const file = post.metadata.files[0];
|
|
|
|
if (file.mime_type && file.mime_type.startsWith('image/')) {
|
|
|
|
// 构建图片信息对象
|
|
|
|
imageInfo = {
|
|
|
|
id: file.id,
|
|
|
|
width: file.width,
|
|
|
|
height: file.height,
|
|
|
|
mimeType: file.mime_type
|
|
|
|
};
|
|
|
|
console.log(file)
|
|
|
|
|
|
|
|
messageContent = {
|
|
|
|
id: file.id,
|
|
|
|
//thumbnail: this.placeholderImage, // 先使用占位图
|
|
|
|
thumbnail: `${mattermost.adminBaseUrl}/api/v4/files/${file.id}/thumbnail`,
|
|
|
|
preview: null,
|
|
|
|
original: `${mattermost.adminBaseUrl}/api/v4/files/${file.id}/preview`
|
|
|
|
};
|
|
|
|
|
|
|
|
// 将图片添加到加载队列
|
|
|
|
//this.addToImageLoadQueue(post.id, file.id);
|
|
|
|
}
|
2025-06-06 03:08:19 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
...post,
|
2025-06-10 03:06:54 +08:00
|
|
|
id: post.id,
|
|
|
|
user_id: post.user_id,
|
|
|
|
message: messageContent,
|
|
|
|
type: messageType,
|
|
|
|
create_time: post.create_at,
|
2025-06-06 03:08:19 +08:00
|
|
|
status: 'success',
|
2025-06-10 03:06:54 +08:00
|
|
|
from_user: fromUser,
|
|
|
|
imageInfo: imageInfo
|
2025-06-06 03:08:19 +08:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2025-06-10 03:06:54 +08:00
|
|
|
// 添加图片到加载队列
|
|
|
|
addToImageLoadQueue(messageId, fileId) {
|
|
|
|
this.imageLoadQueue.push({ messageId, fileId });
|
|
|
|
if (!this.isProcessingQueue) {
|
|
|
|
this.processImageLoadQueue();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// 处理图片加载队列
|
|
|
|
async processImageLoadQueue() {
|
|
|
|
if (this.isProcessingQueue || this.imageLoadQueue.length === 0) return;
|
|
|
|
|
|
|
|
this.isProcessingQueue = true;
|
|
|
|
const { messageId, fileId } = this.imageLoadQueue.shift();
|
|
|
|
|
|
|
|
try {
|
|
|
|
// 获取缩略图
|
|
|
|
const thumbnailData = await mattermost.getThumbnail(fileId);
|
|
|
|
|
|
|
|
// 更新消息中的图片
|
|
|
|
const messageIndex = this.talkList.findIndex(msg => msg.id === messageId);
|
|
|
|
if (messageIndex !== -1) {
|
|
|
|
this.$set(this.talkList[messageIndex].message, 'thumbnail', thumbnailData);
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error('加载缩略图失败:', error);
|
|
|
|
} finally {
|
|
|
|
this.isProcessingQueue = false;
|
|
|
|
// 继续处理队列中的下一个图片
|
|
|
|
if (this.imageLoadQueue.length > 0) {
|
|
|
|
setTimeout(() => this.processImageLoadQueue(), 100);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2025-06-06 03:08:19 +08:00
|
|
|
// 加载消息
|
|
|
|
async loadMessages() {
|
|
|
|
if (this.ajax.flag || this.ajax.loading) return;
|
|
|
|
this.ajax.flag = true;
|
|
|
|
this.ajax.loading = true;
|
|
|
|
|
|
|
|
try {
|
|
|
|
// 获取消息列表
|
|
|
|
const posts = await mattermost.getChannelPosts(this.channel.id, {
|
|
|
|
page: this.ajax.page,
|
|
|
|
per_page: this.ajax.limit
|
|
|
|
});
|
|
|
|
|
|
|
|
console.log('获取到的消息列表:', posts);
|
|
|
|
|
|
|
|
if (!posts || posts.length === 0) {
|
|
|
|
console.log('没有更多消息');
|
|
|
|
this.ajax.last_page = this.ajax.page;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var newuser_ids = [];
|
|
|
|
for (var k in posts) {
|
|
|
|
if (!mattermost.kown_users[posts[k].user_id] && newuser_ids.indexOf(posts[k].user_id) == -1) {
|
|
|
|
newuser_ids.push(posts[k].user_id);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (newuser_ids.length > 0) {
|
|
|
|
var usersinfo = await mattermost.getUsersByIds(newuser_ids);
|
|
|
|
console.log('获取到的用户信息:', usersinfo);
|
|
|
|
usersinfo.data.forEach(user => {
|
|
|
|
mattermost.kown_users[user.id] = user;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
for (var k in posts) {
|
2025-06-10 03:06:54 +08:00
|
|
|
posts[k] = await this.processMessageData(posts[k]);
|
|
|
|
//posts[k]['from_user'] = await mattermost.getUserById(posts[k]['user_id']);//users[posts[k]['user_id']];
|
2025-06-06 03:08:19 +08:00
|
|
|
}
|
|
|
|
// 更新消息列表
|
|
|
|
if (this.ajax.page === 0) {
|
|
|
|
// 首次加载,直接显示消息
|
|
|
|
this.talkList = posts;
|
|
|
|
// 首次加载后滚动到底部
|
|
|
|
this.$nextTick(() => {
|
|
|
|
this.scrollToBottom();
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// 加载更多历史消息,添加到列表前面
|
|
|
|
// 过滤掉已存在的消息
|
|
|
|
const newMessages = posts.filter(msg =>
|
|
|
|
!this.talkList.some(existingMsg => existingMsg.id === msg.id)
|
|
|
|
);
|
|
|
|
for (var k in newMessages) {
|
|
|
|
this.talkList[k] = newMessages[k];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('更新后的消息列表:', this.talkList);
|
|
|
|
|
|
|
|
// 更新分页信息
|
|
|
|
this.ajax.page++;
|
|
|
|
this.ajax.last_page = Math.ceil(posts.length / this.ajax.limit);
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
console.error('加载消息失败:', error);
|
|
|
|
uni.showToast({
|
2025-06-10 20:19:45 +08:00
|
|
|
title: this.$t('im.loading_fail'),
|
2025-06-06 03:08:19 +08:00
|
|
|
icon: 'none'
|
|
|
|
});
|
|
|
|
} finally {
|
|
|
|
this.ajax.flag = false;
|
|
|
|
this.ajax.loading = false;
|
|
|
|
this.scrollView.refresherTriggered = false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2025-06-09 00:34:46 +08:00
|
|
|
|
|
|
|
// 发送消息
|
|
|
|
async sendMessage() {
|
|
|
|
if (!this.content.trim()) return;
|
|
|
|
//console.log(this.talkList);
|
|
|
|
|
|
|
|
try {
|
|
|
|
//console.log('发送消息:', this.content);
|
|
|
|
// 发送消息
|
|
|
|
const result = await mattermost.sendTextMessage(this.channel.id, this.content);
|
2025-06-10 03:06:54 +08:00
|
|
|
//console.log('发送消息:', this.content);
|
2025-06-09 00:34:46 +08:00
|
|
|
if (result && result.id) {
|
|
|
|
this.content = '';
|
2025-06-10 03:06:54 +08:00
|
|
|
//result['from_user'] = await mattermost.getUserById(result.user_id);
|
|
|
|
if(this.channel.type !='O'){
|
|
|
|
this.handleNewMessage({post:result});
|
|
|
|
}
|
|
|
|
//console.log('发送成功:', result,this.talkList);
|
2025-06-09 00:34:46 +08:00
|
|
|
} else {
|
|
|
|
throw new Error('发送失败');
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error('发送消息失败:', error);
|
|
|
|
uni.showToast({
|
2025-06-10 20:19:45 +08:00
|
|
|
title: this.$t('im.send_fail'),
|
2025-06-09 00:34:46 +08:00
|
|
|
icon: 'none'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// 发送图片消息
|
|
|
|
async sendImageMessage(tempFilePath) {
|
2025-06-10 03:06:54 +08:00
|
|
|
try {
|
|
|
|
// 上传图片
|
|
|
|
const result = await mattermost.sendImageMessage(this.channel.id, tempFilePath);
|
2025-06-06 03:08:19 +08:00
|
|
|
|
2025-06-10 03:06:54 +08:00
|
|
|
if (!result || !result.id) {
|
2025-06-10 20:19:45 +08:00
|
|
|
throw new Error(this.$t('im.send_fail'));
|
2025-06-10 03:06:54 +08:00
|
|
|
}
|
|
|
|
if(this.channel.type !='O'){
|
|
|
|
this.handleNewMessage({post:result});
|
|
|
|
}
|
|
|
|
//this.content = '';
|
2025-06-06 03:08:19 +08:00
|
|
|
|
2025-06-10 03:06:54 +08:00
|
|
|
//console.log('发送图片消息成功:', result);
|
|
|
|
} catch (error) {
|
|
|
|
console.error('发送图片消息失败:', error);
|
|
|
|
uni.showToast({
|
2025-06-10 20:19:45 +08:00
|
|
|
title: this.$t('im.send_fail'),
|
2025-06-10 03:06:54 +08:00
|
|
|
icon: 'none'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2025-06-06 03:08:19 +08:00
|
|
|
// 处理新消息
|
2025-06-10 03:06:54 +08:00
|
|
|
async handleNewMessage(message) {
|
2025-06-06 03:08:19 +08:00
|
|
|
console.log('收到新消息:', message);
|
|
|
|
|
|
|
|
// 检查消息格式
|
|
|
|
if (!message || !message.post) {
|
|
|
|
console.error('无效的消息格式:', message);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 解析 post 数据
|
|
|
|
let post;
|
|
|
|
try {
|
|
|
|
post = typeof message.post === 'string' ? JSON.parse(message.post) : message.post;
|
|
|
|
} catch (error) {
|
|
|
|
console.error('解析消息数据失败:', error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 检查 post 数据
|
|
|
|
if (!post || !post.channel_id) {
|
|
|
|
console.error('无效的 post 数据:', post);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (post.channel_id !== this.channel.id) {
|
|
|
|
console.log('不是当前频道的消息,忽略');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 检查消息是否已存在
|
|
|
|
if (this.talkList.some(msg => msg.id === post.id)) {
|
|
|
|
console.log('消息已存在,忽略:', post.id);
|
|
|
|
return;
|
|
|
|
}
|
2025-06-10 03:06:54 +08:00
|
|
|
const newMessage = await this.processMessageData(post, message.sender_name);
|
2025-06-06 03:08:19 +08:00
|
|
|
|
|
|
|
console.log('添加新消息到列表:', newMessage);
|
|
|
|
this.talkList.push(newMessage);
|
|
|
|
|
|
|
|
// 使用 nextTick 确保 DOM 更新后再滚动
|
|
|
|
this.$nextTick(() => {
|
|
|
|
this.scrollToBottom();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
// 处理消息编辑
|
|
|
|
handleMessageEdited(message) {
|
|
|
|
// 检查消息格式
|
|
|
|
if (!message || !message.post) {
|
|
|
|
console.error('无效的消息格式:', message);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 解析 post 数据
|
|
|
|
let post;
|
|
|
|
try {
|
|
|
|
post = typeof message.post === 'string' ? JSON.parse(message.post) : message.post;
|
|
|
|
} catch (error) {
|
|
|
|
console.error('解析消息数据失败:', error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!post || !post.channel_id) {
|
|
|
|
console.error('无效的 post 数据:', post);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (post.channel_id !== this.channel.id) return;
|
|
|
|
|
|
|
|
const index = this.talkList.findIndex(msg => msg.id === post.id);
|
|
|
|
if (index !== -1) {
|
|
|
|
this.talkList[index] = {
|
|
|
|
...this.talkList[index],
|
|
|
|
content: post.message,
|
|
|
|
edited: true
|
|
|
|
};
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// 处理消息删除
|
|
|
|
handleMessageDeleted(message) {
|
|
|
|
// 检查消息格式
|
|
|
|
if (!message || !message.post) {
|
|
|
|
console.error('无效的消息格式:', message);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 解析 post 数据
|
|
|
|
let post;
|
|
|
|
try {
|
|
|
|
post = typeof message.post === 'string' ? JSON.parse(message.post) : message.post;
|
|
|
|
} catch (error) {
|
|
|
|
console.error('解析消息数据失败:', error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!post || !post.channel_id) {
|
|
|
|
console.error('无效的 post 数据:', post);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (post.channel_id !== this.channel.id) return;
|
|
|
|
|
|
|
|
const index = this.talkList.findIndex(msg => msg.id === post.id);
|
|
|
|
if (index !== -1) {
|
|
|
|
this.talkList[index].content = this.$t('This message was deleted');
|
|
|
|
this.talkList[index].deleted = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// 处理状态变化
|
|
|
|
handleStatusChange(status) {
|
|
|
|
// 更新用户在线状态
|
|
|
|
this.talkList = this.talkList.map(msg => {
|
|
|
|
if (msg.from_user.id === status.user_id) {
|
|
|
|
return {
|
|
|
|
...msg,
|
|
|
|
from_user: {
|
|
|
|
...msg.from_user,
|
|
|
|
status: status.status
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return msg;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
// 重新发送消息
|
|
|
|
async resendMessage(message) {
|
|
|
|
const index = this.talkList.findIndex(msg => msg.id === message.id);
|
|
|
|
if (index === -1) return;
|
|
|
|
|
|
|
|
this.talkList[index].status = 'sending';
|
|
|
|
try {
|
|
|
|
const response = await this.sendMessage(message.content, message.type);
|
|
|
|
} catch (error) {
|
|
|
|
this.talkList[index].status = 'failed';
|
|
|
|
uni.showToast({
|
2025-06-10 20:19:45 +08:00
|
|
|
title: this.$t('im.send_fail'),
|
2025-06-06 03:08:19 +08:00
|
|
|
icon: 'none'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// 标记频道为已读
|
|
|
|
async markChannelAsRead() {
|
|
|
|
try {
|
|
|
|
await mattermost.markChannelAsViewed(this.channel.id);
|
|
|
|
this.setUnreadCount({ channelId: this.channel.id, count: 0 });
|
|
|
|
} catch (error) {
|
|
|
|
console.error('标记已读失败:', error);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// 滚动到底部
|
|
|
|
scrollToBottom() {
|
|
|
|
if (this.talkList.length > 0) {
|
|
|
|
const lastMessage = this.talkList[this.talkList.length - 1];
|
|
|
|
this.scrollToMessage(lastMessage.id);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// 滚动到指定消息
|
|
|
|
scrollToMessage(messageId) {
|
|
|
|
this.scrollView.intoView = `msg-${messageId}`;
|
|
|
|
},
|
|
|
|
|
|
|
|
// 加载更多消息
|
|
|
|
async loadMoreMessages() {
|
|
|
|
if (this.hasMoreMessages && !this.isLoading) {
|
|
|
|
// 记录当前滚动位置
|
|
|
|
const oldScrollHeight = this.scrollView.scrollHeight;
|
|
|
|
await this.loadMessages();
|
|
|
|
// 保持滚动位置
|
|
|
|
this.$nextTick(() => {
|
|
|
|
const newScrollHeight = this.scrollView.scrollHeight;
|
|
|
|
const scrollTop = newScrollHeight - oldScrollHeight;
|
|
|
|
this.scrollView.scrollTop = scrollTop;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// 下拉刷新
|
|
|
|
async refresherrefresh() {
|
|
|
|
this.scrollView.refresherTriggered = true;
|
|
|
|
this.ajax.page = 0;
|
|
|
|
await this.loadMessages();
|
|
|
|
},
|
|
|
|
|
|
|
|
// 预览图片
|
2025-06-10 03:06:54 +08:00
|
|
|
previewImage(message) {
|
|
|
|
console
|
|
|
|
.warn('previewImage', message);
|
|
|
|
if (!message.message || !message.message.id) return;
|
|
|
|
const file_id = message.imageInfo.id;
|
|
|
|
// 获取所有图片消息的原图URL
|
2025-06-06 03:08:19 +08:00
|
|
|
const images = this.talkList
|
2025-06-10 03:06:54 +08:00
|
|
|
.filter(msg => msg.type === 'image' && msg.imageInfo && msg.imageInfo.id)
|
|
|
|
.map(msg => {
|
|
|
|
return `${mattermost.adminBaseUrl}/api/v4/files/${msg.imageInfo.id}`;
|
|
|
|
});
|
|
|
|
|
2025-06-06 03:08:19 +08:00
|
|
|
uni.previewImage({
|
|
|
|
urls: images,
|
2025-06-10 03:06:54 +08:00
|
|
|
current: `${mattermost.adminBaseUrl}/api/v4/files/${message.imageInfo.id}`,
|
|
|
|
success: () => {
|
|
|
|
console.log('预览图片成功');
|
|
|
|
},
|
|
|
|
fail: (err) => {
|
|
|
|
console.error('预览图片失败:', err);
|
|
|
|
}
|
2025-06-06 03:08:19 +08:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
// 获取头像URL
|
|
|
|
getAvatarUrl(item, avatar) {
|
|
|
|
//console.log('getAvatarUrl',item);
|
|
|
|
avatar = item.from_user?.avatar;
|
|
|
|
if (!avatar) return '/static/img/avatar.png';
|
|
|
|
return avatar.startsWith('http') ? avatar : `${this.init.cdnurl}${avatar}`;
|
|
|
|
},
|
|
|
|
|
|
|
|
// 获取图片URL
|
|
|
|
getImageUrl(url) {
|
|
|
|
if (!url) return '';
|
|
|
|
return url.startsWith('http') ? url : `${this.init.cdnurl}${url}`;
|
|
|
|
},
|
|
|
|
|
|
|
|
// 格式化时间
|
|
|
|
formatTime(timestamp) {
|
|
|
|
return formatTime(timestamp);
|
|
|
|
},
|
|
|
|
|
|
|
|
// 显示用户信息
|
|
|
|
showUserInfo(user) {
|
|
|
|
// TODO: 实现用户信息展示
|
|
|
|
},
|
|
|
|
|
|
|
|
// 显示群组菜单
|
|
|
|
showGroupMenu() {
|
|
|
|
this.$refs.groupMenu.open();
|
|
|
|
},
|
|
|
|
|
|
|
|
// 显示群组成员
|
|
|
|
showGroupMembers() {
|
|
|
|
// TODO: 实现群组成员展示
|
|
|
|
},
|
|
|
|
|
|
|
|
// 显示群组信息
|
|
|
|
showGroupInfo() {
|
|
|
|
// TODO: 实现群组信息展示
|
|
|
|
},
|
|
|
|
|
|
|
|
// 输入框获得焦点
|
|
|
|
onInputFocus() {
|
|
|
|
this.scrollToBottom();
|
|
|
|
},
|
|
|
|
|
|
|
|
// 输入框失去焦点
|
|
|
|
onInputBlur() {
|
|
|
|
// TODO: 处理输入框失焦事件
|
|
|
|
},
|
|
|
|
|
|
|
|
// 图片加载完成
|
|
|
|
onImageLoad(messageId) {
|
|
|
|
this.imageLoading.set(messageId, true);
|
|
|
|
this.$nextTick(() => {
|
|
|
|
this.scrollToBottom();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
goto(url, type) {
|
|
|
|
if (type == 2) {
|
|
|
|
return uni.switchTab({ url: url })
|
|
|
|
}
|
|
|
|
if (type == 1) {
|
|
|
|
return uni.navigateBack({ delta: url });
|
|
|
|
}
|
|
|
|
uni.navigateTo({
|
|
|
|
url: url
|
|
|
|
})
|
|
|
|
},
|
|
|
|
sendphoto(){
|
|
|
|
uni.chooseImage({
|
|
|
|
count: 9,
|
|
|
|
sizeType: ['original', 'compressed'],
|
|
|
|
sourceType: ['album'],
|
|
|
|
success: (res) => {
|
|
|
|
const tempFilePaths = res.tempFilePaths;
|
|
|
|
const file = tempFilePaths[0];
|
|
|
|
this.sendImageMessage(file);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
sendcamera(){
|
|
|
|
uni.chooseImage({
|
|
|
|
count: 1,
|
|
|
|
sizeType: ['original', 'compressed'],
|
|
|
|
sourceType: ['camera'],
|
|
|
|
success: (res) => {
|
|
|
|
const tempFilePaths = res.tempFilePaths;
|
|
|
|
const file = tempFilePaths[0];
|
|
|
|
this.sendImageMessage(file);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
showemoji(){
|
|
|
|
this.emoji_show=!this.emoji_show;
|
|
|
|
if(this.emoji_show){
|
|
|
|
this.addons_show=!this.emoji_show;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
addEmoji(emoji){
|
|
|
|
this.content += emoji;
|
|
|
|
},
|
|
|
|
showaddons(){
|
|
|
|
this.addons_show=!this.addons_show;
|
|
|
|
if(this.addons_show){
|
|
|
|
this.emoji_show=!this.addons_show;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
.container {
|
|
|
|
height: 100vh;
|
|
|
|
overflow: hidden;
|
|
|
|
background-color: #eee;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
font-family: Poppins, Poppins;
|
|
|
|
|
|
|
|
.box-1 {
|
|
|
|
flex: 1;
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
.talk-list {
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
|
|
.loading-more {
|
|
|
|
text-align: center;
|
|
|
|
padding: 20rpx;
|
|
|
|
color: #999;
|
|
|
|
font-size: 24rpx;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
.uni-icons {
|
|
|
|
margin-right: 10rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.message-item {
|
|
|
|
margin-bottom: 30rpx;
|
|
|
|
|
|
|
|
.item {
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
&.push {
|
|
|
|
flex-direction: row-reverse;
|
|
|
|
|
|
|
|
.body {
|
|
|
|
margin-right: 20rpx;
|
|
|
|
margin-left: 0;
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
|
|
.content {
|
|
|
|
background-color: #BBD8FF;
|
|
|
|
|
|
|
|
&::before {
|
|
|
|
right: -16rpx;
|
|
|
|
left: auto;
|
|
|
|
border-color: transparent transparent transparent #BBD8FF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&.failed {
|
|
|
|
.content {
|
|
|
|
background-color: #ff4d4f;
|
|
|
|
color: #fff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.pic {
|
|
|
|
width: 84rpx;
|
|
|
|
height: 84rpx;
|
|
|
|
border-radius: 50%;
|
|
|
|
flex-shrink: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.body {
|
|
|
|
margin-left: 20rpx;
|
|
|
|
max-width: 60%;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
|
.nickname {
|
|
|
|
font-size: 24rpx;
|
|
|
|
color: #999;
|
|
|
|
margin-bottom: 10rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.content {
|
|
|
|
background-color: #fff;
|
|
|
|
color: #333;
|
|
|
|
padding: 24rpx;
|
|
|
|
border-radius: 10rpx;
|
|
|
|
position: relative;
|
|
|
|
word-break: break-all;
|
|
|
|
|
|
|
|
&::before {
|
|
|
|
content: '';
|
|
|
|
position: absolute;
|
|
|
|
left: -16rpx;
|
|
|
|
top: 20rpx;
|
|
|
|
border: 8rpx solid transparent;
|
|
|
|
border-right-color: #fff;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.image-content {
|
|
|
|
padding: 10rpx;
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
|
|
&::before {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
image {
|
|
|
|
border-radius: 10rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.message-status {
|
|
|
|
position: absolute;
|
|
|
|
right: -30rpx;
|
|
|
|
bottom: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.time {
|
|
|
|
font-size: 24rpx;
|
|
|
|
color: #999;
|
|
|
|
margin-top: 10rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.box-2 {
|
|
|
|
background: #F6F6F6;
|
|
|
|
padding: 24rpx;
|
|
|
|
|
|
|
|
.input-area {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
gap:24rpx;
|
|
|
|
.flex-grow{flex:1;}
|
|
|
|
.uni-easyinput__content{border-radius: 8rpx;}
|
|
|
|
.action-buttons{
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
uni-button{
|
|
|
|
height: 56rpx;
|
|
|
|
line-height: 56rpx;
|
|
|
|
background: transparent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.addons{
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
gap:48rpx;
|
|
|
|
padding: 48rpx 0;
|
|
|
|
height: 224rpx;
|
|
|
|
uni-button{
|
|
|
|
width: 25%;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
font-weight: 400;
|
|
|
|
font-size: 24rpx;
|
|
|
|
background: transparent;
|
|
|
|
color: #999999;
|
|
|
|
line-height: 2;
|
|
|
|
.uni-icons{
|
|
|
|
background: #fff;
|
|
|
|
height: 128rpx;
|
|
|
|
width: 128rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.emoji{
|
|
|
|
display: flex;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
max-height: 30vh;
|
|
|
|
overflow-y: scroll;
|
|
|
|
padding: 24rpx 0;
|
|
|
|
gap:10rpx;
|
|
|
|
view{
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.group-menu {
|
|
|
|
background-color: #fff;
|
|
|
|
border-radius: 20rpx 20rpx 0 0;
|
|
|
|
padding: 30rpx;
|
|
|
|
|
|
|
|
.menu-item {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
padding: 20rpx 0;
|
|
|
|
|
|
|
|
.uni-icons {
|
|
|
|
margin-right: 20rpx;
|
|
|
|
color: #666;
|
|
|
|
}
|
|
|
|
|
|
|
|
text {
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #333;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|