V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
xiaoyan2017
V2EX  ›  推广

taro 多端实践仿微信聊天|taro+react 聊天室

  •  
  •   xiaoyan2017 · 2019-12-16 08:58:39 +08:00 · 3316 次点击
    这是一个创建于 1813 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 自定义 Navigation、tabbar 组件

    taro 自定义弹窗 taroPop 组件

    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.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 端不支持同步存储,只能使用异步存储实现

    /**
     * @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 版

    3 条回复    2020-01-06 12:52:10 +08:00
    evilhero
        1
    evilhero  
       2019-12-16 09:05:51 +08:00 via Android
    我就想问问图友和图控还可以进去吗
    gunavy
        2
    gunavy  
       2019-12-16 09:54:35 +08:00
    taro 算是狗长里不错的东西了
    wuchengqi
        3
    wuchengqi  
       2020-01-06 12:52:10 +08:00
    楼主有 demo 么
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2791 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 15:17 · PVG 23:17 · LAX 07:17 · JFK 10:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.