Procházet zdrojové kódy

Merge branch 'master' into v2

陈帅 před 7 roky
rodič
revize
8f0262b253

+ 1 - 1
.gitignore

@@ -1,7 +1,7 @@
 # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
 
 # dependencies
-node_modules
+**/node_modules
 # roadhog-api-doc ignore
 /src/utils/request-temp.js
 _roadhog-api-doc

+ 1 - 0
.stylelintrc.json

@@ -13,6 +13,7 @@
     "function-parentheses-newline-inside": null,
     "function-max-empty-lines": null,
     "function-whitespace-after": null,
+    "no-missing-end-of-source-newline": null,
     "number-leading-zero": null,
     "number-no-trailing-zeros": null,
     "rule-empty-line-before": null,

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 6 - 2
README.md


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 103 - 0
README.ru-RU.md


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 6 - 2
README.zh-CN.md


+ 3 - 0
jest.config.js

@@ -0,0 +1,3 @@
+module.exports = {
+  testURL: 'http://localhost:8000',
+};

+ 9 - 7
package.json

@@ -23,8 +23,9 @@
     "prettier": "prettier --write ./src/**/**/**/*"
   },
   "dependencies": {
-    "@antv/data-set": "^0.8.9",
-    "antd": "^3.7.3",
+    "@antv/data-set": "^0.9.0",
+    "@babel/polyfill": "^7.0.0-beta.36",
+    "antd": "^3.8.0",
     "bizcharts": "^3.1.10",
     "bizcharts-plugin-slider": "^2.0.3",
     "classnames": "^2.2.6",
@@ -35,9 +36,10 @@
     "moment": "^2.22.2",
     "numeral": "^2.0.6",
     "omit.js": "^1.0.0",
-    "path-to-regexp": "^2.2.1",
-    "prop-types": "^15.6.2",
-    "qs": "^6.5.2",
+    "path-to-regexp": "^2.1.0",
+    "prop-types": "^15.5.10",
+    "qs": "^6.5.0",
+    "react": "^16.4.1",
     "react-container-query": "^0.11.0",
     "react-copy-to-clipboard": "^5.0.1",
     "react-document-title": "^2.0.3",
@@ -60,8 +62,8 @@
     "babel-runtime": "^6.9.2",
     "cross-env": "^5.1.1",
     "cross-port-killer": "^1.0.1",
-    "enzyme": "^3.1.0",
-    "eslint": "^5.1.0",
+    "enzyme": "^3.4.1",
+    "eslint": "^5.0.0",
     "eslint-config-airbnb": "^17.0.0",
     "eslint-config-prettier": "^2.9.0",
     "eslint-config-umi": "^0.1.4",

+ 4 - 4
src/components/Charts/TimelineChart/index.d.ts

@@ -1,11 +1,11 @@
 import * as React from 'react';
 export interface ITimelineChartProps {
   data: Array<{
-    x: string;
-    y1: string;
-    y2: string;
+    x: number;
+    y1: number;
+    y2?: number;
   }>;
-  titleMap: { y1: string; y2: string };
+  titleMap: { y1: string; y2?: string };
   padding?: [number, number, number, number];
   height?: number;
   style?: React.CSSProperties;

+ 6 - 2
src/components/Ellipsis/index.js

@@ -137,11 +137,15 @@ export default class Ellipsis extends Component {
     if (sh <= th) {
       shadowNode.innerHTML = text.substring(0, mid + 1) + suffix;
       sh = shadowNode.offsetHeight;
-      if (sh > th) {
+      if (sh > th || mid === begin) {
         return mid;
       } else {
         begin = mid;
-        mid = Math.floor((end - begin) / 2) + begin;
+        if (end - begin === 1) {
+          mid = 1 + begin;
+        } else {
+          mid = Math.floor((end - begin) / 2) + begin;
+        }
         return this.bisection(th, mid, begin, end, text, shadowNode);
       }
     } else {

+ 1 - 1
src/components/HeaderSearch/index.js

@@ -81,7 +81,7 @@ export default class HeaderSearch extends PureComponent {
   })
   debouncePressEnter() {
     const { onPressEnter } = this.props;
-    const value = this.state;
+    const { value } = this.state;
     onPressEnter(value);
   }
 

+ 2 - 1
src/components/Login/LoginItem.js

@@ -79,6 +79,7 @@ class WarpFormItem extends Component {
       defaultValue,
       rules,
       name,
+      buttonText,
       updateActive,
       type,
       ...restProps
@@ -103,7 +104,7 @@ class WarpFormItem extends Component {
                 size="large"
                 onClick={this.onGetCaptcha}
               >
-                {count ? `${count} s` : '获取验证码'}
+                {count ? `${count} s` : buttonText}
               </Button>
             </Col>
           </Row>

+ 1 - 0
src/components/Login/index.d.ts

@@ -19,6 +19,7 @@ export interface LoginItemProps {
   style?: React.CSSProperties;
   onGetCaptcha?: () => void;
   placeholder?: string;
+  buttonText?: React.ReactNode;
 }
 
 export class LoginItem extends React.Component<LoginItemProps, any> {}

+ 1 - 0
src/components/Login/index.en-US.md

@@ -40,6 +40,7 @@ Property | Description | Type | Default
 ----|------|-----|------
 onGetCaptcha | callback on getting a new Captcha | () => (void \| false \| Promise) | -
 countDown | count down | number |-
+buttonText | text on getting a new Captcha  | ReactNode | '获取验证码'
 
 Apart from the above properties, _Login.Captcha_ support the same properties with _Login.UserName_.
 

+ 2 - 2
src/components/Login/index.zh-CN.md

@@ -32,8 +32,7 @@ name | 控件标记,提交数据中同样以此为 key | String | -
 rules | 校验规则,同 Form getFieldDecorator(id, options) 中 [option.rules 的规则](getFieldDecorator(id, options)) | object[] | -
 
 除上述属性以外,Login.UserName 还支持 antd.Input 的所有属性,并且自带默认的基础配置,包括 `placeholder` `size` `prefix` 等,这些基础配置均可被覆盖。
-
-### Login.Password、Login.Mobile 同 Login.UserName
+## Login.Password、Login.Mobile 同 Login.UserName
 
 ### Login.Captcha
 
@@ -41,6 +40,7 @@ rules | 校验规则,同 Form getFieldDecorator(id, options) 中 [option.rules
 ----|------|-----|------
 onGetCaptcha | 点击获取校验码的回调 | () => (void \| false \| Promise) | -
 countDown | 倒计时 | number |-
+buttonText | 点击获取校验码的说明文字 | ReactNode | '获取验证码'
 
 除上述属性以外,Login.Captcha 支持的属性与 Login.UserName 相同。
 

+ 1 - 1
src/components/NoticeIcon/index.d.ts

@@ -1,4 +1,4 @@
-import React from 'react';
+import * as React from 'react';
 import NoticeIconTab, { INoticeIconData } from './NoticeIconTab';
 
 export interface INoticeIconProps {

+ 12 - 11
src/components/PageHeader/index.js

@@ -218,17 +218,18 @@ export default class PageHeader extends PureComponent {
             </div>
           </div>
         </div>
-        {tabList &&
-          tabList.length && (
-            <Tabs
-              className={styles.tabs}
-              {...activeKeyProps}
-              onChange={this.onChange}
-              tabBarExtraContent={tabBarExtraContent}
-            >
-              {tabList.map(item => <TabPane tab={item.tab} key={item.key} />)}
-            </Tabs>
-          )}
+        {tabList && tabList.length ? (
+          <Tabs
+            className={styles.tabs}
+            {...activeKeyProps}
+            onChange={this.onChange}
+            tabBarExtraContent={tabBarExtraContent}
+          >
+            {tabList.map(item => (
+              <TabPane tab={item.tab} key={item.key} />
+            ))}
+          </Tabs>
+        ) : null}
       </Card>
     );
   }

+ 1 - 1
src/components/Trend/index.md

@@ -19,4 +19,4 @@ order: 14
 |----------|------------------------------------------|-------------|-------|
 | colorful | 是否彩色标记 | Boolean | true |
 | flag | 上升下降标识:`up|down` | string | - |
-| reverseColor | 颜色反转 | Boolean | true |
+| reverseColor | 颜色反转 | Boolean | false |

+ 309 - 0
src/layouts/BasicLayout.js

@@ -0,0 +1,309 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { Layout, Icon, message } from 'antd';
+import DocumentTitle from 'react-document-title';
+import { connect } from 'dva';
+import { Route, Redirect, Switch, routerRedux } from 'dva/router';
+import { ContainerQuery } from 'react-container-query';
+import classNames from 'classnames';
+import pathToRegexp from 'path-to-regexp';
+import { enquireScreen, unenquireScreen } from 'enquire-js';
+import GlobalHeader from '../components/GlobalHeader';
+import GlobalFooter from '../components/GlobalFooter';
+import SiderMenu from '../components/SiderMenu';
+import NotFound from '../routes/Exception/404';
+import { getRoutes } from '../utils/utils';
+import Authorized from '../utils/Authorized';
+import { getMenuData } from '../common/menu';
+import logo from '../assets/logo.svg';
+
+const { Content, Header, Footer } = Layout;
+const { AuthorizedRoute, check } = Authorized;
+
+/**
+ * 根据菜单取得重定向地址.
+ */
+const redirectData = [];
+const getRedirect = item => {
+  if (item && item.children) {
+    if (item.children[0] && item.children[0].path) {
+      redirectData.push({
+        from: `${item.path}`,
+        to: `${item.children[0].path}`,
+      });
+      item.children.forEach(children => {
+        getRedirect(children);
+      });
+    }
+  }
+};
+getMenuData().forEach(getRedirect);
+
+/**
+ * 获取面包屑映射
+ * @param {Object} menuData 菜单配置
+ * @param {Object} routerData 路由配置
+ */
+const getBreadcrumbNameMap = (menuData, routerData) => {
+  const result = {};
+  const childResult = {};
+  for (const i of menuData) {
+    if (!routerData[i.path]) {
+      result[i.path] = i;
+    }
+    if (i.children) {
+      Object.assign(childResult, getBreadcrumbNameMap(i.children, routerData));
+    }
+  }
+  return Object.assign({}, routerData, result, childResult);
+};
+
+const query = {
+  'screen-xs': {
+    maxWidth: 575,
+  },
+  'screen-sm': {
+    minWidth: 576,
+    maxWidth: 767,
+  },
+  'screen-md': {
+    minWidth: 768,
+    maxWidth: 991,
+  },
+  'screen-lg': {
+    minWidth: 992,
+    maxWidth: 1199,
+  },
+  'screen-xl': {
+    minWidth: 1200,
+    maxWidth: 1599,
+  },
+  'screen-xxl': {
+    minWidth: 1600,
+  },
+};
+
+let isMobile;
+enquireScreen(b => {
+  isMobile = b;
+});
+
+@connect(({ user, global = {}, loading }) => ({
+  currentUser: user.currentUser,
+  collapsed: global.collapsed,
+  fetchingNotices: loading.effects['global/fetchNotices'],
+  notices: global.notices,
+}))
+export default class BasicLayout extends React.PureComponent {
+  static childContextTypes = {
+    location: PropTypes.object,
+    breadcrumbNameMap: PropTypes.object,
+  };
+
+  state = {
+    isMobile,
+  };
+
+  getChildContext() {
+    const { location, routerData } = this.props;
+    return {
+      location,
+      breadcrumbNameMap: getBreadcrumbNameMap(getMenuData(), routerData),
+    };
+  }
+
+  componentDidMount() {
+    this.enquireHandler = enquireScreen(mobile => {
+      this.setState({
+        isMobile: mobile,
+      });
+    });
+    const { dispatch } = this.props;
+    dispatch({
+      type: 'user/fetchCurrent',
+    });
+  }
+
+  componentWillUnmount() {
+    unenquireScreen(this.enquireHandler);
+  }
+
+  getPageTitle() {
+    const { routerData, location } = this.props;
+    const { pathname } = location;
+    let title = 'Ant Design Pro';
+    let currRouterData = null;
+    // match params path
+    Object.keys(routerData).forEach(key => {
+      if (pathToRegexp(key).test(pathname)) {
+        currRouterData = routerData[key];
+      }
+    });
+    if (currRouterData && currRouterData.name) {
+      title = `${currRouterData.name} - Ant Design Pro`;
+    }
+    return title;
+  }
+
+  getBaseRedirect = () => {
+    // According to the url parameter to redirect
+    // 这里是重定向的,重定向到 url 的 redirect 参数所示地址
+    const urlParams = new URL(window.location.href);
+
+    const redirect = urlParams.searchParams.get('redirect');
+    // Remove the parameters in the url
+    if (redirect) {
+      urlParams.searchParams.delete('redirect');
+      window.history.replaceState(null, 'redirect', urlParams.href);
+    } else {
+      const { routerData } = this.props;
+      // get the first authorized route path in routerData
+      const authorizedPath = Object.keys(routerData).find(
+        item => check(routerData[item].authority, item) && item !== '/'
+      );
+      return authorizedPath;
+    }
+    return redirect;
+  };
+
+  handleMenuCollapse = collapsed => {
+    const { dispatch } = this.props;
+    dispatch({
+      type: 'global/changeLayoutCollapsed',
+      payload: collapsed,
+    });
+  };
+
+  handleNoticeClear = type => {
+    message.success(`清空了${type}`);
+    const { dispatch } = this.props;
+    dispatch({
+      type: 'global/clearNotices',
+      payload: type,
+    });
+  };
+
+  handleMenuClick = ({ key }) => {
+    const { dispatch } = this.props;
+    if (key === 'triggerError') {
+      dispatch(routerRedux.push('/exception/trigger'));
+      return;
+    }
+    if (key === 'logout') {
+      dispatch({
+        type: 'login/logout',
+      });
+    }
+  };
+
+  handleNoticeVisibleChange = visible => {
+    const { dispatch } = this.props;
+    if (visible) {
+      dispatch({
+        type: 'global/fetchNotices',
+      });
+    }
+  };
+
+  render() {
+    const {
+      currentUser,
+      collapsed,
+      fetchingNotices,
+      notices,
+      routerData,
+      match,
+      location,
+    } = this.props;
+    const { isMobile: mb } = this.state;
+    const baseRedirect = this.getBaseRedirect();
+    const layout = (
+      <Layout>
+        <SiderMenu
+          logo={logo}
+          // 不带Authorized参数的情况下如果没有权限,会强制跳到403界面
+          // If you do not have the Authorized parameter
+          // you will be forced to jump to the 403 interface without permission
+          Authorized={Authorized}
+          menuData={getMenuData()}
+          collapsed={collapsed}
+          location={location}
+          isMobile={mb}
+          onCollapse={this.handleMenuCollapse}
+        />
+        <Layout>
+          <Header style={{ padding: 0 }}>
+            <GlobalHeader
+              logo={logo}
+              currentUser={currentUser}
+              fetchingNotices={fetchingNotices}
+              notices={notices}
+              collapsed={collapsed}
+              isMobile={mb}
+              onNoticeClear={this.handleNoticeClear}
+              onCollapse={this.handleMenuCollapse}
+              onMenuClick={this.handleMenuClick}
+              onNoticeVisibleChange={this.handleNoticeVisibleChange}
+            />
+          </Header>
+          <Content style={{ margin: '24px 24px 0', height: '100%' }}>
+            <Switch>
+              {redirectData.map(item => (
+                <Redirect key={item.from} exact from={item.from} to={item.to} />
+              ))}
+              {getRoutes(match.path, routerData).map(item => (
+                <AuthorizedRoute
+                  key={item.key}
+                  path={item.path}
+                  component={item.component}
+                  exact={item.exact}
+                  authority={item.authority}
+                  redirectPath="/exception/403"
+                />
+              ))}
+              <Redirect exact from="/" to={baseRedirect} />
+              <Route render={NotFound} />
+            </Switch>
+          </Content>
+          <Footer style={{ padding: 0 }}>
+            <GlobalFooter
+              links={[
+                {
+                  key: 'Pro 首页',
+                  title: 'Pro 首页',
+                  href: 'http://pro.ant.design',
+                  blankTarget: true,
+                },
+                {
+                  key: 'github',
+                  title: <Icon type="github" />,
+                  href: 'https://github.com/ant-design/ant-design-pro',
+                  blankTarget: true,
+                },
+                {
+                  key: 'Ant Design',
+                  title: 'Ant Design',
+                  href: 'http://ant.design',
+                  blankTarget: true,
+                },
+              ]}
+              copyright={
+                <Fragment>
+                  Copyright <Icon type="copyright" /> 2018 蚂蚁金服体验技术部出品
+                </Fragment>
+              }
+            />
+          </Footer>
+        </Layout>
+      </Layout>
+    );
+
+    return (
+      <DocumentTitle title={this.getPageTitle()}>
+        <ContainerQuery query={query}>
+          {params => <div className={classNames(params)}>{layout}</div>}
+        </ContainerQuery>
+      </DocumentTitle>
+    );
+  }
+}

+ 6 - 6
src/pages/Forms/AdvancedForm.js

@@ -57,7 +57,12 @@ const tableData = [
   },
 ];
 
-class AdvancedForm extends PureComponent {
+@connect(({ global, loading }) => ({
+  collapsed: global.collapsed,
+  submitting: loading.effects['form/submitAdvancedForm'],
+}))
+@Form.create()
+export default class AdvancedForm extends PureComponent {
   state = {
     width: '100%',
   };
@@ -303,8 +308,3 @@ class AdvancedForm extends PureComponent {
     );
   }
 }
-
-export default connect(({ global, loading }) => ({
-  collapsed: global.collapsed,
-  submitting: loading.effects['form/submitAdvancedForm'],
-}))(Form.create()(AdvancedForm));

+ 4 - 5
src/pages/Forms/StepForm/Step1.js

@@ -15,8 +15,11 @@ const formItemLayout = {
   },
 };
 
+@connect(({ form }) => ({
+  data: form.step,
+}))
 @Form.create()
-class Step1 extends React.PureComponent {
+export default class Step1 extends React.PureComponent {
   render() {
     const { form, dispatch, data } = this.props;
     const { getFieldDecorator, validateFields } = form;
@@ -108,7 +111,3 @@ class Step1 extends React.PureComponent {
     );
   }
 }
-
-export default connect(({ form }) => ({
-  data: form.step,
-}))(Step1);

+ 4 - 5
src/pages/Forms/StepForm/Step3.js

@@ -5,7 +5,10 @@ import { routerRedux } from 'dva/router';
 import Result from 'components/Result';
 import styles from './style.less';
 
-class Step3 extends React.PureComponent {
+@connect(({ form }) => ({
+  data: form.step,
+}))
+export default class Step3 extends React.PureComponent {
   render() {
     const { dispatch, data } = this.props;
     const onFinish = () => {
@@ -67,7 +70,3 @@ class Step3 extends React.PureComponent {
     );
   }
 }
-
-export default connect(({ form }) => ({
-  data: form.step,
-}))(Step3);

+ 4 - 4
src/pages/List/List.js

@@ -30,12 +30,12 @@ export default class SearchList extends Component {
         tab: '文章',
       },
       {
-        key: 'Applications',
-        tab: '应用',
+        key: 'projects',
+        tab: '项目',
       },
       {
-        key: 'Projects',
-        tab: '项目',
+        key: 'applications',
+        tab: '应用',
       },
     ];
 

+ 2 - 2
src/pages/List/TableList.js

@@ -587,7 +587,7 @@ export default class TableList extends PureComponent {
           </Col>
         </Row>
         <div style={{ overflow: 'hidden' }}>
-          <span style={{ float: 'right', marginBottom: 24 }}>
+          <div style={{ float: 'right', marginBottom: 24 }}>
             <Button type="primary" htmlType="submit">
               查询
             </Button>
@@ -597,7 +597,7 @@ export default class TableList extends PureComponent {
             <a style={{ marginLeft: 8 }} onClick={this.toggleForm}>
               收起 <Icon type="up" />
             </a>
-          </span>
+          </div>
         </div>
       </Form>
     );

+ 1 - 0
src/pages/List/TableList.less

@@ -30,6 +30,7 @@
     }
   }
   .submitButtons {
+    display: block;
     white-space: nowrap;
     margin-bottom: 24px;
   }

+ 159 - 0
src/routes/List/BasicList.js

@@ -0,0 +1,159 @@
+import React, { PureComponent } from 'react';
+import moment from 'moment';
+import { connect } from 'dva';
+import {
+  List,
+  Card,
+  Row,
+  Col,
+  Radio,
+  Input,
+  Progress,
+  Button,
+  Icon,
+  Dropdown,
+  Menu,
+  Avatar,
+} from 'antd';
+
+import PageHeaderLayout from '../../layouts/PageHeaderLayout';
+
+import styles from './BasicList.less';
+
+const RadioButton = Radio.Button;
+const RadioGroup = Radio.Group;
+const { Search } = Input;
+
+const ListContent = ({ data: { owner, createdAt, percent, status } }) => (
+  <div className={styles.listContent}>
+    <div className={styles.listContentItem}>
+      <span>Owner</span>
+      <p>{owner}</p>
+    </div>
+    <div className={styles.listContentItem}>
+      <span>开始时间</span>
+      <p>{moment(createdAt).format('YYYY-MM-DD HH:mm')}</p>
+    </div>
+    <div className={styles.listContentItem}>
+      <Progress percent={percent} status={status} strokeWidth={6} style={{ width: 180 }} />
+    </div>
+  </div>
+);
+
+@connect(({ list, loading }) => ({
+  list,
+  loading: loading.models.list,
+}))
+export default class BasicList extends PureComponent {
+  componentDidMount() {
+    const { dispatch } = this.props;
+    dispatch({
+      type: 'list/fetch',
+      payload: {
+        count: 5,
+      },
+    });
+  }
+
+  render() {
+    const {
+      list: { list },
+      loading,
+    } = this.props;
+
+    const Info = ({ title, value, bordered }) => (
+      <div className={styles.headerInfo}>
+        <span>{title}</span>
+        <p>{value}</p>
+        {bordered && <em />}
+      </div>
+    );
+
+    const extraContent = (
+      <div className={styles.extraContent}>
+        <RadioGroup defaultValue="all">
+          <RadioButton value="all">全部</RadioButton>
+          <RadioButton value="progress">进行中</RadioButton>
+          <RadioButton value="waiting">等待中</RadioButton>
+        </RadioGroup>
+        <Search className={styles.extraContentSearch} placeholder="请输入" onSearch={() => ({})} />
+      </div>
+    );
+
+    const paginationProps = {
+      showSizeChanger: true,
+      showQuickJumper: true,
+      pageSize: 5,
+      total: 50,
+    };
+
+    const menu = (
+      <Menu>
+        <Menu.Item>
+          <a>编辑</a>
+        </Menu.Item>
+        <Menu.Item>
+          <a>删除</a>
+        </Menu.Item>
+      </Menu>
+    );
+
+    const MoreBtn = () => (
+      <Dropdown overlay={menu}>
+        <a>
+          更多 <Icon type="down" />
+        </a>
+      </Dropdown>
+    );
+
+    return (
+      <PageHeaderLayout>
+        <div className={styles.standardList}>
+          <Card bordered={false}>
+            <Row>
+              <Col sm={8} xs={24}>
+                <Info title="我的待办" value="8个任务" bordered />
+              </Col>
+              <Col sm={8} xs={24}>
+                <Info title="本周任务平均处理时间" value="32分钟" bordered />
+              </Col>
+              <Col sm={8} xs={24}>
+                <Info title="本周完成任务数" value="24个任务" />
+              </Col>
+            </Row>
+          </Card>
+
+          <Card
+            className={styles.listCard}
+            bordered={false}
+            title="标准列表"
+            style={{ marginTop: 24 }}
+            bodyStyle={{ padding: '0 32px 40px 32px' }}
+            extra={extraContent}
+          >
+            <Button type="dashed" style={{ width: '100%', marginBottom: 8 }} icon="plus">
+              添加
+            </Button>
+            <List
+              size="large"
+              rowKey="id"
+              loading={loading}
+              pagination={paginationProps}
+              dataSource={list}
+              renderItem={item => (
+                <List.Item actions={[<a>编辑</a>, <MoreBtn />]}>
+                  <List.Item.Meta
+                    avatar={<Avatar src={item.logo} shape="square" size="large" />}
+                    title={<a href={item.href}>{item.title}</a>}
+                    description={item.subDescription}
+                  />
+                  <ListContent data={item} />
+                </List.Item>
+              )}
+            />
+          </Card>
+        </div>
+      </PageHeaderLayout>
+    );
+  }
+}