🎬 江城开朗的豌豆:个人主页
🔥 个人专栏 :《 VUE 》 《 javaScript 》
📝 个人网站 :《 江城开朗的豌豆🫛 》
⛺️ 生活的理想,就是为了理想的生活 !
目录
2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:
3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:
⭐ 文章简介(效果图展示)
在现代社交互动中,聊天消息列表是应用程序中的关键组成部分。它不仅仅是一种通信工具,更是人们日常生活中连接感情、分享信息的重要方式之一。随着移动互联网的发展,用户在不同平台上(如App、H5、小程序等)进行聊天的需求也愈发增加。因此,设计并实现一个支持多平台、多种形式的聊天消息列表成为了开发者们的挑战之一。
📟 插件传送门:聊天消息列表
📘 文章背景
最近我专注于优化我们聊天消息列表的交互体验。现在,我们的消息列表页面上有多个标签,每个标签对应着不同的聊天会话。当用户点击某个标签时,页面会流畅地滚动到相应的聊天记录位置,这样用户就可以更方便地查看他们感兴趣的对话内容。
今天下午,我花了些时间在消息列表的交互功能上进行调整和改进。经过一番努力,我成功地实现了这一功能!在这个过程中,我逐步解决了各种技术挑战,体验着一个个问题被一一击破的成就感。这种改进用户体验的过程真是让人感到无比满足!
📘 平台兼容性
Vue2 | Vue3 |
---|---|
√ | √ |
App | 快应用 | 微信小程序 | 支付宝小程序 | 百度小程序 | 字节小程序 | QQ小程序 |
---|---|---|---|---|---|---|
HBuilderX 3.6.11 app-vue app-nvue | √ | √ | √ | √ | √ | √ |
钉钉小程序 | 快手小程序 | 飞书小程序 | 京东小程序 |
---|---|---|---|
√ | √ | √ | √ |
H5-Safari | Android Browser | 微信浏览器(Android) | QQ浏览器(Android) | Chrome | IE | Edge | Firefox | PC-Safari |
---|---|---|---|---|---|---|---|---|
√ | √ | √ | √ | √ | √ | √ | √ | √ |
📘 功能实现
废话不说直接上代码
📟 用户列表完整代码
<template> <view class="page"> <view class="list-item" v-for="(item,index) in users" :key="index" @click="connect(item)"> <view class="avatar"> <text class="round" v-if="item.read"></text> <image :src="item.avatar" mode="widthFix"></image> </view> <view class="content"> <view class="title"> <text class="name">{{ item.name }}</text> <text class="time">{{ item.time }}</text> </view> <view class="txt">{{ item.msg }}</view> </view> </view> </view> </template> <script> export default { data() { return { options: [{ text: '取消', style: { backgroundColor: '#007aff' } }, { text: '确认', style: { backgroundColor: '#dd524d' } }], users: [{ avatar: '/static/avatar/avatar1.png', name: '杨涛', read: 1, time: '23:59', msg: '没有消息就是最好的消息' }, { avatar: '/static/avatar/avatar2.jpg', name: '雨中漫步', read: 1, time: '23:59', msg: '没有消息就是最好的消息' }, { avatar: '/static/avatar/avatar3.jpeg', name: '糖果梦境', read: 1, time: '23:59', msg: '没有消息就是最好的消息' }, { avatar: '/static/avatar/avatar4.png', name: '海上日落', read: 1, time: '23:59', msg: '没有消息就是最好的消息' }, { avatar: '/static/avatar/avatar6.png', name: '男朋友', read: 1, time: '23:59', msg: '没有消息就是最好的消息' }, { avatar: '/static/avatar/avatar8.png', name: '女朋友', read: 1, time: '23:59', msg: '没有消息就是最好的消息' }, { avatar: '/static/avatar/avatar5.jpeg', name: '静谧之夜', read: 1, time: '23:59', msg: '没有消息就是最好的消息' }, { avatar: '/static/avatar/avatar1.png', name: '风吹麦浪', read: 0, time: '23:59', msg: '没有消息就是最好的消息' }, { avatar: '/static/avatar/avatar1.png', name: '路过岁月', read: 0, time: '23:59', msg: '没有消息就是最好的消息' }, { avatar: '/static/avatar/avatar1.png', name: '繁星点点', read: 0, time: '23:59', msg: '没有消息就是最好的消息' } ] }; }, methods: { onClick(e) { console.log('点击了' + (e.position === 'left' ? '左侧' : '右侧') + e.content.text + '按钮') }, swipeChange(e, index) { console.log('当前状态:' + e + ',下标:' + index) }, connect(item) { uni.navigateTo({ url: `/pages/message/message?name=${item.name}&avatar=${item.avatar}` }) } } } </script> <style lang="scss" scoped> .page { padding: 0 32rpx; color: #333; } .list-item { display: flex; padding: 30rpx 0; border-bottom: 1px solid #ccced3; .avatar { width: 90rpx; height: 90rpx; border-radius: 10rpx; margin-right: 20rpx; position: relative; .round { position: absolute; width: 14rpx; height: 14rpx; border-radius: 50%; background: #ef5656; top: -4rpx; right: -4rpx; z-index: 1; } image { width: 100%; height: 100%; border-radius: 10rpx; } } .content { flex: 1; .title { display: flex; justify-content: space-between; .name { font-weight: bold; } .time { color: #999; font-size: 24rpx; } } .txt { margin-top: 10rpx; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; text-align: left; color: #999; font-size: 26rpx; } } } </style>
📟 单人对话框 完整代码
<template> <view class="page"> <scroll-view class="scroll-view" scroll-y scroll-with-animation :scroll-top="top"> <view style="padding: 30rpx 30rpx 240rpx;"> <view class="message" :class="[item.userType]" v-for="(item,index) in list" :key="index"> <image :src="item.avatar" v-if="item.userType === 'friend'" class="avatar" mode="widthFix"></image> <view class="content" v-if="item.messageType === 'image'"> <image :src="item.content" mode="widthFix"></image> </view> <view class="content" v-else> {{ item.content }} </view> <image :src="item.avatar" v-if="item.userType === 'self'" class="avatar" mode="widthFix"></image> </view> </view> </scroll-view> <view class="tool"> <input type="text" v-model="content" class="input" @confirm="send" /> <image src="/static/photo.png" mode="widthFix" class="thumb" @click="chooseImage"></image> </view> </view> </template> <script> export default { data() { return { content: '', list: [], top: 0 }; }, onLoad(options) { uni.setNavigationBarTitle({ title: options.name }) this._friendAvatar = options.avatar this._selfAvatar = '/static/avatar/avatar5.jpeg' this.list = [ { content: '对方历史回复消息', userType: 'friend', avatar: this._friendAvatar }, { content: '历史消息', userType: 'self', avatar: this._selfAvatar } ] }, methods: { send() { this.list.push({ content: this.content, userType: 'self', avatar: this._selfAvatar }) this.content = '' this.scrollToBottom() // 模拟对方回复 setTimeout(()=>{ this.list.push({ content: '周末什么安排', userType: 'friend', avatar: this._friendAvatar }) this.scrollToBottom() }, 1500) }, chooseImage() { uni.chooseImage({ // sourceType: 'album', success: (res) => { this.list.push({ content: res.tempFilePaths[0], userType: 'self', messageType: 'image', avatar: this._selfAvatar }) this.scrollToBottom() // 模拟对方回复 setTimeout(()=>{ this.list.push({ content: '你好呀,朋友~', userType: 'friend', avatar: this._friendAvatar }) this.scrollToBottom() }, 1500) } }) }, scrollToBottom() { this.top = this.list.length * 1000 } } } </script> <style lang="scss" scoped> .scroll-view { /* #ifdef H5 */ height: calc(100vh - 44px); /* #endif */ /* #ifndef H5 */ height: 100vh; /* #endif */ background: #eee; box-sizing: border-box; } .message { display: flex; align-items: flex-start; margin-bottom: 30rpx; .avatar { width: 80rpx; height: 80rpx; border-radius: 10rpx; margin-right: 30rpx; } .content { min-height: 80rpx; max-width: 60vw; box-sizing: border-box; font-size: 28rpx; line-height: 1.3; padding: 20rpx; border-radius: 10rpx; background: #fff; image { width: 200rpx; } } &.self { justify-content: flex-end; .avatar { margin: 0 0 0 30rpx; } .content { position: relative; &::after { position: absolute; content: ''; width: 0; height: 0; border: 16rpx solid transparent; border-left: 16rpx solid #fff; right: -28rpx; top: 24rpx; } } } &.friend { .content { position: relative; &::after { position: absolute; content: ''; width: 0; height: 0; border: 16rpx solid transparent; border-right: 16rpx solid #fff; left: -28rpx; top: 24rpx; } } } } .tool { position: fixed; width: 100%; min-height: 120rpx; left: 0; bottom: 0; background: #fff; display: flex; align-items: flex-start; box-sizing: border-box; padding: 20rpx 24rpx 20rpx 40rpx; padding-bottom: calc(20rpx + constant(safe-area-inset-bottom)/2) !important; padding-bottom: calc(20rpx + env(safe-area-inset-bottom)/2) !important; .input { background: #eee; border-radius: 10rpx; height: 70rpx; margin-right: 30rpx; flex: 1; padding: 0 20rpx; box-sizing: border-box; font-size: 28rpx; } .thumb { width: 64rpx; } } </style>
🔥 文章总结
📟 隐私、权限声明
如说明表达还不够清楚,不清楚怎么使用可导入完整示例项目运行体验和希望对大家有帮助!
1. 本插件需要申请的系统权限列表:
无,开箱即用
2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:
插件不采集任何数据
3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:
没有任何广告,纯分享,方便自己,同时也方便其他能用的的前端好朋友
请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.
使用插件有任何问题欢迎加入QQ讨论群:
作者QQ群:906392632(未满)
✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式
✅ 认为我部分代码过于老旧,可以提供新的API或最新语法
✅ 对于文章中部分内容不理解
✅ 解答我文章中一些疑问
✅ 认为某些交互,功能需要优化,发现BUG
✅ 想要添加新功能,对于整体的设计,外观有更好的建议
最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!