taro-chatroom仿微信聊天室項目是基於taro+react+react-redux+ReactNative+taroPop等技術實現的taro版聊天App實例,支持編譯到三端h5+小程序+RN端,實現了消息發送、表情大圖,圖片預覽、長按菜單、紅包、朋友圈等功能。css
以下圖:編譯到多端效果:H5端/小程序/App端
html
/** * @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均爲自定義組件,詳情見:
Taro實現自定義導航欄+Tabbar菜單
彈窗插件是基於Taro自定義模態框組件,參看:
Taro仿ios/android對話框|模態框vue
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> )
因爲taro中ReactNative端不支持同步存儲,只能使用異步存儲實現
react
/** * @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)
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) } ... }
對於一些兼容樣式,不編譯到RN端,則可經過以下代碼包裹實現/*postcss-pxtransform rn eject enable*/
/*postcss-pxtransform rn eject disable*/
android
taro中實現聊天消息滾動到最底部,因爲RN端不支持 createSelectorQuery,須要作兼容處理。ios
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表情符,實現比較簡單,就不介紹了。
git
// 渲染消息記錄 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> )) }
... // 點擊聊天消息區域 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開發聊天app就基本介紹完了,但願你們能喜歡~~
最後分享個基於Vue實例項目
vue+uniapp+vuex開發的仿抖音短視頻|仿陌陌直播項目vuex