Taro聊天室|react+taro仿微信聊天App界面|taro聊天实例

react

一、项目简述

taro-chatroom是基于Taro多端实例聊天项目,运用Taro+react+react-redux+taroPop+react-native等技术开发的仿微信App界面聊天室,实现了发送消息/emoj表情、gif表情大图、图片预览、发红包、动态圈等功能。

二、预览效果

编译到H5端、小程序、App端效果如下:(后续大图均为APP端)

三、技术栈

  • 编码/技术:Vscode + react/taro/redux/RN
  • iconfont图标:阿里字体图标库
  • 自定义导航栏Navigation + 底部Tabbar
  • 弹窗组件:taroPop(基于Taro封装自定义模态框)
  • 支持编译:H5端 + 小程序 + App端

/**

* @desc Taro入口页面 app.jsx
* @about Q:282310962 wx:xy190310

*/

import Taro, { Component } from \'@tarojs/taro\'

import Index from \'./pages/index\'

// 引入状态管理redux

import { Provider } from \'@tarojs/redux\'

import { store } from \'./store\'

// 引入样式

import \'./app.scss\'

import \'./styles/fonts/iconfont.css\'

import \'./styles/reset.scss\'

class App extends Component {

config = {

pages: [

\'pages/auth/login/index\',

\'pages/auth/register/index\',

\'pages/index/index\',

...

],

window: {

backgroundTextStyle: \'light\',

navigationBarBackgroundColor: \'#fff\',

navigationBarTitleText: \'TaroChat\',

navigationBarTextStyle: \'black\',

navigationStyle: \'custom\'

}

}

// 在 App 类中的 render() 函数没有实际作用

// 请勿修改此函数

render () {

return (

<Provider store={store}>

<Index />

</Provider>

)

}

}

Taro.render(<App />, document.getElementById(\'app\'))

◆ Taro自定义顶部Navigation导航条 + Tabbar菜单

  • 由于项目需求,顶部导航栏/底部tabbar均为自定义组件,详情见:Taro多端自定义导航栏Navbar+Tabbar实例
  • 项目中用到的弹窗插件是基于Taro自定义模态框组件,参看:Taro自定义模态框组件

◆ 登录/注册验证模块

return (

<View className="taro__container flexDC bg-eef1f5">

<Navigation background=\'#eef1f5\' fixed />

<ScrollView className="taro__scrollview flex1" scrollY>

<View className="auth-lgreg">

{/* logo */}

<View className="auth-lgreg__slogan">

<View className="auth-lgreg__slogan-logo">

<Image className="auth-lgreg__slogan-logo__img" src={require(\'../../../assets/taro.png\')} mode="aspectFit" />

</View>

<Text className="auth-lgreg__slogan-text">欢迎来到Taro-Chatroom</Text>

</View>

{/* 表单 */}

<View className="auth-lgreg__forms">

<View className="auth-lgreg__forms-wrap">

<View className="auth-lgreg__forms-item">

<Input className="auth-lgreg__forms-iptxt flex1" placeholder="请输入手机号/昵称" onInput={this.handleInput.bind(this, \'tel\')} />

</View>

<View className="auth-lgreg__forms-item">

<Input className="auth-lgreg__forms-iptxt flex1" placeholder="请输入密码" password onInput={this.handleInput.bind(this, \'pwd\')} />

</View>

</View>

<View className="auth-lgreg__forms-action">

<TouchView onClick={this.handleSubmit}><Text className="auth-lgreg__forms-action__btn">登录</Text></TouchView>

</View>

<View className="auth-lgreg__forms-link">

<Text className="auth-lgreg__forms-link__nav">忘记密码</Text>

<Text className="auth-lgreg__forms-link__nav" onClick={this.GoToRegister}>注册账号</Text>

</View>

</View>

</View>

</ScrollView>

<TaroPop ref="taroPop" />

</View>

)

/**

* @tpl 登录模块

*/

import Taro from \'@tarojs/taro\'

import { View, Text, ScrollView, Image, Input, Button } from \'@tarojs/components\'

import \'./index.scss\'

import { connect } from \'@tarojs/redux\'

import * as actions from \'../../../store/action\'...

class Login extends Taro.Component {

config = {

navigationBarTitleText: \'登录\'

}

constructor(props) {

super(props)

this.state = {

tel: \'\',

pwd: \'\',

}

}

componentWillMount() {

// 判断是否登录

storage.get(\'hasLogin\').then(res => {

if(res && res.hasLogin) {

Taro.navigateTo({url: \'/pages/index/index\'})

}

})

}

// 提交表单

handleSubmit = () => {

let taroPop = this.refs.taroPop

let { tel, pwd } = this.state

if(!tel) {

taroPop.show({content: \'手机号不能为空\', time: 2})

}else if(!util.checkTel(tel)) {

taroPop.show({content: \'手机号格式有误\', time: 2})

}else if(!pwd) {

taroPop.show({content: \'密码不能为空\', time: 2})

}else {

// ...接口数据

...

storage.set(\'hasLogin\', { hasLogin: true })

storage.set(\'user\', { username: tel })

storage.set(\'token\', { token: util.setToken() })

taroPop.show({

skin: \'toast\',

content: \'登录成功\',

icon: \'success\',

time: 2

})

...

}

}

render () {

...

}

}

const mapStateToProps = (state) => {

return {...state.auth}

}

export default connect(mapStateToProps, {

...actions

})(Login)

taro本地存储使用的是异步存储,由于同步存储不支持RN端

/**

* @desc Taro本地存储

*/

import Taro from \'@tarojs/taro\'

export default class Storage {

static get(key) {

return Taro.getStorage({ key }).then(res => res.data).catch(() => \'\')

}

static set(key, data){

return Taro.setStorage({key: key, data: data}).then(res => res)

}

static del(key){

Taro.removeStorage({key: key}).then(res => res)

}

static clear(){

Taro.clearStorage()

}

}

如不希望编译到RN端,使用如下包裹即可

 /*postcss-pxtransform rn eject enable*/ 

 /*postcss-pxtransform rn eject disable*/ 

对于一些RN端不兼容样式,边框、超过多行...,需特殊样式处理

/* 

* 对于不兼容的样式,如RN不兼容border-right,可以通过mixin统一处理

*/

/**

* RN 不支持针对某一边设置 style,即 border-bottom-style 会报错

* 那么 border-bottom: 1px 就需要写成如下形式: border: 0 style color; border-bottom-width: 1px;

*/

@mixin border($dir, $width, $style, $color) {

border: 0 $style $color;

@each $d in $dir {

#{border-#{$d}-width}: $width;

}

}

/**

* NOTE RN 无法通过 text-overflow 实现省略号,这些代码不会编译到 RN 中

*/

@mixin ellipsis {

/*postcss-pxtransform rn eject enable*/

overflow: hidden; white-space: nowrap; text-overflow: ellipsis;

/*postcss-pxtransform rn eject disable*/

}

/**

* NOTE 实现多行文本省略,RN 用 Text 标签的 numberOfLines={2},H5/小程序用 -webkit-line-clamp

*/

@mixin clamp($line) {

/*postcss-pxtransform rn eject enable*/

display: -webkit-box;

overflow: hidden;

-webkit-line-clamp:$line;

/* autoprefixer: ignore next */

-webkit-box-orient: vertical;

/*postcss-pxtransform rn eject disable*/

}

/**

* 对于不能打包到 RN 的样式,可以用 postcss 方式引入

*/

@mixin eject($attr, $value) {

/*postcss-pxtransform rn eject enable*/

#{$attr}: $value;

/*postcss-pxtransform rn eject disable*/

}

◆ Taro聊天实现消息滚动到底部

在taro中实现聊天消息滚动到最底部,由于RN端不支持 createSelectorQuery,如是做了兼容处理。

componentDidMount() {

if(process.env.TARO_ENV === \'rn\') {

this.scrollMsgBottomRN()

}else {

this.scrollMsgBottom()

}

}

// 滚动至聊天底部

scrollMsgBottom = () => {

let query = Taro.createSelectorQuery()

query.select(\'#scrollview\').boundingClientRect()

query.select(\'#msglistview\').boundingClientRect()

query.exec((res) => {

// console.log(res)

if(res[1].height > res[0].height) {

this.setState({ scrollTop: res[1].height - res[0].height })

}

})

}

scrollMsgBottomRN = (t) => {

let that = this

this._timer = setTimeout(() => {

that.refs.ScrollViewRN.scrollToEnd({animated: false})

}, t ? 16 : 0)

}

聊天中表情使用的是emoj表情符,如果使用图片做表情也是可以,不过需要一些特殊匹配处理 :12)  [:高兴],使用emoj就相对简单些罢了。

// 渲染消息记录

renderMsgTpl = (data) => {

return data.map((item, index) => (

<View key={index}>

{item.msgtype == 1 &&

<View className="msgitem msg__time"><Text className="msg__text">{item.msg}</Text></View>

}

{item.msgtype == 2 &&

<View className="msgitem msg__notice"><Text className="msg__text">{item.msg}</Text></View>

}

{item.msgtype == 3 &&

<View className="msgitem">

{!item.isme ? <View className="msg__avator"><Image className="msg__avator-img" src={item.avator} mode="aspectFit" /></View> : null}

<View className={`msg__cntbox ${item.isme ? \'msg-me\' : \'msg-others\'}`}>

<Text className="msg-author">{item.author}</Text>

<View className={`msg__cnt ${item.isme ? \'msg__cnt-me\' : \'msg__cnt-others\'}`} onLongPress={this.handleLongPressMenu}>

<Text className="msg__cnt-text">{item.msg}</Text>

</View>

</View>

{item.isme ? <View className="msg__avator"><Image className="msg__avator-img" src={item.avator} mode="aspectFit" /></View> : null}

</View>

}

{item.msgtype == 4 &&

<View className="msgitem">

{!item.isme ? <View className="msg__avator"><Image className="msg__avator-img" src={item.avator} mode="aspectFit" /></View> : null}

<View className={`msg__cntbox ${item.isme ? \'msg-me\' : \'msg-others\'}`}>

<Text className="msg-author">{item.author}</Text>

<View className={`msg__cnt ${item.isme ? \'msg__cnt-me\' : \'msg__cnt-others\'} msg__lgface`} onLongPress={this.handleLongPressMenu}>

<Image className="msg__lgface-img" src={item.imgsrc} mode="widthFix" />

</View>

</View>

{item.isme ? <View className="msg__avator"><Image className="msg__avator-img" src={item.avator} mode="aspectFit" /></View> : null}

</View>

}

{item.msgtype == 5 &&

<View className="msgitem">

{!item.isme ? <View className="msg__avator"><Image className="msg__avator-img" src={item.avator} mode="aspectFit" /></View> : null}

<View className={`msg__cntbox ${item.isme ? \'msg-me\' : \'msg-others\'}`}>

<Text className="msg-author">{item.author}</Text>

<View className={`msg__cnt ${item.isme ? \'msg__cnt-me\' : \'msg__cnt-others\'} msg__picture`} onClick={this.handlePreviewPicture.bind(this, item.imgsrc)} onLongPress={this.handleLongPressMenu}>

<Image className="msg__picture-img" src={item.imgsrc} mode="widthFix" />

</View>

</View>

{item.isme ? <View className="msg__avator"><Image className="msg__avator-img" src={item.avator} mode="aspectFit" /></View> : null}

</View>

}

...

</View>

))

}

以上就是taro开发聊天室的一些分享,今天就介绍到这里,希望能有些许的帮助~~

以上是 Taro聊天室|react+taro仿微信聊天App界面|taro聊天实例 的全部内容, 来源链接: utcz.com/z/383903.html

回到顶部