123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544 |
- <template>
- <view class="chat">
- <view class="chat-content" @touchstart="hideDrawer">
- <scroll-view id="chat-content-srcoll" scroll-y="true" :scroll-with-animation="scrollAnimation"
- :scroll-top="scrollTop" :scroll-into-view="scrollToView" @scrolltoupper="loadHistory"
- upper-threshold="50">
- <!-- 加载历史数据waitingUI -->
- <view class="chat-content-srcoll-loading" v-if="isHistoryLoading">
- <view class="spinner">
- <view class="rect1"></view>
- <view class="rect2"></view>
- <view class="rect3"></view>
- <view class="rect4"></view>
- <view class="rect5"></view>
- </view>
- </view>
- <!-- 聊天 -->
- <view class="chat-content-srcoll-list">
- <view class="chat-content-srcoll-list-item" v-for="(item, index) in msgList" :key="index"
- :id="item.ID">
- <!-- 用户信息 -->
- <block>
- <!-- 自己发出的信息 -->
- <view class="my-info" v-if="item.flow === 'out'">
- <!-- 左-消息 -->
- <view class="my-info-left">
- <!-- 文字消息 -->
- <view class="">
- <view class="my-username">
- <view class="time">{{ timeFliter(item.time) }}</view>
- <view class="time">{{ item.nick }}</view>
- </view>
- <view v-if="item.type === TIM.TYPES.MSG_TEXT" class="bubble">
- <rich-text :nodes="nodesFliter(item.payload.text)"></rich-text>
- </view>
- <view class="bubble" v-else-if="item.type === TIM.TYPES.MSG_IMAGE">
- <u-image :src="item.payload.imageInfoArray[0].imageUrl"
- :width="item.payload.imageInfoArray[0].width > 400 ? 400 : item.payload.imageInfoArray[0].width"
- :height="item.payload.imageInfoArray[0].height > 400 ? 400 : item.payload.imageInfoArray[0].height"
- border-radius="10"
- @click="previewImage(item.payload.imageInfoArray[0].imageUrl)"></u-image>
- </view>
- <view v-else-if="item.type === TIM.TYPES.MSG_CUSTOM" class="bubble">
- <view>
- {{ item.flow === 'out' ? '发起' : '接收' }}{{ JSON.parse(JSON.parse(item.payload.data).data).call_type == 1 ? '语音通话' : '视频通话' }}
- </view>
- </view>
- </view>
- </view>
- <!-- 右-头像 -->
- <view class="my-info-right">
- <u-avatar :src="item.avatar" size="80" mode="square" bg-color="#fff"></u-avatar>
- </view>
- </view>
- <!-- 别人发出的消息 -->
- <view class="other-info" v-else>
- <!-- 左-头像 -->
- <view class="other-info-left">
- <u-avatar :src="item.avatar" size="80" mode="square" bg-color="#fff"></u-avatar>
- </view>
- <!-- 右-用户名称-时间-消息 -->
- <view class="other-info-right">
- <view class="username">
- <view class="name">{{ item.nick }}</view>
- <view class="time">{{ timeFliter(item.time) }}</view>
- </view>
- <!-- 文字消息 -->
- <view v-if="item.type === TIM.TYPES.MSG_TEXT" class="bubble">
- <rich-text :nodes="nodesFliter(item.payload.text)"></rich-text>
- </view>
- <view class="bubble" v-else-if="item.type === TIM.TYPES.MSG_IMAGE">
- <u-image :src="item.payload.imageInfoArray[0].imageUrl"
- :width="item.payload.imageInfoArray[0].width > 400 ? 400 : item.payload.imageInfoArray[0].width"
- :height="item.payload.imageInfoArray[0].height > 400 ? 400 : item.payload.imageInfoArray[0].height"
- border-radius="10"
- @click="previewImage(item.payload.imageInfoArray[0].imageUrl)"></u-image>
- </view>
- <view v-else-if="item.type === TIM.TYPES.MSG_CUSTOM" class="bubble">
- <view>
- {{ item.flow === 'out' ? '发起' : '接收' }}{{ JSON.parse(JSON.parse(item.payload.data).data).call_type == 1 ? '语音通话' : '视频通话' }}
- </view>
- </view>
- </view>
- </view>
- </block>
- </view>
- </view>
- </scroll-view>
- </view>
- <!-- 底部输入栏 -->
- <view class="input-box" :class="popupLayerClass" @touchmove.stop.prevent="discard">
- <view class="more" @tap="showMore">
- <view class="icon add"></view>
- </view>
- <view class="textbox">
- <view class="text-mode">
- <view class="box">
- <textarea auto-height="true" v-model="textMsg" @focus="textareaFocus" />
- </view>
- <view class="em" @tap="chooseEmoji">
- <view class="icon biaoqing"></view>
- </view>
- </view>
- </view>
- <view class="send" @tap="sendText">
- <view class="btn">发送</view>
- </view>
- </view>
- <!-- 抽屉栏 -->
- <view class="popup-layer" :class="popupLayerClass" @touchmove.stop.prevent="discard">
- <!-- 表情 -->
- <swiper class="emoji-swiper" :class="{ hidden: hideEmoji }" indicator-dots="true" duration="150">
- <swiper-item v-for="(page,pid) in emojiList" :key="pid">
- <view v-for="(em, eid) in page" :key="eid" @tap="addEmoji(em)">
- <image mode="widthFix" :src="'/static/img/emoji/'+ em.url"></image>
- </view>
- </swiper-item>
- </swiper>
- <!-- 更多功能 相册-拍照- -->
- <view class="more-layer" :class="{ hidden: hideMore }">
- <view class="list">
- <view class="box" @tap="chooseImage">
- <view class="icon tupian2"></view>
- </view>
- <view class="box" @tap="camera">
- <view class="icon paizhao"></view>
- </view>
- <!-- <view class="box" @tap="call(1)">
- <view class="iconfont icon-yuyin shipin"></view>
- </view>
- <view class="box" @tap="call(2)">
- <view class="iconfont icon-shipin shipin"></view>
- </view> -->
- </view>
- </view>
- </view>
- <!-- 语音聊天 -->
- <!-- <trtc-calling ref="trtcCalling"></trtc-calling> -->
- </view>
- </template>
- <script>
- // import TrtcCalling from 'components/trtc-calling/index.vue'
- import {
- mapState
- } from 'vuex';
- export default {
- components: {
- // TrtcCalling
- },
- data() {
- return {
- // 消息列表
- isHistoryLoading: false,
- scrollAnimation: false,
- scrollTop: 0,
- scrollToView: '',
- //TIM变量
- conversationActive: null,
- toUserId: '',
- toUserInfo: null,
- userInfo: null,
- msgList: [],
- nextReqMessageID: '',
- count: 15,
- isCompleted: '',
- TIM: null,
- // 抽屉参数
- popupLayerClass: '',
- isVoice: false,
- emojiList: this.$commen.emojiList,
- recording: false,
- textMsg: '',
- //表情定义
- hideEmoji: true,
- // more参数
- hideMore: true
- }
- },
- onLoad(page) {
- if (page.title) {
- uni.setNavigationBarTitle({
- title: page.title
- })
- }
- this.conversationActive = this.$store.state.conversationActive;
- this.toUserId = this.$store.state.toUserId;
- this.TIM = this.$TIM
- this.getMsgList();
- },
- onShow() {
- this.scrollTop = 9999999;
- this.isHistoryLoading = false;
- this.readMessage();
- // setTimeout(() => {
- // this.$refs.trtcCalling.initListener();
- // }, 300)
- },
- computed: {
- ...mapState({
- currentMessageList: state => state.currentMessageList
- })
- },
- watch: {
- currentMessageList(newVal, oldVal) {
- this.msgList = newVal
- this.screenMsg(newVal, oldVal)
- }
- },
- onUnload() {
- this.readMessage();
- },
- methods: {
- /**
- * 消息已读
- */
- readMessage() {
- // 退出页面 将所有的会话内的消息设置为已读
- console.log(this.conversationActive.conversationID)
- this.tim.setMessageRead({
- conversationID: this.conversationActive.conversationID
- }).then(function(imResponse) {
- console.log('全部已读')
- // 已读上报成功
- }).catch(function(imError) {
- // 已读上报失败
- console.warn('setMessageRead error:', imError);
- });
- },
- /**
- * 加载初始页面消息
- */
- getMsgList() {
- // 历史消息列表
- let conversationID = this.conversationActive.conversationID
- this.tim.getMessageList({
- conversationID: conversationID,
- count: this.count
- }).then((res) => {
- console.log(res.data)
- this.$store.commit('pushCurrentMessageList', res.data.messageList)
- this.nextReqMessageID = res.data.nextReqMessageID // 用于续拉,分页续拉时需传入该字段。
- this.isCompleted = res.data.isCompleted
- this.scrollToView = res.data.messageList[res.data.messageList.length - 1]?.ID
- }).catch(err => {
- console.log(err)
- // this.$u.route({
- // url: 'pages/login/login'
- // })
- })
- // 滚动到底部
- this.$nextTick(function() {
- //进入页面滚动到底部
- this.scrollTop = 9999;
- this.$nextTick(function() {
- this.scrollAnimation = true;
- });
- });
- },
- /**
- * 接受消息(定位消息)
- * @param { Object } newVal
- * @param { Object } oldVal
- */
- screenMsg(newVal, oldVal) {
- if (newVal[0] && oldVal[0]) {
- if (newVal[0].ID != oldVal[0].ID && newVal.length >= this.count) {
- let _index = newVal.length - oldVal.length - 1
- this.$nextTick(() => {
- this.scrollToView = newVal[_index].ID
- });
- // 拉取历史记录不用改变定位消息
- } else {
- //新的消息来了 自动向下滑动到最新消息
- this.$nextTick(() => {
- this.scrollToView = newVal[newVal.length - 1].ID
- });
- }
- } else {
- // 第一次拉取历史记录 定位到最后一条消息
- this.$nextTick(() => {
- this.scrollToView = newVal[newVal.length - 1].ID;
- this.scrollTop = 9999999;
- });
- }
- },
- /**
- * 时间过滤
- * @param {Object} timer
- */
- timeFliter(timer) {
- let timeData = new Date(timer * 1000)
- let str = this.$format.dateTimeFliter(timeData)
- return str
- },
- /**
- * 聊天的节点加上外层的
- * @param {String} str
- */
- nodesFliter(str) {
- let nodeStr = '<div style="align-items: center;word-wrap:break-word;">' + str + '</div>'
- return nodeStr
- },
- /**
- * 隐藏抽屉
- */
- hideDrawer() {
- this.popupLayerClass = '';
- setTimeout(() => {
- this.hideMore = true;
- this.hideEmoji = true;
- }, 150);
- },
- /**
- * 选择表情
- */
- chooseEmoji() {
- this.hideMore = true;
- if (this.hideEmoji) {
- this.hideEmoji = false;
- this.openDrawer();
- } else {
- this.hideDrawer();
- }
- },
- /**
- * 触发滑动到顶部(加载历史信息记录)
- * @param {Object} e
- */
- loadHistory(e) {
- this.isHistoryLoading = true;
- // 更多消息列表
- let conversationID = this.conversationActive.conversationID
- this.tim.getMessageList({
- conversationID: conversationID,
- nextReqMessageID: this.nextReqMessageID,
- count: this.count
- }).then((res) => {
- this.$store.commit('unshiftCurrentMessageList', res.data.messageList)
- this.nextReqMessageID = res.data.nextReqMessageID // 用于续拉,分页续拉时需传入该字段。
- this.isCompleted = res.data.isCompleted
- this.isHistoryLoading = false;
- });
- },
- /**
- * 获取焦点,如果不是选表情ing,则关闭抽屉
- */
- textareaFocus() {
- if (this.popupLayerClass == 'showLayer' && this.hideMore == false) {
- this.hideDrawer();
- }
- },
- /**
- * 发送消息
- */
- sendText() {
- this.hideDrawer(); //隐藏抽屉
- if (!this.textMsg) {
- return;
- }
- let content = this.replaceEmoji(this.textMsg);
- let msg = {
- text: content
- }
- this.sendMsg(msg, 'text');
- this.textMsg = ''; //清空输入框
- },
- /**
- * 发送消息
- * @param {Object} content
- * @param {Object} type
- */
- sendMsg(content, type) {
- let message;
- if (type === 'text') {
- message = this.tim.createTextMessage({
- to: this.toUserId,
- conversationType: 'C2C',
- payload: {
- text: content.text
- }
- });
- } else if (type === 'img') {
- message = this.tim.createImageMessage({
- to: this.toUserId,
- conversationType: 'C2C',
- payload: {
- file: content
- }
- })
- }
- this.$store.commit('pushCurrentMessageList', message)
- this.tim.sendMessage(message).then(res => {
- this.$nextTick(() => {
- // 滚动到底
- this.scrollToView = res.data.message.ID
- });
- })
- },
- //替换表情符号为图片
- replaceEmoji(str) {
- let replacedStr = str.replace(/\[([^(\]|\[)]*)\]/g, (item, index) => {
- for (let i = 0; i < this.emojiList.length; i++) {
- let row = this.emojiList[i];
- for (let j = 0; j < row.length; j++) {
- let EM = row[j];
- if (EM.alt == item) {
- //在线表情路径,图文混排必须使用网络路径,请上传一份表情到你的服务器后再替换此路径
- //比如你上传服务器后,你的100.gif路径为https://www.xxx.com/emoji/100.gif 则替换onlinePath填写为https://www.xxx.com/emoji/
- let onlinePath = 'http://veterhchat.hw.hongweisoft.com/static/img/emoji/'
- let imgstr = '<img src="' + onlinePath + EM.url + '">';
- return imgstr;
- }
- }
- }
- });
- return replacedStr;
- },
- /**
- * 更多功能(点击+弹出)
- */
- showMore() {
- this.hideEmoji = true;
- if (this.hideMore) {
- this.hideMore = false;
- this.openDrawer();
- } else {
- this.hideDrawer();
- }
- },
- discard() {
- return;
- },
- // 打开抽屉
- openDrawer() {
- this.popupLayerClass = 'showLayer';
- },
- /**
- * 隐藏抽屉
- */
- hideDrawer() {
- this.popupLayerClass = '';
- setTimeout(() => {
- this.hideMore = true;
- this.hideEmoji = true;
- }, 150);
- },
- /**
- * 添加表情
- * @param {Object} em
- */
- addEmoji(em) {
- this.textMsg += em.alt;
- },
- /**
- * 选择图片发送
- */
- chooseImage() {
- this.getImage('album');
- },
- /**
- * 拍照
- */
- camera() {
- this.getImage('camera');
- },
- /**
- * 通话
- */
- call(type) {
- this.trtcCalling.call({
- userID: this.toUserId,
- type: type,
- timeout: 300
- }).then((res) => {
- console.log('成功:', res)
- this.$refs.trtcCalling.openDialog()
- }).catch(err => {
- console.log('失败', err)
- uni.showToast({
- title: '调用通话失败',
- icon: 'none'
- })
- })
- },
- /**
- * 选照片 or 拍照
- * @param {Object} type
- */
- getImage(type) {
- this.hideDrawer();
- uni.chooseImage({
- sourceType: [type],
- sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
- success: (res) => {
- this.sendMsg(res.tempFiles[0], 'img')
- },
- fail: (err) => {
- uni.showToast({
- icon: 'none',
- title: '图片上传失败!'
- })
- }
- });
- },
- /**
- * 预览图片
- */
- previewImage(img) {
- uni.previewImage({
- urls: [img]
- });
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- @font-face {
- font-family: "iconfont";
- /* Project id 2965205 */
- src: url('//at.alicdn.com/t/font_2965205_e6crsnlz8jf.woff2?t=1637829206479') format('woff2'),
- url('//at.alicdn.com/t/font_2965205_e6crsnlz8jf.woff?t=1637829206479') format('woff'),
- url('//at.alicdn.com/t/font_2965205_e6crsnlz8jf.ttf?t=1637829206479') format('truetype');
- }
- .iconfont {
- font-family: "iconfont" !important;
- font-size: 16px;
- font-style: normal;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- }
- .icon-yuyin:before {
- content: "\e6e1";
- }
- .icon-shipin:before {
- content: "\e64f";
- }
- @import './chat.scss';
- </style>
|