Taro+react仿微信app聊天室|taro仿微信界面|taro聊天/朋友圈

基於Taro+react+redux+RN+taroPop等技術開發的跨端聊天App實例,支持編譯到多端H5+小程序+RN端,界面仿製微信聊天界面,實現了消息發送、表情、圖片預覽、長按菜單、紅包、朋友圈等功能。

Taro三端統一聊天應用:taro-chatroom (仿微信界面聊天App)

技術棧:

  • 編碼/技術:Vscode / react+taro+react-redux+ReactNative
  • 字體圖標:阿里iconfont字體圖標庫
  • 自定義頂部導航條 + 底部Tabbar
  • 彈窗組件:taroPop(taro封裝自定義模態框)
  • 支持編譯:H5端 + 小程序 + App端

爲了展示更豐富的導航條功能,頂部導航欄和底部Tabbar均採用自定義模式,且測試兼容三端。

具體介紹可看看這篇:Taro+react自定義頂部導航 + tabbar菜單

另外項目中用到的彈窗效果也是基於taro自定義Modal實現,可參看:taro自定義對話框

入口頁面App.jsx

項目中使用redux進行狀態管理,具體用法和react裏面一樣。

yarn add redux redux-thunk @tarojs/redux
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中表單操作、redux狀態管理及本地存儲實現

<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)

項目開發中,尤其需要注意RN端樣式問題,對於一些兼容處理,不希望編譯到RN端,則可通過如下實現

/*postcss-pxtransform rn eject enable*/

/*postcss-pxtransform rn eject disable*/
/**
 * 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*/
}

聊天時發送消息滾動到最底部,則需要進行兼容處理,因爲ReactNative端不支持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表情符,這個實現比較簡單,就不介紹了。

...

// 點擊聊天消息區域
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網頁端聊天:https://blog.csdn.net/yanxinyun1990/article/details/89735778

vue+uniapp仿抖音小視頻:https://blog.csdn.net/yanxinyun1990/article/details/103012086

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章