688 lines
15 KiB
Vue
688 lines
15 KiB
Vue
<script setup>
|
|
/**
|
|
* 聊天页面
|
|
*/
|
|
// 腾讯云聊天
|
|
import TencentCloudChat from '@tencentcloud/chat';
|
|
import {
|
|
ref,
|
|
reactive,
|
|
nextTick,
|
|
onUnmounted,
|
|
onMounted,
|
|
computed,
|
|
getCurrentInstance,
|
|
watch,
|
|
} from 'vue'
|
|
// api
|
|
import api from '@/api/index.js'
|
|
// 工具库
|
|
import util from '@/common/js/util.js'
|
|
import {
|
|
onLoad,
|
|
onReady,
|
|
onPageScroll,
|
|
onUnload
|
|
} from "@dcloudio/uni-app"
|
|
|
|
// 单条消息
|
|
import newsTemplate from './components/news-temp'
|
|
// 表情
|
|
import emoji from './emoji.vue'
|
|
// 语音条
|
|
import JyVoice from './jy-voice.vue'
|
|
// 加号菜单
|
|
import JyPlus from './jy-plus.vue'
|
|
|
|
import {
|
|
useStore
|
|
} from 'vuex'
|
|
const {
|
|
proxy
|
|
} = getCurrentInstance()
|
|
const store = useStore()
|
|
|
|
// 聊天对象
|
|
const msg = reactive({
|
|
// 聊天对象
|
|
id: '',
|
|
// 聊天类型
|
|
type: '',
|
|
})
|
|
// 输入的内容
|
|
const content = ref('')
|
|
// 加载
|
|
const loading = ref(false)
|
|
// 用户信息
|
|
const userinfo = computed(() => {
|
|
let result = store.state.userinfo
|
|
return result
|
|
})
|
|
// 列表数据
|
|
const list = reactive({
|
|
// 列表条数
|
|
limit: 20,
|
|
//显示的数据
|
|
data: [],
|
|
//
|
|
total: 0,
|
|
})
|
|
// 滚动条位置
|
|
const top = ref(0)
|
|
// 工具条的高度
|
|
const toolHeight = ref(0)
|
|
// 页码
|
|
const page = ref(1)
|
|
// 当前操作的元素
|
|
const messageItem = ref({})
|
|
// 工具栏状态 voice录音 input输入框 emoji表情 plus加号菜单
|
|
const toolStatus = ref('input')
|
|
// video路径
|
|
const videoUrl = ref('')
|
|
// 视频上下文
|
|
const videoContext = ref(null)
|
|
|
|
onLoad(option => {
|
|
// 用户昵称
|
|
if (option.name) uni.setNavigationBarTitle({
|
|
title: option.name
|
|
})
|
|
|
|
// 用户id
|
|
if (option.msgId) msg.id = option.msgId
|
|
// 聊天类型
|
|
if (option.type) msg.type = option.type
|
|
// 开启消息监听
|
|
addListener()
|
|
// 获取历史消息
|
|
getHistory({
|
|
callback: scrollToBottom
|
|
})
|
|
// #ifdef APP
|
|
uni.onKeyboardHeightChange((rs) => {
|
|
ghostBox.value.height = rs.height + 'px'
|
|
nextTick(() => {
|
|
scrollToBottom()
|
|
})
|
|
})
|
|
// #endif
|
|
})
|
|
|
|
onReady(() => {
|
|
uni.createSelectorQuery().in(proxy).select('#tool').boundingClientRect((rect) => {
|
|
toolHeight.value = rect.height
|
|
}).exec();
|
|
//
|
|
videoContext.value = uni.createVideoContext('video')
|
|
})
|
|
|
|
onPageScroll((ev) => {
|
|
onContentScroll(ev)
|
|
})
|
|
|
|
onUnload(() => {
|
|
// #ifdef APP
|
|
uni.offKeyboardHeightChange(() => {})
|
|
// #endif
|
|
videoContext.value.stop()
|
|
})
|
|
|
|
// 开启监听消息
|
|
function addListener() {
|
|
let onMessageReceived = function(event) {
|
|
console.log('TencentCloudChat.EVENT.MESSAGE_RECEIVED', event)
|
|
return
|
|
// 获取历史记录
|
|
getHistory()
|
|
//
|
|
list.data.push(...event.data)
|
|
}
|
|
|
|
uni.$chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, onMessageReceived);
|
|
}
|
|
|
|
// 移除监听消息
|
|
function removeListener() {
|
|
uni.$chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED);
|
|
}
|
|
|
|
// 获取更多消息记录
|
|
function getMoreHistroy() {
|
|
// 获取第一条消息记录
|
|
if (list.total <= list.data.length) return
|
|
getHistory({
|
|
msgId: list.data[0].id
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 获取历史记录
|
|
* @param {Object} param
|
|
*/
|
|
function getHistory(param = {}) {
|
|
// 验证sdk是否准备完毕
|
|
let isReady = uni.$chat.isReady();
|
|
//
|
|
if (!isReady && userinfo.value.id) {
|
|
setTimeout(function() {
|
|
getHistory(param)
|
|
}, 200);
|
|
return
|
|
}
|
|
|
|
// 如果没有msgId
|
|
// if(!param.msgId)
|
|
if (loading.value) {
|
|
// 提示加载中
|
|
util.showToastAndRedirect("加载中")
|
|
return
|
|
}
|
|
|
|
//
|
|
loading.value = true
|
|
|
|
// 获取单聊聊天记录
|
|
let request = api.news.getUserMsgHistory
|
|
// 如果是群聊 获取群聊聊天记录
|
|
if (msg.type === 'GROUP') api.news.getGroupMsgHistory
|
|
|
|
// 获取历史记录
|
|
request({
|
|
query: {
|
|
msgId: param.msgId || '',
|
|
fromId: userinfo.value.id,
|
|
toId: msg.id,
|
|
limit: param.limit || list.limit,
|
|
},
|
|
}).then(res => {
|
|
if (res.code === 200) {
|
|
// 价格
|
|
const result = res.data
|
|
// 追加
|
|
list.data.unshift(...result.list.map(item => {
|
|
item.callbackData = JSON.parse(item.callbackJson)
|
|
return item
|
|
}))
|
|
// 总数
|
|
list.total = result.totalCount
|
|
console.log('list', list.data)
|
|
nextTick(() => {
|
|
param.callback && param.callback()
|
|
})
|
|
return
|
|
}
|
|
util.alert({
|
|
content: res.msg,
|
|
showCancel: false,
|
|
})
|
|
}).finally(() => {
|
|
loading.value = false
|
|
})
|
|
}
|
|
|
|
// 滚动至底部
|
|
function scrollToBottom() {
|
|
uni.createSelectorQuery().in(proxy).select('#scroll-content').boundingClientRect((res) => {
|
|
top.value = res.height
|
|
|
|
uni.pageScrollTo({
|
|
scrollTop: top.value,
|
|
duration: 0
|
|
})
|
|
// console.log('top.value', top.value)
|
|
}).exec();
|
|
}
|
|
|
|
// 防抖
|
|
function debounce(func, wait = 500) {
|
|
let timeout = null;
|
|
return function(...args) {
|
|
clearTimeout(timeout)
|
|
timeout = setTimeout(() => {
|
|
func.apply(this, args)
|
|
}, wait);
|
|
}
|
|
}
|
|
|
|
// 监听内容滚动
|
|
function onContentScroll(ev) {
|
|
if (ev.scrollTop == 50) getMoreHistroy()
|
|
debounce(() => {
|
|
top.value = ev.detail.scrollTop
|
|
})
|
|
}
|
|
|
|
// 点击发送
|
|
function handleSend() {
|
|
// 发送消息
|
|
sendMsg({
|
|
query: {
|
|
toUserId: msg.id,
|
|
msgType: TencentCloudChat.TYPES.MSG_TEXT,
|
|
},
|
|
data: {
|
|
text: content.value
|
|
},
|
|
success: () => {
|
|
// 清空已发送的消息
|
|
content.value = ''
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 加号菜单发送
|
|
* @param {Object} message 消息对象
|
|
*/
|
|
function handlePlusSend(message) {
|
|
sendMsg(message)
|
|
}
|
|
|
|
/**
|
|
* 发送消息
|
|
* @param {Object} param
|
|
*/
|
|
function sendMsg(param) {
|
|
//
|
|
const request = api.news.sendUserMsg
|
|
//
|
|
if (msg.type == 'group') request = api.news.sendGroupMsg
|
|
// 发送消息
|
|
request({
|
|
query: param.query,
|
|
data: param.data,
|
|
}).then((rs) => {
|
|
if (rs.code == 200) {
|
|
param.success ? param.success() : ''
|
|
//
|
|
getHistory({
|
|
callback: scrollToBottom,
|
|
})
|
|
return
|
|
}
|
|
util.alert({
|
|
content: rs.msg,
|
|
showCancel: false,
|
|
})
|
|
}).catch((rs) => {
|
|
console.log('sendMsg error:', rs);
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 打开红包
|
|
* @param {Object} ev
|
|
*/
|
|
function handleRedPacket(ev) {
|
|
messageItem.value = ev
|
|
api.news.getRedbag({
|
|
data: {
|
|
// 红包id
|
|
bagId: ev.payload.data.id
|
|
}
|
|
}).then(rs => {
|
|
console.log('getRedbag', rs)
|
|
if (rs.code == 200) {
|
|
const result = rs.data
|
|
messageItem.value.payload.data = {
|
|
...result,
|
|
businessID: 'redPacket',
|
|
}
|
|
|
|
// 同步修改消息
|
|
let message = {
|
|
...messageItem.value
|
|
}
|
|
message.payload.data = JSON.stringify(msg.payload.data)
|
|
|
|
uni.$chat.modifyMessage(message).then(rs => {
|
|
console.log('modifyMessage success', rs)
|
|
}).catch(rs => {
|
|
console.log('modifyMessage catch', rs)
|
|
})
|
|
return
|
|
}
|
|
util.alert({
|
|
content: rs.msg,
|
|
showCancel: false,
|
|
})
|
|
})
|
|
proxy.$refs.RedPacket.open()
|
|
}
|
|
|
|
// 领取红包
|
|
function handleOpenReadPacket() {
|
|
//
|
|
if (messageItem.value.payload.data.status != 0) return
|
|
|
|
api.news.grabred({
|
|
data: {
|
|
// 红包id
|
|
id: messageItem.value.payload.data.id
|
|
}
|
|
}).then(rs => {
|
|
if (rs.code == 200) {
|
|
handleRedPacket(messageItem.value)
|
|
return
|
|
}
|
|
util.alert({
|
|
content: rs.msg,
|
|
showCancel: false,
|
|
})
|
|
})
|
|
}
|
|
|
|
// 选择的emoji
|
|
function emojiTap(val) {
|
|
content.value = content.value + val
|
|
}
|
|
|
|
// 点击工具栏
|
|
function handleTool(val) {
|
|
if (toolStatus.value === val) {
|
|
toolStatus.value = 'input'
|
|
return
|
|
}
|
|
toolStatus.value = val
|
|
}
|
|
|
|
// 输入框聚焦
|
|
function onFocus() {
|
|
handleTool('input')
|
|
}
|
|
|
|
// 输入语音
|
|
function voiceSend(message) {
|
|
console.log('handlePlusSend', message)
|
|
sendMsg({
|
|
message,
|
|
})
|
|
}
|
|
|
|
// 监听滚动
|
|
const handleScroll = (e) => {
|
|
if (e.detail.scrollTop === 0) {
|
|
getHistory()
|
|
}
|
|
}
|
|
|
|
// 撑起键盘的高度 打开该元素
|
|
const showGhost = ref(false)
|
|
// 给元素加高度
|
|
const ghostBox = ref({
|
|
height: '0px',
|
|
duration: '0.25s'
|
|
})
|
|
|
|
// 监听键盘高度变化
|
|
function keyboardheightchange(res) {
|
|
ghostBox.value = res.detail
|
|
nextTick(() => {
|
|
showGhost.value = res.detail.height > 0 ? true : false
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 看视频
|
|
* @param {Object} item 聊天消息对象
|
|
*/
|
|
function handleViewVideo(item) {
|
|
videoUrl.value = item.payload.videoUrl
|
|
// 进入全屏
|
|
videoContext.value.requestFullScreen()
|
|
}
|
|
|
|
// 监听视频是否全屏
|
|
function onScreenChange(ev) {
|
|
console.log('onScreenChange', ev)
|
|
if (!ev.fullScreen) videoContext.value.pause()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<view class="app">
|
|
<scroll-view class="scroll-view" scroll-y :scroll-with-animation="true" :scroll-top="top"
|
|
@scroll="onContentScroll" @scrolltoupper="getMoreHistroy">
|
|
<view id="scroll-content" style="padding: 30rpx 30rpx">
|
|
<view v-for="(item, index) in list.data" :key="index">
|
|
<!-- 普通消息 -->
|
|
<view class="message" :class="[item.From_Account === userinfo.id ? 'self' : 'friend']">
|
|
<!-- 如果是我自己 -->
|
|
<view>
|
|
<image :src="item.callbackData.from_url" class="avatar" mode="widthFix" />
|
|
</view>
|
|
<view class="df fdc mlr20">
|
|
<!-- 昵称 -->
|
|
<view class="df fdc" v-if="item.from != userinfo.userId">
|
|
<view class="name">{{ item.callbackData.from_name }}</view>
|
|
</view>
|
|
<!-- 消息 -->
|
|
<newsTemplate :item="item" :msg="msg" @openRedBag="handleRedPacket"
|
|
@viewVideo="handleViewVideo" />
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view v-if="showGhost" :style="{ height: `${ghostBox.height}px`, transition: `${ghostBox.duration}s` }">
|
|
</view>
|
|
<view class="ghost" :style="{height: toolHeight + 'px'}"></view>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
|
|
<view class="tool" id="tool">
|
|
<view class="tool-group" style="background: #F6F6F6;">
|
|
<!-- 语音 -->
|
|
<image src="/static/news-voice.png" mode="widthFix" class="thumb" @click="handleTool('voice')"></image>
|
|
<!-- 摁住说话 -->
|
|
<template v-if="toolStatus == 'voice'">
|
|
<JyVoice @send="voiceSend" :msg="msg" />
|
|
</template>
|
|
<!-- 输入框 -->
|
|
<template v-if="toolStatus != 'voice'">
|
|
<uni-easyinput @focus="onFocus" type="text" v-model="content" :clearable="false" class="input"
|
|
:adjust-position="false" @keyboardheightchange="keyboardheightchange" placeholder="请输入你的问题"
|
|
confirmType="发送" />
|
|
</template>
|
|
<!-- 表情 -->
|
|
<image src="/static/news-emoji.png" mode="widthFix" class="thumb" @click="handleTool('emoji')"></image>
|
|
<!-- 加号 -->
|
|
<template v-if="!content">
|
|
<image src="/static/news-plus.png" mode="widthFix" class="thumb" @click="handleTool('plus')" />
|
|
</template>
|
|
<!-- 文本发送按钮 -->
|
|
<template v-else>
|
|
<view class="send" @click="handleSend">发送</view>
|
|
</template>
|
|
</view>
|
|
<view v-if="showGhost" :style="{ height: `${ghostBox.height}px`, transition: `${ghostBox.duration}s` }"></view>
|
|
<!-- 表情 -->
|
|
<template v-if="toolStatus == 'emoji'">
|
|
<emoji @setEmoj="emojiTap"></emoji>
|
|
</template>
|
|
<!-- 加号 -->
|
|
<template v-if="toolStatus == 'plus'">
|
|
<JyPlus @send="handlePlusSend" :msg="msg"></JyPlus>
|
|
</template>
|
|
</view>
|
|
|
|
<!-- -->
|
|
<video :src="videoUrl" id="video" @fullscreenchange="onScreenChange" />
|
|
|
|
<!-- 红包封面 -->
|
|
<uni-popup ref="RedPacket" type="center">
|
|
<view class="red-bag br20" @touchmove.stop.prevent="">
|
|
<view class="rbag_top">
|
|
<view class="user fmid">
|
|
<view class="avatar">
|
|
<image class="wh80 cir" :src="messageItem.avatar" mode="scaleToFill" />
|
|
</view>
|
|
<view class="ml15 f32">{{messageItem.nick}}的红包</view>
|
|
</view>
|
|
<view class="app_name mt15 tac f40">{{ messageItem.payload.data.name }}</view>
|
|
|
|
<view class="amount f32" v-if="messageItem.payload.data.receive">
|
|
<text class="">已领取</text>
|
|
<text class="value">{{messageItem.payload.data.randomAmount}}</text>
|
|
<text class="unit" v-if="messageItem.payload.data.type == 1">积分</text>
|
|
<text class="unit" v-else-if="messageItem.payload.data.type == 2">余额</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="open_rbag_btn pr fmid" @click="handleOpenReadPacket">
|
|
<text v-if="messageItem.payload.data.receive">已</text>
|
|
<text>开</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="tac mt35" @click.stop="$refs.RedPacket.close()">
|
|
<uni-icons type="close" color="#fbd977" size="32" />
|
|
</view>
|
|
</uni-popup>
|
|
</template>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
@import './index.scss';
|
|
|
|
//
|
|
#video {
|
|
position: fixed;
|
|
top: 100%;
|
|
left: 0;
|
|
}
|
|
|
|
//
|
|
.red-bag {
|
|
position: relative;
|
|
width: 528rpx;
|
|
height: 60vh;
|
|
color: #ECCD97;
|
|
background-color: #e0534a;
|
|
box-shadow: 0 0 20rpx #00000033;
|
|
|
|
.rbag_top {
|
|
padding-top: 60rpx;
|
|
height: 70%;
|
|
background-color: #e0534a;
|
|
border-radius: 0 0 500rpx 500rpx / 0 0 200rpx 200rpx;
|
|
box-shadow: 0 5rpx 5rpx rgba(0, 0, 0, 0.2);
|
|
|
|
.amount {
|
|
margin-top: 120rpx;
|
|
text-align: center;
|
|
letter-spacing: 1rpx;
|
|
|
|
.value {
|
|
font-size: 60rpx;
|
|
font-weight: bold;
|
|
}
|
|
}
|
|
}
|
|
|
|
.open_rbag_btn {
|
|
width: 180rpx;
|
|
height: 180rpx;
|
|
margin: -90rpx auto 0;
|
|
color: #fef5e8;
|
|
font-size: 74rpx;
|
|
font-weight: bold;
|
|
background-color: #ffd287;
|
|
box-shadow: 2rpx 2rpx 6rpx rgba(0, 0, 0, 0.2);
|
|
border-radius: 50%;
|
|
z-index: 1;
|
|
}
|
|
|
|
// 打开红包
|
|
.open_rbag_model {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100vh;
|
|
background-color: rgba(0, 0, 0, 0.3);
|
|
z-index: 1000;
|
|
|
|
.rbag_conbg {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
width: 80%;
|
|
height: 840rpx;
|
|
margin: auto;
|
|
z-index: 1001;
|
|
}
|
|
|
|
.open_rbag_con {
|
|
z-index: 1002;
|
|
|
|
.open_title {
|
|
height: 120rpx;
|
|
line-height: 120rpx;
|
|
text-align: center;
|
|
font-size: 38rpx;
|
|
letter-spacing: 2rpx;
|
|
color: #e46965;
|
|
}
|
|
|
|
.rbag_detail {
|
|
margin-top: 90rpx;
|
|
|
|
.open_money {
|
|
text-align: center;
|
|
font-size: 80rpx;
|
|
color: #c95948;
|
|
font-weight: bold;
|
|
display: flex;
|
|
justify-content: center;
|
|
|
|
.danwei {
|
|
font-size: 30rpx;
|
|
margin-left: 16rpx;
|
|
margin-top: 24rpx;
|
|
}
|
|
}
|
|
|
|
.open_tips {
|
|
text-align: center;
|
|
font-size: 30rpx;
|
|
color: #d26762;
|
|
margin-top: 30rpx;
|
|
}
|
|
}
|
|
|
|
.lookbag_box {
|
|
margin-top: 300rpx;
|
|
display: flex;
|
|
justify-content: center;
|
|
|
|
.lookbag_btn {
|
|
width: 70%;
|
|
height: 90rpx;
|
|
line-height: 90rpx;
|
|
text-align: center;
|
|
font-size: 32rpx;
|
|
color: #c95948;
|
|
letter-spacing: 2rpx;
|
|
background-color: #ffd356;
|
|
border-radius: 50rpx;
|
|
box-shadow: 0rpx 0rpx 4rpx rgba(0, 0, 0, 0.2);
|
|
}
|
|
}
|
|
|
|
.hide_btn {
|
|
position: absolute;
|
|
bottom: -110rpx;
|
|
left: 0;
|
|
right: 0;
|
|
width: 80rpx;
|
|
height: 80rpx;
|
|
line-height: 80rpx;
|
|
text-align: center;
|
|
margin: 0 auto;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style> |