Commit ab0e2a91 by bert

v1

parent eaae6004
# react-redux-antd-ie8
## 特性
* [react](https://github.com/facebook/react)
* [redux](https://github.com/rackt/redux)
* [react-router](https://github.com/rackt/react-router)
* [react-router-redux](https://github.com/rackt/react-router-redux)
* [webpack](https://github.com/webpack/webpack)
* [babel](https://github.com/babel/babel)
* [antd](http://ant.design)
## Code Style
https://github.com/airbnb/javascript
## `npm` 命令
|`npm run <script>`|解释|
|------------------|-----------|
|`dev`|服务启动在 `http://127.0.0.1:8989`,代码热替换开启。|
|`build`|构建项目|
#### `npm run build` 构建文件正常显示:
![Alt text](https://github.com/bertFu/react-redux-antd-ie8/blob/master/src/constants/build.jpg)
## 程序目录
```
.
├── build # 所有打包配置项
├── src # 程序源文件
│ ├── api # superagent 处理,基于node的Ajax组件
│ ├── components # 可复用的直观组件(Presentational Components)
│ ├── constants # 常量管理
│ ├── entries # 主页入口
│ │ ├── index.htlm # 主入口 HTML 文件
│ │ ├── index.js # 主要 JS 文件
│ │ └── index.less # 主入口 css 文件
│ ├── feature # 配置json文件
│ ├── routes # 主路由和异步分割点
│ │ └── index.js # 用store启动主程序路由
│ ├── services # 模拟服务
│ ├── store # Redux指定块
│ │ ├── middlewares # 中间件管理
│ │ ├── modules # reducers/actions的集合
│ │ │ └── menu # 根据规则定制管理目录
│ │ │ ├── menu_action.js # Action管理
│ │ │ ├── menu_reducer.js # Reducer管理
│ │ │ └── menu_state.js # State管理
│ │ ├── createStore.js # 创建和使用redux store
│ │ ├── reducers.js # Reducer注册和注入
│ │ └── types.js # 管理 Action、Reducer 公用的 types
│ ├── util # 工具包
│ └── views # 业务页面管理
│ └── Home # 不规则路由
│ ├── index.js # 路由定义和代码异步分割
│ ├── index.less # 路由定义和代码异步分割
│ └── components # 直观React组件
└── tests # 单元测试
```
## 兼容性
[antd 官方介绍](http://ant.design/docs/react/getting-started#兼容性)
......@@ -62,21 +121,10 @@ node >= 4
+ "fetch-ie8": "^1.4.0",
+ "object-assign": "^4.0.1",
```
## 总结
- 该项目用于测试i8性能,可以当做脚手架使用
- 需要的朋友可以 `star`
- 持续更新
## Code Style
https://github.com/airbnb/javascript
## Develop
```
npm run dev
```
访问 http://127.0.0.1:8989
## Build
```
npm run build
```
Thank you for your attention
......@@ -24,4 +24,14 @@ module.exports = {
});
}, 500);
},
'/api/login': function(req, res) {
setTimeout(function() {
res.json({
success: true,
data: {
user: 'admin'
},
});
}, 500);
}
};
import * as types from '../constants/actionTypes'
export function addTodo(text) {
return { type: types.ADD_TODO, text }
}
export function completeTodo(id) {
return { type: types.COMPLETE_TODO, id }
}
import React, { Component, PropTypes } from 'react';
import Todos from './Todos/Todos';
import MainLayout from '../layouts/MainLayout/MainLayout';
const App = ({ location }) => {
return (
<MainLayout>
<Todos location={location} />
</MainLayout>
);
};
App.propTypes = {
};
export default App;
import React from 'react'
import { bindActionCreators } from 'redux'
import reqwest from 'reqwest'
import md5 from 'md5'
import { connect } from 'react-redux'
import { Button, Modal, Form, Input, Row, Col, DatePicker, Radio, Select, Cascader } from 'antd';
import * as Util from '../../util';
const FormItem = Form.Item;
const RangePicker = DatePicker.RangePicker;
const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;
const Option = Select.Option;
const appID = window.globalConfig.appid;
const token = window.globalConfig.token;
const ts = new Date().getTime().toString().substring(0,10);
const sign = md5(appID+ts+token+'false');
const agUrl = window.globalConfig.url;
import './index.less'
let ExportExcel = React.createClass({
getInitialState() {
return { visible: false };
},
componentDidMount() {
},
showModal() {
this.setState({
visible: true,
});
reqwest({
url: agUrl + 'case2/type',
method: 'get',
type: 'json',
data: [
{ name: 'sign', value: sign },
{ name: 'ts', value: ts },
{ name: 'appID', value: appID } ]
}).then (function(data) {
console.log('this.state', this.state);
if (this.isMounted()) {
this.setState(Object.assign({}, this.state, {typeList: data.content}));
}
}.bind(this))
},
reset() {
this.props.form.resetFields();
},
handleOk(e) {
let subResult = this.props.form.getFieldsValue();
// subResult.startTime = subResult.endTime? this.dataFormat(subResult.startTime) : subResult.endTime;
// subResult.endTime = subResult.endTime? this.dataFormat(subResult.endTime) : subResult.endTime;
subResult.startTime = subResult.startTime && Util.dateFormat(subResult.startTime, 'yyyy-MM-dd hh:mm:ss')
subResult.endTime = subResult.endTime && Util.dateFormat(subResult.endTime, 'yyyy-MM-dd hh:mm:ss')
console.log('getFormatter', subResult.endTime);
const url = "/download?"
+ 'keyword='+ encodeURI(subResult.keyword)
+ '&taskType=' + subResult.taskType
+ '&status=' + subResult.status
+ '&searchType=' + subResult.searchType
+ '&startTime=' + subResult.startTime
+ '&endTime=' + subResult.endTime
// + '&sortType=' + subResult.sortType
+ '&sourceType=' + subResult.sourceType
+ '&sourceUser=' + encodeURI(subResult.sourceUser)
+ '&isTimeout=' + subResult.isTimeout;
console.log('url', url);
window.open(url)
this.setState({
visible: false,
});
},
dataFormat(strTime) {
var date = new Date(strTime);
// return date.getFullYear()+"-"+(date.getMonth()+1)+"-"+date.getDate()+" "+date.getHours()+":"+date.getMinutes()+":"+date.getSeconds();
return Util.dateFormat(date, 'yyyy-MM-dd hh:mm:ss')
},
handleCancel() {
this.setState({
visible: false,
});
},
timeChange(value, dateString) {
console.log('From: ', value[0], ', to: ', value[1]);
console.log('From: ', dateString[0], ', to: ', dateString[1]);
},
userTypeChange(e) {
console.log(`userTypeChange checked:${e.target.value}`);
},
statusChange(e) {
console.log(`statusChange checked:${e.target.value}`);
},
render() {
const { getFieldProps } = this.props.form;
const taskType = this.state.typeList?this.state.typeList.map( (type) => {
return (<RadioButton value={type.id}>{type.case_type}</RadioButton>)
}) : "";
const selectBefore = (
<Select {...getFieldProps('sourceType',{initialValue: 'createUser'})} defaultValue="createUser" style={{ width: 80 }}>
<Option value="createUser">发布人</Option>
<Option value="executorUser">执行人</Option>
<Option value="focusUser">关注人</Option>
</Select>
);
return (
<div>
<Button type="primary" onClick={this.showModal}>Excel导出</Button>
<Modal title="Excel导出" visible={this.state.visible}
width={800}
onOk={this.handleOk} onCancel={this.handleCancel}
okText="确定导出" cancelText="取消"
>
<div>
<Form horizontal className="ant-advanced-search-form">
<Row>
<Col span={24}>
<FormItem
label="关键字"
labelCol={{ span: 3 }}
wrapperCol={{ span: 8 }}
>
<Input {...getFieldProps('keyword', {initialValue: ''})} placeholder="请输入关键字" size="default" />
</FormItem>
</Col>
<Col span={24}>
<FormItem
label="创建时间区间"
labelCol={{ span: 3 }}
>
<Col span="4">
<Select {...getFieldProps('searchType',{initialValue: 'createTime'})} defaultValue="createTime" style={{ width: 110, marginRight: 10 }}>
<Option value="createTime">创建时间区间</Option>
<Option value="finishTime">完成时间区间</Option>
<Option value="confirmFinishTime">确认完成时间区间</Option>
<Option value="closeTime">关闭时间区间</Option>
</Select>
</Col>
<Col span="6">
<FormItem>
<DatePicker format="yyyy-MM-dd HH:mm:ss" showTime {...getFieldProps('startTime', {initialValue: ''})} placeholder="开始时间" />
</FormItem>
</Col>
<Col span="1">
<p className="ant-form-split">-</p>
</Col>
<Col span="6">
<FormItem>
<DatePicker format="yyyy-MM-dd HH:mm:ss" showTime {...getFieldProps('endTime', {initialValue: ''})} placeholder="结束时间" />
</FormItem>
</Col>
</FormItem>
</Col>
<Col span={24}>
<FormItem
label="人员类型"
labelCol={{ span: 3 }}
wrapperCol={{ span: 8 }}
>
<Input {...getFieldProps('sourceUser',{initialValue: ''})} addonBefore={selectBefore} placeholder="请输入人员姓名" />
</FormItem>
</Col>
<Col span={24}>
<FormItem
label="任务进度"
labelCol={{ span: 3 }}
wrapperCol={{ span: 14 }}
>
<RadioGroup {...getFieldProps('isTimeout',{initialValue: ''})}>
<RadioButton value="">全部</RadioButton>
<RadioButton value="0">已超时</RadioButton>
<RadioButton value="1">未超时</RadioButton>
</RadioGroup>
</FormItem>
</Col>
<Col span={24}>
<FormItem
label="请选择状态"
labelCol={{ span: 3 }}
wrapperCol={{ span: 14 }}
>
<RadioGroup {...getFieldProps('status',{initialValue: ''})}>
<RadioButton value="">全部</RadioButton>
<RadioButton value="1">进行中</RadioButton>
<RadioButton value="2">已完成</RadioButton>
<RadioButton value="3">确认完成</RadioButton>
<RadioButton value="4">已关闭</RadioButton>
</RadioGroup>
</FormItem>
</Col>
<Col span={24}>
<FormItem
label="选择case类型"
labelCol={{ span: 3 }}
wrapperCol={{ span: 14 }}
>
<RadioGroup {...getFieldProps('taskType',{initialValue: ''})} >
<RadioButton value="">全部</RadioButton>
{taskType}
</RadioGroup>
</FormItem>
</Col>
</Row>
<Row>
<Col span={12} offset={12} style={{ textAlign: 'right' }}>
<Button onClick={this.reset}>请除条件</Button>
</Col>
</Row>
</Form>
</div>
</Modal>
</div>
);
}
});
ExportExcel = Form.create()(ExportExcel);
function mapStateToProps(state) {
return {
}
}
function mapDispatchToProps(dispatch) {
return {
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ExportExcel);
.ant-advanced-search-form {
padding: 16px 8px;
background: #f8f8f8;
border: 1px solid #d9d9d9;
border-radius: 6px;
}
/* 由于输入标签长度不确定,所以需要微调使之看上去居中 */
.ant-advanced-search-form > .ant-row {
position: relative;
left: -6px;
}
.ant-advanced-search-form .ant-btn + .ant-btn {
margin-left: 8px;
}
......@@ -90,7 +90,7 @@ class Sidebar extends React.Component {
return (
<aside className={styles["ant-layout-sider"]}>
<div className={styles["ant-layout-logo"]}><img src={logo} /><span className={styles["nav-text"]}>催费跟踪后台</span></div>
<div className={styles["ant-layout-logo"]}><img src={logo} /><span className={styles["nav-text"]}>xxxx后台</span></div>
<div className={styles["ant-layout-portrait"]}>
<div className={styles["nav-portrait"]}><img src={touxiang} /></div>
<div className={styles["nav-portrait-name"]}>admin</div>
......
import React, {PropTypes} from 'react';
import { Icon } from 'antd';
import './index.less'
const defaultProps = {
}
const propTypes = {
}
class Star extends React.Component {
constructor() {
super()
}
handleClick(event, onClick) {
event.preventDefault();
event.stopPropagation();
onClick();
}
render() {
const { onClick, style, star } = this.props;
return (
<span
style={style}
className="pull-right star-color"
onClick = {(event) => {this.handleClick(event, onClick)}}>
<Icon type={star ? "star" : "star-o"} />
</span>
)
}
}
Star.propTypes = propTypes;
Star.defaultProps = defaultProps;
export default Star;
\ No newline at end of file
.tree-select .star-color {
display: none;
}
.star-color {
color: #2db7f5;
}
\ No newline at end of file
import React, { Component, PropTypes } from 'react';
import classnames from 'classnames';
import styles from './Todo.less';
const Todo = ({ data, onToggleComplete }) => {
const { text, isComplete } = data;
const todoCls = classnames({
[styles.normal]: true,
[styles.isComplete]: isComplete,
});
return (
<div className={todoCls}>
<div className={styles.checkbox}>
<input
type="checkbox"
value=""
checked={isComplete}
onChange={onToggleComplete.bind(this)}
/>
</div>
<div className={styles.text}>
{text}
</div>
</div>
);
};
Todo.propTypes = {
data: PropTypes.object.isRequired,
onToggleComplete: PropTypes.func.isRequired,
};
export default Todo;
.normal {
display: flex;
}
.checkbox {
margin-right: 6px;
}
.isComplete {
color: #ccc;
text-decoration: line-through;
}
import React, { Component, PropTypes } from 'react';
import { Spin } from 'antd';
import Todo from './Todo';
import styles from './Todos.less';
import { getAll } from '../../services/todos';
class TodosContainer extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
loading: false,
};
}
loadTodos() {
this.setState({ loading: true });
getAll().then(({ jsonResult }) => {
this.setState({
list: jsonResult.data,
loading: false,
});
})
}
handleToggleComplete = (id) => {
const newList = this.state.list.map(todo => {
if (id === todo.id) {
return { ...todo, isComplete: !todo.isComplete };
} else {
return todo;
}
});
this.setState({
list: newList,
});
}
componentDidMount() {
this.loadTodos();
}
render() {
const { location } = this.props;
const { list, loading } = this.state;
const todos = filter({ list, loading }, location.pathname);
return (
<Todos todos={todos} onToggleComplete={this.handleToggleComplete} />
);
}
}
const Todos = ({ todos, onToggleComplete }) => {
const renderList = () => {
const { list, loading } = todos;
if (loading) {
return <Spin />;
}
return (
<div className={styles.list}>
{list.map(item => <Todo
key={item.id}
data={item}
onToggleComplete={onToggleComplete.bind(this, item.id)}
/>
)}
</div>
);
};
return (
<div className={styles.normal}>
{renderList()}
</div>
);
};
Todos.propTypes = {};
function filter(todos, pathname) {
const newList = todos.list.filter(todo => {
if (pathname === '/actived') {
return !todo.isComplete;
}
if (pathname === '/completed') {
return todo.isComplete;
}
return true;
});
return { ...todos, list: newList };
}
export default TodosContainer;
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import Header from '../components/Header';
import Content from '../components/Content';
import * as Actions from '../actions';
class App extends Component{
render(){
const {todos, actions} = this.props;
return (
<div>
<Header actions={actions} />
<Content todos={todos} actions={actions} />
</div>
);
}
}
function mapStateToProps(state){
return {
todos : state.todos
}
}
function mapDispatchToProps(dispatch){
return{
actions : bindActionCreators(Actions, dispatch)
}
}
module.exports = connect(
mapStateToProps,
mapDispatchToProps
)(App);
export const ADD_TODO = 'ADD_TODO'
export const DELETE_TODO = 'DELETE_TODO'
export const EDIT_TODO = 'EDIT_TODO'
export const COMPLETE_TODO = 'COMPLETE_TODO'
export const COMPLETE_ALL = 'COMPLETE_ALL'
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'
......@@ -3,7 +3,6 @@ import './index.less';
import ReactDOM from 'react-dom';
import React from 'react';
import { browserHistory } from 'react-router';
import App from '../components/App';
import Routes from '../routes/index';
const Provider = require('react-redux').Provider;
const configureStore = require('../store/configureStore');
......
import React, { Component, PropTypes } from 'react';
import { Router, Route, IndexRoute, Link } from 'react-router';
import styles from './MainLayout.less';
const MainLayout = ({ children }) => {
return (
<div className={styles.normal}>
<div className={styles.head}>
<h1>Todo App</h1>
</div>
<div className={styles.content}>
<div className={styles.side}>
<h2>Filters:</h2>
<Link to="/">All</Link><br />
<Link to="/actived">Actived</Link><br />
<Link to="/completed">Completed</Link><br />
<Link to="/404">404</Link><br />
</div>
<div className={styles.main}>
{children}
</div>
</div>
<div className={styles.foot}>
Built with react, react-router, ant-tool, css-modules, antd...
</div>
</div>
);
};
MainLayout.propTypes = {
children: PropTypes.element.isRequired,
};
export default MainLayout;
.normal {
display: flex;
flex-direction: column;
height: 100%;
}
.head {
margin-bottom: 20px;
background: cadetblue;
height: 80px;
padding: 8px;
color: #fff;
}
.content {
flex: 1;
display: flex;
}
.side {
padding: 8px;
width: 20%;
min-width: 200px;
background: #fafafa;
margin-right: 20px;
}
.main {
padding: 8px;
flex: 1 0 auto;
}
.foot {
margin-top: 20px;
background: greenyellow;
padding: 8px;
}
import {combineReducers} from 'redux';
import todos from './todos';
const rootReducer = combineReducers({
todos
});
export default rootReducer;
import * as Types from '../constants/actionTypes';
import * as Actions from '../actions';
import objectAssign from 'object-assign';
const initialState = [{
text : 'Hello Ivan!',
completed : false,
id : 1
}]
export default function todos(state = initialState, action){
switch(action.type){
case Types.ADD_TODO:
return [
...state,
{
id : state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
completed : false,
text : action.text
}
];
case Types.COMPLETE_TODO:
return state.map(todo =>
todo.id === action.id ? objectAssign({}, todo, {completed : !todo.completed}) : todo
);
default:
return state;
}
}
\ No newline at end of file
import React, { PropTypes } from 'react';
import { Router, Route, IndexRoute, Link } from 'react-router';
import App from '../components/App';
import Appp from '../views/App';
import { Router, Route, IndexRoute, Link, IndexRedirect } from 'react-router';
import App from '../views/App';
import Home from '../views/Home';
import Login from '../views/Login';
import Test from '../views/Test';
import NotFound from '../components/NotFound';
function validate() {
// 在路由群载入时做 filter 处理
}
const Routes = ({ history }) =>
<Router history={history}>
<Route path="/" component={App} />
<Route path="/actived" component={App} />
<Route path="/login" component={Login} />
<Route component={Appp}>
<Route path="home" component={Home}/>
<Route path="app" component={App}/>
<Route path="test" component={Test}/>
<Route path="/" onEnter={validate}>
<IndexRedirect to="home" />
<Route component={App}>
<Route path="home" component={Home}/>
<Route path="app" component={App}/>
<Route path="test" component={Test}/>
</Route>
</Route>
<Route path="*" component={NotFound}/>
</Router>;
......
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
import rootReducer from '../reducers'
export default function configureStore(initialState) {
const store = createStore(
rootReducer,
initialState,
applyMiddleware(thunkMiddleware, createLogger())
// applyMiddleware(thunkMiddleware)
)
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextReducer = require('../reducers').default
store.replaceReducer(nextReducer)
})
}
return store
}
/**
* 统一管理 `Action` 将 `Action` 中的方法打包提供给 `View` 使用
*
* @connect(
* state => ({...state}),
* dispatch => bindActionCreators(Action, dispatch)
* )
*
* 将逻辑代码统一放在 `Action` 层,这样有利于逻辑重用,且在修改逻辑代码时,不需要改动 `View` 层
*/
import * as leftMenuAction from './modules/leftMenu/left_menu_action' // 左侧菜单
import * as topMenuAction from './modules/topMenu/top_menu_action' // 顶部菜单
import * as userAction from './modules/user/user_action' // 用户
export default {
// ...leftMenuAction,
// ...topMenuAction,
// ...userAction,
}
import {createStore, applyMiddleware, combineReducers} from 'redux';
import thunkMiddleware from 'redux-thunk';
import promiseMiddleware from './middlewares/promiseMiddleware'
import createLogger from 'redux-logger'
import { browserHistory } from 'react-router'
import { routerMiddleware, push } from 'react-router-redux'
const loggerMiddleware = createLogger({
level: 'info',
collapsed: true
})
//使用redux的combineReducers方法将所有reducer打包起来
import reducer from './reducers'
import initState from './initState'
/**
* 一、createStore(reducer, [initialState])
*
* 参数:
* 1、 reducer (Function): 接收两个参数,分别是当前的 state 树和要处理的 action,返回新的 state 树。
* 2、 [initialState] (any): 初始时的 state。
* 在同构应用中,你可以决定是否把服务端传来的 state 水合(hydrate)后传给它,或者从之前保存的用户会话中恢复一个传给它。
* 如果你使用 combineReducers 创建 reducer,它必须是一个普通对象,与传入的 keys 保持同样的结构。否则,你可以自由传入任何 reducer 可理解的内容。
*
* 返回值:
* (Store): 保存了应用所有 state 的对象。改变 state 的惟一方法是 dispatch action。你也可以 subscribe 监听 state 的变化,然后更新 UI。
*
*
*
* applyMiddleware(...middlewares)
*
* desc:
* 使用包含自定义功能的 middleware 来扩展 Redux
* Middleware 并不需要和 createStore 绑在一起使用,也不是 Redux 架构的基础组成部分,但它带来的益处让我们认为有必要在 Redux 核心中包含对它的支持。因此,虽然不同的 middleware 可能在易用性和用法上有所不同
*
* 参数:
* ...middlewares (arguments): 遵循 Redux middleware API 的函数。
* 每个 middleware 接受 Store 的 dispatch 和 getState 函数作为命名参数,并返回一个函数。
* 该函数会被传入 被称为 next 的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,这个函数可以直接调用 next(action),或者在其他需要的时刻调用,甚至根本不去调用它。
* 调用链中最后一个 middleware 会接受真实的 store 的 dispatch 方法作为 next 参数,并借此结束调用链。
* 所以,middleware 的函数签名是 ({ getState, dispatch }) => next => action。
*
* 返回值:
* (Function) 一个应用了 middleware 后的 store enhancer。
* 这个 store enhancer 就是一个函数,并且需要应用到 createStore。它会返回一个应用了 middleware 的新的 createStore。
*/
const createStoreWithMiddleware = applyMiddleware(
// `thunk` 中间件由 `Redux` 提供。作用是使action创建函数可以返回一个function代替一个action对象
thunkMiddleware,
// `applyMiddleware` 来自 `Redux` 可以包装 `Store` 的 `dispatch`, 将 `promise`
promiseMiddleware({ promiseTypeSuffixes: ['PENDING', 'SUCCESS', 'ERROR'] }),
// 在控制台里能看到 redux-logger 中间件输出的 action 日志,它们清晰地反映了业务逻辑是怎样的 。如果有其他人在编辑 todolist,基于 websocket 服务端推送技术的支持,你也可以直接看到别人的操作过程。
// loggerMiddleware,
routerMiddleware(browserHistory)
)(createStore);
const store = createStoreWithMiddleware(reducer, initState)
export default store
// // 默认初始化store
// export default function configureStore(initialState) {
// return createStoreWithMiddleware(reducer, initialState);
// }
/**
* States
*
*/
import * as MenuState from './modules/menu/menu_state' // 顶部菜单
import * as userState from './modules/user/user_state' // 用户
/**
* 初始化states
*
* @return {[type]} [description]
*/
const initialState = {
// menu : new MenuState,
// user : new userState,
}
export default initialState
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment