react網頁版聊天|仿微信、微博web版|react+pc端仿微信實例

1、項目介紹

基於react+react-dom+react-router-dom+redux+react-redux+webpack2.0+nodejs等技術混合開發的仿微信web端聊天室reactWebChat項目,實現了聊天記錄右鍵菜單、發送消息、表情(動圖),圖片、視頻預覽,瀏覽器截圖粘貼發送等功能。css

2、技術選型

  • 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主頁面佈局及路由配置:node

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配合狀態管理:react

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頁面路由配置:jquery

/*
 *  @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();
});

相關文章
相關標籤/搜索