雪球宝贝 發表於 2019-6-29 12:27:00

react网页版聊天|仿微信、微博web版|react+pc端仿微信实例

<h3 id="一项目简介">一、项目介绍</h3>
<p><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif; background-color: rgba(204, 255, 255, 1)">基于react+react-dom+react-router-dom+redux+react-redux+webpack2.0+nodejs等技术混合开发的仿微信web端聊天室reactWebChat项目,实现了聊天记录右键菜单、发送消息、表情(动图),图片、视频预览,浏览器截图粘贴发送等功能。</span></p>
<h3 id="三技术栈">二、技术选型</h3>
<ul>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">MVVM框架:react / react-dom</span></li>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">状态管理:redux / react-redux</span></li>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">页面路由:react-router-dom</span></li>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">弹窗插件:wcPop</span></li>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">打包工具:webpack 2.0</span></li>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">环境配置:node.js + cnpm</span></li>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">图片预览:react-photoswipe</span></li>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">轮播滑动:swiper</span></li>
</ul>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
</span>"name": "react-webchat"<span style="color: rgba(0, 0, 0, 1)">,
</span>"version": "0.1.0"<span style="color: rgba(0, 0, 0, 1)">,
</span>"private": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"dependencies"<span style="color: rgba(0, 0, 0, 1)">: {
    </span>"react": "^16.8.6"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"react-dom": "^16.8.6"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"react-redux": "^7.1.0"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"react-router-dom": "^5.0.1"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"react-scripts": "0.9.x"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"redux": "^4.0.1"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"redux-thunk": "^2.3.0"<span style="color: rgba(0, 0, 0, 1)">
},
</span>"devDependencies"<span style="color: rgba(0, 0, 0, 1)">: {
    </span>"jquery": "^2.2.3"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"react-custom-scrollbars": "^4.2.1"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"react-photoswipe": "^1.3.0"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"swiper": "^4.5.0"<span style="color: rgba(0, 0, 0, 1)">
},
</span>"scripts"<span style="color: rgba(0, 0, 0, 1)">: {
    </span>"start": "set HOST=localhost&amp;&amp; set PORT=3003 &amp;&amp; react-scripts start"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"build": "react-scripts build"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"test": "react-scripts test --env=jsdom"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"eject": "react-scripts eject"<span style="color: rgba(0, 0, 0, 1)">
}
}</span></pre>
</div>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190629121402006-1454126921.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190629121426005-1872757853.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190629121433510-2110291164.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190629121446486-530029836.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190629121503163-1414872915.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190629121519283-1101395026.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190629121540207-1091818263.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190629121555327-942944577.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190629121603989-773788044.png"></p>
<p><strong>◆ App主页面布局及路由配置:</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">render() {
    let token </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.props.token
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;Router&gt;
      &lt;div className="vChat-wrapper flexbox flex-alignc"&gt;
          &lt;div className="vChat-panel" <span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">style={{ backgroundImage: `url(${require("./assets/img/placeholder/vchat__panel-bg02.jpg")})` }}</span><span style="color: rgba(0, 128, 0, 1)">*/</span> &gt;
            &lt;div className="vChat-inner flexbox"&gt;<span style="color: rgba(0, 0, 0, 1)">
            {</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> //顶部(最大、最小、关闭) </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">}
            </span>&lt;Switch&gt;
                &lt;WinBar /&gt;
            &lt;/Switch&gt;
<span style="color: rgba(0, 0, 0, 1)">
            {</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> //侧边栏 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">}
            </span>&lt;Switch&gt;
                &lt;SideBar /&gt;
            &lt;/Switch&gt;
<span style="color: rgba(0, 0, 0, 1)">
            {</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> //主页面 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">}
            </span>&lt;div className="flex1 flexbox"&gt;<span style="color: rgba(0, 0, 0, 1)">
                {</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 路由容器 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">}
                </span>&lt;Switch&gt;<span style="color: rgba(0, 0, 0, 1)">
                  {
                  routers.map((item, index) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                      </span><span style="color: rgba(0, 0, 255, 1)">return</span> &lt;Route key={index} path={item.path} exact render={props =&gt;<span style="color: rgba(0, 0, 0, 1)"> (
                        </span>!item.meta || !item.meta.requireAuth ? (&lt;item.component {...props} /&gt;) : (
                        token ? &lt;item.component {...props} /&gt; : &lt;Redirect to={{pathname: '/login'<span style="color: rgba(0, 0, 0, 1)">, state: {from: props.location}}} /&gt;
                        )
                      )} /&gt;
                  })
                  }
                  {/* 初始化页面跳转 */}
                  &lt;Redirect push to="/index" /&gt;
                &lt;/Switch&gt;
            &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
      &lt;/div&gt;
      &lt;/Router&gt;
    );
}</span></pre>
</div>
<p><strong>◆ react+react-redux配合状态管理:</strong></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190629121931990-1436939996.png"></p>
<div class="cnblogs_code">
<pre>import {combineReducers} from 'redux'<span style="color: rgba(0, 0, 0, 1)">
import defaultState from </span>'./state.js'

<span style="color: rgba(0, 0, 255, 1)">function</span> auth(state =<span style="color: rgba(0, 0, 0, 1)"> defaultState, action) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 不同的action处理不同的逻辑</span>
    <span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (action.type) {
      </span><span style="color: rgba(0, 0, 255, 1)">case</span> 'SET_TOKEN'<span style="color: rgba(0, 0, 0, 1)">:
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
                ...state, token: action.data
            }
      </span><span style="color: rgba(0, 0, 255, 1)">case</span> 'SET_USER'<span style="color: rgba(0, 0, 0, 1)">:
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
                ...state, user: action.data
            }
      </span><span style="color: rgba(0, 0, 255, 1)">case</span> 'SET_LOGOUT'<span style="color: rgba(0, 0, 0, 1)">:
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
                user: </span><span style="color: rgba(0, 0, 255, 1)">null</span>, token: <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
            }
      </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">:
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> { ...state }
    }
}<br></span></pre>
</div>
<p><strong style="margin: 0; padding: 0; font-family: &quot;Helvetica Neue&quot;, Helvetica, Verdana, Arial, sans-serif">◆ react页面路由配置:</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
*@desc 页面地址路由js
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入页面组件</span>
import Login from '../views/auth/login'<span style="color: rgba(0, 0, 0, 1)">
import Register from </span>'../views/auth/register'<span style="color: rgba(0, 0, 0, 1)">
import Index from </span>'../views/index'<span style="color: rgba(0, 0, 0, 1)">
import Contact from </span>'../views/contact'<span style="color: rgba(0, 0, 0, 1)">
import Uinfo from </span>'../views/contact/uinfo'<span style="color: rgba(0, 0, 0, 1)">
import NewFriend from </span>'../views/contact/new-friends'<span style="color: rgba(0, 0, 0, 1)">
import Ucenter from </span>'../views/ucenter'<span style="color: rgba(0, 0, 0, 1)">
import News from </span>'../views/news'<span style="color: rgba(0, 0, 0, 1)">
import NewsDetail from </span>'../views/news/detail'<span style="color: rgba(0, 0, 0, 1)">;

export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> [
    {
      path: </span>'/login', name: 'Login'<span style="color: rgba(0, 0, 0, 1)">, component: Login,
      meta: { hideSideBar: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)"> },
    },
    {
      path: </span>'/register', name: 'Register'<span style="color: rgba(0, 0, 0, 1)">, component: Register,
      meta: { hideSideBar: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)"> },
    },
    {
      path: </span>'/index', name: 'App'<span style="color: rgba(0, 0, 0, 1)">, component: Index,
      meta: { requireAuth: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)"> },
    },
    {
      path: </span>'/contact', name: 'Contact'<span style="color: rgba(0, 0, 0, 1)">, component: Contact,
      meta: { requireAuth: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)"> },
    },
    {
      path: </span>'/contact/uinfo', name: 'Uinfo'<span style="color: rgba(0, 0, 0, 1)">, component: Uinfo,
    },
    {
      path: </span>'/contact/new-friends', name: 'NewFriend'<span style="color: rgba(0, 0, 0, 1)">, component: NewFriend,
      meta: { requireAuth: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)"> },
    },
    {
      path: </span>'/news', name: 'News'<span style="color: rgba(0, 0, 0, 1)">, component: News,
    },
    {
      path: </span>'/news/detail', name: 'NewsDetail'<span style="color: rgba(0, 0, 0, 1)">, component: NewsDetail,
    },
    {
      path: </span>'/ucenter', name: 'Ucenter'<span style="color: rgba(0, 0, 0, 1)">, component: Ucenter,
      meta: { requireAuth: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)"> },
    },

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
]</pre>
</div>
<div class="cnblogs_code">
<pre>import React, { Component } from 'react'<span style="color: rgba(0, 0, 0, 1)">;
import { Link } from </span>'react-router-dom'<span style="color: rgba(0, 0, 0, 1)">;
import {connect} from </span>'react-redux'<span style="color: rgba(0, 0, 0, 1)">

import $ from </span>'jquery'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入wcPop弹窗插件</span>
import { wcPop } from '../../assets/js/wcPop/wcPop'

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入自定义滚动条</span>
import { Scrollbars } from 'react-custom-scrollbars'

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入swiper</span>
import Swiper from 'swiper'<span style="color: rgba(0, 0, 0, 1)">
import </span>'swiper/dist/css/swiper.css'

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入图片预览组件react-photoswipe</span>
import {PhotoSwipe} from 'react-photoswipe'<span style="color: rgba(0, 0, 0, 1)">
import </span>'react-photoswipe/lib/photoswipe.css'

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 导入消息记录列表</span>
import RecordList from '../../components/recordList'</pre>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> &gt;&gt;&gt; 【编辑器+表情处理模块】------------------------------------------</span><span style="color: rgba(0, 128, 0, 1)">
//</span><span style="color: rgba(0, 128, 0, 1)"> ...处理编辑器信息</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> surrounds() {
    setTimeout(</span><span style="color: rgba(0, 0, 255, 1)">function</span> () { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">chrome</span>
      <span style="color: rgba(0, 0, 255, 1)">var</span> sel =<span style="color: rgba(0, 0, 0, 1)"> window.getSelection();
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> anchorNode =<span style="color: rgba(0, 0, 0, 1)"> sel.anchorNode;
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!anchorNode) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (sel.anchorNode === $(".J__wcEditor") ||<span style="color: rgba(0, 0, 0, 1)">
            (sel.anchorNode.nodeType </span>=== 3 &amp;&amp; sel.anchorNode.parentNode === $(".J__wcEditor"))) {

            </span><span style="color: rgba(0, 0, 255, 1)">var</span> range = sel.getRangeAt(0<span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> p = document.createElement("p"<span style="color: rgba(0, 0, 0, 1)">);
            range.surroundContents(p);
            range.selectNodeContents(p);
            range.insertNode(document.createElement(</span>"br")); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">chrome</span>
            sel.collapse(p, 0<span style="color: rgba(0, 0, 0, 1)">);

            (</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> clearBr() {
                </span><span style="color: rgba(0, 0, 255, 1)">var</span> elems = [].slice.call($(".J__wcEditor").children);
                </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> i = 0, len = elems.length; i &lt; len; i++<span style="color: rgba(0, 0, 0, 1)">) {
                  </span><span style="color: rgba(0, 0, 255, 1)">var</span> el =<span style="color: rgba(0, 0, 0, 1)"> elems;
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> (el.tagName.toLowerCase() == "br"<span style="color: rgba(0, 0, 0, 1)">) {
                        $(</span>".J__wcEditor").removeChild(el);
                  }
                }
                elems.length </span>= 0<span style="color: rgba(0, 0, 0, 1)">;
            })();
      }
    }, </span>10<span style="color: rgba(0, 0, 0, 1)">);
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 定义最后光标位置</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> _lastRange = <span style="color: rgba(0, 0, 255, 1)">null</span>, _sel = window.getSelection &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> window.getSelection();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> _rng =<span style="color: rgba(0, 0, 0, 1)"> {
    getRange: </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (_sel &amp;&amp; _sel.rangeCount &gt; 0<span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> _sel.getRangeAt(0<span style="color: rgba(0, 0, 0, 1)">);
      }
    },
    addRange: </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (_lastRange) {
            _sel.removeAllRanges();
            _sel.addRange(_lastRange);
      }
    }
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 格式化编辑器包含标签</span>
$("body").on("click", ".J__wcEditor", <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(){
    $(</span>".wc__choose-panel"<span style="color: rgba(0, 0, 0, 1)">).hide();
    _lastRange </span>=<span style="color: rgba(0, 0, 0, 1)"> _rng.getRange();
});
$(</span>"body").on("focus", ".J__wcEditor", <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(){
    surrounds();
    _lastRange </span>=<span style="color: rgba(0, 0, 0, 1)"> _rng.getRange();
});
$(</span>"body").on("input", ".J__wcEditor", <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(){
    surrounds();
    _lastRange </span>=<span style="color: rgba(0, 0, 0, 1)"> _rng.getRange();
});</span></pre>
</div>
<h4><em>附上最新版React18聊天项目实例</em></h4>
<p><span style="font-size: 12px">react18-webchat网页聊天实例|React Hooks+Arco Design仿微信桌面端</span></p>
<p><span style="font-size: 12px">React-Chat移动端聊天实例|react18 hooks仿微信App聊天界面</span></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190629122544260-1803763615.jpg"></p>
<p>&nbsp;</p>

</div>
<div id="MySignature" role="contentinfo">
    本文为博主原创文章,未经博主允许不得转载,欢迎大家一起交流 QQ(282310962) wx(xy190310)<br><br>
来源:https://www.cnblogs.com/xiaoyan2017/p/11106246.html
頁: [1]
查看完整版本: react网页版聊天|仿微信、微博web版|react+pc端仿微信实例