简易即时通讯聊天

浏览:1302 发布日期:2020/06/11 分类:技术分享 关键字: Vue Socket websocket
原谅css技术很菜,写不出很好看的展示界面,
完整项目Git地址 https://gitee.com/f_longer/vue-app
项目体验地址https://www.fanglonger.com/

<template>
    <!---chat message-->
    <el-dialog :title="chatTitle" @close="chatVisible = false" top="3vh" width="80%"
               :center="center" :show-close="closeModel"
               :close-on-press-escape="closeModel"
               :close-on-click-modal="closeModel"
               :visible.sync="chatVisible">
        <el-row :gutter="24">
            <el-col :span="6" class="user-list">
                <div class="aside">
                    <el-menu background-color="#393d49" text-color="#fff" active-text-color="#ffd04b">
                        <el-menu-item @click="sendUser(user,index)" v-for="(user,index) in client_list" :key="index" :index="index.toString()">
                            <el-avatar :size="50" :src="user.client_img" style="cursor: pointer"/>
                            <span slot="title" style="margin-left: 20px" v-html="user.client_name"/>
                            <!--未读消息数-->
                            <el-badge v-if="user.total" type="danger" :value="user.total" style="top: 10px;right: 15px"/>
                            <!--在线-->
                            <el-badge v-else-if="user.online" type="success" is-dot style="top: 12px;right: 10px"/>
                            <!--离线-->
                            <el-badge v-else-if="!user.online" type="info" is-dot style="top: 12px;right: 10px"/>
                        </el-menu-item>
                    </el-menu>
                </div>
            </el-col>
            <el-col :offset="1" :span="17">
                <el-card shadow="always">
                    <div id="msg">
                        <div v-for="(message,index) in messageLists" :key="index">
                            <div class="msg-img">
                                <el-avatar :size="50" :src="message.avatar_url" style="cursor: pointer"/>
                                <i>{{message.from_client_name}}   {{message.time}}</i>
                            </div>
                            <div class="msg-list" v-html="unescape(message.content)"></div>
                        </div>
                    </div>
                    <div class="input-msg">
                        <emotion @clickEmotion="getEmotion" v-show="showEmotion" :height="300"/>
                        <div>
                            <el-tooltip effect="dark" content="房间名称" placement="top-start">
                                <el-menu :default-active="room_id" mode="horizontal" style="margin-bottom: 10px;border: none">
                                    <el-menu-item @click="setRoomID(room)" v-for="(room,index) in oauthConfig" :key="index" :index="room.id.toString()">
                                        {{room.value}}
                                    </el-menu-item>
                                </el-menu>
                            </el-tooltip>
                            <el-tooltip effect="dark" content="发送表情" placement="top-start">
                                <i @click="showEmotion = !showEmotion" class="el-icon-picture-outline-round icon"/>
                            </el-tooltip>
                            <el-upload :action="cgi.uploadUrl"
                                       :data="fileData"
                                       :headers="headers"
                                       :show-file-list="false"
                                       :on-success="uploadSuccess"
                                       :before-upload="beforeUpload" style="float: left">
                                <el-tooltip effect="dark" content="发送文件和图片" placement="top-start">
                                    <i class="el-icon-picture-outline icon"/>
                                </el-tooltip>
                            </el-upload>
                        </div>
                        <div contentEditable="true" ref="message" id="content" @focus="showEmotion = false" @keydown="setMsg">

                        </div>
                    </div>
                    <div class="input-button" style="text-align: right">
                        <el-tooltip effect="dark" content="Shift + Enter 快捷发送" placement="top-start">
                            <el-button type="primary" round plain size="medium" @click="sendMsg">发 送</el-button>
                        </el-tooltip>
                    </div>
                </el-card>
            </el-col>
        </el-row>
    </el-dialog>
    <!---chat message-->
</template>

<script>
    import { mapGetters,mapActions } from 'vuex'
    import apiLists from '../../api/api'
    import $url from '../../api/url'
    import func from '../../api/func'
    import emotion from '../common/emotion/Index'
    export default {
        name: "chatDemo",
        data(){
            return {
                //workerManChat
                cgi:{
                    uploadUrl:process.env.API_ROOT+$url.fileUpload
                },
                chatTitle:'隨心所欲,隨性而行',
                fileData:{},
                headers:{},
                chatVisible:true,
                client_list:[],
                inputMsg:'',
                emotionLists:[],
                showEmotion:false,
                closeModel:false,
                center:true,
                messageLists:[],
                msg_type:'text',
                to_client_name:'all',
                to_client_id:'all',
                chatMsgClass:'el-icon-chat-dot-round',
                msg_dot:false,
                room_id:'1200',
                noticeArr:[],
                uid:'',
            }
        },
        components:{
            emotion
        },
        computed:{
            ...mapGetters(['userInfo','oauthConfig']),
        },
        methods:{
            ...mapActions(['getOauthConfig']),
            /**
             * TODO:字符串标签转换
             * @param html
             */
            unescape:function (html) {
                return html
                    .replace(html ? /&(?!#?\w+;)/g : /&/g, '&')
                    .replace(/</g, "<")
                    .replace(/>/g, ">")
                    .replace(/"/g, "\"")
                    .replace(/ /g," ")
                    .replace(/'/g, "\'");
            },
            /**
             * TODO:workerMan-chat链接
             * @param ws
             */
            connect:function(ws){
                let __this = this;
                // 连接建立时发送登录信息
                ws.onopen = function(){
                    let str = {
                        type:'login',
                        client_name:__this.userInfo.username,
                        room_id:__this.room_id,
                        client_img:__this.userInfo.avatar_url,
                        uid:__this.userInfo.uuid,
                    };
                    ws.send(JSON.stringify(str))
                };
                ws.onmessage = function(response){
                    let data = JSON.parse(response.data);
                    switch (data['type']) {
                        //心跳
                        case 'ping':
                            ws.send('{"type":"pong"}');
                            break;
                        //登陆
                        case 'login':
                            let unreadArr = [];
                            __this.client_list = data.client_list;
                            //展示单个用户未读消息数
                            for (let i in __this.client_list) {
                                if (__this.userInfo.username === __this.client_list[i]['client_name']) {
                                    unreadArr = __this.client_list[i].unread;
                                }
                            }
                            if (unreadArr.length>0) {
                                for (let i in unreadArr) {
                                    for (let j in __this.client_list) {
                                        if (unreadArr[i]['form'] === __this.client_list[j]['client_name']) {
                                            __this.client_list[j]['total'] = unreadArr[i]['total'];
                                        }
                                    }
                                }
                            }
                            break;
                        //发送消息
                        case 'say':
                            __this.say(data);
                            break;
                        //聊天记录
                        case 'history':
                            __this.messageLists = data.message;
                            console.log(__this.messageLists);
                            break;
                        case 'logout':
                            console.log(data);
                            break;
                    }
                };
                ws.onclose = function() {
                    console.log("连接关闭,定时重连");
                    __this.connect(ws);
                };
                ws.onerror = function() {
                    console.log("出现错误");
                };
            },
            /**
             * TODO:弹出框展示
             */
            getMsgDialog:function(){
                this.chatVisible = !this.chatVisible;
                if (!this.chatVisible) {
                    this.chatMsgClass = 'el-icon-chat-dot-round';
                } else {
                    this.chatMsgClass = 'el-icon-close';
                    //获取聊天记录
                    let str = {
                        type:'history',
                        from_client_name:this.userInfo.username,
                        to_client_name:this.to_client_name,
                        room_id:this.to_client_name === 'all' ? this.room_id : '',
                        uid:this.userInfo.uuid,
                    };
                    this.userInfo.websocketServer.send(JSON.stringify(str));
                    this.getOauthConfig('RoomLists');
                    this.scrollToBottom();
                    this.msg_dot = false;
                }
            },
            /**
             * TODO:设置群聊房间号
             * @param room
             */
            setRoomID:function(room) {
                this.showEmotion = false;
                this.room_id = room.id.toString();
                this.chatTitle = room.value;
                //加入房间
                let login = {
                    type:'login',
                    client_name:this.userInfo.username,
                    room_id:this.room_id,
                    client_img:this.userInfo.avatar_url,
                    uid:this.userInfo.uuid,
                };
                this.userInfo.websocketServer.send(JSON.stringify(login))
                //获取聊天记录
                let str = {
                    type:'history',
                    from_client_name:this.userInfo.username,
                    to_client_name:'all',
                    room_id:this.room_id,
                    uid:this.userInfo.uuid
                };
                this.userInfo.websocketServer.send(JSON.stringify(str));
                this.scrollToBottom();
            },
            /**
             * TODO:设置发送给谁
             * @param user
             * @param client_id
             */
            sendUser:function(user,client_id) {
                this.to_client_name = user.client_name;
                user.total = 0;
                this.to_client_id = client_id === '0' ? 'all' : client_id;
                this.uid = this.to_client_id;
                this.chatTitle = user.client_name === 'all' ? 'ChatRoom' : user.client_name;
                this.avatar_url = this.userInfo.avatar_url;
                //获取聊天记录
                let str = {
                    type:'history',
                    from_client_name:this.userInfo.username,
                    to_client_name:this.to_client_name,
                    room_id:'',
                    uid:this.uid
                };
                this.userInfo.websocketServer.send(JSON.stringify(str));
                this.scrollToBottom();
            },
            /**
             * TODO:获取发送内容
             */
            setMsg:function(){
                this.inputMsg = this.$refs.message.innerHTML;
            },
            /**
             * TODO:发送表情
             * @param emotion
             */
            getEmotion (emotion) {
                this.msg_type = 'text';
                this.inputMsg+= "<img src='"+emotion.icon+"' width='50px' height='50px' alt='"+emotion.emoji+"' title='"+emotion.title+"'>"
                this.$refs.message.innerHTML = this.inputMsg;
            },
            /**
             * TODO:图片上传成功
             * @param response
             */
            uploadSuccess:function(response){
                if (response && response.code === 200){
                    switch (this.msg_type) {
                        case 'img':
                            this.inputMsg+= "<img src='"+response.item.src+"' width='100px' height='100px' alt=''>";
                            break;
                        case 'video':
                            this.inputMsg+= "<video src='"+response.item.src+"' width='200px' height='200px' controls='controls'>";
                            break;
                    }
                    this.$refs.message.innerHTML = this.inputMsg;
                    return ;
                }
                this.$message({type:'warning',message:response.msg});
            },
            /**
             * TODO:图片上传前
             * @param file
             */
            beforeUpload:function(file){
                let ext = file.name.split('.')[1];
                switch (ext.toLocaleLowerCase()) {
                    case 'jpg':
                    case 'gif':
                    case 'png':
                    case 'jpeg':
                        this.msg_type = 'img';
                        if (file.size>2*1024*1024){
                            this.$message({type:'warning',message:'upload image size error'});
                        }
                        break;
                    case 'mp4':
                        this.msg_type = 'video';
                        if (file.size>5*1024*1024){
                            this.$message({type:'warning',message:'upload video size error'});
                        }
                        break;
                    default:
                        this.$message({type:'warning',message:'Unsupported file format'});
                        break;
                }
            },
            /**
             * TODO:发送消息
             * @param data from_client_id from_client_name content time msg_type to_client_name to_client_id
             */
            say:function(data){
                let str = {
                    type:'history',
                    from_client_name:data['from_client_name'],
                    to_client_name:data['to_client_name'],
                    room_id:data['room_id'],
                    uid:data['uid'],
                };
                //获取历史记录信息
                this.userInfo.websocketServer.send(JSON.stringify(str));
                this.messageLists.push(data);
                if (this.username!==data['from_client_name']){
                    if (data['to_client_id']!=='all') {
                        this.chatTitle = data['from_client_name'];
                        this.to_client_name = data['from_client_name'];
                        this.to_client_id = data['from_client_id'];
                    }
                    this.msg_dot = true;
                }
                if (this.chatVisible && this.username===data['from_client_name']) {
                    if (data['to_client_id']!=='all') {
                        this.chatTitle = data['to_client_name'];
                        this.to_client_name = data['to_client_name'];
                        this.to_client_id = data['to_client_id'];
                    }
                    this.scrollToBottom();
                }
            },
            /**
             * TODO:消息发送
             */
            sendMsg:function(){
                this.showEmotion = false;
                this.inputMsg= this.$refs.message !== undefined ? this.$refs.message.innerHTML : '';
                if (this.inputMsg.trim() !== '') {
                    //发送消息
                    let str = {
                        type:'say',
                        to_client_id:this.to_client_id,
                        to_client_name:this.to_client_name,
                        from_client_name:this.userInfo.username,
                        msg_type:this.msg_type,
                        content:this.inputMsg,
                        avatar_url:this.userInfo.avatar_url,
                        room_id:this.to_client_name === 'all' ? this.room_id : '',
                        uid:this.uid,
                    };
                    this.userInfo.websocketServer.send(JSON.stringify(str));
                    //获取历史记录信息
                    let history = {
                        type:'history',
                        from_client_name:this.userInfo.username,
                        to_client_name:this.to_client_name,
                        room_id:this.to_client_name === 'all' ? this.room_id : '',
                        uid:this.uid,
                    };
                    this.userInfo.websocketServer.send(JSON.stringify(history));
                    this.messageLists.push(str);
                    this.scrollToBottom();
                    this.$refs.message.innerHTML = '';
                    this.inputMsg = '';
                    return ;
                }
                if (this.chatVisible) {
                    this.$refs.message.focus();
                    this.$message({type:'info',message:'Please enter message'})
                }
            },
            /**
             * TODO:滚动条滚动到底部
             */
            scrollToBottom: function () {
                this.$nextTick(() => {
                    let div = document.getElementById('msg');
                    try {
                        div.scrollTop = div.scrollHeight
                    } catch (e) {
                        this.$message.error(JSON.stringify(e));
                    }
                })
            }
        },
        /**
         * todo:生命周期
         */
        created(){
            //图片上传参数
            this.avatar_url = this.avatarUrl;
            this.fileData.token = this.token;
            this.fileData.rand = false;
            this.headers.Authorization = `${func.set_password(func.set_random(32),func.set_random(12))}${this.token}${func.set_password(func.set_random(32),func.set_random(12))}`
            //客服系统初始化
            this.connect(this.userInfo.websocketServer);
            this.getOauthConfig('RoomLists');
            let __this = this;
            //键盘事件
            document.onkeydown = function (e) {
                if (e.code === 'Enter' && e.shiftKey) {
                    e.preventDefault();
                    __this.sendMsg();
                    return false;
                }
            }
        },
        /**
         * todo:生命周期
         */
        watch:{
            chatVisible:function () {
                if (!this.chatVisible) {
                    this.chatMsgClass = 'el-icon-chat-dot-round';
                }
            },
            inputMsg:function () {
                this.inputMsg = this.$refs.message.innerHTML;
            }
        },
    }
</script>
<style scoped>
    .el-main{
        margin-bottom: 60px;
    }
    .user-list{
        box-shadow: 0 2px 12px #ffffff, 0 0 6px #F5F5F5;
        min-height:715px;
        background-color:#ffffff;
        border-radius:10px;
        -moz-border-radius:10px;
        -webkit-border-radius:10px;
        max-height: 715px;
        overflow: hidden;
        overflow-y: auto;
        padding: 0 !important;
    }
    .user-list .aside {
        background-color:#393d49;
        border-radius:10px;
        -moz-border-radius:10px;
        -webkit-border-radius:10px;
    }
    .contact-list{
        box-shadow: 0 1px 0 #ffffff, 0 1px 2px #545c64;
        min-height:600px;
        border: 1px solid #F5F5F5;
        border-radius:10px;
        -moz-border-radius:10px;
        -webkit-border-radius:10px;
    }
    #msg{
        max-height: 380px;
        min-height: 380px;
        margin:10px 0 20px 0;
        overflow: hidden;
        overflow-y: auto;
        border: 1px solid #eee;
        border-radius: 10px;
        -moz-border-radius:10px;
        -webkit-border-radius:10px;
    }
    #msg .msg-list{
        box-shadow: 0 1px 0 #ffffff, 0 1px 2px #505860;
        line-height: 28px;
        min-height: 20px;
        margin: 0 15px 15px 15px;
        font-family: "Source Code Pro", monospace;
        font-size: 15px;
        padding:15px;
        border-radius: 10px;
        -moz-border-radius:10px;
        -webkit-border-radius:10px;
        background:-webkit-gradient(linear, left top, left bottom, from(#eaeaea), to(#ffffff));
         background:-moz-linear-gradient(top, #eaeaea, #ffffff);
            background:-o-linear-gradient(top, #eaeaea, #ffffff);
            background:linear-gradient(top, #eaeaea, #ffffff);
        position:relative;
    }
    #msg .msg-list:after {
        content:'';
        position:absolute;
        bottom:100%;
        left:15px;
        width:0;
        height:0;
        border-width:10px;
        border-style:solid;
        border-color:transparent;
        margin-bottom:-4px;
        border-bottom-width:16px;
        border-bottom-color:currentColor;
        color:#eaeaea;
    }
    #msg .msg-img{
        margin: 15px;
    }
    #content{
        border: 1px solid #eee;
        height: 95px;
        border-radius: 10px;
        padding:10px;
        overflow: scroll;
        overflow-x: hidden;
    }
    .input-msg{
        min-height: 100px;
        margin-bottom: 10px;
    }
    .input-msg .icon{
        font-size: 25px;
        margin:0 10px 10px 0;
        color: #000;
        cursor: pointer;
    }
    .msg-icon{
        font-size: 50px;
        color:#ffffff;
        position:fixed;
        right:10px;
        bottom:10%;
        width: 80px;
        height: 80px;
        background: #0066ff;
        border-radius: 40px;
        -moz-border-radius:40px;
        -webkit-border-radius:40px;
        z-index: 20004;
    }
    .msg-count{
        position:absolute;
        right:9px;
        bottom:54%;
    }
</style>

附件 QQ截图20200612151237.png ( 105.57 KB 下载:8 次 )

最佳答案
评论( 相关
后面还有条评论,点击查看>>