最近需要做一个聊天框展示效果,消息里面还存在消息集合,不用怀疑肯定是要递归实现,正好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')
}
]
网友评论