一、项目介绍
基于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();
- });
