├── .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 | ![cnode](https://github.com/biyunbo/react-cnode/raw/master/show/cnode.png) 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 | ![截图](https://github.com/biyunbo/react-cnode/raw/master/show/shouyeliebiao.png) 33 | ![截图](https://github.com/biyunbo/react-cnode/raw/master/show/xiangqing.png) 34 | ![截图](https://github.com/biyunbo/react-cnode/raw/master/show/fabu.png) 35 | ![截图](https://github.com/biyunbo/react-cnode/raw/master/show/gerenzhongxin.png) 36 | ![截图](https://github.com/biyunbo/react-cnode/raw/master/show/xiaoxi.png) 37 | ![截图](https://github.com/biyunbo/react-cnode/raw/master/show/denglu.png) 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 |
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 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 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 | }; --------------------------------------------------------------------------------