├── .gitignore ├── README.md ├── index.html ├── package.json ├── src ├── actions │ ├── Chat │ │ └── index.js │ └── index.js ├── components │ ├── App │ │ ├── App.jsx │ │ ├── App.scss │ │ └── index.js │ └── common │ │ ├── Scroll │ │ ├── Scroll.jsx │ │ ├── Scroll.scss │ │ └── index.js │ │ └── Svg │ │ ├── Svg.jsx │ │ ├── Svg.scss │ │ ├── images │ │ └── icon.svg │ │ └── index.js ├── constants │ └── Chat │ │ └── index.js ├── pages │ ├── Chat │ │ ├── Index │ │ │ ├── Index.scss │ │ │ ├── images │ │ │ │ ├── 1.jpg │ │ │ │ ├── 2.png │ │ │ │ ├── 3.jpg │ │ │ │ ├── arrow.png │ │ │ │ ├── bg.jpg │ │ │ │ ├── head.jpg │ │ │ │ ├── icon.svg │ │ │ │ ├── icon2.png │ │ │ │ └── lg-btn.png │ │ │ └── index.jsx │ │ ├── Login │ │ │ ├── Index.scss │ │ │ ├── images │ │ │ │ ├── password.png │ │ │ │ └── user.png │ │ │ └── index.jsx │ │ ├── Messages │ │ │ ├── Dialogue │ │ │ │ ├── Index.scss │ │ │ │ ├── images │ │ │ │ │ ├── Bin.jpg │ │ │ │ │ ├── arrow.png │ │ │ │ │ ├── bg.jpg │ │ │ │ │ ├── head.jpg │ │ │ │ │ └── icon.svg │ │ │ │ └── index.jsx │ │ │ ├── Index │ │ │ │ ├── Index.scss │ │ │ │ ├── images │ │ │ │ │ ├── Bin.jpg │ │ │ │ │ ├── arrow.png │ │ │ │ │ ├── bg.jpg │ │ │ │ │ ├── head.jpg │ │ │ │ │ └── icon.svg │ │ │ │ └── index.jsx │ │ │ └── Send │ │ │ │ ├── Index.scss │ │ │ │ ├── images │ │ │ │ ├── Bin.jpg │ │ │ │ ├── arrow.png │ │ │ │ ├── bg.jpg │ │ │ │ ├── head.jpg │ │ │ │ └── icon.svg │ │ │ │ └── index.jsx │ │ └── Sidebar │ │ │ ├── Index │ │ │ ├── Index.scss │ │ │ ├── images │ │ │ │ ├── Bin.jpg │ │ │ │ ├── arrow.png │ │ │ │ ├── bg.jpg │ │ │ │ ├── head.jpg │ │ │ │ ├── icon.svg │ │ │ │ └── search.png │ │ │ └── index.jsx │ │ │ └── List │ │ │ ├── Index.scss │ │ │ ├── images │ │ │ ├── Bin.jpg │ │ │ ├── arrow.png │ │ │ ├── bg.jpg │ │ │ ├── head.jpg │ │ │ └── icon.svg │ │ │ └── index.jsx │ └── route.js ├── reducers │ ├── Chat │ │ └── index.js │ └── index.js ├── store │ └── index.js ├── style │ ├── base.scss │ ├── func.scss │ └── index.scss └── utils │ ├── ajax.js │ ├── dia.js │ ├── events.js │ ├── fetch.js │ ├── storage.js │ └── store.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .cache 3 | package-lock.json 4 | *.log 5 | node_modules 6 | assets 7 | lib 8 | assets 9 | coverage 10 | _book 11 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react+redux-chat 模仿实现PC微信聊天 2 | 3 | ### 在线示例地址 [demo](https://meibin08.github.io/react-redux-chat/index.html "react+redux-chat 模仿实现PC微信聊天,react redux 入门示例, NodeJS 版,微信,聊天,mac chat,@IT·平头哥联盟,首席填坑官∙苏南,@IT·平头哥联盟-首席填坑官∙苏南") 4 | + 了解更多?——[@IT·平头哥联盟](https://honeybadger8.github.io/blog/ "@IT·平头哥联盟-首席填坑官∙苏南") 5 | 6 | 该示例主要使用了react、redux、iscroll、fetch等组件实现模仿实现PC微信聊天, 7 | 实现了发送消息、模拟登录、暂时使用fetch定时请求更新接收消息等功能, 8 | 实时通讯功能还在持续更新实现中, 9 | 10 | 希望能对喜欢react,对于redux还处理迷茫,不知如何入手的小伙伴能起到入门指引, 11 | - 如有不足之处,欢迎拍砖指出, 12 | - 如果该项目帮助了您,请记得帮我点颗星,就是对我最大的支持, 13 | - 当然如果您在使用的过程中,有不懂的地方,或更好的建议,我们也可以一起来讨论,欢迎加入QQ群一起讨论 14 | **** 15 | 16 | ## 技术交流 17 | - 公众号:`honeyBadger8` 下方可扫码👇 18 | - 群:912594095、[386485473](https://shang.qq.com/wpa/qunwpa?idkey=d44baf17512787eb0e4f268849a3239d6b9675145a606e21b9a055176bd1c0e2 "React\redux技术交流群") 19 | - 博客:[@IT·平头哥联盟](https://honeybadger8.github.io/blog/ "@IT·平头哥联盟-首席填坑官∙苏南") 20 | 21 | ## 安装依赖包 22 | - npm install 23 | 24 | ## 开发环境 25 | - npm run dev 26 | - 访问http://localhost:8085/index.html 27 | 28 | ## 生成环境 29 | - npm run build 30 | 31 | ## 关于我 32 | - 最近在写博客了,有兴趣的同学可以关注一下我和朋友一起整的公众号,专注于分享前端/测试的一些心得总结👇👇, 33 | - 公众号:`honeyBadger8`。 34 | 35 | ![@IT·平头哥联盟-首席填坑官∙苏南,公众号:honeyBadger8,宝剑锋从磨砺出 梅花香自苦寒来,用心分享 做有温度的攻城狮,专注于分享前端、测试 等领域的积累,文章来源于(自己/群友)工作中积累的经验、填过的坑,希望能尽绵薄之力 助其他同学少走一些弯路](https://honeybadger8.github.io/blog/frontends/_banner/card.gif "@IT·平头哥联盟-首席填坑官∙苏南,公众号:honeyBadger8") 36 | 37 | ## 图片预览 38 | ![模仿实现PC微信聊天,PC Windows 微信 聊天 移动聊天 免费 发信息 发图片 语音 weixin 离线消息 微博 私信 流量,微信,聊天,客服 系统,前端,React.js小书,教程,react,reactjs,node.js,JavaScript 库,webpack,ES6,express js,scss,sass,构建用户界面,组件化,入门教程,网易云音乐,网易云音乐 api,技术交流,@IT·平头哥联盟,首席填坑官∙苏南,@IT·平头哥联盟-首席填坑官∙苏南](https://meibin08.github.io/react-redux-chat/images/index.png) 39 | ![react+redux-chat 模仿实现PC微信聊天,react redux 入门示例, NodeJS 版,,@IT·平头哥联盟,首席填坑官∙苏南,@IT·平头哥联盟-首席填坑官∙苏南](https://meibin08.github.io/react-redux-chat/images/login.png) 40 | 41 | ## 还可以打赏哦~ 42 | 43 | - 如果觉得此示例对你有帮助,可以打赏我一点小费哦~ ^_^ ~ 44 | - 45 | ![苏南, 前端,热爱前端开发,@IT·平头哥联盟,首席填坑官∙苏南,@IT·平头哥联盟-首席填坑官∙苏南,5年前端开发工作经验,meibin08@163.com,react爱好者,业余时间爱写一些自己感兴趣的东西,实践自己所想,爱好:跑步、音乐、爬山、看书、羽毛球等,'宝剑锋从磨砺出,梅花得自苦寒来'](https://meibin08.github.io/NeteaseCloudMusic-SSR/static/reward@x1.png?20180803) 46 | 47 | 48 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 微信 客服系统 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-chat", 3 | "version": "1.0.0", 4 | "description": "模仿实现PC微信聊天 @IT·平头哥联盟-首席填坑官∙苏南", 5 | "main": "app.js", 6 | "scripts": { 7 | "dev": "cross-env NODE_ENV=development webpack-dev-server --inline", 8 | "build": "rm -rf assets && cross-env NODE_ENV=production webpack --progress --hide-modules" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/meibin08/react-redux-chat.git" 13 | }, 14 | "files": [ 15 | "lib", 16 | "src", 17 | "es" 18 | ], 19 | "keywords": [ 20 | "react", 21 | "reactjs", 22 | "hot", 23 | "reload", 24 | "hmr", 25 | "live", 26 | "edit", 27 | "flux", 28 | "redux", 29 | "@IT·平头哥联盟-首席填坑官∙苏南", 30 | "模仿实现PC微信聊天","PC Windows 微信 聊天 移动聊天 免费 发信息 发图片 语音 weixin 离线消息 微博 私信 流量","微信","聊天","客服 系统","前端","React.js小书","教程","node.js","JavaScript 库","webpack","ES6","express js","scss","sass","构建用户界面","组件化","入门教程","redux","入门示例","demo","网易云音乐","网易云音乐 api" 31 | ], 32 | "dependencies": { 33 | "cross-env": "^1.0.6" 34 | }, 35 | "devDependencies": { 36 | "autoprefixer-loader": "^3.2.0", 37 | "babel-core": "^6.9.0", 38 | "babel-loader": "^6.2.4", 39 | "babel-plugin-add-module-exports": "^0.2.1", 40 | "babel-preset-es2015": "^6.9.0", 41 | "babel-preset-react": "^6.5.0", 42 | "babel-preset-react-hmre": "^1.1.1", 43 | "babel-preset-stage-0": "^6.5.0", 44 | "babel-plugin-transform-runtime": "^6.3.13", 45 | "babel-runtime": "^6.0.0", 46 | "classnames": "^2.2.5", 47 | "css-loader": "^0.23.1", 48 | "iscroll": "^5.2.0", 49 | "isomorphic-fetch": "^2.2.1", 50 | "es6-promise": "^3.2.1", 51 | "extract-text-webpack-plugin": "^1.0.1", 52 | "fastclick": "^1.0.6", 53 | "file-loader": "^0.8.5", 54 | "html-loader": "^0.4.3", 55 | "html-webpack-plugin": "^2.17.0", 56 | "copy-webpack-plugin": "^*", 57 | "node-sass": "^3.7.0", 58 | "react": "^0.14.0", 59 | "react-dom": "^0.14.0", 60 | "react-imageloader": "^2.1.0", 61 | "sass-loader": "^3.2.0", 62 | "style-loader": "^0.13.1", 63 | "url-loader": "^0.5.7", 64 | "webpack": "^1.13.1", 65 | "webpack-dev-server": "^1.12.0", 66 | "redux": "^3.0.4", 67 | "react-redux": "^4.0.0", 68 | "redux-thunk": "^1.0.3" 69 | }, 70 | "author": "Bin.Mei ,QQ群:386485473", 71 | "license": "MIT" 72 | } 73 | -------------------------------------------------------------------------------- /src/actions/Chat/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @authors :Bin Mei 3 | * @date :2017-05-22 4 | * @description:react-redux-chat -> 仿微信聊天工具 5 | */ 6 | 7 | import {ajaxJson} from "src/utils/ajax"; 8 | import {fetchJson} from "src/utils/fetch"; 9 | import Storage from 'src/utils/storage'; 10 | import {CHAT_LOGIN,SET_SESSION,FILTER_SEARCH,CHAT_INIT,SEND_MESSAGE,RECEIVE_MESSAGE,SET_DESTROY,SET_LOGOUT} from "src/constants/Chat"; 11 | 12 | 13 | let _store = new Storage(), 14 | Storage_Key = 'username'; 15 | 16 | 17 | let chat = { 18 | chat_init:(data)=>{ 19 | return { 20 | type:CHAT_INIT, 21 | data 22 | } 23 | }, 24 | chatLogin:(options)=>{ 25 | 26 | return (dispatch)=>{ 27 | const {data,success,error}=options; 28 | /*ajaxJson({ 29 | type:"POST", 30 | url:"/initSession", 31 | data:data, 32 | success:(req)=>{ 33 | console.log(req) 34 | if(req.res == 10000){ 35 | let {data}= req; 36 | dispatch({ 37 | type:CHAT_LOGIN, 38 | data 39 | }); 40 | }else{ 41 | console.log(req.errorMsg) 42 | }; 43 | success&&success(req); 44 | },error:()=>{ 45 | error&&error(); 46 | } 47 | }); 48 | return ;*/ 49 | fetchJson({ 50 | type:"POST", 51 | url:"/initSession", 52 | data:data, 53 | success:req=>{ 54 | console.log(req) 55 | if(req.res == 10000){ 56 | _store.set(Storage_Key,data.username,120); 57 | dispatch({ 58 | type:CHAT_LOGIN, 59 | data:req 60 | }); 61 | }else{ 62 | 63 | }; 64 | success&&success(req); 65 | }, 66 | error:err=>{ 67 | console.log(err); 68 | error&&error(); 69 | } 70 | }); 71 | 72 | }; 73 | }, 74 | filter_search:(data)=>{ 75 | return { 76 | type:FILTER_SEARCH, 77 | data 78 | } 79 | }, 80 | set_session:(data)=>{ 81 | return { 82 | type:SET_SESSION, 83 | data 84 | } 85 | }, 86 | send_message:(options)=>{ 87 | return (dispatch)=>{ 88 | const {user,id,content,success,error}=options; 89 | fetchJson({ 90 | type:"POST", 91 | url:"/pushMessage?sid=" + user.sid, 92 | data:{ 93 | 'sid': user.sid, 94 | 'id': id, 95 | 'content':content 96 | }, 97 | success:(req)=>{ 98 | if(req.res == 10000){ 99 | let {data}= req; 100 | 101 | data.unshift({ 102 | content:content, 103 | date: Date.now(), 104 | self: 1 105 | }); 106 | dispatch({ 107 | type:SEND_MESSAGE, 108 | data 109 | }); 110 | }else{ 111 | console.log(req.errorMsg) 112 | }; 113 | success&&success(req); 114 | },error:()=>{ 115 | error&&error(); 116 | } 117 | }); 118 | }; 119 | }, 120 | //接收消息 121 | receive_message:(options)=>{ 122 | return (dispatch)=>{ 123 | const {user,id_list,success,error}=options; 124 | fetchJson({ 125 | type:"POST", 126 | url:"/getMessage?sid=" + user.sid, 127 | data:{ 128 | 'id_list':id_list, 129 | }, 130 | success:(req)=>{ 131 | if(req.res == 10000){ 132 | let {data}= req; 133 | dispatch({ 134 | type:RECEIVE_MESSAGE, 135 | data 136 | }); 137 | }else{ 138 | console.log(req.errorMsg) 139 | }; 140 | success&&success(req); 141 | },error:()=>{ 142 | error&&error(); 143 | } 144 | }); 145 | }; 146 | }, 147 | //送客 148 | set_destroy:(options)=>{ 149 | 150 | return (dispatch)=>{ 151 | const {user,id,success,error}=options; 152 | ajaxJson({ 153 | type:"GET", 154 | url:"/destroySession?sid="+user.sid+'&openid='+id, 155 | success:(req)=>{ 156 | if(req.res == 10000){ 157 | let {data}= req; 158 | dispatch({ 159 | type:SET_DESTROY, 160 | data: content 161 | }); 162 | }else{ 163 | console.log(req.errorMsg) 164 | }; 165 | success&&success(req); 166 | },error:()=>{ 167 | error&&error(); 168 | } 169 | }); 170 | }; 171 | }, 172 | set_logout:(data)=>{ 173 | return { 174 | type:SET_LOGOUT, 175 | data 176 | } 177 | } 178 | }; 179 | export default chat; 180 | 181 | 182 | -------------------------------------------------------------------------------- /src/actions/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @authors :Bin Mei 3 | * @date :2017-05-22 4 | * @description:react-redux-chat -> 仿微信聊天工具 5 | */ 6 | 7 | import chatIndex from "./Chat"; 8 | 9 | let actions = Object.assign({}, 10 | chatIndex, 11 | ); 12 | export default actions; 13 | -------------------------------------------------------------------------------- /src/components/App/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | // import Three from "src/utils/three"; 3 | import './App.scss'; 4 | 5 | class Index extends Component { 6 | 7 | render() { 8 | return ( 9 |
10 | {this.props.children} 11 |
12 | ) 13 | } 14 | } 15 | 16 | export default Index 17 | -------------------------------------------------------------------------------- /src/components/App/App.scss: -------------------------------------------------------------------------------- 1 | 2 | .app-container { 3 | position: relative; 4 | width: 100%; 5 | max-width: 750px; 6 | min-width: 320px; 7 | min-height: 100%; 8 | margin: 0px auto; 9 | overflow: hidden; 10 | } 11 | 12 | @media screen and (min-width: 600px) { 13 | .app-container { 14 | max-width: 1000px; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/App/index.js: -------------------------------------------------------------------------------- 1 | import './App.scss' 2 | import App from './App.jsx'; 3 | export default App; -------------------------------------------------------------------------------- /src/components/common/Scroll/Scroll.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import classnames from 'classnames'; 3 | import IScroll from 'iscroll/build/iscroll-probe.js'; 4 | import Event from 'src/utils/events'; 5 | 6 | class Scroll extends Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | myScroll: null 12 | } 13 | } 14 | 15 | componentDidMount() { 16 | setTimeout( () => { 17 | if(!this.refs.scrollWrapper){ 18 | return false; 19 | } 20 | this.state.myScroll = new IScroll(this.refs.scrollWrapper, { 21 | mouseWheel: true, 22 | probeType: 3, 23 | bounce: false, 24 | preventDefault: false, 25 | disablePointer: false, 26 | fadeScrollbars:true, 27 | scrollbars: this.props.scrollbar, 28 | }); 29 | 30 | this.stopTouchmove = (e) => { 31 | e.preventDefault(); 32 | }; 33 | this.state.myScroll.on('scroll', () => { 34 | this.props.onScroll && this.props.onScroll(this.state.myScroll.y); 35 | }); 36 | 37 | let allowScroll = this.props.allowScroll; 38 | if(!allowScroll){ 39 | Event.on(this.refs.scrollWrapper, 'touchmove', this.stopTouchmove); 40 | Event.on(document, 'touchmove', this.stopTouchmove); 41 | }; 42 | this.toBottom(); 43 | }, 250); 44 | 45 | } 46 | toBottom(){ 47 | let {isToBottom} = this.props; 48 | if(!isToBottom){return;}; 49 | let {myScroll} = this.state; 50 | myScroll.scrollTo(0,myScroll.maxScrollY, 5); 51 | } 52 | manualTouchMove(allowScroll){ 53 | if(allowScroll){ 54 | 55 | Event.off(document, 'touchmove', this.stopTouchmove); 56 | }else{ 57 | Event.on(document, 'touchmove', this.stopTouchmove); 58 | } 59 | } 60 | 61 | componentDidUpdate() { 62 | setTimeout(() => { 63 | this.state.myScroll.refresh(); 64 | this.toBottom(); 65 | }, 350); 66 | } 67 | 68 | componentWillReceiveProps(nextProps) { 69 | this.manualTouchMove(nextProps.allowScroll); 70 | if(nextProps.refresh) { 71 | setTimeout(() => { 72 | this.state.myScroll && this.state.myScroll.refresh(); 73 | }, 150); 74 | } 75 | const { ScrollToY } = nextProps; 76 | 77 | if(!ScrollToY || this.updateY == nextProps.updateY) { 78 | return; 79 | } 80 | 81 | this.updateY = nextProps.updateY; 82 | 83 | this.state.myScroll.scrollTo(0, -ScrollToY, 500) 84 | } 85 | 86 | componentWillUnmount() { 87 | Event.off(this.refs.scrollWrapper, 'touchmove', this.stopTouchmove); 88 | Event.off(document, 'touchmove', this.stopTouchmove); 89 | } 90 | 91 | render() { 92 | const props = this.props; 93 | const { children, className, ...others } = props; 94 | return ( 95 |
96 |
97 | { children } 98 |
99 |
100 | ) 101 | } 102 | } 103 | 104 | export default Scroll 105 | -------------------------------------------------------------------------------- /src/components/common/Scroll/Scroll.scss: -------------------------------------------------------------------------------- 1 | .scroll-wrapper { 2 | position: absolute; 3 | z-index: 1; 4 | top: 0; 5 | bottom: 0; 6 | left: 0; 7 | width: 100%; 8 | // background: #ccc; 9 | overflow: hidden; 10 | } 11 | 12 | .scroller { 13 | position: absolute; 14 | z-index: 1; 15 | -webkit-tap-highlight-color: rgba(0,0,0,0); 16 | width: 100%; 17 | -webkit-transform: translateZ(0); 18 | -moz-transform: translateZ(0); 19 | -ms-transform: translateZ(0); 20 | -o-transform: translateZ(0); 21 | transform: translateZ(0); 22 | -webkit-touch-callout: none; 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | -ms-user-select: none; 26 | user-select: none; 27 | -webkit-text-size-adjust: none; 28 | -moz-text-size-adjust: none; 29 | -ms-text-size-adjust: none; 30 | -o-text-size-adjust: none; 31 | text-size-adjust: none; 32 | } 33 | .iScrollVerticalScrollbar.iScrollLoneScrollbar{ 34 | position: absolute; 35 | z-index: 9999; 36 | width: 5px; 37 | bottom: 2px; 38 | top: 2px; 39 | right: 1px; 40 | overflow: hidden; 41 | pointer-events: none; 42 | .iScrollIndicator{ 43 | box-sizing: border-box; 44 | position: absolute; 45 | background: rgba(66, 70, 77, 0.498039); 46 | border: 1px solid rgba(118, 112, 112, 0.7); 47 | border-radius: 5px; 48 | width: 100%; 49 | transition-duration: 0ms; 50 | display: block; 51 | height: 308px; 52 | } 53 | } -------------------------------------------------------------------------------- /src/components/common/Scroll/index.js: -------------------------------------------------------------------------------- 1 | import './Scroll.scss' 2 | import Scroll from './Scroll.jsx' 3 | 4 | export default Scroll -------------------------------------------------------------------------------- /src/components/common/Svg/Svg.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import classnames from 'classnames'; 3 | 4 | class Svg extends Component { 5 | 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | 10 | } 11 | } 12 | render() { 13 | const props = this.props; 14 | const { hash,herf,title, className, ...others } = props; 15 | return ( 16 | 17 | 18 | 19 | ) 20 | } 21 | } 22 | 23 | export default Svg 24 | -------------------------------------------------------------------------------- /src/components/common/Svg/Svg.scss: -------------------------------------------------------------------------------- 1 | .svg-default{ 2 | width:30px; 3 | height:30px; 4 | background:#fff; 5 | border-radius: 50%; 6 | &:hover{ 7 | box-shadow:0 0 4px #fff; 8 | } 9 | } -------------------------------------------------------------------------------- /src/components/common/Svg/images/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | github 4 | 5 | 6 | 7 | QQ 8 | 9 | 10 | 11 | 退出 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/common/Svg/index.js: -------------------------------------------------------------------------------- 1 | import './Svg.scss' 2 | import Svg from './Svg.jsx' 3 | 4 | export default Svg -------------------------------------------------------------------------------- /src/constants/Chat/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @authors :Bin Mei 3 | * @date :2017-05-22 4 | * @description:react-redux-chat -> 仿微信聊天工具 5 | */ 6 | 7 | export const CHAT_INIT = "CHAT_INIT"; 8 | export const CHAT_LOGIN = "CHAT_LOGIN"; 9 | export const FILTER_SEARCH = "FILTER_SEARCH"; 10 | export const SET_SESSION = "SET_SESSION"; 11 | export const SEND_MESSAGE = "SEND_MESSAGE"; 12 | export const RECEIVE_MESSAGE = "RECEIVE_MESSAGE"; 13 | export const SET_DESTROY = "SET_DESTROY"; 14 | export const SET_LOGOUT = "SET_LOGOUT"; 15 | 16 | -------------------------------------------------------------------------------- /src/pages/Chat/Index/Index.scss: -------------------------------------------------------------------------------- 1 | @import "src/style/index"; 2 | 3 | body{ 4 | background: #f5f5f5 url(./images/bg.jpg) no-repeat center; 5 | -webkit-background-size:100% 100%; 6 | background-size:100% 100%; 7 | } 8 | .wechat{ 9 | margin: 30px auto; 10 | width: 900px; 11 | height: 650px; 12 | overflow: hidden; 13 | border-radius: 3px; 14 | box-shadow:2px 2px 2px rgba(21, 22, 26, 0.33); 15 | border:1px solid #cfcfcf; 16 | background-color:rgba(0,0,0,.8); 17 | .message-w { 18 | height: calc(100% - 160px); 19 | } 20 | .hide{display:none;} 21 | } -------------------------------------------------------------------------------- /src/pages/Chat/Index/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Index/images/1.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Index/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Index/images/2.png -------------------------------------------------------------------------------- /src/pages/Chat/Index/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Index/images/3.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Index/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Index/images/arrow.png -------------------------------------------------------------------------------- /src/pages/Chat/Index/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Index/images/bg.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Index/images/head.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Index/images/head.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Index/images/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 查看规则 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/pages/Chat/Index/images/icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Index/images/icon2.png -------------------------------------------------------------------------------- /src/pages/Chat/Index/images/lg-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Index/images/lg-btn.png -------------------------------------------------------------------------------- /src/pages/Chat/Index/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @authors :Bin Mei 3 | * @date :2017-05-22 4 | * @description:react-redux-chat -> 仿微信聊天工具 5 | */ 6 | 7 | import React, { Component, PropTypes } from 'react'; 8 | import {bindActionCreators} from "redux"; 9 | import {connect} from "react-redux"; 10 | import classnames from 'classnames'; 11 | import actions from "src/actions"; 12 | import Sidebar from "../Sidebar/Index"; 13 | import Messages from "../Messages/Index"; 14 | import Login from "../Login"; 15 | import {fetchJson} from "src/utils/fetch"; 16 | import Storage from 'src/utils/storage'; 17 | 18 | import './Index.scss'; 19 | 20 | let _store = new Storage(), 21 | Storage_Key = 'username'; 22 | 23 | class wechat extends Component{ 24 | constructor(props){ 25 | super(props); 26 | 27 | this.state = { 28 | 29 | 30 | }; 31 | } 32 | componentDidMount(){ 33 | //dia(this); 34 | let {ACTIONS} = this.props; 35 | ACTIONS.chat_init(); 36 | 37 | 38 | } 39 | 40 | render(){ 41 | let {_sessions,_user}=this.props; 42 | return ( 43 |
44 | {_sessions.length>0&&Object.keys(_user).length>0?( 45 |
46 | 47 | 48 |
49 | ):( 50 | 51 | ) 52 | } 53 | 54 |
55 | ); 56 | } 57 | }; 58 | 59 | let mapStateToProps=(state)=>{ 60 | let {sessions,user} = state.chatIndex; 61 | return { 62 | _sessions:sessions, 63 | _user:user 64 | }; 65 | }; 66 | 67 | let mapDispatchToProps=(dispatch)=>{ 68 | return { 69 | ACTIONS:bindActionCreators(actions,dispatch) 70 | }; 71 | }; 72 | export default connect(mapStateToProps,mapDispatchToProps)(wechat); 73 | 74 | -------------------------------------------------------------------------------- /src/pages/Chat/Login/Index.scss: -------------------------------------------------------------------------------- 1 | @import "src/style/index"; 2 | 3 | .login-form{ 4 | position: absolute; 5 | top: 50%; 6 | left: 50%; 7 | z-index: 9; 8 | width: 360px; 9 | margin-left: -180px; 10 | margin-top: -180px; 11 | padding-bottom: 40px; 12 | border-radius: 5px; 13 | background-color: rgba(255,255,255,.5); 14 | -webkit-box-shadow: 1px 1px 3px #2e2d33; 15 | -moz-box-shadow: 1px 3px 3px #2e2d33; 16 | box-shadow: 1px 1px 3px #105a84; 17 | border: 1px solid #787777; 18 | input.lg-inp{ 19 | border: none; 20 | background-color: transparent; 21 | width: 100%; 22 | height: 45px; 23 | font-size: 14px; 24 | display: inline-block; 25 | } 26 | h1{ 27 | font-size: 24px; 28 | color: #434343; 29 | font-style: normal; 30 | text-align: center; 31 | display: block; 32 | padding: 20px 0; 33 | } 34 | .row-error{ 35 | margin: 20px 25px 0; 36 | padding-left: 40px; 37 | } 38 | .row{ 39 | border: 1px solid #a7a5a5; 40 | border-radius: 5px; 41 | background-color:rgba(255,255,255,.7); 42 | margin: 20px 25px 0; 43 | padding-left: 40px; 44 | overflow: hidden; 45 | } 46 | .row.account{ 47 | @include images($url:"./images/user.png",$size:(20px auto)); 48 | background-position: 11px 11px; 49 | } 50 | .row.pwd{ 51 | @include images($url:"./images/password.png",$size:(20px auto)); 52 | background-position: 11px 11px; 53 | } 54 | .row.code{ 55 | padding-left: 0; 56 | margin-right: 145px; 57 | padding-left: 40px; 58 | } 59 | .check-code{ 60 | margin: 20px 25px 0; 61 | } 62 | .check-code p{margin-top: 0; 63 | margin-left: 0;} 64 | a.send{ 65 | float: right; 66 | width: 125px; 67 | height: 45px; 68 | line-height: 45px; 69 | border: 1px solid #e0e0e0; 70 | border-radius: 5px; 71 | background-color: #eeeeee; 72 | font-size: 16px; 73 | color: #797979; 74 | text-align: center; 75 | cursor: pointer; 76 | } 77 | .login-btn{ 78 | margin: 30px 20px 0; 79 | } 80 | .login-btn a{ 81 | display: block; 82 | width: 100%; 83 | height: 50px; 84 | line-height: 50px; 85 | font-size: 22px; 86 | color: #ffffff; 87 | text-align: center; 88 | border-radius: 5px; 89 | background-color: #ed4141; 90 | overflow: hidden; 91 | cursor: pointer; 92 | } 93 | .error-att{ 94 | display: block; 95 | height: 20px; 96 | line-height: 20px; 97 | padding-left: 26px; 98 | font-size: 12px; 99 | color: #ff6040; 100 | } 101 | } -------------------------------------------------------------------------------- /src/pages/Chat/Login/images/password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Login/images/password.png -------------------------------------------------------------------------------- /src/pages/Chat/Login/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Login/images/user.png -------------------------------------------------------------------------------- /src/pages/Chat/Login/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @authors :Bin Mei 3 | * @date :2017-05-22 4 | * @description:react-redux-chat -> 仿微信聊天工具 5 | */ 6 | 7 | import React, { Component, PropTypes } from 'react'; 8 | import {bindActionCreators} from "redux"; 9 | import {connect} from "react-redux"; 10 | import classnames from 'classnames'; 11 | import actions from "src/actions"; 12 | import Sidebar from "../Sidebar/Index"; 13 | import Messages from "../Messages/Index"; 14 | import {fetchJson} from "src/utils/fetch"; 15 | 16 | 17 | import './Index.scss'; 18 | 19 | 20 | 21 | class Login extends Component{ 22 | constructor(props){ 23 | super(props); 24 | this.flag = false; 25 | this.state = { 26 | name: '', 27 | password: '', 28 | error:"请随意输入,账号、密码 格式均为英文+数字" 29 | }; 30 | } 31 | set(e){ 32 | let {name,value}=e.target; 33 | this.setState({ 34 | [`${name}`]:value 35 | }); 36 | } 37 | submit(){ 38 | let {ACTIONS} = this.props; 39 | let {name,password}=this.state; 40 | var name_reg = /^([a-zA-Z0-9]+)$/; 41 | var pwd_reg = /^([a-zA-Z0-9]){3,15}$/; 42 | if(!name.trim() || !name_reg.test(name.trim())){ 43 | this.setState({error:"请输入正确的账号"}); 44 | return false; 45 | }else if(!password.trim()){ 46 | this.setState({error:"请输入您的密码"}); 47 | return false; 48 | }else if(!pwd_reg.test(password.trim())){ 49 | this.setState({error:"密码格式有误,请重新输入"}); 50 | return false; 51 | }else{ 52 | this.setState({error:"正在登录中……"}); 53 | if(this.flag){ 54 | return false; 55 | }; 56 | this.flag = true; 57 | ACTIONS.chatLogin({ 58 | data:{username:name,password:password}, 59 | success:(req)=>{ 60 | this.flag = false; 61 | },error:()=>{ 62 | this.flag = false; 63 | } 64 | }); 65 | }; 66 | } 67 | keyUp(e){ 68 | 69 | if(e.keyCode === 13){ 70 | this.submit(); 71 | } 72 | } 73 | render(){ 74 | let {error} = this.state; 75 | return ( 76 |
77 |

微信客服

78 |

this.set(e)} name="name" type="text" placeholder="账号"/>

79 |

this.set(e)} onKeyUp={(e)=>this.keyUp(e)} name="password" placeholder="密码" />

80 |

{error}

81 |
82 | this.submit()} >确 定 83 |
84 |
85 | ); 86 | } 87 | }; 88 | 89 | let mapStateToProps=(state)=>{ 90 | let {sessions,user} = state.chatIndex; 91 | return { 92 | _sessions:sessions, 93 | _user:user 94 | }; 95 | }; 96 | 97 | let mapDispatchToProps=(dispatch)=>{ 98 | return { 99 | ACTIONS:bindActionCreators(actions,dispatch) 100 | }; 101 | }; 102 | export default connect(mapStateToProps,mapDispatchToProps)(Login); 103 | 104 | -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Dialogue/Index.scss: -------------------------------------------------------------------------------- 1 | @import "src/style/index"; 2 | 3 | .chat-main { 4 | .dialog{ 5 | display:none; 6 | &.show{display:block} 7 | .mask{ 8 | position:absolute; 9 | left:0;bottom:0; 10 | top:0;right:0; 11 | background-color:rgba(0,0,0,.01) 12 | } 13 | .dia-cont{ 14 | position:absolute; 15 | left:50px; 16 | top:20%; 17 | width:260px; 18 | height:180px; 19 | background:#fff; 20 | border-radius:3px; 21 | box-shadow:2px 2px 6px #d8d8d8; 22 | padding:20px; 23 | z-index:10; 24 | .avatar { 25 | img{ 26 | width:58px; 27 | height:58px; 28 | border-radius:5px; 29 | } 30 | width:60px; 31 | height:60px; 32 | background:#fff; 33 | border-radius:3px; 34 | border:1px solid #d8d8d8; 35 | float: left; 36 | margin: 0 10px 0 0; 37 | border-radius: 3px; 38 | } 39 | .nickname{ 40 | line-height:60px; 41 | font-size:14px; 42 | } 43 | .remark{ 44 | margin:20px 0 0; 45 | padding:20px 0 0; 46 | border-top:1px solid #ddd; 47 | line-height:24px; 48 | label{ 49 | color:#a19f9f 50 | } 51 | .input{ 52 | margin-left:5px; 53 | border:1px solid transparent; 54 | padding:2px 4px; 55 | border-radius:3px; 56 | line-height:20px; 57 | max-width:106px; 58 | &:hover{ 59 | background:#e3eef5; 60 | border:1px solid #b6dbf3; 61 | } 62 | &:focus{ 63 | background:none; 64 | border:1px solid #f1f1f1; 65 | } 66 | } 67 | } 68 | } 69 | 70 | } 71 | .group-name{ 72 | height:50px; 73 | line-height:50px; 74 | padding:0 20px; 75 | background-color:#f7fcff; 76 | border-bottom:1px solid #ddd; 77 | h3{ 78 | font-size:15px; 79 | } 80 | } 81 | .message { 82 | 83 | height:calc(100% - 50px); 84 | position:relative; 85 | overflow: hidden; 86 | // overflow-y: scroll; 87 | ul{padding: 10px 15px;} 88 | &.hidden{overflow-y:hidden} 89 | li { 90 | margin-bottom: 15px; 91 | overflow: hidden; 92 | &.first{ 93 | text-align:center; 94 | .history{ 95 | background:#dcdcdc; 96 | padding:3px 5px; 97 | border-radius:3px; 98 | font-size:12px; 99 | cursor:pointer ; 100 | } 101 | } 102 | } 103 | 104 | .time { 105 | margin: 7px 0; 106 | text-align: center; 107 | 108 | > span { 109 | display: inline-block; 110 | padding: 0 18px; 111 | font-size: 12px; 112 | color: #fff; 113 | border-radius: 2px; 114 | background-color: #dcdcdc; 115 | } 116 | } 117 | .avatar { 118 | float: left; 119 | margin: 0 10px 0 0; 120 | border-radius: 100px; 121 | } 122 | .text { 123 | display: inline-block; 124 | position: relative; 125 | padding: 4.5px 10px; 126 | max-width: calc(100% - 150px); 127 | min-height: 30px; 128 | line-height:21px; 129 | font-size: 12px; 130 | text-align: left; 131 | word-break: break-all; 132 | background-color: #f7fcff; 133 | border-radius: 4px; 134 | margin: 3px 0 0; 135 | a.link{ 136 | color:#1144e7; 137 | } 138 | &:before { 139 | content: " "; 140 | position: absolute; 141 | top: 9px; 142 | right: 100%; 143 | border: 6px solid transparent; 144 | border-right-color: #f7fcff; 145 | } 146 | } 147 | 148 | .self { 149 | text-align: right; 150 | 151 | .avatar { 152 | float: right; 153 | margin: 0 0 0 10px; 154 | } 155 | .text { 156 | background-color: #b2e281; 157 | 158 | &:before { 159 | right: inherit; 160 | left: 100%; 161 | border-right-color: transparent; 162 | border-left-color: #b2e281; 163 | } 164 | } 165 | } 166 | } 167 | .iScrollVerticalScrollbar.iScrollLoneScrollbar .iScrollIndicator{ 168 | background: rgba(200, 199, 199, 0.998039); 169 | border: 1px solid rgba(220, 220, 210, 1); 170 | } 171 | } -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Dialogue/images/Bin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Messages/Dialogue/images/Bin.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Dialogue/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Messages/Dialogue/images/arrow.png -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Dialogue/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Messages/Dialogue/images/bg.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Dialogue/images/head.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Messages/Dialogue/images/head.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Dialogue/images/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 查看规则 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Dialogue/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @authors :Bin Mei 3 | * @date :2017-05-22 4 | * @description:react-redux-chat -> 仿微信聊天工具 5 | */ 6 | 7 | import React, { Component, PropTypes } from 'react'; 8 | import {bindActionCreators} from "redux"; 9 | import {connect} from "react-redux"; 10 | import classnames from 'classnames'; 11 | import actions from "src/actions"; 12 | import Scroll from 'src/components/common/Scroll' 13 | // import dia from 'src/utils/dia'; 14 | 15 | import './Index.scss'; 16 | 17 | 18 | 19 | 20 | class Messages extends Component{ 21 | constructor(props){ 22 | super(props); 23 | 24 | this.state = { 25 | z:1 26 | }; 27 | } 28 | componentDidMount(){ 29 | //dia(this); 30 | } 31 | _goTo(y){ 32 | console.log(y) 33 | } 34 | time(date,prevDate){ 35 | // console.log(date,prevDate) 36 | let Interval = 2*60*1000;//区间 37 | let _date = new Date(date); 38 | let _prevDate = new Date(prevDate); 39 | let ret =_date.getTime() - _prevDate.getTime(); 40 | if(ret>=Interval){ 41 | return _date.getFullYear()+"-"+(_date.getMonth()+1)+"-"+_date.getDate(); 42 | }; 43 | return ""; 44 | } 45 | link (str){ 46 | var reg = /(http:\/\/|https:\/\/)((\w|=|\?|\.|\/|&|-)+)/ig 47 | return str.replace(reg,'$1$2') 48 | } 49 | render(){ 50 | let {_user,_currentChat} = this.props; 51 | return ( 52 | 53 |
54 |
55 |

{_currentChat.user.name}

56 |
57 |
58 | this._goTo(y)}> 59 |
    60 |
  • 查看更多历史消息
  • 61 | { 62 | _currentChat.messages.map((item,i)=>{ 63 | return ( 64 |
  • 65 | { 66 | i!=0&&this.time(item.date,_currentChat.messages[i-1].date)!=''?( 67 |

    68 | {this.time(item.date,_currentChat.messages[i-1].date)} 69 |

    70 | ):( 71 | null 72 | ) 73 | } 74 | 75 |
    76 | 77 |
    78 |
    79 |
  • 80 | ); 81 | }) 82 | } 83 |
84 |
85 |
86 |
87 |

88 |
89 |
90 |

91 |

测试的

92 |
93 |

94 | 95 | 96 |

97 |
98 |
99 |
100 | ); 101 | } 102 | }; 103 | 104 | let mapStateToProps=(state)=>{ 105 | let {sessions,user,currentChat} = state.chatIndex; 106 | return { 107 | _sessions:sessions, 108 | _currentChat:currentChat, 109 | _user:user 110 | }; 111 | }; 112 | 113 | let mapDispatchToProps=(dispatch)=>{ 114 | return { 115 | ACTIONS:bindActionCreators(actions,dispatch) 116 | }; 117 | }; 118 | export default connect(mapStateToProps,mapDispatchToProps)(Messages); 119 | 120 | -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Index/Index.scss: -------------------------------------------------------------------------------- 1 | @import "src/style/index"; 2 | 3 | .chat-main { 4 | position: relative; 5 | overflow: hidden; 6 | background-color: #eff3f6; 7 | height:100%; 8 | } 9 | .dialogue-tips{ 10 | height:100%; 11 | line-height: 600px; 12 | font-size:16px; 13 | color:#999; 14 | text-align:center; 15 | background-color:#eee; 16 | } -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Index/images/Bin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Messages/Index/images/Bin.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Index/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Messages/Index/images/arrow.png -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Index/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Messages/Index/images/bg.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Index/images/head.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Messages/Index/images/head.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Index/images/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 查看规则 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Index/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @authors :Bin Mei 3 | * @date :2017-05-22 4 | * @description:react-redux-chat -> 仿微信聊天工具 5 | */ 6 | 7 | import React, { Component, PropTypes } from 'react'; 8 | import {bindActionCreators} from "redux"; 9 | import {connect} from "react-redux"; 10 | import classnames from 'classnames'; 11 | import actions from "src/actions"; 12 | import Scroll from 'src/components/common/Scroll' 13 | // import dia from 'src/utils/dia'; 14 | import Dialogue from "../Dialogue"; 15 | import Send from "../Send"; 16 | import './Index.scss'; 17 | 18 | 19 | 20 | 21 | class Messages extends Component{ 22 | constructor(props){ 23 | super(props); 24 | 25 | this.state = { 26 | 27 | }; 28 | } 29 | componentDidMount(){ 30 | //dia(this); 31 | 32 | } 33 | 34 | render(){ 35 | let {_sessions,_currentChat,_currentId} = this.props; 36 | 37 | if(!Object.keys(_currentChat).length || _currentChat.id != _currentId){ 38 | return ( 39 |
请选择要对话的用户
40 | ); 41 | }; 42 | return ( 43 |
44 | 45 | 46 |
47 | ); 48 | } 49 | }; 50 | 51 | let mapStateToProps=(state)=>{ 52 | let {sessions,user,currentChat,currentUserId} = state.chatIndex; 53 | return { 54 | _sessions:sessions, 55 | _user:user, 56 | _currentId:currentUserId, 57 | _currentChat:currentChat 58 | }; 59 | }; 60 | 61 | let mapDispatchToProps=(dispatch)=>{ 62 | return { 63 | ACTIONS:bindActionCreators(actions,dispatch) 64 | }; 65 | }; 66 | export default connect(mapStateToProps,mapDispatchToProps)(Messages); 67 | 68 | -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Send/Index.scss: -------------------------------------------------------------------------------- 1 | @import "src/style/index"; 2 | 3 | .send { 4 | height: 160px; 5 | border-top: solid 1px #ddd; 6 | background:#fff; 7 | textarea { 8 | padding: 10px; 9 | height: 73%; 10 | width: 100%; 11 | border: none; 12 | outline: none; 13 | font-family: "Micrsofot Yahei"; 14 | resize: none; 15 | } 16 | .hadler{ 17 | padding:5px 15px; 18 | position:relative; 19 | .tips{ 20 | &.show{ 21 | display:block; 22 | } 23 | display:none; 24 | position:absolute; 25 | right:8px; 26 | bottom:50px; 27 | border-radius:5px; 28 | border:1px solid #ddd; 29 | background:#fff; 30 | font-size:13px; 31 | padding:5px 10px; 32 | box-shadow:1px 1px 3px #d8d8d8; 33 | &::after,&:before{ 34 | content:""; 35 | z-index:1; 36 | position:absolute; 37 | right:20px; 38 | bottom:-20px; 39 | border-color: #ddd transparent transparent; 40 | border-style:solid; 41 | border-width:10px; 42 | } 43 | &:before{ 44 | z-index:2; 45 | bottom:-19px; 46 | border-color: #fff transparent transparent; 47 | 48 | } 49 | } 50 | button{ 51 | border: solid 1px #ddd; 52 | padding:6px 10px; 53 | background:#303942; 54 | border-radius:3px; 55 | color:#fff; 56 | &:active{ 57 | background:#656567; 58 | } 59 | 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Send/images/Bin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Messages/Send/images/Bin.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Send/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Messages/Send/images/arrow.png -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Send/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Messages/Send/images/bg.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Send/images/head.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Messages/Send/images/head.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Send/images/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 查看规则 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/pages/Chat/Messages/Send/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @authors :Bin Mei 3 | * @date :2017-05-22 4 | * @description:react-redux-chat -> 仿微信聊天工具 5 | */ 6 | 7 | import React, { Component, PropTypes } from 'react'; 8 | import {bindActionCreators} from "redux"; 9 | import {connect} from "react-redux"; 10 | import classnames from 'classnames'; 11 | import actions from "src/actions"; 12 | 13 | import dia from 'src/utils/dia'; 14 | 15 | import './Index.scss'; 16 | 17 | 18 | 19 | 20 | class Messages extends Component{ 21 | constructor(props){ 22 | super(props); 23 | this.time = null; 24 | this.flag = false; 25 | this.state = { 26 | content:"", 27 | tips:false 28 | }; 29 | } 30 | componentDidMount(){ 31 | dia(this); 32 | 33 | } 34 | isTisp(){ 35 | clearTimeout(this.time); 36 | this.open("tips"); 37 | this.tips=true; 38 | this.time = setTimeout(()=>{ 39 | this.close("tips"); 40 | },2400); 41 | } 42 | Set(e){ 43 | let {name,value}= e.target; 44 | if(e.ctrlKey && e.keyCode === 13){ 45 | value=value+"\n"; 46 | }; 47 | this.setState({ 48 | [`${name}`]:value 49 | }); 50 | 51 | } 52 | filter(str){ 53 | return str(/[|`|~|#|$|^|{|}|\\|[\\]|<|>|~#|——|{|}|【|】]/) 54 | } 55 | validate(e){ 56 | let {content} = this.state; 57 | if(( content.trim().length <= 0) ){ 58 | this.setState({content:content.trim()}); 59 | e.target.value=content.trim(); 60 | this.isTisp(); 61 | return false; 62 | }; 63 | return true; 64 | 65 | } 66 | save(){ 67 | let {ACTIONS,_user,_currentId} = this.props; 68 | let {content} = this.state; 69 | if(this.flag){ 70 | return false; 71 | }; 72 | this.flag = true; 73 | ACTIONS.send_message({ 74 | user:_user,id:_currentId,content:content, 75 | success:(req)=>{ 76 | this.flag = false; 77 | }, 78 | error:()=>{ 79 | this.flag = false; 80 | } 81 | }); 82 | this.setState({ 83 | content:"" 84 | },()=>{ 85 | this.refs.textarea.value =""; 86 | }); 87 | } 88 | enter(e){ 89 | let {ACTIONS,_user,_currentId} = this.props; 90 | let {name,value}= e.target; 91 | let {content} = this.state; 92 | if(e.keyCode === 13 && !this.validate(e)){ 93 | return false; 94 | }else if(e.ctrlKey && e.keyCode === 13){ 95 | value=value+"\n"; 96 | e.target.value= value; 97 | this.setState({ 98 | [`${name}`]:value 99 | }); 100 | return false; 101 | }; 102 | if( ( content.trim().length && e.keyCode === 13 )){ 103 | this.save(); 104 | console.log("发送内容") 105 | return false; 106 | }; 107 | this.setState({ 108 | [`${name}`]:value 109 | }); 110 | } 111 | sends(e){ 112 | if(!this.validate(e)){ 113 | return false; 114 | }else{ 115 | this.save(); 116 | }; 117 | } 118 | destroy(){ 119 | let {ACTIONS,_user,_currentId} = this.props; 120 | if(_currentId == 1){ 121 | return; 122 | }; 123 | ACTIONS.set_destroy({ 124 | user:_user,id:_currentId 125 | }); 126 | } 127 | render(){ 128 | let {tips,content}=this.state; 129 | return ( 130 |
131 | 132 |

133 | 134 | 135 | 不能发送空白信息或特殊字符 136 |

137 |
138 | ); 139 | } 140 | }; 141 | 142 | let mapStateToProps=(state)=>{ 143 | let {sessions,user,currentUserId} = state.chatIndex; 144 | return { 145 | _sessions:sessions, 146 | _user:user, 147 | _currentId:currentUserId 148 | }; 149 | }; 150 | 151 | let mapDispatchToProps=(dispatch)=>{ 152 | return { 153 | ACTIONS:bindActionCreators(actions,dispatch) 154 | }; 155 | }; 156 | export default connect(mapStateToProps,mapDispatchToProps)(Messages); 157 | 158 | -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/Index/Index.scss: -------------------------------------------------------------------------------- 1 | @import "src/style/index"; 2 | 3 | .sidebar { 4 | float: left; 5 | width: 230px; 6 | color: #f4f4f4; 7 | background-color: #303942; 8 | height:100%; 9 | .card { 10 | padding:26px 15px 23px; 11 | border-bottom: solid 1px #24272C; 12 | .user{ 13 | padding:0 0 15px 11px; 14 | .avatar{ 15 | width:47px; 16 | height:47px; 17 | border-radius:100px; 18 | border:1px solid #fff; 19 | background-color:#ccc; 20 | } 21 | } 22 | .avatar, .name { 23 | vertical-align: middle; 24 | 25 | } 26 | .name { 27 | display: inline-block; 28 | margin: 0 0 0 15px; 29 | font-size:22px; 30 | } 31 | .search { 32 | padding: 0 10px; 33 | width: 100%; 34 | font-size: 12px; 35 | @include images($url:"./images/search.png",$size:(20px auto)); 36 | background-position:95% 50%; 37 | color: #fff; 38 | height: 30px; 39 | line-height: 30px; 40 | border: solid 1px #4b4949; 41 | border-radius: 100px; 42 | outline: none; 43 | background-color: #26292E; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/Index/images/Bin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Sidebar/Index/images/Bin.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/Index/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Sidebar/Index/images/arrow.png -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/Index/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Sidebar/Index/images/bg.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/Index/images/head.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Sidebar/Index/images/head.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/Index/images/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 查看规则 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/Index/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Sidebar/Index/images/search.png -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/Index/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @authors :Bin Mei 3 | * @date :2017-05-22 4 | * @description:react-redux-chat -> 仿微信聊天工具 5 | */ 6 | 7 | import React, { Component, PropTypes } from 'react'; 8 | import {bindActionCreators} from "redux"; 9 | import {connect} from "react-redux"; 10 | import classnames from 'classnames'; 11 | import actions from "src/actions"; 12 | 13 | import List from '../List'; 14 | 15 | import './Index.scss'; 16 | 17 | 18 | 19 | 20 | class Sidebar extends Component{ 21 | constructor(props){ 22 | super(props); 23 | 24 | this.state = { 25 | 26 | 27 | }; 28 | } 29 | componentDidMount(){ 30 | //dia(this); 31 | 32 | } 33 | search(e){ 34 | let {value}=e.target; 35 | let {ACTIONS} = this.props; 36 | ACTIONS.filter_search(value); 37 | } 38 | render(){ 39 | let {_user} = this.props; 40 | return ( 41 |
42 |
43 |
44 | 45 |

{_user.name}

46 |
47 |
48 | this.search(e)} placeholder="search user..." /> 49 |
50 |
51 | 52 |
53 | ); 54 | } 55 | }; 56 | 57 | let mapStateToProps=(state)=>{ 58 | let {user,filterKey} = state.chatIndex; 59 | return { 60 | _user:user, 61 | _filterKey:filterKey 62 | }; 63 | }; 64 | 65 | let mapDispatchToProps=(dispatch)=>{ 66 | return { 67 | ACTIONS:bindActionCreators(actions,dispatch) 68 | }; 69 | }; 70 | export default connect(mapStateToProps,mapDispatchToProps)(Sidebar); 71 | 72 | -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/List/Index.scss: -------------------------------------------------------------------------------- 1 | @import "src/style/index"; 2 | 3 | .logout{ 4 | padding:7.5px 0; 5 | border-top:1px solid #292C33; 6 | text-align:center; 7 | button{ 8 | background:#fff; 9 | border-radius:4px; 10 | border:1px solid #d8d8d8; 11 | height:25px; 12 | line-height:23px; 13 | padding:0 10px; 14 | } 15 | .ic{ 16 | display: inline-block; 17 | width:30px; 18 | height:30px; 19 | position:relative; 20 | &:hover{ 21 | .msg{display:block;} 22 | } 23 | .msg{ 24 | display:none; 25 | position:absolute; 26 | z-index:3; 27 | padding:4px 7px; 28 | background-color:#26292E; 29 | top:-58px; 30 | left:-50px; 31 | width:180px; 32 | line-height:20px; 33 | border-radius:5px; 34 | color:#fff; 35 | font-size:12px; 36 | box-shadow:1px 1px 0px rgba(255, 255, 255, 0.19); 37 | &:after{ 38 | content: ""; 39 | position:absolute; 40 | bottom:-16px; 41 | left:56px; 42 | width: 8px; 43 | height: 8px; 44 | border:8px solid transparent; 45 | border-top-color:#26292E; 46 | 47 | } 48 | 49 | } 50 | &.qq .msg{ 51 | width:200px; 52 | left:-82px; 53 | &:after{ 54 | left:89px; 55 | } 56 | } 57 | &:not(:first-child){ 58 | margin-left:14px; 59 | } 60 | } 61 | } 62 | .list { 63 | overflow:hidden; 64 | height:455px; 65 | // overflow-y: auto; 66 | overflow:hidden; 67 | position:relative; 68 | li { 69 | padding: 13px 15px; 70 | border-bottom: 1px solid #292C33; 71 | cursor: pointer; 72 | transition: background-color .1s; 73 | position:relative; 74 | &:hover { 75 | background-color: rgba(255, 255, 255, 0.03); 76 | } 77 | &.active { 78 | background-color: #464e55; 79 | } 80 | &.hide{ 81 | display: none; 82 | } 83 | .dot{ 84 | position:absolute; 85 | left: 42px; 86 | top: 9px; 87 | width: 8px; 88 | height: 8px; 89 | border-radius:100%; 90 | background:#f71b1b; 91 | } 92 | } 93 | .avatar{ 94 | display:inline-block; 95 | margin:0; 96 | background:#f1f1f1; 97 | } 98 | .avatar img, .name { 99 | vertical-align: middle; 100 | } 101 | .avatar, .avatar img{ 102 | border-radius: 5px; 103 | } 104 | .name { 105 | display: inline-block; 106 | margin: 0 0 0 15px; 107 | max-width: 110px; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/List/images/Bin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Sidebar/List/images/Bin.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/List/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Sidebar/List/images/arrow.png -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/List/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Sidebar/List/images/bg.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/List/images/head.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meibin08/react-redux-chat/e06a7adab5fd8b2a034de446bf1c57e2e7347174/src/pages/Chat/Sidebar/List/images/head.jpg -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/List/images/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 查看规则 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/pages/Chat/Sidebar/List/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @authors :Bin Mei 3 | * @date :2017-05-22 4 | * @description:react-redux-chat -> 仿微信聊天工具 5 | */ 6 | 7 | import React, { Component, PropTypes } from 'react'; 8 | import {bindActionCreators} from "redux"; 9 | import {connect} from "react-redux"; 10 | import classnames from 'classnames'; 11 | import actions from "src/actions"; 12 | import Scroll from 'src/components/common/Scroll'; 13 | import Svg from 'src/components/common/Svg'; 14 | 15 | // import dia from 'src/utils/dia'; 16 | 17 | import './Index.scss'; 18 | 19 | 20 | 21 | 22 | class List extends Component{ 23 | constructor(props){ 24 | super(props); 25 | this.time = null; 26 | this.flag = false; 27 | this.state = { 28 | }; 29 | } 30 | componentDidMount(){ 31 | this.getMessage(); 32 | } 33 | componentWillUnmount(){ 34 | clearInterval(this.time); 35 | } 36 | Random(num){ 37 | let {_id_list} = this.props; 38 | let arr = _id_list.concat([]); 39 | var return_array=[]; 40 | for (var i = 0; i0) { 45 | var arrIndex = Math.floor(Math.random()*arr.length); 46 | return_array[i] = arr[arrIndex]; 47 | arr.splice(arrIndex, 1); 48 | } else { 49 | break; 50 | }; 51 | }else{ 52 | break; 53 | }; 54 | } 55 | return return_array; 56 | } 57 | getMessage(){ 58 | this.time = setInterval(()=>{ 59 | let {ACTIONS,_user} = this.props; 60 | let id_list = this.Random(3); 61 | // return ; 62 | if(id_list.length<=0 || this.flag){ 63 | return false; 64 | }; 65 | this.flag = true; 66 | ACTIONS.receive_message({ 67 | id_list:id_list, 68 | user:_user, 69 | success:req=>{ 70 | this.flag = false; 71 | },error:err=>{ 72 | this.flag = false; 73 | } 74 | }); 75 | },8000); 76 | 77 | // console.log(y) 78 | } 79 | render(){ 80 | let {_filterKey,_sessions,_currentId,_currentChat,ACTIONS} = this.props; 81 | return ( 82 |
83 |
84 | 85 |
    86 | { 87 | _sessions.map((item,i)=>{ 88 | return ( 89 |
  • ACTIONS.set_session(item.id)}> 93 |

    94 | 95 |

    96 |

    {item.user.name}

    97 | {item.status?():(null)} 98 |
  • 99 | ); 100 | }) 101 | } 102 |
103 |
104 |
105 |
106 | 107 | 108 |

如果该示例帮助了你,记得去github上帮我点颗星哦

109 |
110 | 111 | 112 |

您在使用的过程中,有不懂的疑问或者bug可以加QQ群,一起交流哦

113 |
114 | this.props.ACTIONS.set_logout()}> 115 | 116 | 117 | 118 |
119 |
120 | ); 121 | } 122 | }; 123 | 124 | let mapStateToProps=(state)=>{ 125 | let {sessions,user,id_list,filterKey,currentChat,currentUserId} = state.chatIndex; 126 | return { 127 | _sessions:sessions, 128 | _user:user, 129 | _id_list:id_list, 130 | _filterKey:filterKey, 131 | _currentChat:currentChat, 132 | _currentId:currentUserId 133 | }; 134 | }; 135 | 136 | let mapDispatchToProps=(dispatch)=>{ 137 | return { 138 | ACTIONS:bindActionCreators(actions,dispatch) 139 | }; 140 | }; 141 | export default connect(mapStateToProps,mapDispatchToProps)(List); 142 | 143 | -------------------------------------------------------------------------------- /src/pages/route.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {Provider} from "react-redux"; 4 | import Store from "src/store"; 5 | import App from 'src/components/App'; 6 | import Chat from 'src/pages/Chat/Index'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | 13 | , 14 | document.getElementById('app') 15 | ); 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/reducers/Chat/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @authors :Bin Mei 3 | * @date :2017-05-22 4 | * @description:react-redux-chat -> 仿微信聊天工具 5 | */ 6 | 7 | import {CHAT_LOGIN,SET_SESSION,FILTER_SEARCH,CHAT_INIT,SEND_MESSAGE,RECEIVE_MESSAGE,SET_DESTROY,SET_LOGOUT} from "src/constants/Chat"; 8 | import Storage from 'src/utils/storage'; 9 | let _stores = new Storage(), 10 | Storage_Key = 'username'; 11 | 12 | 13 | let initStates = { 14 | user:{ 15 | name:"Bin", 16 | img:"https://ps.ssl.qhimg.com/t01531c2d8bd3dbe644.jpg" 17 | }, 18 | sessions:[ 19 | { 20 | id:1, 21 | user: { 22 | name:"使用帮助", 23 | img:"https://ps.ssl.qhimg.com/t01531c2d8bd3dbe644.jpg" 24 | }, 25 | messages:[ 26 | { 27 | content:"该示例主要使用了react、redux、iscroll、fetch等组件实现模仿实现PC微信聊天,", 28 | date: Date.now(), 29 | self: 0 30 | },{ 31 | content:"希望能对喜欢react,对于redux还处理迷茫,不知如何入手的小伙伴能起到入门指引", 32 | date: Date.now(), 33 | self: 0 34 | }, 35 | { 36 | content:"如有不足之处,欢迎拍砖指出", 37 | date: Date.now(), 38 | self: 0 39 | }, 40 | { 41 | content:"如果该项目帮助了您,请记得帮我点颗星,就是对我最大的支持", 42 | date: Date.now(), 43 | self: 0 44 | }, 45 | { 46 | content:"项目地址:https://github.com/meibin08/react-redux-chat", 47 | date: Date.now(), 48 | self: 1 49 | },{ 50 | content:"当然如果您在使用的过程中,有不懂的地方,或更好的建议,我们也可以一起来讨论,欢迎加入React\redux技术交流群一起讨论", 51 | date: Date.now(), 52 | self: 0 53 | }, 54 | { 55 | content:"QQ技术交流群:386485473", 56 | date: Date.now(), 57 | self: 1 58 | } 59 | ] 60 | } 61 | ], 62 | currentChat:{}, 63 | currentUserId:1, 64 | id_list:[], 65 | filterKey:"" 66 | }; 67 | let currentChat={}; 68 | let sessions= []; 69 | function chatIndex(state = initStates,action){ 70 | switch(action.type){ 71 | 72 | case CHAT_LOGIN: 73 | let id_list = action.data.sessions.map((item)=>{ 74 | return item.id; 75 | }); 76 | // console.log("SEARCH_RESULT = 17",initStates); 77 | action.data.sessions.unshift(initStates.sessions[0]); 78 | return Object.assign({},state,{...action.data,id_list,currentUserId:1,currentChat:initStates.sessions[0]}); 79 | 80 | case CHAT_INIT: 81 | var _store = JSON.parse(localStorage.getItem("_store")||"{}"); 82 | if(!_stores.get(Storage_Key)){ 83 | // console.log(111) 84 | localStorage.clear(); 85 | return Object.assign({},state,{...initStates,sessions:[]}); 86 | }; 87 | if(_store && _store.chatIndex){ 88 | let {sessions,currentUserId,user,id_list}=_store.chatIndex; 89 | // console.log(89,sessions); 90 | currentChat = (sessions.filter((item)=>item.id==currentUserId)[0]||{}); 91 | // return Object.assign({},state,{sessions,currentUserId,user,id_list,currentChat:currentChat,filterKey:""}); 92 | }; 93 | return Object.assign({},state,(_store.chatIndex||{}),{currentChat:currentChat,filterKey:""}); 94 | 95 | //搜索 96 | case FILTER_SEARCH: 97 | 98 | return Object.assign({},state,{ 99 | filterKey:action.data 100 | }); 101 | 102 | case SET_SESSION: 103 | 104 | sessions = state.sessions.map((item)=>{ 105 | if(item.id==action.data){ 106 | item.status=false; 107 | currentChat= item; 108 | }; 109 | return item; 110 | }); 111 | return Object.assign({},state,{ 112 | sessions, 113 | currentChat, 114 | currentUserId:action.data 115 | }); 116 | 117 | case SEND_MESSAGE: //发送消息 118 | // console.log("SEND_MESSAGE",action.data); 119 | 120 | sessions = state.sessions.map((item)=>{ 121 | if(item.id==state.currentUserId){ 122 | item.messages=item.messages.concat(action.data); 123 | currentChat= item; 124 | }; 125 | return item; 126 | }); 127 | // (sessions.filter((item)=>item.id==state.currentUserId)[0]) 128 | return Object.assign({},state,{ 129 | sessions:sessions, 130 | currentChat:currentChat 131 | }); 132 | //接收消息 133 | case RECEIVE_MESSAGE: 134 | // console.log("SEND_MESSAGE",action.data); 135 | if(action.data.length <= 0){ 136 | return state; 137 | }; 138 | for(let key in action.data){ 139 | console.log(action.data[key]) 140 | let {id} = action.data[key]; 141 | sessions = state.sessions.map((item)=>{ 142 | 143 | if(item.id == id && item.id != state.currentUserId){ 144 | item.status = true; 145 | item.messages=item.messages.concat(action.data[key].messages); 146 | 147 | }; 148 | if(item.id==state.currentUserId){ 149 | currentChat= item; 150 | }; 151 | return item; 152 | }); 153 | }; 154 | // (sessions.filter((item)=>item.id==state.currentUserId)[0]) 155 | return Object.assign({},state,{ 156 | sessions:sessions, 157 | currentChat:currentChat 158 | }); 159 | // 送客 160 | case SET_DESTROY: 161 | let _sessions = state.sessions.filter((item)=>item.id !== state.currentUserId); 162 | // (sessions.filter((item)=>item.id==state.currentUserId)[0]) 163 | return Object.assign({},state,{ 164 | sessions:_sessions, 165 | currentChat:_sessions[0], 166 | currentUserId:_sessions[0].id 167 | }); 168 | //退出 169 | case SET_LOGOUT: 170 | localStorage.clear(); 171 | return Object.assign({},state,{currentChat:1,user:{},sessions:[],filterKey:""}); 172 | default: 173 | return state; 174 | }; 175 | }; 176 | 177 | export default chatIndex; 178 | 179 | 180 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | 2 | //combinReducers用于合并各模块的reducers; 3 | import {combineReducers} from "redux"; 4 | import chatIndex from "./Chat"; 5 | 6 | export default combineReducers({ 7 | chatIndex, 8 | }); 9 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @authors :Bin Mei 3 | * @date :2017-05-22 4 | * @description:react-redux-chat -> 仿微信聊天工具 5 | */ 6 | import {createStore,applyMiddleware} from "redux"; 7 | import thunk from "redux-thunk"; 8 | import reducers from "src/reducers"; 9 | 10 | function configStore (){ 11 | let createStoreWithMiddleware = applyMiddleware(thunk)(createStore); 12 | //dev环境开启redux调试 13 | let cStore = createStoreWithMiddleware(reducers,(__DEBUG__ && window.devToolsExtension ? window.devToolsExtension() : undefined)); 14 | return cStore; 15 | }; 16 | let Store = configStore(); 17 | 18 | let currentVal ; 19 | Store.subscribe(() => { 20 | let prevVal = currentVal; 21 | currentVal = Store.getState(); 22 | localStorage.setItem("_store",JSON.stringify(currentVal)); 23 | if (prevVal !== currentVal) { 24 | //console.log(currentVal,'state发生了变化') 25 | // console.log('Some deep nested property changed from', prevVal, 'to', currentVal) 26 | }; 27 | }) 28 | export default Store; -------------------------------------------------------------------------------- /src/style/base.scss: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | -webkit-box-sizing: border-box; 4 | -moz-box-sizing: border-box; 5 | box-sizing: border-box; 6 | } 7 | *:before, 8 | *:after { 9 | -webkit-box-sizing: border-box; 10 | -moz-box-sizing: border-box; 11 | box-sizing: border-box; 12 | } 13 | 14 | html { 15 | font-size: 14px; 16 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 17 | } 18 | 19 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,form,legend,input,textarea,p,th,td{margin:0;padding:0;} 20 | a,input,button,select,textarea{outline:none}img{border:0;vertical-align:bottom;}em{font-style:normal}textarea{resize:none;} 21 | body{font-family: "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", "Hiragino Sans GB", "WenQuanYi Micro Hei", sans-serif; 22 | font-size: 14px; 23 | line-height: 1.42857143; 24 | color: #383838;} 25 | a{text-decoration:none;} 26 | a:hover{text-decoration:none;} 27 | ol,ul{list-style:none} 28 | h1,h2,h3,h4,h5{font-size:14px;} 29 | input,label,select{vertical-align:middle;font:100% "Microsoft YaHei",Tahoma} 30 | .clearfix:before,.clearfix:after{content:".";display:table;height:0;font-size:0;line-height:0;visibility:hidden;} 31 | .clearfix:after{clear:both;} 32 | .clearfix{*zoom:1;} 33 | .fl{float:left;display:inline-block;} 34 | .fr{float:right;display:inline-block;} 35 | body, html { 36 | height: 100%; 37 | overflow: hidden; 38 | } -------------------------------------------------------------------------------- /src/style/func.scss: -------------------------------------------------------------------------------- 1 | 2 | // 闭合子元素的浮动 3 | @mixin clearfix ($extend:true) { 4 | @if $extend { 5 | @extend %clearfix; 6 | } @else { 7 | *zoom: 1; 8 | &:before, 9 | &:after { 10 | content: ""; 11 | display: table; 12 | } 13 | &:after { 14 | clear: both; 15 | } 16 | } 17 | } 18 | %clearfix{ 19 | @include clearfix(false); 20 | } 21 | 22 | @mixin border($top:1, $right:1, $bottom:1, $left:1, $color:#ebebf0) { 23 | background-image:linear-gradient(180deg, $color, $color 50%, transparent 50%), 24 | linear-gradient(90deg, $color, $color 50%, transparent 50%), 25 | linear-gradient(0deg, $color, $color 50%, transparent 50%), 26 | linear-gradient(90deg, $color, $color 50%, transparent 50%); 27 | background-size: 100% $top + px, $right + px 100%, 100% $bottom + px, $left + px 100%; 28 | background-repeat: no-repeat; 29 | background-position: top, right top, bottom, left top ; 30 | } 31 | 32 | @mixin borderTop($top:1, $color:#ebebf0) { 33 | @include border($top, 0, 0, 0, $color); 34 | } 35 | @mixin borderRight($right:1, $color:#ebebf0) { 36 | @include border(0, $right, 0, 0, $color); 37 | } 38 | @mixin borderBottom($bottom:1, $color:#ebebf0) { 39 | @include border(0, 0, $bottom, 0, $color); 40 | } 41 | @mixin borderLeft($left:1, $color:#ebebf0) { 42 | @include border(0, 0, 0, $left, $color); 43 | } 44 | @mixin borderColor($color:#ebebf0) { 45 | @include border(1, 1, 1, 1, $color); 46 | } 47 | @mixin borderRadius($width:1,$style:solid,$color:#ebebf0,$radius:2px) { 48 | position:relative; 49 | &:after{ 50 | left:0px; 51 | top:0px; 52 | right:-100%; 53 | bottom:-100%; 54 | border-radius:$radius; 55 | border-width: $width + px; 56 | border-style: $style; 57 | border-color: $color; 58 | position:absolute; 59 | display:block; 60 | -webkit-transform:scale(0.5); 61 | -webkit-transform-origin:0% 0%; 62 | content:''; 63 | } 64 | } 65 | 66 | @mixin images($url:'images/check.png',$repeat:no-repeat,$size:auto) { 67 | background-image:url($url); 68 | background-repeat:$repeat; 69 | -webkit-background-size:$size; 70 | background-size:$size; 71 | } 72 | @mixin flex() { 73 | display: box; 74 | display: -webkit-box; 75 | display: flex; 76 | display: -webkit-flex; 77 | } 78 | @mixin ellipsis{ 79 | white-space: nowrap;text-overflow: ellipsis;overflow:hidden; 80 | } 81 | @mixin box($pack: center, $align: center) { 82 | @include flex; 83 | box-pack: $pack; 84 | box-align: $align; 85 | -webkit-box-pack: $pack; 86 | -webkit-box-align: $align; 87 | } 88 | @mixin flex-y{ 89 | -webkit-align-self: center; 90 | align-self: center; 91 | } 92 | -------------------------------------------------------------------------------- /src/style/index.scss: -------------------------------------------------------------------------------- 1 | @import "func"; 2 | @import "base"; 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/utils/ajax.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const ajaxJson = (options) => { 4 | 5 | options.url ="https://easy-mock.com/mock/59294d8e91470c0ac1fe8a4c/staff"+options.url; 6 | 7 | const { url, type, data, ...others } = options; 8 | 9 | let opts = { 10 | type: type || 'get', 11 | url, 12 | data, 13 | success:(resData)=>{ 14 | resHandler(resData,options) 15 | }, 16 | error:(error,status)=>{ 17 | errorHandler(error,options,status) 18 | }, 19 | } 20 | $.ajax(opts); 21 | } 22 | 23 | 24 | function toJson(resp, options) { 25 | if (resp.status >= 400) { 26 | return errorHandler(null, options, resp.status) 27 | } 28 | return resp.json() 29 | } 30 | 31 | // 请求成功处理 32 | function resHandler(resData, options) { 33 | 34 | if (resData.status && resData.status != 200) { 35 | return errorHandler(resData.error, options, resData.status); 36 | } 37 | 38 | if (!resData || resData.res > 20000) { 39 | options.error && options.error(resData) 40 | console.log(resData.message); 41 | } else { 42 | options.success && options.success(resData); 43 | } 44 | } 45 | 46 | // 异常处理 47 | function errorHandler(error, options, status) { 48 | options.error && options.error(error); 49 | console.log(`网络异常,请稍后重试(${status})`) 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/dia.js: -------------------------------------------------------------------------------- 1 | 2 | //页面组件共用方法 3 | 4 | 5 | module.exports = (domain)=>{ 6 | domain.open = (key, props)=>{ 7 | domain.setState({ 8 | [`${key}`]: true, 9 | props 10 | }); 11 | } 12 | domain.close = (key)=>{ 13 | domain.setState({ 14 | [`${key}`]: false, 15 | }); 16 | } 17 | }; -------------------------------------------------------------------------------- /src/utils/events.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | 5 | on: function (el, type, callback) { 6 | if(el.addEventListener) { 7 | el.addEventListener(type, callback); 8 | } else { 9 | el.attachEvent('on' + type, function() { 10 | callback.call(el); 11 | }); 12 | } 13 | }, 14 | 15 | off: function (el, type, callback) { 16 | if(el.removeEventListener) { 17 | el.removeEventListener(type, callback); 18 | } else { 19 | el.detachEvent('off' + type, callback); 20 | } 21 | }, 22 | 23 | once: function (el, type, callback) { 24 | let typeArray = type.split(' '); 25 | let recursiveFunction = function(e){ 26 | e.target.removeEventListener(e.type, recursiveFunction); 27 | return callback(e); 28 | }; 29 | 30 | for (let i = typeArray.length - 1; i >= 0; i--) { 31 | this.on(el, typeArray[i], recursiveFunction); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/fetch.js: -------------------------------------------------------------------------------- 1 | import promise from 'es6-promise' 2 | import fetch from 'isomorphic-fetch' 3 | // import StaticToast from 'src/components/common/toast' 4 | 5 | promise.polyfill(); 6 | 7 | export const fetchJson = (options) => { 8 | 9 | 10 | options.url ="https://easy-mock.com/mock/59294d8e91470c0ac1fe8a4c/staff"+options.url; 11 | const { url, type, data, ...others } = options; 12 | let opts = { 13 | ...others, 14 | method: type || 'get', 15 | // credentials: 'include',// 日志上报的,需求注释这一行 16 | headers: { 17 | 'X-Avoscloud-Application-Id': 'toi4KhzlzSCXvIzkI9FHIEt5-gzGzoHsz', 18 | 'X-Avoscloud-Application-Key':'5NNtepVs7mF6R8U8TPjImffo', 19 | 'Accept': 'application/json', 20 | 'Content-Type': 'application/json' 21 | } 22 | } 23 | 24 | if (['POST', 'PUT'].indexOf(opts.method.toUpperCase()) >= 0) { 25 | opts.body = JSON.stringify(data) 26 | } 27 | 28 | fetch(url, opts) 29 | .then(resData => toJson(resData, opts)) 30 | .then(resData => resHandler(resData, opts)) 31 | // .catch(error => errorHandler(error, opts)) 32 | } 33 | 34 | export const fetchFormData = (options) => { 35 | 36 | const { url, type, data, ...others } = options; 37 | 38 | let opts = { 39 | ...others, 40 | method: type || 'get', 41 | credentials: 'include', 42 | } 43 | 44 | if (['POST', 'PUT'].indexOf(opts.method.toUpperCase()) >= 0) { 45 | opts.body = data 46 | } 47 | 48 | fetch(url, opts) 49 | .then(resData => toJson(resData, opts)) 50 | .then(resData => resHandler(resData, opts)) 51 | // .catch(error => errorHandler(error, opts)) 52 | } 53 | 54 | 55 | function toJson(resp, options) { 56 | if (resp.status >= 400) { 57 | return errorHandler(null, options, resp.status) 58 | } 59 | return resp.json() 60 | } 61 | 62 | // 请求成功处理 63 | function resHandler(resData, options) { 64 | 65 | if (resData.status && resData.status != 200) { 66 | return errorHandler(resData.error, options, resData.status); 67 | } 68 | 69 | if (!resData || resData.code > 20000) { 70 | options.error && options.error(resData) 71 | console.log(resData.message); 72 | } else { 73 | options.success && options.success(resData); 74 | } 75 | } 76 | 77 | // 异常处理 78 | function errorHandler(error, options, status) { 79 | options.error && options.error(error); 80 | console.log(`网络异常,请稍后重试(${status})`) 81 | } 82 | -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | 2 | class storage { 3 | 4 | constructor(props) { 5 | this.props = props || {} 6 | this.source = this.props.source || window.localStorage 7 | } 8 | 9 | get(key) { 10 | const data = this.source, 11 | timeout = data[`${key}__expires__`] 12 | 13 | // 过期失效 14 | if (new Date().getTime() >= timeout) { 15 | this.remove(key) 16 | return; 17 | } 18 | 19 | const value = data[key] 20 | ? JSON.parse(data[key]) 21 | : data[key] 22 | return value 23 | } 24 | 25 | // 设置缓存 26 | // timeout:过期时间(分钟) 27 | set(key, value, timeout) { 28 | let data = this.source 29 | data[key] = JSON.stringify(value) 30 | if (timeout) 31 | data[`${key}__expires__`] = new Date().getTime() + 1000*60*timeout 32 | return value 33 | } 34 | 35 | remove(key) { 36 | let data = this.source, 37 | value = data[key] 38 | delete data[key] 39 | delete data[`${key}__expires__`] 40 | return value 41 | } 42 | 43 | } 44 | 45 | module.exports = storage -------------------------------------------------------------------------------- /src/utils/store.js: -------------------------------------------------------------------------------- 1 | var storage = window.localStorage,store,_api,even_storage=function(){}; 2 | 3 | function isJSON(obj){ 4 | return typeof(obj) === "object" && Object.prototype.toString.call(obj).toLowerCase() === "[object object]" && !obj.length; 5 | } 6 | function stringify (val) { 7 | return val === undefined || typeof val === "function" ? val+'' : JSON.stringify(val); 8 | } 9 | function deserialize(value){ 10 | if (typeof value !== 'string') { return undefined ;} 11 | try { return JSON.parse(value) ;} 12 | catch(e) { return value || undefined ;} 13 | } 14 | function isFunction(value) { return ({}).toString.call(value) === "[object Function]";} 15 | function isArray(value) { return value instanceof Array;} 16 | 17 | 18 | function Store(){ 19 | if(!(this instanceof Store)){ 20 | return new Store(); 21 | } 22 | } 23 | 24 | Store.prototype = { 25 | set: function(key, val){ 26 | even_storage('set',key,val); 27 | if(key&&!isJSON(key)){ 28 | storage.setItem(key, stringify(val)); 29 | }else if(key&&isJSON(key)&&!val){ 30 | for (var a in key) this.set(a, key[a]); 31 | } 32 | return this; 33 | }, 34 | get: function(key){ 35 | if(!key) { 36 | var ret = {}; 37 | this.forEach(function(key, val) { 38 | ret[key] = val; 39 | }); 40 | return ret; 41 | } 42 | return deserialize(storage.getItem(key)); 43 | }, 44 | clear: function(){ 45 | this.forEach(function(key, val) { 46 | even_storage('clear',key,val); 47 | }); 48 | storage.clear(); 49 | return this; 50 | }, 51 | remove: function(key) { 52 | var val = this.get(key); 53 | storage.removeItem(key); 54 | even_storage('remove',key,val); 55 | return val; 56 | }, 57 | has:function(key){return storage.hasOwnProperty(key);}, 58 | keys:function(){ 59 | var d=[]; 60 | this.forEach(function(k, list){ 61 | d.push(k); 62 | }); 63 | return d; 64 | }, 65 | size: function(){ return this.keys().length;}, 66 | forEach: function(callback) { 67 | for (var i=0; i-1) dt[arr[i]]=this.get(arr[i]); 77 | } 78 | return dt; 79 | }, 80 | onStorage: function(cb){ 81 | if(cb && isFunction(cb)) even_storage = cb; 82 | return this; 83 | } 84 | } 85 | 86 | store = function(key, data){ 87 | var argm = arguments,_Store = Store(),dt = null; 88 | if(argm.length ===0) return _Store.get(); 89 | if(argm.length ===1){ 90 | if(typeof(key) === "string") return _Store.get(key); 91 | if(isJSON(key)) return _Store.set(key); 92 | } 93 | if(argm.length === 2 && typeof(key) === "string"){ 94 | if(!data)return _Store.remove(key); 95 | if(data&&typeof(data) === "string")return _Store.set(key,data); 96 | if(data&&isFunction(data)) { 97 | dt = null 98 | dt = data(key,_Store.get(key)) 99 | return dt?store.set(key, dt):store; 100 | } 101 | } 102 | if(argm.length === 2 && isArray(key) && isFunction(data)){ 103 | for (var i = 0; i < key.length; i++) { 104 | dt = data(key[i],_Store.get(key[i])) 105 | store.set(key[i], dt) 106 | } 107 | return store 108 | } 109 | } 110 | for (var a in Store.prototype) store[a] = Store.prototype[a]; 111 | 112 | module.exports = store; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var HtmlWebPackPlugin = require('html-webpack-plugin'); 3 | var CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var path = require('path'); 6 | 7 | 8 | var config = { 9 | 10 | entry: { 11 | index: './src/pages/route.js' 12 | }, 13 | 14 | output: { 15 | path: __dirname + '/assets', 16 | filename: 'js/[name].js', 17 | chunkFilename: 'js/[name].[chunkhash:8].js', 18 | publicPath: '/react-redux-chat/' 19 | }, 20 | 21 | plugins: [ 22 | // new ExtractTextPlugin('[name].css', { 23 | // allChunks: true 24 | // }), 25 | new webpack.optimize.CommonsChunkPlugin({ 26 | name: 'vendors' 27 | }), 28 | new webpack.optimize.DedupePlugin(), 29 | new webpack.optimize.OccurenceOrderPlugin(), 30 | new webpack.NoErrorsPlugin(), 31 | new ExtractTextPlugin('css/[name].[chunkhash:8].css'), 32 | ], 33 | 34 | resolve: { 35 | extensions: ['', '.js', '.jsx', '.scss'], 36 | alias: { 37 | src : __dirname + '/src', 38 | } 39 | }, 40 | 41 | module: { 42 | loaders: [ 43 | { 44 | test: /\.scss$/, 45 | loader: 'style-loader!css?-minimize!autoprefixer?{browsers:["last 2 version", "> 1%", "iOS 7"]}!sass?sourceMap' 46 | }, 47 | // { test: /\.scss$/i, loader: ExtractTextPlugin.extract('style','css?sourceMap&modules&importLoaders=1&localI‌​dentName=[name]__[local]___[hash:base64:5]!sass?sourceMap') }, 48 | { 49 | test: /\.(woff|woff2|ttf|eot|svg)$/, 50 | loader: 'file-loader?name=fonts/[name].[hash:8].[ext]' 51 | }, 52 | { 53 | test: /\.(png|jpg|jpeg|gif)$/, 54 | loader: 'url-loader?limit=8192&name=images/[name].[hash:8].[ext]' 55 | } 56 | ] 57 | }, 58 | 59 | }; 60 | 61 | if (process.env.NODE_ENV === 'development') { 62 | config.devtool = 'eval-source-map'; 63 | config.module.loaders.push({ 64 | test: /\.(js|jsx)$/, 65 | exclude: /node_modules/, 66 | loader: 'babel', 67 | query: { 68 | presets: ['react', 'es2015', 'stage-0', 'react-hmre'], 69 | plugins: ['add-module-exports',"transform-runtime"] 70 | } 71 | }); 72 | // webpack-dev-server配置 73 | config.devServer= { 74 | port:8085, 75 | // host:"dev.honeybadger8.com", 76 | historyApiFallback: true, 77 | noInfo: false, 78 | publicPath: config.output.publicPath, 79 | stats: { 80 | colors: true, 81 | chunks: false 82 | } 83 | }; 84 | config.plugins.push(new webpack.DefinePlugin({ 85 | "process.env": { 86 | NODE_ENV: JSON.stringify("development") 87 | }, 88 | __DEBUG__: true 89 | })); 90 | config.plugins.push(new webpack.HotModuleReplacementPlugin()); 91 | }else{ 92 | config.module.loaders.push({ 93 | test: /\.(js|jsx)$/, 94 | exclude: /node_modules/, 95 | loader: 'babel', 96 | query: { 97 | presets: ['react', 'es2015', 'stage-0'], 98 | plugins: ['add-module-exports',"transform-runtime"] 99 | } 100 | }); 101 | config.plugins.push(new webpack.DefinePlugin({ 102 | "process.env": { 103 | NODE_ENV: JSON.stringify("production") 104 | }, 105 | __DEBUG__: false 106 | })); 107 | config.plugins.push(new webpack.optimize.UglifyJsPlugin({ 108 | compress: { 109 | warnings: false 110 | }, 111 | output: { 112 | comments: false, 113 | } 114 | })); 115 | config.plugins.push(new HtmlWebPackPlugin({ 116 | filename: path.resolve(__dirname, 'assets/index.html'), 117 | template: "index.html", 118 | inject: false 119 | })); 120 | } 121 | module.exports = config; --------------------------------------------------------------------------------