react网页版聊天|仿微信、微博web版|react+pc端仿微信实例
<h3 id="一项目简介">一、项目介绍</h3><p><span style="font-size: 12px; font-family: "comic sans ms", 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: "comic sans ms", sans-serif">MVVM框架:react / react-dom</span></li>
<li><span style="font-size: 12px; font-family: "comic sans ms", sans-serif">状态管理:redux / react-redux</span></li>
<li><span style="font-size: 12px; font-family: "comic sans ms", sans-serif">页面路由:react-router-dom</span></li>
<li><span style="font-size: 12px; font-family: "comic sans ms", sans-serif">弹窗插件:wcPop</span></li>
<li><span style="font-size: 12px; font-family: "comic sans ms", sans-serif">打包工具:webpack 2.0</span></li>
<li><span style="font-size: 12px; font-family: "comic sans ms", sans-serif">环境配置:node.js + cnpm</span></li>
<li><span style="font-size: 12px; font-family: "comic sans ms", sans-serif">图片预览:react-photoswipe</span></li>
<li><span style="font-size: 12px; font-family: "comic sans ms", 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&& set PORT=3003 && 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><Router>
<div className="vChat-wrapper flexbox flex-alignc">
<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> >
<div className="vChat-inner flexbox"><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><Switch>
<WinBar />
</Switch>
<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><Switch>
<SideBar />
</Switch>
<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><div className="flex1 flexbox"><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><Switch><span style="color: rgba(0, 0, 0, 1)">
{
routers.map((item, index) </span>=><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <Route key={index} path={item.path} exact render={props =><span style="color: rgba(0, 0, 0, 1)"> (
</span>!item.meta || !item.meta.requireAuth ? (<item.component {...props} />) : (
token ? <item.component {...props} /> : <Redirect to={{pathname: '/login'<span style="color: rgba(0, 0, 0, 1)">, state: {from: props.location}}} />
)
)} />
})
}
{/* 初始化页面跳转 */}
<Redirect push to="/index" />
</Switch>
</div>
</div>
</div>
</div>
</Router>
);
}</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: "Helvetica Neue", 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)"> >>> 【编辑器+表情处理模块】------------------------------------------</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 && 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 < 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 &&<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 && _sel.rangeCount > 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> </p>
</div>
<div id="MySignature" role="contentinfo">
本文为博主原创文章,未经博主允许不得转载,欢迎大家一起交流 QQ(282310962) wx(xy190310)<br><br>
来源:https://www.cnblogs.com/xiaoyan2017/p/11106246.html
頁:
[1]