微信小程序聊天界面实现:从布局到交互

在移动应用开发中,聊天功能是许多社交、客服类小程序的核心模块。本文将基于实际代码,详细讲解如何在微信小程序中实现一个功能完善的聊天界面,包括消息展示、发送、撤回、复制等核心功能,以及键盘适配、自动滚动等细节处理。

先看示例图片效果:
示例效果
一、功能概述
本次实现的聊天界面包含以下核心功能:

区分当前用户与其他用户的消息(左对齐 vs 右对齐)
支持消息时间展示(特定消息显示发送时间)
消息发送功能(文本消息)
长按消息支持复制、撤回操作(撤回仅支持自己 5 分钟内的消息)
自动适配键盘高度变化,保持聊天区域正常显示
消息发送后自动滚动到底部
支持消息撤回状态展示(“你撤回了一条消息”)

二、核心代码结构
整个聊天界面的实现分为三个部分:数据结构设计(JS data)、页面布局(WXML)、样式设计(WXSS)和逻辑处理(JS 方法)。

  1. 数据结构设计(Page.data)
    首先需要定义界面所需的核心数据,主要包括当前用户信息、聊天消息列表、布局计算参数等:
data: {
  // 当前登录用户信息
  loginInfo: {
    'userId': '20258888',
    'nick': '西红柿炒番茄',
    'avatar': '/static/images/local/logo.jpg',
  },
  // 聊天消息列表
  dataList: [{
      'chatId': '1752843999000', // 消息唯一标识(时间戳)
      'userId': '20250718',      // 发送者ID
      'nick': '蒜鸟编程',         // 发送者昵称
      'avatar': 'https://xxx.jpg',// 发送者头像
      'status': '0',             // 消息状态(0-正常,1-撤回)
      'showTime': '21:06',       // 显示时间(仅部分消息展示)
      'content': '欢迎关注蒜鸟编程!', // 消息内容
      'createDate': 1752843999000 // 创建时间戳
    },
    // ...更多消息
  ],
  msgInfo: '', // 输入框内容
  toolInfo: '', // 长按操作框信息
  scrollToId: '', // 滚动到指定元素ID
  // 布局计算参数
  footHeight: 36,    // 底部区域高度
  chatHeight: 540,   // 聊天区域高度
  viewHeight: 640,   // 窗口总高度
  boardHeight: 0,    // 键盘高度
  lineCount: 1       // 输入框行数
}

核心字段说明:

  • status:用于区分消息状态,0为正常消息,1为撤回消息
  • createDate:用于判断消息是否在撤回时效内(5 分钟)
  • 布局参数:用于动态计算聊天区域高度,适配不同设备和键盘状态

2. 页面布局实现(WXML)
聊天界面的布局核心是scroll-view滚动容器,配合消息列表渲染和底部输入区域:

<!-- 聊天消息区域 -->
<scroll-view 
  scroll-y="{{true}}" 
  scroll-into-view="{{scrollToId}}" 
  bind:tap="onPageTap" 
  style="height: {{chatHeight}}px;"
>
  <view class="seat"></view>
  <!-- 循环渲染消息列表 -->
  <block wx:for="{{dataList}}" wx:for-item="item" wx:key="item.chatId">
    <!-- 时间展示(仅当消息有showTime时显示) -->
    <view class="row-date" hidden="{{!item.showTime}}">
      <text>{{item.showTime}}</text>
    </view>
    
    <!-- 撤回消息展示 -->
    <view class="row-back" wx:if="{{item.status=='1'}}">
      <text wx:if="{{item.userId==loginInfo.userId}}">你撤回了一条消息</text>
      <text wx:else>“{{item.nick}}”撤回了一条消息</text>
    </view>
    
    <!-- 正常消息展示 -->
    <view class="row {{item.userId==loginInfo.userId?'row-r':''}}" wx:else>
      <!-- 头像区域 -->
      <view class="row-left">
        <image class="row-avatar" src="{{item.avatar}}" mode="widthFix"></image>
      </view>
      <!-- 消息内容区域 -->
      <view class="row-right">
        <!-- 对方昵称(仅显示他人消息的昵称) -->
        <view class="row-nick" wx:if="{{item.userId!=loginInfo.userId}}">{{item.nick}}</view>
        <!-- 消息内容(绑定长按事件) -->
        <view class="row-info {{item.userId==loginInfo.userId?'info-r':'info-l'}}" 
              bind:longpress="toolClick" 
              data-item="{{item}}">
          {{item.content}}
        </view>
        <!-- 长按操作框(复制/撤回) -->
        <view class="level tool-box {{toolInfo.boxRight+' '+toolInfo.boxBottom}}" 
              wx:if="{{toolInfo.chatId == item.chatId}}">
          <view class="tool-trans {{toolInfo.trans}}"></view>
          <view class="tool" bind:tap="buttonClick" data-type="copy">
            <text>复制</text>
          </view>
          <view class="tool" bind:tap="buttonClick" data-type="back" wx:if="{{toolInfo.showBack}}">
            <text>撤回</text>
          </view>
        </view>
      </view>
    </view>
  </block>
  <!-- 用于滚动到底部的锚点 -->
  <view id="endView" class="seat"></view>
</scroll-view>

<!-- 底部输入区域 -->
<view class="foot-box" id="bottomArea" bind:tap="onPageTap">
  <view class="level {{lineCount>2?'input-box':''}}">
    <view class="input-area">
      <textarea class="input-text" 
                bindinput="inputClick" 
                bindkeyboardheightchange="getKeyBoardHeight" 
                bindlinechange="lineClick" 
                value="{{msgInfo}}" 
                show-confirm-bar="{{false}}" 
                hold-keyboard="{{true}}" 
                adjust-keyboard-to="bottom" 
                cursor-spacing="{{20}}" 
                confirm-type="return" 
                auto-height="{{true}}"></textarea>
    </view>
    <view class="input-btn">
      <text bind:tap="sendTextMessage" class="input-send">发 送</text>
    </view>
  </view>
</view>

布局核心要点:

  • 使用scroll-view实现消息列表滚动,通过scroll-into-view实现自动滚动到底部
  • 用wx:for循环渲染消息,通过wx:if区分撤回消息和正常消息
  • 通过item.userId==loginInfo.userId判断消息发送者,动态添加样式类(左 / 右对齐)
  • 长按消息时显示操作框(复制 / 撤回),通过toolInfo.chatId控制显示哪个消息的操作框
  • 底部textarea支持自动高度调整、键盘高度监听,适配输入体验

3. 样式设计(WXSS)
样式设计需要解决消息区分显示、操作框定位、响应式布局等问题:

/* 基础样式 */
page {
  background-color: white;
  overflow: hidden;
}

.seat {
  height: 10rpx; /* 占位元素,避免滚动到边缘时内容被遮挡 */
}

/* 消息行样式 */
.row {
  display: flex;
  margin-bottom: 20rpx;
}
.row-r {
  flex-direction: row-reverse; /* 自己的消息右对齐 */
  text-align: right;
}

/* 头像样式 */
.row-left {
  width: 15%;
  text-align: center;
}
.row-avatar {
  width: 80rpx;
  height: 80rpx;
  border-radius: 10rpx;
}

/* 时间样式 */
.row-date {
  text-align: center;
  font-size: 26rpx;
  color: #616060;
  margin: 20rpx 0;
}
.row-date text {
  padding: 3rpx 15rpx;
  border-radius: 5rpx;
  background-color: #e9e9e9;
}

/* 撤回消息样式 */
.row-back {
  text-align: center;
  font-size: 26rpx;
  color: #616060;
  margin: 20rpx 0;
}

/* 消息内容区域 */
.row-right {
  width: 70%;
  position: relative; /* 用于操作框绝对定位 */
}
.row-nick {
  font-size: 24rpx;
  margin-bottom: 10rpx;
  color: #6a6b6e;
}
.row-info {
  font-size: 30rpx;
  border-radius: 10rpx;
  padding: 20rpx;
  color: white;
  display: inline-block;
  text-align: left;
}
.info-l {
  background-color: #377AF6; /* 他人消息蓝色背景 */
}
.info-r {
  background-color: #18b566; /* 自己消息绿色背景 */
}

/* 长按操作框样式 */
.tool-box {
  position: absolute;
  z-index: 9;
  background-color: #818181;
  color: white;
  padding: 15rpx 10rpx;
  border-radius: 10rpx;
}
.tool {
  margin: 0 20rpx;
  display: flex;
  flex-direction: column;
  text-align: center;
  font-size: 26rpx;
}
/* 操作框定位 */
.tool-r { right: 10rpx; }
.tool-t { top: -120rpx; }
.tool-b { bottom: -120rpx; }

/* 操作框三角指示器 */
.tool-trans {
  width: 0;
  height: 0;
  position: absolute;
  border-left: 14rpx solid transparent;
  border-right: 14rpx solid transparent;
}
.trans-tlf { /* 上方操作框的下三角 */
  bottom: -17rpx;
  border-top: 20rpx solid #858383;
}
.trans-blf { /* 下方操作框的上三角 */
  top: -17rpx;
  border-bottom: 20rpx solid #858383;
}
.trans-r { right: 20rpx; }
.trans-l { left: 20rpx; }

/* 底部输入区域样式 */
.foot-box {
  background-color: #f1f1f1;
  padding: 10rpx;
}
.level {
  display: flex;
  flex-direction: row;
  align-items: center;
}
.input-box {
  align-items: flex-end; /* 输入框多行时底部对齐 */
}
.input-area {
  width: 84%;
  padding: 20rpx;
  background-color: white;
  border-radius: 10rpx;
}
.input-text {
  width: 100%;
  font-size: 32rpx;
  max-height: 300rpx; /* 限制输入框最大高度 */
}
.input-btn {
  width: 16%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.input-send {
  color: white;
  font-size: 30rpx;
  padding: 15rpx;
  background-color: #fa4242;
  border-radius: 10rpx;
}

/* 隐藏滚动条 */
::-webkit-scrollbar {
  width: 0;
  height: 0;
}

样式设计核心:

  • 通过flex布局实现消息左 / 右对齐
  • 用position: absolute实现长按操作框的动态定位
  • 用 CSS 边框特性实现操作框的三角指示器(小箭头)
  • 隐藏滚动条提升视觉体验
  • 限制输入框最大高度,避免输入内容过多时界面变形

4. 逻辑处理(JS 方法)
逻辑处理主要解决消息发送、操作交互、布局适配等问题,核心方法如下:
(1)初始化布局计算
在页面加载完成后,计算聊天区域高度(适配不同设备屏幕):

onReady() {
  let query = wx.createSelectorQuery();
  let sys = wx.getSystemInfoSync().windowHeight; // 获取屏幕高度
  // 查询底部区域高度
  query.select('#bottomArea').boundingClientRect((res) => {
    let top = res.height;
    this.setData({
      footHeight: top,
      chatHeight: sys - top, // 聊天区域高度 = 屏幕高度 - 底部高度
      viewHeight: sys,
      scrollToId: 'endView' // 初始滚动到底部
    });
  }).exec();
}

(2)消息发送功能
处理文本消息发送,更新消息列表并滚动到底部:

sendTextMessage() {
  const msg = this.data.msgInfo.trim();
  if (!msg) return; // 空消息不发送
  
  const { viewHeight, footHeight, boardHeight, loginInfo, dataList } = this.data;
  let time = new Date().getTime(); // 用时间戳作为消息唯一ID
  // 构造新消息对象
  let newMsg = [{
    ...loginInfo,
    'chatId': time,
    'status': '0', // 正常消息状态
    'content': msg,
    'createDate': time
  }];
  // 更新数据
  this.setData({
    msgInfo: '', // 清空输入框
    chatHeight: viewHeight - footHeight - boardHeight, // 调整聊天区域高度
    dataList: [...dataList, ...newMsg] // 添加新消息
  }, () => {
    this.scrollToBottom(); // 发送后滚动到底部
  });
}

(3)长按消息操作(复制 / 撤回)
处理消息长按事件,显示操作框并计算定位:

toolClick(e) {
  const row = e.currentTarget.dataset.item;
  if (!row.status) return; // 仅处理正常消息
  
  const top = e.changedTouches[0].clientY; // 点击位置Y坐标
  const flag = row.userId == this.data.loginInfo.userId; // 是否是自己的消息
  
  // 计算操作框定位(左右)
  row.boxRight = flag ? 'tool-r' : '';
  // 计算操作框定位(上下,点击位置过小时显示在下方)
  row.boxBottom = top < 100 ? 'tool-b' : 'tool-t';
  // 计算三角指示器位置
  if (top < 100) {
    row.trans = flag ? 'trans-blf trans-r' : 'trans-blf trans-l';
  } else {
    row.trans = flag ? 'trans-tlf trans-r' : 'trans-tlf trans-l';
  }
  // 判断是否允许撤回(自己的消息且发送时间<5分钟)
  row.showBack = ((Date.now() - row.createDate) < 5 * 60 * 1000) && flag;
  
  this.setData({
    toolInfo: row // 显示操作框
  });
}

(4)复制与撤回实现
处理操作框的点击事件,执行复制或撤回操作:

buttonClick(e) {
  const tag = e.currentTarget.dataset.type;
  const obj = this.data.toolInfo;
  
  if (tag == 'copy') {
    // 复制消息内容到剪贴板
    wx.setClipboardData({
      data: obj.content,
    })
    return;
  }
  
  // 撤回消息(更新消息状态为1)
  const index = this.data.dataList.findIndex(item => item.chatId === obj.chatId);
  if (index !== -1) {
    this.setData({
      [`dataList[${index}].status`]: '1' // 用数据路径更新指定消息状态
    });
  }
}

(5)键盘适配与滚动控制
处理键盘高度变化,动态调整聊天区域高度,并确保消息滚动到底部:

// 键盘高度变化时触发
getKeyBoardHeight(e) {
  let board = e.detail.height || 0;
  const { viewHeight, footHeight } = this.data;
  this.setData({
    chatHeight: viewHeight - footHeight - board, // 聊天区域高度 = 屏幕高度 - 底部高度 - 键盘高度
    boardHeight: board
  })
  this.scrollToBottom(); // 键盘弹出后滚动到底部
},

// 滚动到最底部
scrollToBottom() {
  this.setData({
    scrollToId: 'endView' // 通过scroll-into-view滚动到endView
  })
}

三、关键技术点解析

1、动态布局计算

通过wx.getSystemInfoSync()获取屏幕高度,结合wx.createSelectorQuery()查询元素尺寸,动态计算聊天区域高度,确保在不同设备和键盘状态下都能正常显示。

2、消息定位与样式区分

通过userId判断消息发送者,动态添加样式类实现左 / 右对齐;用status字段区分正常消息和撤回消息,分别渲染不同 UI。

3、长按操作框定位逻辑

根据点击位置的 Y 坐标(clientY)和消息发送者(自己 / 他人),动态计算操作框的上下左右位置,确保操作框不会超出屏幕范围。

4、自动滚动到底部

通过scroll-view的scroll-into-view属性,每次发送消息或键盘弹出时,将scrollToId设为底部锚点endView,实现自动滚动。

5、textarea 适配

通过auto-height属性实现输入框高度自适应,bindlinechange监听行数变化调整布局,bindkeyboardheightchange适配键盘高度。

四、扩展与优化建议

1、功能扩展

  • 支持发送图片、表情、语音等多媒体消息
  • 添加消息状态显示(发送中、已发送、已读)
  • 实现消息分页加载(历史消息下拉加载)

2、性能优化

  • 对长列表消息使用virtual-list虚拟列表,减少 DOM 节点
  • 图片懒加载,避免一次性加载过多头像
  • 消息数据本地缓存(wx.setStorageSync),提升页面加载速度

3、体验优化

  • 添加消息发送动画
  • 支持消息撤回动画过渡
  • 键盘弹出时自动聚焦到输入框

五、总结

本文实现的聊天界面涵盖了消息展示、发送、撤回、复制等核心功能,并通过动态布局计算、键盘适配、自动滚动等细节处理,提供了良好的用户体验。代码结构清晰,逻辑明确,可作为小程序聊天功能开发的基础模板,根据实际需求进行扩展即可。

最后贴出详细的完整代码

下面是完整的代码,可以直接复制到微信小程序项目中使用:

1.js代码:

Page({
  data: {
    loginInfo: {
      'userId': '20258888',
      'nick': '西红柿炒番茄',
      'avatar': '/static/images/local/logo.jpg',
    },
    dataList: [{
        'chatId': '1752843999000',
        'userId': '20250718',
        'nick': '蒜鸟编程',
        'avatar': 'https://img1.baidu.com/it/u=143492308,3605851733&fm=253&app=120&f=JPEG?w=800&h=800',
        'status': '0',
        'showTime': '21:06',
        'content': '欢迎关注蒜鸟编程!',
        'createDate': 1752843999000
      },
      {
        'chatId': '1752844119000',
        'userId': '20258888',
        'nick': '西红柿炒番茄',
        'avatar': '/static/images/local/logo.jpg',
        'status': '0',
        'content': '哈喽',
        'createDate': 1752844119000
      },
      {
        'chatId': '1752844179000',
        'userId': '20258888',
        'nick': '西红柿炒番茄',
        'avatar': '/static/images/local/logo.jpg',
        'status': '0',
        'content': '还在吗?',
        'createDate': 1752844179000
      },
      {
        'chatId': '1752844299000',
        'userId': '20250718',
        'nick': '蒜鸟编程',
        'avatar': 'https://img1.baidu.com/it/u=143492308,3605851733&fm=253&app=120&f=JPEG?w=800&h=800',
        'status': '0',
        'showTime': '21:11',
        'content': '在的呀',
        'createDate': 1752844299000
      },
      {
        'chatId': '1752844479000',
        'userId': '20250707',
        'nick': '公众号',
        'avatar': 'https://q6.itc.cn/q_70/images03/20250306/355fba6a5cb049f5b98c2ed9f03cc5e1.jpeg',
        'status': '0',
        'content': '我是公众号,嘿嘿,欢迎关注我呦~',
        'createDate': 1752844479000
      },
      {
        'chatId': '1752844659000',
        'userId': '20258888',
        'nick': '西红柿炒番茄',
        'avatar': '/static/images/local/logo.jpg',
        'status': '1',
        'showTime': '21:17',
        'content': '撤回的消息',
        'createDate': 1752844659000
      },
      {
        'chatId': '1752844839000',
        'userId': '20258888',
        'nick': '西红柿炒番茄',
        'avatar': '/static/images/local/logo.jpg',
        'status': '0',
        'content': '谢谢大家',
        'createDate': 1752844839000
      },
    ],
    msgInfo: '',
    toolInfo: '',
    scrollToId: '',
    // 键盘计算相关
    footHeight: 36,
    chatHeight: 540,
    viewHeight: 640,
    boardHeight: 0,
    lineCount: 1
  },

  onReady() {
    let query = wx.createSelectorQuery();
    let sys = wx.getSystemInfoSync().windowHeight;
    query.select('#bottomArea').boundingClientRect((res) => {
      let top = res.height;
      this.setData({
        footHeight: top,
        chatHeight: sys - top,
        viewHeight: sys,
        scrollToId: 'endView'
      });
    }).exec();
  },

  // 页面点击监听
  onPageTap() {
    this.toolMethod();
  },

  toolMethod() {
    this.setData({
      toolInfo: ''
    })
  },

  // 发送文本
  sendTextMessage() {
    const msg = this.data.msgInfo.trim();
    if (!msg) return;
    const {
      viewHeight,
      footHeight,
      boardHeight,
      loginInfo,
      dataList
    } = this.data;
    let time = new Date().getTime();
    let attr = [{
      ...loginInfo,
      'chatId': time,
      'status': '0',
      'content': msg,
      'createDate': time
    }];
    this.setData({
      msgInfo: '',
      chatHeight: viewHeight - footHeight - boardHeight,
      dataList: [...dataList, ...attr]
    }, () => {
      this.scrollToBottom();
    });
  },

  // 长按监听
  toolClick(e) {
    const row = e.currentTarget.dataset.item;
    if (!row.status) {
      return;
    }
    const top = e.changedTouches[0].clientY;
    const flag = row.userId == this.data.loginInfo.userId;
    row.boxRight = flag ? 'tool-r' : '';
    row.boxBottom = top < 100 ? 'tool-b' : 'tool-t';
    if (top < 100) {
      row.trans = flag ? 'trans-blf trans-r' : 'trans-blf trans-l';
    } else {
      row.trans = flag ? 'trans-tlf trans-r' : 'trans-tlf trans-l';
    }
    row.showBack = ((Date.now() - row.createDate) < 5 * 60 * 1000) && flag;
    this.setData({
      toolInfo: row
    });
  },

  // 更新聊天记录【撤回、复制】
  buttonClick(e) {
    const tag = e.currentTarget.dataset.type;
    const obj = this.data.toolInfo;
    if (tag == 'copy') {
      wx.setClipboardData({
        data: obj.content,
      })
      return;
    }
    const index = this.data.dataList.findIndex(item => item.chatId === obj.chatId);
    if (index !== -1) {
      this.setData({
        [`dataList[${index}].status`]: '1'
      });
    }
  },

  // 回车换行监听
  lineClick(e) {
    let obj = e.detail;
    let top = obj.height + 10;
    if (obj.lineCount > 1 && obj.lineCount < 8) {
      const {
        viewHeight,
        footHeight,
        boardHeight
      } = this.data;
      this.setData({
        chatHeight: viewHeight - footHeight - boardHeight - top,
        lineCount: obj.lineCount
      }, () => {
        this.scrollToBottom();
      })
    }
  },

  // 键盘高度
  getKeyBoardHeight(e) {
    let board = e.detail.height || 0;
    const {
      viewHeight,
      footHeight
    } = this.data;
    this.setData({
      chatHeight: viewHeight - footHeight - board,
      boardHeight: board
    })
    this.scrollToBottom();
  },

  //输入时数据绑定
  inputClick(e) {
    this.setData({
      msgInfo: e.detail.value
    });
  },

  // 滑动到最底部
  scrollToBottom() {
    this.setData({
      scrollToId: 'endView'
    })
  },
})

2.wxml代码:

<scroll-view scroll-y="{{true}}" bindscrolltoupper="scrollToupperClick" scroll-into-view="{{scrollToId}}" bind:tap="onPageTap" style="height: {{chatHeight}}px;">
  <view class="seat"></view>
  <block wx:for="{{dataList}}" wx:for-item="item" wx:for-index="index" wx:key="item">
    <view class="row-date" hidden="{{!item.showTime}}">
      <text>{{item.showTime}}</text>
    </view>
    <view class="row-back" wx:if="{{item.status=='1'}}">
      <text wx:if="{{item.userId==loginInfo.userId}}">你撤回了一条消息</text>
      <text wx:else>“{{item.nick}}”你撤回了一条消息</text>
    </view>
    <view class="row {{item.userId==loginInfo.userId?'row-r':''}}" wx:else>
      <view class="row-left">
        <image class="row-avatar" src="{{item.avatar}}" mode="widthFix"></image>
      </view>
      <view class="row-right">
        <view class="row-nick" wx:if="{{item.userId!=loginInfo.userId}}">{{item.nick}}</view>
        <view class="row-info {{item.userId==loginInfo.userId?'info-r':'info-l'}}" bind:longpress="toolClick" data-item="{{item}}">{{item.content}}
        </view>
        <view class="level tool-box {{toolInfo.boxRight+' '+toolInfo.boxBottom}}" wx:if="{{toolInfo.chatId == item.chatId}}">
          <view class="tool-trans {{toolInfo.trans}}"></view>
          <view class="tool" bind:tap="buttonClick" data-type="copy">
            <text>复制</text>
          </view>
          <view class="tool" bind:tap="buttonClick" data-type="back" wx:if="{{toolInfo.showBack}}">
            <text>撤回</text>
          </view>
        </view>
      </view>
    </view>
  </block>
  <view id="endView" class="seat"></view>
</scroll-view>
<!-- 键盘 -->
<view class="foot-box" id="bottomArea" bind:tap="onPageTap">
  <view class="level {{lineCount>2?'input-box':''}}">
    <view class="input-area">
      <textarea class="input-text" bindinput="inputClick" bindkeyboardheightchange="getKeyBoardHeight" bindlinechange="lineClick" value="{{msgInfo}}" show-confirm-bar="{{false}}" hold-keyboard="{{true}}" adjust-keyboard-to="bottom" cursor-spacing="{{20}}" confirm-type="return" confirm-hold="{{true}}" disable-default-padding="{{true}}" adjust-position="{{false}}" auto-height="{{true}}"></textarea>
    </view>
    <view class="input-btn">
      <text bind:tap="sendTextMessage" class="input-send">发 送</text>
    </view>
  </view>
</view>

3.wxss代码:

page {
  background-color: white;
  overflow: hidden;
}

.seat {
  height: 10rpx;
}

.row {
  display: flex;
  margin-bottom: 20rpx;
}

.row-r {
  flex-direction: row-reverse;
  text-align: right;
}

.row-left {
  width: 15%;
  text-align: center;
}

.row-avatar {
  width: 80rpx;
  height: 80rpx;
  border-radius: 10rpx;
}

.row-date,
.row-back {
  text-align: center;
  font-size: 26rpx;
  color: #616060;
  margin-top: 20rpx;
  margin-bottom: 20rpx;
}

.row-date text {
  padding: 3rpx 15rpx;
  border-radius: 5rpx;
  background-color: #e9e9e9;
}

.row-right {
  width: 70%;
  position: relative;
}

.row-nick {
  font-size: 24rpx;
  margin-bottom: 10rpx;
  color: #6a6b6e;
}

.row-info {
  font-size: 30rpx;
  border-radius: 10rpx;
  padding: 20rpx;
  color: white;
  display: inline-block;
  text-align: left;
}

.info-l {
  background-color: #377AF6;
}

.info-r {
  background-color: #18b566;
}

/* 弹出 */
.tool-box {
  position: absolute;
  z-index: 9;
  background-color: #818181;
  color: white;
  padding: 15rpx 10rpx;
  border-radius: 10rpx;
}

.tool {
  margin: 0 20rpx;
  display: flex;
  flex-direction: column;
  text-align: center;
  font-size: 26rpx;
}

.tool-r {
  right: 10rpx;
}

.tool-t {
  top: -120rpx;
}

.tool-b {
  bottom: -120rpx;
}

.tool-trans {
  width: 0;
  height: 0;
  position: absolute;
  text-align: center;
  border-left: 14rpx solid transparent;
  border-right: 14rpx solid transparent;
}

.trans-tlf {
  bottom: -17rpx;
  border-top: 20rpx solid #858383;
}

.trans-blf {
  top: -17rpx;
  border-bottom: 20rpx solid #858383;
}

.trans-r {
  right: 20rpx;
}

.trans-l {
  left: 20rpx;
}

/* 底部键盘 */
.foot-box {
  background-color: #f1f1f1;
  padding: 10rpx;
}

.level {
  display: flex;
  flex-direction: row;
  align-items: center;
}

.input-box {
  align-items: flex-end;
}

.input-area {
  width: 84%;
  padding: 20rpx;
  background-color: white;
  border-radius: 10rpx;
  color: #6a6b6e;
}

.input-text {
  width: 100%;
  font-size: 32rpx;
  max-height: 300rpx;
}

.input-btn {
  width: 16%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.input-send {
  color: white;
  font-size: 30rpx;
  padding: 15rpx;
  background-color: #fa4242;
  border-radius: 10rpx;
}

::-webkit-scrollbar {
  width: 0;
  height: 0;
}

4、json代码:

{
  "navigationBarTitleText": "聊天窗口升级版",
  "usingComponents": {},
  "navigationBarTextStyle":"white",
  "navigationBarBackgroundColor": "#2b85e4"
}

更多示例,请关注 蒜鸟编程(微信小程序 / 抖音 / 小红书/微信公众号/微博****等同名称),在这里,编程与生活的边界从未如此清晰,期待与大家共同成长!后续将围绕编程技术持续更新更多实践示例,如果在实现过程中遇到问题,或者有更好的优化建议,欢迎在评论区交流讨论!

更多推荐