Jelajahi Sumber

Dva@2 (#13)

* temp save

* upgrade to dva@2

* update

* fix ci

* remove useless Link & add missing `to` prop setting
ddcat1115 8 tahun lalu
induk
melakukan
cfc0aea567

+ 12 - 0
mock/api.js

@@ -113,6 +113,8 @@ export const getNotice = [
     description: '那是一种内在的东西, 他们到达不了,也无法触及的',
     updatedAt: new Date(),
     member: '科学搬砖组',
+    href: '',
+    memberLink: '',
   },
   {
     id: 'xxx2',
@@ -121,6 +123,8 @@ export const getNotice = [
     description: '希望是一个好东西,也许是最好的,好东西是不会消亡的',
     updatedAt: new Date('2017-07-24 11:00:00'),
     member: '全组都是吴彦祖',
+    href: '',
+    memberLink: '',
   },
   {
     id: 'xxx3',
@@ -129,6 +133,8 @@ export const getNotice = [
     description: '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆',
     updatedAt: new Date(),
     member: '中二少女团',
+    href: '',
+    memberLink: '',
   },
   {
     id: 'xxx4',
@@ -137,6 +143,8 @@ export const getNotice = [
     description: '那时候我只会想自己想要什么,从不想自己拥有什么',
     updatedAt: new Date('2017-07-23 06:23:00'),
     member: '程序员日常',
+    href: '',
+    memberLink: '',
   },
   {
     id: 'xxx5',
@@ -145,6 +153,8 @@ export const getNotice = [
     description: '凛冬将至',
     updatedAt: new Date('2017-07-23 06:23:00'),
     member: '高逼格设计天团',
+    href: '',
+    memberLink: '',
   },
   {
     id: 'xxx6',
@@ -153,6 +163,8 @@ export const getNotice = [
     description: '生命就像一盒巧克力,结果往往出人意料',
     updatedAt: new Date('2017-07-23 06:23:00'),
     member: '骗你来学计算机',
+    href: '',
+    memberLink: '',
   },
 ];
 

+ 4 - 3
package.json

@@ -17,15 +17,16 @@
   },
   "dependencies": {
     "antd": "next",
+    "dva": "^2.0.3",
     "classnames": "^2.2.5",
-    "dva": "^1.2.1",
     "lodash": "^4.17.4",
     "numeral": "^2.0.6",
     "prop-types": "^15.5.10",
     "qs": "^6.5.0",
     "react": "^15.6.2",
     "react-document-title": "^2.0.3",
-    "react-dom": "^15.6.2"
+    "react-dom": "^15.6.2",
+    "lodash.clonedeep": "^4.5.0"
   },
   "devDependencies": {
     "babel-eslint": "^8.0.1",
@@ -56,7 +57,7 @@
     "nightmare": "^2.10.0",
     "react-test-renderer": "^15.6.2",
     "redbox-react": "^1.3.2",
-    "roadhog": "^1.0.2",
+    "roadhog": "^1.2.1",
     "roadhog-api-doc": "^0.1.8",
     "stylelint": "^8.1.0",
     "stylelint-config-standard": "^17.0.0"

+ 2 - 0
src/common/nav.js

@@ -34,6 +34,7 @@ import RegisterResult from '../routes/User/RegisterResult';
 
 const data = [{
   component: BasicLayout,
+  layout: 'BasicLayout',
   name: '首页', // for breadcrumb
   path: '',
   children: [{
@@ -152,6 +153,7 @@ const data = [{
   }],
 }, {
   component: UserLayout,
+  layout: 'UserLayout',
   children: [{
     name: '帐户',
     icon: 'user',

+ 27 - 1
src/components/PageHeader/index.js

@@ -18,6 +18,8 @@ export default class PageHeader extends PureComponent {
   static contextTypes = {
     routes: PropTypes.array,
     params: PropTypes.object,
+    location: PropTypes.object,
+    breadcrumbNameMap: PropTypes.object,
   };
   onChange = (key) => {
     if (this.props.onTabChange) {
@@ -28,10 +30,12 @@ export default class PageHeader extends PureComponent {
     return {
       routes: this.props.routes || this.context.routes,
       params: this.props.params || this.context.params,
+      location: this.props.location || this.context.location,
+      breadcrumbNameMap: this.props.breadcrumbNameMap || this.context.breadcrumbNameMap,
     };
   };
   render() {
-    const { routes, params } = this.getBreadcrumbProps();
+    const { routes, params, location, breadcrumbNameMap } = this.getBreadcrumbProps();
     const { title, logo, action, content, extraContent,
       breadcrumbList, tabList, className } = this.props;
     const clsString = classNames(styles.pageHeader, className);
@@ -45,6 +49,28 @@ export default class PageHeader extends PureComponent {
           itemRender={itemRender}
         />
       );
+    } else if (location && location.pathname) {
+      const pathSnippets = location.pathname.split('/').filter(i => i);
+      const extraBreadcrumbItems = pathSnippets.map((_, index) => {
+        const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
+        return (
+          <Breadcrumb.Item key={url}>
+            <Link to={url}>
+              {breadcrumbNameMap[url] || breadcrumbNameMap[url.replace('/', '')] || url}
+            </Link>
+          </Breadcrumb.Item>
+        );
+      });
+      const breadcrumbItems = [(
+        <Breadcrumb.Item key="home">
+          <Link to="/">Home</Link>
+        </Breadcrumb.Item>
+      )].concat(extraBreadcrumbItems);
+      breadcrumb = (
+        <Breadcrumb className={styles.breadcrumb}>
+          {breadcrumbItems}
+        </Breadcrumb>
+      );
     } else if (breadcrumbList && breadcrumbList.length) {
       breadcrumb = (
         <Breadcrumb className={styles.breadcrumb}>

+ 2 - 3
src/index.js

@@ -1,9 +1,8 @@
 import dva from 'dva';
 import G2 from 'g2';
-// import { browserHistory } from 'dva/router';
 import 'moment/locale/zh-cn';
 import models from './models';
-
+// import { browserHistory } from 'dva/router';
 import './index.less';
 
 G2.track(false);
@@ -16,7 +15,7 @@ const app = dva({
 // 2. Plugins
 // app.use({});
 
-// 3. Model
+// 3. Model move to router
 models.forEach((m) => {
   app.model(m);
 });

+ 36 - 14
src/layouts/BasicLayout.js

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import { Layout, Menu, Icon, Avatar, Dropdown, Tag, message, Spin } from 'antd';
 import DocumentTitle from 'react-document-title';
 import { connect } from 'dva';
-import { Link, routerRedux } from 'dva/router';
+import { Link, routerRedux, Route, Redirect, Switch } from 'dva/router';
 import moment from 'moment';
 import groupBy from 'lodash/groupBy';
 import styles from './BasicLayout.less';
@@ -11,14 +11,15 @@ import HeaderSearch from '../components/HeaderSearch';
 import NoticeIcon from '../components/NoticeIcon';
 import GlobalFooter from '../components/GlobalFooter';
 import { getNavData } from '../common/nav';
+import { getRouteData } from '../utils/utils';
 
 const { Header, Sider, Content } = Layout;
 const { SubMenu } = Menu;
 
 class BasicLayout extends React.PureComponent {
   static childContextTypes = {
-    routes: PropTypes.array,
-    params: PropTypes.object,
+    location: PropTypes.object,
+    breadcrumbNameMap: PropTypes.object,
   }
   constructor(props) {
     super(props);
@@ -29,8 +30,14 @@ class BasicLayout extends React.PureComponent {
     };
   }
   getChildContext() {
-    const { routes, params } = this.props;
-    return { routes, params };
+    const { location } = this.props;
+    const routeData = getRouteData('BasicLayout');
+    const menuData = getNavData().reduce((arr, current) => arr.concat(current.children), []);
+    const breadcrumbNameMap = {};
+    routeData.concat(menuData).forEach((item) => {
+      breadcrumbNameMap[item.path] = item.name;
+    });
+    return { location, breadcrumbNameMap };
   }
   componentDidMount() {
     this.props.dispatch({
@@ -98,13 +105,15 @@ class BasicLayout extends React.PureComponent {
     });
   }
   getPageTitle() {
-    const { routes } = this.props;
-    for (let i = routes.length - 1; i >= 0; i -= 1) {
-      if (routes[i].breadcrumbName) {
-        return `${routes[i].breadcrumbName} - Ant Design Pro`;
+    const { location } = this.props;
+    const { pathname } = location;
+    let title = 'Ant Design Pro';
+    getRouteData('UserLayout').forEach((item) => {
+      if (item.path === pathname) {
+        title = `${item.name} - Ant Design Pro`;
       }
-    }
-    return 'Ant Design Pro';
+    });
+    return title;
   }
   getNoticeData() {
     const { notices = [] } = this.props;
@@ -161,7 +170,7 @@ class BasicLayout extends React.PureComponent {
     }
   }
   render() {
-    const { children, currentUser, collapsed, fetchingNotices } = this.props;
+    const { currentUser, collapsed, fetchingNotices } = this.props;
 
     const menu = (
       <Menu className={styles.menu} selectedKeys={[]} onClick={this.onMenuClick}>
@@ -171,7 +180,6 @@ class BasicLayout extends React.PureComponent {
         <Menu.Item key="logout"><Icon type="logout" />退出登录</Menu.Item>
       </Menu>
     );
-
     const noticeData = this.getNoticeData();
 
     // Don't show popup menu when it is been collapsed
@@ -270,7 +278,21 @@ class BasicLayout extends React.PureComponent {
               </div>
             </Header>
             <Content style={{ margin: '24px 24px 0', height: '100%' }}>
-              {children}
+              <Switch>
+                {
+                  getRouteData('BasicLayout').map(item =>
+                    (
+                      <Route
+                        exact={item.exact}
+                        key={item.path}
+                        path={item.path}
+                        component={item.component}
+                      />
+                    )
+                  )
+                }
+                <Redirect to="/dashboard/analysis" />
+              </Switch>
               <GlobalFooter
                 links={[{
                   title: '帮助',

+ 25 - 14
src/layouts/UserLayout.js

@@ -1,10 +1,11 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { Link } from 'dva/router';
+import { Link, Route } from 'dva/router';
 import DocumentTitle from 'react-document-title';
 import { Icon } from 'antd';
 import GlobalFooter from '../components/GlobalFooter';
 import styles from './UserLayout.less';
+import { getRouteData } from '../utils/utils';
 
 const links = [{
   title: '帮助',
@@ -21,25 +22,24 @@ const copyright = <div>Copyright <Icon type="copyright" /> 2017 蚂蚁金服体
 
 class UserLayout extends React.PureComponent {
   static childContextTypes = {
-    routes: PropTypes.array,
-    params: PropTypes.object,
+    location: PropTypes.object,
   }
   getChildContext() {
-    const { routes, params } = this.props;
-    return { routes, params };
+    const { location } = this.props;
+    return { location };
   }
   getPageTitle() {
-    const { routes } = this.props;
-    for (let i = routes.length - 1; i >= 0; i -= 1) {
-      if (routes[i].breadcrumbName) {
-        return `${routes[i].breadcrumbName} - Ant Design Pro`;
+    const { location } = this.props;
+    const { pathname } = location;
+    let title = 'Ant Design Pro';
+    getRouteData('UserLayout').forEach((item) => {
+      if (item.path === pathname) {
+        title = `${item.name} - Ant Design Pro`;
       }
-    }
-    return 'Ant Design Pro';
+    });
+    return title;
   }
   render() {
-    const { children } = this.props;
-
     return (
       <DocumentTitle title={this.getPageTitle()}>
         <div className={styles.container}>
@@ -52,7 +52,18 @@ class UserLayout extends React.PureComponent {
             </div>
             <p className={styles.desc}>Ant Design 是西湖区最具影响力的 Web 设计规范</p>
           </div>
-          {children}
+          {
+            getRouteData('UserLayout').map(item =>
+              (
+                <Route
+                  exact={item.exact}
+                  key={item.path}
+                  path={item.path}
+                  component={item.component}
+                />
+              )
+            )
+          }
           <GlobalFooter className={styles.footer} links={links} copyright={copyright} />
         </div>
       </DocumentTitle>

+ 8 - 37
src/router.js

@@ -1,48 +1,19 @@
 import React from 'react';
-import { Router, Route, Redirect } from 'dva/router';
+import { Router, Route, Switch, Redirect } from 'dva/router';
 import { LocaleProvider } from 'antd';
 import zhCN from 'antd/lib/locale-provider/zh_CN';
-import navData from './common/nav';
-
-function getRoutes(data, level = 0) {
-  return data.map((item, i) => {
-    let children;
-    if (item.children) {
-      children = getRoutes(item.children, level + 1);
-    }
-    let homePageRedirect;
-    if (level === 1 && i === 0) {
-      let indexPath;
-      // First children router
-      if (item.children && item.children[0]) {
-        indexPath = `/${item.path}/${item.children[0].path}`;
-      } else {
-        indexPath = item.path;
-      }
-      homePageRedirect = <Redirect from="/" to={indexPath} />;
-    }
-    if (item.noRoute) {
-      return null;
-    }
-    return (
-      <Route
-        key={item.key || item.path || ''}
-        path={item.path}
-        breadcrumbName={item.name}
-        component={item.component}
-      >
-        {homePageRedirect}
-        {children}
-      </Route>
-    );
-  });
-}
+import BasicLayout from './layouts/BasicLayout';
+import UserLayout from './layouts/UserLayout';
 
 function RouterConfig({ history }) {
   return (
     <LocaleProvider locale={zhCN}>
       <Router history={history}>
-        {getRoutes(navData)}
+        <Switch>
+          <Route path="/user" component={UserLayout} />
+          <Route path="/" component={BasicLayout} />
+          <Redirect to="/" />
+        </Switch>
       </Router>
     </LocaleProvider>
   );

+ 24 - 17
src/routes/Forms/StepForm/index.js

@@ -1,8 +1,10 @@
-import React, { cloneElement, PureComponent } from 'react';
+import React, { PureComponent } from 'react';
 import { connect } from 'dva';
 import { Card, Steps, Form } from 'antd';
 import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
 import Step1 from './Step1';
+import Step2 from './Step2';
+import Step3 from './Step3';
 import styles from '../style.less';
 
 const { Step } = Steps;
@@ -10,16 +12,26 @@ const { Step } = Steps;
 @Form.create()
 class StepForm extends PureComponent {
   getCurrentStep() {
-    const { routes } = this.props;
-    switch (routes[routes.length - 1].path) {
+    const { location } = this.props;
+    const { pathname } = location;
+    const pathList = pathname.split('/');
+    switch (pathList[pathList.length - 1]) {
       case 'step-form': return 0;
       case 'confirm': return 1;
       case 'result': return 2;
       default: return 0;
     }
   }
+  getCurrentComponent() {
+    const componentMap = {
+      0: Step1,
+      1: Step2,
+      2: Step3,
+    };
+    return componentMap[this.getCurrentStep()];
+  }
   render() {
-    const { form, stepFormData, submitting, dispatch, children } = this.props;
+    const { form, stepFormData, submitting, dispatch } = this.props;
     const formItemLayout = {
       labelCol: {
         span: 5,
@@ -28,6 +40,7 @@ class StepForm extends PureComponent {
         span: 19,
       },
     };
+    const CurrentComponent = this.getCurrentComponent();
     return (
       <PageHeaderLayout title="分步表单" content="将表单内容进行分步可以提高用户处理的专注度,降低页面复杂性。">
         <Card bordered={false}>
@@ -37,19 +50,13 @@ class StepForm extends PureComponent {
               <Step title="确认转账信息" />
               <Step title="完成" />
             </Steps>
-            {children ? cloneElement(children, {
-              form,
-              formItemLayout,
-              data: stepFormData,
-              submitting,
-              dispatch,
-            }) : (
-              <Step1
-                formItemLayout={formItemLayout}
-                form={form}
-                dispatch={dispatch}
-              />
-            )}
+            <CurrentComponent
+              formItemLayout={formItemLayout}
+              form={form}
+              dispatch={dispatch}
+              data={stepFormData}
+              submitting={submitting}
+            />
           </div>
         </Card>
       </PageHeaderLayout>

+ 25 - 27
src/routes/List/CoverCardList.js

@@ -1,7 +1,7 @@
 import React, { PureComponent } from 'react';
 import moment from 'moment';
 import { connect } from 'dva';
-import { Link, routerRedux } from 'dva/router';
+import { routerRedux } from 'dva/router';
 import { Row, Col, Form, Card, Select, List, Input } from 'antd';
 
 import PageHeaderLayout from '../../layouts/PageHeaderLayout';
@@ -78,33 +78,31 @@ export default class CoverCardList extends PureComponent {
         dataSource={list}
         renderItem={item => (
           <List.Item>
-            <Link>
-              <Card
-                hoverable
-                cover={<img alt={item.title} src={item.cover} />}
-              >
-                <Card.Meta
-                  title={item.title}
-                  description={item.subDescription}
-                />
-                <div className={styles.cardItemContent}>
-                  <span>{moment(item.updatedAt).fromNow()}</span>
-                  <div className={styles.avatarList}>
-                    <AvatarList size="mini">
-                      {
-                        item.members.map((member, i) => (
-                          <AvatarList.Item
-                            key={`${item.id}-avatar-${i}`}
-                            src={member.avatar}
-                            tips={member.name}
-                          />
-                        ))
-                      }
-                    </AvatarList>
-                  </div>
+            <Card
+              hoverable
+              cover={<img alt={item.title} src={item.cover} />}
+            >
+              <Card.Meta
+                title={item.title}
+                description={item.subDescription}
+              />
+              <div className={styles.cardItemContent}>
+                <span>{moment(item.updatedAt).fromNow()}</span>
+                <div className={styles.avatarList}>
+                  <AvatarList size="mini">
+                    {
+                      item.members.map((member, i) => (
+                        <AvatarList.Item
+                          key={`${item.id}-avatar-${i}`}
+                          src={member.avatar}
+                          tips={member.name}
+                        />
+                      ))
+                    }
+                  </AvatarList>
                 </div>
-              </Card>
-            </Link>
+              </div>
+            </Card>
           </List.Item>
         )}
       />

+ 31 - 0
src/utils/utils.js

@@ -1,4 +1,7 @@
 import moment from 'moment';
+import cloneDeep from 'lodash/cloneDeep';
+import navData from '../common/nav';
+
 
 export function fixedZero(val) {
   return val * 1 < 10 ? `0${val}` : val;
@@ -48,3 +51,31 @@ export function getTimeDistance(type) {
     return [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)];
   }
 }
+
+function getPlainNode(nodeList, parentPath = '') {
+  const arr = [];
+  nodeList.forEach((node) => {
+    const item = node;
+    item.path = `${parentPath}/${item.path || ''}`.replace(/\/+/g, '/');
+    item.exact = true;
+    if (item.children && !item.component) {
+      arr.push(...getPlainNode(item.children, item.path));
+    } else {
+      if (item.children && item.component) {
+        item.exact = false;
+      }
+      arr.push(item);
+    }
+  });
+  return arr;
+}
+
+export function getRouteData(path) {
+  if (!navData.some(item => item.layout === path) ||
+      !(navData.filter(item => item.layout === path)[0].children)) {
+    return null;
+  }
+  const dataList = cloneDeep(navData.filter(item => item.layout === path)[0]);
+  const nodeList = getPlainNode(dataList.children);
+  return nodeList;
+}