|
|
|
|
|
<template>
|
|
|
|
|
|
<!-- 模板完全保持不变,与原来一致 -->
|
|
|
|
|
|
<view class=" chat-box" id="chatBox">
|
|
|
|
|
|
<view class="chat-main-box" :style="{height: chatMainHeight+'px'}" >
|
|
|
|
|
|
<view class="chat-message" @click="switchChatTabBox('none')" >
|
|
|
|
|
|
<scroll-view class="scroll-box"
|
|
|
|
|
|
scroll-y="true"
|
|
|
|
|
|
upper-threshold="200"
|
|
|
|
|
|
@scrolltoupper="onScrollToTop"
|
|
|
|
|
|
:scroll-into-view="scrollTarget"
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
|
|
|
|
<view v-if="chat" v-for="(msgInfo, idx) in chat.messages" :key="idx">
|
|
|
|
|
|
<!-- ========== 选择模式:显示CheckBox ========== -->
|
|
|
|
|
|
<view v-if="selectionMode && canReport(msgInfo)" class="message-wrapper"
|
|
|
|
|
|
@click="toggleSelectMessage(msgInfo.id)">
|
|
|
|
|
|
<!-- :class="{ self: msgInfo.selfSend }"-->
|
|
|
|
|
|
<view class="checkbox-container">
|
|
|
|
|
|
<view class="checkbox" :class="{ checked: selectedMessageIds.includes(msgInfo.id) }">
|
|
|
|
|
|
<text v-if="selectedMessageIds.includes(msgInfo.id)">✓</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<chat-message-item :ref="'message'+msgInfo.id" v-if="idx >= showMinIdx"
|
|
|
|
|
|
:headImage="headImage(msgInfo)"
|
|
|
|
|
|
@call="onRtCall(msgInfo)"
|
|
|
|
|
|
:showName="showName(msgInfo)"
|
|
|
|
|
|
@recall="onRecallMessage"
|
|
|
|
|
|
@delete="onDeleteMessage"
|
|
|
|
|
|
@copy="onCopyMessage"
|
|
|
|
|
|
@longPressHead="onLongPressHead(msgInfo)"
|
|
|
|
|
|
@download="onDownloadFile"
|
|
|
|
|
|
@audioStateChange="onAudioStateChange"
|
|
|
|
|
|
:id="'chat-item-' + idx"
|
|
|
|
|
|
:msgInfo="msgInfo"
|
|
|
|
|
|
:groupMembers="groupMembers">
|
|
|
|
|
|
</chat-message-item>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view v-else="!selectionMode" >
|
|
|
|
|
|
<chat-message-item :ref="'message'+msgInfo.id" v-if="idx >= showMinIdx"
|
|
|
|
|
|
:headImage="headImage(msgInfo)"
|
|
|
|
|
|
@call="onRtCall(msgInfo)"
|
|
|
|
|
|
:showName="showName(msgInfo)"
|
|
|
|
|
|
@recall="onRecallMessage"
|
|
|
|
|
|
@delete="onDeleteMessage"
|
|
|
|
|
|
@copy="onCopyMessage"
|
|
|
|
|
|
@longPressHead="onLongPressHead(msgInfo)"
|
|
|
|
|
|
@download="onDownloadFile"
|
|
|
|
|
|
@audioStateChange="onAudioStateChange"
|
|
|
|
|
|
:id="'chat-item-' + idx"
|
|
|
|
|
|
:msgInfo="msgInfo"
|
|
|
|
|
|
:groupMembers="groupMembers">
|
|
|
|
|
|
</chat-message-item>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view id="chat-bottom" style="height: 2rpx;"></view>
|
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
<view v-if="!isInBottom" class="scroll-to-bottom" @click="onClickToBottom">
|
|
|
|
|
|
{{ newMessageSize > 0 ? newMessageSize+'条新消息' :'回到底部'}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<submit ref="submitRef" @inputs="onInputs" @send-message="onSendMessage" @heights="onHeights" />
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<!-- ========== 选择模式底部操作栏(独立 fixed 层级,避免嵌套 fixed 渲染异常) ========== -->
|
|
|
|
|
|
<view v-if="selectionMode" class="selection-bar">
|
|
|
|
|
|
<view class="selection-right">
|
|
|
|
|
|
<text class="selection-count">已选 {{ selectedMessageIds.length }} 条</text>
|
|
|
|
|
|
<button class="btn-cancel-selection" @click="cancelSelection">取消</button>
|
|
|
|
|
|
<button class="btn-confirm-selection" @click="nextStep">下一步</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ========== 投诉底部弹窗(点击右上角符号弹出) ========== -->
|
|
|
|
|
|
<view v-if="showReportMenu" class="report-menu-mask" @click="closeReportMenu">
|
|
|
|
|
|
<view class="report-menu-panel" @click.stop>
|
|
|
|
|
|
<view class="report-menu-title">请选择操作</view>
|
|
|
|
|
|
<view class="report-menu-item report-menu-complain" @click="enterSelectionMode">投诉</view>
|
|
|
|
|
|
<view class="report-menu-item" @click="closeReportMenu">取消</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
import { mapState, mapGetters, mapActions } from 'vuex';
|
|
|
|
|
|
import { myCache,clearBadgeNumber } from '../../utils/utils.js';
|
|
|
|
|
|
import submit from '@/components/chat/submit.vue';
|
|
|
|
|
|
import myVideo from "@/components/myVideo.vue";
|
|
|
|
|
|
//音频播放
|
|
|
|
|
|
const innerAudioContext = uni.createInnerAudioContext();
|
|
|
|
|
|
export default {
|
|
|
|
|
|
components: {
|
|
|
|
|
|
submit,
|
|
|
|
|
|
myVideo
|
|
|
|
|
|
},
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
scrollTarget: '',
|
|
|
|
|
|
chat: {},
|
|
|
|
|
|
userInfo: {},
|
|
|
|
|
|
group: {},
|
|
|
|
|
|
moreHeight:0,
|
|
|
|
|
|
groupMembers: [],
|
|
|
|
|
|
isReceipt: true,
|
|
|
|
|
|
scrollMsgIdx: 0,
|
|
|
|
|
|
scrollTop:0,
|
|
|
|
|
|
chatTabBox: 'none',
|
|
|
|
|
|
showRecord: false,
|
|
|
|
|
|
chatMainHeight: 0,
|
|
|
|
|
|
bottomPadding: 0,
|
|
|
|
|
|
keyboardHeight: 0,
|
|
|
|
|
|
topOffset: 0,
|
|
|
|
|
|
submitHeight: 0,
|
|
|
|
|
|
windowHeight: 1000,
|
|
|
|
|
|
initHeight: 1000,
|
|
|
|
|
|
atUserIds: [],
|
|
|
|
|
|
needScrollToBottom: false,
|
|
|
|
|
|
showMinIdx: 0,
|
|
|
|
|
|
reqQueue: [],
|
|
|
|
|
|
isSending: false,
|
|
|
|
|
|
isShowKeyBoard: false,
|
|
|
|
|
|
editorCtx: null,
|
|
|
|
|
|
isEmpty: true,
|
|
|
|
|
|
isFocus: false,
|
|
|
|
|
|
isReadOnly: false,
|
|
|
|
|
|
playingAudio: null,
|
|
|
|
|
|
isInBottom: true,
|
|
|
|
|
|
newMessageSize: 0,
|
|
|
|
|
|
// ===== 投诉选择模式 =====
|
|
|
|
|
|
selectionMode: false, // 是否处于选择模式
|
|
|
|
|
|
selectedMessageIds: [], // 已选中的消息ID列表
|
|
|
|
|
|
showReportMenu: false, // 是否显示投诉底部弹窗(投诉 / 取消)
|
|
|
|
|
|
selectableTypes: [0, 'TEXT', 1, 'IMAGE', 2, 'AUDIO', 3, 'VIDEO'] // 可选的消息类型
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
// 映射 Vuex state
|
|
|
|
|
|
...mapState('chatStore', ['chats']),
|
|
|
|
|
|
// ...mapState('userStore', ['userInfo']),
|
|
|
|
|
|
|
|
|
|
|
|
// ...mapState('configStore', ['config']),
|
|
|
|
|
|
// 映射 Vuex getters
|
|
|
|
|
|
...mapGetters('friendStore', ['findFriend']),
|
|
|
|
|
|
...mapGetters('chatStore', ['findChatIdx']),
|
|
|
|
|
|
|
|
|
|
|
|
// 当前用户信息(兼容原 `mine`)
|
|
|
|
|
|
mine() {
|
|
|
|
|
|
return myCache('user') || {};
|
|
|
|
|
|
},
|
|
|
|
|
|
// 好友信息
|
|
|
|
|
|
friend() {
|
|
|
|
|
|
if (!this.userInfo.id) return null;
|
|
|
|
|
|
return this.findFriend(this.userInfo.id);
|
|
|
|
|
|
},
|
|
|
|
|
|
// 会话标题
|
|
|
|
|
|
title() {
|
|
|
|
|
|
if (!this.chat) return '';
|
|
|
|
|
|
let title = this.chat.showName;
|
|
|
|
|
|
if (this.chat.type === 'GROUP' ) {
|
|
|
|
|
|
const size = this.groupMembers.filter(m => !m.quit).length;
|
|
|
|
|
|
title += `(${size})`;
|
|
|
|
|
|
}
|
|
|
|
|
|
return title;
|
|
|
|
|
|
},
|
|
|
|
|
|
// 消息发送接口 URL
|
|
|
|
|
|
messageAction() {
|
|
|
|
|
|
return `/api/message/${this.chat.type.toLowerCase()}/send`;
|
|
|
|
|
|
},
|
|
|
|
|
|
// 消息数量
|
|
|
|
|
|
messageSize() {
|
|
|
|
|
|
return this.chat?.messages?.length || 0;
|
|
|
|
|
|
},
|
|
|
|
|
|
// 未读消息数
|
|
|
|
|
|
unreadCount() {
|
|
|
|
|
|
return this.chat?.unreadCount || 0;
|
|
|
|
|
|
},
|
|
|
|
|
|
// 是否被封禁
|
|
|
|
|
|
isBanned() {
|
|
|
|
|
|
return (this.chat.type === 'PRIVATE' && this.userInfo.isBanned) ||
|
|
|
|
|
|
(this.chat.type === 'GROUP' && this.group.isBanned);
|
|
|
|
|
|
},
|
|
|
|
|
|
// @ 成员列表
|
|
|
|
|
|
atUserItems() {
|
|
|
|
|
|
const items = [];
|
|
|
|
|
|
this.atUserIds.forEach(id => {
|
|
|
|
|
|
if (id === -1) {
|
|
|
|
|
|
items.push({ id: -1, showNickName: '全体成员' });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const member = this.groupMembers.find(m => m.userId === id);
|
|
|
|
|
|
if (member) items.push(member);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
return items;
|
|
|
|
|
|
},
|
|
|
|
|
|
// 群成员数
|
|
|
|
|
|
memberSize() {
|
|
|
|
|
|
return this.groupMembers.filter(m => !m.quit).length;
|
|
|
|
|
|
},
|
|
|
|
|
|
// 配置中的 webrtc 最大通道数(用于模板)
|
|
|
|
|
|
webrtcMaxChannel() {
|
|
|
|
|
|
// return this.config?.webrtc?.maxChannel || 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
watch: {
|
|
|
|
|
|
messageSize(newSize, oldSize) {
|
|
|
|
|
|
if (newSize > oldSize) {
|
|
|
|
|
|
if (this.isInBottom) {
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.newMessageSize++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
unreadCount(newCount) {
|
|
|
|
|
|
if (newCount > 0) {
|
|
|
|
|
|
this.toReadedMessage();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
onLoad(options) {
|
|
|
|
|
|
|
|
|
|
|
|
// 从 Vuex state 中获取当前会话
|
|
|
|
|
|
this.chat = this.chats[options.chatIdx];
|
|
|
|
|
|
const size = this.messageSize;
|
|
|
|
|
|
this.showMinIdx = size > 20 ? size - 20 : 0;
|
|
|
|
|
|
this.toReadedMessage();
|
|
|
|
|
|
|
|
|
|
|
|
if (this.chat.type === 'GROUP') {
|
|
|
|
|
|
this.loadGroup(this.chat.targetId);
|
|
|
|
|
|
uni.setNavigationBarTitle({
|
|
|
|
|
|
title: this.chat.showName
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.loadFriend(this.chat.targetId);
|
|
|
|
|
|
this.loadReaded(this.chat.targetId);
|
|
|
|
|
|
uni.setNavigationBarTitle({
|
|
|
|
|
|
title: this.chat.showName
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// 激活当前会话(action)
|
|
|
|
|
|
this.activeChat(options.chatIdx);
|
|
|
|
|
|
this.isReceipt = true;
|
|
|
|
|
|
this.listenKeyBoard();
|
|
|
|
|
|
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
|
this.windowHeight = uni.getSystemInfoSync().windowHeight;
|
|
|
|
|
|
this.reCalChatMainHeight();
|
|
|
|
|
|
|
|
|
|
|
|
// #ifdef H5
|
|
|
|
|
|
this.initHeight = window.innerHeight;
|
|
|
|
|
|
const chatBox = document.getElementById('chatBox');
|
|
|
|
|
|
chatBox.addEventListener('touchmove', e => e.preventDefault(), { passive: false });
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
onUnload() {
|
|
|
|
|
|
this.unListenKeyboard();
|
|
|
|
|
|
// 退出时重置选择模式
|
|
|
|
|
|
this.selectionMode = false;
|
|
|
|
|
|
this.selectedMessageIds = [];
|
|
|
|
|
|
},
|
|
|
|
|
|
onShow() {
|
|
|
|
|
|
if (this.needScrollToBottom) {
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
this.needScrollToBottom = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.setBadge();
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
// ==================== 投诉选择模式 ====================
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取可选的消息列表(排除系统提示消息)
|
|
|
|
|
|
*/
|
|
|
|
|
|
getAvailableMessages() {
|
|
|
|
|
|
return (this.chat?.messages || []).filter(msg => {
|
|
|
|
|
|
// 排除系统提示类型
|
|
|
|
|
|
if (msg.type === 'TIP_TEXT') return false;
|
|
|
|
|
|
// 排除已经被撤回的消息
|
|
|
|
|
|
if (msg.isRecalled) return false;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
canReport(msgInfo) {
|
|
|
|
|
|
if (!msgInfo) return false
|
|
|
|
|
|
// 1. 本人发送的消息不能投诉
|
|
|
|
|
|
if (msgInfo.selfSend) return false
|
|
|
|
|
|
// 2. 系统提示消息不能投诉(如"撤回了一条消息"等)
|
|
|
|
|
|
if (msgInfo.type === 21) return false
|
|
|
|
|
|
|
|
|
|
|
|
if (msgInfo.type === 20) return false
|
|
|
|
|
|
return true
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 切换选择模式
|
|
|
|
|
|
*/
|
|
|
|
|
|
enterSelectionMode() {
|
|
|
|
|
|
this.showReportMenu = false; // 关闭底部弹窗
|
|
|
|
|
|
this.selectionMode = true;
|
|
|
|
|
|
this.selectedMessageIds = [];
|
|
|
|
|
|
// 隐藏键盘
|
|
|
|
|
|
uni.hideKeyboard();
|
|
|
|
|
|
// 关闭其他面板
|
|
|
|
|
|
this.switchChatTabBox('none');
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 关闭投诉底部弹窗
|
|
|
|
|
|
*/
|
|
|
|
|
|
closeReportMenu() {
|
|
|
|
|
|
this.showReportMenu = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 取消选择模式
|
|
|
|
|
|
*/
|
|
|
|
|
|
cancelSelection() {
|
|
|
|
|
|
this.selectionMode = false;
|
|
|
|
|
|
this.selectedMessageIds = [];
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 切换单条消息的选中状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
toggleSelectMessage(msgId) {
|
|
|
|
|
|
// 查找消息
|
|
|
|
|
|
const msg = this.chat.messages.find(m => m.id === msgId);
|
|
|
|
|
|
if (!msg || !this.canReport(msg)) return
|
|
|
|
|
|
|
|
|
|
|
|
const idx = this.selectedMessageIds.indexOf(msgId);
|
|
|
|
|
|
if (idx > -1) {
|
|
|
|
|
|
this.selectedMessageIds.splice(idx, 1);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.selectedMessageIds.push(msgId);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 点击"下一步":校验选择并跳转到投诉页
|
|
|
|
|
|
*/
|
|
|
|
|
|
nextStep() {
|
|
|
|
|
|
if (this.selectedMessageIds.length === 0) {
|
|
|
|
|
|
uni.showToast({ title: '请至少选择一条消息', icon: 'none' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建投诉页参数
|
|
|
|
|
|
const sendType = this.chat.type === 'GROUP' ? '1' : '2';
|
|
|
|
|
|
const senderId = this.chat.targetId;
|
|
|
|
|
|
const senderName = this.chat.showName || '';
|
|
|
|
|
|
const senderAvatar = this.chat.headImage || '';
|
|
|
|
|
|
|
|
|
|
|
|
// 将选中的消息ID存入本地缓存,供投诉页读取(避免URL参数过长)
|
|
|
|
|
|
// 按选中顺序提取完整聊天记录
|
|
|
|
|
|
const selectedRecords = this.selectedMessageIds
|
|
|
|
|
|
.map(id => this.chat.messages.find(m => m.id === id))
|
|
|
|
|
|
.filter(msg => !!msg);
|
|
|
|
|
|
// 直接存储完整聊天记录(含全部消息字段),供投诉页读取
|
|
|
|
|
|
uni.setStorageSync('report_selected_messages', selectedRecords);
|
|
|
|
|
|
uni.setStorageSync('report_send_type', sendType);
|
|
|
|
|
|
uni.setStorageSync('report_sender_id', senderId);
|
|
|
|
|
|
uni.setStorageSync('report_sender_name', senderName);
|
|
|
|
|
|
uni.setStorageSync('report_sender_avatar', senderAvatar);
|
|
|
|
|
|
// 退出选择模式
|
|
|
|
|
|
this.selectionMode = false;
|
|
|
|
|
|
this.selectedMessageIds = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转投诉页
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
url: `/pages/chatbox/report?sendType=${sendType}&senderId=${senderId}&fromChat=true`
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
onNavigationBarButtonTap(e) {
|
|
|
|
|
|
// 点击右上角符号:弹出底部操作菜单(投诉 / 取消)
|
|
|
|
|
|
if (this.selectionMode) return; // 选择模式中忽略
|
|
|
|
|
|
this.showReportMenu = true;
|
|
|
|
|
|
},
|
|
|
|
|
|
// 映射 Vuex actions
|
|
|
|
|
|
...mapActions('chatStore', [
|
|
|
|
|
|
'insertMessage',
|
|
|
|
|
|
'deleteMessage',
|
|
|
|
|
|
'recallMessage',
|
|
|
|
|
|
'moveTop',
|
|
|
|
|
|
'resetUnreadCount',
|
|
|
|
|
|
'updateChatFromGroup',
|
|
|
|
|
|
'updateChatFromFriend',
|
|
|
|
|
|
'updateChatFromUser',
|
|
|
|
|
|
'readedMessage',
|
|
|
|
|
|
'activeChat'
|
|
|
|
|
|
]),
|
|
|
|
|
|
...mapActions('friendStore', ['updateFriend']),
|
|
|
|
|
|
...mapActions('groupStore', ['updateGroup']),
|
|
|
|
|
|
// 设置app角标
|
|
|
|
|
|
setBadge() {
|
|
|
|
|
|
var count = myCache("massageCount")?myCache("massageCount"):-1;
|
|
|
|
|
|
if (count > 0) {
|
|
|
|
|
|
// app清除角标的数量
|
|
|
|
|
|
//#ifdef APP-PLUS
|
|
|
|
|
|
plus.runtime.setBadgeNumber(count);
|
|
|
|
|
|
//#endif
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// app清除角标的数量
|
|
|
|
|
|
clearBadgeNumber();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
// 以下是原业务方法,只需将 `this.chatStore.xxx` 替换为映射后的方法名
|
|
|
|
|
|
onRecorderInput() {
|
|
|
|
|
|
this.showRecord = true;
|
|
|
|
|
|
this.switchChatTabBox('none');
|
|
|
|
|
|
},
|
|
|
|
|
|
onKeyboardInput() {
|
|
|
|
|
|
this.showRecord = false;
|
|
|
|
|
|
this.switchChatTabBox('none');
|
|
|
|
|
|
},
|
|
|
|
|
|
onSendRecord(data) {
|
|
|
|
|
|
if (this.isBanned) {
|
|
|
|
|
|
this.showBannedTip();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const msgInfo = {
|
|
|
|
|
|
content: JSON.stringify(data),
|
|
|
|
|
|
type: this.$enums.MESSAGE_TYPE.AUDIO,
|
|
|
|
|
|
receipt: this.isReceipt
|
|
|
|
|
|
};
|
|
|
|
|
|
this.fillTargetId(msgInfo, this.chat.targetId);
|
|
|
|
|
|
this.sendMessageRequest(msgInfo).then(res => {
|
|
|
|
|
|
var m=res.data.data;
|
|
|
|
|
|
m.selfSend = true;
|
|
|
|
|
|
this.insertMessage({ msgInfo: m, chatInfo: this.chat });
|
|
|
|
|
|
this.moveChatToTop();
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
this.isReceipt = true;
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
onInputs(data) {
|
|
|
|
|
|
if (this.isBanned) {
|
|
|
|
|
|
this.showBannedTip();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const { message, type } = data;
|
|
|
|
|
|
if (type === 'txt') {
|
|
|
|
|
|
// 文本消息直接发送
|
|
|
|
|
|
const msgInfo = {
|
|
|
|
|
|
content: message,
|
|
|
|
|
|
type: this.$enums.MESSAGE_TYPE.TEXT,
|
|
|
|
|
|
receipt: true, // 可根据需要调整
|
|
|
|
|
|
};
|
|
|
|
|
|
this.fillTargetId(msgInfo, this.chat.targetId);
|
|
|
|
|
|
this.sendMessageRequest(msgInfo).then(m => {
|
|
|
|
|
|
m.selfSend = true;
|
|
|
|
|
|
this.insertMessage({ msgInfo: m, chatInfo: this.chat });
|
|
|
|
|
|
this.moveChatToTop();
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
});
|
|
|
|
|
|
} else if (type === 'image' || type === 'video' || type === 'audio') {
|
|
|
|
|
|
// 图片/视频/语音需要先上传,这里直接调用现有的上传逻辑
|
|
|
|
|
|
// 因为 submit 组件已经做了文件选择,只需用 message 路径
|
|
|
|
|
|
this.uploadAndSend(message, type);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 其他类型(如定位)可按需扩展
|
|
|
|
|
|
console.warn('未处理的消息类型:', type);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 接收 submit 组件发出的高度变化事件
|
|
|
|
|
|
* @param {number} height - submit 组件总高度(px)
|
|
|
|
|
|
*/
|
|
|
|
|
|
onHeights(height) {
|
|
|
|
|
|
if (height>60){
|
|
|
|
|
|
this.moreHeight=height-58
|
|
|
|
|
|
this.chatMainHeight -= this.moreHeight;
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
}else {
|
|
|
|
|
|
this.moreHeight=0;
|
|
|
|
|
|
this.reCalChatMainHeight();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 扩展的上传发送方法(类似 submit 中原有的 uploadFile 逻辑)
|
|
|
|
|
|
uploadAndSend(filePath, type) {
|
|
|
|
|
|
// 复用原有上传逻辑,例如 onUploadImageBefore 等
|
|
|
|
|
|
// 此处简化,实际应调用统一的发送流程
|
|
|
|
|
|
// ...
|
|
|
|
|
|
},
|
|
|
|
|
|
onRtCall(msgInfo) {
|
|
|
|
|
|
if (msgInfo.type === this.$enums.MESSAGE_TYPE.ACT_RT_VOICE) {
|
|
|
|
|
|
this.onPriviteVoice();
|
|
|
|
|
|
} else if (msgInfo.type === this.$enums.MESSAGE_TYPE.ACT_RT_VIDEO) {
|
|
|
|
|
|
this.onPriviteVideo();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
onPriviteVideo() {
|
|
|
|
|
|
const friendInfo = encodeURIComponent(JSON.stringify(this.friend));
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
url: `/pages/chat/chat-private-video?mode=video&friend=${friendInfo}&isHost=true`
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
onPriviteVoice() {
|
|
|
|
|
|
const friendInfo = encodeURIComponent(JSON.stringify(this.friend));
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
url: `/pages/chat/chat-private-video?mode=voice&friend=${friendInfo}&isHost=true`
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
onGroupVideo() {
|
|
|
|
|
|
const ids = [this.info.userid];
|
|
|
|
|
|
this.$refs.selBox.init(ids, ids, []);
|
|
|
|
|
|
this.$refs.selBox.open();
|
|
|
|
|
|
},
|
|
|
|
|
|
onInviteOk(ids) {
|
|
|
|
|
|
if (ids.length < 2) return;
|
|
|
|
|
|
const users = ids.map(id => {
|
|
|
|
|
|
const m = this.groupMembers.find(m => m.userId === id);
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: m.userId,
|
|
|
|
|
|
nickName: m.showNickName,
|
|
|
|
|
|
headImage: m.headImage,
|
|
|
|
|
|
isCamera: false,
|
|
|
|
|
|
isMicroPhone: true
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
const groupId = this.group.id;
|
|
|
|
|
|
const inviterId = this.info.userid;
|
|
|
|
|
|
const userInfos = encodeURIComponent(JSON.stringify(users));
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
url: `/pages/chat/chat-group-video?groupId=${groupId}&isHost=true&inviterId=${inviterId}&userInfos=${userInfos}`
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
moveChatToTop() {
|
|
|
|
|
|
const idx = this.findChatIdx(this.chat);
|
|
|
|
|
|
if (idx >= 0) {
|
|
|
|
|
|
this.moveTop(idx);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
switchReceipt() {
|
|
|
|
|
|
this.isReceipt = !this.isReceipt;
|
|
|
|
|
|
},
|
|
|
|
|
|
openAtBox() {
|
|
|
|
|
|
this.$refs.atBox.init(this.atUserIds);
|
|
|
|
|
|
this.$refs.atBox.open();
|
|
|
|
|
|
},
|
|
|
|
|
|
onAtComplete(atUserIds) {
|
|
|
|
|
|
this.atUserIds = atUserIds;
|
|
|
|
|
|
},
|
|
|
|
|
|
onLongPressHead(msgInfo) {
|
|
|
|
|
|
if (!msgInfo.selfSend && this.chat.type === 'GROUP' && this.atUserIds.indexOf(msgInfo.sendId) < 0) {
|
|
|
|
|
|
this.atUserIds.push(msgInfo.sendId);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
headImage(msgInfo) {
|
|
|
|
|
|
if (this.chat.type === 'GROUP') {
|
|
|
|
|
|
const member = this.groupMembers.find(m => m.userId === msgInfo.sendId);
|
|
|
|
|
|
return member ? member.headImage : '';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return msgInfo.selfSend ? this.mine.avatar : this.chat.headImage;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
showName(msgInfo) {
|
|
|
|
|
|
if (this.chat.type === 'GROUP') {
|
|
|
|
|
|
const member = this.groupMembers.find(m => m.userId === msgInfo.sendId);
|
|
|
|
|
|
return member ? member.showNickName : '';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return msgInfo.selfSend ? this.mine.nickName : this.chat.showName;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
onSendMessage(payload) {
|
|
|
|
|
|
if (this.isBanned) {
|
|
|
|
|
|
this.showBannedTip();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const { content, type } = payload;
|
|
|
|
|
|
// 文本消息直接发送
|
|
|
|
|
|
if (type === 'txt') {
|
|
|
|
|
|
// 仿照 sendTextMessage 构造 msgInfo,但无需从 editor 获取内容
|
|
|
|
|
|
// 注意:这里需要处理 @ 和回执,可从当前实例获取 this.atUserIds 和 this.isReceipt
|
|
|
|
|
|
let sendText = content.trim();
|
|
|
|
|
|
if (!sendText && this.atUserIds.length === 0) {
|
|
|
|
|
|
return uni.showToast({ title: '不能发送空白信息', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
// const receiptText = this.isReceipt ? '【回执消息】' : '';
|
|
|
|
|
|
const atText = this.createAtText(); // 沿用已有的 createAtText 方法
|
|
|
|
|
|
const msgInfo = {
|
|
|
|
|
|
content: this.html2Escape(sendText) + atText,
|
|
|
|
|
|
atUserIds: this.atUserIds,
|
|
|
|
|
|
receipt: this.isReceipt,
|
|
|
|
|
|
type: this.$enums.MESSAGE_TYPE.TEXT
|
|
|
|
|
|
};
|
|
|
|
|
|
// 清空 @ 和回执状态
|
|
|
|
|
|
this.atUserIds = [];
|
|
|
|
|
|
this.isReceipt = true;
|
|
|
|
|
|
this.fillTargetId(msgInfo, this.chat.targetId);
|
|
|
|
|
|
this.sendMessageRequest(msgInfo).then(m => {
|
|
|
|
|
|
m.selfSend = true;
|
|
|
|
|
|
this.insertMessage({ msgInfo: m, chatInfo: this.chat });
|
|
|
|
|
|
this.moveChatToTop();
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// 图片消息
|
|
|
|
|
|
else if (type === 'image') {
|
|
|
|
|
|
// 直接使用现有的图片上传发送逻辑,可调用 onUploadImageBefore 的流程
|
|
|
|
|
|
// 但 onUploadImageBefore 需要 file 对象,这里 content 是文件路径
|
|
|
|
|
|
// 可以模拟一个 file 对象,或直接调用发送图片的封装方法
|
|
|
|
|
|
this.uploadAndSendImage(content);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 视频消息
|
|
|
|
|
|
else if (type === 'video') {
|
|
|
|
|
|
this.uploadAndSendVideo(content);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 音频消息
|
|
|
|
|
|
else if (type === 'audio') {
|
|
|
|
|
|
// content 为 { voice, time }
|
|
|
|
|
|
this.uploadAndSendAudio(content);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 其他类型可根据需要扩展
|
|
|
|
|
|
else {
|
|
|
|
|
|
uni.showToast({ title: '暂不支持该类型', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传并发送图片
|
|
|
|
|
|
* @param {string} filePath - 图片本地路径
|
|
|
|
|
|
*/
|
|
|
|
|
|
uploadAndSendImage(filePath) {
|
|
|
|
|
|
if (this.isBanned) {
|
|
|
|
|
|
this.showBannedTip();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 构造临时消息(模拟文件对象)
|
|
|
|
|
|
const data = { originUrl: filePath, thumbUrl: filePath };
|
|
|
|
|
|
|
|
|
|
|
|
const msgInfo = {
|
|
|
|
|
|
id: 0,
|
|
|
|
|
|
tmpId: this.generateId(),
|
|
|
|
|
|
sendId: this.mine.userid,
|
|
|
|
|
|
content: JSON.stringify(data),
|
|
|
|
|
|
sendTime: Date.now(),
|
|
|
|
|
|
selfSend: true,
|
|
|
|
|
|
type: this.$enums.MESSAGE_TYPE.IMAGE,
|
|
|
|
|
|
readedCount: 0,
|
|
|
|
|
|
loadStatus: 'loading',
|
|
|
|
|
|
status: this.$enums.MESSAGE_STATUS.UNSEND,
|
|
|
|
|
|
receipt: this.isReceipt
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.fillTargetId(msgInfo, this.chat.targetId);
|
|
|
|
|
|
// 先插入消息(占位)
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
this.moveChatToTop();
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
|
|
|
|
|
|
// 开始上传
|
|
|
|
|
|
const token = uni.getStorageSync('token');
|
|
|
|
|
|
uni.uploadFile({
|
|
|
|
|
|
url: uni.$http.baseUrl + '/api/image/upload',
|
|
|
|
|
|
filePath: filePath,
|
|
|
|
|
|
name: 'file',
|
|
|
|
|
|
header: { Authorization: token },
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
const result = JSON.parse(res.data);
|
|
|
|
|
|
if (result.data && result.data.originUrl) {
|
|
|
|
|
|
// 更新消息内容
|
|
|
|
|
|
msgInfo.content = JSON.stringify(result.data);
|
|
|
|
|
|
msgInfo.receipt = this.isReceipt;
|
|
|
|
|
|
// 发送消息请求
|
|
|
|
|
|
this.sendMessageRequest(msgInfo).then(m => {
|
|
|
|
|
|
msgInfo.loadStatus = 'ok';
|
|
|
|
|
|
msgInfo.id = m.id;
|
|
|
|
|
|
// 再次插入更新消息
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
this.isReceipt = true; // 重置回执状态(可根据需求调整)
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 上传失败
|
|
|
|
|
|
msgInfo.loadStatus = 'fail';
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
uni.showToast({ title: result.msg || '图片上传失败', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
msgInfo.loadStatus = 'fail';
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
uni.showToast({ title: '图片上传失败', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传并发送视频
|
|
|
|
|
|
* @param {string} filePath - 视频本地路径
|
|
|
|
|
|
*/
|
|
|
|
|
|
uploadAndSendVideo(filePath) {
|
|
|
|
|
|
// 视频上传逻辑与图片类似,但接口可能不同,此处假设使用 /api/file/upload
|
|
|
|
|
|
// 消息类型为 MESSAGE_TYPE.VIDEO
|
|
|
|
|
|
const data = { url: filePath };
|
|
|
|
|
|
|
|
|
|
|
|
const msgInfo = {
|
|
|
|
|
|
id: 0,
|
|
|
|
|
|
tmpId: this.generateId(),
|
|
|
|
|
|
sendId: this.mine.userid,
|
|
|
|
|
|
content: JSON.stringify(data),
|
|
|
|
|
|
sendTime: Date.now(),
|
|
|
|
|
|
selfSend: true,
|
|
|
|
|
|
type: this.$enums.MESSAGE_TYPE.VIDEO,
|
|
|
|
|
|
loadStatus: 'loading',
|
|
|
|
|
|
status: this.$enums.MESSAGE_STATUS.UNSEND,
|
|
|
|
|
|
receipt: this.isReceipt
|
|
|
|
|
|
};
|
|
|
|
|
|
this.fillTargetId(msgInfo, this.chat.targetId);
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
this.moveChatToTop();
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
const token = uni.getStorageSync('token');
|
|
|
|
|
|
uni.uploadFile({
|
|
|
|
|
|
url: uni.$http.baseUrl + '/api/file/upload',
|
|
|
|
|
|
filePath: filePath,
|
|
|
|
|
|
name: 'file',
|
|
|
|
|
|
header: { Authorization: token },
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
const result = JSON.parse(res.data);
|
|
|
|
|
|
if (result.data) {
|
|
|
|
|
|
msgInfo.content = JSON.stringify({ url: result.data });
|
|
|
|
|
|
msgInfo.receipt = this.isReceipt;
|
|
|
|
|
|
this.sendMessageRequest(msgInfo).then(m => {
|
|
|
|
|
|
msgInfo.loadStatus = 'ok';
|
|
|
|
|
|
msgInfo.id = m.id;
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
this.isReceipt = true;
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
msgInfo.loadStatus = 'fail';
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
uni.showToast({ title: result.msg || '视频上传失败', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: () => {
|
|
|
|
|
|
msgInfo.loadStatus = 'fail';
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
uni.showToast({ title: '视频上传失败', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传并发送音频
|
|
|
|
|
|
* @param {Object} audioData - { voice: 文件路径, time: 秒数 }
|
|
|
|
|
|
*/
|
|
|
|
|
|
uploadAndSendAudio(audioData) {
|
|
|
|
|
|
const { voice, time } = audioData;
|
|
|
|
|
|
const data = { url: voice, duration: time };
|
|
|
|
|
|
const msgInfo = {
|
|
|
|
|
|
id: 0,
|
|
|
|
|
|
tmpId: this.generateId(),
|
|
|
|
|
|
sendId: this.mine.userid,
|
|
|
|
|
|
content: JSON.stringify(data),
|
|
|
|
|
|
sendTime: Date.now(),
|
|
|
|
|
|
selfSend: true,
|
|
|
|
|
|
type: this.$enums.MESSAGE_TYPE.AUDIO,
|
|
|
|
|
|
loadStatus: 'loading',
|
|
|
|
|
|
status: this.$enums.MESSAGE_STATUS.UNSEND,
|
|
|
|
|
|
receipt: this.isReceipt
|
|
|
|
|
|
};
|
|
|
|
|
|
this.fillTargetId(msgInfo, this.chat.targetId);
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
this.moveChatToTop();
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
|
|
|
|
|
|
const token = uni.getStorageSync('token');
|
|
|
|
|
|
uni.uploadFile({
|
|
|
|
|
|
url: uni.$http.baseUrl + '/api/file/upload',
|
|
|
|
|
|
filePath: voice,
|
|
|
|
|
|
name: 'file',
|
|
|
|
|
|
header: { Authorization: token },
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
const result = JSON.parse(res.data);
|
|
|
|
|
|
if (result.data) {
|
|
|
|
|
|
msgInfo.content = JSON.stringify({ url: result.data, duration: time });
|
|
|
|
|
|
msgInfo.receipt = this.isReceipt;
|
|
|
|
|
|
this.sendMessageRequest(msgInfo).then(m => {
|
|
|
|
|
|
msgInfo.loadStatus = 'ok';
|
|
|
|
|
|
msgInfo.id = m.id;
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
this.isReceipt = true;
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
msgInfo.loadStatus = 'fail';
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
uni.showToast({ title: result.msg || '音频上传失败', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: () => {
|
|
|
|
|
|
msgInfo.loadStatus = 'fail';
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
uni.showToast({ title: '音频上传失败', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
sendTextMessage() {
|
|
|
|
|
|
this.editorCtx.getContents({
|
|
|
|
|
|
success: e => {
|
|
|
|
|
|
this.editorCtx.clear();
|
|
|
|
|
|
if (this.isBanned) {
|
|
|
|
|
|
this.showBannedTip();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
let sendText = '';
|
|
|
|
|
|
e.delta.ops.forEach(op => {
|
|
|
|
|
|
if (op.insert.image) {
|
|
|
|
|
|
sendText += `#${op.attributes.alt};`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
sendText += op.insert;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!sendText.trim() && this.atUserIds.length === 0) {
|
|
|
|
|
|
return uni.showToast({ title: '不能发送空白信息', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
// const receiptText = this.isReceipt ? '【回执消息】' : '';
|
|
|
|
|
|
const atText = this.createAtText();
|
|
|
|
|
|
const msgInfo = {
|
|
|
|
|
|
content: this.html2Escape(sendText) + atText,
|
|
|
|
|
|
atUserIds: this.atUserIds,
|
|
|
|
|
|
receipt: this.isReceipt,
|
|
|
|
|
|
type: 0
|
|
|
|
|
|
};
|
|
|
|
|
|
this.atUserIds = [];
|
|
|
|
|
|
this.isReceipt = true;
|
|
|
|
|
|
this.fillTargetId(msgInfo, this.chat.targetId);
|
|
|
|
|
|
this.sendMessageRequest(msgInfo).then(m=> {
|
|
|
|
|
|
m.selfSend = true;
|
|
|
|
|
|
this.insertMessage({ msgInfo: m, chatInfo: this.chat });
|
|
|
|
|
|
this.moveChatToTop();
|
|
|
|
|
|
}).finally(() => {
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
createAtText() {
|
|
|
|
|
|
let text = '';
|
|
|
|
|
|
this.atUserIds.forEach(id => {
|
|
|
|
|
|
if (id === -1) {
|
|
|
|
|
|
text += ' @全体成员';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const member = this.groupMembers.find(m => m.userId === id);
|
|
|
|
|
|
if (member) text += ` @${member.showNickName}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
return text;
|
|
|
|
|
|
},
|
|
|
|
|
|
fillTargetId(msgInfo, targetId) {
|
|
|
|
|
|
if (this.chat.type === 'GROUP') {
|
|
|
|
|
|
msgInfo.groupId = targetId;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
msgInfo.recvId = targetId;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
scrollToBottom() {
|
|
|
|
|
|
this.scrollTarget = '';
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
|
this.scrollTarget = 'chat-bottom'; // 足够大
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// let size = this.messageSize;
|
|
|
|
|
|
// if (size > 0) {
|
|
|
|
|
|
// this.scrollToMsgIdx(size - 1);
|
|
|
|
|
|
//
|
|
|
|
|
|
// }
|
|
|
|
|
|
},
|
|
|
|
|
|
scrollToMsgIdx(idx) {
|
|
|
|
|
|
if (idx === this.scrollMsgIdx && idx > 0) {
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
|
this.scrollMsgIdx = idx - 1;
|
|
|
|
|
|
this.scrollToMsgIdx(idx);
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
|
this.scrollMsgIdx = idx;
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
onShowEmoChatTab() {
|
|
|
|
|
|
this.showRecord = false;
|
|
|
|
|
|
this.switchChatTabBox('emo');
|
|
|
|
|
|
},
|
|
|
|
|
|
onShowToolsChatTab() {
|
|
|
|
|
|
this.showRecord = false;
|
|
|
|
|
|
this.switchChatTabBox('tools');
|
|
|
|
|
|
},
|
|
|
|
|
|
switchChatTabBox(type) {
|
|
|
|
|
|
this.chatTabBox = type;
|
|
|
|
|
|
// this.reCalChatMainHeight();
|
|
|
|
|
|
uni.hideKeyboard();
|
|
|
|
|
|
// 通知 submit 组件关闭其内部面板
|
|
|
|
|
|
if (this.$refs.submitRef) {
|
|
|
|
|
|
this.moreHeight=0;
|
|
|
|
|
|
this.$refs.submitRef.closeAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
// if (type !== 'tools' && this.$refs.fileUpload) {
|
|
|
|
|
|
// this.$refs.fileUpload.hide();
|
|
|
|
|
|
// }
|
|
|
|
|
|
},
|
|
|
|
|
|
selectEmoji(emoText) {
|
|
|
|
|
|
const path = this.$emo.textToPath(emoText);
|
|
|
|
|
|
this.isReadOnly = true;
|
|
|
|
|
|
this.isEmpty = false;
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
|
this.editorCtx.insertImage({
|
|
|
|
|
|
src: path,
|
|
|
|
|
|
alt: emoText,
|
|
|
|
|
|
extClass: 'emoji-small',
|
|
|
|
|
|
nowrap: true,
|
|
|
|
|
|
complete: () => {
|
|
|
|
|
|
this.isReadOnly = false;
|
|
|
|
|
|
this.editorCtx.blur();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
onUploadImageBefore(file) {
|
|
|
|
|
|
if (this.isBanned) {
|
|
|
|
|
|
this.showBannedTip();
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
const data = { originUrl: file.path, thumbUrl: file.path };
|
|
|
|
|
|
const msgInfo = {
|
|
|
|
|
|
id: 0,
|
|
|
|
|
|
tmpId: this.generateId(),
|
|
|
|
|
|
fileId: file.uid,
|
|
|
|
|
|
sendId: this.info.userid,
|
|
|
|
|
|
content: JSON.stringify(data),
|
|
|
|
|
|
sendTime: Date.now(),
|
|
|
|
|
|
selfSend: true,
|
|
|
|
|
|
type: this.$enums.MESSAGE_TYPE.IMAGE,
|
|
|
|
|
|
readedCount: 0,
|
|
|
|
|
|
loadStatus: 'loading',
|
|
|
|
|
|
status: this.$enums.MESSAGE_STATUS.UNSEND
|
|
|
|
|
|
};
|
|
|
|
|
|
this.fillTargetId(msgInfo, this.chat.targetId);
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
this.moveChatToTop();
|
|
|
|
|
|
file.msgInfo = msgInfo;
|
|
|
|
|
|
file.chat = this.chat;
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
},
|
|
|
|
|
|
onUploadImageSuccess(file, res) {
|
|
|
|
|
|
const msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
|
|
|
|
|
|
msgInfo.content = JSON.stringify(res.data);
|
|
|
|
|
|
msgInfo.receipt = this.isReceipt;
|
|
|
|
|
|
this.sendMessageRequest(msgInfo).then( res=> {
|
|
|
|
|
|
var m =res.data.data
|
|
|
|
|
|
msgInfo.loadStatus = 'ok';
|
|
|
|
|
|
msgInfo.id = m.id;
|
|
|
|
|
|
this.isReceipt = true;
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: file.chat });
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
onUploadImageFail(file) {
|
|
|
|
|
|
const msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
|
|
|
|
|
|
msgInfo.loadStatus = 'fail';
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: file.chat });
|
|
|
|
|
|
},
|
|
|
|
|
|
onUploadFileBefore(file) {
|
|
|
|
|
|
if (this.isBanned) {
|
|
|
|
|
|
this.showBannedTip();
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
const data = { name: file.name, size: file.size, url: file.path };
|
|
|
|
|
|
const msgInfo = {
|
|
|
|
|
|
id: 0,
|
|
|
|
|
|
tmpId: this.generateId(),
|
|
|
|
|
|
sendId: this.info.userid,
|
|
|
|
|
|
content: JSON.stringify(data),
|
|
|
|
|
|
sendTime: Date.now(),
|
|
|
|
|
|
selfSend: true,
|
|
|
|
|
|
type: this.$enums.MESSAGE_TYPE.FILE,
|
|
|
|
|
|
readedCount: 0,
|
|
|
|
|
|
loadStatus: 'loading',
|
|
|
|
|
|
status: this.$enums.MESSAGE_STATUS.UNSEND
|
|
|
|
|
|
};
|
|
|
|
|
|
this.fillTargetId(msgInfo, this.chat.targetId);
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
this.moveChatToTop();
|
|
|
|
|
|
file.msgInfo = msgInfo;
|
|
|
|
|
|
file.chat = this.chat;
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
},
|
|
|
|
|
|
onUploadFileSuccess(file, res) {
|
|
|
|
|
|
const data = { name: file.name, size: file.size, url: res.data };
|
|
|
|
|
|
const msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
|
|
|
|
|
|
msgInfo.content = JSON.stringify(data);
|
|
|
|
|
|
msgInfo.receipt = this.isReceipt;
|
|
|
|
|
|
this.sendMessageRequest(msgInfo).then(res => {
|
|
|
|
|
|
var m=res.data.data
|
|
|
|
|
|
msgInfo.loadStatus = 'ok';
|
|
|
|
|
|
msgInfo.id = m.id;
|
|
|
|
|
|
this.isReceipt = true;
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: file.chat });
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
onUploadFileFail(file) {
|
|
|
|
|
|
const msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
|
|
|
|
|
|
msgInfo.loadStatus = 'fail';
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: file.chat });
|
|
|
|
|
|
},
|
|
|
|
|
|
onDeleteMessage(msgInfo) {
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '删除消息',
|
|
|
|
|
|
content: '确认删除消息?',
|
|
|
|
|
|
success: res => {
|
|
|
|
|
|
if (!res.cancel) {
|
|
|
|
|
|
this.deleteMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
uni.showToast({ title: '删除成功', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
async onRecallMessage(msgInfo) {
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '撤回消息',
|
|
|
|
|
|
content: '确认撤回消息?',
|
|
|
|
|
|
success: res => {
|
|
|
|
|
|
if (!res.cancel) {
|
|
|
|
|
|
// const url = `/message/${this.chat.type.toLowerCase()}/recall/${msgInfo.id}`;
|
|
|
|
|
|
// uni.$http.delete(url).then(m => {
|
|
|
|
|
|
// m.selfSend = true;
|
|
|
|
|
|
// this.recallMessage({ msgInfo: m, chatInfo: this.chat });
|
|
|
|
|
|
// });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
onCopyMessage(msgInfo) {
|
|
|
|
|
|
uni.setClipboardData({
|
|
|
|
|
|
data: msgInfo.content,
|
|
|
|
|
|
success: () => uni.showToast({ title: '复制成功' }),
|
|
|
|
|
|
fail: () => uni.showToast({ title: '复制失败', icon: 'none' })
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
onDownloadFile(msgInfo) {
|
|
|
|
|
|
const url = JSON.parse(msgInfo.content).url;
|
|
|
|
|
|
uni.downloadFile({
|
|
|
|
|
|
url,
|
|
|
|
|
|
success(res) {
|
|
|
|
|
|
if (res.statusCode === 200) {
|
|
|
|
|
|
const filePath = encodeURI(res.tempFilePath);
|
|
|
|
|
|
uni.openDocument({ filePath, showMenu: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail() {
|
|
|
|
|
|
uni.showToast({ title: '文件下载失败', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
onClickToBottom() {
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.isInBottom = true;
|
|
|
|
|
|
this.newMessageSize = 0;
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
},
|
|
|
|
|
|
onScrollToTop() {
|
|
|
|
|
|
console.log("lalal...........")
|
|
|
|
|
|
if (this.showMinIdx > 0) {
|
|
|
|
|
|
// #ifndef H5
|
|
|
|
|
|
this.scrollToMsgIdx(this.showMinIdx);
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
this.showMinIdx = this.showMinIdx > 20 ? this.showMinIdx - 20 : 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.isInBottom = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
onScrollToBottom() {
|
|
|
|
|
|
this.isInBottom = true;
|
|
|
|
|
|
this.newMessageSize = 0;
|
|
|
|
|
|
},
|
|
|
|
|
|
onShowMore() {
|
|
|
|
|
|
if (this.chat.type === 'GROUP') {
|
|
|
|
|
|
uni.navigateTo({ url: `/pages/group/group-info?id=${this.group.id}` });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uni.navigateTo({ url: `/pages/common/user-info?id=${this.userInfo.id}` });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
onTextInput(e) {
|
|
|
|
|
|
this.isEmpty = e.detail.html === '<p><br></p>';
|
|
|
|
|
|
},
|
|
|
|
|
|
onEditorReady() {
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
|
const query = uni.createSelectorQuery().in(this);
|
|
|
|
|
|
query.select('#editor').context(res => {
|
|
|
|
|
|
this.editorCtx = res.context;
|
|
|
|
|
|
}).exec();
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
onEditorFocus() {
|
|
|
|
|
|
this.isFocus = true;
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
this.switchChatTabBox('none');
|
|
|
|
|
|
},
|
|
|
|
|
|
onEditorBlur() {
|
|
|
|
|
|
this.isFocus = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
onAudioStateChange(state, msgInfo) {
|
|
|
|
|
|
const playingAudio = this.$refs['message' + msgInfo.id]?.[0];
|
|
|
|
|
|
if (state === 'PLAYING' && playingAudio !== this.playingAudio) {
|
|
|
|
|
|
this.playingAudio?.stopPlayAudio();
|
|
|
|
|
|
this.playingAudio = playingAudio;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
//获取最大已读id
|
|
|
|
|
|
async loadReaded(fid) {
|
|
|
|
|
|
await uni.$http.get( `/api/message/private/maxReadedId?friendId=${fid}`).then(res => {
|
|
|
|
|
|
//获取最大已读id
|
|
|
|
|
|
this.readedMessage({ friendId: fid, maxId: res.data.data });
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
async toReadedMessage() {
|
|
|
|
|
|
if (this.unreadCount === 0) return;
|
|
|
|
|
|
const url = this.chat.type === 'GROUP'
|
|
|
|
|
|
? `/api/message/group/readed?groupId=${this.chat.targetId}`
|
|
|
|
|
|
: `/api/message/private/readed?friendId=${this.chat.targetId}`;
|
|
|
|
|
|
await uni.$http.put(url).then(() => {
|
|
|
|
|
|
this.resetUnreadCount(this.chat);
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
async loadGroup(groupId) {
|
|
|
|
|
|
await uni.$http.get(`/api/group/find/${groupId}`).then(res => {
|
|
|
|
|
|
var group =res.data.data
|
|
|
|
|
|
this.group = group;
|
|
|
|
|
|
this.updateChatFromGroup(group);
|
|
|
|
|
|
this.updateGroup(group);
|
|
|
|
|
|
});
|
|
|
|
|
|
await uni.$http.get(`/api/group/members/${groupId}`).then(members => {
|
|
|
|
|
|
this.groupMembers = members.data.data;
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
updateFriendInfo() {
|
|
|
|
|
|
if (this.friend) {
|
|
|
|
|
|
const friend = JSON.parse(JSON.stringify(this.friend));
|
|
|
|
|
|
friend.headImage = this.userInfo.headImageThumb;
|
|
|
|
|
|
friend.nickName = this.userInfo.nickName;
|
|
|
|
|
|
friend.showNickName = friend.remarkNickName || friend.nickName;
|
|
|
|
|
|
this.updateFriend(friend);
|
|
|
|
|
|
this.updateChatFromFriend(friend);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.updateChatFromUser(this.userInfo);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
async loadFriend(friendId) {
|
|
|
|
|
|
await uni.$http.get( `/api/friend/find/${friendId}`).then(userInfo => {
|
|
|
|
|
|
this.userInfo = userInfo.data.data;
|
|
|
|
|
|
this.updateFriendInfo();
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
rpxTopx(rpx) {
|
|
|
|
|
|
const info = uni.getSystemInfoSync();
|
|
|
|
|
|
return Math.floor(info.windowWidth * rpx / 750);
|
|
|
|
|
|
},
|
|
|
|
|
|
html2Escape(str) {
|
|
|
|
|
|
return str.replace(/[<>&"]/g, c => ({ '<': '<', '>': '>', '&': '&', '"': '"' }[c]));
|
|
|
|
|
|
},
|
|
|
|
|
|
sendMessageRequest(msgInfo) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
this.reqQueue.push({ msgInfo, resolve, reject });
|
|
|
|
|
|
this.processReqQueue();
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
async processReqQueue() {
|
|
|
|
|
|
if (this.reqQueue.length && !this.isSending) {
|
|
|
|
|
|
this.isSending = true;
|
|
|
|
|
|
const reqData = this.reqQueue.shift();
|
|
|
|
|
|
await uni.$http.post( this.messageAction,reqData.msgInfo)
|
|
|
|
|
|
.then(res => reqData.resolve(res.data.data))
|
|
|
|
|
|
.catch(e => reqData.reject(e))
|
|
|
|
|
|
.finally(() => {
|
|
|
|
|
|
this.isSending = false;
|
|
|
|
|
|
this.processReqQueue();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
reCalChatMainHeight() {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
let h = this.windowHeight;
|
|
|
|
|
|
// 减去标题栏高度
|
|
|
|
|
|
h -= 25;
|
|
|
|
|
|
// 减去键盘高度
|
|
|
|
|
|
// if (this.isShowKeyBoard || this.chatTabBox !== 'none') {
|
|
|
|
|
|
// h -= this.keyboardHeight;
|
|
|
|
|
|
// console.log("3h",h)
|
|
|
|
|
|
// this.scrollToBottom();
|
|
|
|
|
|
// }
|
|
|
|
|
|
// #ifndef H5
|
|
|
|
|
|
// h5需要减去状态栏高度
|
|
|
|
|
|
h -= uni.getSystemInfoSync().statusBarHeight;
|
|
|
|
|
|
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
this.chatMainHeight = h;
|
|
|
|
|
|
// if (this.isShowKeyBoard || this.chatTabBox !== 'none') {
|
|
|
|
|
|
// this.scrollToBottom();
|
|
|
|
|
|
// }
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// #ifdef H5
|
|
|
|
|
|
if (uni.getSystemInfoSync().platform === 'ios') {
|
|
|
|
|
|
[50, 100, 500].forEach(delay => {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
uni.pageScrollTo({ scrollTop: 0, duration: 10 });
|
|
|
|
|
|
}, delay);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
}, 30);
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
listenKeyBoard() {
|
|
|
|
|
|
// #ifdef H5
|
|
|
|
|
|
if (navigator.platform === 'Win32') {
|
|
|
|
|
|
console.log('navigator.platform:', navigator.platform);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (uni.getSystemInfoSync().platform === 'ios') {
|
|
|
|
|
|
window.addEventListener('focusin', this.focusInListener);
|
|
|
|
|
|
window.addEventListener('focusout', this.focusOutListener);
|
|
|
|
|
|
if (window.visualViewport) {
|
|
|
|
|
|
window.visualViewport.addEventListener('resize', this.resizeListener);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
window.addEventListener('resize', this.resizeListener);
|
|
|
|
|
|
}
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
// #ifndef H5
|
|
|
|
|
|
uni.onKeyboardHeightChange(this.keyBoardListener);
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
unListenKeyboard() {
|
|
|
|
|
|
// #ifdef H5
|
|
|
|
|
|
window.removeEventListener('resize', this.resizeListener);
|
|
|
|
|
|
window.removeEventListener('focusin', this.focusInListener);
|
|
|
|
|
|
window.removeEventListener('focusout', this.focusOutListener);
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
// #ifndef H5
|
|
|
|
|
|
uni.offKeyboardHeightChange(this.keyBoardListener);
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
keyBoardListener(res) {
|
|
|
|
|
|
this.isShowKeyBoard = res.height > 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (this.isShowKeyBoard)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
this.chatMainHeight -= (res.height-this.moreHeight);
|
|
|
|
|
|
this.$refs.submitRef.keyboardHeight=res.height;
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
}else {
|
|
|
|
|
|
this.$refs.submitRef.keyboardHeight=0;
|
|
|
|
|
|
this.reCalChatMainHeight();
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('kb.chatMainHeight ',this.chatMainHeight )
|
|
|
|
|
|
console.log('this.morehight ',this.moreHeight )
|
|
|
|
|
|
},
|
|
|
|
|
|
resizeListener() {
|
|
|
|
|
|
console.log("123")
|
|
|
|
|
|
let height = this.initHeight - window.innerHeight;
|
|
|
|
|
|
if (window.visualViewport && uni.getSystemInfoSync().platform === 'ios') {
|
|
|
|
|
|
height = this.initHeight - window.visualViewport.height;
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('resizeListener:', window.visualViewport?.height);
|
|
|
|
|
|
this.isShowKeyBoard = height > 150;
|
|
|
|
|
|
if (this.isShowKeyBoard) this.keyboardHeight = height;
|
|
|
|
|
|
this.reCalChatMainHeight();
|
|
|
|
|
|
},
|
|
|
|
|
|
focusInListener() {
|
|
|
|
|
|
console.log("456")
|
|
|
|
|
|
this.isShowKeyBoard = true;
|
|
|
|
|
|
this.reCalChatMainHeight();
|
|
|
|
|
|
},
|
|
|
|
|
|
focusOutListener() {
|
|
|
|
|
|
console.log("789")
|
|
|
|
|
|
this.isShowKeyBoard = false;
|
|
|
|
|
|
this.reCalChatMainHeight();
|
|
|
|
|
|
},
|
|
|
|
|
|
showBannedTip() {
|
|
|
|
|
|
const msgInfo = {
|
|
|
|
|
|
tmpId: this.generateId(),
|
|
|
|
|
|
sendId: this.mine.userid,
|
|
|
|
|
|
sendTime: Date.now(),
|
|
|
|
|
|
type: this.$enums.MESSAGE_TYPE.TIP_TEXT
|
|
|
|
|
|
};
|
|
|
|
|
|
if (this.chat.type === 'PRIVATE') {
|
|
|
|
|
|
msgInfo.recvId = this.mine.userid;
|
|
|
|
|
|
msgInfo.content = `该用户已被管理员封禁,原因:${this.userInfo.reason}`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
msgInfo.groupId = this.group.id;
|
|
|
|
|
|
msgInfo.content = `本群聊已被管理员封禁,原因:${this.group.reason}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.insertMessage({ msgInfo, chatInfo: this.chat });
|
|
|
|
|
|
},
|
|
|
|
|
|
generateId() {
|
|
|
|
|
|
return String(Date.now()) + String(Math.floor(Math.random() * 1000));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
|
|
.chat-box {
|
|
|
|
|
|
$icon-color: rgba(0, 0, 0, 0.88);
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
background-color: rgba(244, 244, 244, 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.chat-main-box {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
z-index: 2;
|
|
|
|
|
|
|
|
|
|
|
|
.chat-message {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
background-color: white;
|
|
|
|
|
|
|
|
|
|
|
|
.scroll-box {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.scroll-to-bottom {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
right: 30rpx;
|
|
|
|
|
|
bottom: 30rpx;
|
|
|
|
|
|
font-size: $im-font-size;
|
|
|
|
|
|
color: $im-color-primary;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
padding: 10rpx 30rpx;
|
|
|
|
|
|
border-radius: 25rpx;
|
|
|
|
|
|
box-shadow: $im-box-shadow-dark;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ========== 选择模式样式 ========== */
|
|
|
|
|
|
.message-wrapper {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
padding: 4rpx 0;
|
|
|
|
|
|
transition: background 0.2s;
|
|
|
|
|
|
|
|
|
|
|
|
/* 本人发送的消息:整行靠右,保持消息原右侧位置不变 */
|
|
|
|
|
|
&.self {
|
|
|
|
|
|
flex-direction: row-reverse;
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.message-wrapper .checkbox-container {
|
|
|
|
|
|
padding: 20rpx 12rpx 0 16rpx;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
align-self: stretch;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.checkbox {
|
|
|
|
|
|
width: 42rpx;
|
|
|
|
|
|
height: 42rpx;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
border: 2rpx solid #ccc;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
&.checked {
|
|
|
|
|
|
background: $im-color-primary;
|
|
|
|
|
|
border-color: $im-color-primary;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ========== 底部选择操作栏 ========== */
|
|
|
|
|
|
.selection-bar {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
border-top: 1rpx solid #e8e8e8;
|
|
|
|
|
|
padding: 16rpx 24rpx;
|
|
|
|
|
|
padding-bottom: env(safe-area-inset-bottom, 16rpx);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.06);
|
|
|
|
|
|
|
|
|
|
|
|
.selection-right {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 16rpx;
|
|
|
|
|
|
.selection-count {
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
margin-right: 8rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
.btn-cancel-selection {
|
|
|
|
|
|
padding: 10rpx 28rpx;
|
|
|
|
|
|
height: 60rpx;
|
|
|
|
|
|
line-height: 40rpx;
|
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
border-radius: 30rpx;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.btn-confirm-selection {
|
|
|
|
|
|
padding: 10rpx 32rpx;
|
|
|
|
|
|
height: 60rpx;
|
|
|
|
|
|
line-height: 40rpx;
|
|
|
|
|
|
background: $im-color-primary;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
border-radius: 30rpx;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
box-shadow: 0 4rpx 12rpx $im-color-primary;
|
|
|
|
|
|
&:disabled {
|
|
|
|
|
|
background: #ccc;
|
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ========== 投诉底部弹窗样式 ========== */
|
|
|
|
|
|
.report-menu-mask {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
background: rgba(0, 0, 0, 0.45);
|
|
|
|
|
|
z-index: 999;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
}
|
|
|
|
|
|
.report-menu-panel {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
border-radius: 24rpx 24rpx 0 0;
|
|
|
|
|
|
padding-bottom: env(safe-area-inset-bottom, 24rpx);
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
.report-menu-title {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
color: #999999;
|
|
|
|
|
|
padding: 24rpx 0 12rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
.report-menu-item {
|
|
|
|
|
|
height: 100rpx;
|
|
|
|
|
|
line-height: 100rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
|
color: #333333;
|
|
|
|
|
|
border-top: 1rpx solid #f0f0f0;
|
|
|
|
|
|
&:active {
|
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.report-menu-complain {
|
|
|
|
|
|
color: $im-color-primary;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|