赞
踩
- <template>
- <div class="container" ref="chatWrapper">
- <div class="header">
- <head-bar class="chat-head" :title="'Chat'" @goback="goBack"></head-bar>
- </div>
- <div class="content" ref="chatContainer">
- <lottieLoad v-if="loadingMore"></lottieLoad>
- <div
- class="chat-item"
- :id="`chat-item-${index}`"
- v-for="(item, index) in chatList"
- :key="index"
- :style="{ flexDirection: item.sender === 'me' ? 'row-reverse' : 'row' }"
- >
- <div class="message-avatar">
- <img v-if="item.from_client_avatar" :src="item.from_client_avatar" />
- <img v-else src="@/assets/images/userProfile.png" alt="" />
- </div>
- <div class="message-content">
- <div
- class="message-header"
- :style="{ textAlign: item.sender === 'me' ? 'right' : 'left' }"
- >
- <span class="message-username">{{ item.from_client_name }}</span>
- <span class="message-time">
- {{ item.time ? item.time.split(' ')[1].substr(0, 5) : '' }}
- </span>
- </div>
- <div
- class="message-text"
- v-html="item.content"
- v-if="!item.is_img"
- :style="{ float: item.sender === 'me' ? 'right' : 'left' }"
- ></div>
- <div
- v-else
- class="message-text massage-img"
- @click="showMoreImg(item, index)"
- :style="{ float: item.sender === 'me' ? 'right' : 'left' }"
- >
- <!-- <van-image :src="item.content">
- @click="showMoreImg(item, index)"
- <template v-slot:loading>
- <van-loading type="spinner" size="20" />
- </template>
- </van-image> -->
-
- <img v-if="item.content" :src="item.content" alt="" />
- </div>
- </div>
- </div>
- <div class="toBottom" @click="scrollToBottom1" v-if="newMessageNum > 0">
- <img src="@/assets/icons/new-message.png" alt="" />
- <span class="msg-num">{{ newMessageNum }}</span>
- <span>See the latest news</span>
- </div>
- </div>
- <!-- 聊天输入 -->
- <div class="footer">
- <van-row gutter="10">
- <van-col span="18">
- <div class="searchInput" ref="mesageInput">
- <V3Emoji
- size="small"
- :textAreaOption="{ placeholder: 'Your Message' }"
- :custom-theme="customTheme"
- :textArea="true"
- ref="emoji"
- v-model="chatContent"
- class="emoji"
- >
- <img
- class="publicIcon photoImg"
- src="@/assets/icons/photoAlbum.png"
- alt=""
- @click.stop="handlePhoto"
- />
- <img class="publicIcon" src="@/assets/icons/emote.png" alt="" />
- </V3Emoji>
- </div>
- </van-col>
- <van-col span="6">
- <van-button
- style="height: 50px"
- round
- block
- type="primary"
- native-type="submit"
- @click="sendMessage"
- >
- Send
- <!-- {{ t('ey.send') }} -->
- </van-button>
- </van-col>
- </van-row>
-
- <!-- 图片 -->
- <input
- type="file"
- ref="fileInput"
- style="display: none"
- accept="image/*"
- @change="handleFileChange"
- />
- </div>
-
- </div>
- </template>
js 部分。
- <script lang="ts">
- import {
- defineComponent,
- ref,
- onMounted,
- toRefs,
- nextTick,
- watch,
- onUpdated,
- computed,
- } from 'vue'
- import { useRouter } from 'vue-router'
- import { useStore } from 'vuex'
- import headBar from '@/components/model/headModel.vue'
- import V3Emoji from 'vue3-emoji'
- import 'vue3-emoji/dist/style.css'
- import websocket from '@/utils/websocket'
- import { getChatRoomBind, uploadImg, getHistoryRecord } from '@/api/chatApi'
- import { useI18n } from 'vue-i18n'
- import lottieLoad from '@/components/model/lottieLoad.vue'
- import $okToast from '@/views/mobile/compontents/tipsDialog'
- import { load } from '@/views/mobile/compontents/lodding/index'
- export default defineComponent({
- name: 'userInfo',
- components: {
- headBar,
- friendRequest,
- V3Emoji,
- lottieLoad,
- },
- setup() {
- const { t } = useI18n()
- const store = useStore()
- const router = useRouter()
- const chatList: any = ref([])
- const isLogin = ref<boolean>(false)
- let ws
- const state = reactive({
- friendShow: false,
- })
- watch(
- () => store.state.chat.client_id,
- (id) => {
- client_id.value = id
- }
- )
- const loadingMore = ref(false)
- onMounted(async () => {
- await getHistory()
- const element: any = chatContainer.value
- if (element) {
- chatList.value.unshift(...sliceHistoryList.value.reverse())
- await nextTick()
- if (chatContainer.value) {
- scrollToBottom()
- }
-
- element.addEventListener('scroll', (e) => {
- let scrollH = element.scrollHeight
- let clientH = element.clientHeight
- let scrollT = element.scrollTop
- if (clientH + scrollT >= scrollH) {
- newMessageNum.value = 0
- }
- if (scrollT <= 0) {
- if (
- sliceNum.value <= historyList.value.length &&
- !loadingMore.value
- ) {
- sliceNum.value += 10
- }
- loadingMore.value = true
- if (sliceHistoryList.value.length > 0) {
- setTimeout(async () => {
- chatList.value.unshift(...sliceHistoryList.value.reverse())
- await nextTick()
- let firstEle = element.querySelector(
- `#chat-item-${sliceHistoryList.value.length}`
- )
- element.scrollTo({
- top: firstEle.offsetTop - 42,
- behavior: 'instant',
- })
- loadingMore.value = false
- }, 1000)
- } else {
- setTimeout(() => {
- loadingMore.value = false
- // message.warning('no more')
- }, 500)
- }
- }
- })
- }
- })
- const sliceNum = ref(0)
- const newMessageNum = ref(0)
- const client_id = ref()
- const isBind = ref(false)
-
- const chatContainer = ref()
- //监听信息
- const handleMessage = () => {
- ws.onMessage((data) => {
- let msg = JSON.parse(data)
- if (msg.type == 'connect') {
- client_id.value = msg.client_id
- // store.commit('setClientId', msg.client_id)
- if (isLogin.value) {
- getChatRoomBind(client_id.value).then((res: any) => {
- if (res.code == 200) {
- isBind.value = true
- }
- })
- }
- } else if (msg.type == 'login') {
- $okToast.show({
- type: 'warn',
- content: 'please login',
- textAlign: 'left',
- })
- } else {
- if (msg.from_user_id == store.getters.userInfo.user_id) {
- msg.sender = 'me'
- chatList.value.push(msg)
- newMessageNum.value = 0
- scrollToBottom()
- } else {
- msg.sender = 'other'
- chatList.value.push(msg)
- const element: any = chatContainer.value
- nextTick(() => {
- if (element) {
- const lastElement = element.querySelector(
- '.chat-item:last-child'
- )
- let observe = new IntersectionObserver((entries) => {
- //观察该el是否进入可视区域
- if (entries[0].isIntersecting) {
- newMessageNum.value = 0
- scrollToBottom()
- } else {
- newMessageNum.value += 1
- }
- //停止观察
- observe.unobserve(lastElement)
- })
- //开始观察
- observe.observe(lastElement)
- }
- })
- }
- }
- })
- }
-
- watch(
- () => store.getters.token,
- (token) => {
- if (token.access_token) {
- isLogin.value = true
- ws = new websocket('websocket地址')
- handleMessage()
- } else {
- isLogin.value = false
- ws && ws.close(0)
- }
- },
- {
- immediate: true,
- }
- )
- watch(
- () => store.state.dailyDate.chatStatus,
- (flag) => {
- if (flag) {
- scrollToBottom()
- }
- }
- )
- // onUpdated(() => {
- // if (newMessageNum.value == 0) {
- // scrollToBottom()
- // }
- // })
- //滚动到底部
- const scrollToBottom = () => {
- const element: any = chatContainer.value
- const lastElement = element?.querySelector('.chat-item:last-child')
- if (lastElement) {
- setTimeout(() => {
- newMessageNum.value = 0
- lastElement.scrollIntoView({ behavior: 'smooth' })
- }, 0)
- }
- }
- const handleLink = () => {
- router.push({ name: 'Demo' })
- }
-
- let socket: any = null
-
- const handleCancle = () => {
- state.friendShow = false
- }
-
- // 确认
- const handleSubmit = () => {
- state.friendShow = false
- }
- // e, isImg = false
- const sendMessage = () => {
- if (!isLogin.value) {
- $okToast.show({
- type: 'warn',
- content: 'please login',
- textAlign: 'left',
- })
- return
- }
- if (!isBind.value) {
- $okToast.show({
- type: 'warn',
- content: 'please reload',
- textAlign: 'left',
- })
- return
- }
- let msg = {
- chat: 'group',
- type: 'say',
- content: chatContent.value,
- is_img: false,
- }
- ws.send(msg)
- chatContent.value = ''
- }
-
- //获取历史信息
- const historyList = ref<any[]>([])
- const getHistory = () => {
- let params = {
- limit: 100,
- }
- let ajax = getHistoryRecord(params).then((res) => {
- if (res.code == 200) {
- res.data.forEach((item) => {
- if (item.from_user_id == store.getters.userInfo.user_id) {
- item.sender = 'me'
- }
- })
- historyList.value = res.data
- }
- })
- return ajax
- }
-
- const sliceHistoryList = computed(() => {
- return historyList.value.slice(sliceNum.value, sliceNum.value + 10)
- })
- //
- const customTheme = {
- 'V3Emoji-hoverColor': '#303237',
- 'V3Emoji-activeColor': '#303237',
- 'V3Emoji-shadowColor': 'none',
- 'V3Emoji-backgroundColor': '#222428',
- 'V3Emoji-fontColor': '#ffffff',
- }
- const chatContent = ref('')
- const fileInput = ref<HTMLInputElement | null>(null)
- const handlePhoto = () => {
- if (fileInput.value) {
- fileInput.value.click()
- }
- }
-
- // 相册图片发送
- const handleFileChange = () => {
- if (fileInput.value?.files) {
- // 处理文件
- const file = fileInput.value.files[0]
- if (file.size > 5 * 1024 * 1024) {
- $okToast.show({
- type: 'warn',
- content: 'The picture is too large',
- textAlign: 'left',
- })
- return
- }
- const formData = new FormData()
- formData.append('file', file)
- load.show('Image uploading in progress...')
- uploadImg(formData).then((res) => {
- if (res.code == 200) {
- let url = res.data
- const charContent = {
- chat: 'group',
- type: 'say',
- content: url,
- is_img: true,
- }
- if (fileInput.value) fileInput.value.value = ''
- ws.send(charContent)
- load.hide()
- } else {
- load.hide()
- }
- })
- // 发送formData到服务器 loadingDirective
- }
- }
- const goBack = () => {
- router.go(-1)
- }
- const scrollToBottom1 = () => {
- if (chatContainer.value) {
- chatContainer.value.scrollTop = chatContainer.value.scrollHeight
- }
- }
- const images = ref()
- const imgShow = ref(false)
- const imgIndex = ref(0)
- const showMoreImg = (e, index) => {
- imgShow.value = true
- const newImages: any = []
- chatList.value.map((item, indet) => {
- if (item.is_img) {
- newImages.push({
- img: item.content,
- indexs: indet,
- })
- }
- })
- images.value = newImages
-
- imgIndex.value = index
- const abs = images.value.findIndex(function (element) {
- return element.indexs === index
- })
- imgIndex.value = abs
- }
-
- return {
- ...toRefs(state),
- handleFileChange,
- fileInput,
- handlePhoto,
- customTheme,
- chatContent,
- handleLink,
- socket,
- t,
- chatList,
- handleCancle,
- sendMessage,
- scrollToBottom,
- newMessageNum,
- sliceHistoryList,
- chatContainer,
- loadingMore,
- goBack,
- scrollToBottom1,
- images,
- imgIndex,
- handleSubmit,
- showMoreImg,
- imgShow,
- }
- },
- })
- </script>
3.websocket.ts
- import store from '@/store'
- import { getChatRoomBind } from '@/api/chatApi'
-
-
- let heartBeat, //心跳得定时器
- serverHeartBeat, //服务器响应的定时器
- beat_time = 50000, //心跳时间间隔
- reconnectTimer,
- reconnectNum = 3, //重连次数
- reconnectFlag = true, //控制重连,一次一次来
- beat_data = {
- chat:"ping",
- }
- let client_id
- let isAccident = 1
- export default class WebSocketClient {
- private ws: WebSocket | any;
- private url;
- private message_func;
- constructor(url: string) {
- this.url = url
- this.initWebSocket(url)
-
- }
- initWebSocket(url) {
- this.ws = new WebSocket(url);
- this.ws.onopen = () => {
- //重连之后需要再绑定
- // if (reconnectNum < 3 && store.getters.token.access_token) {
- // getChatRoomBind(store.state.chat.client_id)
- // }
- // 开始心跳
- console.log('链接成功');
-
- this.heartBeat(1)
- reconnectTimer && clearTimeout(reconnectTimer)
- };
-
- this.ws.onmessage = (event) => {
-
- let msg = JSON.parse(event.data)
- if (msg.chat === 'pong') {
- console.log('正常');
- serverHeartBeat && clearTimeout(serverHeartBeat)
- }
- if (msg.type == 'connect' && reconnectNum < 3) {
- client_id = msg.client_id
- store.commit('setClientId', msg.client_id)
- if (reconnectNum < 3 && store.getters.token.access_token) {
- getChatRoomBind(client_id)
- reconnectNum = 3
- }
- }
- if (msg.chat !== 'pong') {
- if (this.message_func) {
- this.onMessage(this.message_func)
- }
- }
-
- }
-
- this.ws.onclose = () => {
- console.log('断线,onclose');
-
- if (store.getters.token.access_token && isAccident == 1) {
- this.reconnect()
- }
- this.heartBeat(2)
- };
-
- this.ws.onerror = (error: Event) => {
- if (store.getters.token.access_token) {
- this.reconnect()
- }
- this.heartBeat(2)
-
- };
-
- }
-
- public send(data: any) {
-
- if (this.ws.readyState === WebSocket.OPEN) {
- this.ws.send(JSON.stringify(data));
- } else {
- console.error('WebSocket未连接');
- }
- }
-
- public close(accident = 1) {
- console.log('断线,close');
- this.heartBeat(2)
- this.ws.close();
- isAccident = accident
- //是否是主动断开的,1意外,0主动
- if (accident == 1) {
- this.reconnect()
- }
-
- }
-
-
-
-
- // 监听消息
- onMessage(callback: (data: any) => void): void {
- this.ws.onmessage = event => {
-
- this.message_func = callback
- //忽略心跳返回信息
- let msg = JSON.parse(event.data)
- if (msg.chat !== 'pong') {
- callback(event.data);
- }
- if (msg.chat === 'pong') {
- serverHeartBeat && clearTimeout(serverHeartBeat)
- }
- }
-
-
-
-
-
- }
- onOpen(callback: (data:any) => void): void{
- this.ws?.addEventListener('open', (data) => {
-
- callback(data)
- })
- }
-
-
- heartBeat(opa = 1) {
- // 是否开启心跳
- if (opa == 1) {
- heartBeat = setInterval(() => {
- if (this.ws.readyState === WebSocket.OPEN) {
- console.log('心跳');
- this.send(beat_data)
- serverHeartBeat = setTimeout(() => {
- //3秒内没收到消息,断开重连
- this.close()
- clearInterval(heartBeat)
- }, 3000);
- }
- //
- }, beat_time)
-
- } else {
- heartBeat && clearInterval(heartBeat)
- serverHeartBeat && clearTimeout(serverHeartBeat)
- }
- }
-
- reconnect() {
- heartBeat && clearInterval(heartBeat)
- serverHeartBeat && clearTimeout(serverHeartBeat)
- if(!reconnectFlag) return
- if (reconnectNum > 0 && reconnectFlag) {
- reconnectTimer = setTimeout(() => {
- console.log('重连',reconnectNum);
-
- this.initWebSocket(this.url)
- reconnectNum -= 1
- reconnectFlag = true
- }, 5000);
- reconnectFlag = false
- } else {
- console.error('websocket error');
- }
- }
-
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。