├── .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 |
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 | }
--------------------------------------------------------------------------------