├── src ├── views │ ├── Demo │ │ ├── index.less │ │ ├── index.js │ │ ├── Root.js │ │ └── CommentList.js │ ├── Test │ │ ├── index.less │ │ └── index.js │ ├── Home │ │ ├── index.less │ │ └── index.js │ ├── Login │ │ ├── index.less │ │ └── index.js │ └── App │ │ ├── index.js │ │ └── index.less ├── constants │ ├── KeyCode.js │ ├── logo.jpg │ ├── build.jpg │ ├── ProjectConf.js │ ├── ResCode.js │ └── index.js ├── components │ ├── NavPath │ │ ├── index.less │ │ └── index.js │ ├── Header │ │ ├── logo.png │ │ ├── index.less │ │ └── index.js │ ├── Sidebar │ │ ├── logo.jpg │ │ ├── touxiang.jpg │ │ ├── index.less │ │ └── index.js │ ├── PanelBox │ │ ├── index.less │ │ └── index.js │ ├── Footer │ │ ├── index.less │ │ └── index.js │ ├── NotFound.less │ ├── NotFound.jsx │ ├── UpdateForm.jsx │ ├── BDUploader.jsx │ ├── CreateForm.jsx │ ├── Loding │ │ ├── index.less │ │ └── index.js │ ├── RetrieveForm.jsx │ ├── CreateFormItem.jsx │ └── FeatureSetConfig.jsx ├── entries │ ├── index.less │ ├── index.js │ └── index.html ├── services │ ├── todos.js │ └── xFetch.js ├── api │ ├── index.js │ └── api.js ├── store │ ├── modules │ │ ├── user │ │ │ ├── user_state.js │ │ │ ├── user_action.js │ │ │ └── user_reducer.js │ │ └── menu │ │ │ ├── menu_state.js │ │ │ ├── menu_action.js │ │ │ └── menu_reducer.js │ ├── types.js │ ├── configureStore.js │ ├── reducers.js │ └── middlewares │ │ └── promiseMiddleware.js ├── feature │ ├── topMenu.js │ ├── leftMenu.js │ ├── Feature7.jsx │ ├── Feature3-1.jsx │ ├── Feature1-2.jsx │ ├── Feature2-3.jsx │ ├── Feature2.jsx │ ├── Feature1-1.jsx │ ├── Feature1-3.jsx │ ├── Feature1.jsx │ ├── Feature5.jsx │ ├── Feature3.jsx │ ├── Feature2-1.jsx │ ├── Feature2-2.jsx │ ├── Feature1-4.jsx │ ├── Feature4.jsx │ └── Feature6.jsx ├── routes │ └── index.js ├── util │ ├── promise.js │ ├── util.js │ ├── index.js │ └── templateUtil.js └── common │ ├── util.js │ └── config.js ├── .gitignore ├── .babelrc ├── CHANGELOG.md ├── test ├── test_helper.js ├── helloWorld.spec.js └── containers │ ├── Root.spec.js │ └── CommentList.spec.js ├── .editorconfig ├── .eslintrc ├── proxy.config.js ├── webpack.config.js ├── package.json └── README.md /src/views/Demo/index.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/Test/index.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .DS_Store 4 | 5 | 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "stage-0", "es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 2 | `2016.07.23` 3 | - 提供登入功能 4 | - 提供主页框架与简单展示 -------------------------------------------------------------------------------- /src/constants/KeyCode.js: -------------------------------------------------------------------------------- 1 | export const KEY_ENTER = 13; 2 | export const KEY_ESCAPE = 27; 3 | -------------------------------------------------------------------------------- /src/components/NavPath/index.less: -------------------------------------------------------------------------------- 1 | .ant-layout-breadcrumb { 2 | margin: 7px 0 -17px 24px; 3 | } 4 | -------------------------------------------------------------------------------- /src/constants/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertFu/react-redux-antd-ie8/HEAD/src/constants/logo.jpg -------------------------------------------------------------------------------- /src/constants/build.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertFu/react-redux-antd-ie8/HEAD/src/constants/build.jpg -------------------------------------------------------------------------------- /src/components/Header/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertFu/react-redux-antd-ie8/HEAD/src/components/Header/logo.png -------------------------------------------------------------------------------- /src/components/Sidebar/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertFu/react-redux-antd-ie8/HEAD/src/components/Sidebar/logo.jpg -------------------------------------------------------------------------------- /src/components/Sidebar/touxiang.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertFu/react-redux-antd-ie8/HEAD/src/components/Sidebar/touxiang.jpg -------------------------------------------------------------------------------- /src/constants/ProjectConf.js: -------------------------------------------------------------------------------- 1 | export const PAGER = { 2 | pageId : 1, 3 | recPerPage : 10, 4 | total : 0 5 | } -------------------------------------------------------------------------------- /src/constants/ResCode.js: -------------------------------------------------------------------------------- 1 | import types from '../store/types' 2 | 3 | export default { 4 | '408': types.NEED_LOGIN // 需要登录 5 | }; 6 | -------------------------------------------------------------------------------- /test/test_helper.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import sinon from 'sinon'; 3 | 4 | global.expect = expect; 5 | global.sinon = sinon; -------------------------------------------------------------------------------- /src/views/Home/index.less: -------------------------------------------------------------------------------- 1 | .home-row { 2 | margin-bottom: 20px; 3 | } 4 | .home-title-x { 5 | text-align: center; 6 | line-height: 30px; 7 | } 8 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | export const IMAGE_HOST_TEST = 'http://res.ikaibei.com/uploadtest/' 2 | export const IMAGE_HOST_PROD = 'http://res.ikaibei.com/upload/' 3 | -------------------------------------------------------------------------------- /src/entries/index.less: -------------------------------------------------------------------------------- 1 | 2 | :global { 3 | html, body, #root { 4 | height: 100%; 5 | } 6 | body { 7 | background: #fafafa; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/helloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | describe('hello world', () => { 4 | it('works!', () => { 5 | expect(true).to.be.true; 6 | }); 7 | }); -------------------------------------------------------------------------------- /src/components/PanelBox/index.less: -------------------------------------------------------------------------------- 1 | .panel-box { 2 | margin-bottom: 20px; 3 | > .ant-collapse-item { 4 | > .ant-collapse-header { 5 | padding-left: 10px; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/services/todos.js: -------------------------------------------------------------------------------- 1 | import xFetch from './xFetch'; 2 | 3 | export async function getAll() { 4 | return xFetch('/api/todos'); 5 | } 6 | 7 | export async function getLogin() { 8 | return xFetch('/api/login'); 9 | } -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import Api from './api'; 2 | 3 | const api = new Api({ 4 | baseURI: '/', 5 | headers: { 6 | 'Accept': 'application/json', 7 | 'Content-Type': 'application/json' 8 | } 9 | }) 10 | 11 | export default api 12 | -------------------------------------------------------------------------------- /src/views/Demo/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { render } from 'react-dom'; 3 | 4 | class Root extends Component { 5 | render() { 6 | return

Hello World

; 7 | } 8 | } 9 | 10 | export default Root; -------------------------------------------------------------------------------- /src/components/Footer/index.less: -------------------------------------------------------------------------------- 1 | .ant-layout-footer { 2 | height: 64px; 3 | line-height: 64px; 4 | text-align: center; 5 | font-size: 12px; 6 | color: #999; 7 | background: #fff; 8 | border-top: 1px solid #e9e9e9; 9 | width: 100%; 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | 18 | -------------------------------------------------------------------------------- /src/store/modules/user/user_state.js: -------------------------------------------------------------------------------- 1 | // import { Record, Map } from 'immutable'; 2 | 3 | /** 4 | * immutable 确定操作的对象是不可变的,更好的维护性能 5 | * 具体说明在 `immutable.md` 文件中 6 | */ 7 | 8 | const InitState = { 9 | user: null, 10 | loggingIn: false, 11 | loggingOut: false, 12 | loginErrors: null 13 | } 14 | 15 | export default InitState 16 | -------------------------------------------------------------------------------- /src/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import styles from './index.less' 4 | 5 | export default class Footer extends React.Component { 6 | constructor () { 7 | super() 8 | } 9 | 10 | render () { 11 | 12 | return ( 13 |
14 | xxxx 版权所有 © 2015 xxxxxx.com 15 |
16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/views/Demo/Root.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | const styles = { 4 | height: '100%', 5 | background: '#333' 6 | } 7 | 8 | class Root extends Component { 9 | render() { 10 | return ( 11 |
12 |

Welcome to testing React!

13 |
14 | ) 15 | } 16 | } 17 | 18 | export default Root; -------------------------------------------------------------------------------- /src/feature/topMenu.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | key: 'home', 4 | name: 'Home', 5 | icon: 'book' 6 | }, 7 | { 8 | key: 'test', 9 | name: 'Test', 10 | icon: 'user' 11 | }, 12 | { 13 | key: 'loding', 14 | name: '加载效果', 15 | icon: 'smile-circle' 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /src/components/NotFound.less: -------------------------------------------------------------------------------- 1 | .normal { 2 | width: 100%; 3 | height: 100%; 4 | min-height: 100vh; 5 | padding-top: 120px; 6 | } 7 | 8 | .container { 9 | padding: 0; 10 | margin: 0 auto; 11 | width: 620px; 12 | height: 300px; 13 | text-align: center; 14 | } 15 | 16 | .title { 17 | font-size: 80px; 18 | color: #666; 19 | margin-top: 20px; 20 | margin-bottom: 10px; 21 | } 22 | 23 | .desc { 24 | font-size: 14px; 25 | } 26 | -------------------------------------------------------------------------------- /src/store/modules/menu/menu_state.js: -------------------------------------------------------------------------------- 1 | // import { Record, Map } from 'immutable'; 2 | 3 | /** 4 | * immutable 确定操作的对象是不可变的,更好的维护性能 5 | * 具体说明在 `immutable.md` 文件中 6 | */ 7 | 8 | const InitState = { 9 | currentIndex : 0, 10 | leftMenu : [], 11 | leftMenuType : '', 12 | topMenu : [], 13 | navpath : [], 14 | collapse : false, 15 | selectClass : '', 16 | selectKey : 'my_case', 17 | status : 1 18 | } 19 | 20 | export default InitState -------------------------------------------------------------------------------- /src/views/Demo/CommentList.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | const propTypes = { 4 | onMount: PropTypes.func.isRequired, 5 | isActive: PropTypes.bool 6 | }; 7 | 8 | class CommentList extends Component { 9 | componentDidMount() { 10 | this.props.onMount(); 11 | } 12 | 13 | render() { 14 | return ( 15 | 18 | ) 19 | } 20 | } 21 | 22 | CommentList.propTypes = propTypes; 23 | export default CommentList; -------------------------------------------------------------------------------- /src/components/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | import styles from './NotFound.less'; 4 | 5 | const NotFound = () => { 6 | return ( 7 |
8 |
9 |

404

10 |

未找到该页面

11 | 12 |
13 |
14 | ); 15 | }; 16 | 17 | export default NotFound; 18 | -------------------------------------------------------------------------------- /src/entries/index.js: -------------------------------------------------------------------------------- 1 | import './index.html'; 2 | import './index.less'; 3 | import ReactDOM from 'react-dom'; 4 | import React from 'react'; 5 | import { browserHistory } from 'react-router'; 6 | import Routes from '../routes/index'; 7 | const Provider = require('react-redux').Provider; 8 | const configureStore = require('../store/configureStore'); 9 | const store = configureStore() 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); 17 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-airbnb", 3 | "parser": "babel-eslint", 4 | "rules": { 5 | "spaced-comment": [0], 6 | "no-unused-vars": [0], 7 | "no-empty": [0], 8 | "react/wrap-multilines": [0], 9 | "react/no-multi-comp": [0], 10 | "no-constant-condition": [0], 11 | "react/jsx-no-bind": [0], 12 | "react/prop-types": [0], 13 | "arrow-body-style": [0], 14 | "react/prefer-stateless-function": [0], 15 | "semi": [0] 16 | }, 17 | "ecmaFeatures": { 18 | "experimentalObjectRestSpread": true 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/views/Login/index.less: -------------------------------------------------------------------------------- 1 | 2 | .login-row { 3 | height: 100%; 4 | // background: url(./login-bg.png) no-repeat; 5 | background-size: 100% 100%; 6 | } 7 | 8 | @media (min-width: 768px) { 9 | .login-form { 10 | padding: 70px 8px; 11 | } 12 | } 13 | @media (min-width: 1500px) { 14 | .login-form { 15 | padding: 125px 8px; 16 | } 17 | } 18 | 19 | .login-form { 20 | // background: url(./input-bg.png) no-repeat; 21 | background: #ccc; 22 | background-size: 100% 100%; 23 | border-radius: 6px; 24 | } 25 | .check-margin { 26 | margin-top: -15px; 27 | margin-bottom: 8px; 28 | } -------------------------------------------------------------------------------- /src/store/types.js: -------------------------------------------------------------------------------- 1 | import keyMirror from 'key-mirror' 2 | 3 | /** 4 | * key-mirror: 5 | * keyMirror() 创建的对象,值会与名字一致,编码起来更方便 6 | */ 7 | 8 | export default keyMirror({ 9 | // Menu 10 | UPDATE_NAVPATH : null, 11 | GET_TOP_MENU : null, 12 | GET_LEFT_MENU : null, 13 | GET_MANAGE_LEFT_MENU : null, 14 | UPDATE_COLLAPSE : null, 15 | UPDATE_STATUS : null, 16 | INIT_MENU : null, 17 | GET_ADD_CASE_LEFT_MENU : null, 18 | 19 | // User 20 | UID_NOT_FOUND : null, 21 | FETCH_PROFILE : null, 22 | LOGIN : null, 23 | LOGOUT : null 24 | }) 25 | -------------------------------------------------------------------------------- /src/entries/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/PanelBox/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import './index.less' 4 | 5 | export default class PanelBox extends React.Component { 6 | constructor () { 7 | super() 8 | } 9 | 10 | render () { 11 | 12 | return ( 13 |
14 |
15 |
16 | {this.props.title} 17 |
18 |
19 |
20 | {this.props.children} 21 |
22 |
23 |
24 |
25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /proxy.config.js: -------------------------------------------------------------------------------- 1 | // Learn more on how to config. 2 | // - https://github.com/dora-js/dora-plugin-proxy#规则定义 3 | 4 | module.exports = { 5 | '/api/todos': function(req, res) { 6 | setTimeout(function() { 7 | res.json({ 8 | success: true, 9 | data: [ 10 | { 11 | id: 1, 12 | text: 'Learn antd', 13 | isComplete: true, 14 | }, 15 | { 16 | id: 2, 17 | text: 'Learn ant-tool', 18 | }, 19 | { 20 | id: 3, 21 | text: 'Learn dora', 22 | }, 23 | ], 24 | }); 25 | }, 500); 26 | }, 27 | '/api/login': function(req, res) { 28 | setTimeout(function() { 29 | res.json({ 30 | success: true, 31 | data: { 32 | user: 'admin' 33 | }, 34 | }); 35 | }, 500); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux' 2 | import thunkMiddleware from 'redux-thunk' 3 | import createLogger from 'redux-logger' 4 | import promiseMiddleware from './middlewares/promiseMiddleware'; 5 | import rootReducer from './reducers' 6 | 7 | export default function configureStore(initialState) { 8 | const store = createStore( 9 | rootReducer, 10 | initialState, 11 | applyMiddleware( 12 | thunkMiddleware, 13 | createLogger(), 14 | promiseMiddleware({ promiseTypeSuffixes: ['PENDING', 'SUCCESS', 'ERROR'] }), 15 | ) 16 | // applyMiddleware(thunkMiddleware) 17 | ) 18 | if (module.hot) { 19 | // Enable Webpack hot module replacement for reducers 20 | module.hot.accept('../reducers', () => { 21 | const nextReducer = require('../reducers').default 22 | store.replaceReducer(nextReducer) 23 | }) 24 | } 25 | 26 | return store 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/feature/leftMenu.js: -------------------------------------------------------------------------------- 1 | module.exports.caseMenu = [ 2 | { 3 | key: 'home', 4 | name: 'Home', 5 | icon: 'info-circle-o', 6 | num: '10', 7 | dot: false 8 | }, 9 | { 10 | key: 'test', 11 | name: 'Test', 12 | icon: 'check-circle-o', 13 | num: '20', 14 | dot: false 15 | }, 16 | { 17 | key: 'feature1', 18 | name: '列表', 19 | icon: 'check-circle', 20 | num: '11', 21 | dot: false 22 | }, 23 | { 24 | key: 'feature1_1', 25 | name: '表单', 26 | icon: 'poweroff', 27 | num: '12', 28 | dot: false 29 | }, 30 | { 31 | key: 'feature1_3', 32 | name: '统计图', 33 | icon: 'poweroff', 34 | num: '12', 35 | dot: false 36 | } 37 | ] 38 | 39 | module.exports.emptyMenu = [] -------------------------------------------------------------------------------- /src/feature/Feature7.jsx: -------------------------------------------------------------------------------- 1 | // 纯数据展现情况列表 2 | import React from 'react'; 3 | import { Upload, Icon } from 'antd'; 4 | import baidubce from 'bce-sdk-js'; 5 | 6 | import BDUploader from '../components/BDUploader'; 7 | 8 | const Feature7 = React.createClass({ 9 | getInitialState: function() { 10 | return { 11 | img_url:'' 12 | }; 13 | }, 14 | 15 | render: function() { 16 | const self = this; 17 | return
18 |

{this.props.title}

19 | 20 | 21 | 22 |
; 23 | }, 24 | 25 | uploadSuccess: function(url){ 26 | console.log(url) 27 | this.setState({ 28 | img_url: url 29 | }) 30 | } 31 | }); 32 | 33 | export default Feature7; 34 | -------------------------------------------------------------------------------- /src/api/api.js: -------------------------------------------------------------------------------- 1 | import superagent from 'superagent'; 2 | 3 | const methods = [ 4 | 'get', 5 | 'head', 6 | 'post', 7 | 'put', 8 | 'del', 9 | 'options', 10 | 'patch' 11 | ]; 12 | 13 | class _Api { 14 | 15 | constructor(opts) { 16 | 17 | this.opts = opts || {}; 18 | 19 | if (!this.opts.baseURI) 20 | throw new Error('baseURI option is required'); 21 | 22 | methods.forEach(method => 23 | this[method] = (path, { params, data } = {}) => new Promise((resolve, reject) => { 24 | const request = superagent[method](this.opts.baseURI + path); 25 | 26 | if (params) { 27 | request.query(params); 28 | } 29 | 30 | if (this.opts.headers) { 31 | request.set(this.opts.headers); 32 | } 33 | 34 | if (data) { 35 | request.send(data); 36 | } 37 | 38 | request.end((err, { body } = {}) => err ? reject(body || err) : resolve(body)); 39 | }) 40 | ); 41 | 42 | } 43 | 44 | } 45 | 46 | const Api = _Api; 47 | 48 | export default Api; 49 | -------------------------------------------------------------------------------- /src/services/xFetch.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | import cookie from 'js-cookie'; 3 | 4 | const errorMessages = (res) => `${res.status} ${res.statusText}`; 5 | 6 | function check401(res) { 7 | if (res.status === 401) { 8 | location.href = '/401'; 9 | } 10 | return res; 11 | } 12 | 13 | function check404(res) { 14 | if (res.status === 404) { 15 | return Promise.reject(errorMessages(res)); 16 | } 17 | return res; 18 | } 19 | 20 | function jsonParse(res) { 21 | return res.json().then(jsonResult => ({ ...res, jsonResult })); 22 | } 23 | 24 | function errorMessageParse(res) { 25 | const { success, message } = res.jsonResult; 26 | if (!success) { 27 | return Promise.reject(message); 28 | } 29 | return res; 30 | } 31 | 32 | function xFetch(url, options) { 33 | const opts = { ...options }; 34 | opts.headers = { 35 | ...opts.headers, 36 | authorization: cookie.get('authorization') || '', 37 | }; 38 | 39 | return fetch(url, opts) 40 | .then(check401) 41 | .then(check404) 42 | .then(jsonParse) 43 | .then(errorMessageParse); 44 | } 45 | 46 | export default xFetch; 47 | -------------------------------------------------------------------------------- /src/store/modules/user/user_action.js: -------------------------------------------------------------------------------- 1 | import {getCookie} from '../../../util'; 2 | import types from '../../types'; 3 | 4 | import reqwest from 'reqwest'; 5 | import { getLogin } from '../../../services/todos'; 6 | 7 | /** 8 | * 判断 `cookis` 中是否有 `uid` 没有触发没有发现 `uid` 的 `Action` 9 | * 10 | * 如果有 `uid` 通过 `uid` 向服务端获取用户信息 11 | * 12 | */ 13 | export function fetchProfile() { 14 | let uid = getCookie('uid'); 15 | 16 | if (uid === undefined) { 17 | return { type: types.UID_NOT_FOUND }; 18 | } 19 | 20 | return { 21 | type: types.FETCH_PROFILE, 22 | payload: { 23 | // promise: api.post('my') 24 | user: 'admin' 25 | } 26 | } 27 | } 28 | 29 | /** 30 | * 登录 31 | * 32 | * 通过 `用户账号/密码` 获取用户信息 33 | */ 34 | export function login(user, password) { 35 | return { 36 | type: types.LOGIN, 37 | payload: { 38 | promise: getLogin() 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * 退出功能: 45 | * 46 | * 如果是服务端维护退出状态则发送退出请求 47 | * 如果是在本地维护登录状态,则清空本地 `cookis` 与 `state` 中缓存的用户信息 48 | * 49 | */ 50 | export function logout() { 51 | return { 52 | type: types.LOGOUT, 53 | payload: { 54 | // promise: api.post('logout') 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/Header/index.less: -------------------------------------------------------------------------------- 1 | .ant-layout-header { 2 | background: #fff; 3 | .header-menu { 4 | > li { 5 | float: right; 6 | } 7 | } 8 | } 9 | 10 | // .ant-layout-wrapper { 11 | // padding: 0 0 0 50px; 12 | // } 13 | // .ant-layout-wrapper .ant-layout-header { 14 | // background: #404040; 15 | // height: 64px; 16 | // } 17 | // .ant-layout-wrapper .ant-layout-logo { 18 | // position: relative; 19 | // width: 200px; 20 | // height: 32px; 21 | // border-radius: 6px; 22 | // margin: 16px 28px 16px 0; 23 | // padding: 6px 0px 0 55px; 24 | // float: left; 25 | // } 26 | 27 | // .ant-layout-wrapper .ant-layout-logo img { 28 | // position: absolute; 29 | // top: 0; 30 | // left: 0; 31 | // height: 100%; 32 | // } 33 | 34 | .top-nav-img { 35 | width: 20px; 36 | margin-right: 3px; 37 | position: relative; 38 | top: 5px; 39 | left: 0; 40 | } 41 | 42 | // 顶部导航因为Link表单导致激活颜色问题,这里强制设置激活颜色 43 | .ant-menu-item-selected > a, .ant-menu-item-selected > a:hover { 44 | color: #2db7f5 !important; 45 | } 46 | 47 | // 菜单 48 | .ant-layout-header .ant-layout-wrapper .collapse-icon { 49 | line-height: 64px; 50 | padding-top: 20px; 51 | float: left; 52 | margin: 0 20px 0 20px; 53 | font-size: 20px; 54 | cursor: pointer; 55 | } -------------------------------------------------------------------------------- /src/components/NavPath/index.js: -------------------------------------------------------------------------------- 1 | import React, {PropTypes} from 'react' 2 | import { bindActionCreators } from 'redux' 3 | import { Breadcrumb } from 'antd' 4 | import { connect } from 'react-redux' 5 | 6 | import styles from './index.less' 7 | 8 | const defaultProps = { 9 | navpath: [] 10 | } 11 | 12 | const propTypes = { 13 | navpath: PropTypes.array 14 | } 15 | 16 | class NavPath extends React.Component { 17 | constructor (props) { 18 | super(props) 19 | } 20 | 21 | render () { 22 | const { navpath } = this.props 23 | const bread = navpath.map((item)=>{ 24 | return ( 25 | {item.name} 26 | ) 27 | }) 28 | const breadInit = 我的发布 29 | return ( 30 |
31 | 32 | 首页 33 | {bread.length == 0? breadInit: bread} 34 | 35 |
36 | ) 37 | } 38 | } 39 | 40 | NavPath.propTypes = propTypes; 41 | NavPath.defaultProps = defaultProps; 42 | 43 | function mapStateToProps(state) { 44 | return { 45 | // navpath: state.menu.navpath 46 | } 47 | } 48 | 49 | export default connect(mapStateToProps)(NavPath) 50 | -------------------------------------------------------------------------------- /src/store/modules/user/user_reducer.js: -------------------------------------------------------------------------------- 1 | // import _ from 'lodash'; 2 | import { message } from 'antd'; 3 | import { createReducer } from '../../../util'; 4 | import types from '../../types'; 5 | import initialState from './user_state'; 6 | import objectAssign from 'object-assign'; 7 | 8 | export default createReducer(initialState, { 9 | 10 | [`${types.LOGIN}_PENDING`]: (state, data) => { 11 | 12 | return objectAssign({}, state, { 13 | loggingIn : true 14 | }) 15 | }, 16 | 17 | [`${types.LOGIN}_ERROR`]: (state, data) => { 18 | 19 | return objectAssign({}, state, { 20 | loggingIn : false, 21 | user: null, 22 | loginErrors: data.message 23 | }) 24 | }, 25 | 26 | [`${types.LOGIN}_SUCCESS`]: (state, data) => { 27 | console.log('data', data); 28 | return objectAssign({}, state, { 29 | loggingIn : true, 30 | user: data.jsonResult.data, 31 | loginErrors: null 32 | }) 33 | }, 34 | 35 | [`${types.LOGOUT}_SUCCESS`]: (state, data) => { 36 | 37 | return objectAssign({}, state, { 38 | loggingOut : true, 39 | user: null, 40 | loginErrors: null 41 | }) 42 | }, 43 | 44 | [`${types.FETCH_PROFILE}_SUCCESS`]: (state, data) => { 45 | 46 | return objectAssign({}, state, { 47 | loggingIn : false, 48 | user: data.conten.user, 49 | loginErrors: null 50 | }) 51 | }, 52 | }) -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Router, Route, IndexRoute, Link, IndexRedirect } from 'react-router'; 3 | import App from '../views/App'; 4 | import Home from '../views/Home'; 5 | import Login from '../views/Login'; 6 | import Test from '../views/Test'; 7 | import Feature1 from '../feature/Feature1'; 8 | import Feature1_1 from '../feature/Feature1-1'; 9 | import Feature1_3 from '../feature/Feature1-3'; 10 | import NotFound from '../components/NotFound'; 11 | import Loding from '../components/Loding'; 12 | 13 | function validate() { 14 | // 在路由群载入时做 filter 处理 15 | } 16 | 17 | const Routes = ({ history }) => 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ; 35 | 36 | Routes.propTypes = { 37 | history: PropTypes.any, 38 | }; 39 | 40 | export default Routes; 41 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // Learn more on how to config. 2 | // - https://github.com/ant-tool/atool-build#配置扩展 3 | 4 | const webpack = require('atool-build/lib/webpack'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const glob = require('glob'); 8 | 9 | module.exports = function(webpackConfig) { 10 | webpackConfig.babel.plugins.push('transform-runtime'); 11 | webpackConfig.babel.plugins.push(['antd', { 12 | style: 'css', // if true, use less 13 | }]); 14 | 15 | // Enable this if you have to support IE8. 16 | webpackConfig.module.loaders.unshift({ 17 | test: /\.jsx?$/, 18 | loader: 'es3ify-loader', 19 | }); 20 | 21 | // Parse all less files as css module. 22 | webpackConfig.module.loaders.forEach(function(loader, index) { 23 | if (typeof loader.test === 'function' && loader.test.toString().indexOf('\\.less$') > -1) { 24 | loader.test = /\.dont\.exist\.file/; 25 | } 26 | if (loader.test.toString() === '/\\.module\\.less$/') { 27 | loader.test = /\.less$/; 28 | } 29 | }); 30 | 31 | // Load src/entries/*.js as entry automatically. 32 | const files = glob.sync('./src/entries/*.js'); 33 | const newEntries = files.reduce(function(memo, file) { 34 | const name = path.basename(file, '.js'); 35 | memo[name] = file; 36 | return memo; 37 | }, {}); 38 | webpackConfig.entry = Object.assign({}, webpackConfig.entry, newEntries); 39 | 40 | return webpackConfig; 41 | }; 42 | -------------------------------------------------------------------------------- /src/util/promise.js: -------------------------------------------------------------------------------- 1 | let Promise = window.Promise; 2 | 3 | if (!Promise) { 4 | Promise = function (executor) { 5 | executor(this.resolve.bind(this), this.reject.bind(this)); 6 | this._thens = []; 7 | }; 8 | 9 | Promise.prototype = { 10 | then: function (onResolve, onReject, onProgress) { 11 | this._thens.push({resolve: onResolve, reject: onReject, progress: onProgress}); 12 | }, 13 | 14 | 'catch': function (onReject) { 15 | this._thens.push({reject: onReject}); 16 | }, 17 | 18 | resolve: function (value) { 19 | this._complete('resolve', value); 20 | }, 21 | 22 | reject: function (reason) { 23 | this._complete('reject', reason); 24 | }, 25 | 26 | progress: function (status) { 27 | let i = 0; 28 | let aThen; 29 | 30 | while (aThen = this._thens[i++]) { 31 | aThen.progress && aThen.progress(status); 32 | } 33 | }, 34 | 35 | _complete: function (which, arg) { 36 | this.then = which === 'resolve' ? 37 | function (resolve) { resolve && resolve(arg); } : 38 | function (resolve, reject) { reject && reject(arg); }; 39 | 40 | this.resolve = this.reject = this.progress = 41 | function () { throw new Error('Promise already completed.'); }; 42 | 43 | let aThen; 44 | let i = 0; 45 | 46 | while (aThen = this._thens[i++]) { 47 | aThen[which] && aThen[which](arg); 48 | } 49 | 50 | delete this._thens; 51 | } 52 | }; 53 | } 54 | 55 | export default Promise; 56 | -------------------------------------------------------------------------------- /test/containers/Root.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; // required to get test to work. we can get around this later with more configuration 2 | import { shallow } from 'enzyme'; // method from enzyme which allows us to do shallow render 3 | import Root from '../../src/views/Demo/Root'; // import our soon to be component 4 | 5 | // describe('(Container) Root', () => { 6 | // it('renders as a
', () => { 7 | // const wrapper = shallow(); 8 | // expect(wrapper.type()).to.eql('div'); 9 | // }); 10 | 11 | // it('has style with height 100%', () => { 12 | // const wrapper = shallow(); 13 | // const expectedStyles = { 14 | // height: '100%', 15 | // background: '#333' 16 | // } 17 | // expect(wrapper.prop('style')).to.eql(expectedStyles); 18 | // }); 19 | 20 | // it('contains a header explaining the app', () => { 21 | // const wrapper = shallow(); 22 | // expect(wrapper.find('.welcome-header')).to.have.length(1); 23 | // }); 24 | // }); 25 | 26 | describe('(Container) Root', () => { 27 | const wrapper = shallow(); 28 | 29 | it('renders as a
', () => { 30 | expect(wrapper.type()).to.eql('div'); 31 | }); 32 | 33 | it('has style with height 100%', () => { 34 | const expectedStyles = { 35 | height: '100%', 36 | background: '#333' 37 | } 38 | expect(wrapper.prop('style')).to.eql(expectedStyles); 39 | }); 40 | 41 | it('contains a header explaining the app', () => { 42 | expect(wrapper.find('.welcome-header')).to.have.length(1); 43 | }); 44 | }); -------------------------------------------------------------------------------- /src/common/util.js: -------------------------------------------------------------------------------- 1 | 2 | // 自定义的配置数据 3 | 4 | const DateFormat = function(date, fmt) { 5 | let o = { 6 | "M+" : date.getMonth()+1, //月份 7 | "d+" : date.getDate(), //日 8 | "h+" : date.getHours()%12 == 0 ? 12 : date.getHours()%12, //小时 9 | "H+" : date.getHours(), //小时 10 | "m+" : date.getMinutes(), //分 11 | "s+" : date.getSeconds(), //秒 12 | "q+" : Math.floor((date.getMonth()+3)/3), //季度 13 | "S" : date.getMilliseconds() //毫秒 14 | }; 15 | let week = { 16 | "0" : "/u65e5", 17 | "1" : "/u4e00", 18 | "2" : "/u4e8c", 19 | "3" : "/u4e09", 20 | "4" : "/u56db", 21 | "5" : "/u4e94", 22 | "6" : "/u516d" 23 | }; 24 | if(/(y+)/.test(fmt)){ 25 | fmt=fmt.replace(RegExp.$1, (date.getFullYear()+"").substr(4 - RegExp.$1.length)); 26 | } 27 | if(/(E+)/.test(fmt)){ 28 | fmt=fmt.replace(RegExp.$1, ((RegExp.$1.length>1) ? (RegExp.$1.length>2 ? "/u661f/u671f" : "/u5468") : "")+week[date.getDay()+""]); 29 | } 30 | for(let k in o){ 31 | if(new RegExp("("+ k +")").test(fmt)){ 32 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length))); 33 | } 34 | } 35 | return fmt; 36 | } 37 | 38 | 39 | export default { 40 | DateFormat : DateFormat 41 | }; -------------------------------------------------------------------------------- /src/feature/Feature3-1.jsx: -------------------------------------------------------------------------------- 1 | // 含有可操作 table 栏的数据展示 2 | import React from 'react'; 3 | 4 | import Immutable from 'immutable'; 5 | import TinyMCE from 'react-tinymce'; 6 | //https://github.com/ded/reqwest 7 | import Reqwest from 'reqwest'; 8 | 9 | const Feature = React.createClass({ 10 | getInitialState: function(){ 11 | return { 12 | value: '' 13 | } 14 | }, 15 | render: function() { 16 | let config = { 17 | content: "

This is the initial content of the editor

", 18 | config: { 19 | height: '250', 20 | plugins: [ 21 | "advlist autolink lists charmap print preview hr anchor pagebreak spellchecker", 22 | "searchreplace wordcount visualblocks visualchars fullscreen insertdatetime nonbreaking", 23 | "save table contextmenu directionality emoticons paste textcolor" 24 | ], 25 | toolbar: "insertfile undo redo | styleselect fontselect fontsizeselect| bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | l | print preview fullpage | forecolor backcolor", 26 | }, 27 | onChange: this.handleEditorChange 28 | } 29 | return
30 |

html文本:{this.state.value}

31 | 32 |
33 | }, 34 | 35 | handleEditorChange(e) { 36 | window.e = e; 37 | this.setState({ 38 | value: e.target.getContent() 39 | }); 40 | }, 41 | }); 42 | 43 | export default Feature; 44 | -------------------------------------------------------------------------------- /src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | // import { routerReducer } from 'react-router-redux'; 3 | 4 | import user from './modules/user/user_reducer'; 5 | import menu from './modules/menu/menu_reducer'; 6 | 7 | /** 8 | * combineReducers(reducers) 9 | * 10 | * desc: 11 | * combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore。 12 | * 合并后的 reducer 可以调用各个子 reducer,并把它们的结果合并成一个 state 对象。state 对象的结构由传入的多个 reducer 的 key 决定。 13 | * 14 | * 最终,state 对象的结构会是这样的: 15 | * 16 | * { 17 | * reducer1: ... 18 | * reducer2: ... 19 | * } 20 | * 21 | * 自定义 `State` 树名称: 22 | * 通过为传入对象的 reducer 命名不同来控制 state key 的命名。例如,你可以调用 combineReducers({ todos: myTodosReducer, counter: myCounterReducer }) 将 state 结构变为 { todos, counter }。 23 | * 通常的做法是命名 reducer,然后 state 再去分割那些信息,因此你可以使用 ES6 的简写方法:combineReducers({ counter, todos })。这与 combineReducers({ counter: counter, todos: todos }) 一样。 24 | * 25 | * 参数: 26 | * reducers (Object): 一个对象,它的值(value) 对应不同的 reducer 函数,这些 reducer 函数后面会被合并成一个。下面会介绍传入 reducer 函数需要满足的规则。 27 | * 28 | * 返回值: 29 | * (Function):一个调用 reducers 对象里所有 reducer 的 reducer,并且构造一个与 reducers 对象结构相同的 state 对象。 30 | * 31 | * 注意,每个传入 combineReducers 的 reducer 都需满足以下规则: 32 | * 1、所有未匹配到的 action,必须把它接收到的第一个参数也就是那个 state 原封不动返回。(在 `switch` 最后 `return state`) 33 | * 2、永远不能返回 undefined。当过早 return 时非常容易犯这个错误,为了避免错误扩散,遇到这种情况时 combineReducers 会抛异常。 34 | * 3、如果传入的 state 就是 undefined,一定要返回对应 reducer 的初始 state。根据上一条规则,初始 state 禁止使用 undefined。 35 | * 使用 ES6 的默认参数值语法来设置初始 state 很容易,但你也可以手动检查第一个参数是否为 undefined。(`state = initState`,当 `state` 为 `undefined` 时会赋默认值 `initState`) 36 | * 37 | */ 38 | 39 | export default combineReducers({ 40 | user, 41 | menu, 42 | // routing: routerReducer 43 | }); 44 | -------------------------------------------------------------------------------- /src/feature/Feature1-2.jsx: -------------------------------------------------------------------------------- 1 | // 纯数据展现情况列表 2 | import React from 'react'; 3 | 4 | import FeatureSetConfig from '../components/FeatureSetConfig'; 5 | 6 | import Immutable from 'immutable'; 7 | import Reqwest from 'reqwest'; 8 | 9 | import testData from '../common/test-data'; 10 | 11 | const simple_conf = { 12 | 13 | type: 'simpleObject', 14 | 15 | initData: function(callback){ 16 | // 模拟数据 17 | setTimeout(function(){ 18 | let object = testData.simpleObject; 19 | object.key = object.docid; 20 | 21 | callback(object); 22 | }, 1000) 23 | }, 24 | 25 | operate:[ 26 | { 27 | text: '确认数据', 28 | style: { 29 | 'marginRight': '30px' 30 | }, 31 | callback: function(item){ 32 | console.log(item) 33 | } 34 | }, { 35 | text: '展示数据', 36 | callback: function(item){ 37 | console.log(item) 38 | } 39 | } 40 | ], 41 | 42 | UType:[ 43 | { 44 | name: 'docid', 45 | label: '唯一标识', 46 | type: 'string', 47 | placeholder: '请输入标示名称' 48 | },{ 49 | name: 'title', 50 | label: '标题', 51 | type: 'string', 52 | placeholder: '请输入标示名称' 53 | },{ 54 | name: 'link', 55 | label: '链接', 56 | type: 'string' 57 | },{ 58 | name: 'date', 59 | label: '日期', 60 | type: 'date' 61 | },{ 62 | name: 'img', 63 | label: '图片', 64 | type: 'imageUpload' 65 | } 66 | ] 67 | } 68 | 69 | const Feature1 = FeatureSetConfig(simple_conf); 70 | 71 | export default Feature1; 72 | -------------------------------------------------------------------------------- /test/containers/CommentList.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // Once we set up Karma to run our tests through webpack 4 | // we will no longer need to have these long relative paths 5 | import CommentList from '../../src/views/Demo/CommentList'; 6 | import { 7 | describeWithDOM, 8 | mount, 9 | shallow, 10 | spyLifecycle 11 | } from 'enzyme'; 12 | 13 | describe('(Component) CommentList', () => { 14 | 15 | // using special describeWithDOM helper that enzyme 16 | // provides so if other devs on my team don't have JSDom set up 17 | // properly or are using old version of node it won't bork their test suite 18 | // 19 | // All of our tests that depend on mounting should go inside one of these 20 | // special describe blocks 21 | describeWithDOM('Lifecycle methods', () => { 22 | it('calls componentDidMount', () => { 23 | spyLifecyle(CommentList); 24 | 25 | const props = { 26 | onMount: () => {}, // an anonymous function in ES6 arrow syntax 27 | isActive: false 28 | } 29 | 30 | // using destructuring to pass props down 31 | // easily and then mounting the component 32 | mount(); 33 | 34 | // CommentList's componentDidMount should have been 35 | // called once. spyLifecyle attaches sinon spys so we can 36 | // make this assertion 37 | expect( 38 | CommentList.prototype.componentDidMount.calledOnce 39 | ).to.be.true; 40 | }); 41 | 42 | it('calls onMount prop once it mounts', () => { 43 | // create a spy for the onMount function 44 | const props = { onMount: sinon.spy() }; 45 | 46 | // mount our component 47 | mount(); 48 | 49 | // expect that onMount was called 50 | expect(props.onMount.calledOnce).to.be.true; 51 | }); 52 | }); 53 | }); -------------------------------------------------------------------------------- /src/feature/Feature2-3.jsx: -------------------------------------------------------------------------------- 1 | // 纯数据展现情况列表 2 | import React from 'react'; 3 | 4 | import FeatureSetConfig from '../components/FeatureSetConfig'; 5 | 6 | import Immutable from 'immutable'; 7 | import Reqwest from 'reqwest'; 8 | 9 | import testData from '../common/test-data'; 10 | 11 | const simple_conf = { 12 | 13 | type: 'simpleObject', 14 | 15 | initData: function(callback){ 16 | // 模拟数据 17 | setTimeout(function(){ 18 | let object = testData.simpleObject; 19 | object.key = object.docid; 20 | 21 | callback(object); 22 | }, 1000) 23 | }, 24 | 25 | Update:function(data, callback){ 26 | console.log(data); 27 | callback(data); 28 | }, 29 | 30 | operate:[ 31 | { 32 | text: '确认修改', 33 | type: 'update', 34 | style: { 35 | 'marginRight': '30px', 36 | 'marginLight': '80px' 37 | } 38 | }, { 39 | text: '展示数据', 40 | callback: function(item){ 41 | console.log(item) 42 | } 43 | } 44 | ], 45 | 46 | UType:[ 47 | { 48 | name: 'docid', 49 | label: '唯一标识', 50 | type: 'string', 51 | placeholder: '请输入标示名称' 52 | },{ 53 | name: 'title', 54 | label: '标题', 55 | type: 'string', 56 | placeholder: '请输入标示名称' 57 | },{ 58 | name: 'link', 59 | label: '链接', 60 | type: 'string' 61 | },{ 62 | name: 'date', 63 | label: '日期', 64 | type: 'date' 65 | },{ 66 | name: 'img', 67 | label: '图片', 68 | type: 'imageUpload' 69 | } 70 | ] 71 | } 72 | 73 | const Feature1 = FeatureSetConfig(simple_conf); 74 | 75 | export default Feature1; 76 | -------------------------------------------------------------------------------- /src/store/modules/menu/menu_action.js: -------------------------------------------------------------------------------- 1 | import api from '../../../api'; 2 | import types from '../../types'; 3 | 4 | import reqwest from 'reqwest'; 5 | import {emptyMenu, caseMenu} from '../../../feature/leftMenu'; 6 | import topMenu from '../../../feature/topMenu'; 7 | 8 | /** 9 | * 顶部菜单切换,更新面包屑 10 | * 11 | * @export 12 | * @param {any} path 13 | * @param {any} key 14 | * @returns 15 | */ 16 | export function updateNavPath(path, key) { 17 | return { 18 | type: types.UPDATE_NAVPATH, 19 | payload: { 20 | data: path, 21 | key: key 22 | } 23 | } 24 | } 25 | 26 | /** 27 | * 获取case顶部菜单 28 | * 向服务端获取 `顶部菜单` 信息,服务端可以自由控制 `顶部菜单` 的权限问题 29 | * 30 | * @export 31 | * @returns 32 | */ 33 | export function getTopMenu() { 34 | return { 35 | type: types.GET_TOP_MENU, 36 | params: { 37 | topMenu: topMenu 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * 获取cese侧边菜单数据(我的发布、我的case、我的关注) 44 | * 45 | * 获取 `侧边菜单` 的统计数据 46 | * 根据 `taskMatch` 字段获取筛选 47 | * 我的发布 = 1 48 | * 我的case = 2 49 | * 我的关注 = 3 50 | * 51 | * @export 52 | * @param {any} taskMatchObj 53 | * @returns 54 | */ 55 | export function getCaseMenu() { 56 | return { 57 | type: types.GET_LEFT_MENU, 58 | params: { 59 | leftMenu: caseMenu 60 | } 61 | } 62 | } 63 | 64 | /** 65 | * 侧栏菜单的隐藏显示控制 66 | * 67 | * 通过点击切换按钮触发该功能 68 | * 目前在 `View/App` 下控制,打开与隐藏的Class 69 | * 70 | * @export 71 | * @param {any} collapse 72 | * @returns 73 | */ 74 | export function updateCollapse(collapse) { 75 | return { 76 | type: types.UPDATE_COLLAPSE, 77 | payload: { 78 | collapse: collapse, 79 | } 80 | } 81 | } 82 | 83 | /** 84 | * 修改 state 85 | * 86 | * @export 87 | * @param {any} status 88 | * @returns 89 | */ 90 | export function updateStatus(status) { 91 | return { 92 | type: types.UPDATE_STATUS, 93 | payload: { 94 | status: status, 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/components/UpdateForm.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form, Modal, message } from 'antd'; 3 | 4 | import CFormItem from './CreateFormItem'; 5 | 6 | 7 | let UForm = React.createClass({ 8 | getInitialState: function() { 9 | return { 10 | 11 | }; 12 | }, 13 | 14 | render: function() { 15 | const self = this; 16 | const UType = this.props.UType; 17 | const updateItem = this.props.updateItem; 18 | 19 | // 详情见antd form 文档 20 | const { getFieldProps } = this.props.form; 21 | const formItemLayout = { 22 | labelCol: { span: 6 }, 23 | wrapperCol: { span: 18 }, 24 | }; 25 | 26 | return UType ? 27 |
28 | 29 |
30 | { 31 | UType.map(function(item){ 32 | item.defaultValue = updateItem[item.name]||''; 33 | //return self.dealConfigUType(item, defaultValue); 34 | return 35 | }) 36 | } 37 | 38 |
39 |
: 40 |
41 | }, 42 | 43 | handleUpdate: function(){ 44 | this.props.submit(this.props.form.getFieldsValue()); 45 | }, 46 | 47 | handleReset: function() { 48 | this.props.form.resetFields(); 49 | }, 50 | 51 | hideModal: function() { 52 | this.props.hideForm(); 53 | this.handleReset(); 54 | } 55 | }); 56 | UForm = Form.create()(UForm); 57 | 58 | export default UForm; 59 | -------------------------------------------------------------------------------- /src/util/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility functions. 3 | */ 4 | import Promise from './promise'; 5 | 6 | const _ = {}; 7 | 8 | function extend (target, source, deep) { 9 | for (const key in source) { 10 | if (deep && (_.isPlainObject(source[key]) || _.isArray(source[key]))) { 11 | if (_.isPlainObject(source[key]) && !_.isPlainObject(target[key])) { 12 | target[key] = {}; 13 | } 14 | if (_.isArray(source[key]) && !_.isArray(target[key])) { 15 | target[key] = []; 16 | } 17 | 18 | extend(target[key], source[key], deep); 19 | } else if (source[key] !== undefined) { 20 | target[key] = source[key]; 21 | } 22 | } 23 | } 24 | 25 | _.isFunction = function (obj) { 26 | return obj && typeof obj === 'function'; 27 | }; 28 | 29 | _.isString = function(value) { 30 | return value && typeof value === 'string'; 31 | }; 32 | 33 | _.isNumber = function(value) { 34 | return typeof value === 'number'; 35 | }; 36 | 37 | _.options = function (key, obj, options) { 38 | const opts = obj.$options || {}; 39 | 40 | return _.extend({}, 41 | opts[key], 42 | options 43 | ); 44 | }; 45 | 46 | _.each = function (obj, iterator) { 47 | let i; 48 | let key; 49 | 50 | if (typeof obj.length == 'number') { 51 | for (i = 0; i < obj.length; i++) { 52 | iterator.call(obj[i], obj[i], i); 53 | } 54 | } else if (_.isObject(obj)) { 55 | for (key in obj) { 56 | if (obj.hasOwnProperty(key)) { 57 | iterator.call(obj[key], obj[key], key); 58 | } 59 | } 60 | } 61 | 62 | return obj; 63 | }; 64 | 65 | _.extend = function (target) { 66 | const array = []; 67 | const args = array.slice.call(arguments, 1); 68 | 69 | let deep; 70 | 71 | if (typeof target == 'boolean') { 72 | deep = target; 73 | target = args.shift(); 74 | } 75 | 76 | args.forEach(function (arg) { 77 | extend(target, arg, deep); 78 | }); 79 | 80 | return target; 81 | }; 82 | 83 | 84 | _.Promise = Promise; 85 | 86 | export default _; 87 | -------------------------------------------------------------------------------- /src/views/Home/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Row, Col, Collapse, Alert } from 'antd'; 3 | const Panel = Collapse.Panel; 4 | 5 | // import {Line,Pie,Doughnut} from 'react-chartjs'; 6 | 7 | import styles from './index.less' 8 | 9 | const chartOption = { 10 | responsive: true 11 | } 12 | 13 | const text = ` 14 | A dog is a type of domesticated animal. 15 | Known for its loyalty and faithfulness, 16 | it can be found as a welcome guest in many households across the world. 17 | A dog is a type of domesticated animal. 18 | Known for its loyalty and faithfulness, 19 | it can be found as a welcome guest in many households across the world. 20 | A dog is a type of domesticated animal. 21 | Known for its loyalty and faithfulness, 22 | it can be found as a welcome guest in many households across the world. 23 | `; 24 | 25 | export default class Home extends React.Component { 26 | constructor() { 27 | super() 28 | } 29 | 30 | componentWillMount() { 31 | } 32 | 33 | callback() { 34 | 35 | } 36 | 37 | render() { 38 | const detail = ( 39 | 40 | 41 |

{text}

42 |
43 | 44 |

{text}

45 |
46 | 47 |

{text}

48 |
49 |
50 | ) 51 | return ( 52 |
53 | 59 | 60 | 61 | {detail} 62 | 63 | 64 | {/**/} 65 | 66 | 67 | {detail} 68 | 69 | 70 |
71 | ) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/components/BDUploader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Icon, message } from 'antd'; 3 | import baidubce from 'bce-sdk-js'; 4 | 5 | let config = { 6 | endpoint: 'http://bj.bcebos.com', //传入Bucket所在区域域名 7 | credentials: { 8 | ak: 'e4a6dcc47a3b46f3a5ebf0f81d0e4928', //您的AccessKey 9 | sk: '8d26035b87e14989a4343f0e43c0ce71' //您的SecretAccessKey 10 | } 11 | }; 12 | 13 | let client = new baidubce.BosClient(config); 14 | 15 | const createKey = function(ext){ 16 | let date = new Date(); 17 | 18 | let year = date.getFullYear(); 19 | let month = date.getMonth()+1; 20 | month = (month < 10) ? '0' + month: month; 21 | 22 | let day = date.getDate(); 23 | day = (day < 10) ? '0' + day: day; 24 | 25 | return year + '/' + month + '/' + day + '/' + (+new Date())+ '.'+ext; 26 | } 27 | 28 | // 百度浏览器特定文件上传组件 29 | let BDUploader = React.createClass({ 30 | getInitialState: function() { 31 | return { 32 | 33 | }; 34 | }, 35 | 36 | render: function() { 37 | return
38 | 41 | 42 |
; 43 | }, 44 | 45 | openFileInput: function(e){ 46 | this.refs.fileInput.click(); 47 | }, 48 | uploadImg: function(e){ 49 | let self = this; 50 | 51 | let file = e.target.files[0]; // 获取要上传的文件 52 | let ext = file.name.split(/\./g).pop(); 53 | 54 | let key = createKey(ext); // 保存到bos时的key,您可更改,默认以文件名作为key 55 | let mimeType = baidubce.MimeType.guess(ext); 56 | 57 | client.putObjectFromBlob("mbrowser", key, file, { 58 | 'Content-Type': /^text\//.test(mimeType)? mimeType+'; charset=UTF-8': mimeType 59 | }).then(function (res) { 60 | // 上传完成,添加您的代码 61 | let imgUrl = config.endpoint+'/v1/mbrowser/'+key; 62 | console.log('上传成功', imgUrl); 63 | self.props.success&&self.props.success(imgUrl); 64 | }).catch(function (err) { 65 | // 上传失败,添加您的代码 66 | self.props.error&&self.props.error(error); 67 | }); 68 | } 69 | }); 70 | 71 | export default BDUploader; 72 | -------------------------------------------------------------------------------- /src/components/CreateForm.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form, Modal, Button } from 'antd'; 3 | 4 | import CFormItem from './CreateFormItem'; 5 | 6 | let CForm = React.createClass({ 7 | getInitialState: function() { 8 | return { visible: false }; 9 | }, 10 | 11 | render: function() { 12 | const self = this; 13 | const CType = this.props.CType; 14 | 15 | const { getFieldProps } = this.props.form; 16 | const formItemLayout = { 17 | labelCol: { span: 6 }, 18 | wrapperCol: { span: 18 }, 19 | }; 20 | 21 | return CType ? 22 |
23 | 24 | 25 |
26 | { 27 | CType.map(function(item){ 28 | //return self.dealConfigCType(item); 29 | return 30 | }) 31 | } 32 | 33 |
34 |
: 35 |
36 | }, 37 | 38 | handleCreate: function(){ 39 | 40 | console.log('收到表单值:', this.props.form.getFieldsValue()); 41 | 42 | this.props.form.validateFields((errors, values) => { 43 | if (!!errors) { 44 | console.log('Errors in form!!!'); 45 | return; 46 | }else{ 47 | console.log('Submit!!!'); 48 | this.props.submit(values); 49 | this.hideModal(); 50 | } 51 | }); 52 | //this.props.submit(this.props.form.getFieldsValue()); 53 | 54 | }, 55 | 56 | handleReset: function() { 57 | this.props.form.resetFields(); 58 | }, 59 | 60 | showModal: function() { 61 | this.setState({ visible: true }); 62 | }, 63 | 64 | hideModal: function() { 65 | this.setState({ visible: false }); 66 | this.handleReset(); 67 | } 68 | }); 69 | CForm = Form.create()(CForm); 70 | 71 | export default CForm; 72 | -------------------------------------------------------------------------------- /src/views/App/index.js: -------------------------------------------------------------------------------- 1 | import React, {PropTypes} from 'react'; 2 | import {bindActionCreators} from 'redux'; 3 | import {connect} from 'react-redux'; 4 | import {Affix, Row, Col, Icon} from 'antd'; 5 | 6 | import NavPath from '../../components/NavPath'; 7 | import Header from '../../components/Header'; 8 | import Sidebar from '../../components/Sidebar'; 9 | import Footer from '../../components/Footer'; 10 | 11 | import { fetchProfile, logout } from '../../store/modules/user/user_action'; 12 | // import 'antd/dist/antd.less'; 13 | import styles from './index.less'; 14 | 15 | class App extends React.Component { 16 | constructor(props) { 17 | super(props); 18 | } 19 | 20 | /** 21 | * 判断登入权限 22 | */ 23 | componentWillMount() { 24 | const {actions} = this.props; 25 | actions.fetchProfile(); 26 | } 27 | /** 28 | * 监听porps 29 | * 30 | * 1、监听注销字段,刷新页面 31 | * 32 | * @param {any} nextProps 33 | */ 34 | componentWillReceiveProps(nextProps) { 35 | const loggingOut = nextProps.loggingOut; 36 | if (loggingOut) { 37 | window.location.href = '/'; 38 | } 39 | } 40 | render() { 41 | const {user, actions} = this.props; 42 | const { collapse } = this.props; // 判断侧边栏隐藏显示 43 | 44 | return ( 45 |
46 | 47 |
48 |
49 | 50 |
51 |
52 | {this.props.children} 53 |
54 |
55 |
56 |
57 |
58 | ); 59 | } 60 | } 61 | 62 | App.propTypes = { 63 | user: PropTypes.object, 64 | children: PropTypes.node.isRequired 65 | }; 66 | 67 | App.contextTypes = { 68 | history: PropTypes.object.isRequired, 69 | store: PropTypes.object.isRequired 70 | }; 71 | 72 | const mapStateToProps = (state) => { 73 | const {user} = state; 74 | return { 75 | user: user ? user : null, 76 | loggingOut: user.loggingOut, 77 | // collapse: state.menu.collapse 78 | }; 79 | }; 80 | 81 | function mapDispatchToProps(dispatch) { 82 | return { 83 | actions: bindActionCreators({ fetchProfile, logout }, dispatch) 84 | }; 85 | } 86 | 87 | export default connect(mapStateToProps, mapDispatchToProps)(App); 88 | -------------------------------------------------------------------------------- /src/store/middlewares/promiseMiddleware.js: -------------------------------------------------------------------------------- 1 | import { isPromise } from '../../util' 2 | 3 | const defaultTypes = ['PENDING', 'FULFILLED', 'REJECTED'] 4 | export default function promiseMiddleware (config = {}) { 5 | const promiseTypeSuffixes = config.promiseTypeSuffixes || defaultTypes 6 | 7 | return (_ref) => { 8 | const dispatch = _ref.dispatch 9 | 10 | return next => action => { 11 | if (!isPromise(action.payload)) { 12 | return next(action) 13 | } 14 | 15 | const { type, payload, meta, params = {} } = action 16 | const { promise, data } = payload 17 | const [ PENDING, FULFILLED, REJECTED ] = (meta || {}).promiseTypeSuffixes || promiseTypeSuffixes 18 | 19 | /** 20 | * Dispatch the first async handler. This tells the 21 | * reducer that an async action has been dispatched. 22 | */ 23 | next({ 24 | type: `${type}_${PENDING}`, 25 | ...!!data ? { payload: data, params } : {}, 26 | ...!!meta ? { meta } : {} 27 | }) 28 | 29 | const isAction = resolved => resolved && (resolved.meta || resolved.payload) 30 | const isThunk = resolved => typeof resolved === 'function' 31 | const getResolveAction = isError => ({ 32 | type: `${type}_${isError ? REJECTED : FULFILLED}`, 33 | ...!!meta ? { meta } : {}, 34 | ...!!isError ? { error: true } : {} 35 | }) 36 | 37 | /** 38 | * Re-dispatch one of: 39 | * 1. a thunk, bound to a resolved/rejected object containing ?meta and type 40 | * 2. the resolved/rejected object, if it looks like an action, merged into action 41 | * 3. a resolve/rejected action with the resolve/rejected object as a payload 42 | */ 43 | action.payload.promise = promise.then( 44 | (resolved = {}) => { 45 | const resolveAction = getResolveAction() 46 | return dispatch(isThunk(resolved) ? resolved.bind(null, resolveAction) : { 47 | ...resolveAction, 48 | ...isAction(resolved) ? resolved : { 49 | ...!!resolved && { payload: resolved, params } 50 | } 51 | }) 52 | }, 53 | (rejected = {}) => { 54 | const resolveAction = getResolveAction(true) 55 | return dispatch(isThunk(rejected) ? rejected.bind(null, resolveAction) : { 56 | ...resolveAction, 57 | ...isAction(rejected) ? rejected : { 58 | ...!!rejected && { payload: rejected, params } 59 | } 60 | }) 61 | } 62 | ) 63 | 64 | return action 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "entry": {}, 4 | "dependencies": { 5 | "antd": "^1.1.0", 6 | "atool-build": "0.7.x", 7 | "babel-plugin-antd": "0.4.x", 8 | "babel-plugin-transform-runtime": "^6.8.0", 9 | "babel-runtime": "^6.6.1", 10 | "bce-sdk-js": "^0.1.8", 11 | "classnames": "^2.2.3", 12 | "echarts-for-react": "^1.1.2", 13 | "es3ify-loader": "^0.2.0", 14 | "fetch-ie8": "^1.4.0", 15 | "history": "^2.0.1", 16 | "immutable": "^3.8.1", 17 | "isomorphic-fetch": "^2.2.1", 18 | "js-cookie": "^2.1.1", 19 | "key-mirror": "^1.0.1", 20 | "lodash": "^4.13.1", 21 | "md5": "^2.1.0", 22 | "object-assign": "^4.0.1", 23 | "react": "0.14.x", 24 | "react-chartjs": "^0.7.3", 25 | "react-dom": "0.14.x", 26 | "react-redux": "^4.4.0", 27 | "react-router": "2.3.x", 28 | "react-router-redux": "^4.0.5", 29 | "redux": "^3.3.1", 30 | "redux-logger": "^2.6.1", 31 | "redux-thunk": "^2.0.1", 32 | "reqwest": "^2.0.5", 33 | "superagent": "^2.1.0" 34 | }, 35 | "devDependencies": { 36 | "atool-test-mocha": "^0.1.4", 37 | "babel-eslint": "^6.0.0", 38 | "babel-core": "^6.4.0", 39 | "babel-loader": "~6.2.1", 40 | "babel-polyfill": "^6.3.14", 41 | "babel-preset-es2015": "^6.3.13", 42 | "babel-preset-react": "^6.3.13", 43 | "babel-register": "^6.3.13", 44 | "chai": "^3.4.1", 45 | "dora": "0.3.x", 46 | "dora-plugin-browser-history": "^0.1.1", 47 | "dora-plugin-hmr": "0.6.x", 48 | "dora-plugin-livereload": "0.3.x", 49 | "dora-plugin-proxy": "0.6.x", 50 | "dora-plugin-webpack": "0.6.x", 51 | "enzyme": "^1.2.0", 52 | "expect": "^1.20.1", 53 | "eslint": "^2.7.0", 54 | "eslint-config-airbnb": "6.x", 55 | "eslint-plugin-react": "4.x", 56 | "glob": "^7.0.3", 57 | "jsdom": "^9.4.1", 58 | "karma": "^0.13.19", 59 | "karma-chai": "^0.1.0", 60 | "karma-mocha": "^0.2.1", 61 | "karma-phantomjs-launcher": "^0.2.3", 62 | "karma-sourcemap-loader": "^0.3.6", 63 | "karma-spec-reporter": "0.0.23", 64 | "karma-webpack": "^1.7.0", 65 | "mocha": "^2.3.4", 66 | "react-addons-test-utils": "^0.14.6", 67 | "sinon": "^1.17.2" 68 | }, 69 | "scripts": { 70 | "build": "atool-build", 71 | "lint": "eslint --ext .js,.jsx src", 72 | "start": "dora -p 8001 --plugins \"webpack,proxy,livereload?enableJs=false&injectHost=127.0.0.1,browser-history?index=/src/entries/index.html\"", 73 | "test-antd": "atool-test-mocha ./src/**/__tests__/*-test.js", 74 | "test": "mocha --compilers js:babel-core/register --require ./test/test_helper.js --recursive", 75 | "test:watch": "npm test -- --watch" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/components/Sidebar/index.less: -------------------------------------------------------------------------------- 1 | // .ant-layout-aside .ant-layout-logo { 2 | // width: 150px; 3 | // height: 32px; 4 | // background: #333; 5 | // border-radius: 6px; 6 | // margin: 16px 24px 16px 28px; 7 | // } 8 | 9 | 10 | // .ant-layout-aside .ant-layout-sider { 11 | // width: 224px; 12 | // background: #404040; 13 | // position: absolute; 14 | // overflow: auto; 15 | // padding-bottom: 24px; 16 | // height: 100%; 17 | // } 18 | 19 | // .ant-layout-aside .ant-layout-sider > .ant-menu { 20 | // margin-bottom: 20px; 21 | // } 22 | 23 | /*********************/ 24 | .nav-text { 25 | vertical-align: baseline; 26 | display: inline-block; 27 | white-space: nowrap; 28 | } 29 | .ant-layout-logo { 30 | position: relative; 31 | width: 150px; 32 | height: 32px; 33 | // background: #333; 34 | color: #999; 35 | border-radius: 6px; 36 | margin: 16px 24px 16px 28px; 37 | padding: 7px 0px 0 60px; 38 | transition: all 0.3s ease; 39 | } 40 | .ant-layout-logo > img { 41 | position: absolute; 42 | top: 0; 43 | left: -11px; 44 | height: 100%; 45 | } 46 | 47 | .ant-layout-sider { 48 | width: 224px; 49 | background: #404040; 50 | position: absolute; 51 | overflow: visible; 52 | padding-bottom: 24px; 53 | height: 100%; 54 | transition: all 0.3s ease; 55 | } 56 | .nav-portrait img { 57 | width: 120px; 58 | height: 120px; 59 | border-radius: 50%; 60 | transition: all 1s ease; 61 | } 62 | 63 | 64 | // 头像 65 | .nav-portrait { 66 | text-align: center; 67 | margin-bottom: 10px; 68 | margin-top: 20px; 69 | transition: all 3s ease; 70 | } 71 | .nav-portrait img { 72 | width: 120px; 73 | height: 120px; 74 | border-radius: 50%; 75 | transition: all 1s ease; 76 | } 77 | .nav-portrait-title { 78 | color: #ccc; 79 | text-align: center; 80 | cursor: pointer; 81 | transition: all 3s ease; 82 | } 83 | .nav-portrait-title:hover { 84 | text-decoration: underline; 85 | } 86 | .nav-portrait-name { 87 | color: #2db7f5; 88 | text-align: center; 89 | transition: all 3s ease; 90 | } 91 | 92 | 93 | .ant-layout-aside-collapse .ant-layout-sider > .ant-layout-portrait > .nav-portrait { 94 | height: 32px; 95 | transition: all 3s ease; 96 | } 97 | .ant-layout-aside-collapse .ant-layout-sider > .ant-layout-portrait > .nav-portrait img { 98 | width: 32px; 99 | height: 32px; 100 | transition: all 0s ease; 101 | } 102 | .ant-layout-aside-collapse .ant-layout-sider > .ant-layout-portrait > .nav-portrait-title, 103 | .ant-layout-aside-collapse .ant-layout-sider > .ant-layout-portrait > .nav-portrait-name { 104 | display: none; 105 | transition: all 3s ease; 106 | } 107 | -------------------------------------------------------------------------------- /src/store/modules/menu/menu_reducer.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { message } from 'antd'; 3 | import { createReducer } from '../../../util'; 4 | import types from '../../types'; 5 | import initialState from './menu_state'; 6 | import objectAssign from 'object-assign'; 7 | 8 | export default createReducer(initialState, { 9 | [`${types.GET_TOP_MENU}`]: (state, data, params) => { 10 | return objectAssign({}, state, { topMenu: params.topMenu }); 11 | }, 12 | 13 | [`${types.UPDATE_NAVPATH}`]: (state, data) => { 14 | 15 | let navpath = [], tmpOb, tmpKey, child, selectClass = 'ant-menu-item-selected'; 16 | /** 17 | * 判断 `Action` 传来的数据是否有 `面包屑` 信息 18 | * 19 | * `Menu` 控件传来的值 ['子级控件值', '父级控件值'],将数组倒序,先遍历父级标签 20 | * 如果不做方向操作,`child` 没有子级菜单列表,导致程序出错 21 | * 22 | * 判断如果是父级标签,查询菜单Json,取到对应Key的值。 23 | * 储存该菜单的子集菜单集合到 `child` 中 24 | * 将 `key`、`name` 添加到 `面包屑` 集合中 25 | * 26 | * 判断如果是子级标签,取出对应的Key值 27 | * 判断 `child` 是否有值, 28 | * 取到对应的子级数据中的Json 29 | * 将 `key`、`name` 添加到 `面包屑` 集合中 30 | * 31 | * 在面包屑组件中遍历 `navpath` 构建 `Breadcrumb` 标签的值,按顺序显示相关内容 32 | * 33 | * `selectClass` 用于解决同步菜单二级菜单点击时 `class` 选中状态的问题,目前没有二级菜单,所以暂时没有用处 34 | * 35 | */ 36 | if (data.data) { 37 | data.data.reverse().map((item) => { 38 | if (item.indexOf('sub') != -1) { 39 | tmpKey = item.replace('sub', ''); 40 | tmpOb = _.find(state.topMenu, function(o) { 41 | return o.key == tmpKey; 42 | }); 43 | child = tmpOb.child; 44 | navpath.push({ 45 | key: tmpOb.key, 46 | name: tmpOb.name 47 | }) 48 | // selectClass = '' 49 | } 50 | if (item.indexOf('menu') != -1) { 51 | tmpKey = item.replace('menu', ''); 52 | if (child) { 53 | tmpOb = _.find(child, function(o) { 54 | return o.key == tmpKey; 55 | }); 56 | } 57 | navpath.push({ 58 | key: tmpOb.key, 59 | name: tmpOb.name 60 | }) 61 | } 62 | }) 63 | } 64 | 65 | return objectAssign({}, state, { 66 | currentIndex: data.key * 1, 67 | navpath: navpath, 68 | selectClass: selectClass 69 | }); 70 | }, 71 | 72 | [`${types.UPDATE_STATUS}`]: (state, data) => { 73 | return objectAssign({}, state, { status: data.status }); 74 | }, 75 | 76 | [`${types.GET_LEFT_MENU}`]: (state, data, params) => { 77 | return objectAssign({}, state, { leftMenu: params.leftMenu }); 78 | }, 79 | 80 | [`${types.INIT_MENU}`]: (state, data, params) => { 81 | 82 | params.leftMenu.map((item) => { 83 | item.num = 0; 84 | }); 85 | 86 | return objectAssign({}, state, { leftMenuType: leftMenuType }); 87 | }, 88 | 89 | [`${types.UPDATE_COLLAPSE}`]: (state, data) => { 90 | return objectAssign({}, state, { collapse: !data.collapse }); 91 | }, 92 | 93 | [`${types.GET_ADD_CASE_LEFT_MENU}`]: (state, data) => { 94 | return objectAssign({}, state, { leftMenu: leftMenu }); 95 | }, 96 | }) -------------------------------------------------------------------------------- /src/feature/Feature2.jsx: -------------------------------------------------------------------------------- 1 | // 含有可操作 table 栏的数据展示 2 | import React from 'react'; 3 | 4 | import FeatureSetConfig from '../components/FeatureSetConfig'; 5 | 6 | import Immutable from 'immutable'; 7 | //https://github.com/ded/reqwest 8 | import Reqwest from 'reqwest'; 9 | 10 | 11 | const conf = { 12 | 13 | initData: function(callback){ 14 | 15 | let data = { 16 | type: 'entry_list', 17 | num: 20, 18 | ua: 'bd_1_1_1_5-5-0-0_1', 19 | cuid: '00000000000000000000000000000000%7C0000000000000000', 20 | channel: 'AA_0', 21 | dir: 'up' 22 | } 23 | 24 | Reqwest({ 25 | url: 'http://uil.cbs.baidu.com/rssfeed/fetch?fn=?', 26 | data: data, 27 | type: 'jsonp', 28 | jsonpCallback: 'fn', 29 | success: function (data) { 30 | let lists = data.data.stream_data; 31 | 32 | // 必须要向数据中 添加唯一的 key 33 | lists.forEach(function(ele) { 34 | ele.key = ele.docid; 35 | }); 36 | 37 | callback(lists); 38 | } 39 | }); 40 | 41 | }, 42 | 43 | Delete: function(data, callback){ 44 | 45 | let dataI = Immutable.fromJS({ 46 | type: 'entry_list' 47 | }).merge({id: data.key}); 48 | 49 | // ... 操作删除请求 50 | console.log(dataI.toJS()); 51 | 52 | // 模拟请求删除成功的回调 53 | setTimeout(function(){ 54 | callback(); 55 | }, 1000) 56 | 57 | }, 58 | 59 | columns: [ 60 | { 61 | title: 'DOCID', // table header 文案 62 | dataIndex: 'docid', // 数据对象内的属性,也做react vdom 的key 63 | type: 'string', // table 内显示的类型 64 | sort: true, // 是否需要排序 65 | width:200 // 列宽 可选 不填为默认均分 66 | }, { 67 | title: '标题', 68 | dataIndex: 'title', 69 | type: 'string' 70 | }, { 71 | title: '链接', 72 | dataIndex: 'link', 73 | type: 'link' 74 | }, { 75 | title: '操作', 76 | type: 'operate', // 操作的类型必须为 operate 77 | width: 80, 78 | btns: [{ 79 | text: '删除', 80 | type: 'delete' 81 | }, { 82 | text: '展示', 83 | callback: function(item){ 84 | alert(JSON.stringify(item)); 85 | } 86 | }], // 可选 87 | 88 | // 对应btns 的回调函数 89 | // item为操作的单一数据对象 90 | // callback 为组件的回调函数,将处理之后的数据回传 删除则传undefined 91 | // callbacks: [function(item, callback){ 92 | // item.docid = 0; 93 | // callback(item, 'update'); 94 | // },function(item, callback){ 95 | // callback(item, 'delete'); 96 | // }] 97 | } 98 | ] 99 | 100 | }; 101 | 102 | const Feature2 = FeatureSetConfig(conf); 103 | 104 | export default Feature2; 105 | -------------------------------------------------------------------------------- /src/feature/Feature1-1.jsx: -------------------------------------------------------------------------------- 1 | // 纯数据展现情况列表 2 | import React from 'react'; 3 | 4 | import FeatureSetConfig from '../components/FeatureSetConfig'; 5 | 6 | import Immutable from 'immutable'; 7 | import Reqwest from 'reqwest'; 8 | 9 | import testData from '../common/test-data'; 10 | 11 | // 增加(Create)、重新取得数据(Retrieve)、更新(Update)和删除(Delete) 12 | const table_conf = { 13 | 14 | type: 'tableList', // tableList graphList simpleObject complexObject 15 | 16 | // 初始化展现的数据,使用callback 回传列表数据 17 | // 需要手动添加唯一id key 18 | // callback 组件数据的回调函数(接受列表数据参数) 19 | initData: function(callback){ 20 | // 接口调用数据形式 21 | /* 22 | let data = { 23 | type: 'entry_list', 24 | num: 20, 25 | ua: 'bd_1_1_1_5-5-0-0_1', 26 | cuid: '00000000000000000000000000000000%7C0000000000000000', 27 | channel: 'AA_0', 28 | dir: 'up' 29 | } 30 | 31 | Reqwest({ 32 | url: 'http://uil.cbs.baidu.com/rssfeed/fetch?fn=?', 33 | data: data, 34 | type: 'jsonp', 35 | jsonpCallback: 'fn', 36 | success: function (data) { 37 | let lists = data.data.stream_data; 38 | 39 | // 必须要向数据中 添加唯一的 key 40 | lists.forEach(function(ele) { 41 | ele.key = ele.docid; 42 | }); 43 | 44 | callback(lists); 45 | } 46 | }); 47 | */ 48 | 49 | // 模拟数据 50 | setTimeout(function(){ 51 | let list = testData.tableList; 52 | list.forEach(function(ele) { 53 | ele.key = ele.docid; 54 | }); 55 | callback(list); 56 | }, 1000) 57 | }, 58 | 59 | // table 列表展现配置 60 | // { 61 | // title table显示表题 62 | // dataIndex 显示数据中的key 63 | // type 展现形式 (string image link) 64 | // render 自定义展现形式 参数 (当前数据,当前对象数据) 65 | // sort 是否需要排序功能 66 | // width 自定义该列宽度 否则等分 67 | // } 68 | // 69 | // table 列表头标题 70 | columns: [ 71 | { 72 | title: 'DOCID', // table header 文案 73 | dataIndex: 'docid', // 数据对象内的属性,也做react vdom 的key 74 | type: 'string', // table 内显示的类型 75 | sort: true, // 是否需要排序 76 | width:200 77 | }, { 78 | title: '标题', 79 | dataIndex: 'title', 80 | type: 'string' 81 | }, { 82 | title: '链接', 83 | dataIndex: 'link', 84 | type: 'link', 85 | render: (text) => ( 86 | 链接 87 | ), 88 | width: 50 89 | },{ 90 | title: '日期', 91 | dataIndex: 'date', 92 | type: 'string', 93 | width: 150 94 | },{ 95 | title: '图片', 96 | dataIndex: 'img', 97 | type: 'image' 98 | } 99 | ] 100 | 101 | }; 102 | 103 | const Feature1 = FeatureSetConfig(table_conf); 104 | 105 | export default Feature1; 106 | -------------------------------------------------------------------------------- /src/components/Loding/index.less: -------------------------------------------------------------------------------- 1 | .boxflex{display:box;display:-webkit-box;} 2 | .center{display:box;display:-webkit-box;-webkit-box-pack:center;-webkit-box-align:center;} 3 | .box{width:200px;height:200px;border:1px solid #ccc;position:relative;} 4 | .move{animation:move 2s infinite;-webkit-animation:move 2s infinite;} 5 | .scale{animation:scale 1s infinite;-webkit-animation:scale 1s infinite;} 6 | .line{animation:line 1s infinite;-webkit-animation:line 1s infinite;} 7 | .fz{animation:fz 1.5s infinite;-webkit-animation:fz 1.5s infinite;} 8 | .delay1{animation-delay:0.25s;-webkit-animation-delay:0.25s;} 9 | .delay2{animation-delay:0.5s;-webkit-animation-delay:0.5s;} 10 | .load{width:100px;height:100px;border:10px solid #ccc;border-radius:50%;display:block;} 11 | .load1{border-top:10px solid #64efb9;} 12 | .load2{position:relative;} 13 | .load2:before{position:absolute;top:0;left:50%;margin-left:-10px;margin-top:-15px;width:20px;height:20px;border-radius:50%;background:#fff;content:'';box-shadow:0 0 10px #747373;} 14 | .sc,.li{display:block;} 15 | .sc i{width:20px;height:20px;background:#64efb9;border-radius:50%;display:inline-block;margin:0 10px;} 16 | .li i{display:inline-block;background:#64efb9;width:10px;height:50px;margin:0 4px;} 17 | .li em{background:#fff;border:10px solid #64efb9;width:50px;height:50px;display:inline-block;} 18 | .li i:nth-child(2){animation-delay:.2s;-webkit-animation-delay:.2s;} 19 | .li i:nth-child(3){animation-delay:.3s;-webkit-animation-delay:.3s;} 20 | .li i:nth-child(4){animation-delay:.4s;-webkit-animation-delay:.4s;} 21 | .li i:nth-child(5){animation-delay:.5s;-webkit-animation-delay:.5s;} 22 | .li i:nth-child(6){animation-delay:.6s;-webkit-animation-delay:.6s;} 23 | 24 | @keyframes move{ 25 | 0%{ 26 | transform:rotateZ(0); 27 | } 28 | 100%{ 29 | transform:rotateZ(360deg); 30 | } 31 | } 32 | @-webkit-keyframes move{ 33 | 0%{ 34 | -webkit-transform:rotateZ(0); 35 | } 36 | 100%{ 37 | -webkit-transform:rotateZ(360deg); 38 | } 39 | } 40 | @keyframes scale{ 41 | /*0%{ 42 | transform:scale3d(1,1,1); 43 | }*/ 44 | 50%{ 45 | transform:scale3d(0,0,0); 46 | } 47 | /*100%{ 48 | transform:scale3d(1,1,1); 49 | }*/ 50 | } 51 | @-webkit-keyframes scale{ 52 | /*0%{ 53 | -webkit-transform:scale3d(1,1,1); 54 | }*/ 55 | 50%{ 56 | -webkit-transform:scale3d(0,0,0); 57 | } 58 | /*100%{ 59 | -webkit-transform:scale3d(1,1,1); 60 | }*/ 61 | } 62 | @keyframes line{ 63 | 50%{ 64 | transform:scaleY(0); 65 | } 66 | } 67 | @-webkit-keyframes line{ 68 | 50%{ 69 | -webkit-transform:scaleY(0); 70 | } 71 | } 72 | @keyframes fz{ 73 | 0%{ 74 | transform:perspective(160px); 75 | } 76 | 50%{ 77 | transform:perspective(160px) rotateX(-180deg) rotateY(0); 78 | } 79 | 100%{ 80 | transform:perspective(160px) rotateX(-180deg) rotateY(-180deg); 81 | } 82 | } 83 | @-webkit-keyframes fz{ 84 | 0%{ 85 | -webkit-transform:perspective(160px); 86 | } 87 | 50%{ 88 | -webkit-transform:perspective(160px) rotateX(-180deg) rotateY(0); 89 | } 90 | 100%{ 91 | -webkit-transform:perspective(160px) rotateX(-180deg) rotateY(-180deg); 92 | } 93 | } -------------------------------------------------------------------------------- /src/feature/Feature1-3.jsx: -------------------------------------------------------------------------------- 1 | // 纯数据展现情况列表 2 | import React from 'react'; 3 | 4 | import FeatureSetConfig from '../components/FeatureSetConfig'; 5 | 6 | import Immutable from 'immutable'; 7 | import Reqwest from 'reqwest'; 8 | 9 | import testData from '../common/test-data'; 10 | 11 | const graph_conf = { 12 | 13 | type: 'graphList', // tableList graphList simpleObject complexObject 14 | EchartStyle: { 15 | width: '100%', 16 | height: '450px' 17 | }, 18 | 19 | // 初始化展现的数据,使用callback 回传列表数据 20 | // 需要手动添加唯一id key 21 | // callback 组件数据的回调函数(接受列表数据参数) 22 | initData: function(callback){ 23 | 24 | // 模拟数据 25 | setTimeout(function(){ 26 | let series = testData.graphList; 27 | series.forEach(function(item) { 28 | item.type = 'line'; 29 | item.stack = '总量' 30 | }); 31 | 32 | callback(series); 33 | }, 1000) 34 | }, 35 | 36 | // 参考echarts 参数 37 | option : { 38 | title: { 39 | text: '堆叠区域图' 40 | }, 41 | tooltip : { 42 | trigger: 'axis' 43 | }, 44 | legend: { 45 | data:['邮件营销','联盟广告','视频广告'] 46 | }, 47 | toolbox: { 48 | feature: { 49 | saveAsImage: {} 50 | } 51 | }, 52 | grid: { 53 | left: '3%', 54 | right: '4%', 55 | bottom: '3%', 56 | containLabel: true 57 | }, 58 | xAxis : [ 59 | { 60 | type : 'category', 61 | boundaryGap : false, 62 | data : ['周一','周二','周三','周四','周五','周六','周日'] 63 | } 64 | ], 65 | yAxis : [ 66 | { 67 | type : 'value' 68 | } 69 | ] 70 | } 71 | 72 | }; 73 | 74 | const graph_conf2 = { 75 | 76 | type: 'graphList', // tableList graphList simpleObject complexObject 77 | style: { 78 | width: '100%', 79 | height: '450px' 80 | }, 81 | 82 | // 初始化展现的数据,使用callback 回传列表数据 83 | // 需要手动添加唯一id key 84 | // callback 组件数据的回调函数(接受列表数据参数) 85 | initData: function(callback){ 86 | 87 | // 模拟数据 88 | setTimeout(function(){ 89 | let series = [ 90 | { 91 | name: '访问来源', 92 | type: 'pie', 93 | radius : '55%', 94 | center: ['50%', '60%'], 95 | data:[ 96 | {value:335, name:'直接访问'}, 97 | {value:310, name:'邮件营销'}, 98 | {value:234, name:'联盟广告'}, 99 | {value:135, name:'视频广告'}, 100 | {value:1548, name:'搜索引擎'} 101 | ], 102 | itemStyle: { 103 | emphasis: { 104 | shadowBlur: 10, 105 | shadowOffsetX: 0, 106 | shadowColor: 'rgba(0, 0, 0, 0.5)' 107 | } 108 | } 109 | } 110 | ] 111 | 112 | callback(series); 113 | }, 1000) 114 | }, 115 | 116 | // 参考echarts 参数 117 | option : { 118 | title : { 119 | text: '某站点用户访问来源', 120 | subtext: '纯属虚构', 121 | x:'center' 122 | }, 123 | tooltip : { 124 | trigger: 'item', 125 | formatter: "{a}
{b} : {c} ({d}%)" 126 | }, 127 | legend: { 128 | orient: 'vertical', 129 | left: 'left', 130 | data: ['直接访问','邮件营销','联盟广告','视频广告','搜索引擎'] 131 | } 132 | } 133 | 134 | }; 135 | 136 | const Feature = FeatureSetConfig(graph_conf2); 137 | 138 | export default Feature; 139 | -------------------------------------------------------------------------------- /src/views/Login/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Form, Input, Button, Row, Col, notification, Checkbox } from 'antd'; 3 | import { bindActionCreators } from 'redux'; 4 | import { connect } from 'react-redux'; 5 | 6 | import { login } from '../../store/modules/user/user_action'; 7 | 8 | const FormItem = Form.Item; 9 | 10 | import styles from './index.less' 11 | 12 | const propTypes = { 13 | user: PropTypes.string, 14 | loggingIn: PropTypes.bool, 15 | loginErrors: PropTypes.string 16 | }; 17 | 18 | const contextTypes = { 19 | router: PropTypes.object.isRequired, 20 | store: PropTypes.object.isRequired 21 | }; 22 | 23 | class Login extends React.Component { 24 | 25 | constructor(props) { 26 | super(props) 27 | } 28 | 29 | /** 30 | * 监听props变化 31 | * 32 | * 1、判断登入失败 33 | * 2、判断登入成功 34 | * 3、跳转默认页面 35 | * 36 | * @param {any} nextProps 37 | */ 38 | componentWillReceiveProps(nextProps) { 39 | const error = nextProps.loginErrors; 40 | const isLoggingIn = nextProps.loggingIn; 41 | const user = nextProps.user 42 | 43 | if (error != this.props.loginErrors && error) { 44 | notification.error({ 45 | message: 'Login Fail', 46 | description: error 47 | }); 48 | } 49 | 50 | if (!isLoggingIn && !error && user) { 51 | notification.success({ 52 | message: 'Login Success', 53 | description: 'Welcome ' + user, 54 | duration: 2, 55 | }); 56 | } 57 | 58 | if (user) { 59 | this.context.router.replace('/home'); 60 | } 61 | } 62 | 63 | /** 64 | * 提交登入请求 65 | * 66 | * @param {any} e 67 | */ 68 | handleSubmit(e) { 69 | e.preventDefault() 70 | const data = this.props.form.getFieldsValue() 71 | this.props.login(data.user, data.password) 72 | } 73 | 74 | render() { 75 | const { getFieldProps } = this.props.form 76 | return ( 77 | 78 | 79 |
80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 记住账号密码 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 |
111 | 112 |
113 | ) 114 | } 115 | } 116 | 117 | Login.contextTypes = contextTypes; 118 | Login.propTypes = propTypes; 119 | Login = Form.create()(Login); 120 | 121 | function mapStateToProps(state) { 122 | const {user} = state; 123 | if (user.user) { 124 | return { user: user.user, loggingIn: user.loggingIn, loginErrors: '' }; 125 | } 126 | 127 | return { user: null, loggingIn: user.loggingIn, loginErrors: user.loginErrors }; 128 | } 129 | 130 | function mapDispatchToProps(dispatch) { 131 | return { 132 | login: bindActionCreators(login, dispatch) 133 | } 134 | } 135 | 136 | export default connect(mapStateToProps, mapDispatchToProps)(Login) 137 | -------------------------------------------------------------------------------- /src/components/RetrieveForm.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form, Select, Input, Button, Icon , DatePicker, TimePicker, Radio, Switch} from 'antd'; 3 | import { Upload, Modal, message } from 'antd'; 4 | 5 | const FormItem = Form.Item; 6 | const Option = Select.Option; 7 | const RadioGroup = Radio.Group; 8 | 9 | let RForm = React.createClass({ 10 | render: function() { 11 | const self = this; 12 | const RType = this.props.RType; 13 | return RType ? 14 | (
15 |
16 | { 17 | RType.map(function(item){ 18 | return self.dealConfigRType(item); 19 | }) 20 | } 21 | 22 | 23 | 24 |
25 |
): 26 |
; 27 | }, 28 | 29 | dealConfigRType: function(item){ 30 | const { getFieldProps } = this.props.form; 31 | 32 | switch (item.type){ 33 | case 'string': 34 | return 37 | 39 | 40 | break; 41 | 42 | case 'date': 43 | return 46 | 47 | 48 | break; 49 | 50 | case 'select': 51 | return 54 | 61 | 62 | break; 63 | 64 | case 'radio': 65 | return 68 | 69 | { 70 | item.options.map(function(item){ 71 | return {item.text} 72 | }) 73 | } 74 | 75 | 76 | break; 77 | 78 | case 'switch': 79 | return 82 | 83 | 84 | break; 85 | 86 | default: 87 | return ''; 88 | break; 89 | } 90 | }, 91 | 92 | handleRetrieve: function(){ 93 | 94 | console.log('收到表单值:', this.props.form.getFieldsValue()); 95 | this.props.submit(this.props.form.getFieldsValue()); 96 | } 97 | }); 98 | RForm = Form.create()(RForm); 99 | 100 | export default RForm; 101 | -------------------------------------------------------------------------------- /src/feature/Feature1.jsx: -------------------------------------------------------------------------------- 1 | // 纯数据展现情况列表 2 | import React from 'react'; 3 | 4 | import FeatureSetConfig from '../components/FeatureSetConfig'; 5 | 6 | // import Immutable from 'immutable'; 7 | // import Reqwest from 'reqwest'; 8 | 9 | import testData from '../common/test-data'; 10 | 11 | // 增加(Create)、重新取得数据(Retrieve)、更新(Update)和删除(Delete) 12 | const table_conf = { 13 | 14 | type: 'tableList', // tableList graphList simpleObject complexObject 15 | 16 | // 初始化展现的数据,使用callback 回传列表数据 17 | // 需要手动添加唯一id key 18 | // callback 组件数据的回调函数(接受列表数据参数) 19 | initData: function(callback){ 20 | // 接口调用数据形式 21 | /* 22 | let data = { 23 | type: 'entry_list', 24 | num: 20, 25 | ua: 'bd_1_1_1_5-5-0-0_1', 26 | cuid: '00000000000000000000000000000000%7C0000000000000000', 27 | channel: 'AA_0', 28 | dir: 'up' 29 | } 30 | 31 | Reqwest({ 32 | url: 'http://uil.cbs.baidu.com/rssfeed/fetch?fn=?', 33 | data: data, 34 | type: 'jsonp', 35 | jsonpCallback: 'fn', 36 | success: function (data) { 37 | let lists = data.data.stream_data; 38 | 39 | // 必须要向数据中 添加唯一的 key 40 | lists.forEach(function(ele) { 41 | ele.key = ele.docid; 42 | }); 43 | 44 | callback(lists); 45 | } 46 | }); 47 | */ 48 | 49 | // 模拟数据 50 | setTimeout(function(){ 51 | let list = testData.tableList; 52 | list.forEach(function(ele) { 53 | ele.key = ele.docid; 54 | }); 55 | callback(list); 56 | }, 1000) 57 | }, 58 | 59 | // table 列表展现配置 60 | // { 61 | // title table显示表题 62 | // dataIndex 显示数据中的key 63 | // type 展现形式 (string image link) 64 | // render 自定义展现形式 参数 (当前数据,当前对象数据) 65 | // sort 是否需要排序功能 66 | // width 自定义该列宽度 否则等分 67 | // } 68 | // 69 | columns: [ 70 | { 71 | title: 'DOCID', 72 | dataIndex: 'docid', 73 | type: 'string' 74 | }, { 75 | title: '标题', 76 | dataIndex: 'title', 77 | type: 'string' 78 | }, { 79 | title: '链接', 80 | dataIndex: 'link', 81 | type: 'link', 82 | render: (text, item) => ({item.title}) // 可自定义 83 | } 84 | ] 85 | 86 | }; 87 | 88 | const simple_conf = { 89 | 90 | type: 'simpleObject', 91 | 92 | initData: function(callback){ 93 | // 模拟数据 94 | setTimeout(function(){ 95 | let object = testData.simpleObject; 96 | object.key = object.docid; 97 | 98 | callback(object); 99 | }, 1000) 100 | }, 101 | 102 | operate:[ 103 | { 104 | text: '确认数据', 105 | style: { 106 | 'marginRight': '30px', 107 | 'marginLeft': '80px' 108 | }, 109 | callback: function(item){ 110 | console.log(item) 111 | } 112 | }, { 113 | text: '展示数据', 114 | callback: function(item){ 115 | console.log(item) 116 | } 117 | } 118 | ], 119 | 120 | UType:[ 121 | { 122 | name: 'docid', 123 | label: '唯一标识', 124 | type: 'string', 125 | placeholder: '请输入标示名称' 126 | },{ 127 | name: 'title', 128 | label: '标题', 129 | type: 'string', 130 | placeholder: '请输入标示名称' 131 | },{ 132 | name: 'link', 133 | label: '链接', 134 | type: 'string' 135 | },{ 136 | name: 'date', 137 | label: '日期', 138 | type: 'date' 139 | },{ 140 | name: 'img', 141 | label: '图片', 142 | type: 'imageUpload' 143 | } 144 | ] 145 | } 146 | 147 | const Feature1 = FeatureSetConfig(simple_conf); 148 | 149 | export default Feature1; 150 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { bindActionCreators } from 'redux' 3 | import { connect } from 'react-redux' 4 | import { Row, Col, Icon, Menu, Dropdown } from 'antd' 5 | import styles from './index.less' 6 | // import styles from './index.less'; 7 | import { Link } from 'react-router' 8 | 9 | import * as menu from '../../store/modules/menu/menu_action' 10 | 11 | import logo from './logo.png' 12 | 13 | const SubMenu = Menu.SubMenu; 14 | const MenuItemGroup = Menu.ItemGroup; 15 | const DropdownButton = Dropdown.Button; 16 | 17 | const defaultProps = { 18 | topMenu: [], 19 | currentIndex: 0 20 | } 21 | 22 | const propTypes = { 23 | topMenu: PropTypes.array, 24 | currentIndex: PropTypes.number 25 | } 26 | 27 | class Header extends React.Component { 28 | constructor () { 29 | super() 30 | this.onCollapseChange = this.onCollapseChange.bind(this); 31 | this.menuClickHandle = this.menuClickHandle.bind(this); 32 | } 33 | 34 | componentDidMount () { 35 | /** 36 | * 初始化头部菜单,通过服务端获取。 37 | */ 38 | this.props.menu.getTopMenu() 39 | } 40 | 41 | menuClickHandle (item) { 42 | /** 43 | * 加入二级导航时,可以通过判断字符串头标记来定位是住导航还是子导航 44 | * 45 | */ 46 | // if (item.key.indexOf('sub') != -1) { 47 | // console.log('是主导航') 48 | // } else { 49 | // console.log('是副级导航'); 50 | // } 51 | 52 | /** 53 | * 特定功能:返回旧版 54 | * 55 | * 判断 `key = index.html` 则跳转旧版 56 | * 因为在导航前加入了标记,所以这里判断时也需要加入 `sub` 标记 57 | * 58 | */ 59 | 60 | /** 61 | * 默认情况更改 `面包屑` 信息 62 | */ 63 | // this.props.menu.updateNavPath(item.keyPath, item.key) 64 | } 65 | 66 | onCollapseChange(collapse) { 67 | /** 68 | * 侧栏切换功能 69 | * 70 | * 点击特定按钮更改侧栏状态,实现侧栏切换功能 71 | * 通过判断侧栏状态,显示不同的class 72 | * 该 `class` 在 `View/App` 下: `
` 73 | * 该功能可以放在任意位置,目前放在顶部实现 74 | * 75 | */ 76 | // this.props.menu.updateCollapse(this.props.collapse) 77 | } 78 | 79 | render () { 80 | const { topMenu } = this.props; 81 | console.log('topMenu', topMenu); 82 | const menu = topMenu.map((item) => { 83 | /** 84 | * 遍历 `topMenu` 显示顶部信息 85 | * 86 | * `sub` 标记为是主导航 87 | * `key` 用于页面跳转与记录唯一 88 | * `name` 标签显示内容 89 | * 90 | */ 91 | return ( 92 | 93 | 94 | 95 | {item.name} 96 | 97 | 98 | ) 99 | }); 100 | 101 | // return ( 102 | //
103 | //
104 | //
105 | // 110 | // { menu } 111 | // 112 | //
113 | //
114 | // ) 115 | return ( 116 |
117 |
118 | 123 | { menu } 124 | 125 |
126 |
127 | ) 128 | } 129 | } 130 | 131 | Header.propTypes = propTypes; 132 | Header.defaultProps = defaultProps; 133 | 134 | const mapStateToProps = (state) => { 135 | console.log('state', state); 136 | return { 137 | topMenu : state.menu.topMenu, 138 | // currentIndex : state.menu.currentIndex, 139 | // collapse : state.menu.collapse, 140 | // selectKey : state.menu.selectKey 141 | }; 142 | }; 143 | 144 | function mapDispatchToProps(dispatch) { 145 | return { 146 | menu: bindActionCreators(menu, dispatch), 147 | }; 148 | } 149 | 150 | export default connect(mapStateToProps, mapDispatchToProps)(Header); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-redux-antd-ie8 2 | 3 | ## 特性 4 | * [react](https://github.com/facebook/react) 5 | * [redux](https://github.com/rackt/redux) 6 | * [react-router](https://github.com/rackt/react-router) 7 | * [react-router-redux](https://github.com/rackt/react-router-redux) 8 | * [webpack](https://github.com/webpack/webpack) 9 | * [babel](https://github.com/babel/babel) 10 | * [antd](http://ant.design) 11 | 12 | ## Code Style 13 | 14 | https://github.com/airbnb/javascript 15 | 16 | ## `npm` 命令 17 | 18 | |`npm run