├── .babelrc
├── .gitignore
├── README.md
├── index.html
├── package.json
├── postcss.config.js
├── show
├── cnode.png
├── denglu.png
├── fabu.png
├── gerenzhongxin.png
├── shouyeliebiao.png
├── xiangqing.png
└── xiaoxi.png
├── src
├── App.js
├── AsyncComponent.js
├── actions
│ ├── global.js
│ ├── indexlist.js
│ ├── message.js
│ ├── publish.js
│ ├── topic.js
│ └── user.js
├── app.less
├── components
│ ├── Common
│ │ ├── Index.js
│ │ └── img
│ │ │ └── loading.gif
│ ├── IndexList
│ │ ├── Header.js
│ │ ├── List.js
│ │ └── index.less
│ ├── Message
│ │ ├── List.js
│ │ └── index.less
│ ├── Topic
│ │ ├── Comment.js
│ │ ├── Header.js
│ │ └── index.less
│ └── UserView
│ │ ├── List.js
│ │ └── index.less
├── containers
│ ├── IndexList.js
│ ├── Login.js
│ ├── Message.js
│ ├── Publish.js
│ ├── Topic.js
│ └── UserView.js
├── entry.js
├── iconfont
│ ├── iconfont.css
│ ├── iconfont.eot
│ ├── iconfont.svg
│ ├── iconfont.ttf
│ └── iconfont.woff
├── reducers
│ ├── global.js
│ ├── index.js
│ ├── indexList.js
│ ├── message.js
│ ├── publish.js
│ ├── topic.js
│ └── user.js
├── template
│ └── index.html
└── utils
│ ├── cookie.js
│ └── instance.js
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | //babel配置文件,不需要做修改,因为都配置好了
2 | {
3 | "presets": [
4 | ["es2015", {"modules": false}],
5 | "react",
6 | "stage-2"
7 | ],
8 | "plugins": [
9 | "react-hot-loader/babel",
10 | ["transform-runtime", {
11 | "helpers": false,
12 | "polyfill": false,
13 | "regenerator": true,
14 | "moduleName": "babel-runtime"
15 | }],
16 | "transform-decorators-legacy",
17 | "transform-async-to-generator",
18 | "transform-do-expressions",
19 | "syntax-do-expressions"
20 | ]
21 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Numerous always-ignore extensions
2 | *.bak
3 | *.patch
4 | *.diff
5 | *.err
6 |
7 | # temp file for git conflict merging
8 | *.orig
9 | *.log
10 | *.rej
11 | *.swo
12 | *.swp
13 | *.zip
14 | *.vi
15 | *~
16 | *.sass-cache
17 | *.tmp.html
18 | *.dump
19 |
20 | # OS or Editor folders
21 | .DS_Store
22 | ._*
23 | .cache
24 | .project
25 | .settings
26 | .tmproj
27 | *.esproj
28 | *.sublime-project
29 | *.sublime-workspace
30 | nbproject
31 | thumbs.db
32 | *.iml
33 |
34 | # Folders to ignore
35 | .hg
36 | .svn
37 | .CVS
38 | .idea
39 | node_modules/
40 | jscoverage_lib/
41 | bower_components/
42 | dist/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-cnode
2 | 基于webpack2 + react + react-router4 + redux + less + ES6 的React版cnode社区
3 | ------------------------------------------------------------------
4 |
5 | 项目网站[cnode中文社区](http://cnode.byb224.top)
6 |
7 | 手机端扫描二维码
8 |
9 | 
10 |
11 | ### 下载
12 | ```
13 | git clone https://github.com/biyunbo/react-cnode.git
14 | cd react-cnode
15 | npm install yarn -g(如果没有yarn)
16 | yarn(安装依赖)
17 | ```
18 |
19 | ### 启动
20 | ```
21 | yarn start(开发环境访问:http://localhost:8888/)
22 | ```
23 | ### 模块
24 | ```
25 | 1.登录,退出
26 | 2.个人中心
27 | 3.列表,列表详情
28 | 4.发布主题
29 | 5.消息
30 | ```
31 | ### 效果图(样式是自己写的。。。。略丑哈哈哈哈)
32 | 
33 | 
34 | 
35 | 
36 | 
37 | 
38 |
39 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | react-cnode
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-cnode",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "start": "webpack-dev-server",
6 | "lint": "eslint src",
7 | "build-mac": "export NODE_ENV=production && webpack --progress --hide-modules --config webpack.config.js",
8 | "build-win": "set NODE_ENV=production&& webpack --progress --hide-modules --config webpack.config.js",
9 | "test": "jest"
10 | },
11 | "main": "index.js",
12 | "license": "MIT",
13 | "dependencies": {
14 | "axios": "^0.16.2",
15 | "babel-cli": "^6.24.1",
16 | "babel-core": "^6.25.0",
17 | "babel-loader": "^7.1.1",
18 | "babel-plugin-react-transform": "^2.0.2",
19 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
20 | "babel-plugin-transform-runtime": "^6.23.0",
21 | "babel-polyfill": "^6.23.0",
22 | "babel-preset-es2015": "^6.24.1",
23 | "babel-preset-latest": "^6.24.1",
24 | "babel-preset-react": "^6.24.1",
25 | "babel-preset-react-hmre": "^1.1.1",
26 | "babel-preset-stage-0": "^6.24.1",
27 | "babel-preset-stage-1": "^6.24.1",
28 | "babel-preset-stage-2": "^6.24.1",
29 | "babel-preset-stage-3": "^6.24.1",
30 | "babel-runtime": "^6.25.0",
31 | "bundle-loader": "^0.5.5",
32 | "compression": "^1.7.0",
33 | "css-loader": "^0.28.4",
34 | "express": "^4.15.4",
35 | "extract-text-webpack-plugin": "^3.0.0",
36 | "fastclick": "^1.0.6",
37 | "file-loader": "^0.11.2",
38 | "history": "^4.6.3",
39 | "html-webpack-plugin": "^2.30.1",
40 | "http-proxy-middleware": "^0.17.4",
41 | "image-webpack-loader": "^3.3.1",
42 | "imports-loader": "^0.7.1",
43 | "less": "^2.7.2",
44 | "less-loader": "^4.0.5",
45 | "node-sass": "^4.5.3",
46 | "postcss-loader": "^2.0.6",
47 | "prop-types": "^15.5.10",
48 | "react": "^15.6.1",
49 | "react-dom": "^15.6.1",
50 | "react-hot-loader": "3.0.0-beta.7",
51 | "react-redux": "^5.0.6",
52 | "react-router": "^4.1.2",
53 | "react-router-dom": "^4.1.2",
54 | "react-router-redux": "^4.0.8",
55 | "react-transition-group": "^2.2.0",
56 | "redux": "^3.7.2",
57 | "redux-devtools-extension": "^2.13.2",
58 | "redux-logger": "^3.0.6",
59 | "redux-promise": "^0.5.3",
60 | "redux-thunk": "^2.2.0",
61 | "sass-loader": "^6.0.6",
62 | "style-loader": "^0.18.2",
63 | "url-loader": "^0.5.9",
64 | "webpack": "^3.5.4",
65 | "webpack-dev-server": "^2.7.1"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer')({
4 | browsers: ["last 2 versions"]
5 | })
6 | ]
7 | }
--------------------------------------------------------------------------------
/show/cnode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biyunbo/react-cnode/4683983df4fbaa037858595f8d88a10a4e2e1283/show/cnode.png
--------------------------------------------------------------------------------
/show/denglu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biyunbo/react-cnode/4683983df4fbaa037858595f8d88a10a4e2e1283/show/denglu.png
--------------------------------------------------------------------------------
/show/fabu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biyunbo/react-cnode/4683983df4fbaa037858595f8d88a10a4e2e1283/show/fabu.png
--------------------------------------------------------------------------------
/show/gerenzhongxin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biyunbo/react-cnode/4683983df4fbaa037858595f8d88a10a4e2e1283/show/gerenzhongxin.png
--------------------------------------------------------------------------------
/show/shouyeliebiao.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biyunbo/react-cnode/4683983df4fbaa037858595f8d88a10a4e2e1283/show/shouyeliebiao.png
--------------------------------------------------------------------------------
/show/xiangqing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biyunbo/react-cnode/4683983df4fbaa037858595f8d88a10a4e2e1283/show/xiangqing.png
--------------------------------------------------------------------------------
/show/xiaoxi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biyunbo/react-cnode/4683983df4fbaa037858595f8d88a10a4e2e1283/show/xiaoxi.png
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { bindActionCreators } from 'redux';
4 | import {NavLink, Route, HashRouter as Router } from 'react-router-dom';
5 | import createHistory from 'history/createHashHistory';
6 | const history = createHistory()
7 |
8 | /*
9 | 全局导入less
10 | */
11 | import './iconfont/iconfont.css'
12 | import './app.less'
13 | import './components/IndexList/index.less'
14 | import './components/Topic/index.less'
15 | import './components/UserView/index.less'
16 | import './components/Message/index.less'
17 |
18 |
19 | import * as indexList from 'actions/indexList';
20 | import * as global from 'actions/global';
21 | import { asyncComponent } from './AsyncComponent';
22 |
23 | import IndexList from 'containers/IndexList';
24 | import {Footer} from 'components/Common/Index';
25 |
26 |
27 | const Topic = asyncComponent(() => import(/* webpackChunkName: "Topic" */ "./containers/Topic"))
28 | const UserView = asyncComponent(() => import(/* webpackChunkName: "Topic" */ "./containers/UserView"))
29 | const Publish = asyncComponent(() => import(/* webpackChunkName: "Topic" */ "./containers/Publish"))
30 | const Message = asyncComponent(() => import(/* webpackChunkName: "Topic" */ "./containers/Message"))
31 | const Login = asyncComponent(() => import(/* webpackChunkName: "Topic" */ "./containers/Login"))
32 | @connect (
33 | state => state,
34 | dispatch => bindActionCreators({...global}, dispatch)
35 | )
36 | export default class App extends React.Component {
37 | render() {
38 | return (
39 |
40 | {
41 | return(
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | )
52 | }}/>
53 |
54 |
55 |
56 | );
57 | }
58 | }
--------------------------------------------------------------------------------
/src/AsyncComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | export const asyncComponent = loadComponent => (
3 | class AsyncComponent extends React.Component {
4 | state = {
5 | Component: null,
6 | }
7 |
8 | componentWillMount() {
9 | if (this.hasLoadedComponent()) {
10 | return;
11 | }
12 |
13 | loadComponent()
14 | .then(module => module.default)
15 | .then((Component) => {
16 | this.setState({ Component });
17 | })
18 | .catch((err) => {
19 | console.error(`Cannot load component in `);
20 | throw err;
21 | });
22 | }
23 |
24 | hasLoadedComponent() {
25 | return this.state.Component !== null;
26 | }
27 |
28 | render() {
29 | const { Component } = this.state;
30 | return (Component) ? : null;
31 | }
32 | }
33 | );
--------------------------------------------------------------------------------
/src/actions/global.js:
--------------------------------------------------------------------------------
1 | import instance from 'utils/instance';
2 | import { saveData } from 'utils/cookie'
3 |
4 | export const successLogin = (accesstoken, loginname, id ) => ({
5 | type: 'SUCCESS_LOGIN',
6 | accesstoken,
7 | loginname,
8 | id
9 | })
10 |
11 | export const postAccessToken = (access_token) => async (dispatch, getState) =>{
12 | try {
13 | let response = await instance.post(`accesstoken/?accesstoken=${access_token}`)
14 | await response.data.success ? dispatch(successLogin(access_token ,response.data.loginname ,response.data.id)):dispatch(failLogin(response.data.error_msg))
15 | saveData("access_token",access_token,10)
16 | } catch(error) {
17 | console.log('error: ', error)
18 | }
19 | }
20 |
21 | export const failLogin = (error_msg) => ({
22 | type: 'FAIL_LOGIN',
23 | error_msg
24 | })
25 |
26 | export const loginOut = () => ({
27 | type: 'LOG_OUT'
28 | })
29 |
--------------------------------------------------------------------------------
/src/actions/indexlist.js:
--------------------------------------------------------------------------------
1 | import instance from 'utils/instance';
2 |
3 | export const selectTab = (tab) => ({
4 | type: 'SELECT_TAB',
5 | tab
6 | })
7 |
8 | export const receiveTopic = (tab, topics, page, limit) =>({
9 | type: 'RECEIVE_TOPICS',
10 | tab,
11 | topics,
12 | page,
13 | limit
14 | })
15 |
16 | export const requestTopic = (tab) => ({
17 | type: 'REQUEST_TOPICS',
18 | tab
19 | })
20 |
21 | export const recordScrollT = (scrollT) =>({
22 | type: 'RECORD_SCROLLT',
23 | scrollT
24 | })
25 |
26 | export const getList = (tab, page = 1, limit = 10) => async (dispatch, getState) => {
27 | dispatch(requestTopic(tab))
28 | //dispatch(recordScrollT())
29 | try {
30 | let response = await instance.get(`/topics?tab=${tab}&page=${page}&limit=${limit}`)
31 | await dispatch(receiveTopic(tab, response.data, page, limit))
32 | } catch (error) {
33 | console.log('error: ', error)
34 | }
35 | }
--------------------------------------------------------------------------------
/src/actions/message.js:
--------------------------------------------------------------------------------
1 | import instance from 'utils/instance';
2 |
3 | //未读消息个数
4 | export const messageNum = (num) => ({
5 | type : 'MESSAGE_NUM',
6 | num
7 | })
8 | //标记全部已读
9 | export const postMessage = (accesstoken) => async (dispatch,getState) =>{
10 | try {
11 | let data = {"accesstoken":accesstoken}
12 | let response = await instance.post(`message/mark_all`,data)
13 | } catch(error) {
14 | console.log('error: ', error)
15 | }
16 | }
17 | //获取未读消息数
18 | export const getMessagecount = (accesstoken) => async (dispatch,getState) =>{
19 | try {
20 | let response = await instance.get(`message/count/?accesstoken=${accesstoken}`)
21 | await dispatch(messageNum(response.data.data))
22 | console.log(response.data.data)
23 | } catch(error) {
24 | console.log('error: ', error)
25 | }
26 | }
27 |
28 | //获取已读和未读消息
29 | export const messageCenter = (data) => ({
30 | type : 'MESSAGE_CENTER',
31 | data
32 | })
33 |
34 | export const setTabm = (tab) => ({
35 | type : 'SET_TABM',
36 | tab
37 | })
38 |
39 | export const getMessage = (accesstoken) => async (dispatch,getState) =>{
40 | try {
41 | let response = await instance.get(`messages/?accesstoken=${accesstoken}`)
42 | await dispatch(messageCenter(response.data))
43 | } catch(error) {
44 | console.log('error: ', error)
45 | }
46 | }
--------------------------------------------------------------------------------
/src/actions/publish.js:
--------------------------------------------------------------------------------
1 | import instance from 'utils/instance';
2 |
3 | export const receivePublishTopic = (success, topic_id) => ({
4 | type: 'RECEIVE_PUBLISHTOPIC',
5 | success,
6 | topic_id
7 | })
8 |
9 | export const postPublishTopics = (accesstoken ,tab , title,content ) => async (dispatch ,getState) =>{
10 | try {
11 | let data = {"accesstoken":accesstoken,"tab":tab,"content":content,"title":title}
12 | let response = await instance.post(`/topics/`,data)
13 | await response.data.success && dispatch(receivePublishTopic(response.data.success, response.data.topic_id))
14 | console.log(response)
15 | } catch(error) {
16 | console.log('error: ', error)
17 | }
18 | }
19 |
20 | export const publishTab = (tab) =>({
21 | type : "PUBLISH_TAB",
22 | tab,
23 | })
24 |
25 | export const publishTitle = (title) =>({
26 | type : "PUBLISH_TITLE",
27 | title
28 | })
29 |
30 | export const publishContent = (content) =>({
31 | type : "PUBLISH_CONTENT",
32 | content
33 | })
--------------------------------------------------------------------------------
/src/actions/topic.js:
--------------------------------------------------------------------------------
1 | import instance from 'utils/instance';
2 |
3 | export const requestArticle = (id) => ({
4 | type: 'REQUEST_ARTICLE',
5 | id
6 | })
7 |
8 | export const getArticle = (id) => async (dispatch, getState) => {
9 | dispatch(requestArticle(id))
10 | try {
11 | let response = await instance.get(`/topic/${id}`)
12 | await dispatch(receiveArticle(response.data,id))
13 | } catch (error) {
14 | console.log('error: ', error)
15 | }
16 | }
17 |
18 | export const receiveArticle = (data,id) => ({
19 | type: 'RECEIVE_ARTICLE',
20 | data,
21 | id
22 | })
23 |
--------------------------------------------------------------------------------
/src/actions/user.js:
--------------------------------------------------------------------------------
1 | import instance from 'utils/instance';
2 |
3 | export const userCenter = (data) => ({
4 | type : 'USER_CENTER',
5 | data
6 | })
7 |
8 | export const setTab = (tab) => ({
9 | type : 'SET_TAB',
10 | tab
11 | })
12 |
13 | export const getUser = (id) => async (dispatch, getState) =>{
14 | try {
15 | let response = await instance.get(`user/${id}`)
16 | await dispatch(userCenter(response.data))
17 | } catch(error) {
18 | console.log('error: ', error)
19 | }
20 | }
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/app.less:
--------------------------------------------------------------------------------
1 | /*全局css样式*/
2 | body, ul, li, a, p, h2, h3, textarea, button, input, select {
3 | padding: 0;
4 | margin: 0;
5 | list-style: none;
6 | text-decoration: none;
7 | border: none;
8 | font-family: "微软雅黑";
9 | &:focus {
10 | outline: none;
11 | }
12 | }
13 | h2{
14 | background-color: #eee;
15 | margin: 0;
16 | padding: 10px;
17 | font-size: 16px;
18 | }
19 | body,html{
20 | width: 100%;
21 | height: 100%;
22 | }
23 | img {
24 | max-width: 100%;
25 | max-height: 100%;
26 | }
27 | .box{
28 | display: flex;
29 | flex-direction:column;
30 | flex-wrap:nowrap;
31 | width: 100%;
32 | height: 100%;
33 | }
34 | #root{
35 | width: 100%;
36 | height: 100%;
37 | }
38 | .main{
39 | display: flex;
40 | flex-direction:column;
41 | flex:1;
42 | overflow: auto;
43 | }
44 | /*底部导航*/
45 | .footer-nav{
46 | width:100%;
47 | display: flex;
48 | flex-direction:row;
49 | justify-content:space-around;
50 | text-align: center;
51 | border-top: 1px solid #ccc;
52 | a{
53 | color: #555;
54 | }
55 | .active{
56 | color:#00C5CD;
57 | }
58 | }
59 | /*Loading正在加载*/
60 | .loading{
61 | text-align: center;
62 | img{
63 | width: 50px;
64 | height: 50px;
65 | }
66 | }
67 |
68 | /*列表页*/
69 | .list-box{
70 | overflow: auto;
71 | flex:1;
72 | }
73 |
74 | .main-z{
75 | overflow: auto;
76 | flex:1;
77 | }
78 |
79 | /*登录页面*/
80 | .denglu{
81 | input{
82 | border:1px solid #ccc;
83 | padding: 0 30px;
84 | width: 240px;
85 | height: 35px;
86 | display: block;
87 | margin: 0 auto;
88 | margin-top: 200px;
89 | text-align: center;
90 | color: #ccc;
91 | font-size: 18px;
92 | border-radius: 5px;
93 | }
94 | .login{
95 | width: 130px;
96 | height: 40px;
97 | margin: 0 auto;
98 | margin-top: 20px;
99 | border-radius: 10px;
100 | text-align: center;
101 | line-height: 40px;
102 | background: #00C5CD;
103 | color: #fff;
104 | }
105 | }
106 |
107 | /*未登录状态*/
108 | .nologin{
109 | text-align: center;
110 | margin-top: 30px;
111 | a{
112 | color: #00C5CD;
113 | }
114 | }
115 |
116 | /*个人中心*/
117 | .usercenter{
118 | .tou{
119 | text-align: center;
120 | padding-top: 20px;
121 | img{
122 | width: 100px;
123 | height: 100px;
124 | border-radius: 50%;
125 | }
126 | }
127 | .name{
128 | text-align: center;
129 | }
130 | .personal{
131 | display: flex;
132 | flex-direction:row;
133 | justify-content:center;
134 | font-size: 12px;
135 | .left{
136 | margin-right: 10px;
137 | }
138 | .right{
139 | margin-left: 10px;
140 | }
141 | }
142 | .content{
143 | display: flex;
144 | flex-direction:row;
145 | justify-content:center;
146 | font-size: 16px;
147 | text-align: center;
148 | margin-top: 30px;
149 | height: 40px;
150 | background: #efefef;
151 | line-height: 38px;
152 | .left{
153 | width: 50%;
154 | height: 38px;
155 | border-bottom: 2px solid #efefef;
156 | }
157 | .right{
158 | width: 50%;
159 | height: 38px;
160 | border-bottom: 2px solid #efefef;
161 | }
162 | .on{
163 | border-bottom: 2px solid #00C5CD;
164 | }
165 |
166 | }
167 | .out{
168 | width: 50px;
169 | height: 50px;
170 | background: #00C5CD;
171 | position: fixed;
172 | bottom: 75px;
173 | right: 30px;
174 | text-align: center;
175 | border-radius: 15px;
176 | line-height: 50px;
177 | }
178 | }
179 |
180 | /*消息*/
181 | .message{
182 | .top{
183 | display: flex;
184 | flex-direction:row;
185 | justify-content:center;
186 | font-size: 16px;
187 | text-align: center;
188 | height: 40px;
189 | background: #efefef;
190 | line-height: 38px;
191 | .left{
192 | width: 50%;
193 | height: 38px;
194 | border-bottom: 2px solid #efefef;
195 | }
196 | .right{
197 | width: 50%;
198 | height: 38px;
199 | border-bottom: 2px solid #efefef;
200 | position: relative;
201 | span{
202 | background: red;
203 | width: 20px;
204 | height: 20px;
205 | display: block;
206 | border-radius: 50%;
207 | line-height: 20px;
208 | color: #fff;
209 | right: 30px;
210 | top: 8px;
211 | position: absolute;
212 | }
213 | }
214 | .on{
215 | border-bottom: 2px solid #00C5CD;
216 | }
217 | }
218 | }
219 |
220 | /*发布*/
221 | .publish{
222 | select{
223 | display: block;
224 | width: 300px;
225 | height: 30px;
226 | margin: 0 auto;
227 | border-radius: 10px;
228 | border:1px solid #00C5CD;
229 | padding: 0 20px;
230 | }
231 | input{
232 | display: block;
233 | width: 260px;
234 | height: 30px;
235 | margin: 0 auto;
236 | border-radius: 10px;
237 | border:1px solid #00C5CD;
238 | padding: 0 20px;
239 | }
240 | textarea{
241 | display: block;
242 | border-radius: 10px;
243 | width: 260px;
244 | height: 260px;
245 | border:1px solid #00C5CD;
246 | padding: 20px 20px;
247 | margin-top: 30px;
248 | margin: 0 auto;
249 | }
250 | .btn{
251 | width: 300px;
252 | height: 30px;
253 | border-radius: 10px;
254 | text-align: center;
255 | line-height: 30px;
256 | background: #00C5CD;
257 | margin: 0 auto;
258 | margin-top: 30px;
259 | color: #fff;
260 | }
261 | p{
262 | display: block;
263 | width: 300px;
264 | margin: 0 auto;
265 | font-size: 16px;
266 | margin-top: 10px;
267 | }
268 | }
--------------------------------------------------------------------------------
/src/components/Common/Index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink,Link } from 'react-router-dom';
3 |
4 |
5 | let loading = require('./img/loading.gif');
6 |
7 | export class Footer extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | //构造函数用法
11 | //常用来绑定自定义函数,切记不要在这里或者组件的任何位置setState,state全部在reducer初始化,相信对开发的后期很有帮助
12 | //例子:this.myfunction = this.myfunction.bind(this)
13 | }
14 | render() {
15 | return(
16 |
34 | )
35 | }
36 | }
37 | /*正在*/
38 | export class Loading extends React.Component {
39 | constructor(props) {
40 | super(props);
41 | }
42 | render() {
43 | return(
44 |
45 |

46 |
47 | )
48 | }
49 | }
50 | /*公共头部*/
51 | export class Header extends React.Component {
52 | constructor(props) {
53 | super(props);
54 | this.handleClick = this.handleClick.bind(this)
55 | }
56 |
57 | componentWillMount() {
58 | //组件挂载前触发此函数
59 | }
60 | componentWillReceiveProps(newProps) {
61 | //props改变触发此函数
62 | }
63 | handleClick() {
64 | //该函数用来执行组件内部的事件,比如在这里就是nav组件菜单的导航点击事件
65 | // this.props.history.push('/')
66 | history.go(-1);
67 | }
68 | render() {
69 | let { title,leftto } = this.props;
70 | let left = null;
71 | if(leftto == "kong"){
72 | left = ()
73 | }else if(leftto == "fanhui"){
74 | left = (
75 |
76 |
77 |
78 | )
79 | }
80 | return(
81 |
82 | {left}
83 |
{title}
84 |
85 | )
86 | }
87 | }
88 |
89 | /*未登录状态*/
90 | export class Nologin extends React.Component {
91 | constructor(props) {
92 | super(props);
93 | }
94 | render() {
95 | return(
96 |
97 | 您还未登录,去登录~
98 |
99 | )
100 | }
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/src/components/Common/img/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biyunbo/react-cnode/4683983df4fbaa037858595f8d88a10a4e2e1283/src/components/Common/img/loading.gif
--------------------------------------------------------------------------------
/src/components/IndexList/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import { Link } from 'react-router-dom';
5 | import PropTypes from 'prop-types';
6 | import queryString from 'query-string';
7 | //action
8 | import * as indexList from 'actions/indexList';
9 |
10 | export default class Header extends React.Component {
11 | constructor(props) {
12 | super(props);
13 | //构造函数用法
14 | //常用来绑定自定义函数,切记不要在这里或者组件的任何位置setState,state全部在reducer初始化,相信对开发的后期很有帮助
15 | //例子:this.myfunction = this.myfunction.bind(this)
16 | this.handleClick = this.handleClick.bind(this)
17 | }
18 |
19 | componentWillMount() {
20 | //组件挂载前触发此函数
21 | }
22 | componentWillReceiveProps(newProps) {
23 | //props改变触发此函数
24 | }
25 | handleClick() {
26 | //该函数用来执行组件内部的事件,比如在这里就是nav组件菜单的导航点击事件
27 | // this.props.history.push('/')
28 | }
29 | render() {
30 | let tab = this.props.indexList.selectedTab;
31 | return(
32 |
51 | )
52 | }
53 | }
--------------------------------------------------------------------------------
/src/components/IndexList/List.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import { Link } from 'react-router-dom';
5 | import PropTypes from 'prop-types';
6 | import queryString from 'query-string';
7 | //action
8 | import * as indexList from 'actions/indexList';
9 |
10 | import {Loading} from 'components/Common/Index';
11 |
12 | export default class List extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | }
16 |
17 | componentWillMount() {
18 | //组件挂载前触发此函数
19 | }
20 | componentWillReceiveProps(newProps) {
21 | //props改变触发此函数
22 | }
23 | render() {
24 | let {topics} = this.props.indexList.tabData;
25 | return(
26 |
27 | {
28 | topics.data.map((ele, index) => {
29 | return (
30 |
31 | )
32 | })
33 | }
34 |
35 |
36 | )
37 | }
38 | }
39 |
40 | class Li extends React.Component {
41 | constructor(props) {
42 | super(props);
43 | }
44 | render() {
45 | let { id, title, author, visit_count, reply_count, create_at, last_reply_at, good, top } = this.props
46 | return(
47 |
48 |
49 |

50 |
51 |
52 |
53 |
54 | {
55 | top && 顶
56 | }
57 | {
58 | top && 精
59 | }
60 | {title}
61 |
62 |
63 |
64 |
{reply_count}/{visit_count}分享
65 |
{create_at.substring(0,10)}
66 |
67 |
68 |
69 | )
70 | }
71 | }
--------------------------------------------------------------------------------
/src/components/IndexList/index.less:
--------------------------------------------------------------------------------
1 | /*tab导航 Header*/
2 | .tab-nav{
3 | width:100%;
4 | height:30px;
5 | flex-basis: 30px;
6 | ul{
7 | width:100%;
8 | display: flex;
9 | flex-direction:row;
10 | justify-content:space-around;
11 | text-align: center;
12 | background:#fff;
13 | height:30px;
14 | li{
15 | padding:5px;
16 | border-bottom:3px solid #fff;
17 | a{
18 | color:#555;
19 | }
20 | }
21 | .no{
22 | border-bottom:3px solid #00C5CD;
23 | }
24 | }
25 | }
26 | /*列表 List*/
27 | .list{
28 | padding:10px;
29 | display: flex;
30 | flex-direction:row;
31 | .img{
32 | width:50px;
33 | height:50px;
34 | }
35 | .text{
36 | height: 50px;
37 | padding-left: 5px;
38 | flex: 1;
39 | .li1{
40 | overflow:hidden;
41 | width: 100%;
42 | height: 25px;
43 | line-height: 25px;
44 | text-overflow:ellipsis;
45 | color:#555555;
46 | display: flex;
47 | flex-direction:row;
48 | justify-content:space-between;
49 | .ding{
50 | font-weight: 600;
51 | color: red;
52 | margin:0 5px;
53 | }
54 | .jing{
55 | font-weight: 600;
56 | color: #436EEE;
57 | margin:0 5px;
58 | }
59 | }
60 | .li2{
61 | color:#999;
62 | width: 100%;
63 | height: 25px;
64 | line-height: 25px;
65 | display: flex;
66 | flex-direction:row;
67 | justify-content:space-between;
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/src/components/Message/List.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import { Link } from 'react-router-dom';
5 | import PropTypes from 'prop-types';
6 | import queryString from 'query-string';
7 |
8 | import { formatDate } from 'utils/cookie';
9 |
10 | export default class List extends React.Component {
11 | constructor(props) {
12 | super(props);
13 | }
14 | render() {
15 | let {list} = this.props;
16 | console.log(list)
17 | return(
18 |
19 | {
20 | list.length == 0 &&
暂无数据~
21 | }
22 | {
23 | list.map((ele, index) => {
24 | return (
25 |
26 | )
27 | })
28 | }
29 |
30 | )
31 | }
32 | }
33 |
34 | class Li extends React.Component {
35 | constructor(props) {
36 | super(props);
37 | }
38 | render() {
39 | let {avatar_url , loginname} = this.props.author;
40 | let {content , create_at} = this.props.reply;
41 | let {id , title} = this.props.topic;
42 | return(
43 |
44 |
45 |

46 |
{formatDate(create_at)}
47 |
48 |
49 |
{loginname}
50 |
51 |
{title}
52 |
53 |
54 |
55 | )
56 | }
57 | }
--------------------------------------------------------------------------------
/src/components/Message/index.less:
--------------------------------------------------------------------------------
1 | .xiaoxi{
2 | .li{
3 | border-bottom: 1px solid #ccc;
4 | padding: 10px;
5 | .p1{
6 | display: flex;
7 | flex-direction:row;
8 | justify-content:space-between;
9 | img{
10 | width: 30px;
11 | height: 30px;
12 | }
13 | .name{
14 | font-size: 16px;
15 | color: #555;
16 | }
17 | .time{
18 | font-size: 14px;
19 | color: #ccc;
20 | line-height: 30px;
21 | }
22 | .title{
23 | font-size: 16px;
24 | color: #00C5CD;
25 | }
26 | }
27 | .content{
28 | padding: 5px;
29 | }
30 | }
31 | .zanwu{
32 | text-align: center;
33 | line-height: 40px;
34 | color: #00C5CD;
35 | }
36 | }
--------------------------------------------------------------------------------
/src/components/Topic/Comment.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import { Link } from 'react-router-dom';
5 | import PropTypes from 'prop-types';
6 | import queryString from 'query-string';
7 |
8 | import { formatDate } from 'utils/cookie';
9 |
10 |
11 | export default class Comment extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | //构造函数用法
15 | //常用来绑定自定义函数,切记不要在这里或者组件的任何位置setState,state全部在reducer初始化,相信对开发的后期很有帮助
16 | //例子:this.myfunction = this.myfunction.bind(this)
17 | this.handleClick = this.handleClick.bind(this)
18 | }
19 |
20 | componentWillMount() {
21 | //组件挂载前触发此函数
22 | }
23 | componentWillReceiveProps(newProps) {
24 | //props改变触发此函数
25 | }
26 | handleClick() {
27 | //该函数用来执行组件内部的事件
28 | }
29 | render() {
30 | let {reply_count,replies} = this.props
31 | return(
32 |
33 |
共{reply_count}条评论
34 | {
35 | !isEmpty(replies) && replies.map((ele,index) => {
36 | return (
37 |
38 |
39 |
40 |

41 |
{ele.author.loginname}
42 |
43 |
44 | {index+1}楼
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | {formatDate(ele.create_at)}
54 |
55 |
56 |
57 | {ele.ups.length}
58 |
59 |
60 |
61 |
62 |
63 | )
64 | })
65 | }
66 |
67 | )
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/Topic/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import { Link } from 'react-router-dom';
5 | import PropTypes from 'prop-types';
6 | import queryString from 'query-string';
7 | //action
8 | import * as indexList from 'actions/indexList';
9 |
10 |
11 | export default class Header extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | //构造函数用法
15 | //常用来绑定自定义函数,切记不要在这里或者组件的任何位置setState,state全部在reducer初始化,相信对开发的后期很有帮助
16 | //例子:this.myfunction = this.myfunction.bind(this)
17 | this.handleClick = this.handleClick.bind(this)
18 | }
19 |
20 | componentWillMount() {
21 | //组件挂载前触发此函数
22 | }
23 | componentWillReceiveProps(newProps) {
24 | //props改变触发此函数
25 | }
26 | handleClick() {
27 | //该函数用来执行组件内部的事件,比如在这里就是nav组件菜单的导航点击事件
28 | // this.props.history.push('/')
29 | history.go(-1);
30 | }
31 | render() {
32 | return(
33 |
34 |
35 |
36 |
37 |
详情
38 |
39 | )
40 | }
41 | }
--------------------------------------------------------------------------------
/src/components/Topic/index.less:
--------------------------------------------------------------------------------
1 | @style-border:#ccc;
2 | @style-bg:#00C5CD;
3 | @name-color:#4aa84a;
4 | /*头部*/
5 | .top{
6 | width: 100%;
7 | display: flex;
8 | flex-direction:row;
9 | justify-content:space-between;
10 | height: 40px;
11 | line-height: 40px;
12 | background: @style-bg;
13 | .fanhui{
14 | padding-left: 10px;
15 | i{
16 | font-size: 16px;
17 | font-weight: 600px;
18 | color:#fff;
19 | }
20 | }
21 | .title{
22 | flex:1;
23 | text-align: center;
24 | color: #fff;
25 | }
26 | }
27 |
28 | /*文章头部*/
29 | .main-top{
30 | .top1{
31 | padding:5px 20px;
32 | display: flex;
33 | flex-direction:row;
34 | justify-content:space-between;
35 | border-bottom: 1px solid @style-border;
36 | color: #555;
37 | height: 25px;
38 | line-height: 25px;
39 | .left{
40 | span{
41 | color: @name-color;
42 | }
43 | }
44 | .right{
45 | color:#888888;
46 | }
47 | }
48 | .top2{
49 | padding:5px 20px;
50 | border-bottom: 1px solid @style-border;
51 | .p1{
52 | font-size: 20px;
53 | line-height: 30px;
54 | }
55 | .p2{
56 | padding:5px 0;
57 | display: flex;
58 | flex-direction:row;
59 | justify-content:space-between;
60 | .left{
61 | color: #d0d0d0;
62 | }
63 | .right{
64 | width: 120px;
65 | height: 30px;
66 | background: @style-bg;
67 | border-radius: 5px;
68 | text-align: center;
69 | line-height: 30px;
70 | color: #fff;
71 | }
72 | }
73 | }
74 | }
75 |
76 | /*文章主题*/
77 | .markdown-body{
78 | padding:5px 20px;
79 | a{
80 | display: block;
81 | word-wrap:break-word;
82 | width: 100%;
83 | color:#4078c0;
84 | &:hover{
85 | color:#4078c0;
86 | }
87 | }
88 |
89 | p{
90 | width: 100%;
91 | }
92 | img{
93 | margin: auto;
94 | width:100%;
95 | }
96 |
97 | li{
98 | list-style:none;
99 | }
100 | code{
101 | white-space: normal;
102 | word-break: break-all;
103 | }
104 | }
105 |
106 | /*评论*/
107 | .comment{
108 | .comment-top{
109 | height: 40px;
110 | background: #eee;
111 | border-left: 15px solid @style-bg;
112 | line-height: 40px;
113 | }
114 | .li{
115 | margin-top: 10px;
116 | border:1px solid @style-border;
117 | .list1{
118 | padding:10px 15px;
119 | height: 50px;
120 | display: flex;
121 | flex-direction:row;
122 | justify-content:space-between;
123 | line-height: 50px;
124 | .left{
125 | display: flex;
126 | flex-direction:row;
127 | img{
128 | width: 50px;
129 | height: 50px;
130 | border-radius: 50%;
131 | }
132 | span{
133 | color:@name-color;
134 | padding-left: 10px;
135 | }
136 | }
137 | .right{
138 | color:@style-border;
139 | }
140 | }
141 | .list2{
142 | border-top: 1px solid @style-border;
143 | padding:20px 15px;
144 | .p1{
145 | color: #555555;
146 | font-size: 14px;
147 | }
148 | .p2{
149 | margin-top: 20px;
150 | display: flex;
151 | flex-direction:row;
152 | justify-content:space-between;
153 | height: 30px;
154 | line-height: 30px;
155 | .right{
156 | i{
157 | margin: 0 10px;
158 | font-size: 30px;
159 | }
160 | }
161 | }
162 | }
163 | }
164 | }
--------------------------------------------------------------------------------
/src/components/UserView/List.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import { Link } from 'react-router-dom';
5 | import PropTypes from 'prop-types';
6 | import queryString from 'query-string';
7 |
8 | import { formatDate } from 'utils/cookie';
9 |
10 | export default class List extends React.Component {
11 | constructor(props) {
12 | super(props);
13 | }
14 | render() {
15 | let {list} = this.props;
16 | return(
17 |
18 | {
19 | list.length == 0 &&
暂无数据~
20 | }
21 | {
22 | list.map((ele, index) => {
23 | return (
24 |
25 | )
26 | })
27 | }
28 |
29 | )
30 | }
31 | }
32 |
33 | class Li extends React.Component {
34 | constructor(props) {
35 | super(props);
36 | }
37 | render() {
38 | let {title,last_reply_at,id} = this.props;
39 | return(
40 |
41 |
42 |
{title}
43 |
{formatDate(last_reply_at)}
44 |
45 |
46 | )
47 | }
48 | }
--------------------------------------------------------------------------------
/src/components/UserView/index.less:
--------------------------------------------------------------------------------
1 | .xinxi{
2 | .li{
3 | height: 40px;
4 | border-bottom: 1px solid #ccc;
5 | line-height: 40px;
6 | display: flex;
7 | flex-direction:row;
8 | justify-content:space-around;
9 | .title{
10 | width: 75%;
11 | display:block;
12 | white-space:nowrap;
13 | overflow:hidden;
14 | text-overflow:ellipsis;
15 | color: #555;
16 | }
17 | .time{
18 | width: 18%;
19 | font-size: 14px;
20 | color:#ccc;
21 | }
22 | }
23 | .zanwu{
24 | text-align: center;
25 | line-height: 40px;
26 | color: #00C5CD;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/containers/IndexList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { bindActionCreators } from 'redux';
4 | import { connect } from 'react-redux';
5 | import PropTypes from 'prop-types';
6 | import queryString from 'query-string';
7 | /*actions*/
8 | import * as indexList from 'actions/indexList';
9 | import * as global from 'actions/global';
10 |
11 | /*组件*/
12 | import Header from 'components/IndexList/Header';
13 | import List from 'components/IndexList/List';
14 | import {Loading} from 'components/Common/Index';
15 |
16 | import { readData } from 'utils/cookie'
17 |
18 | @connect(
19 | state => state,
20 | dispatch => bindActionCreators({...indexList,...global}, dispatch)
21 | )
22 | export default class HomeContainer extends React.Component {
23 | constructor(props) {
24 | super(props);
25 | //构造函数用法
26 | //常用来绑定自定义函数,切记不要在这里或者组件的任何位置setState,state全部在reducer初始化,相信对开发的后期很有帮助
27 | //例子:this.myfunction = this.myfunction.bind(this)
28 | this.scroll = this.scroll.bind(this);
29 | this.loadmore = this.loadmore.bind(this);
30 | }
31 |
32 | componentWillMount () {
33 | let {selectedTab , tabData} = this.props.indexList;
34 | let { topics } = tabData;
35 | //判断是否登录
36 | if(readData("access_token")){
37 | this.props.postAccessToken(readData("access_token"));
38 | }
39 | if (topics.length === 0) {
40 | this.props.getList(selectedTab);
41 | }
42 | }
43 | componentWillReceiveProps(newProps) {
44 | //props改变触发此函数
45 | if (newProps.location.search !== this.props.location.search) {
46 | //url改变
47 | let tab = queryString.parse(this.props.history.location.search).tab || 'all';
48 | this.props.selectTab(tab);
49 | //发起请求
50 | this.props.getList(tab);
51 | }
52 | }
53 | scroll() {
54 | if(this.mylistdiv != null){
55 | //console.log(this.mylistdiv.scrollTop,this.mylistdiv.offsetHeight,this.mylist.offsetHeight)
56 | if (this.mylistdiv.scrollTop + this.mylistdiv.offsetHeight >= this.mylist.offsetHeight) {
57 | this.loadmore(this.mylistdiv.scrollTop);
58 | }
59 | }
60 |
61 | }
62 | loadmore(scrollT) {
63 | let {tabData , selectedTab} = this.props.indexList;
64 | let num = this.props.indexList.tabData.limit;
65 | num = num + 10;
66 | if (!tabData.isFecthing) {
67 | //this.props.recordScrollT(scrollT);
68 | this.props.getList(selectedTab,1,num);
69 | }
70 |
71 | }
72 | render() {
73 | let { topics } = this.props.indexList.tabData;
74 | let { tabData } = this.props.indexList
75 | return(
76 |
77 |
78 |
this.mylistdiv = ref} className="list-box" onScroll={this.scroll}>
79 |
this.mylist = ref} className="list-boxz">
80 | {
81 | topics.length === 0 &&
82 | }
83 | {
84 | !isEmpty(topics) &&
85 | }
86 |
87 |
88 |
89 | )
90 | }
91 | }
92 |
93 |
94 |
--------------------------------------------------------------------------------
/src/containers/Login.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import PropTypes from 'prop-types';
5 | import queryString from 'query-string';
6 |
7 | import { formatDate } from 'utils/cookie';
8 |
9 | /*actions*/
10 | import * as global from 'actions/global';
11 | import * as user from 'actions/user';
12 |
13 | /*组件*/
14 | import { Header } from 'components/Common/Index';
15 |
16 |
17 | @connect(
18 | state => state,
19 | dispatch => bindActionCreators({...global,...user}, dispatch)
20 | )
21 | export default class Login extends React.Component {
22 | constructor(props) {
23 | super(props);
24 | this.handleClick = this.handleClick.bind(this)
25 | }
26 | componentWillMount(){
27 |
28 | }
29 | handleClick(){
30 | let token = this.myvalue.value
31 | this.props.postAccessToken(token);
32 | }
33 | render() {
34 | return(
35 |
36 |
37 |
38 |
this.myvalue = ref} />
39 |
登录
40 |
41 |
42 | )
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/containers/Message.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import PropTypes from 'prop-types';
5 | import queryString from 'query-string';
6 |
7 | import { formatDate } from 'utils/cookie';
8 |
9 | /*actions*/
10 | import * as message from 'actions/message';
11 | /*组件*/
12 | import { Header,Nologin } from 'components/Common/Index';
13 | import List from 'components/Message/list';
14 |
15 | @connect(
16 | state => state,
17 | dispatch => bindActionCreators({...message}, dispatch)
18 | )
19 | export default class Message extends React.Component {
20 | constructor(props) {
21 | super(props);
22 | }
23 | componentWillMount(){
24 | if(this.props.global.accesstoken){
25 | this.props.getMessage(this.props.global.accesstoken)
26 | this.props.getMessagecount(this.props.global.accesstoken)
27 | }
28 | }
29 | componentWillReceiveProps(nextProps){
30 | // if(nextProps.global.success !== this.props.global.success){
31 | // this.props.getUser(nextProps.global.loginname)
32 | // }
33 | }
34 | render() {
35 | let {success} = this.props.global
36 | let {data} = this.props.message.data
37 | return(
38 |
39 |
40 | {
41 | success ? !isEmpty(data)&& :
42 | }
43 |
44 | )
45 | }
46 | }
47 |
48 | //消息主体
49 | class Main extends React.Component {
50 | constructor(props) {
51 | super(props);
52 | }
53 | render() {
54 | let {has_read_messages,hasnot_read_messages} = this.props.message.data.data
55 | let {tab ,num} = this.props.message
56 | let handleClick = this.props.handleClick
57 | let cleft = "left"
58 | let cright = "right"
59 | if(tab == 1 ){
60 | cleft+=" on"
61 | }
62 | if(tab == 2 ){
63 | cright+=" on"
64 | }
65 | console.log(num > 0)
66 | return(
67 |
68 |
69 |
{ this.props.setTabm(1) } }>
70 | 已读消息
71 |
72 |
{ this.props.setTabm(2), this.props.postMessage(this.props.global.accesstoken)} }>
73 | 未读消息{num > 0 &&{num}}
74 |
75 |
76 | {
77 | tab == 1 &&
78 | }
79 | {
80 | tab == 2 &&
81 | }
82 |
83 | )
84 | }
85 | }
--------------------------------------------------------------------------------
/src/containers/Publish.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import PropTypes from 'prop-types';
5 | import queryString from 'query-string';
6 |
7 | import { formatDate } from 'utils/cookie';
8 |
9 | /*actions*/
10 | import * as global from 'actions/global';
11 | import * as publish from 'actions/publish';
12 |
13 | /*组件*/
14 | import { Header,Nologin } from 'components/Common/Index';
15 |
16 |
17 | @connect(
18 | state => state,
19 | dispatch => bindActionCreators({...global,...publish}, dispatch)
20 | )
21 | export default class Publish extends React.Component {
22 | constructor(props) {
23 | super(props);
24 | this.tabInput = this.tabInput.bind(this)
25 | this.titleInput = this.titleInput.bind(this)
26 | this.contentInput = this.contentInput.bind(this)
27 | this.handleClick = this.handleClick.bind(this)
28 | }
29 | componentWillMount(){
30 |
31 | }
32 | componentWillReceiveProps(nextProps){
33 | if(nextProps.global.success !== this.props.global.success){
34 | this.props.getUser(nextProps.global.loginname)
35 | }
36 | if(nextProps.publish.success){
37 | this.props.history.push("/Topic/"+nextProps.publish.topic_id)
38 | this.props.publishTab("dev");
39 | this.props.publishTitle("")
40 | this.props.publishContent("")
41 | }
42 | }
43 | tabInput(e){
44 | this.props.publishTab(e.target.value)
45 | }
46 | titleInput(e){
47 | this.props.publishTitle(e.target.value)
48 | }
49 | contentInput(e){
50 | this.props.publishContent(e.target.value)
51 | }
52 | handleClick(){
53 | let {tab,title,content} = this.props.publish
54 | let {accesstoken} = this.props.global
55 | if(title.length < 10){
56 | return alert('标题字数10字以上');
57 | }else if(content.length < 20){
58 | return alert('内容字数20字以上');
59 | }else{
60 | this.props.postPublishTopics(accesstoken,tab,title,content)
61 | }
62 | }
63 | render() {
64 | let {success} = this.props.global
65 | return(
66 |
67 |
68 | {
69 | success ? :
70 | }
71 |
72 | )
73 | }
74 | }
75 |
76 | //发布主体
77 | class Main extends React.Component {
78 | constructor(props) {
79 | super(props);
80 | }
81 | render() {
82 | let {tab,title,content} = this.props.publish
83 | return(
84 |
85 |
请选择发布类型
86 |
92 |
标题
93 |
94 |
内容
95 |
96 |
发布
97 |
98 | )
99 | }
100 | }
--------------------------------------------------------------------------------
/src/containers/Topic.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import PropTypes from 'prop-types';
5 | import queryString from 'query-string';
6 |
7 | import { formatDate } from 'utils/cookie';
8 |
9 | /*actions*/
10 | import * as topic from 'actions/topic';
11 |
12 | /*组件*/
13 | import Comment from 'components/Topic/Comment';
14 | import {Loading ,Header} from 'components/Common/Index';
15 |
16 |
17 |
18 | @connect(
19 | state => state,
20 | dispatch => bindActionCreators({...topic}, dispatch)
21 | )
22 | export default class Topic extends React.Component {
23 | constructor(props) {
24 | super(props);
25 | //构造函数用法
26 | //常用来绑定自定义函数,切记不要在这里或者组件的任何位置setState,state全部在reducer初始化,相信对开发的后期很有帮助
27 | //例子:this.myfunction = this.myfunction.bind(this)
28 | this.handleClick = this.handleClick.bind(this)
29 | }
30 |
31 | componentWillMount() {
32 | let {id} = this.props.match.params
33 | let {topic , getArticle} = this.props
34 | if(!isEmpty(topic)){
35 | getArticle(id)
36 | }
37 |
38 | }
39 | componentWillReceiveProps(newProps) {
40 |
41 | }
42 | handleClick() {
43 | //该函数用来执行组件内部的事件,比如在这里就是nav组件菜单的导航点击事件
44 | // this.props.history.push('/')
45 | }
46 | render() {
47 | let {data} = this.props.topic.data;
48 | let {isFecthing} = this.props.topic;
49 | return(
50 |
51 |
52 | {
53 | isFecthing ?
: !isEmpty(data) &&
54 |
55 |
56 |
57 |
58 | 作者:{data.author.loginname}
59 |
60 |
61 | 发表于{formatDate(data.create_at)}
62 |
63 |
64 |
65 |
{data.title}
66 |
67 |
68 | 浏览次数{data.visit_count}
69 |
70 |
71 | 关注
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | }
81 |
82 | )
83 | }
84 | }
--------------------------------------------------------------------------------
/src/containers/UserView.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import PropTypes from 'prop-types';
5 | import queryString from 'query-string';
6 |
7 | import { formatDate,removeData } from 'utils/cookie';
8 |
9 | /*actions*/
10 | import * as global from 'actions/global';
11 | import * as user from 'actions/user';
12 |
13 | /*组件*/
14 | import { Header } from 'components/Common/Index';
15 | import Login from 'containers/Login';
16 | import List from 'components/Userview/list';
17 |
18 |
19 |
20 | @connect(
21 | state => state,
22 | dispatch => bindActionCreators({...global,...user}, dispatch)
23 | )
24 | export default class Userview extends React.Component {
25 | constructor(props) {
26 | super(props);
27 | this.handleClick = this.handleClick.bind(this)
28 | }
29 | componentWillMount(){
30 | let {success} = this.props.global
31 | if(success){
32 | this.props.getUser(this.props.global.loginname)
33 | }
34 | }
35 | componentWillReceiveProps(nextProps){
36 | if(nextProps.global.success !== this.props.global.success){
37 | this.props.getUser(nextProps.global.loginname)
38 | }
39 | }
40 | handleClick(){
41 | this.props.loginOut();
42 | removeData("access_token");
43 | }
44 | render() {
45 | let {success} = this.props.global
46 | let {data} = this.props.user.data
47 |
48 | return(
49 |
50 | {
51 | success ? !isEmpty(data)&& :
52 | }
53 |
54 | )
55 | }
56 | }
57 |
58 | //个人中心主体
59 | class Main extends React.Component {
60 | constructor(props) {
61 | super(props);
62 | }
63 | render() {
64 | let {avatar_url,create_at,loginname,score,recent_replies,recent_topics} = this.props.user.data.data
65 | let {tab} = this.props.user
66 | let handleClick = this.props.handleClick
67 | let cleft = "left"
68 | let cright = "right"
69 | if(tab == 1 ){
70 | cleft+=" on"
71 | }
72 | if(tab == 2 ){
73 | cright+=" on"
74 | }
75 | return(
76 |
77 |
78 |
79 |
80 |

81 |
82 |
{loginname}
83 |
84 |
85 | 积分:{score}
86 |
87 |
88 | 注册于:{formatDate(create_at)}
89 |
90 |
91 |
92 |
{ this.props.setTab(1) } }>主题
93 |
{ this.props.setTab(2) } }>回复
94 |
95 | {
96 | tab == 1 &&
97 | }
98 | {
99 | tab == 2 &&
100 | }
101 |
退出
102 |
103 |
104 | )
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/entry.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { createStore, applyMiddleware } from 'redux';
4 | import { Provider } from 'react-redux';
5 | import { composeWithDevTools } from 'redux-devtools-extension';
6 | import thunk from 'redux-thunk';
7 | import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux';
8 | import { AppContainer } from 'react-hot-loader';
9 | import App from './App';
10 | import createHistory from 'history/createBrowserHistory';
11 | import rootReducer from './reducers/index';
12 | import {createLogger} from 'redux-logger';
13 |
14 | var FastClick = require('fastclick');
15 |
16 | //按模块导入lodash,可以有效减小vendor.js的大小
17 | import isEmpty from 'lodash/isEmpty';
18 | import isEqual from 'lodash/isEqual';
19 | import debounce from 'lodash/debounce';
20 | import isArray from 'lodash/isArray';
21 |
22 | window.isEmpty = isEmpty;
23 | window.isEqual = isEqual;
24 | window.debounce = debounce;
25 | window.isArray = isArray;
26 |
27 | const history = createHistory();
28 | const middleware = routerMiddleware(history)
29 | const logger = createLogger();
30 |
31 | //解决移动端300毫秒延迟
32 | FastClick.attach(document.body);
33 | const middlewares = [thunk, middleware,logger];
34 |
35 | const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(...middlewares)));
36 | console.log(store)
37 | const render = Component =>
38 | ReactDOM.render(
39 |
40 |
41 |
42 |
43 | ,
44 | document.getElementById('root')
45 | );
46 |
47 | render(App)
48 |
49 | if(module.hot) {
50 | module.hot.accept('./App', () => { render(App) });
51 | }
--------------------------------------------------------------------------------
/src/iconfont/iconfont.css:
--------------------------------------------------------------------------------
1 |
2 | @font-face {font-family: "iconfont";
3 | src: url('iconfont.eot?t=1503471443506'); /* IE9*/
4 | src: url('iconfont.eot?t=1503471443506#iefix') format('embedded-opentype'), /* IE6-IE8 */
5 | url('iconfont.woff?t=1503471443506') format('woff'), /* chrome, firefox */
6 | url('iconfont.ttf?t=1503471443506') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
7 | url('iconfont.svg?t=1503471443506#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family:"iconfont" !important;
12 | font-size:25px;
13 | font-style:normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-duduyinleappicon1601:before { content: "\e629"; }
19 |
20 | .icon-fabu:before { content: "\e78a"; }
21 |
22 | .icon-transmit:before { content: "\e600"; }
23 |
24 | .icon-fanhui:before { content: "\e624"; }
25 |
26 | .icon-wode:before { content: "\e62f"; }
27 |
28 | .icon-shouye:before { content: "\e62e"; }
29 |
30 | .icon-xiaoxi:before { content: "\e630"; }
31 |
32 |
--------------------------------------------------------------------------------
/src/iconfont/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biyunbo/react-cnode/4683983df4fbaa037858595f8d88a10a4e2e1283/src/iconfont/iconfont.eot
--------------------------------------------------------------------------------
/src/iconfont/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
55 |
--------------------------------------------------------------------------------
/src/iconfont/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biyunbo/react-cnode/4683983df4fbaa037858595f8d88a10a4e2e1283/src/iconfont/iconfont.ttf
--------------------------------------------------------------------------------
/src/iconfont/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biyunbo/react-cnode/4683983df4fbaa037858595f8d88a10a4e2e1283/src/iconfont/iconfont.woff
--------------------------------------------------------------------------------
/src/reducers/global.js:
--------------------------------------------------------------------------------
1 | // 初始化状态
2 | let Initialization = {
3 | success : false
4 | }
5 |
6 | export function global(state = Initialization , action) {
7 | switch (action.type) {
8 | case 'SUCCESS_LOGIN':
9 | //console.log('登录成功');
10 | return Object.assign({},state,{
11 | success : true,
12 | loginname : action.loginname,
13 | id : action.id,
14 | accesstoken : action.accesstoken
15 | })
16 | case 'FAIL_LOGIN':
17 | //console.log('登录失败');
18 | return Object.assign({},state,{
19 | success : false,
20 | failmessage : action.error_msg
21 | })
22 | case 'LOG_OUT':
23 | return Object.assign({},state,{
24 | success : false
25 | })
26 | default:
27 | return state
28 | }
29 | }
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { routerReducer } from 'react-router-redux'
3 |
4 | import { indexList } from './indexList';
5 | import { topic } from './topic';
6 | import { global } from './global';
7 | import { user } from './user';
8 | import { message } from './message';
9 | import { publish } from './publish';
10 |
11 |
12 |
13 | //注册reducer,每个自定义的reducer都要来这里注册!!!不注册会报错。
14 | const rootReducer = combineReducers({
15 | routing: routerReducer,
16 | /* your reducers */
17 | global,
18 | indexList,
19 | topic,
20 | user,
21 | message,
22 | publish
23 | });
24 |
25 | export default rootReducer;
26 |
--------------------------------------------------------------------------------
/src/reducers/indexList.js:
--------------------------------------------------------------------------------
1 | // 初始化状态
2 | let Initialization = {
3 | selectedTab: 'all',
4 | tabData: {
5 | page: 0,
6 | limit: 0,
7 | scrollT: 0,
8 | topics: [],
9 | isFecthing: false
10 | }
11 | }
12 |
13 | export function indexList(state = Initialization, action) {
14 | switch (action.type) {
15 | case 'SELECT_TAB':
16 | return Object.assign({},state,{
17 | selectedTab : action.tab,
18 | tabData : {
19 | ...state.tabData,
20 | topics: []
21 | }
22 | })
23 | case 'RECORD_SCROLLT':
24 | return Object.assign({},state,{
25 | tabData : {
26 | ...state.tabData,
27 | scrollT : action.scrollT
28 | }
29 | })
30 | case 'REQUEST_TOPICS':
31 | //请求开始
32 | return Object.assign({},state,{
33 | tabData : {
34 | ...state.tabData,
35 | isFecthing : true
36 | }
37 | })
38 | case 'RECEIVE_TOPICS':
39 | return Object.assign({},state,{
40 | tabData:{
41 | page : action.page,
42 | topics : action.topics,
43 | limit : action.limit,
44 | isFecthing : false
45 | }
46 | })
47 | default:
48 | return state
49 | }
50 | }
--------------------------------------------------------------------------------
/src/reducers/message.js:
--------------------------------------------------------------------------------
1 | let Initialization = {
2 | data : [],
3 | num : 0,
4 | tab : 1
5 | }
6 |
7 | export function message(state = Initialization , action) {
8 | switch (action.type) {
9 | case 'MESSAGE_CENTER':
10 | return Object.assign({},state,{
11 | data : action.data
12 | })
13 | case 'SET_TABM':
14 | return Object.assign({},state,{
15 | tab : action.tab
16 | })
17 | case 'MESSAGE_NUM':
18 | return Object.assign({},state,{
19 | num : action.num
20 | })
21 | default:
22 | return state
23 | }
24 | }
--------------------------------------------------------------------------------
/src/reducers/publish.js:
--------------------------------------------------------------------------------
1 | let Initialization = {
2 | tab : "dev",
3 | title : "",
4 | content : "",
5 | success : false,
6 | topic_id : ""
7 | }
8 |
9 | export function publish(state = Initialization , action) {
10 | switch (action.type) {
11 | case 'RECEIVE_PUBLISHTOPIC':
12 | return Object.assign({},state,{
13 | success : action.success,
14 | topic_id : action.topic_id
15 | })
16 | case 'PUBLISH_TAB':
17 | return Object.assign({},state,{
18 | tab : action.tab
19 | })
20 | case 'PUBLISH_TITLE':
21 | return Object.assign({},state,{
22 | title : action.title
23 | })
24 | case 'PUBLISH_CONTENT':
25 | return Object.assign({},state,{
26 | content : action.content
27 | })
28 | default:
29 | return state
30 | }
31 | }
--------------------------------------------------------------------------------
/src/reducers/topic.js:
--------------------------------------------------------------------------------
1 | // 初始化状态
2 | let initNavList = {
3 | isFecthing: false,
4 | data: {}
5 | }
6 |
7 | export function topic(state = initNavList, action) {
8 | switch (action.type) {
9 | case 'REQUEST_ARTICLE':
10 | return Object.assign({},state,{
11 | isFecthing : true
12 | })
13 | case 'RECEIVE_ARTICLE':
14 | return Object.assign({},state,{
15 | data : action.data,
16 | isFecthing : false
17 | })
18 | default:
19 | return state
20 | }
21 | }
--------------------------------------------------------------------------------
/src/reducers/user.js:
--------------------------------------------------------------------------------
1 | let Initialization = {
2 | data : [],
3 | tab : 1
4 | }
5 |
6 | export function user(state = Initialization , action) {
7 | switch (action.type) {
8 | case 'USER_CENTER':
9 | return Object.assign({},state,{
10 | data : action.data
11 | })
12 | case 'SET_TAB':
13 | return Object.assign({},state,{
14 | tab : action.tab
15 | })
16 | default:
17 | return state
18 | }
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/src/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | react-socket
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/utils/cookie.js:
--------------------------------------------------------------------------------
1 |
2 | //保存cookie
3 | export function saveData(name, value, min) {
4 | if (min) {
5 | var date = new Date();
6 | date.setTime(date.getTime() + (min * 60 * 1000 ));
7 | var expires = "; expires=" + date.toUTCString();
8 | }
9 | else var expires = "";
10 | document.cookie = name + "=" + value + expires + "; path=/";
11 | }
12 | // 读取cookie
13 | export function readData(name) {
14 | var nameEQ = name + "=";
15 | var ca = document.cookie.split(';');
16 | for (var i = 0; i < ca.length; i++) {
17 | var c = ca[i];
18 | while (c.charAt(0) == ' ') c = c.substring(1, c.length);
19 | if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
20 | }
21 | return null;
22 | }
23 |
24 | export function removeData(name) {
25 | saveData(name, "", -1);
26 | }
27 | // 保存localstorage
28 | export function localItem (key, value) {
29 | if (arguments.length == 1) {
30 | return localStorage.getItem(key);
31 | } else {
32 | return localStorage.setItem(key, value);
33 | }
34 | }
35 | // 删除localstorage
36 | export function removeLocalItem (key) {
37 | if (key) {
38 | return localStorage.removeItem(key);
39 | }
40 | return localStorage.removeItem();
41 | }
42 |
43 | //时间格式化
44 | export function formatDate (str) {
45 | var date = new Date(str);
46 | var time = new Date().getTime() - date.getTime(); //现在的时间-传入的时间 = 相差的时间(单位 = 毫秒)
47 | if (time < 0) {
48 | return '';
49 | } else if (time / 1000 < 60) {
50 | return '刚刚';
51 | } else if ((time / 60000) < 60) {
52 | return parseInt((time / 60000)) + '分钟前';
53 | } else if ((time / 3600000) < 24) {
54 | return parseInt(time / 3600000) + '小时前';
55 | } else if ((time / 86400000) < 31) {
56 | return parseInt(time / 86400000) + '天前';
57 | } else if ((time / 2592000000) < 12) {
58 | return parseInt(time / 2592000000) + '月前';
59 | } else {
60 | return parseInt(time / 31536000000) + '年前';
61 | }
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/src/utils/instance.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | //封装好的get和post接口,调用方法情况action文件
4 | const instance = axios.create({
5 | baseURL: 'https://cnodejs.org/api/v1', //设置默认api路径
6 | timeout: 5000, //设置超时时间
7 | headers: {'content-type': 'application/json; charset=utf-8'}
8 | });
9 |
10 | export default instance;
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var autoprefixer = require('autoprefixer');
4 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
5 | var HtmlWebpackPlugin = require('html-webpack-plugin');
6 | var argv = require('yargs').argv;
7 |
8 | //判断当前运行环境是开发模式还是生产模式
9 | const nodeEnv = process.env.NODE_ENV || 'development';
10 | const isPro = nodeEnv === 'production';
11 |
12 | console.log('当前运行环境:',isPro ? 'production' : 'development')
13 |
14 | var plugins = [
15 | new ExtractTextPlugin('styles.css'),
16 | new webpack.optimize.CommonsChunkPlugin({
17 | name:'vendor',
18 | minChunks: function (module) {
19 | // 该配置假定你引入的 vendor 存在于 node_modules 目录中
20 | return module.context && module.context.indexOf('node_modules') !== -1;
21 | }
22 | }),
23 | new webpack.DefinePlugin({
24 | // 定义全局变量
25 | 'process.env':{
26 | 'NODE_ENV': JSON.stringify(nodeEnv)
27 | }
28 | })
29 | ,
30 | new HtmlWebpackPlugin({ //根据模板插入css/js等生成最终HTML
31 | filename: path.join(__dirname, './index.html'), //生成的html存放路径
32 | template: 'template/index.html', //html模板路径
33 | hash: true, //为静态资源生成hash值
34 | })
35 | ]
36 |
37 | var app = ['./entry'];
38 |
39 | if(isPro) {
40 | new webpack.LoaderOptionsPlugin({
41 | minimize: true,
42 | debug: false
43 | }),
44 | new webpack.optimize.UglifyJsPlugin({
45 | sourceMap: true,
46 | comments: false,
47 | ie8: true
48 | })
49 | } else {
50 | app.unshift('react-hot-loader/patch', 'webpack-dev-server/client?http://localhost:8888', 'webpack/hot/only-dev-server')
51 | // 'react-hot-loader/patch' 开启 React 代码的模块热替换(HMR)
52 | // 'webpack-dev-server/client?http://localhost:8888'为 webpack-dev-server 的环境打包代码
53 | // 然后连接到指定服务器域名与端口,可以换成本机ip
54 | // 'webpack/hot/only-dev-server'为热替换(HMR)打包好代码
55 | // only- 意味着只有成功更新运行代码才会执行热替换(HMR)
56 | plugins.push(
57 | new webpack.HotModuleReplacementPlugin(),
58 | // 开启全局的模块热替换(HMR)
59 | new webpack.NamedModulesPlugin(),
60 | // 控制台输出模块命名美化
61 | new webpack.NoEmitOnErrorsPlugin()
62 | //这样可以确保输出资源不会包含错误
63 | )
64 | }
65 |
66 | module.exports = {
67 | context: path.resolve(__dirname, 'src'),
68 | devtool: isPro ? 'source-map' : 'inline-source-map',
69 | entry: {
70 | app: app
71 | },
72 | output: {
73 | filename: '[name].js',
74 | path: path.join(__dirname, 'build'),
75 | publicPath: isPro ? './build/' : './build/',
76 | chunkFilename: '[name].js'
77 | },
78 | // BASE_URL是全局的api接口访问地址
79 | plugins,
80 | // alias是配置全局的路径入口名称,只要涉及到下面配置的文件路径,可以直接用定义的单个字母表示整个路径
81 | resolve: {
82 | extensions: ['.js', '.jsx', '.less', '.scss', '.css'],
83 | modules: [
84 | path.resolve(__dirname, 'node_modules'),
85 | path.join(__dirname, './src')
86 | ],
87 | alias: {
88 | "actions": path.resolve(__dirname, "src/actions"),
89 | "components": path.resolve(__dirname, "src/components"),
90 | "containers": path.resolve(__dirname, "src/containers"),
91 | "reducers": path.resolve(__dirname, "src/reducers"),
92 | "utils": path.resolve(__dirname, "src/utils")
93 | }
94 | },
95 |
96 | module: {
97 | rules: [{
98 | test: /\.js$/,
99 | exclude: /(node_modules|bower_components)/,
100 | use: {
101 | loader: 'babel-loader?cacheDirectory=true'
102 | }
103 | }, {
104 | test: /\.(less|css)$/,
105 | use: ExtractTextPlugin.extract({
106 | use: ["css-loader", "less-loader", "postcss-loader"]
107 | })
108 | }, {
109 | test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
110 | use: ['url-loader?limit=8000&name=files/[md5:hash:base64:10].[ext]']
111 | }]
112 | },
113 | devServer: {
114 | hot: true,
115 | compress: true,
116 | port: 8888,
117 | historyApiFallback: true,
118 | contentBase: path.resolve(__dirname),
119 | publicPath: '/build/',
120 | stats: {
121 | modules: false,
122 | chunks: false
123 | },
124 | },
125 | };
--------------------------------------------------------------------------------