├── .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 | 
36 |
37 | ## 图片预览
38 | 
39 | 
40 |
41 | ## 还可以打赏哦~
42 |
43 | - 如果觉得此示例对你有帮助,可以打赏我一点小费哦~ ^_^ ~
44 | -
45 | 
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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&localIdentName=[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;
--------------------------------------------------------------------------------