|
|
@ -3,6 +3,16 @@ |
|
|
<nav-bar>{{ title }}</nav-bar> |
|
|
<nav-bar>{{ title }}</nav-bar> |
|
|
<view class="chat-main-box" :style="{height: chatMainHeight+'px'}"> |
|
|
<view class="chat-main-box" :style="{height: chatMainHeight+'px'}"> |
|
|
<view class="chat-message" @click="switchChatTabBox('none')"> |
|
|
<view class="chat-message" @click="switchChatTabBox('none')"> |
|
|
|
|
|
<!-- 固定在聊天区顶部的常见问题提示条 --> |
|
|
|
|
|
<view v-if="showAutoQuestionTip" class="question-tip-fixed"> |
|
|
|
|
|
<view class="tip-title">请选择您想咨询的问题:</view> |
|
|
|
|
|
<view class="question-list"> |
|
|
|
|
|
<view class="question-item" v-for="(q, i) in commonQuestions" :key="i" |
|
|
|
|
|
@click="sendQuestionMessage(q)"> |
|
|
|
|
|
{{ q }} |
|
|
|
|
|
</view> |
|
|
|
|
|
</view> |
|
|
|
|
|
</view> |
|
|
<scroll-view class="scroll-box" scroll-y="true" upper-threshold="200" @scrolltoupper="onScrollToTop" |
|
|
<scroll-view class="scroll-box" scroll-y="true" upper-threshold="200" @scrolltoupper="onScrollToTop" |
|
|
@scroll="onScroll" :scroll-into-view="'chat-item-' + scrollMsgIdx" :scroll-top="scrollTop"> |
|
|
@scroll="onScroll" :scroll-into-view="'chat-item-' + scrollMsgIdx" :scroll-top="scrollTop"> |
|
|
<view v-if="chat" class="chat-wrap"> |
|
|
<view v-if="chat" class="chat-wrap"> |
|
|
@ -126,43 +136,109 @@ import UNI_APP from '@/.env.js'; |
|
|
export default { |
|
|
export default { |
|
|
data() { |
|
|
data() { |
|
|
return { |
|
|
return { |
|
|
// chat: {}, |
|
|
|
|
|
userInfo: {}, |
|
|
userInfo: {}, |
|
|
group: {}, |
|
|
group: {}, |
|
|
groupMembers: [], |
|
|
groupMembers: [], |
|
|
isReceipt: false, // 是否回执消息 |
|
|
showAutoQuestionTip: true, |
|
|
scrollMsgIdx: 0, // 滚动条定位为到哪条消息 |
|
|
commonQuestions: [], // 空数组,接口获取 |
|
|
|
|
|
autoReplyList: [], // 存储完整回复列表 |
|
|
|
|
|
isReceipt: false, |
|
|
|
|
|
scrollMsgIdx: 0, |
|
|
chatTabBox: 'none', |
|
|
chatTabBox: 'none', |
|
|
currentTargetId: null, // 加这一行 |
|
|
currentTargetId: null, |
|
|
currentChatType: 'PRIVATE', |
|
|
currentChatType: 'PRIVATE', |
|
|
isLoading: true, // 添加加载状态 |
|
|
_activeChatIdx: 0, |
|
|
defaultTitle: '加载中...', // 默认标题 |
|
|
|
|
|
// activeChatIdx: 0, |
|
|
|
|
|
_activeChatIdx: 0, // 添加这个私有变量 |
|
|
|
|
|
showRecord: false, |
|
|
showRecord: false, |
|
|
chatMainHeight: 800, // 聊天窗口高度 |
|
|
chatMainHeight: 800, |
|
|
keyboardHeight: 290, // 键盘高度 |
|
|
keyboardHeight: 290, |
|
|
screenHeight: 1000, // 屏幕高度 |
|
|
screenHeight: 1000, |
|
|
windowHeight: 1000, // 窗口高度 |
|
|
windowHeight: 1000, |
|
|
initHeight: 1000, // h5初始高度 |
|
|
initHeight: 1000, |
|
|
atUserIds: [], |
|
|
atUserIds: [], |
|
|
showMinIdx: 0, // 下标小于showMinIdx的消息不显示,否则可能很卡 |
|
|
showMinIdx: 0, |
|
|
reqQueue: [], // 请求队列 |
|
|
reqQueue: [], |
|
|
isSending: false, // 是否正在发送请求 |
|
|
isSending: false, |
|
|
isShowKeyBoard: false, // 键盘是否正在弹起 |
|
|
isShowKeyBoard: false, |
|
|
editorCtx: null, // 编辑器上下文 |
|
|
editorCtx: null, |
|
|
isEmpty: true, // 编辑器是否为空 |
|
|
isEmpty: true, |
|
|
isFocus: false, // 编辑器是否焦点 |
|
|
isFocus: false, |
|
|
isReadOnly: false, // 编辑器是否只读 |
|
|
isReadOnly: false, |
|
|
playingAudio: null, // 当前正在播放的录音消息 |
|
|
playingAudio: null, |
|
|
isInBottom: true, // 滚动条是否在底部 |
|
|
isInBottom: true, |
|
|
newMessageSize: 0, // 滚动条不在底部时新的消息数量 |
|
|
newMessageSize: 0, |
|
|
scrollTop: 0, // 用于ios h5定位滚动条 |
|
|
scrollTop: 0, |
|
|
scrollViewHeight: 0, // 滚动条总长度 |
|
|
scrollViewHeight: 0, |
|
|
maxTmpId: 0 // 最后生成的临时id |
|
|
maxTmpId: 0 |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
methods: { |
|
|
methods: { |
|
|
|
|
|
loadCommonQuestions(userId) { |
|
|
|
|
|
this.$http({ |
|
|
|
|
|
url: "/auto/reply/list?userId=" + userId, |
|
|
|
|
|
method: 'get', |
|
|
|
|
|
}).then(res => { |
|
|
|
|
|
// 你的接口是 res.data 才是列表 |
|
|
|
|
|
let list = res || []; |
|
|
|
|
|
this.autoReplyList = list; |
|
|
|
|
|
this.commonQuestions = list.map(item => item.replyTitle); |
|
|
|
|
|
}).catch(() => { |
|
|
|
|
|
// this.commonQuestions = ["什么时候发货?", "如何申请退款?", "订单怎么取消?", "可以开发票吗?", "商品是否包邮?"]; |
|
|
|
|
|
}); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
sendQuestionMessage(replyTitle) { |
|
|
|
|
|
let autoReply = this.autoReplyList.find(item => item.replyTitle === replyTitle); |
|
|
|
|
|
if (!autoReply) return; |
|
|
|
|
|
|
|
|
|
|
|
// 检查是否被封禁 |
|
|
|
|
|
if (this.isBanned) { |
|
|
|
|
|
this.showBannedTip(); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let msgInfo = { |
|
|
|
|
|
tmpId: this.generateId(), |
|
|
|
|
|
receipt: this.isReceipt |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// ============== 区分类型 ============== |
|
|
|
|
|
if (autoReply.replyType === 0) { |
|
|
|
|
|
// 文本 |
|
|
|
|
|
msgInfo.type = this.$enums.MESSAGE_TYPE.TEXT; |
|
|
|
|
|
msgInfo.content = autoReply.replyContent; |
|
|
|
|
|
} else if (autoReply.replyType === 1) { |
|
|
|
|
|
// 图片 |
|
|
|
|
|
msgInfo.type = this.$enums.MESSAGE_TYPE.IMAGE; |
|
|
|
|
|
msgInfo.content = JSON.stringify({ |
|
|
|
|
|
originUrl: autoReply.replyContent, |
|
|
|
|
|
thumbUrl: autoReply.replyContent |
|
|
|
|
|
}); |
|
|
|
|
|
} else { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.fillTargetId(msgInfo, this.chat.targetId); |
|
|
|
|
|
const chat = this.chat; |
|
|
|
|
|
if (!chat) return; |
|
|
|
|
|
|
|
|
|
|
|
let tmpMessage = this.buildTmpMessage(msgInfo); |
|
|
|
|
|
this.chatStore.insertMessage(tmpMessage, chat); |
|
|
|
|
|
this.moveChatToTop(); |
|
|
|
|
|
|
|
|
|
|
|
this.sendMessageRequest(msgInfo).then((m) => { |
|
|
|
|
|
tmpMessage = JSON.parse(JSON.stringify(tmpMessage)); |
|
|
|
|
|
tmpMessage.id = m.id; |
|
|
|
|
|
tmpMessage.status = m.status; |
|
|
|
|
|
this.chatStore.updateMessage(tmpMessage, chat); |
|
|
|
|
|
this.scrollToBottom(); |
|
|
|
|
|
this.isReceipt = false; |
|
|
|
|
|
}).catch(() => { |
|
|
|
|
|
tmpMessage = JSON.parse(JSON.stringify(tmpMessage)); |
|
|
|
|
|
tmpMessage.status = this.$enums.MESSAGE_STATUS.FAILED; |
|
|
|
|
|
this.chatStore.updateMessage(tmpMessage, chat); |
|
|
|
|
|
}); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
onRecorderInput() { |
|
|
onRecorderInput() { |
|
|
this.showRecord = true; |
|
|
this.showRecord = true; |
|
|
this.switchChatTabBox('none'); |
|
|
this.switchChatTabBox('none'); |
|
|
@ -1038,7 +1114,6 @@ export default { |
|
|
}); |
|
|
}); |
|
|
}, |
|
|
}, |
|
|
generateId() { |
|
|
generateId() { |
|
|
// 生成临时id |
|
|
|
|
|
const id = String(new Date().getTime()) + String(Math.floor(Math.random() * 1000)); |
|
|
const id = String(new Date().getTime()) + String(Math.floor(Math.random() * 1000)); |
|
|
// 必须保证id是递增 |
|
|
// 必须保证id是递增 |
|
|
if (this.maxTmpId > id) { |
|
|
if (this.maxTmpId > id) { |
|
|
@ -1050,13 +1125,7 @@ export default { |
|
|
}, |
|
|
}, |
|
|
computed: { |
|
|
computed: { |
|
|
title() { |
|
|
title() { |
|
|
// 加载中显示默认标题 |
|
|
if (!this.chat) return ""; |
|
|
if (this.isLoading) { |
|
|
|
|
|
return this.defaultTitle; |
|
|
|
|
|
} |
|
|
|
|
|
if (!this.chat) { |
|
|
|
|
|
return ""; |
|
|
|
|
|
} |
|
|
|
|
|
let title = this.chat.showName; |
|
|
let title = this.chat.showName; |
|
|
if (this.isGroup) { |
|
|
if (this.isGroup) { |
|
|
let size = this.groupMembers.filter(m => !m.quit).length; |
|
|
let size = this.groupMembers.filter(m => !m.quit).length; |
|
|
@ -1076,36 +1145,12 @@ export default { |
|
|
get() { return this._activeChatIdx || 0; }, |
|
|
get() { return this._activeChatIdx || 0; }, |
|
|
set(val) { this._activeChatIdx = val; } |
|
|
set(val) { this._activeChatIdx = val; } |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// activeChatIdx: { |
|
|
|
|
|
// get() { |
|
|
|
|
|
// return this._activeChatIdx || 0; |
|
|
|
|
|
// }, |
|
|
|
|
|
// set(val) { |
|
|
|
|
|
// this._activeChatIdx = val; |
|
|
|
|
|
// } |
|
|
|
|
|
// }, |
|
|
|
|
|
|
|
|
|
|
|
mine() { |
|
|
mine() { |
|
|
return this.userStore.userInfo; |
|
|
return this.userStore.userInfo; |
|
|
}, |
|
|
}, |
|
|
friend() { |
|
|
friend() { |
|
|
return this.friendStore.findFriend(this.userInfo.id); |
|
|
return this.friendStore.findFriend(this.userInfo.id); |
|
|
}, |
|
|
}, |
|
|
title() { |
|
|
|
|
|
if (!this.chat) { |
|
|
|
|
|
return ""; |
|
|
|
|
|
} |
|
|
|
|
|
let title = this.chat.showName; |
|
|
|
|
|
if (this.isGroup) { |
|
|
|
|
|
let size = this.groupMembers.filter(m => !m.quit).length; |
|
|
|
|
|
title += `(${size})`; |
|
|
|
|
|
} |
|
|
|
|
|
return title; |
|
|
|
|
|
}, |
|
|
|
|
|
// messageAction() { |
|
|
|
|
|
// return `/message/${this.chat.type.toLowerCase()}/send`; |
|
|
|
|
|
// }, |
|
|
|
|
|
messageAction() { |
|
|
messageAction() { |
|
|
if (!this.chat) return ''; |
|
|
if (!this.chat) return ''; |
|
|
return `/message/${this.chat.type.toLowerCase()}/send`; |
|
|
return `/message/${this.chat.type.toLowerCase()}/send`; |
|
|
@ -1171,8 +1216,6 @@ export default { |
|
|
deep: true, |
|
|
deep: true, |
|
|
immediate: true |
|
|
immediate: true |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 监听消息数组长度变化 |
|
|
|
|
|
'chat.messages.length': function(newLength, oldLength) { |
|
|
'chat.messages.length': function(newLength, oldLength) { |
|
|
if (newLength > oldLength && this.isInBottom) { |
|
|
if (newLength > oldLength && this.isInBottom) { |
|
|
this.$nextTick(() => { |
|
|
this.$nextTick(() => { |
|
|
@ -1180,8 +1223,6 @@ export default { |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 原有的 messageSize watch |
|
|
|
|
|
messageSize: function(newSize, oldSize) { |
|
|
messageSize: function(newSize, oldSize) { |
|
|
if (newSize > oldSize && oldSize > 0) { |
|
|
if (newSize > oldSize && oldSize > 0) { |
|
|
let lastMessage = this.chat.messages[newSize - 1]; |
|
|
let lastMessage = this.chat.messages[newSize - 1]; |
|
|
@ -1218,12 +1259,10 @@ export default { |
|
|
let targetId = null; |
|
|
let targetId = null; |
|
|
let type = 'PRIVATE'; |
|
|
let type = 'PRIVATE'; |
|
|
|
|
|
|
|
|
// 从列表进入 |
|
|
|
|
|
if (options.targetId) { |
|
|
if (options.targetId) { |
|
|
targetId = Number(options.targetId); |
|
|
targetId = Number(options.targetId); |
|
|
type = options.type || 'PRIVATE'; |
|
|
type = options.type || 'PRIVATE'; |
|
|
} else { |
|
|
} else { |
|
|
// 直接打开页面 |
|
|
|
|
|
if (this.friendStore.friends.length === 0) await this.friendStore.loadFriend(); |
|
|
if (this.friendStore.friends.length === 0) await this.friendStore.loadFriend(); |
|
|
const first = this.friendStore.friends[0]; |
|
|
const first = this.friendStore.friends[0]; |
|
|
if (!first) return uni.showToast({ title: '暂无好友', icon: 'none' }); |
|
|
if (!first) return uni.showToast({ title: '暂无好友', icon: 'none' }); |
|
|
@ -1233,7 +1272,6 @@ export default { |
|
|
await this.chatStore.loadChat(); |
|
|
await this.chatStore.loadChat(); |
|
|
await new Promise(r => setTimeout(r, 300)); |
|
|
await new Promise(r => setTimeout(r, 300)); |
|
|
|
|
|
|
|
|
// 查找会话 |
|
|
|
|
|
let chat = this.chatStore.chats.find(c => c.type === type && c.targetId === targetId); |
|
|
let chat = this.chatStore.chats.find(c => c.type === type && c.targetId === targetId); |
|
|
if (!chat) { |
|
|
if (!chat) { |
|
|
const friend = this.friendStore.findFriend(targetId) || this.friendStore.friends[0]; |
|
|
const friend = this.friendStore.findFriend(targetId) || this.friendStore.friends[0]; |
|
|
@ -1262,6 +1300,9 @@ export default { |
|
|
this.loadFriend(targetId); |
|
|
this.loadFriend(targetId); |
|
|
this.loadReaded(targetId); |
|
|
this.loadReaded(targetId); |
|
|
|
|
|
|
|
|
|
|
|
// 加载常见问题 |
|
|
|
|
|
this.loadCommonQuestions(targetId); |
|
|
|
|
|
|
|
|
this.listenKeyBoard(); |
|
|
this.listenKeyBoard(); |
|
|
this.windowHeight = uni.getSystemInfoSync().windowHeight; |
|
|
this.windowHeight = uni.getSystemInfoSync().windowHeight; |
|
|
this.screenHeight = uni.getSystemInfoSync().screenHeight; |
|
|
this.screenHeight = uni.getSystemInfoSync().screenHeight; |
|
|
@ -1332,6 +1373,43 @@ export default { |
|
|
|
|
|
|
|
|
.scroll-box { |
|
|
.scroll-box { |
|
|
height: 100%; |
|
|
height: 100%; |
|
|
|
|
|
padding-top: 120rpx; /* 给常见问题条留出空间 */ |
|
|
|
|
|
box-sizing: border-box; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 固定在聊天顶部的常见问题提示条(不会滚动) |
|
|
|
|
|
.question-tip-fixed { |
|
|
|
|
|
position: absolute; |
|
|
|
|
|
top: 0; |
|
|
|
|
|
left: 0; |
|
|
|
|
|
right: 0; |
|
|
|
|
|
z-index: 10; |
|
|
|
|
|
background-color: #ffffff; |
|
|
|
|
|
padding: 12rpx 20rpx; |
|
|
|
|
|
// border-bottom: 1rpx solid #f0f0f0; |
|
|
|
|
|
.tip-title { |
|
|
|
|
|
font-size: 26rpx; |
|
|
|
|
|
color: #999; |
|
|
|
|
|
text-align: center; |
|
|
|
|
|
margin-bottom: 8rpx; |
|
|
|
|
|
} |
|
|
|
|
|
.question-list { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
flex-wrap: wrap; |
|
|
|
|
|
gap: 10rpx; |
|
|
|
|
|
justify-content: center; |
|
|
|
|
|
} |
|
|
|
|
|
.question-item { |
|
|
|
|
|
background-color: #f5f7fa; |
|
|
|
|
|
border-radius: 30rpx; |
|
|
|
|
|
padding: 8rpx 16rpx; |
|
|
|
|
|
font-size: 26rpx; |
|
|
|
|
|
color: #333; |
|
|
|
|
|
border: 1rpx solid #eaeaea; |
|
|
|
|
|
} |
|
|
|
|
|
.question-item:active { |
|
|
|
|
|
background-color: #e6e6e6; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.scroll-to-bottom { |
|
|
.scroll-to-bottom { |
|
|
|