小编目前在做毕业设计,主题为“高考志愿信息交流平台”,面向高中生和大学生,辛苦各位读者大佬朋友们填下问卷,点击链接https://www.wjx.cn/jq/98944127.aspx或扫描二维码、微信小程序码均可,希望各位能提供一些调查数据,先在这里谢过各位了(*^_^*)
本文主要介绍了消息也的开发,主要包括3部分:
消息列表页开发,包括页面配置、消息列表组件的开发和封装、下拉刷新功能和下拉弹出层组件的使用;
我的好友列表页开发,包括页面配置、导航组件开发、好友列表组件开发和封装、以及性别图标显示;
聊天页面开发,包括页面配置、输入框组件开发、聊天列表组件的开发和封装、以及聊天页功能完善。
消息页也需要在pages.json中配置顶部导航栏,包括好友和菜单图标,如下:
{
"path" : "pages/msg/msg",
"style" :
{
"navigationBarTitleText": "消息列表",
"enablePullDownRefresh": false,
"app-plus": {
"titleNView": {
"buttons": [
{
"color":"#333333",
"colorPressed":"#FD597C",
"float":"left",
"fontSize":"20px",
"fontSrc":"/static/iconfont.ttf",
"text": "\ue60d"
},
{
"color":"#333333",
"colorPressed":"#FD597C",
"float":"right",
"fontSize":"20px",
"fontSrc":"/static/iconfont.ttf",
"text": "\ue652"
}
]
}
}
}
}
为了本文项目练手所需,需要在https://www.iconfont.cn/中下载好友
、男性
、女性
、个人
、发送
等图标,同时将icon.css和iconfont.ttf更新为最新状态。
显然,已经配置好左侧和右侧的图标。
先实现消息列表项,如下:
<template>
<view>
<view class="flex align-center p-2">
<image src="/static/img/userpic/5.jpg" style="width: 80rpx; height: 80rpx;" class="rounded-circle mr-2">image>
<view class="flex flex-column flex-1">
<view class="flex align-center justify-between">
<text class="font-md">Corleytext>
<text class="font text-secondry">13:34text>
view>
<view class="flex align-center justify-between">
<text class="text-secondry">大佬,你好text>
view>
view>
view>
view>
template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
script>
<style>
style>
base.css如下:
/* 内外边距 */
.p-2 {
padding: 20rpx;
}
/* flex布局 */
.flex {
/* #ifndef APP-APP-PLUS-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.flex-wrap {
flex-wrap: wrap;
}
.flex-column {
flex-direction: column;
}
.align-center {
align-items: center;
}
.align-start {
align-items: flex-start;
}
.justify-between {
justify-content: space-between;
}
.justify-center {
justify-content: center;
}
.flex-1 {
flex: 1;
}
/* 圆角 */
.rounded-circle {
border-radius: 100%;
}
.rounded {
border-radius: 8rpx;
}
/* margin */
.mr-2 {
margin-right: 20rpx;
}
.mr-1 {
margin-right: 10rpx;
}
.my-2 {
margin-top: 20rpx;
margin-bottom: 20rpx;
}
.my-1 {
margin-top: 10rpx;
margin-bottom: 10rpx;
}
.mx-2 {
margin-left: 20rpx;
margin-right: 20rpx;
}
.mx-1 {
margin-left: 10rpx;
margin-right: 10rpx;
}
.mt-1 {
margin-top: 10rpx;
}
.ml-auto {
margin-left: auto;
}
.ml-2 {
margin-left: 20rpx;
}
/* padding */
.p-2 {
padding-left: 20rpx;
padding-right: 20rpx;
padding-top: 20rpx;
padding-bottom: 20rpx;
}
.px-5 {
padding-left: 50rpx;
padding-right: 50rpx;
}
.px-3 {
padding-left: 30rpx;
padding-right: 30rpx;
}
.px-2 {
padding-left: 20rpx;
padding-right: 20rpx;
}
.px-1 {
padding-left: 10rpx;
padding-right: 10rpx;
}
.py-3 {
padding-top: 30rpx;
padding-bottom: 30rpx;
}
.py-2 {
padding-top: 20rpx;
padding-bottom: 20rpx;
}
.pt-7 {
padding-top: 70rpx;
}
.pb-2 {
padding-bottom: 20rpx;
}
/* 边框 */
.border {
border-width: 1rpx;
border-style: solid;
border-color: #DEE2E6;
}
.border-bottom {
border-bottom: 1rpx solid #DEE2E6;
}
.border-top {
border-top: 1rpx solid #DEE2E6;
}
.border-light-secondary {
border: 1rpx solid #AAA8AB;
}
/* 字体 */
.font-lg {
font-size: 40rpx;
}
.font-md {
font-size: 35rpx;
}
.font {
font-size: 30rpx;
}
.font-sm {
font-size: 25rpx;
}
.font-weight-bold {
font-weight: bold;
}
/* 文字颜色 */
.text-white {
color: #FFFFFF;
}
.text-light-muted {
color: #A9A5A0;
}
/* 文字换行溢出处理 */
.text-ellipsis {
/* #ifndef APP-PLUS-APP-PLUS-NVUE */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* #endif */
/* #ifdef APP-PLUS-APP-PLUS-NVUE */
lines: 1;
/* #endif */
}
/* 宽度 */
/* #ifndef APP-PLUS-NVUE */
.w-100 {
width: 100%;
}
/* #endif */
/* scroll-view */
/* #ifndef APP-PLUS-NVUE */
.scroll-row {
width: 100%;
white-space: nowrap;
}
.scroll-row-item {
display: inline-block !important;
}
/* #endif */
/* 背景 */
.bg-light {
background-color: #F8F9FA;
}
.bg-secondary {
background-color: #AAA8AB;
}
.bg-white {
background-color: #FFFFFF;
}
.bg-dark {
background-color: #333333;
}
/* 定位 */
.position-relative {
position: relative;
}
.position-absolute {
position: absolute;
}
.position-fixed {
position: fixed;
}
/* 定位-固定顶部 */
.fixed-top {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 1030;
}
/* 定位-固定底部 */
.fixed-bottom {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 1030;
}
.top-0 {
top: 0;
}
.left-0 {
left: 0;
}
.right-0 {
right: 0;
}
.bottom-0 {
bottom: 0;
}
common.css如下:
/* 本项目全局样式 */
/* 背景 */
.bg-main {
background-color: #FF4A6A;
}
/* 文本颜色 */
.text-main {
color: #FF4A6A;
}
.text-secondry {
color: #AAA8AB;
}
.text-dark {
color: #333333;
}
/* 下拉弹出框样式 */
.popup-content {
background-color: #fff;
padding: 7px;
}
已经正常显示了消息列表。
再实现新消息提示,需要使用uni-app提供的扩展组件数字角标uni-badge
,可参考https://ext.dcloud.net.cn/plugin?id=21。
其位于hello_uniapp演示项目的components/uni-badge目录下,直接将uni-badge目录拷贝至本项目的components/uni-ui目录下即可。
如下:
<template>
<view>
<view class="flex align-center p-2 border-bottom border-" hover-class="bg-light">
<image src="/static/img/userpic/5.jpg" style="width: 80rpx; height: 80rpx;" class="rounded-circle mr-2">image>
<view class="flex flex-column flex-1">
<view class="flex align-center justify-between">
<text class="font-md">Corleytext>
<text class="font text-secondry">13:34text>
view>
<view class="flex align-center justify-between">
<text class="text-secondry">大佬,你好text>
<uni-badge text="3" type="error">uni-badge>
view>
view>
view>
view>
template>
<script>
import uniBadge from '@/components/uni-ui/uni-badge/uni-badge.vue';
export default {
data() {
return {
}
},
components: {
uniBadge
},
methods: {
}
}
script>
<style>
style>
可以看到,已经出现消息提示数字角标。
现在处理消息长度过长的问题,使用文字溢出的样式即可,如下:
<view class="flex align-center justify-between">
<text class="text-secondry text-ellipsis" style="max-width: 500rpx;">大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?text>
<uni-badge text="3" type="error">uni-badge>
view>
多余的文字就会以省略号的形式显示。
将其封装为组件时,需要先构建数据,因为数据中传入的时间形式是时间戳,因此需要转化为时间字符串,使用时间处理库来转换时间,在common目录下新建time.js用于处理时间,如下:
export default {
// 计算当前日期星座
getHoroscope(date) {
let c = ['摩羯', '水瓶', '双鱼', '白羊', '金牛', '双子', '巨蟹', '狮子', '处女', '天秤', '天蝎', '射手', '摩羯']
date = new Date(date);
let month = date.getMonth() + 1;
let day = date.getDate();
let startMonth = month - (day - 14 < '865778999988'.charAt(month));
return c[startMonth] + '座';
},
// 计算指定时间与当前的时间差
sumAge(data) {
let dateBegin = new Date(data.replace(/-/g, "/"));
let dateEnd = new Date(); //获取当前时间
let dateDiff = dateEnd.getTime() - dateBegin.getTime(); //时间差的毫秒数
let dayDiff = Math.floor(dateDiff / (24 * 3600 * 1000)); //计算出相差天数
let leave1 = dateDiff % (24 * 3600 * 1000) //计算天数后剩余的毫秒数
let hours = Math.floor(leave1 / (3600 * 1000)) //计算出小时数
//计算相差分钟数
let leave2 = leave1 % (3600 * 1000) //计算小时数后剩余的毫秒数
let minutes = Math.floor(leave2 / (60 * 1000)) //计算相差分钟数
//计算相差秒数
let leave3 = leave2 % (60 * 1000) //计算分钟数后剩余的毫秒数
let seconds = Math.round(leave3 / 1000)
return dayDiff + "天 " + hours + "小时 ";
},
// 获取聊天时间(相差300s内的信息不会显示时间)
getChatTime(v1, v2) {
v1 = v1.toString().length < 13 ? v1 * 1000 : v1;
v2 = v2.toString().length < 13 ? v2 * 1000 : v2;
if (((parseInt(v1) - parseInt(v2)) / 1000) > 300) {
return this.gettime(v1);
}
},
// 人性化时间格式
gettime(shorttime) {
shorttime = shorttime.toString().length < 13 ? shorttime * 1000 : shorttime;
let now = (new Date()).getTime();
let cha = (now - parseInt(shorttime)) / 1000;
if (cha < 43200) {
// 当天
return this.dateFormat(new Date(shorttime), "{A} {t}:{ii}");
} else if (cha < 518400) {
// 隔天 显示日期+时间
return this.dateFormat(new Date(shorttime), "{Mon}月{DD}日 {A} {t}:{ii}");
} else {
// 隔年 显示完整日期+时间
return this.dateFormat(new Date(shorttime), "{Y}-{MM}-{DD} {A} {t}:{ii}");
}
},
parseNumber(num) {
return num < 10 ? "0" + num : num;
},
dateFormat(date, formatStr) {
let dateObj = {
},
rStr = /\{([^}]+)\}/,
mons = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'];
dateObj["Y"] = date.getFullYear();
dateObj["M"] = date.getMonth() + 1;
dateObj["MM"] = this.parseNumber(dateObj["M"]);
dateObj["Mon"] = mons[dateObj['M'] - 1];
dateObj["D"] = date.getDate();
dateObj["DD"] = this.parseNumber(dateObj["D"]);
dateObj["h"] = date.getHours();
dateObj["hh"] = this.parseNumber(dateObj["h"]);
dateObj["t"] = dateObj["h"] > 12 ? dateObj["h"] - 12 : dateObj["h"];
dateObj["tt"] = this.parseNumber(dateObj["t"]);
dateObj["A"] = dateObj["h"] > 12 ? '下午' : '上午';
dateObj["i"] = date.getMinutes();
dateObj["ii"] = this.parseNumber(dateObj["i"]);
dateObj["s"] = date.getSeconds();
dateObj["ss"] = this.parseNumber(dateObj["s"]);
while (rStr.test(formatStr)) {
formatStr = formatStr.replace(rStr, dateObj[RegExp.$1]);
}
return formatStr;
},
// 获取年龄
getAgeByBirthday(data) {
let birthday = new Date(data.replace(/-/g, "\/"));
let d = new Date();
return d.getFullYear() - birthday.getFullYear() - ((d.getMonth() < birthday.getMonth() || d.getMonth() == birthday.getMonth() &&
d.getDate() < birthday.getDate()) ? 1 : 0);
}
}
msg.vue创建数据如下:
<template>
<view>
<block v-for="(item, index) in list" :key="index">
<view class="flex align-center p-2 border-bottom border-" hover-class="bg-light">
<image :src="item.avatar" style="width: 80rpx; height: 80rpx;" class="rounded-circle mr-2">image>
<view class="flex flex-column flex-1">
<view class="flex align-center justify-between">
<text class="font-md">{
{item.username}}text>
<text class="font-sm text-secondry">{
{item.update_time | formatTime}}text>
view>
<view class="flex align-center justify-between">
<text class="text-secondry text-ellipsis" style="max-width: 500rpx;">{
{item.data}}text>
<uni-badge :text="item.noread" type="error">uni-badge>
view>
view>
view>
block>
view>
template>
<script>
import uniBadge from '@/components/uni-ui/uni-badge/uni-badge.vue';
import $T from '@/common/time.js';
export default {
data() {
return {
list: [
{
avatar: '/static/img/userpic/5.jpg',
username: 'Corley',
update_time: 1612075613,
data: '我再看看吧,谢谢大佬',
noread: 5
},
{
avatar: '/static/img/userpic/1.jpg',
username: 'Brynn',
update_time: 1612075606,
data: '大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?',
noread: 0
},
{
avatar: '/static/img/userpic/3.jpg',
username: 'Ellie',
update_time: 1612075255,
data: '那我也有点懵了',
noread: 3
},
{
avatar: '/static/img/userpic/9.jpg',
username: 'Ainsley',
update_time: 1612075983,
data: '真机调试不太方便,我就用浏览器和微信开发者工具调试的。',
noread: 1
},
{
avatar: '/static/img/userpic/17.jpg',
username: 'Bella',
update_time: 1612074571,
data: 'mysql16进制数据怎么查询才快呢?',
noread: 2
}
]
}
},
components: {
uniBadge
},
// 过滤器
filters: {
formatTime(value) {
return $T.gettime(value);
}
},
methods: {
}
}
script>
<style>
style>
导入了time.js中的gettime()
函数获取格式化时间,同时创建过滤器来将时间戳格式化。
可以看到,显示出了消息列表,并且时间是格式化后的时间。
此时再封装为组件,components下新建msg目录,下新建msg-list.vue组件如下:
<template>
<view class="flex align-center p-2 border-bottom border-" hover-class="bg-light">
<image :src="item.avatar" style="width: 80rpx; height: 80rpx;" class="rounded-circle mr-2">image>
<view class="flex flex-column flex-1">
<view class="flex align-center justify-between">
<text class="font-md">{
{item.username}}text>
<text class="font-sm text-secondry">{
{item.update_time | formatTime}}text>
view>
<view class="flex align-center justify-between">
<text class="text-secondry text-ellipsis" style="max-width: 500rpx;">{
{item.data}}text>
<uni-badge :text="item.noread" type="error">uni-badge>
view>
view>
view>
template>
<script>
import uniBadge from '@/components/uni-ui/uni-badge/uni-badge.vue';
import $T from '@/common/time.js';
export default {
props: {
item: Object,
index: Number
},
// 过滤器
filters: {
formatTime(value) {
return $T.gettime(value);
}
},
components: {
uniBadge
}
}
script>
<style>
style>
msg.vue如下:
<template>
<view>
<block v-for="(item, index) in list" :key="index">
<msg-list :item="item" :index="index">msg-list>
block>
view>
template>
<script>
import msgList from '@/components/msg/msg-list.vue';
export default {
data() {
return {
list: [
{
avatar: '/static/img/userpic/5.jpg',
username: 'Corley',
update_time: 1612075613,
data: '我再看看吧,谢谢大佬',
noread: 5
},
{
avatar: '/static/img/userpic/1.jpg',
username: 'Brynn',
update_time: 1612075606,
data: '大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?',
noread: 0
},
{
avatar: '/static/img/userpic/3.jpg',
username: 'Ellie',
update_time: 1612075255,
data: '那我也有点懵了',
noread: 3
},
{
avatar: '/static/img/userpic/9.jpg',
username: 'Ainsley',
update_time: 1612075983,
data: '真机调试不太方便,我就用浏览器和微信开发者工具调试的。',
noread: 1
},
{
avatar: '/static/img/userpic/17.jpg',
username: 'Bella',
update_time: 1612074571,
data: 'mysql16进制数据怎么查询才快呢?',
noread: 2
}
]
}
},
components: {
msgList
},
methods: {
}
}
script>
<style>
style>
可以达到与之前相同的效果。
消息页的下拉刷新在pages.json中配置enablePullDownRefresh
属性为true
,pages.json文件配置如下:
{
"path" : "pages/msg/msg",
"style" :
{
"navigationBarTitleText": "消息列表",
"enablePullDownRefresh": true,
"app-plus": {
"titleNView": {
"buttons": [
{
"color":"#333333",
"colorPressed":"#FD597C",
"float":"left",
"fontSize":"20px",
"fontSrc":"/static/iconfont.ttf",
"text": "\ue60d"
},
{
"color":"#333333",
"colorPressed":"#FD597C",
"float":"right",
"fontSize":"20px",
"fontSrc":"/static/iconfont.ttf",
"text": "\ue652"
}
]
}
}
}
}
同时在页面中配置onPullDownRefresh
生命周期,如下:
// 监听下拉刷新
onPullDownRefresh() {
this.refresh();
},
methods: {
refresh() {
setTimeout(()=>{
this.list = test_data;
// 停止下拉刷新
uni.stopPullDownRefresh();
}, 2000)
}
}
可以看到,已经实现了下拉刷新的效果。
现在再实现没有数据时的默认界面,通过条件渲染实现,如下:
<template>
<view>
<template v-if="list.length > 0">
<block v-for="(item, index) in list" :key="index">
<msg-list :item="item" :index="index">msg-list>
block>
template>
<template v-else>
<no-thing>no-thing>
template>
view>
template>
<script>
const test_data = [{
avatar: '/static/img/userpic/5.jpg',
username: 'Corley',
update_time: 1612075613,
data: '我再看看吧,谢谢大佬',
noread: 5
},
{
avatar: '/static/img/userpic/1.jpg',
username: 'Brynn',
update_time: 1612075606,
data: '大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?',
noread: 0
},
{
avatar: '/static/img/userpic/3.jpg',
username: 'Ellie',
update_time: 1612075255,
data: '那我也有点懵了',
noread: 3
},
{
avatar: '/static/img/userpic/9.jpg',
username: 'Ainsley',
update_time: 1612075983,
data: '真机调试不太方便,我就用浏览器和微信开发者工具调试的。',
noread: 1
},
{
avatar: '/static/img/userpic/17.jpg',
username: 'Bella',
update_time: 1612074571,
data: 'mysql16进制数据怎么查询才快呢?',
noread: 2
}
];
import msgList from '@/components/msg/msg-list.vue';
import noThing from '@/components/common/no-thing.vue';
export default {
data() {
return {
list: []
}
},
components: {
msgList,
noThing
},
// 监听下拉刷新
onPullDownRefresh() {
this.refresh();
},
methods: {
refresh() {
setTimeout(()=>{
this.list = test_data;
// 停止下拉刷新
uni.stopPullDownRefresh();
}, 2000)
}
}
}
script>
<style>
style>
显示:
显然,此时在没有数据时会显示给定的默认组件。
下拉弹出框需要使用uni-app提供的扩展组件,直接使用hello_uniapp项目下的components/uni-popup/uni-popup.vue
组件即可,将uni-popup目录和同级的uni-transition目录拷贝至本项目的components/uni-ui目录下。
再使用下拉弹出层,如下:
<template>
<view>
<template v-if="list.length > 0">
<block v-for="(item, index) in list" :key="index">
<msg-list :item="item" :index="index">msg-list>
block>
template>
<template v-else>
<no-thing>no-thing>
template>
<uni-popup ref="popup" type="top">弹出层uni-popup>
view>
template>
<script>
const test_data = [{
avatar: '/static/img/userpic/5.jpg',
username: 'Corley',
update_time: 1612075613,
data: '我再看看吧,谢谢大佬',
noread: 5
},
{
avatar: '/static/img/userpic/1.jpg',
username: 'Brynn',
update_time: 1612075606,
data: '大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?',
noread: 0
},
{
avatar: '/static/img/userpic/3.jpg',
username: 'Ellie',
update_time: 1612075255,
data: '那我也有点懵了',
noread: 3
},
{
avatar: '/static/img/userpic/9.jpg',
username: 'Ainsley',
update_time: 1612075983,
data: '真机调试不太方便,我就用浏览器和微信开发者工具调试的。',
noread: 1
},
{
avatar: '/static/img/userpic/17.jpg',
username: 'Bella',
update_time: 1612074571,
data: 'mysql16进制数据怎么查询才快呢?',
noread: 2
}
];
import msgList from '@/components/msg/msg-list.vue';
import noThing from '@/components/common/no-thing.vue';
import uniPopup from '@/components/uni-ui/uni-popup/uni-popup.vue';
export default {
data() {
return {
list: []
}
},
components: {
msgList,
noThing,
uniPopup
},
// 页面加载
onLoad() {
this.list = test_data;
},
// 监听下拉刷新
onPullDownRefresh() {
this.refresh();
},
// 监听原生导航栏按钮事件
onNavigationBarButtonTap(e) {
console.log(e);
switch (e.index){
case 0: // 左边
break;
case 1: // 右边
this.$refs.popup.open();
break;
default:
break;
}
},
methods: {
refresh() {
setTimeout(()=>{
this.list = test_data;
// 停止下拉刷新
uni.stopPullDownRefresh();
}, 2000)
}
}
}
script>
<style>
style>
因为下拉弹出层是通过点击右上角的菜单图标弹出的,因此需要生命周期onNavigationBarButtonTap
来实现,其事件参数中有一个值为index,其值表示顶部可点击按钮的下标,从左往右第一个图标为0、第二个图标为1,即点击好友图标时index值为0、点击菜单图标时index值为1。
显示:
可以看到,已经实现了点击弹出下拉弹出框。
现在进一步完善下拉弹出选项,如下:
<template>
<view>
<template v-if="list.length > 0">
<block v-for="(item, index) in list" :key="index">
<msg-list :item="item" :index="index">msg-list>
block>
template>
<template v-else>
<no-thing>no-thing>
template>
<uni-popup ref="popup" type="top">
<view class="popup-content flex align-center justify-center font-md border-bottom" hover-class="bg-light">
<text class="iconfont icon-sousuo mr-2">text> 添加好友
view>
<view class="popup-content flex align-center justify-center font-md" hover-class="bg-light">
<text class="iconfont icon-shanchu mr-2">text> 清除列表
view>
uni-popup>
view>
template>
<script>
const test_data = [{
avatar: '/static/img/userpic/5.jpg',
username: 'Corley',
update_time: 1612075613,
data: '我再看看吧,谢谢大佬',
noread: 5
},
{
avatar: '/static/img/userpic/1.jpg',
username: 'Brynn',
update_time: 1612075606,
data: '大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?',
noread: 0
},
{
avatar: '/static/img/userpic/3.jpg',
username: 'Ellie',
update_time: 1612075255,
data: '那我也有点懵了',
noread: 3
},
{
avatar: '/static/img/userpic/9.jpg',
username: 'Ainsley',
update_time: 1612075983,
data: '真机调试不太方便,我就用浏览器和微信开发者工具调试的。',
noread: 1
},
{
avatar: '/static/img/userpic/17.jpg',
username: 'Bella',
update_time: 1612074571,
data: 'mysql16进制数据怎么查询才快呢?',
noread: 2
}
];
import msgList from '@/components/msg/msg-list.vue';
import noThing from '@/components/common/no-thing.vue';
import uniPopup from '@/components/uni-ui/uni-popup/uni-popup.vue';
export default {
data() {
return {
list: []
}
},
components: {
msgList,
noThing,
uniPopup
},
// 页面加载
onLoad() {
this.list = test_data;
},
// 监听下拉刷新
onPullDownRefresh() {
this.refresh();
},
// 监听原生导航栏按钮事件
onNavigationBarButtonTap(e) {
console.log(e);
switch (e.index){
case 0: // 左边
break;
case 1: // 右边
this.$refs.popup.open();
break;
default:
break;
}
},
methods: {
refresh() {
setTimeout(()=>{
this.list = test_data;
// 停止下拉刷新
uni.stopPullDownRefresh();
}, 2000)
}
}
}
script>
<style>
style>
显示:
现在给下拉弹出层选项添加点击事件,如下:
<template>
<view>
<template v-if="list.length > 0">
<block v-for="(item, index) in list" :key="index">
<msg-list :item="item" :index="index">msg-list>
block>
template>
<template v-else>
<no-thing>no-thing>
template>
<uni-popup ref="popup" type="top">
<view class="popup-content flex align-center justify-center font-md border-bottom" hover-class="bg-light" @click="popupEvent('friend')">
<text class="iconfont icon-sousuo mr-2">text> 添加好友
view>
<view class="popup-content flex align-center justify-center font-md" hover-class="bg-light" @click="popupEvent('clear')">
<text class="iconfont icon-shanchu mr-2">text> 清除列表
view>
uni-popup>
view>
template>
<script>
const test_data = [{
avatar: '/static/img/userpic/5.jpg',
username: 'Corley',
update_time: 1612075613,
data: '我再看看吧,谢谢大佬',
noread: 5
},
{
avatar: '/static/img/userpic/1.jpg',
username: 'Brynn',
update_time: 1612075606,
data: '大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?',
noread: 0
},
{
avatar: '/static/img/userpic/3.jpg',
username: 'Ellie',
update_time: 1612075255,
data: '那我也有点懵了',
noread: 3
},
{
avatar: '/static/img/userpic/9.jpg',
username: 'Ainsley',
update_time: 1612075983,
data: '真机调试不太方便,我就用浏览器和微信开发者工具调试的。',
noread: 1
},
{
avatar: '/static/img/userpic/17.jpg',
username: 'Bella',
update_time: 1612074571,
data: 'mysql16进制数据怎么查询才快呢?',
noread: 2
}
];
import msgList from '@/components/msg/msg-list.vue';
import noThing from '@/components/common/no-thing.vue';
import uniPopup from '@/components/uni-ui/uni-popup/uni-popup.vue';
export default {
data() {
return {
list: []
}
},
components: {
msgList,
noThing,
uniPopup
},
// 页面加载
onLoad() {
this.list = test_data;
},
// 监听下拉刷新
onPullDownRefresh() {
this.refresh();
},
// 监听原生导航栏按钮事件
onNavigationBarButtonTap(e) {
console.log(e);
switch (e.index) {
case 0: // 左边
// 关闭弹出层
this.$refs.popup.close();
break;
case 1: // 右边
this.$refs.popup.open();
break;
default:
break;
}
},
methods: {
// 下拉刷新
refresh() {
setTimeout(() => {
this.list = test_data;
// 停止下拉刷新
uni.stopPullDownRefresh();
}, 2000)
},
// 弹出层选项点击事件
popupEvent(e) {
switch (e) {
case 'friend':
console.log('Adding friend');
break;
case 'clear':
console.log('Clearing list');
break;
default:
break;
}
// 关闭弹出层
this.$refs.popup.close();
}
}
}
script>
<style>
style>
显示:
可以看到,正确地触发了点击事件。
我的好友列表页入口在消息页,如下:
// 监听原生导航栏按钮事件
onNavigationBarButtonTap(e) {
console.log(e);
switch (e.index) {
case 0: // 左边
uni.navigateTo({
url: '../user-list/user-list',
});
// 关闭弹出层
this.$refs.popup.close();
break;
case 1: // 右边
this.$refs.popup.open();
break;
default:
break;
}
},
先创建我的好友列表页user-list
,配置pages.json如下:
{
"path" : "pages/user-list/user-list",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": false,
"app-plus": {
// 动画效果
"animationType":"slide-in-left",
// 导航栏配置
"titleNView": {
// 取消返回按钮
"autoBackButton": false,
// 搜索框配置
"searchInput": {
"align":"center",
"backgroundColor":"#F5F4F2",
"borderRadius":"4px",
"disabled": true,
"placeholder": "搜索用户",
"placeholderColor": "#6D6C67"
},
// 按钮设置
"buttons": [
{
"color":"#333333",
"colorPressed":"#FD597C",
"float":"right",
"fontSize":"15px",
"text": "取消"
}
]
}
}
}
}
其中,animationType
用于设置窗口显示的动画效果为新窗体从左侧进入;
autoBackButton
用于取消返回按钮。
同时需要实现点击搜索框时跳转到搜索页、点击取消时返回上一页,user-list.vue如下:
<template>
<view>
view>
template>
<script>
export default {
data() {
return {
}
},
// 监听点击输入框事件
onNavigationBarSearchInputClicked() {
uni.navigateTo({
url: '../search/search',
});
},
// 监听点击按钮事件
onNavigationBarButtonTap() {
uni.navigateBack({
delta: 1
});
},
methods: {
}
}
script>
<style>
style>
显示:
显然,实现了页面切换和动画效果显示。
现进一步完善导航栏,包括互关、关注和粉丝3个导航栏选项,与话题详情页类似。
user-list.vue如下:
<template>
<view>
<view class="flex align-center py-2">
<view class="flex-1 flex align-center justify-center" v-for="(item ,index) in tabBars" :key="'tab'+index" :class="index === tabIndex ? 'font-lg font-weight-bold text-main' : 'font-md'"
@click="changeTab(index)">{
{item.name}} <text v-if="item.num > 0" class="ml-2">{
{item.num}}text>view>
view>
view>
template>
<script>
export default {
data() {
return {
tabIndex: 0,
tabBars: [{
name: '互关',
num: 2
},
{
name: '关注',
num: 0
},
{
name: '粉丝',
num: 5
}
]
}
},
// 监听点击输入框事件
onNavigationBarSearchInputClicked() {
uni.navigateTo({
url: '../search/search',
});
},
// 监听点击按钮事件
onNavigationBarButtonTap() {
uni.navigateBack({
delta: 1
});
},
methods: {
changeTab(index) {
this.tabIndex = index;
}
}
}
script>
<style>
style>
其中,好友人数大于0时才显示个数。
可以看到,实现了顶部导航栏开发。
好友列表和首页列表类似,先构建如下:
<template>
<view>
<view class="flex align-center py-2">
<view class="flex-1 flex align-center justify-center" v-for="(item ,index) in tabBars" :key="'tab'+index" :class="index === tabIndex ? 'font-lg font-weight-bold text-main' : 'font-md'"
@click="changeTab(index)">{
{item.name}} <text v-if="item.num > 0" class="ml-2">{
{item.num}}text>view>
view>
<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
<swiper-item v-for="(item, index) in newsList" :key="index">
<scroll-view scroll-y="true" :style="'height: '+scrollH+'px;'" @scrolltolower="loadMore(index)">
<template v-if="item.list.length > 0">
<block v-for="(item2, index2) in item.list" :key="index2">
<view>{
{item2}}view>
<divider>divider>
block>
<load-more :loadmore="item.loadmore">load-more>
template>
<template v-else>
<no-thing>no-thing>
template>
scroll-view>
swiper-item>
swiper>
view>
template>
<script>
import loadMore from '@/components/common/load-more.vue';
export default {
data() {
return {
tabIndex: 0,
tabBars: [{
name: '互关',
num: 2
},
{
name: '关注',
num: 0
},
{
name: '粉丝',
num: 5
}
],
// 列表高度
scrollH: 600,
newsList: []
}
},
// 监听点击输入框事件
onNavigationBarSearchInputClicked() {
uni.navigateTo({
url: '../search/search',
});
},
// 监听点击按钮事件
onNavigationBarButtonTap() {
uni.navigateBack({
delta: 1
});
},
onLoad() {
uni.getSystemInfo({
success: function(res) {
console.log(res);
this.scrollH = res.windowHeight - uni.upx2px(100);
}
});
// 根据选项生成列表
this.getData();
},
methods: {
changeTab(index) {
this.tabIndex = index;
},
// 监听滑动
onChangeTab(e) {
console.log(e);
this.changeTab(e.detail.current);
},
// 上拉加载更多
loadMore(index) {
// 获取当前列表
let item = this.newsList[index];
// 判断是否处于可加载状态
if (item.loadmore !== '上拉加载更多') return;
// 修改当前列表加载状态
item.loadmore = '加载中...';
// 模拟数据请求
setTimeout(() => {
// 加载数据
item.list = [...item.list, ...item.list];
// 恢复加载状态
this.newsList[index].loadmore = '上拉加载更多';
}, 2000)
},
// 获取数据
getData() {
var arr = [];
for (let i = 0; i < this.tabBars.length; i++) {
// 生成列表模板
let obj = {
// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
loadmore: "上拉加载更多",
list: []
}
for (let j = 0; j < this.tabBars[i].num; j++) {
obj.list.push('user' + (j + 1));
}
arr.push(obj);
}
this.newsList = arr;
}
}
}
script>
<style>
style>
显示:
可以看到,模拟出了好友列表。
进一步构造好友列表项,如下:
<block v-for="(item2, index2) in item.list" :key="index2">
<view class="flex align-center p-2 border-bottom">
<image src="/static/img/userpic/6.jpg" style="width: 100rpx; height: 100rpx;" class="rounded-circle mr-2">image>
<view class="flex flex-column flex-1">
<text class="font-md text-dark">Corleytext>
<text>男text>
view>
<view class="uni-icon uni-icon-checkbox-filled text-light-muted">view>
view>
block>
显示:
可以看到,已经模拟出了好友列表信息。
使用badge组件实现性别图标,为了可以插入性别图标文本,需要对官方提供的uni-badge组件进行稍微改进,如下:
<template>
<view v-if="text" :class="inverted ? 'uni-badge--' + type + ' uni-badge--' + size + ' uni-badge--' + type + '-inverted' : 'uni-badge--' + type + ' uni-badge--' + size" :style="badgeStyle" class="uni-badge" @click="onClick()"><slot>slot> {
{ text }}view>
template>
user-list.vue如下:
<view class="flex align-center p-2 border-bottom">
<image src="/static/img/userpic/6.jpg" style="width: 100rpx; height: 100rpx;" class="rounded-circle mr-2">image>
<view class="flex flex-column flex-1">
<text class="font-md text-dark">Corleytext>
<uni-badge text="18" type="error" size="small">
<text class="iconfont icon-nanxing text-white font-sm" style="margin-right: 5rpx;">text>
uni-badge>
view>
<view class="uni-icon uni-icon-checkbox-filled text-light-muted">view>
view>
components: {
loadMore,
uniBadge
},
可以看到,已经显示出了性别和年龄。
封装好友列表组件之前,需要先构建数据,如下:
<template>
<view>
<view class="flex align-center py-2">
<view class="flex-1 flex align-center justify-center" v-for="(item ,index) in tabBars" :key="'tab'+index" :class="index === tabIndex ? 'font-lg font-weight-bold text-main' : 'font-md'"
@click="changeTab(index)">{
{item.name}} <text v-if="item.num > 0" class="ml-2">{
{item.num}}text>view>
view>
<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
<swiper-item v-for="(item, index) in userList" :key="index">
<scroll-view scroll-y="true" :style="'height: '+scrollH+'px;'" @scrolltolower="loadMore(index)">
<template v-if="item.list.length > 0">
<block v-for="(item2, index2) in item.list" :key="index2">
<view class="flex align-center p-2 border-bottom" hover-class="bg-light">
<image :src="item2.avatar" style="width: 100rpx; height: 100rpx;" class="rounded-circle mr-2">image>
<view class="flex flex-column flex-1">
<text class="font-md text-dark">{
{item2.username}}text>
<uni-badge :text="item2.age" :type="item2.sex === 1 ? 'error' : (item2.sex === 2 ? 'primary' : 'default')" size="small">
<template v-if="item2.sex > 0">
<text class="iconfont text-white font-sm" :class="item2.sex === 1 ? 'icon-nvxing' : 'icon-nanxing'" style="margin-right: 5rpx;">text>
template>
uni-badge>
view>
<view class="uni-icon uni-icon-checkbox-filled" :class="item2.isFollow ? 'text-light-muted' : 'text-main'">view>
view>
block>
<load-more :loadmore="item.loadmore">load-more>
template>
<template v-else>
<no-thing>no-thing>
template>
scroll-view>
swiper-item>
swiper>
view>
template>
<script>
const test_data1 = {
avatar: '/static/img/userpic/15.jpg',
username: 'Corley',
sex: 1, // 0未知、1女性、2男性
age: 23,
isFollow: true
}
const test_data2 = {
avatar: '/static/img/userpic/7.jpg',
username: 'Casey',
sex: 0, // 0未知、1女性、2男性
age: 15,
isFollow: false
}
const test_data3 = {
avatar: '/static/img/userpic/13.jpg',
username: 'Henry',
sex: 2, // 0未知、1女性、2男性
age: 18,
isFollow: true
}
import loadMore from '@/components/common/load-more.vue';
import uniBadge from '@/components/uni-ui/uni-badge/uni-badge.vue';
export default {
data() {
return {
tabIndex: 0,
tabBars: [{
name: '互关',
num: 2
},
{
name: '关注',
num: 0
},
{
name: '粉丝',
num: 5
}
],
// 列表高度
scrollH: 600,
userList: []
}
},
// 监听点击输入框事件
onNavigationBarSearchInputClicked() {
uni.navigateTo({
url: '../search/search',
});
},
// 监听点击按钮事件
onNavigationBarButtonTap() {
uni.navigateBack({
delta: 1
});
},
onLoad() {
uni.getSystemInfo({
success: function(res) {
console.log(res);
this.scrollH = res.windowHeight - uni.upx2px(100);
}
});
// 根据选项生成列表
this.getData();
},
components: {
loadMore,
uniBadge
},
methods: {
changeTab(index) {
this.tabIndex = index;
},
// 监听滑动
onChangeTab(e) {
console.log(e);
this.changeTab(e.detail.current);
},
// 上拉加载更多
loadMore(index) {
// 获取当前列表
let item = this.userList[index];
// 判断是否处于可加载状态
if (item.loadmore !== '上拉加载更多') return;
// 修改当前列表加载状态
item.loadmore = '加载中...';
// 模拟数据请求
setTimeout(() => {
// 加载数据
item.list = [...item.list, ...item.list];
// 恢复加载状态
this.userList[index].loadmore = '上拉加载更多';
}, 2000)
},
// 获取数据
getData() {
var arr = [];
for (let i = 0; i < this.tabBars.length; i++) {
// 生成列表模板
let obj = {
// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
loadmore: "上拉加载更多",
list: []
}
for (let j = 0; j < this.tabBars[i].num; j++) {
if (j % 3 === 0) {
obj.list.push(test_data1);
}
else if (j % 3 === 1) {
obj.list.push(test_data2);
}
else {
obj.list.push(test_data3);
}
}
arr.push(obj);
}
this.userList = arr;
console.log(this.userList);
}
}
}
script>
<style>
style>
显示:
可以看到,头像、昵称、性别、是否关注、点击效果等均正常显示。
再将其封装为组件,components下新建user-list组件(包含同名目录),如下:
<template>
<view class="flex align-center p-2 border-bottom" hover-class="bg-light">
<image :src="item.avatar" style="width: 100rpx; height: 100rpx;" class="rounded-circle mr-2">image>
<view class="flex flex-column flex-1">
<text class="font-md text-dark">{
{item.username}}text>
<uni-badge :text="item.age" :type="item.sex === 1 ? 'error' : (item.sex === 2 ? 'primary' : 'default')" size="small">
<template v-if="item.sex > 0">
<text class="iconfont text-white font-sm" :class="item.sex === 1 ? 'icon-nvxing' : 'icon-nanxing'" style="margin-right: 5rpx;">text>
template>
uni-badge>
view>
<view class="uni-icon uni-icon-checkbox-filled" :class="item.isFollow ? 'text-light-muted' : 'text-main'">view>
view>
template>
<script>
import uniBadge from '@/components/uni-ui/uni-badge/uni-badge.vue';
export default {
props: {
item: Object,
index: Number
},
components: {
uniBadge
}
}
script>
<style>
style>
pages/user-list/user-list.vue如下:
<template>
<view>
<view class="flex align-center py-2">
<view class="flex-1 flex align-center justify-center" v-for="(item ,index) in tabBars" :key="'tab'+index" :class="index === tabIndex ? 'font-lg font-weight-bold text-main' : 'font-md'"
@click="changeTab(index)">{
{item.name}} <text v-if="item.num > 0" class="ml-2">{
{item.num}}text>view>
view>
<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
<swiper-item v-for="(item, index) in userList" :key="index">
<scroll-view scroll-y="true" :style="'height: '+scrollH+'px;'" @scrolltolower="loadMore(index)">
<template v-if="item.list.length > 0">
<block v-for="(item2, index2) in item.list" :key="index2">
<user-list :item="item2" :index="index2">user-list>
block>
<load-more :loadmore="item.loadmore">load-more>
template>
<template v-else>
<no-thing>no-thing>
template>
scroll-view>
swiper-item>
swiper>
view>
template>
<script>
const test_data1 = {
avatar: '/static/img/userpic/15.jpg',
username: 'Corley',
sex: 1, // 0未知、1女性、2男性
age: 23,
isFollow: true
}
const test_data2 = {
avatar: '/static/img/userpic/7.jpg',
username: 'Casey',
sex: 0, // 0未知、1女性、2男性
age: 15,
isFollow: false
}
const test_data3 = {
avatar: '/static/img/userpic/13.jpg',
username: 'Henry',
sex: 2, // 0未知、1女性、2男性
age: 18,
isFollow: true
}
import loadMore from '@/components/common/load-more.vue';
import userList from '@/components/user-list/user-list.vue';
export default {
data() {
return {
tabIndex: 0,
tabBars: [{
name: '互关',
num: 2
},
{
name: '关注',
num: 0
},
{
name: '粉丝',
num: 5
}
],
// 列表高度
scrollH: 600,
userList: []
}
},
// 监听点击输入框事件
onNavigationBarSearchInputClicked() {
uni.navigateTo({
url: '../search/search',
});
},
// 监听点击按钮事件
onNavigationBarButtonTap() {
uni.navigateBack({
delta: 1
});
},
onLoad() {
uni.getSystemInfo({
success: function(res) {
console.log(res);
this.scrollH = res.windowHeight - uni.upx2px(100);
}
});
// 根据选项生成列表
this.getData();
},
components: {
loadMore,
userList
},
methods: {
changeTab(index) {
this.tabIndex = index;
},
// 监听滑动
onChangeTab(e) {
console.log(e);
this.changeTab(e.detail.current);
},
// 上拉加载更多
loadMore(index) {
// 获取当前列表
let item = this.userList[index];
// 判断是否处于可加载状态
if (item.loadmore !== '上拉加载更多') return;
// 修改当前列表加载状态
item.loadmore = '加载中...';
// 模拟数据请求
setTimeout(() => {
// 加载数据
item.list = [...item.list, ...item.list];
// 恢复加载状态
this.userList[index].loadmore = '上拉加载更多';
}, 2000)
},
// 获取数据
getData() {
var arr = [];
for (let i = 0; i < this.tabBars.length; i++) {
// 生成列表模板
let obj = {
// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
loadmore: "上拉加载更多",
list: []
}
for (let j = 0; j < this.tabBars[i].num; j++) {
if (j % 3 === 0) {
obj.list.push(test_data1);
}
else if (j % 3 === 1) {
obj.list.push(test_data2);
}
else {
obj.list.push(test_data3);
}
}
arr.push(obj);
}
this.userList = arr;
console.log(this.userList);
}
}
}
script>
<style>
style>
效果与之前相同。
现对用户列表页进行进一步优化,页面上下滑动时会出现闪动的情况,这是页面高度设置的问题,指定标签栏样式为style="height: 100rpx;"
即可;
同时用户列表的数据较少时,上拉加载不会触发触底事件,此时可以隐藏上拉加载组件。
如下:
<template>
<view>
<view class="flex align-center" style="height: 100rpx;">
<view class="flex-1 flex align-center justify-center" v-for="(item ,index) in tabBars" :key="'tab'+index" :class="index === tabIndex ? 'font-lg font-weight-bold text-main' : 'font-md'"
@click="changeTab(index)">{
{item.name}} <text v-if="item.num > 0" class="ml-2">{
{item.num}}text>view>
view>
<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
<swiper-item v-for="(item, index) in userList" :key="index">
<scroll-view scroll-y="true" :style="'height: '+scrollH+'px;'" @scrolltolower="loadMore(index)">
<template v-if="item.list.length > 0">
<block v-for="(item2, index2) in item.list" :key="index2">
<user-list :item="item2" :index="index2">user-list>
block>
<template v-if="item.list.length > 10">
<load-more :loadmore="item.loadmore">load-more>
template>
template>
<template v-else>
<no-thing>no-thing>
template>
scroll-view>
swiper-item>
swiper>
view>
template>
<script>
const test_data1 = {
avatar: '/static/img/userpic/15.jpg',
username: 'Corley',
sex: 1, // 0未知、1女性、2男性
age: 23,
isFollow: true
}
const test_data2 = {
avatar: '/static/img/userpic/7.jpg',
username: 'Casey',
sex: 0, // 0未知、1女性、2男性
age: 15,
isFollow: false
}
const test_data3 = {
avatar: '/static/img/userpic/13.jpg',
username: 'Henry',
sex: 2, // 0未知、1女性、2男性
age: 18,
isFollow: true
}
import loadMore from '@/components/common/load-more.vue';
import userList from '@/components/user-list/user-list.vue';
export default {
data() {
return {
tabIndex: 0,
tabBars: [{
name: '互关',
num: 5
},
{
name: '关注',
num: 3
},
{
name: '粉丝',
num: 12
}
],
// 列表高度
scrollH: 600,
userList: []
}
},
// 监听点击输入框事件
onNavigationBarSearchInputClicked() {
uni.navigateTo({
url: '../search/search',
});
},
// 监听点击按钮事件
onNavigationBarButtonTap() {
uni.navigateBack({
delta: 1
});
},
onLoad() {
uni.getSystemInfo({
success: function(res) {
console.log(res);
this.scrollH = res.windowHeight - uni.upx2px(100);
}
});
// 根据选项生成列表
this.getData();
},
components: {
loadMore,
userList
},
methods: {
changeTab(index) {
this.tabIndex = index;
},
// 监听滑动
onChangeTab(e) {
console.log(e);
this.changeTab(e.detail.current);
},
// 上拉加载更多
loadMore(index) {
// 获取当前列表
let item = this.userList[index];
// 判断是否处于可加载状态
if (item.loadmore !== '上拉加载更多') return;
// 修改当前列表加载状态
item.loadmore = '加载中...';
// 模拟数据请求
setTimeout(() => {
// 加载数据
item.list = [...item.list, ...item.list];
// 恢复加载状态
this.userList[index].loadmore = '上拉加载更多';
}, 2000)
},
// 获取数据
getData() {
var arr = [];
for (let i = 0; i < this.tabBars.length; i++) {
// 生成列表模板
let obj = {
// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
loadmore: "上拉加载更多",
list: []
}
for (let j = 0; j < this.tabBars[i].num; j++) {
if (j % 3 === 0) {
obj.list.push(test_data1);
}
else if (j % 3 === 1) {
obj.list.push(test_data2);
}
else {
obj.list.push(test_data3);
}
}
arr.push(obj);
}
this.userList = arr;
console.log(this.userList);
}
}
}
script>
<style>
style>
显示:
可以看到,有一定的优化效果。
聊天界面主要实现文字聊天,先创建页面user-chat,其入口为消息列表msg-list,如下:
<template>
<view class="flex align-center p-2 border-bottom border-" hover-class="bg-light" @click="open()">
<image :src="item.avatar" style="width: 80rpx; height: 80rpx;" class="rounded-circle mr-2">image>
<view class="flex flex-column flex-1">
<view class="flex align-center justify-between">
<text class="font-md">{
{item.username}}text>
<text class="font-sm text-secondry">{
{item.update_time | formatTime}}text>
view>
<view class="flex align-center justify-between">
<text class="text-secondry text-ellipsis" style="max-width: 500rpx;">{
{item.data}}text>
<uni-badge :text="item.noread" type="error">uni-badge>
view>
view>
view>
template>
<script>
import uniBadge from '@/components/uni-ui/uni-badge/uni-badge.vue';
import $T from '@/common/time.js';
export default {
props: {
item: Object,
index: Number
},
// 过滤器
filters: {
formatTime(value) {
return $T.gettime(value);
}
},
components: {
uniBadge
},
methods: {
// 打开聊天页
open() {
uni.navigateTo({
url: '../../pages/user-chat/user-chat'
});
}
}
}
script>
<style>
style>
同时配置pages.json如下:
{
"path" : "pages/user-chat/user-chat",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": false,
"app-plus": {
"bounce":"none",
"titleNView": {
"buttons": [
{
"color":"#333333",
"colorPressed":"#FD597C",
"float":"right",
"fontSize":"20px",
"fontSrc":"/static/iconfont.ttf",
"text": "\ue60b"
}
]
}
}
}
}
可以看到,实现了页面配置。
现进一步实现底部聊天操作条,user-chat.vue如下:
<template>
<view>
<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
<input type="text" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" />
<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;">view>
view>
view>
template>
显示:
可以看到,已经显示出底部消息发送框,并且有动画效果。
聊天消息列表通过scroll-view
组件实现,先实现如下:
<template>
<view>
<scroll-view scroll-y="true" :style="'height:'+scrollH+'px;'">
<view v-for="i in 100" :key="i">message{
{i}}view>
scroll-view>
<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
<input type="text" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" />
<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;">view>
view>
view>
template>
<script>
export default {
data() {
return {
scrollH: 500
}
},
onLoad() {
uni.getSystemInfo({
success(res) {
console.log(res);
this.scrollH = res.windowHeight - uni.upx2px(101);
}
})
},
methods: {
}
}
script>
<style>
style>
显示:
再实现消息,消息包括头像和消息内容,如下:
<template>
<view>
<scroll-view scroll-y="true" :style="'height:'+scrollH+'px;'">
<view class="flex align-start px-2 my-2">
<image src="/static/img/userpic/14.jpg" class="rounded-circle" style="width: 100rpx; height: 100rpx;">image>
<view class="bg-light p-2 rounded mx-2" style="min-width: 100rpx; max-width: 400rpx;">
大佬,你好
view>
view>
<view class="flex align-start px-2" style="flex-direction: row-reverse;">
<image src="/static/img/userpic/11.jpg" class="rounded-circle" style="width: 100rpx; height: 100rpx;">image>
<view class="bg-light p-2 rounded mx-2" style="min-width: 100rpx; max-width: 400rpx;">
你好啊,大佬不敢当
view>
view>
scroll-view>
<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
<input type="text" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" />
<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;">view>
view>
view>
template>
右侧的消息是通过给左侧的消息添加样式flex-direction: row-reverse;
实现的。
可以看到,实现了发送消息的列表效果。
封装组件之前需要先构建数据,如下:
<template>
<view>
<scroll-view scroll-y="true" :style="'height:'+scrollH+'px;'">
<block v-for="(item, index) in list">
<view class="flex align-start px-2 my-2" :style="item.user_id === uid ? 'flex-direction: row-reverse;' : ''">
<image :src="item.avatar" class="rounded-circle" style="width: 100rpx; height: 100rpx;">image>
<view class="bg-light p-2 rounded mx-2" style="min-width: 100rpx; max-width: 400rpx;">
{
{item.data}}
view>
view>
block>
scroll-view>
<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
<input type="text" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" />
<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;">view>
view>
view>
template>
<script>
export default {
data() {
return {
scrollH: 500,
list: [
{
user_id: 2,
username: 'Natalia',
avatar: '/static/img/userpic/14.jpg',
data: '大佬,你好',
type: 'text', // text、image、video、audio、link
create_time: 1612156732
},
{
user_id: 1,
username: 'Corley',
avatar: '/static/img/userpic/11.jpg',
data: '你好啊,大佬不敢当',
type: 'text', // text、image、video、audio、link
create_time: 1612156872
}
],
// 模拟当前登录用户userid
uid: 1
}
},
computed: {
isSelf() {
return this.data
}
},
onLoad() {
uni.getSystemInfo({
success(res) {
console.log(res);
this.scrollH = res.windowHeight - uni.upx2px(101);
}
})
},
methods: {
}
}
script>
<style>
style>
可以达到与之前相同的效果。
再实现封装组件,在components目录下创建user-chat目录,下新建user-chat-list组件,如下:
<template>
<view class="flex align-start px-2 my-2" :style="isSelf ? 'flex-direction: row-reverse;' : ''">
<image :src="item.avatar" class="rounded-circle" style="width: 100rpx; height: 100rpx;">image>
<view class="bg-light p-2 rounded mx-2" style="min-width: 100rpx; max-width: 400rpx;">
{
{item.data}}
view>
view>
template>
<script>
// 模拟当前登录用户userid
const uid = 1;
export default {
props: {
item: Object,
index: Number,
},
computed: {
// 是否是登录用户本人
isSelf() {
return uid === this.item.user_id;
}
},
}
script>
<style>
style>
user-chat.vue如下:
<template>
<view>
<scroll-view scroll-y="true" :style="'height:'+scrollH+'px;'">
<block v-for="(item, index) in list">
<user-chat-list :item="item" :index="index">user-chat-list>
block>
scroll-view>
<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
<input type="text" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" />
<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;">view>
view>
view>
template>
<script>
import userChatList from '@/components/user-chat/user-chat-list.vue';
export default {
data() {
return {
scrollH: 500,
list: [
{
user_id: 2,
username: 'Natalia',
avatar: '/static/img/userpic/14.jpg',
data: '大佬,你好',
type: 'text', // text、image、video、audio、link
create_time: 1612156732
},
{
user_id: 1,
username: 'Corley',
avatar: '/static/img/userpic/11.jpg',
data: '你好啊,大佬不敢当',
type: 'text', // text、image、video、audio、link
create_time: 1612156872
}
]
}
},
components: {
userChatList
},
onLoad() {
uni.getSystemInfo({
success(res) {
console.log(res);
this.scrollH = res.windowHeight - uni.upx2px(101);
}
})
},
methods: {
}
}
script>
<style>
style>
效果与之前相同。
再完善消息时间显示,根据相邻消息对应的时间戳的差来判断是否显示,直接使用time.js中提供的getChatTime(v1, v2)
函数来实现即可,默认时间差为5分钟,user-chat-list.vue如下:
<template>
<view class="">
<template v-if="shortTime">
<view class="flex align-center justify-center py-2 font-sm text-light-muted">
{
{shortTime}}
view>
template>
<view class="flex align-start px-2 my-2" :style="isSelf ? 'flex-direction: row-reverse;' : ''">
<image :src="item.avatar" class="rounded-circle" style="width: 100rpx; height: 100rpx;">image>
<view class="bg-light p-2 rounded mx-2" style="min-width: 100rpx; max-width: 400rpx;">
{
{item.data}}
view>
view>
view>
template>
<script>
// 模拟当前登录用户userid
const uid = 1;
import $T from '@/common/time.js';
export default {
props: {
item: Object,
index: Number,
preTime: [Number, String]
},
computed: {
// 是否是登录用户本人
isSelf() {
return uid === this.item.user_id;
},
// 转化时间
shortTime() {
return $T.getChatTime(this.item.create_time, this.preTime);
}
},
}
script>
<style>
style>
需要父组件向该组件传递上一条消息的时间。
父组件user-chat.vue如下:
<template>
<view>
<scroll-view scroll-y="true" :style="'height:'+scrollH+'px;'">
<block v-for="(item, index) in list">
<user-chat-list :item="item" :index="index" :preTime="index > 0 ? list[index-1].create_time : 0">user-chat-list>
block>
scroll-view>
<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
<input type="text" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" />
<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;">view>
view>
view>
template>
显示:
可以看到,已经实现了显示消息时间,并且是人性化的时间显示。
首先绑定消息输入框的输入内容;
同时绑定发送按钮的点击事件,实现发送消息;
并且实现在输入消息并发送之后,需要清空输入框;
并绑定@confirm
事件,可以在点击完成或前往按钮时触发发送事件;
输入框会部分隐藏消息列表,此时需要修改组件的高度;
在进入聊天页时,需要默认滚到页面最底部,通过scroll-into-view
属性来实现。
如下:
<template>
<view>
<scroll-view scroll-y="true" style="position: absolute; left: 0; top: 0; right: 0; bottom: 100rpx;" :scroll-into-view="scrollInto" scroll-with-animation>
<block v-for="(item, index) in list">
<view :id="'chat'+index">
<user-chat-list :item="item" :index="index" :preTime="index > 0 ? list[index-1].create_time : 0">user-chat-list>
view>
block>
scroll-view>
<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
<input type="text" v-model="content" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" @confirm="submit()" />
<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;" @click="submit()">view>
view>
view>
template>
<script>
import userChatList from '@/components/user-chat/user-chat-list.vue';
export default {
data() {
return {
list: [
{
user_id: 2,
username: 'Natalia',
avatar: '/static/img/userpic/14.jpg',
data: '大佬,你好',
type: 'text', // text、image、video、audio、link
create_time: 1612156712
},
{
user_id: 2,
username: 'Corley',
avatar: '/static/img/userpic/14.jpg',
data: '我想请教一个关于uni-app的问题,不知道是否方便?',
type: 'text',
create_time: 1612156872
},
{
user_id: 1,
username: 'Natalia',
avatar: '/static/img/userpic/11.jpg',
data: '你好啊,大佬不敢当',
type: 'text',
create_time: 1612156905
},
{
user_id: 1,
username: 'Corley',
avatar: '/static/img/userpic/11.jpg',
data: '有什么你就说吧',
type: 'text',
create_time: 1612157023
},
{
user_id: 1,
username: 'Corley',
avatar: '/static/img/userpic/11.jpg',
data: '只要我会的都会解答',
type: 'text',
create_time: 1612157029
},
{
user_id: 2,
username: 'Corley',
avatar: '/static/img/userpic/14.jpg',
data: '有几个问题',
type: 'text',
create_time: 1612157411
},
{
user_id: 2,
username: 'Natalia',
avatar: '/static/img/userpic/14.jpg',
data: '1.在导航栏上单击搜索输入监听搜索框的事件该写在什么位置啊,为什么我写的触发不了?',
type: 'text',
create_time: 1612157439
},
{
user_id: 2,
username: 'Corley',
avatar: '/static/img/userpic/14.jpg',
data: '2.关注顶踩的动画css怎么获取到的啊?',
type: 'text',
create_time: 1612157455
},
{
user_id: 2,
username: 'Natalia',
avatar: '/static/img/userpic/14.jpg',
data: '3.首页开发最后代码写完,再点击关注和点赞,踩,就会报错。辛苦看一看啊',
type: 'text',
create_time: 1612157503
},
{
user_id: 1,
username: 'Corley',
avatar: '/static/img/userpic/11.jpg',
data: '好的,我马上看',
type: 'text',
create_time: 1612157821
}
],
content: '',
scrollInto: ''
}
},
components: {
userChatList
},
// 页面加载完成
onReady() {
this.pageToBottom();
},
methods: {
// 发送消息
submit() {
if (this.content === ''){
return uni.showToast({
title: '消息不能为空',
icon: 'none'
});
}
let obj = {
user_id: 1,
username: 'Corley',
avatar: '/static/img/userpic/11.jpg',
data: this.content,
type: 'text',
create_time: (new Date()).getTime()
}
this.list.push(obj);
// 清空输入框
this.content = '';
// 滚动到底部
this.pageToBottom();
},
// 滚动到底部
pageToBottom() {
let lastIndex = this.list.length - 1;
if (lastIndex < 0) return;
this.scrollInto = 'chat' + lastIndex;
}
}
}
script>
<style>
style>
显示:
可以看到,此时表现更好。
现在还需要完善聊天页导航栏标题,应该是当前聊天的用户名,使用页面消息传递的方式实现,msg-list.vue如下:
open() {
uni.navigateTo({
url: '../../pages/user-chat/user-chat?username=' + this.item.username
});
}
user-chat.vue如下:
onLoad(e) {
console.log(e);
this.username = e.username;
},
// 页面加载完成
onReady() {
this.pageToBottom();
uni.setNavigationBarTitle({
title: this.username
});
},
显示:
此时,顶部导航栏标题是用户名。
使用已经实现好的库和组件可以加速开发,文中使用到了uni-app官方提供的扩展组件uni-badge用于显示消息数、uni-popup用于实现下拉弹出框,同时使用专门的JS库来进行时间处理,不需要自己再造轮子,大大加快了开发。