Vue Cli 3項(xiàng)目使用融云IM實(shí)現(xiàn)聊天功能的方法
介紹:前臺(tái)使用vue開(kāi)發(fā)的單頁(yè)面,后臺(tái)使用ant design pro單頁(yè)面,實(shí)現(xiàn)手機(jī)端和后臺(tái)聊天功能。
效果如圖(PC+移動(dòng)):
一、申請(qǐng)融云賬號(hào)(token、appKey)
建議先看教程:sdk使用介紹
過(guò)一遍教程,接下來(lái)開(kāi)始寫(xiě)
二、引入融云IM
如圖:
位置:public/index.html,引入
<script src="https://cdn.ronghub.com/RongIMLib-2.3.5.min.js"></script>
三、可以正常使用RongIMLib其自帶方法了
app.vue 不是全代碼(因?yàn)橹皇沁B接)
created () { //生命周期函數(shù)-可發(fā)起求 let that = this //融云初始化 RongIMLib.RongIMClient.init('4z3hrv4ovrt'); //------------------------------重要填寫(xiě)appkey that.beforeIm() //設(shè)置監(jiān)聽(tīng),必須先設(shè)置監(jiān)聽(tīng),再連接 that.nowIm() //連接融云 }, methods: { ...mapMutations({ //讀取最新消息列表,并設(shè)置----------------------------重要 getAnswer:'getAnswer' }), beforeIm(){ let that = this // 連接狀態(tài)監(jiān)聽(tīng)器 RongIMClient.setConnectionStatusListener({ onChanged: function (status) { // status 標(biāo)識(shí)當(dāng)前連接狀態(tài) switch (status) { case RongIMLib.ConnectionStatus.CONNECTED: console.log('鏈接成功'); break; case RongIMLib.ConnectionStatus.CONNECTING: console.log('正在鏈接'); break; case RongIMLib.ConnectionStatus.DISCONNECTED: console.log('斷開(kāi)連接'); break; case RongIMLib.ConnectionStatus.KICKED_OFFLINE_BY_OTHER_CLIENT: console.log('其他設(shè)備登錄'); break; case RongIMLib.ConnectionStatus.DOMAIN_INCORRECT: console.log('域名不正確'); break; case RongIMLib.ConnectionStatus.NETWORK_UNAVAILABLE: console.log('網(wǎng)絡(luò)不可用'); break; } } }); // 消息監(jiān)聽(tīng)器 RongIMClient.setOnReceiveMessageListener({ // 接收到的消息 onReceived: function (message) { // 判斷消息類型 switch(message.messageType){ case RongIMClient.MessageType.TextMessage: // message.content.content => 文字內(nèi)容 //----------------------------重要-------把獲取的消息存放在store中,全局公用homeIm.vue要使用 console.log('8080',message,message.content.content) that.getAnswer(message.content) break; case RongIMClient.MessageType.VoiceMessage: // message.content.content => 格式為 AMR 的音頻 base64 break; case RongIMClient.MessageType.ImageMessage: // message.content.content => 圖片縮略圖 base64 // message.content.imageUri => 原圖 URL break; case RongIMClient.MessageType.LocationMessage: // message.content.latiude => 緯度 // message.content.longitude => 經(jīng)度 // message.content.content => 位置圖片 base64 break; case RongIMClient.MessageType.RichContentMessage: // message.content.content => 文本消息內(nèi)容 // message.content.imageUri => 圖片 base64 // message.content.url => 原圖 URL break; case RongIMClient.MessageType.InformationNotificationMessage: // do something break; case RongIMClient.MessageType.ContactNotificationMessage: // do something break; case RongIMClient.MessageType.ProfileNotificationMessage: // do something break; case RongIMClient.MessageType.CommandNotificationMessage: // do something break; case RongIMClient.MessageType.CommandMessage: // do something break; case RongIMClient.MessageType.UnknownMessage: // do something break; default: // do something } } }); }, nowIm(){ //自己的token------從接口獲取,寫(xiě)到緩存 var token = JSON.parse(localStorage.getItem('userInfo')).IMUser.token//"WzrthC5f4UfuiI7dIwCQ5fwtGfqCdobpowIZkcQnj8PQOQuAJb/nIi1ayzGFwJguvbQZxbJH3x0="; RongIMClient.connect(token, { onSuccess: function(userId) { console.log('Connect successfully. ' + userId); }, onTokenIncorrect: function() { console.log('token 無(wú)效'); }, onError: function(errorCode){ var info = ''; switch (errorCode) { case RongIMLib.ErrorCode.TIMEOUT: info = '超時(shí)'; break; case RongIMLib.ConnectionState.UNACCEPTABLE_PAROTOCOL_VERSION: info = '不可接受的協(xié)議版本'; break; case RongIMLib.ConnectionState.IDENTIFIER_REJECTED: info = 'appkey不正確'; break; case RongIMLib.ConnectionState.SERVER_UNAVAILABLE: info = '服務(wù)器不可用'; break; } console.log(info); } }); } },
main.js 代碼
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import './assets/style.css' // 外部static樣式 ------重要 import './assets/js/rem.js' //rem適配 ------重要 import my from './assets/js/lbc.js' //------不重要 ---自定義全局方法 import HomeNews from './components/HomeNews.vue' //自定義組件 ------重要 Vue.component('HomeNews',HomeNews) Vue.prototype.$my=my //------不重要 ---自定義全局方法 使用 this.$my.xxx Vue.config.productionTip = false new Vue({ router, store, render: h => h(App) }).$mount('#app')
store.js 全代碼
import Vue from 'vue' import Vuex from 'vuex' import axios from 'axios' const API_PROXY = 'https://bird.ioliu.cn/v1/?url='; //代理 Vue.use(Vuex) export default new Vuex.Store({ state: { answer:[] }, getters: { }, mutations: { getAnswer (state, playload) {//--------------重要 let say ={ //自定義消息組件所需參數(shù) type:1, css:'left', txt:playload.content, date:'', headImg:playload.extra } state.answer.push(say) console.log(playload) }, }, actions: { } })
homeIm.vue
//一如以往,不廢話,直接代碼 <template> <div class="homeIm" id='homeIm'> //----------------------------------------------------重要-------------------自定義消息組件,下面會(huì)貼碼 <home-news v-for="(item ,index) in answer" :key='index' :item='item' :data='item'></home-news> <div class="posFix bottom0 left0 flex justsa alic w100 bgf " style="min-height:.6rem;"> <img src="../assets/images/mike.png" class="mike pl10 pr10"/> <van-field v-model="say" placeholder="請(qǐng)輸入" class="flex1 border borRad" /> <van-button size="large " @click="send" type='info' class="button borRad ml10 mr10" :disabled ='say?false:true'>確定</van-button> </div> </div> </template> <script> import Vue from "vue"; import { Field ,Button } from "vant"; import router from "../router.js"; import axios from "axios"; import {mapState,mapGetters,mapActions,mapMutations} from 'vuex' const arr = [ Field ,Button]; arr.map(e => { //動(dòng)態(tài)生成組件 Vue.use(e); }); export default { data() { return { say:'小仙女,你好鴨' }; }, name: "homeIm", props: { msg: String }, created() { //this.getChatRecord() //獲取聊天記錄,要錢(qián) this.$nextTick(() => {//------------------------重要-------有消息就滾動(dòng)到底部----------------------- let list = document.getElementById('homeIm') document.documentElement.scrollTop = list.scrollHeight //如不行,請(qǐng)嘗試-> list.scrollTop = list.scrollHeight }) }, computed:{ ...mapState({ answer:"answer", }), }, watch: { //------------------------重要-------有消息就滾動(dòng)到底部----------------------- answer() { this.$nextTick(() => { let list = document.getElementById('homeIm') document.documentElement.scrollTop = list.scrollHeight //如不行,請(qǐng)嘗試-> list.scrollTop = list.scrollHeight }) } }, methods: { send() { let that = this let msg = new RongIMLib.TextMessage({ content: that.say, extra: 'https://img.52z.com/upload/news/image/20171120/20171120080335_21404.jpg' }); let conversationType = RongIMLib.ConversationType.PRIVATE; // 單聊, 其他會(huì)話選擇相應(yīng)的消息類型即可 let targetId = JSON.parse(localStorage.getItem('userInfo')).IMUser.assistantId; // 目標(biāo) Id RongIMClient.getInstance().sendMessage(conversationType, targetId, msg, { onSuccess: function (message) { // message 為發(fā)送的消息對(duì)象并且包含服務(wù)器返回的消息唯一 Id 和發(fā)送消息時(shí)間戳 console.log('Send successfully',message,message.content.content); let say = { type:1, css:'right', txt:message.content.content, headImg:'https://img.52z.com/upload/news/image/20171120/20171120080335_21404.jpg' } that.answer.push(say) that.say = '' }, onError: function (errorCode, message) { let info = ''; switch (errorCode) { case RongIMLib.ErrorCode.TIMEOUT: info = '超時(shí)'; break; case RongIMLib.ErrorCode.UNKNOWN: info = '未知錯(cuò)誤'; break; case RongIMLib.ErrorCode.REJECTED_BY_BLACKLIST: info = '在黑名單中,無(wú)法向?qū)Ψ桨l(fā)送消息'; break; case RongIMLib.ErrorCode.NOT_IN_DISCUSSION: info = '不在討論組中'; break; case RongIMLib.ErrorCode.NOT_IN_GROUP: info = '不在群組中'; break; case RongIMLib.ErrorCode.NOT_IN_CHATROOM: info = '不在聊天室中'; break; } console.log('發(fā)送失敗: ' + info + errorCode); } }); }, getChatRecord(){ let conversationType = RongIMLib.ConversationType.PRIVATE; //單聊, 其他會(huì)話選擇相應(yīng)的消息類型即可 let targetId = '2'; // 想獲取自己和誰(shuí)的歷史消息,targetId 賦值為對(duì)方的 Id let timestrap = null; // 默認(rèn)傳 null,若從頭開(kāi)始獲取歷史消息,請(qǐng)賦值為 0, timestrap = 0; let count = 20; // 每次獲取的歷史消息條數(shù),范圍 0-20 條,可以多次獲取 RongIMLib.RongIMClient.getInstance().getHistoryMessages(conversationType, targetId, timestrap, count, { onSuccess: function(list, hasMsg) { // list => Message 數(shù)組。 // hasMsg => 是否還有歷史消息可以獲取。 console.log('歷史紀(jì)錄',list, hasMsg) }, onError: function(error) { console.log('GetHistoryMessages, errorcode:' + error); } }); } }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .mike{ width: .3rem;height: .3rem; } .border{border:1px solid #ccc;} .button{ width: .8rem;height: .46rem; } .homeIm{padding-bottom: .7rem;} </style>
homeNews.vue 全代碼
<template> <div class="homeNews"> <!-- 1:文字,2:紅包,3:文章 ,css:類型 --> <div v-if="data.type == 1&&data.css == 'left'"> <div class="colora1 fz12 lh40 pt10">{{data.date}}</div> <div class="flex pl20 pr20 borBox"> <img :src="data.headImg" class="head borRad"> <div class="frame borRad bgf lh24 fz16 tl color3 pl10 pr10 pt10 pb10 borBox w250 ml15 posRel"> {{data.txt}} </div> </div> </div> <router-link to="/homeRedBag" v-else-if="data.type == 2&&data.css == 'left'"> <div class="colora1 fz12 lh40 pt10">{{data.date}}</div> <div class="flex pl20 pr20 borBox"> <img :src="data.headImg" class="head borRad"> <div class="frame borRad bgf9 lh24 fz16 tl colorF w200 ml15 posRel redFrame"> <div class="flex alic pl10 pr10 pt10 pb10 borBox"> <img src="../assets/images/redTabs.png" class="redTabs"/> <span class="pl10">{{data.title}}</span> </div> <div class="fz12 color6 bgf pl15 borBox txt">{{data.txt}}</div> </div> </div> </router-link> <router-link to="/homeArticle" v-else-if="data.type == 3&&data.css == 'left'"> <div class="colora1 fz12 lh40 pt10">{{data.date}}</div> <div class="flex pl20 pr20 borBox"> <img :src="data.headImg" class="head borRad"> <div class="frame borRad bgf fz16 tl color3 pl10 pr10 pt10 pb10 borBox w250 ml15 posRel"> <div class="fz20 txt2 mb10 lh30">{{data.title}}</div> <div class="flex justsa alic"> <div class="colora1 txt2 flex1 lh20">{{data.txt}}</div> <img :src="data.banner" class="banner"/> </div> </div> </div> </router-link> <div v-if="data.type == 1&&data.css == 'right'"> <div class="colora1 fz12 lh40 pt10">{{data.date}}</div> <div class="flex pl20 pr20 borBox juste"> <div class="frame-right borRad bgf lh24 fz16 tl color3 pl10 pr10 pt10 pb10 borBox w250 mr15 posRel"> {{data.txt}} </div> <img :src="data.headImg" class="head borRad"> </div> </div> </div> </template> <script> import Vue from "vue"; import router from "../router.js"; export default { name: "homeNews", props:['data'], data() { return { }; }, created() { console.log() }, methods: { } }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .head { width: 0.36rem; height: 0.36rem; } .frame::before { display: block; content: ''; width: 0px; /* 寬高設(shè)置為0,很重要,否則達(dá)不到效果 */ height: 0px; border: .10rem solid #fff; border-bottom-color: transparent; /* 設(shè)置透明背景色 */ border-top-color: transparent; border-left-color: transparent; position: absolute;left:-.2rem;top:.1rem; } .frame-right::before{ display: block; content: ''; width: 0px; /* 寬高設(shè)置為0,很重要,否則達(dá)不到效果 */ height: 0px; border: .10rem solid #fff; border-bottom-color: transparent; /* 設(shè)置透明背景色 */ border-top-color: transparent; border-right-color: transparent; position: absolute;right:-.2rem;top:.1rem; } .redFrame::before{ display: block; content: ''; width: 0px; /* 寬高設(shè)置為0,很重要,否則達(dá)不到效果 */ height: 0px; border: .10rem solid #F99F3E; border-bottom-color: transparent; /* 設(shè)置透明背景色 */ border-top-color: transparent; border-left-color: transparent; position: absolute;left:-.2rem;top:.1rem; } .redFrame-right::before{ display: block; content: ''; width: 0px; /* 寬高設(shè)置為0,很重要,否則達(dá)不到效果 */ height: 0px; border: .10rem solid #F99F3E; border-bottom-color: transparent; /* 設(shè)置透明背景色 */ border-top-color: transparent; border-left-color: transparent; position: absolute;right:-.2rem;top:.1rem; } .w250{max-width: 2.5rem;} .w200{max-width: 2rem;} .redTabs{ width: .32rem; height:.39rem; } .txt{ border-bottom-left-radius: .05rem; border-bottom-right-radius: .05rem; } .banner{width: .4rem;height: .4rem;} </style> style.css 我自己的樣式表 @charset "utf-8"; /* CSS Document 劉白超修改于2019/3/10*/ html,body{height: 100%;width: 100%;word-wrap:break-word;} *{margin: 0;padding: 0;} .tc{text-align: center} .tr{text-align: right} .tl{text-align: left} .vm{vertical-align: middle;} .vs{vertical-align: sub;} .fl{float: left;} .fr{float: right;} .fz24{font-size: .24rem;} .fz20{font-size: .2rem;} .fz18{font-size: .18rem;} .fz16{font-size: .16rem;} .fz14{font-size: .14rem;} .fz12{font-size: .12rem;} .fw{font-weight: 600;} .mr5{margin-right: .05rem} .mr10{margin-right: .10rem} .mr15{margin-right: .15rem} .mr20{margin-right: .20rem} .ml5{margin-left:.05rem;} .ml10{margin-left:.10rem;} .ml15{margin-left:.15rem;} .ml20{margin-left:.20rem;} .ml24{margin-left:.24rem;} .mt40{margin-top:.40rem;} .mt20{margin-top: .20rem;} .mt15{margin-top: .15rem;} .mt10{margin-top: .10rem;} .mt5{margin-top: .05rem;} .mb5{margin-bottom: .05rem;} .mb10{margin-bottom: .10rem;} .mb15{margin-bottom: .15rem;} .mb20{margin-bottom: .20rem;} .pt5{padding-top: .05rem;} .pt10{padding-top: .10rem;} .pt15{padding-top: .15rem;} .pt20{padding-top: .20rem;} .pt30{padding-top: .30rem;} .pb5{padding-bottom: .05rem;} .pb10{padding-bottom: .10rem;} .pb15{padding-bottom: .15rem;} .pb20{padding-bottom: .20rem;} .pl5{padding-left: .05rem;} .pl10{padding-left: .10rem;} .pl15{padding-left: .15rem;} .pl20{padding-left: .20rem;} .pl30{padding-left: .30rem;} .pr5{padding-right: .05rem;} .pr10{padding-right: .10rem;} .pr15{padding-right: .15rem;} .pr20{padding-right: .20rem;} .pr30{padding-right: .30rem;} .bgef{background: #EFEFEF;} .bgf{background: #fff;} .bgf9{background: #F99F3E} .ee {background: #eee;} .bg259{background:#259DFF !important} .colordd{color: #DD4D41} .colorf9 {color: #ff9800;} .colore5{color: #e51c23;} .colorF{color: #fff;} .color3{color: #333;} .color6{color: #666;} .color9{color: #999;} .colora1{color:#a1a1a1} .color259{color:#259DFF} .color005{color:#00559B} .colorf3{color:#F3665E} .lh24{line-height: .24rem} .lh20{line-height: .20rem;} .lh30{line-height: .30rem;} .lh40{line-height: .40rem;} .lh50{line-height: .50rem;} .lh60{line-height: .60rem;} .hide{display: none} .show{display: block} .inline{display: inline-block;} .indent2{text-indent: 2em;} .txt2{ overflow : hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .wn{white-space:nowrap;} .flex{display: flex;} .flex1{flex:1;} .colu{flex-direction: column;} .justc{justify-content: center;} .justs{justify-content: space-between}/*兩端對(duì)齊*/ .justsa{justify-content: space-around}/*分散對(duì)齊*/ .juste{justify-content: flex-end;} .alic{align-items: center} .wrap{flex-wrap:wrap} .childEnd{align-self:flex-end;} .posAbs{position: absolute;} .posRel{position: relative;} .posFix{position: fixed;} .top0{top:0;} .bottom0{bottom:0;} .left0{left:0;} .right0{right: 0;} .w100{width: 100%} .h100{height: 100%} .borBox{box-sizing: border-box;} .borderte0{border-top:1px solid #e0e0e0; } .borderbe0{border-bottom:1px solid #e0e0e0; } .borRad{border-radius:.05rem;} .over{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;} .overH{overflow: hidden} .clear{zoom:1;} .clear:after{content: "\0020";display: block;height: 0;clear: both;} .mask{width: 100%;height: 100%;background: rgba(20, 20, 20, 0.5);position: fixed;z-index: 5;top: 0;left: 0;} .cursor{cursor: pointer;} .noClick{pointer-events: none;} li{list-style:none;} a{text-decoration:none;color:#555;} a:hover{color:#555;} img{display:block;vertical-align:middle;} a img,fieldset{border:0;} i,em{font-style:normal} b,strong,th{font-weight:100;} input,textarea,select{outline:none;} textarea{resize:none;} table{border-collapse:collapse;} .btn{ width: calc(100% - .54rem); background: #259DFF; font: .2rem/.5rem ""; text-align: center; color: #fff; border-radius: 5px; margin-left: .27rem; } rem.js rem自適應(yīng)單位 //例如設(shè)計(jì)稿為375,最大寬度為750,則為(375,750) !function(e,t){function n(){var n=l.getBoundingClientRect().width;t=t||540,n>t&&(n=t);var i=100*n/e;r.innerHTML="html{font-size:"+i+"px;}"}var i,d=document,o=window,l=d.documentElement,r=document.createElement("style");if(l.firstElementChild)l.firstElementChild.appendChild(r);else{var a=d.createElement("div");a.appendChild(r),d.write(a.innerHTML),a=null}n(),o.addEventListener("resize",function(){clearTimeout(i),i=setTimeout(n,300)},!1),o.addEventListener("pageshow",function(e){e.persisted&&(clearTimeout(i),i=setTimeout(n,300))},!1),"complete"===d.readyState?d.body.style.fontSize="16px":d.addEventListener("DOMContentLoaded",function(e){d.body.style.fontSize="16px"},!1)}(375,640);
完了,okk,文中所需icon,請(qǐng)自行到阿里icon下載
結(jié)尾:項(xiàng)目中需要配置rem。
總結(jié)
以上所述是小編給大家介紹的Vue Cli 3項(xiàng)目使用融云IM實(shí)現(xiàn)聊天功能的方法,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺(jué)得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!
- Vue+express+Socket實(shí)現(xiàn)聊天功能
- Vue實(shí)現(xiàn)聊天界面
- vue實(shí)現(xiàn)web在線聊天功能
- vue+web端仿微信網(wǎng)頁(yè)版聊天室功能
- Vue.js仿微信聊天窗口展示組件功能
- vue + socket.io實(shí)現(xiàn)一個(gè)簡(jiǎn)易聊天室示例代碼
- 基于Vue2實(shí)現(xiàn)的仿手機(jī)QQ單頁(yè)面應(yīng)用功能(接入聊天機(jī)器人 )
- vue實(shí)現(xiàn)的微信機(jī)器人聊天功能案例【附源碼下載】
- 基于vue和websocket的多人在線聊天室
- Vue+ssh框架實(shí)現(xiàn)在線聊天
相關(guān)文章
vue獲取元素寬、高、距離左邊距離,右,上距離等還有XY坐標(biāo)軸的方法
今天小編就為大家分享一篇vue獲取元素寬、高、距離左邊距離,右,上距離等還有XY坐標(biāo)軸的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09Vue中實(shí)現(xiàn)回車(chē)鍵切換焦點(diǎn)的方法
這篇文章主要介紹了在Vue中實(shí)現(xiàn)回車(chē)鍵切換焦點(diǎn)的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02Vue使用vue-cli創(chuàng)建項(xiàng)目
這篇文章主要介紹了Vue使用vue-cli創(chuàng)建項(xiàng)目,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09Delete `,` 如何解決(vue eslint與prettier沖突)
這篇文章主要介紹了Delete `,` 如何解決(vue eslint與prettier沖突)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10el-select單選時(shí)選擇后輸入框的is-focus狀態(tài)并沒(méi)有取消問(wèn)題解決
這篇文章主要給大家介紹了關(guān)于el-select單選時(shí)選擇后輸入框的is-focus狀態(tài)并沒(méi)有取消問(wèn)題的解決過(guò)程,文中通過(guò)圖文以及代碼示例將解決的辦法介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01Vue-CLI 3 scp2自動(dòng)部署項(xiàng)目至服務(wù)器的方法
這篇文章主要介紹了Vue-CLI 3 scp2自動(dòng)部署項(xiàng)目至服務(wù)器的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07