Taro 开发三端实例 taro-chatroom 仿微信聊天 App 界面 (H5 + 小程序 + App 端)
之前有使用uni-app 多端技术开发过仿抖音短视频 /陌陌直播实例,今天给大家分享的是基于 taro+react+redux+rn 等技术开发的仿微信界面聊天项目,基本实现了消息发送、动图表情、图片预览、长按菜单、红包 /朋友圈等功能。
支持编译到多端 h5/小程序 /app 效果如下:
技术栈:
- 编码 /技术:Vscode + react/taro/redux/react-native
- iconfont 图标:阿里字体图标库
- 自定义导航栏 Navigation + 底部 Tabbar
- 弹窗组件:taroPop (基于 Taro 封装自定义模态框)
- 支持编译:H5 端 + 小程序 + RN 端
入口页面 app.jsx
引入公共样式、组件页面及态管理
/** * @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')) 自定义配置导航栏+tabbar
自定义导航栏配置非常简单,只需在 window 选项下配置 navigationStyle: 'custom' ,只要不设置 tabbar 参数就可以自定义底部组件了。
cOnfig= { pages: [ 'pages/auth/login/index', 'pages/auth/register/index', 'pages/index/index', ... ], window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', navigationBarTitleText: 'TaroChat', navigationBarTextStyle: 'black', navigationStyle: 'custom' } } 这里不详细介绍如何实现自定义 Navbar+tabbar,可以去参看:
taro 实现表单登录验证|状态管理|本地存储
<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.pn')} 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> 由于 taro 中 ReactNative 端不支持同步存储,只能使用异步存储实现
/** * @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' import TouchView from '@components/touchView' import Navigation from '@components/navigation' // 引入自定义弹窗 taroPop import TaroPop from '@components/taroPop' import storage from '@utils/storage' import util from '@utils/util' 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 }) ... } } // 去注册 GoToRegister = () => { Taro.navigateTo({url: '/pages/auth/register/index'}) } render () { ... } } const mapStateToProps = (state) => { return {...state.auth} } export default connect(mapStateToProps, { ...actions })(Login) 对于一些兼容样式,不支持编译到 RN 端,则可通过如下代码包裹实现 /*postcss-pxtransform rn eject enable*/ /*postcss-pxtransform rn eject disable*/
滚动聊天信息底部
taro 中实现聊天消息滚动到最底部,由于 RN 端不支持 createSelectorQuery,需另做兼容处理
// 滚动至聊天底部 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) } componentDidMount() { if(process.env.TARO_ENV === 'rn') { this.scrollMsgBottomRN() }else { this.scrollMsgBottom() } } 聊天中表情模块则是使用 emoj 表情符,实现起来较简单,这里不介绍了
... // 点击聊天消息区域 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 聊天项目就基本介绍完了,后续也会继续分享一些实战案例~~
react-native 聊天室|RN 版聊天 App 仿微信实例|RN 仿微信界面
vue 仿微信网页版|vue+web 端聊天室|仿微信客户端 vue 版
