美文网首页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