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

1、項目簡述

taro-chatroom是基於Taro多端實例聊天項目,運用Taro+react+react-redux+taroPop+react-native等技術開發的仿微信App界面聊天室,實現了發送消息/emoj表情、gif表情大圖、圖片預覽、發紅包、動態圈等功能。css

2、預覽效果

編譯到H5端、小程序、App端效果以下:(後續大圖均爲APP端)html

3、技術棧

  • 編碼/技術: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菜單react

◆ 登陸/註冊驗證模塊git

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端web

/**
 * @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端,使用以下包裹便可redux

 /*postcss-pxtransform rn eject enable*/ 小程序

 /*postcss-pxtransform rn eject disable*/ react-native

對於一些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聊天實現消息滾動到底部app

在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開發聊天室的一些分享,今天就介紹到這裏,但願能有些許的幫助~~ 🙃🙃 

相關文章
相關標籤/搜索