├── .babelrc ├── .gitignore ├── README.md ├── config └── webpack.config.js ├── package-lock.json ├── package.json └── src ├── assets └── logo.jpg ├── index.html ├── scripts ├── constants │ └── ui.js ├── index.js ├── pages │ ├── app.js │ ├── mobile │ │ └── index.js │ └── pc │ │ ├── detail.js │ │ └── index.js ├── redux │ ├── app.js │ └── index.js └── utils │ └── index.js └── styles └── styles.css /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015","stage-0"], 3 | "plugins": [ 4 | ["antd",{"style": "css","libraryName": "antd"}] //桌面版UI 5 | // ["antd",{"style": "css","libraryName": "antd-mobile"}] //手机版UI,目前高清方案很乱,支持很差 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | *.log 4 | .DS_Store 5 | build/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | reactjs-universal-project 2 | 3 | 作为通用模板以快速开发
4 | 以夜间模式的开启、关闭,以及如何关闭网页后重启继续从缓存读取为例。
5 | 6 | 使用方法:
7 | npm install
8 |
9 | 调试命令:
10 | npm run dev
11 | 浏览器打开http://localhost:8080
12 |
13 | 打包命令
14 | npm run build
15 |

16 | demo截图:
17 | WX20170314-214733@2x.png 18 |

19 | 详情:
20 | demo工程涉及到的内容:
21 | react-router
22 | redux + redux-thunk
23 | antDesign + antDesign-Mobile
24 | webpack
25 | webpack-dev-server
26 | postcss使用
27 | 28 |
我的博客:http://www.cnblogs.com/rayshen/
29 | -------------------------------------------------------------------------------- /config/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var precss = require('precss'); 4 | var calc = require('postcss-calc'); 5 | var cssnext = require('cssnext'); 6 | var autoprefixer = require('autoprefixer'); 7 | 8 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 9 | module.exports = { 10 | entry: [ 11 | 'babel-polyfill', 12 | './src/scripts/index.js' 13 | ], 14 | output: { 15 | path: path.resolve(__dirname, '../build'), 16 | filename: 'scripts/bundle.js', 17 | }, 18 | resolve: { 19 | modulesDirectories: ['node_modules', path.join(__dirname, '../node_modules')], 20 | extensions: ['', '.web.tsx', '.web.ts', '.web.jsx', '.web.js', '.ts', '.tsx', '.js', '.jsx', '.json'], 21 | }, 22 | plugins: [ 23 | new webpack.optimize.OccurenceOrderPlugin(), 24 | new webpack.DefinePlugin({ 25 | 'process.env': { 26 | 'NODE_ENV': "'production'" 27 | } 28 | }), 29 | new webpack.optimize.UglifyJsPlugin({ 30 | compressor: { 31 | warnings: false 32 | } 33 | }), 34 | new ExtractTextPlugin('/styles/styles.css', { 35 | disabled: process.env.NODE_ENV === 'dev' 36 | }), 37 | new webpack.DefinePlugin({ 38 | __DEBUG__: JSON.stringify(JSON.parse(process.env.NODE_ENV === 'dev')) 39 | }), 40 | ], 41 | 42 | module: { 43 | loaders: [ 44 | // js 45 | { 46 | test: /\.(js)?$/, 47 | loaders: [ 48 | 'react-hot', 49 | 'babel' 50 | ], 51 | exclude: /node_modules/ 52 | }, 53 | // CSS 54 | { 55 | test: /\.(css)$/, 56 | loader: ExtractTextPlugin.extract( 57 | 'style', 58 | 'css!postcss', { 59 | publicPath: '../' 60 | } 61 | ) 62 | }, 63 | // HTML 64 | { 65 | test: /\.(html)$/, 66 | exclude: /node_modules/, 67 | loader: 'file?name=[path][name].[ext]&context=./src' 68 | }, 69 | // image 70 | { 71 | test: /.*\.(gif|png|jpe?g|svg)$/i, 72 | exclude: /node_modules/, 73 | loaders: [ 74 | 'file?hash=sha512&digest=hex&name=[path][hash].[ext]&context=./src' 75 | ] 76 | } 77 | ] 78 | }, 79 | postcss: function () { 80 | return [cssnext, precss, autoprefixer, calc]; 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactjs-universal-project", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "rimraf build && NODE_ENV=production webpack --config ./config/webpack.config.js", 6 | "dev": "NODE_ENV=dev webpack-dev-server --host 0.0.0.0 --colors --content-base ./build --config ./config/webpack.config.js" 7 | }, 8 | "dependencies": { 9 | "antd": "^2.7.1", 10 | "antd-mobile": "^0.9.15", 11 | "autoprefixer": "^6.7.2", 12 | "babel-core": "^6.7.7", 13 | "babel-loader": "^6.2.4", 14 | "babel-plugin-antd": "^0.5.1", 15 | "babel-plugin-react-transform": "^2.0.2", 16 | "babel-plugin-transform-object-rest-spread": "^6.23.0", 17 | "babel-plugin-transform-react-display-name": "^6.5.0", 18 | "babel-polyfill": "^6.7.4", 19 | "babel-preset-es2015": "^6.6.0", 20 | "babel-preset-react": "^6.5.0", 21 | "babel-preset-stage-0": "^6.22.0", 22 | "css-loader": "^0.23.1", 23 | "cssnext": "^1.8.4", 24 | "extract-text-webpack-plugin": "^1.0.1", 25 | "less-loader": "^2.2.3", 26 | "postcss-calc": "^5.3.1", 27 | "postcss-loader": "^1.3.0", 28 | "precss": "^1.4.0", 29 | "react": "^15.0.2", 30 | "react-addons-css-transition-group": "^15.0.2", 31 | "react-addons-transition-group": "^15.4.2", 32 | "react-bootstrap": "^0.30.7", 33 | "react-dom": "^15.0.2", 34 | "react-hammerjs": "^0.5.0", 35 | "react-hot-loader": "^1.3.1", 36 | "react-redux": "^4.4.5", 37 | "react-router": "^2.4.0", 38 | "react-router-redux": "^4.0.4", 39 | "react-transform-catch-errors": "^1.0.2", 40 | "react-transform-hmr": "^1.0.4", 41 | "redbox-react": "^1.2.3", 42 | "redux": "^3.5.2", 43 | "redux-thunk": "^2.2.0", 44 | "rimraf": "^2.5.2", 45 | "sass-loader": "^6.0.0", 46 | "style-loader": "^0.13.1", 47 | "stylus": "^0.54.5", 48 | "stylus-loader": "^2.0.0", 49 | "superagent": "^3.4.1", 50 | "webpack": "^1.13.0", 51 | "webpack-dev-server": "^1.14.1" 52 | }, 53 | "devDependencies": { 54 | "babel-plugin-import": "^1.1.0", 55 | "expect": "^1.18.0", 56 | "expect-jsx": "^2.5.1", 57 | "file-loader": "^0.10.0", 58 | "react-addons-test-utils": "^15.0.2", 59 | "url-loader": "^0.5.7" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/assets/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayshen/reactjs-universal-project/e9b281b7b399485ebf7c5ff12a6906ab94805f42/src/assets/logo.jpg -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | reactjs-universal-demo 8 | 9 | 10 | 11 |

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/scripts/constants/ui.js: -------------------------------------------------------------------------------- 1 | export const themeColor = { 2 | dark:'black', 3 | light:'#319FDE' 4 | } -------------------------------------------------------------------------------- /src/scripts/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | // Import HTML 4 | import '../index.html'; 5 | // Import css 6 | import '../styles/styles.css'; 7 | // Import Components 8 | import App from './pages/app'; 9 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 10 | import { Provider } from 'react-redux'; 11 | import store from './redux/index.js'; 12 | import index from './pages/pc'; 13 | import detail from './pages/pc/detail'; 14 | 15 | const router = ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ) 27 | 28 | render(router, document.getElementById('root')); 29 | -------------------------------------------------------------------------------- /src/scripts/pages/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class index extends React.Component { 4 | render(){ 5 | return ( 6 |
7 | {React.cloneElement(this.props.children, {...this.props})} 8 |
9 | ) 10 | } 11 | } -------------------------------------------------------------------------------- /src/scripts/pages/mobile/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PropTypes } from 'react-router'; 3 | import { connect } from 'react-redux'; 4 | 5 | //UI component 6 | import { NavBar, Icon } from 'antd-mobile'; 7 | 8 | class index extends React.Component { 9 | constructor(props, context) { 10 | super(props, context); 11 | this.state = { 12 | supportedBanks: null, 13 | fail: false, 14 | render: false, 15 | }; 16 | } 17 | componentDidMount() { 18 | } 19 | render() { 20 | return ( 21 |
22 | console.log('onLeftClick')} 23 | rightContent={[, ]} 24 | >NavBar 25 |
26 | ); 27 | } 28 | } 29 | 30 | index.contextTypes = { 31 | router: React.PropTypes.object.isRequired 32 | }; 33 | export default connect( 34 | (state) => ({ 35 | 36 | }), 37 | (dispatch) => ({ 38 | 39 | }) 40 | )(index); 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/scripts/pages/pc/detail.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PropTypes,withRouter} from 'react-router'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | 6 | import * as appActions from '../../redux/app'; 7 | 8 | //UI component 9 | import {Button} from 'antd'; 10 | import {themeColor} from '../../constants/ui'; 11 | 12 | class index extends React.Component { 13 | constructor(props, context) { 14 | super(props, context); 15 | this.state = { 16 | 17 | }; 18 | } 19 | componentDidMount() { 20 | 21 | } 22 | 23 | render() { 24 | return ( 25 |
26 |
27 |

详情

28 |
29 | 34 |
35 |
36 |
37 | 45 |
46 |
47 | ); 48 | } 49 | } 50 | 51 | export default connect( 52 | (state) => ({ 53 | darkMode:state.app.darkMode 54 | }), 55 | (dispatch) => ({ 56 | setDarkMode:bindActionCreators(appActions.setDarkMode,dispatch) 57 | }) 58 | )(withRouter(index)); 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/scripts/pages/pc/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PropTypes,withRouter} from 'react-router'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | 6 | import * as appActions from '../../redux/app'; 7 | 8 | //UI component 9 | import {Button} from 'antd'; 10 | 11 | import {themeColor} from '../../constants/ui'; 12 | 13 | class index extends React.Component { 14 | constructor(props, context) { 15 | super(props, context); 16 | this.state = { 17 | 18 | }; 19 | } 20 | componentDidMount() { 21 | 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 |
28 |

首页

29 |
30 | 35 |
36 |
37 |
38 | 46 | 47 |
48 |
49 | ); 50 | } 51 | } 52 | 53 | export default connect( 54 | (state) => ({ 55 | darkMode:state.app.darkMode 56 | }), 57 | (dispatch) => ({ 58 | setDarkMode:bindActionCreators(appActions.setDarkMode,dispatch) 59 | }) 60 | )(withRouter(index)); 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/scripts/redux/app.js: -------------------------------------------------------------------------------- 1 | export const appActionTypes = { 2 | SET_DARK_MODE:"SET_DARK_MODE", 3 | } 4 | 5 | /** 6 | * reducers 7 | */ 8 | const initialState = { 9 | darkMode:false 10 | } 11 | export const appReducer = (state = initialState, action)=>{ 12 | switch(action.type) { 13 | case appActionTypes.SET_DARK_MODE: 14 | return { 15 | ...state, 16 | darkMode:action.value 17 | } 18 | default: 19 | return state; 20 | } 21 | } 22 | 23 | /** 24 | * actions 25 | */ 26 | //设置演示颜色 27 | export const setDarkMode = (value) =>{ 28 | return async (dispatch)=>{ 29 | // do something async/sync operation 30 | localStorage.setItem('DARK_MODE',value?'1':'0'); 31 | dispatch({ 32 | 'type': appActionTypes.SET_DARK_MODE, 33 | 'value': value, 34 | }) 35 | } 36 | } -------------------------------------------------------------------------------- /src/scripts/redux/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware,combineReducers} from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { routerReducer } from 'react-router-redux'; 4 | 5 | import {appReducer} from './app'; 6 | 7 | const rootReducer = combineReducers({app:appReducer, routing: routerReducer}); 8 | 9 | 10 | const configureStore = (initialState) =>{ 11 | return createStore(rootReducer,initialState,applyMiddleware(thunk)); 12 | } 13 | 14 | //初始化缓存 15 | let darkMode = localStorage.getItem('DARK_MODE') == '1'?true:false; 16 | 17 | const store = configureStore({ 18 | app:{ 19 | darkMode 20 | } 21 | }); 22 | 23 | export default store; 24 | -------------------------------------------------------------------------------- /src/scripts/utils/index.js: -------------------------------------------------------------------------------- 1 | export const qsParams = (() => { 2 | let qs = location.search; 3 | if (!qs && location.hash) { 4 | let p = location.hash.indexOf('?'); 5 | qs = location.hash.substr(p); 6 | } 7 | if (!qs) return {}; 8 | if (qs[0] === '?') qs = qs.slice(1); 9 | let pairs = qs.split('&'); 10 | if (!pairs || pairs.length === 0) return {}; 11 | return pairs.reduce((params, p) => { 12 | let idx = p.indexOf('='); 13 | if (p.slice(0, idx) === '_k') return params; 14 | if (idx > 0) { 15 | params[p.slice(0, idx)] = decodeURIComponent(p.slice(idx + 1)); 16 | } 17 | return params; 18 | }, {}); 19 | })(); -------------------------------------------------------------------------------- /src/styles/styles.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin:auto; 3 | #root{ 4 | .container{ 5 | .mx-title-bar{ 6 | position: fixed; 7 | top:0px; 8 | left:0px; 9 | right:0px; 10 | z-index: 100; 11 | height: 44px; 12 | color: rgb(255, 255, 255); 13 | text-align: center; 14 | line-height: 44px; 15 | box-shadow: rgba(0, 0, 0, 0.0470588) 0px 1px 1px 1px; 16 | background-color: #319FDE; 17 | display:flex; 18 | flex-direction:row; 19 | align-items:center; 20 | justify-content: center; 21 | .mx-title-bar-title{ 22 | font-size:18px; 23 | } 24 | .mx-title-bar-left{ 25 | position: absolute; 26 | left:10px; 27 | line-height: 44px; 28 | height: 44px; 29 | } 30 | .mx-title-bar-right{ 31 | position: absolute; 32 | right:10px; 33 | line-height: 44px; 34 | height: 44px; 35 | } 36 | .mx-title-bar-button { 37 | line-height: 44px; 38 | height: 44px; 39 | width:44px; 40 | padding: 10 8px; 41 | border: 0; 42 | border-radius: 2px; 43 | background-color: transparent; 44 | box-sizing: border-box; 45 | -webkit-appearance: none; 46 | } 47 | } 48 | .content{ 49 | margin-top:44px; 50 | display:flex; 51 | align-items:center; 52 | justify-content:center; 53 | flex-direction:column; 54 | .main-img{ 55 | position:absolute; 56 | top:50%; 57 | width:100px; 58 | height:100px; 59 | } 60 | .switch-button{ 61 | margin-top:20px; 62 | } 63 | } 64 | } 65 | } 66 | } --------------------------------------------------------------------------------