美文网首页1024Vue.jsWeb前端之路
47、pc端类似微信聊天框案例

47、pc端类似微信聊天框案例

作者: 圆梦人生 | 来源:发表于2023-11-12 16:51 被阅读0次

    最近需要做一个聊天框展示效果,消息里面还存在消息集合,不用怀疑肯定是要递归实现,正好vue中也有组件递归的使用,翻阅了之前写过的组件递归案例vue递归组件案例

    • ps: 图片就不提供了,按照对应扩展名手动添加

    效果图

    image.png

    案例

    demo.vue

      <!--
        @author: SM
        @desc: 微信聊天记录案例
    -->
    <template>
      <div style="background-color: white;display:inline-block">
        <chatrecordList :title="chatItem.msg.title" :item="chatItem.msg && chatItem.msg.item"/>
      </div>
    </template>
    
    <script>
    
    // 导入聊天组件
    import ChatrecordList from './chatrecordList'
    
    export default {
        components: {
            ChatrecordList
        },
        data () {
            return {
                // 模拟数据
                chatItem: {
                    msg:{
                        title: 'xx和xx聊天记录',
                        item: [{
            "type": "ChatRecordText",
            "msgtime": 1699862191694,
            "content": {
                "content": "在吗?需要参加一个需求会议。"
            }
        },
        {
            "type": "ChatRecordFile",
            "msgtime": 1699862191694,
            "content": {
                "filename": "从入门到精通.zip",
                "filesize": "101010101",
                "fileext": "zip"
            }
        },
        {
            "type": "ChatRecordLink",
            "msgtime": 1699862191694,
            "content": {
                "title": "这是分享链接的标题",
                "image_url": "https://fc1tn.baidu.com/it/u=3658245154,2773193846&fm=202&mola=new&crop=v1",
                "description": "链接描述"
            }
        },
        
        {
            "type": "chatrecord",
            "msgtime": 1699862191694,
            "content": {
                "title": "张三和李四聊天内容",
                "item": [{
                        "type": "ChatRecordText",
                        "msgtime": 1699862191694,
                        "content": {
                            "content": "在吗?需要参加一个需求会议。"
                        }
                    },
                    {
                        "type": "ChatRecordFile",
                        "msgtime": 1699862191694,
                        "content": {
                            "filename": "2从入门到精通.zip",
                            "filesize": "101010101",
                            "fileext": "zip"
                        }
                    },
                    {
                        "type": "ChatRecordLink",
                        "msgtime": 1699862191694,
                        "content": {
                            "title": "2这是分享链接的标题",
                            "image_url": "https://fc1tn.baidu.com/it/u=3658245154,2773193846&fm=202&mola=new&crop=v1",
                            "description": "链接描述"
                        },
                        "fileext": "zip"
                    },
                    {
                        "type": "ChatRecordLocation",
                        "msgtime": 1699862191694,
                        "content": {
                            "address": "2经纬度",
                            "longitude": "116.374373",
                            "latitude": "39.91582"
                        }
                    }
                ]
            }
        },
    
        {
            "type": "ChatRecordLocation",
            "msgtime": 1699862191694,
            "content": {
                "address": "经纬度",
                "longitude": "116.374373",
                "latitude": "39.91582"
            }
        }
    ]
                    }
                }
            }
        }
    }
    
    </script>
    <style lang='scss' scoped>
    </style>
    

    chatrecordList.vue 递归组件

    <!--
        @author: SM
        @desc: 微信聊天记录递归组件
    -->
    <template>
      <div class="x-chatrecordlist-cls">
        <!-- {{item}} -->
        <div class="chart-box cursor-pointer" @click="showChart = true">
            <div class="chart-title">{{title}}</div>
            <div class="chart-content">
                <div class="list-item" v-for="(citem, index) in item" v-if="index < 3">
                    <span>
                        {{citem.type == 'ChatRecordText' ? (citem[chatRecordType[citem.type]].content && citem[chatRecordType[citem.type]].content.length > 18 ? citem[chatRecordType[citem.type]].content.substring(0, 18) : citem[chatRecordType[citem.type]].content) : '['+chatRecordType[citem.type]+']'}}
                    </span>
                </div>
            </div>
            <div class="chart-footer" v-if="item.length > 1">聊天记录</div>
        </div>
        <!-- 弹出详细聊天内容  :modal="modal"  :modal="false" -->
        <el-dialog
         class="x-chatrecordlist-dialog-cls"
          :title="title"
          :visible.sync="showChart"
          :append-to-body="true"
          width="800px">
          <el-scrollbar style="height: 600px">
            <div class="x-chatrecordlist-info-cls" v-for="(citem, index) in item">
                <div> {{dayjs(citem.msgtime).format('YYYY-MM-DD HH:hh:mm')}} </div>
                <!-- 内容区域 -->
                <div class="info-content">
                    <!-- 文本 -->
                    <div v-if="citem.type == 'ChatRecordText'">{{citem.content && citem.content.content}}</div>
                    <div v-else-if="citem.type == 'ChatRecordFile'">
                        <!-- <el-tooltip class="item" effect="light" content="点击可下载" placement="top"> -->
                            <!-- @click="downLoad(citem.content)" -->
                            <div class="newfile"  >
                                <img :src="formateFileType(citem.content, 'file')" alt="" />
                                <div class="leftdescri">
                                <div class="name">{{citem.content &&  citem.content.filename}}</div>
                                <div class="size">{{citem.content &&  citem.content.filesize | threeDecimal}}</div>
                                </div>
                            </div>
                        <!-- </el-tooltip> -->
                    </div>
                    <div v-else-if="citem.type == 'ChatRecordImage'">[图片]</div>
                    <div v-else-if="citem.type == 'ChatRecordVideo'">[视频]</div>
                    <div v-else-if="citem.type == 'ChatRecordLink'">
                        <div class="msgtypecard cursor-pointer">
                            <div @click="goPage(citem.content)">
                            <div class="card_name">
                                <div class="title">{{ citem.content &&  citem.content.title }}</div>
                            </div>
                            <div class="middle">
                                <img
                                :src="
                                    citem.content &&  citem.content.image_url == ''
                                    ? defaultLinkPic
                                    : citem.content &&  citem.content.image_url
                                "
                                alt=""
                                style="width: 48px; height: 48px; margin-right: 5px;padding-bottom: 5px;"
                                />
                                <div class="desp">{{ citem.content &&  citem.content.description }}</div>
                            </div>
                            </div>
                        </div>
                    </div>
                    <div v-else-if="citem.type == 'ChatRecordLocation'">
                       <div style="width:400px;height:200px;position: relative; padding-bottom: 14px;"  @click="addmapImg(citem)">
                        <div class="mapadress" style="padding-bottom: 5px;">{{citem.content &&  citem.content.address}}</div>
                        <el-amap :center='[citem.content &&  citem.content.longitude, citem.content &&  citem.content.latitude]' zoom='15' >
                            <el-amap-marker vid="marker" :position="[citem.content &&  citem.content.longitude, citem.content &&  citem.content.latitude]" :label='{ content: citem.content &&  citem.content.title, offset:[-10,-23]}'></el-amap-marker>
                        </el-amap>
                        </div>
                    </div>
                    <div v-else-if="citem.type == 'chatrecord'">
                        <chatrecordList :title="citem.content && citem.content.title" :item="citem.content.item"/>
                    </div>
                    <div v-else>{{defaultMsg}}</div>
                </div>
            </div>
          </el-scrollbar>
        </el-dialog>
        <!-- 弹出详细聊天内容 -->
    
        <!-- map 预览 -->
        <div class="shabowbox" v-if="mapImg" :style="{'z-index':  getPopupZIndex() + 1}">
            <div class="close" @click="mapImg = false">
                <i class="el-icon-circle-close"></i>
            </div>
            <div class="shabowboxvidoe">
                <el-amap :center='[mpaItem.longitude, mpaItem.latitude]' zoom='15' >
                    <el-amap-marker vid="marker" :position='[mpaItem.longitude, mpaItem.latitude]' :label='{ content: mpaItem.title, offset:[-10,-23]}'></el-amap-marker>
                </el-amap>
            </div>
        </div>
        <!-- map 预览 -->
    
      </div>
    </template>
    
    <script>
    // z-index问题
    import { PopupManager } from 'element-ui/lib/utils/popup';
    // 文件类型判断
    import { loadFileType } from "./fileType.js";
    // 日期工具类
    import dayjs from 'dayjs'
    //
    export default {
        name: 'chatrecordList',
        data () {
            return {
                // 文件类型
                loadFileType,
                // 下载文件路径
                action: process.env.VUE_APP_BASE_STATIC_PATH,
                mpaItem: {},
                // 预览地图
                mapImg:false,
                // 默认图片占位符
                defaultLinkPic: require("@/assets/file_icon/link1.png"),
                defaultMsg: '[该消息类型暂不能展示]',
                dayjs,
                // 展示聊天内容
                showChart: false,
                // 定义显示的聊天类别 v-if="citem.type == 'ChatRecordText'"
                chatRecordType: {
                    ChatRecordText: 'content',
                    ChatRecordFile: '文件',
                    ChatRecordImage: '图片',
                    ChatRecordVideo: '视频',
                    ChatRecordLink: '链接',
                    ChatRecordLocation: '位置',
                    ChatRecordMixed: '聊天记录',
                    // 这个类别里面还有子集
                    chatrecord: '聊天记录'
                }
            }
        },
        // 外部透传的值
        props: {
            // 数组
            item: Array,
            // 标题
            title: String,
            // 弹出详情是否需要遮照
            modal: {
                type: Boolean,
                default: true
            }
        },
        filters:{
            threeDecimal: function(value){
                value-=0 // 类型转换
                let min=1024*8 // 1KB
                let max=1024*1024*8 // 1MB
                if((value>max)){
                    return (value/max).toFixed(3)+'MB'
                }else if(value>min){
                    return (value/min).toFixed(3)+'KB'
                }else{
                    return (value/8).toFixed(3)+'B'
                }
            }
        },
        methods: {
             formateFileType(item, type) {
                // item = JSON.parse(item);
                if (type == "file") {
                    let obj = this.loadFileType.find((citem) => {
                        return citem.extension == item.fileext;
                    });
    
                    if (obj) {
                        return obj.icon;
                    } else {
                        return require("@/assets/file_icon/unknow.png");
                    }
                }
            },
            // 下载文件
            downLoad(item) {
                item = JSON.parse(item);
                let url = this.action + item.attachment
                const link = document.createElement("a");
                link.href = url;
                link.setAttribute("download", item.filename); // 下载文件的名称及文件类型后缀
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link); // 下载完成移除元素
                window.URL.revokeObjectURL(url); // 释放掉blob对象
            },
            // z-index
            getPopupZIndex(){
                return PopupManager.nextZIndex();
            },
            // 跳转链接
            goPage(chatItem) {
                chatItem = JSON.parse(chatItem);
                window.open(chatItem.link_url, "_blank");
            },
            // 地图
            addmapImg(item){
                this.mpaItem = JSON.parse(item.content) || {}
                this.mapImg = true
            },
        }
    }
    
    </script>
    <style lang='scss'>
        .x-chatrecordlist-cls {
            font-size: 14px;
            .chart-box {
                padding: 12px;
                border: 1px solid #ccc;
                border-radius: 6px;
                width: 260px;
                .chart-title {
                    color: #222222;
                }
            }
            .chart-content {
                padding-top: 4px;
                max-height: 120px;
                overflow: hidden;
                font-weight: 800;
                .list-item {
                    line-height: 22px;
                }
            }
            .chart-footer {
                border-top: 1px solid #ccc;
                margin-top: 8px;
                padding-top: 8px;
            }
            .chart-content, .chart-footer {
                font-size: 12px;
                color: #999999
            }
    
            // 弹出显示
              .shabowbox {
                position: fixed;
                width: 100%;
                height: 100%;
                background: rgba(0, 0, 0, 0.4);
                left: 0;
                top: 0;
                z-index: 99999999999999;
                .shabowboxvidoe {
                    position: fixed;
                    width: 800px;
                    height: 475px;
                    left: 50%;
                    margin-left: -400px;
                    top: 50%;
                    margin-top: -235px;
                    z-index: 2001;
                    background: #fff;
                }
            }
            // 弹出显示
    
              .close {
                position: fixed;
                width: 50px;
                height: 50px;
                right: 10px;
                z-index: 2012;
                top: 10px;
                text-align: center;
                line-height: 50px;
                font-size: 20px;
                color: #fff;
                cursor: pointer;
                font-size: 43px;
            }
        }
        .x-chatrecordlist-dialog-cls {
            .el-scrollbar__bar.is-vertical {
                display: none !important;
            }
            // 弹框里面的内容
            .x-chatrecordlist-info-cls {
                border-bottom: 1px solid #ccc;
                margin-bottom: 6px;
                .info-content {
                    color: #222222;
                    padding-top: 14px;
                    padding-bottom: 14px;
                }
                 /* 新文件 */
                .newfile{
                    display: flex;
                    width: 237px;
                    height: 68px;
                    border: 1px solid rgba(224,224,224,1);
                    border-radius: 4px;
                    padding: 12px;
                    img{
                    width: 40px;
                    height: 40px;
                    margin-right: 12px;
                    }
                    .leftdescri{
                    flex:1;
                    .name{
                        font-size: 14px;
                        color: #222222;
                        letter-spacing: 0;
                        font-weight: 400;
                        margin-bottom: 7px;
                        overflow: hidden;
                        text-overflow: ellipsis;
                        display: -webkit-box;
                        -webkit-line-clamp: 1;  /* 多行文本的行数  3 - 表示三行文本超出后在第三行后显示(...) */
                        -webkit-box-orient: vertical;
                    }
                    .size{
                        font-size: 12px;
                        color: #666666;
                        letter-spacing: 0;
                        font-weight: 400;
                    }
                    }
                }
                // 链接、卡片样式
                .msgtypecard {
                        width: 340px;
                        height: 112px;
                        margin: 10px;
                        margin-left: 5px;
                        font-size: 16px;
                        color: #222;
                        border-radius: 8px;
                        -webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2),
                        0 6px 8px 0 rgba(0, 0, 0, 0.19);
                        box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 8px 0 rgba(0, 0, 0, 0.19);
                        position: relative;
                        .card_name {
                        padding: 10px;
                        }
                        .title {
                        font-size: 16px;
                        color: #000;
                        overflow: hidden;
                        text-overflow: ellipsis;
                        display: -webkit-box;
                        -webkit-line-clamp: 2;
                        -webkit-box-orient: vertical;
                        display: -moz-box;
                        -moz-line-clamp: 2;
                        -moz-box-orient: vertical;
                        word-wrap: break-word;
                        word-break: break-all;
                        white-space: normal;
                        }
                        .desp {
                        font-size: 14px;
                        color: rgb(68, 67, 67);
                        float: left;
                        width: 100%;
                        height: 100%;
                        // padding-top: 10px;
                        .title {
                            width: 100%;
                            overflow: hidden;
                            text-overflow: ellipsis;
                            display: -webkit-box;
                            -webkit-line-clamp: 2;
                            -webkit-box-orient: vertical;
                            display: -moz-box;
                            -moz-line-clamp: 2;
                            -moz-box-orient: vertical;
                            word-wrap: break-word;
                            word-break: break-all;
                            white-space: normal;
                        }
                        }
                        .middle {
                        width: 100%;
                        padding-left: 10px;
                        .desp {
                            font-size: 14px;
                            color: rgb(68, 67, 67);
                            float: left;
                            width: 70%;
                            height: 100%;
                            // padding-bottom: 10px;
                            overflow: hidden;
                            text-overflow: ellipsis;
                            display: -webkit-box;
                            -webkit-line-clamp: 2;
                            -webkit-box-orient: vertical;
                            display: -moz-box;
                            -moz-line-clamp: 2;
                            -moz-box-orient: vertical;
                            word-wrap: break-word;
                            word-break: break-all;
                            white-space: normal;
                        }
                        img {
                            float: right;
                            width: 25%;
                            height: 100%;
                        }
                        }
                        .card_foot {
                        position: absolute;
                        height: 20px;
                        border-top: 1px solid #efefef;
                        text-align: left;
                        bottom: 15px;
                        padding: 10px;
                        color: #333;
                        font-weight: bold;
                        width: 100%;
                        .weapp {
                            font-family: PingFangSC-Regular;
                            font-size: 12px;
                            color: #999999;
                            letter-spacing: 0;
                            font-weight: 400;
                        }
                        }
                } // end 卡片、链接
            }
       }
    </style>
    

    fileType.vue 文件扩展名对应的图片

    //  扩展名和下载类型的对应关系
    export const loadFileType = [{
            extension: 'doc',
            icon: require('@/assets/file_icon/word_1.png')
        },
        {
            extension: 'docx',
            icon: require('@/assets/file_icon/word_1.png')
        },
        {
            extension: 'xls',
            icon: require('@/assets/file_icon/excel.png')
        },
        {
            extension: 'xlsx',
            icon: require('@/assets/file_icon/excel.png')
        },
        {
            extension: 'ppt',
            icon: require('@/assets/file_icon/PPT.png')
        },
        {
            extension: 'pptx',
            icon: require('@/assets/file_icon/PPT.png')
        },
        {
            extension: 'mp3',
            icon: require('@/assets/file_icon/yinyue.png')
        },
        {
            extension: 'mp4',
            icon: require('@/assets/file_icon/yinyue.png')
        },
        {
            extension: 'm4a',
            icon: require('@/assets/file_icon/yinyue.png')
        },
        {
            extension: 'pdf',
            icon: require('@/assets/file_icon/PDF_.png')
        },
        {
            extension: 'zip',
            icon: require('@/assets/file_icon/zip.png')
        },
        {
            extension: 'jpeg',
            icon: require('@/assets/file_icon/pic.png')
        },
        {
            extension: 'jpg',
            icon: require('@/assets/file_icon/pic.png')
        },
        {
            extension: 'png',
            icon: require('@/assets/file_icon/pic.png')
        },
        {
            extension: 'txt',
            icon: require('@/assets/file_icon/txt.png')
        }
    ]
    

    相关文章

      网友评论

        本文标题:47、pc端类似微信聊天框案例

        本文链接:https://www.haomeiwen.com/subject/svcrwdtx.html