zoukankan      html  css  js  c++  java
  • react网页版聊天|仿微信、微博web版|react+pc端仿微信实例

    一、项目介绍

    基于react+react-dom+react-router-dom+redux+react-redux+webpack2.0+nodejs等技术混合开发的仿微信web端聊天室reactWebChat项目,实现了聊天记录右键菜单、发送消息、表情(动图),图片、视频预览,浏览器截图粘贴发送等功能。

    二、技术选型

    • MVVM框架:react / react-dom
    • 状态管理:redux / react-redux
    • 页面路由:react-router-dom
    • 弹窗插件:wcPop
    • 打包工具:webpack 2.0
    • 环境配置:node.js + cnpm
    • 图片预览:react-photoswipe
    • 轮播滑动:swiper
    {
      "name": "react-webchat",
      "version": "0.1.0",
      "private": true,
      "dependencies": {
        "react": "^16.8.6",
        "react-dom": "^16.8.6",
        "react-redux": "^7.1.0",
        "react-router-dom": "^5.0.1",
        "react-scripts": "0.9.x",
        "redux": "^4.0.1",
        "redux-thunk": "^2.3.0"
      },
      "devDependencies": {
        "jquery": "^2.2.3",
        "react-custom-scrollbars": "^4.2.1",
        "react-photoswipe": "^1.3.0",
        "swiper": "^4.5.0"
      },
      "scripts": {
        "start": "set HOST=localhost&& set PORT=3003 && react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test --env=jsdom",
        "eject": "react-scripts eject"
      }
    }

    ◆ App主页面布局及路由配置:

    render() {
        let token = this.props.token
        return (
          <Router>
            <div className="vChat-wrapper flexbox flex-alignc">
              <div className="vChat-panel" /*style={{ backgroundImage: `url(${require("./assets/img/placeholder/vchat__panel-bg02.jpg")})` }}*/ >
                <div className="vChat-inner flexbox">
                  {/* //顶部(最大、最小、关闭) */}
                  <Switch>
                    <WinBar />
                  </Switch>
    
                  {/* //侧边栏 */}
                  <Switch>
                    <SideBar />
                  </Switch>
    
                  {/* //主页面 */}
                  <div className="flex1 flexbox">
                    {/* 路由容器 */}
                    <Switch>
                      {
                        routers.map((item, index) => {
                          return <Route key={index} path={item.path} exact render={props => (
                            !item.meta || !item.meta.requireAuth ? (<item.component {...props} />) : (
                              token ? <item.component {...props} /> : <Redirect to={{pathname: '/login', state: {from: props.location}}} />
                            )
                          )} />
                        })
                      }
                      {/* 初始化页面跳转 */}
                      <Redirect push to="/index" />
                    </Switch>
                  </div>
                </div>
              </div>
            </div>
          </Router>
        );
    }

    ◆ react+react-redux配合状态管理:

    import {combineReducers} from 'redux'
    import defaultState from './state.js'
    
    function auth(state = defaultState, action) {
        // 不同的action处理不同的逻辑
        switch (action.type) {
            case 'SET_TOKEN':
                return {
                    ...state, token: action.data
                }
            case 'SET_USER':
                return {
                    ...state, user: action.data
                }
            case 'SET_LOGOUT':
                return {
                    user: null, token: null
                }
            default:
                return { ...state }
        }
    }

    ◆ react页面路由配置:

    /*
     *  @desc 页面地址路由js
     */
    
    // 引入页面组件
    import Login from '../views/auth/login'
    import Register from '../views/auth/register'
    import Index from '../views/index'
    import Contact from '../views/contact'
    import Uinfo from '../views/contact/uinfo'
    import NewFriend from '../views/contact/new-friends'
    import Ucenter from '../views/ucenter'
    import News from '../views/news'
    import NewsDetail from '../views/news/detail';
    
    export default [
        {
            path: '/login', name: 'Login', component: Login,
            meta: { hideSideBar: true },
        },
        {
            path: '/register', name: 'Register', component: Register,
            meta: { hideSideBar: true },
        },
        {
            path: '/index', name: 'App', component: Index,
            meta: { requireAuth: true },
        },
        {
            path: '/contact', name: 'Contact', component: Contact,
            meta: { requireAuth: true },
        },
        {
            path: '/contact/uinfo', name: 'Uinfo', component: Uinfo,
        },
        {
            path: '/contact/new-friends', name: 'NewFriend', component: NewFriend,
            meta: { requireAuth: true },
        },
        {
            path: '/news', name: 'News', component: News,
        },
        {
            path: '/news/detail', name: 'NewsDetail', component: NewsDetail,
        },
        {
            path: '/ucenter', name: 'Ucenter', component: Ucenter,
            meta: { requireAuth: true },
        },
    
        // ...
    ]
    import React, { Component } from 'react';
    import { Link } from 'react-router-dom';
    import {connect} from 'react-redux'
    
    import $ from 'jquery'
    // 引入wcPop弹窗插件
    import { wcPop } from '../../assets/js/wcPop/wcPop'
    
    // 引入自定义滚动条
    import { Scrollbars } from 'react-custom-scrollbars'
    
    // 引入swiper
    import Swiper from 'swiper'
    import 'swiper/dist/css/swiper.css'
    
    // 引入图片预览组件react-photoswipe
    import {PhotoSwipe} from 'react-photoswipe'
    import 'react-photoswipe/lib/photoswipe.css'
    
    // 导入消息记录列表
    import RecordList from '../../components/recordList'
    // >>> 【编辑器+表情处理模块】------------------------------------------
    // ...处理编辑器信息
    function surrounds() {
        setTimeout(function () { //chrome
            var sel = window.getSelection();
            var anchorNode = sel.anchorNode;
            if (!anchorNode) return;
            if (sel.anchorNode === $(".J__wcEditor")[0] ||
                (sel.anchorNode.nodeType === 3 && sel.anchorNode.parentNode === $(".J__wcEditor")[0])) {
    
                var range = sel.getRangeAt(0);
                var p = document.createElement("p");
                range.surroundContents(p);
                range.selectNodeContents(p);
                range.insertNode(document.createElement("br")); //chrome
                sel.collapse(p, 0);
    
                (function clearBr() {
                    var elems = [].slice.call($(".J__wcEditor")[0].children);
                    for (var i = 0, len = elems.length; i < len; i++) {
                        var el = elems[i];
                        if (el.tagName.toLowerCase() == "br") {
                            $(".J__wcEditor")[0].removeChild(el);
                        }
                    }
                    elems.length = 0;
                })();
            }
        }, 10);
    }
    
    // 定义最后光标位置
    var _lastRange = null, _sel = window.getSelection && window.getSelection();
    var _rng = {
        getRange: function () {
            if (_sel && _sel.rangeCount > 0) {
                return _sel.getRangeAt(0);
            }
        },
        addRange: function () {
            if (_lastRange) {
                _sel.removeAllRanges();
                _sel.addRange(_lastRange);
            }
        }
    }
    
    // 格式化编辑器包含标签
    $("body").on("click", ".J__wcEditor", function(){
        $(".wc__choose-panel").hide();
        _lastRange = _rng.getRange();
    });
    $("body").on("focus", ".J__wcEditor", function(){
        surrounds();
        _lastRange = _rng.getRange();
    });
    $("body").on("input", ".J__wcEditor", function(){
        surrounds();
        _lastRange = _rng.getRange();
    });

  • 相关阅读:
    字符串方法
    函数的属性和方法
    数组的去重!!
    常见的数组方法
    JS中的函数
    JavaScript 中表达式和语句的区别
    运算符优先级
    题解 CF813B 【The Golden Age】
    题解 CF834B 【The Festive Evening】
    题解 CF810B 【Summer sell-off】
  • 原文地址:https://www.cnblogs.com/xiaoyan2017/p/11106246.html
Copyright © 2011-2022 走看看