前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >taro多端实例|仿微信界面app聊天|taro聊天

taro多端实例|仿微信界面app聊天|taro聊天

原创
作者头像
andy2018
修改2019-12-16 11:17:44
3.6K0
修改2019-12-16 11:17:44
举报
文章被收录于专栏:h5h5

taro-chatroom多端实战项目是基于taro+react+redux+reactNative等技术开发的仿微信界面聊天实例,实现了消息发送、动图表情、图片查看、红包/朋友圈、小组等功能。

支持编译到多端:h5+小程序+app端 ,效果图如下:

技术实现:

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

页面入口app.jsx配置

代码语言:javascript
复制
/**
  * @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中通过如下方式配置页面路径及全局窗口配置

代码语言:javascript
复制
config = {
    pages: [
      'pages/auth/login/index',
      'pages/auth/register/index',
      'pages/index/index',
      ...
    ],
    window: {
      backgroundTextStyle: 'light',
      navigationBarBackgroundColor: '#fff',
      navigationBarTitleText: 'TaroChat',
      navigationBarTextStyle: 'black',
      navigationStyle: 'custom'
    }
}

项目中顶部导航条及底部tabbar均为自定义组件模式,这里不多介绍,可以去看之前的分享文章

Taro实现自定义导航栏+Tabbar菜单

Taro仿ios/android对话框|模态框

taro实现登录表单验证、状态管理、本地存储

代码语言:javascript
复制
<Input placeholder="请输入手机号/昵称" onInput={this.handleInput.bind(this, 'tel')} />
代码语言:javascript
复制
this.state = {
	tel: '',
	pwd: '',
}

handleInput = (key, e) => {
    this.setState({ [key]: e.detail.value })
}

通过上面方法就可以简单实现获取input值了。更详细介绍如下:

代码语言:javascript
复制
<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>
代码语言:javascript
复制
/**
 * @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端 不支持同步存储setStorage,只能使用setStorageSync异步存储了

对于一些兼容样式,不编译到RN端,则可通过如下代码包裹实现

/*postcss-pxtransform rn eject enable*/

/*postcss-pxtransform rn eject disable*/

taro滚动聊天消息底部

在taro中实现聊天消息滚动到底部也需要兼容处理,由于RN端不支持 createSelectorQuery

代码语言:javascript
复制
// 滚动至聊天底部
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)
}
代码语言:javascript
复制
componentDidMount() {
    if(process.env.TARO_ENV === 'rn') {
        this.scrollMsgBottomRN()
    }else {
        this.scrollMsgBottom()
    }
}

taro提供环境变量来支持不同平台的兼容性

代码语言:javascript
复制
let taroEnv = process.env.TARO_ENV
if(taroEnv === 'rn') {
	...
}else if(taroEnv === 'weapp') {
	...
}else if(taroEnv === 'h5') {
	...
}
代码语言:javascript
复制
// 渲染消息记录
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>
	))
}
代码语言:javascript
复制
...

// 点击聊天消息区域
msgPanelClicked = () => {
	if(!this.state.showFootToolbar) return
	this.setState({ showFootToolbar: false })
}

// 表情、选择区切换
swtEmojChooseView = (index) => {
	this.setState({ showFootToolbar: true, showFootViewIndex: index })
}

// 底部表情tab切换
swtEmojTab = (index) => {
	let lists = this.state.emotionJson
	for(var i = 0, len = lists.length; i < len; i++) {
		lists[i].selected = false
	}
	lists[index].selected = true
	this.setState({ emotionJson: lists })
}


/* >>> 【编辑器/表情处理模块】------------------------------------- */
bindEditorInput = (e) => {
	this.setState({
		editorText: e.detail.value,
		editorLastCursor: e.detail.cursor
	})
}
bindEditorFocus = (e) => {
	this.setState({ editorLastCursor: e.detail.cursor })
}
bindEditorBlur = (e) => {
	this.setState({ editorLastCursor: e.detail.cursor })
}

handleEmotionTaped = (emoj) => {
	if(emoj == 'del') return
	// 在光标处插入表情
	let { editorText, editorLastCursor } = this.state
	let lastCursor = editorLastCursor ? editorLastCursor : editorText.length
	let startStr = editorText.substr(0, lastCursor)
	let endStr = editorText.substr(lastCursor)
	this.setState({
		editorText: startStr + `${emoj} ` + endStr
	})
}

...

好了,基于taro聊天实例项目到这里就介绍完了,希望能有些帮助!!👊👊

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 技术实现:
  • 页面入口app.jsx配置
  • taro实现登录表单验证、状态管理、本地存储
  • taro滚动聊天消息底部
相关产品与服务
云开发 CloudBase
云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档