diff --git a/README.md b/README.md
index 9a4a2ae..dd132c4 100644
--- a/README.md
+++ b/README.md
@@ -3,27 +3,28 @@ React-test
## 介绍
-这是一个react测试项目,旨在通过webpack打包生成用于restful API的one page react项目
+这是一个react测试项目,旨在通过webpack打包生成用于restful API的one page react项目.
+该demo展示了异步登录,页面检测登录自动跳转,不同页面渲染等相关操作.
+须安装[webpack](https://github.com/webpack/webpack),[redux-devtools-extension](https://github.com/zalmoxisus/redux-devtools-extension)
-## 技术栈
+## 目录结构
```
-*babel
-*webpack
-*react
-*redux
-*react-redux
-*react-router
-*react-router-redux
+webpack.config.js //webpack配置文件
+app.js //express应用程序
+-views //express模板
+-public //静态文件夹
+-client //客户端项目文件夹
+--app.js //入口文件
+--config.js //配置文件
+---actions //redux action列表
+---reducers //redux reducer列表
+---components //react组件
+---pages //页面组件
```
-## 目录结构
+##如何运行
```
--client //客户端项目库
---app.js //入口文件
---router.js //路由
----actions //redux action列表
----reducers //redux reducer列表
----components //react组件
----pages //页面组件
--public //发布目录
+npm install
+webpack
+npm start
```
\ No newline at end of file
diff --git a/app.js b/app.js
new file mode 100644
index 0000000..f2218da
--- /dev/null
+++ b/app.js
@@ -0,0 +1,53 @@
+//加载依赖
+var express = require('express');
+var path = require('path');
+
+var app = express();
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'));
+app.set('view engine', 'html');
+app.engine('html', require('ejs-mate'));
+
+app.use(express.static(path.join(__dirname, 'public')));
+
+//路由设置
+app.get('*',function(req,res){
+ res.render('index',{});
+});
+
+
+// catch 404 and forward to error handler
+app.use(function(req, res, next) {
+ var err = {status:404};
+ next(err);
+});
+
+// error handlers
+
+// production error handler
+// no stacktraces leaked to user
+app.use(function(err, req, res, next) {
+ console.log(err);
+ if(!err.status){
+ err.status=500;
+ }
+ switch(err.status){
+ case 403:
+ err.message = '没有权限访问';
+ break;
+ case 404:
+ err.message = '找不到这个页面';
+ break;
+ case 500:
+ err.message='系统出错了';
+ break;
+ }
+ res.status(err.status);
+ res.render('error', {
+ title: '出错拉',
+ error:err
+ });
+});
+
+module.exports = app;
diff --git a/bin/www b/bin/www
new file mode 100644
index 0000000..c38896a
--- /dev/null
+++ b/bin/www
@@ -0,0 +1,89 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || 8080);
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+ var port = parseInt(val, 10);
+
+ if (isNaN(port)) {
+ // named pipe
+ return val;
+ }
+
+ if (port >= 0) {
+ // port number
+ return port;
+ }
+
+ return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+ if (error.syscall !== 'listen') {
+ throw error;
+ }
+
+ var bind = typeof port === 'string'
+ ? 'Pipe ' + port
+ : 'Port ' + port
+
+ // handle specific listen errors with friendly messages
+ switch (error.code) {
+ case 'EACCES':
+ console.error(bind + ' requires elevated privileges');
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(bind + ' is already in use');
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+ var addr = server.address();
+ var bind = typeof addr === 'string'
+ ? 'pipe ' + addr
+ : 'port ' + addr.port;
+ console.log('Listening on ' + bind);
+}
\ No newline at end of file
diff --git a/client/actions/auth.js b/client/actions/auth.js
new file mode 100644
index 0000000..ceedd36
--- /dev/null
+++ b/client/actions/auth.js
@@ -0,0 +1,32 @@
+/**
+ * Action - auth
+ * 验证方法
+ */
+//加载依赖
+import { push } from 'react-router-redux'; //router跳转方法
+
+//导入常量
+import { LOGIN_STATUS_IN } from '../actions/login';
+
+/*
+ * action 创建函数
+ */
+
+/**
+ * [authLoginStatus description]
+ * @param {string} login_status 登录状态
+ * @param {boolen} check_status 须检测的状态,true为检测是否登录(当未登录时,跳转到登录页),false为检测是否未登录(当登录时,跳转到dashboard页)
+ * @return {function} [description]
+ */
+export function authLoginStatus(login_status,check_status){
+ return function(dispatch) {
+ //检测登录,当未登录时,跳转到登录页
+ if(check_status&&login_status!==LOGIN_STATUS_IN){
+ dispatch(push('/'));
+ }
+ //检测未登录,当登录时,跳转到dashboard页
+ if(!check_status&&login_status===LOGIN_STATUS_IN){
+ dispatch(push('/dashboard/'));
+ }
+ }
+}
\ No newline at end of file
diff --git a/client/actions/login.js b/client/actions/login.js
new file mode 100644
index 0000000..107b8c2
--- /dev/null
+++ b/client/actions/login.js
@@ -0,0 +1,124 @@
+/**
+ * Action - login
+ * 登录,控制登录操作
+ */
+//加载依赖
+import fetch from 'isomorphic-fetch';
+import {
+ API_LOGIN, API_LOGOUT
+}
+from '../config';
+import {
+ push
+}
+from 'react-router-redux'; //router跳转方法
+
+/*
+ * action 类型
+ */
+//登入请求
+export const LOGIN_REQUEST = 'LOGIN_REQUEST';
+export const LOGIN_RECEIVE = 'LOGIN_RECEIVE';
+export const LOGIN_ERROR = 'LOGIN_ERROR';
+//登出请求
+export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
+export const LOGOUT_RECEIVE = 'LOGOUT_RECEIVE';
+export const LOGOUT_ERROR = 'LOGOUT_ERROR';
+//状态
+export const LOGIN_STATUS_OUT = 'out'; //登出
+export const LOGIN_STATUS_IN = 'in'; //登入
+export const LOGIN_STATUS_LOAD = 'load'; //加载
+export const LOGIN_STATUS_ERROR = 'error'; //出错
+/*
+ * action 创建函数
+ */
+//登录
+export function loginRequest() {
+ return {
+ type: LOGIN_REQUEST
+ }
+}
+
+export function loginReceive(data) {
+ return {
+ type: LOGIN_RECEIVE,
+ info: data
+ }
+}
+
+export function loginError(data) {
+ return {
+ type: LOGIN_ERROR,
+ msg: data
+ }
+}
+
+export function loginPost(data) {
+ return function(dispatch) {
+ if (!data.name || !data.password) {
+ dispatch(loginError('没有输入用户名或密码'));
+ return false;
+ }
+ dispatch(loginRequest());
+ let headers = new Headers(data);
+ let request = new Request(API_LOGIN, {
+ method: 'GET',
+ headers: headers
+ })
+ fetch(request).then(response => response.json())
+ .then(json => {
+ if (json.head.status === 200) {
+ dispatch(loginReceive(json.body));
+ //跳转到登录后首页
+ dispatch(push('/dashboard/'));
+ } else {
+ dispatch(loginError(json.head.msg));
+ }
+ }).catch(error => {
+ console.log(error);
+ dispatch(loginError('网络错误,请重试'));
+ })
+ }
+}
+
+//登出
+export function logoutRequest() {
+ return {
+ type: LOGOUT_REQUEST
+ }
+}
+
+export function logoutReceive(data) {
+ return {
+ type: LOGOUT_RECEIVE,
+ info: data
+ }
+}
+
+export function logoutError(data) {
+ return {
+ type: LOGOUT_ERROR,
+ msg: data
+ }
+}
+
+export function logoutPost() {
+ return function(dispatch) {
+ dispatch(logoutRequest());
+ let request = new Request(API_LOGOUT, {
+ method: 'GET'
+ });
+ fetch(request).then(response => response.json())
+ .then(json => {
+ if (json.head.status === 200) {
+ dispatch(logoutReceive(json.body));
+ //跳转到登录后首页
+ dispatch(push('/'));
+ } else {
+ dispatch(logoutError(json.head.msg));
+ }
+ }).catch(error =>
+ dispatch(loginError('网络错误,请重试'))
+ )
+ }
+}
\ No newline at end of file
diff --git a/client/app.js b/client/app.js
index cf20eec..dbdd9f5 100644
--- a/client/app.js
+++ b/client/app.js
@@ -1,83 +1,55 @@
/**
* app
*/
-import React, { PropTypes } from 'react'
-import ReactDOM from 'react-dom'
-import { createStore,applyMiddleware} from 'redux'
-import { Provider, connect } from 'react-redux'
-import thunk from 'redux-thunk'
-// React component
-class Counter extends React.Component {
- render () {
- const { value, onIncreaseClick } = this.props
- return (
-
- {value}
-
-
- )
- }
-}
-
-Counter.propTypes = {
- value: PropTypes.number.isRequired,
- onIncreaseClick: PropTypes.func.isRequired
-}
+//加载依赖
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { createStore, applyMiddleware } from 'redux';
+import { Provider,Counter } from 'react-redux';
+import { Router, Route, browserHistory,IndexRoute } from 'react-router';
+import { syncHistory } from 'react-router-redux';
+import thunk from 'redux-thunk';
+import {reducer} from './reducers/index';
-// Action
-const increaseAction = {type: 'increase'}
+//加载页面
+import Layout from './pages/layout'; //公共组件
+import Home from './pages/home'; //首页
+import Dashboard from './pages/dashboard'; //控制面板
-// Reducer
-function counter (state = {count: 0}, action) {
- let count = state.count
- switch (action.type) {
- case 'increase':
- return {count: count + 1}
- default:
- return state
- }
-}
+// Sync dispatched route actions to the history
+const reduxRouterMiddleware = syncHistory(browserHistory);
//插入中间件
let createStoreWithMiddleware = applyMiddleware(
- thunk
+ thunk,
+ reduxRouterMiddleware
)(createStore)
-//载入redux debug插件
-function configureStore(reducer,initialState) {
- const store = createStoreWithMiddleware(reducer, initialState,
- window.devToolsExtension ? window.devToolsExtension() : undefined
- );
- return store;
+if(process.env.NODE_ENV==='production'){
+ var store = createStoreWithMiddleware(reducer,{});
}
-// Store
-//let store = createStore(counter);
-let store = configureStore(counter,{count:0});
-
-// Map Redux state to component props
-function mapStateToProps (state) {
- return {
- value: state.count
+else{
+ //载入redux debug插件
+ function configureStore(initialState) {
+ const store = createStoreWithMiddleware(reducer, initialState,
+ window.devToolsExtension ? window.devToolsExtension() : undefined
+ );
+ return store;
}
+ // Store
+ var store = configureStore({});
}
-// Map Redux actions to component props
-function mapDispatchToProps (dispatch) {
- return {
- onIncreaseClick: () => dispatch(increaseAction)
- }
-}
-
-// Connected Component
-let App = connect(
- mapStateToProps,
- mapDispatchToProps
-)(Counter)
-
+//创建路由
ReactDOM.render(
-
+
+
+
+
+
+
,
document.getElementById('page')
)
\ No newline at end of file
diff --git a/client/components/login.js b/client/components/login.js
new file mode 100644
index 0000000..7bca242
--- /dev/null
+++ b/client/components/login.js
@@ -0,0 +1,45 @@
+/**
+ * Page - index
+ * 首页
+ */
+import React, {PropTypes,Component} from 'react';
+import { LOGIN_STATUS_OUT,LOGIN_STATUS_IN,LOGIN_STATUS_LOAD,LOGIN_STATUS_ERROR } from '../actions/login';
+
+//封装app组件
+class Login extends Component {
+ render() {
+ const {status,msg} = this.props;
+ var Tip='',Button=;
+ switch(status){
+ case LOGIN_STATUS_ERROR:
+ Tip=提示信息:{msg}
+ break;
+ case LOGIN_STATUS_LOAD:
+ Button=;
+ break;
+ }
+ return (
+
+ );
+ }
+
+ handleSubmit(e) {
+ e.preventDefault();
+ this.props.onLoginSubmit({
+ name:this.refs.name.value,
+ password:this.refs.password.value
+ });
+ }
+}
+//组件限制
+Login.propTypes={
+ onLoginSubmit:PropTypes.func.isRequired,
+ status:PropTypes.string.isRequired,
+ msg:PropTypes.string
+}
+export default Login;
\ No newline at end of file
diff --git a/client/config.js b/client/config.js
new file mode 100644
index 0000000..4d507cf
--- /dev/null
+++ b/client/config.js
@@ -0,0 +1,7 @@
+/**
+ * 配置信息
+ */
+//API配置
+const API_PATH='/api/';
+export const API_LOGIN=API_PATH+'login.json';
+export const API_LOGOUT=API_PATH+'loginout.json';
\ No newline at end of file
diff --git a/client/pages/dashboard.js b/client/pages/dashboard.js
new file mode 100644
index 0000000..c3c18bd
--- /dev/null
+++ b/client/pages/dashboard.js
@@ -0,0 +1,33 @@
+/**
+ * Page - index
+ * 首页
+ */
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { push } from 'react-router-redux';
+
+//加载action
+import { authLoginStatus } from '../actions/auth';
+import { logoutPost,LOGIN_STATUS_IN } from '../actions/login';
+
+//加载reducer
+import {propMap} from '../reducers/index';
+
+//封装组件
+class Dashboard extends Component {
+ componentDidMount(){
+ const { login,dispatch,routing } = this.props;
+ //检测是否未登录
+ dispatch(authLoginStatus(login.status,true));
+ }
+ render() {
+ const { login,dispatch } = this.props;
+ return (
+
+ );
+ }
+}
+export default connect(propMap)(Dashboard);
\ No newline at end of file
diff --git a/client/pages/home.js b/client/pages/home.js
new file mode 100644
index 0000000..a989bbf
--- /dev/null
+++ b/client/pages/home.js
@@ -0,0 +1,37 @@
+/**
+ * Page - index
+ * 首页
+ */
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { push } from 'react-router-redux';
+
+//加载组件
+import Login from '../components/login';
+
+//加载action
+import { authLoginStatus } from '../actions/auth';
+import { loginPost,LOGIN_STATUS_IN } from '../actions/login';
+
+//加载reducer
+import {propMap} from '../reducers/index';
+
+//封装组件
+class Index extends Component {
+ componentDidMount(){
+ const { login,dispatch } = this.props;
+ //检测是否登录
+ dispatch(authLoginStatus(login.status,true));
+ }
+ render() {
+ const { login,dispatch } = this.props;
+ var LoginBlock=dispatch(loginPost(data)) } status={login.status} msg={login.msg} />;
+ return (
+
+
这是一个测试程序,当前登录状态 {login.status}
+ {LoginBlock}
+
+ );
+ }
+}
+export default connect(propMap)(Index);
\ No newline at end of file
diff --git a/client/pages/layout.js b/client/pages/layout.js
new file mode 100644
index 0000000..8ba84ba
--- /dev/null
+++ b/client/pages/layout.js
@@ -0,0 +1,18 @@
+/**
+ * Page - layout
+ * 框架
+ */
+import React, { Component } from 'react';
+
+//封装组件
+class Layout extends Component {
+ render() {
+ return (
+
+
这是一个APP
+ {this.props.children}
+
+ );
+ }
+}
+export default Layout;
\ No newline at end of file
diff --git a/client/reducers/index.js b/client/reducers/index.js
new file mode 100644
index 0000000..cff9967
--- /dev/null
+++ b/client/reducers/index.js
@@ -0,0 +1,26 @@
+/**
+ * Reducer - index
+ * 汇总
+ */
+//加载reducers
+import login from './login';
+
+import { combineReducers } from 'redux';
+import { routeReducer } from 'react-router-redux';
+
+/**
+ * 组织注入的object
+ * @param {object} state state数据
+ * @return {object} 返回的prop数据
+ */
+export function propMap(state){
+ return {
+ routing:state.routing,
+ login:state.login
+ }
+}
+
+export const reducer = combineReducers({
+ login,
+ routing: routeReducer
+})
\ No newline at end of file
diff --git a/client/reducers/login.js b/client/reducers/login.js
new file mode 100644
index 0000000..65c36e8
--- /dev/null
+++ b/client/reducers/login.js
@@ -0,0 +1,55 @@
+/**
+ * Reducer - login
+ * 登录
+ * 生成变量:login
+ */
+//导入action常量
+import { LOGIN_REQUEST, LOGIN_RECEIVE, LOGIN_ERROR, LOGOUT_REQUEST, LOGOUT_RECEIVE, LOGOUT_ERROR,LOGIN_STATUS_OUT,LOGIN_STATUS_IN,LOGIN_STATUS_LOAD,LOGIN_STATUS_ERROR } from '../actions/login';
+
+//初始化state
+let init_state = {
+ status: LOGIN_STATUS_OUT,
+ info: {
+ name: '',
+ role: ''
+ },
+ msg: ''
+}
+
+export default function login(state = init_state, action) {
+ switch (action.type) {
+ case LOGIN_REQUEST:
+ return Object.assign({}, state, {
+ status: LOGIN_STATUS_LOAD
+ });
+ break;
+ case LOGIN_RECEIVE:
+ return Object.assign({}, state, {
+ status: LOGIN_STATUS_IN,
+ info: action.info
+ });
+ break;
+ case LOGIN_ERROR:
+ return Object.assign({}, state, {
+ status: LOGIN_STATUS_ERROR,
+ msg: action.msg
+ });
+ break;
+ case LOGOUT_REQUEST:
+ return Object.assign({}, state, {
+ status: LOGIN_STATUS_LOAD
+ });
+ break;
+ case LOGOUT_RECEIVE:
+ return Object.assign({}, state, init_state);
+ break;
+ case LOGOUT_ERROR:
+ return Object.assign({}, state, {
+ status:LOGIN_STATUS_ERROR,
+ msg: action.msg
+ });
+ break;
+ default:
+ return state;
+ }
+}
diff --git a/client/router.js b/client/router.js
deleted file mode 100644
index de11bdc..0000000
--- a/client/router.js
+++ /dev/null
@@ -1,3 +0,0 @@
-/**
- * router components
- */
\ No newline at end of file
diff --git a/package.json b/package.json
index 93ad0f1..e566f91 100644
--- a/package.json
+++ b/package.json
@@ -1,26 +1,30 @@
{
- "name": "react-test",
- "version": "1.0.0",
- "description": "react test",
- "main": "webpack.config.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "author": "",
- "license": "ISC",
- "devDependencies": {
- "babel-core": "^6.5.2",
- "babel-loader": "^6.2.3",
- "babel-preset-es2015": "^6.5.0",
- "babel-preset-react": "^6.5.0",
- "react": "^0.14.7",
- "react-dom": "^0.14.7",
- "react-redux": "^4.4.0",
- "react-router": "^2.0.0",
- "redux": "^3.3.1",
- "webpack": "^1.12.13"
- },
- "dependencies": {
- "redux-thunk": "^1.0.3"
- }
+ "name": "react-test",
+ "version": "1.0.0",
+ "description": "react test",
+ "main": "webpack.config.js",
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "author": "bakajinsei",
+ "license": "ISC",
+ "dependencies": {
+ "ejs-mate": "^2.3.0",
+ "express": "~4.11.1"
+ },
+ "devDependencies": {
+ "babel-core": "^6.5.2",
+ "babel-loader": "^6.2.3",
+ "babel-preset-es2015": "^6.5.0",
+ "babel-preset-react": "^6.5.0",
+ "isomorphic-fetch": "^2.2.1",
+ "react": "^0.14.7",
+ "react-dom": "^0.14.7",
+ "react-redux": "^4.4.0",
+ "react-router": "^2.0.0",
+ "react-router-redux": "^3.0.0",
+ "redux": "^3.3.1",
+ "redux-thunk": "^1.0.3",
+ "webpack": "^1.12.13"
+ }
}
diff --git a/public/api/login.json b/public/api/login.json
new file mode 100644
index 0000000..b0c0dbd
--- /dev/null
+++ b/public/api/login.json
@@ -0,0 +1,10 @@
+{
+ "head":{
+ "status":200,
+ "msg":"登录成功"
+ },
+ "body":{
+ "name":"test",
+ "role":"管理员"
+ }
+}
\ No newline at end of file
diff --git a/public/api/loginout.json b/public/api/loginout.json
new file mode 100644
index 0000000..097b1b8
--- /dev/null
+++ b/public/api/loginout.json
@@ -0,0 +1,7 @@
+{
+ "head":{
+ "status":200,
+ "msg":"登出成功"
+ },
+ "body":null
+}
\ No newline at end of file
diff --git a/views/error.html b/views/error.html
new file mode 100644
index 0000000..45bb282
--- /dev/null
+++ b/views/error.html
@@ -0,0 +1,7 @@
+
+
+React Test
+
+