;
7 |
8 | export type IReactComponent =
9 | | React.StatelessComponent
10 | | React.ComponentClass
11 | | React.ClassicComponentClass
;
12 |
13 | interface Secured {
14 | (authority: authority, error?: React.ReactNode): (target: T) => T;
15 | }
16 |
17 | export interface AuthorizedRouteProps extends RouteProps {
18 | authority: authority;
19 | }
20 | export class AuthorizedRoute extends React.Component {}
21 |
22 | interface check {
23 | (
24 | authority: authority,
25 | target: T,
26 | Exception: S
27 | ): T | S;
28 | }
29 |
30 | export interface AuthorizedProps {
31 | authority: authority;
32 | noMatch?: React.ReactNode;
33 | }
34 |
35 | export class Authorized extends React.Component {
36 | static Secured: Secured;
37 | static AuthorizedRoute: typeof AuthorizedRoute;
38 | static check: check;
39 | }
40 |
41 | declare function renderAuthorize(currentAuthority: string): typeof Authorized;
42 |
43 | export default renderAuthorize;
44 |
--------------------------------------------------------------------------------
/server/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | express-mongoose-es6-rest-api:
5 | build:
6 | context: .
7 | volumes:
8 | # Mounts the project directory on the host to /app inside the container,
9 | # allowing you to modify the code without having to rebuild the image.
10 | - .:/app
11 | # Just specify a path and let the Engine create a volume.
12 | # Data present in the base image at the specified mount point will be copied
13 | # over to the new volume upon volume initialization.
14 | # node_modules from this new volume will be used and not from your local dev env.
15 | - /app/node_modules/
16 |
17 | # Expose ports [HOST:CONTAINER}
18 | ports:
19 | - "4040:4040"
20 |
21 | # Set environment variables from this file
22 | env_file:
23 | - .env
24 |
25 | # Overwrite any env var defined in .env file (if required)
26 | environment:
27 | - MONGO_HOST=mongodb://mongo/express-mongoose-es6-rest-api-development
28 | - DEBUG=express-mongoose-es6-rest-api:*
29 |
30 | # Link to containers in another service.
31 | # Links also express dependency between services in the same way as depends_on,
32 | # so they determine the order of service startup.
33 | links:
34 | - mongo
35 | mongo:
36 | image: "mongo:3.4"
37 | ports:
38 | - "27017:27017"
39 |
--------------------------------------------------------------------------------
/antdProClient/config/router.config.js:
--------------------------------------------------------------------------------
1 | export default [
2 | // app
3 | {
4 | path: '/',
5 | component: '../layouts/BasicLayout',
6 | Routes: ['src/pages/Authorized'],
7 | // authority: ['admin'],
8 | routes: [
9 | { path: '/', redirect: '/upload' },
10 | {
11 | path: '/upload',
12 | name: 'upload',
13 | icon: 'read',
14 | component: './Upload'
15 | },
16 | {
17 | name: 'exception',
18 | icon: 'warning',
19 | path: '/exception',
20 | hideInMenu: true,
21 | routes: [
22 | // exception
23 | {
24 | path: '/exception/403',
25 | name: 'not-permission',
26 | component: './Exception/403',
27 | },
28 | {
29 | path: '/exception/404',
30 | name: 'not-find',
31 | component: './Exception/404',
32 | },
33 | {
34 | path: '/exception/500',
35 | name: 'server-error',
36 | component: './Exception/500',
37 | },
38 | {
39 | path: '/exception/trigger',
40 | name: 'trigger',
41 | hideInMenu: true,
42 | component: './Exception/TriggerException',
43 | },
44 | ],
45 | },
46 | {
47 | component: '404',
48 | },
49 | ],
50 | }
51 | ];
52 |
--------------------------------------------------------------------------------
/antdProClient/src/components/TopNavHeader/index.less:
--------------------------------------------------------------------------------
1 | .head {
2 | width: 100%;
3 | transition: background 0.3s, width 0.2s;
4 | height: 64px;
5 | padding: 0 12px 0 0;
6 | box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
7 | position: relative;
8 | :global {
9 | .ant-menu-submenu.ant-menu-submenu-horizontal {
10 | height: 100%;
11 | padding-top: 9px;
12 | .ant-menu-submenu-title {
13 | height: 100%;
14 | }
15 | }
16 | }
17 | &.light {
18 | background-color: #fff;
19 | }
20 | .main {
21 | display: flex;
22 | height: 64px;
23 | padding-left: 24px;
24 | &.wide {
25 | max-width: 1200px;
26 | margin: auto;
27 | padding-left: 4px;
28 | }
29 | .left {
30 | flex: 1;
31 | display: flex;
32 | }
33 | .right {
34 | width: 324px;
35 | }
36 | }
37 | }
38 |
39 | .logo {
40 | width: 165px;
41 | height: 64px;
42 | position: relative;
43 | line-height: 64px;
44 | transition: all 0.3s;
45 | overflow: hidden;
46 | img {
47 | display: inline-block;
48 | vertical-align: middle;
49 | height: 32px;
50 | }
51 | h1 {
52 | color: #fff;
53 | display: inline-block;
54 | vertical-align: middle;
55 | font-size: 16px;
56 | margin: 0 0 0 12px;
57 | font-weight: 400;
58 | }
59 | }
60 |
61 | .light {
62 | h1 {
63 | color: #002140;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/antdProClient/src/components/Charts/MiniBar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Chart, Tooltip, Geom } from 'bizcharts';
3 | import autoHeight from '../autoHeight';
4 | import styles from '../index.less';
5 |
6 | @autoHeight()
7 | class MiniBar extends React.Component {
8 | render() {
9 | const { height, forceFit = true, color = '#1890FF', data = [] } = this.props;
10 |
11 | const scale = {
12 | x: {
13 | type: 'cat',
14 | },
15 | y: {
16 | min: 0,
17 | },
18 | };
19 |
20 | const padding = [36, 5, 30, 5];
21 |
22 | const tooltip = [
23 | 'x*y',
24 | (x, y) => ({
25 | name: x,
26 | value: y,
27 | }),
28 | ];
29 |
30 | // for tooltip not to be hide
31 | const chartHeight = height + 54;
32 |
33 | return (
34 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
48 | );
49 | }
50 | }
51 | export default MiniBar;
52 |
--------------------------------------------------------------------------------
/antdProClient/src/components/SelectLang/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { FormattedMessage, setLocale, getLocale } from 'umi/locale';
3 | import { Menu, Icon, Dropdown } from 'antd';
4 | import classNames from 'classnames';
5 | import styles from './index.less';
6 |
7 | export default class SelectLang extends PureComponent {
8 | changLang = ({ key }) => {
9 | setLocale(key);
10 | };
11 |
12 | render() {
13 | const { className } = this.props;
14 | const selectedLang = getLocale();
15 | const langMenu = (
16 |
30 | );
31 | return (
32 |
33 |
34 |
35 |
36 |
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/antdProClient/src/pages/Exception/TriggerException.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Button, Spin, Card } from 'antd';
3 | import { connect } from 'dva';
4 | import styles from './style.less';
5 |
6 | @connect(state => ({
7 | isloading: state.error.isloading,
8 | }))
9 | class TriggerException extends PureComponent {
10 | state = {
11 | isloading: false,
12 | };
13 |
14 | triggerError = code => {
15 | this.setState({
16 | isloading: true,
17 | });
18 | const { dispatch } = this.props;
19 | dispatch({
20 | type: 'error/query',
21 | payload: {
22 | code,
23 | },
24 | });
25 | };
26 |
27 | render() {
28 | const { isloading } = this.state;
29 | return (
30 |
31 |
32 |
35 |
38 |
41 |
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | export default TriggerException;
51 |
--------------------------------------------------------------------------------
/antdProClient/src/components/GlobalHeader/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Icon } from 'antd';
3 | import Link from 'umi/link';
4 | import Debounce from 'lodash-decorators/debounce';
5 | import styles from './index.less';
6 | import RightContent from './RightContent';
7 |
8 | export default class GlobalHeader extends PureComponent {
9 | componentWillUnmount() {
10 | this.triggerResizeEvent.cancel();
11 | }
12 | /* eslint-disable*/
13 | @Debounce(600)
14 | triggerResizeEvent() {
15 | // eslint-disable-line
16 | const event = document.createEvent('HTMLEvents');
17 | event.initEvent('resize', true, false);
18 | window.dispatchEvent(event);
19 | }
20 | toggle = () => {
21 | const { collapsed, onCollapse } = this.props;
22 | onCollapse(!collapsed);
23 | this.triggerResizeEvent();
24 | };
25 | render() {
26 | const { collapsed, isMobile, logo } = this.props;
27 | return (
28 |
29 | {isMobile && (
30 |
31 |

32 |
33 | )}
34 |
39 |
40 |
41 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/antdProClient/src/components/NoticeIcon/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: NoticeIcon
3 | subtitle: 通知菜单
4 | cols: 1
5 | order: 9
6 | ---
7 |
8 | 用在导航工具栏上,作为整个产品统一的通知中心。
9 |
10 | ## API
11 |
12 | 参数 | 说明 | 类型 | 默认值
13 | ----|------|-----|------
14 | count | 图标上的消息总数 | number | -
15 | bell | translate this please -> Change the bell Icon | ReactNode | ``
16 | loading | 弹出卡片加载状态 | boolean | false
17 | onClear | 点击清空按钮的回调 | function(tabName) | -
18 | onItemClick | 点击列表项的回调 | function(item, tabProps) | -
19 | onTabChange | 切换页签的回调 | function(tabTitle) | -
20 | popupAlign | 弹出卡片的位置配置 | Object [alignConfig](https://github.com/yiminghe/dom-align#alignconfig-object-details) | -
21 | onPopupVisibleChange | 弹出卡片显隐的回调 | function(visible) | -
22 | popupVisible | 控制弹层显隐 | boolean | -
23 | locale | 默认文案 | Object | `{ emptyText: '暂无数据', clear: '清空' }`
24 |
25 | ### NoticeIcon.Tab
26 |
27 | 参数 | 说明 | 类型 | 默认值
28 | ----|------|-----|------
29 | title | 消息分类的页签标题 | string | -
30 | name | 消息分类的标识符 | string | -
31 | list | 列表数据,格式参照下表 | Array | `[]`
32 | showClear | 是否显示清空按钮 | boolean | true
33 | emptyText | 针对每个 Tab 定制空数据文案 | ReactNode | -
34 | emptyImage | 针对每个 Tab 定制空数据图片 | string | -
35 |
36 |
37 | ### Tab data
38 |
39 | 参数 | 说明 | 类型 | 默认值
40 | ----|------|-----|------
41 | avatar | 头像图片链接 | string \| ReactNode | -
42 | title | 标题 | ReactNode | -
43 | description | 描述信息 | ReactNode | -
44 | datetime | 时间戳 | ReactNode | -
45 | extra | 额外信息,在列表项右上角 | ReactNode | -
46 |
--------------------------------------------------------------------------------
/server/docker-compose.test.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | express-mongoose-es6-rest-api:
5 | build:
6 | context: .
7 |
8 | image: express-mongoose-es6-rest-api:latest
9 |
10 | volumes:
11 | # Mounts the project directory on the host to /app inside the container,
12 | # allowing you to modify the code without having to rebuild the image.
13 | - .:/app
14 | # Just specify a path and let the Engine create a volume.
15 | # Data present in the base image at the specified mount point will be copied
16 | # over to the new volume upon volume initialization.
17 | # node_modules from this new volume will be used and not from your local dev env.
18 | - /app/node_modules/
19 |
20 | # Set environment variables from this file
21 | env_file:
22 | - .env
23 |
24 | # Overwrite any env var defined in .env file (if required)
25 | environment:
26 | - MONGO_HOST=mongodb://mongo/express-mongoose-es6-rest-api-test
27 | - DEBUG=express-mongoose-es6-rest-api:*
28 |
29 | # Link to containers in another service.
30 | # Links also express dependency between services in the same way as depends_on,
31 | # so they determine the order of service startup.
32 | links:
33 | - mongo
34 |
35 | command:
36 | - /bin/bash
37 | - -c
38 | - yarn --pure-lockfile && yarn test
39 | mongo:
40 | image: "mongo:3.4.2"
41 | ports:
42 | - "27017:27017"
43 |
--------------------------------------------------------------------------------
/antdProClient/src/components/TagSelect/demo/controlled.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 3
3 | title: 受控模式
4 | ---
5 |
6 | 结合 `Tag` 的 `TagSelect` 组件,方便的应用于筛选类目的业务场景中。
7 |
8 | ```jsx
9 | import { Button } from 'antd';
10 | import TagSelect from 'ant-design-pro/lib/TagSelect';
11 |
12 | class Demo extends React.Component {
13 | state = {
14 | value: ['cat1'],
15 | };
16 | handleFormSubmit = value => {
17 | this.setState({
18 | value,
19 | });
20 | };
21 | checkAll = () => {
22 | this.setState({
23 | value: ['cat1', 'cat2', 'cat3', 'cat4', 'cat5', 'cat6'],
24 | });
25 | };
26 | render() {
27 | return (
28 |
29 |
30 |
35 |
36 | 类目一
37 | 类目二
38 | 类目三
39 | 类目四
40 | 类目五
41 | 类目六
42 |
43 |
44 |
45 | );
46 | }
47 | }
48 |
49 | ReactDOM.render(, mountNode);
50 | ```
51 |
--------------------------------------------------------------------------------
/antdProClient/src/components/EditableItem/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Input, Icon } from 'antd';
3 | import styles from './index.less';
4 |
5 | export default class EditableItem extends PureComponent {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | value: props.value,
10 | editable: false,
11 | };
12 | }
13 |
14 | handleChange = e => {
15 | const { value } = e.target;
16 | this.setState({ value });
17 | };
18 |
19 | check = () => {
20 | this.setState({ editable: false });
21 | const { value } = this.state;
22 | const { onChange } = this.state;
23 | if (onChange) {
24 | onChange(value);
25 | }
26 | };
27 |
28 | edit = () => {
29 | this.setState({ editable: true });
30 | };
31 |
32 | render() {
33 | const { value, editable } = this.state;
34 | return (
35 |
36 | {editable ? (
37 |
38 |
39 |
40 |
41 | ) : (
42 |
43 | {value || ' '}
44 |
45 |
46 | )}
47 |
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/antdProClient/src/components/PageHeaderWrapper/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FormattedMessage } from 'umi/locale';
3 | import Link from 'umi/link';
4 | import PageHeader from '@/components/PageHeader';
5 | import { connect } from 'dva';
6 | import GridContent from './GridContent';
7 | import styles from './index.less';
8 | import MenuContext from '@/layouts/MenuContext';
9 |
10 | const PageHeaderWrapper = ({ children, contentWidth, wrapperClassName, top, ...restProps }) => (
11 |
12 | {top}
13 |
14 | {value => (
15 | }
18 | {...value}
19 | key="pageheader"
20 | {...restProps}
21 | linkElement={Link}
22 | itemRender={item => {
23 | if (item.locale) {
24 | return ;
25 | }
26 | return item.name;
27 | }}
28 | />
29 | )}
30 |
31 | {children ? (
32 |
33 | {children}
34 |
35 | ) : null}
36 |
37 | );
38 |
39 | export default connect(({ setting }) => ({
40 | contentWidth: setting.contentWidth,
41 | }))(PageHeaderWrapper);
42 |
--------------------------------------------------------------------------------
/antdProClient/src/components/DescriptionList/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: DescriptionList
3 | subtitle: 描述列表
4 | cols: 1
5 | order: 4
6 | ---
7 |
8 | 成组展示多个只读字段,常见于详情页的信息展示。
9 |
10 | ## API
11 |
12 | ### DescriptionList
13 |
14 | | 参数 | 说明 | 类型 | 默认值 |
15 | |----------|------------------------------------------|-------------|-------|
16 | | layout | 布局方式 | Enum{'horizontal', 'vertical'} | 'horizontal' |
17 | | col | 指定信息最多分几列展示,最终一行几列由 col 配置结合[响应式规则](/components/DescriptionList#响应式规则)决定 | number(0 < col <= 4) | 3 |
18 | | title | 列表标题 | ReactNode | - |
19 | | gutter | 列表项间距,单位为 `px` | number | 32 |
20 | | size | 列表型号 | Enum{'large', 'small'} | - |
21 |
22 | #### 响应式规则
23 |
24 | | 窗口宽度 | 展示列数 |
25 | |---------------------|---------------------------------------------|
26 | | `≥768px` | `col` |
27 | | `≥576px` | `col < 2 ? col : 2` |
28 | | `<576px` | `1` |
29 |
30 | ### DescriptionList.Description
31 |
32 | | 参数 | 说明 | 类型 | 默认值 |
33 | |----------|------------------------------------------|-------------|-------|
34 | | term | 列表项标题 | ReactNode | - |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/antdProClient/src/components/FooterToolbar/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 | import styles from './index.less';
5 |
6 | export default class FooterToolbar extends Component {
7 | static contextTypes = {
8 | isMobile: PropTypes.bool,
9 | };
10 |
11 | state = {
12 | width: undefined,
13 | };
14 |
15 | componentDidMount() {
16 | window.addEventListener('resize', this.resizeFooterToolbar);
17 | this.resizeFooterToolbar();
18 | }
19 |
20 | componentWillUnmount() {
21 | window.removeEventListener('resize', this.resizeFooterToolbar);
22 | }
23 |
24 | resizeFooterToolbar = () => {
25 | const sider = document.querySelector('.ant-layout-sider');
26 | if (sider == null) {
27 | return;
28 | }
29 | const { isMobile } = this.context;
30 | const width = isMobile ? null : `calc(100% - ${sider.style.width})`;
31 | const { width: stateWidth } = this.state;
32 | if (stateWidth !== width) {
33 | this.setState({ width });
34 | }
35 | };
36 |
37 | render() {
38 | const { children, className, extra, ...restProps } = this.props;
39 | const { width } = this.state;
40 | return (
41 |
42 |
{extra}
43 |
{children}
44 |
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/antdProClient/src/components/PageHeader/index.test.js:
--------------------------------------------------------------------------------
1 | import { getBreadcrumb } from './breadcrumb';
2 | import { urlToList } from '../_utils/pathTools';
3 |
4 | const routerData = {
5 | '/dashboard/analysis': {
6 | name: '分析页',
7 | },
8 | '/userinfo': {
9 | name: '用户列表',
10 | },
11 | '/userinfo/:id': {
12 | name: '用户信息',
13 | },
14 | '/userinfo/:id/addr': {
15 | name: '收货订单',
16 | },
17 | };
18 | describe('test getBreadcrumb', () => {
19 | it('Simple url', () => {
20 | expect(getBreadcrumb(routerData, '/dashboard/analysis').name).toEqual('分析页');
21 | });
22 | it('Parameters url', () => {
23 | expect(getBreadcrumb(routerData, '/userinfo/2144').name).toEqual('用户信息');
24 | });
25 | it('The middle parameter url', () => {
26 | expect(getBreadcrumb(routerData, '/userinfo/2144/addr').name).toEqual('收货订单');
27 | });
28 | it('Loop through the parameters', () => {
29 | const urlNameList = urlToList('/userinfo/2144/addr').map(
30 | url => getBreadcrumb(routerData, url).name
31 | );
32 | expect(urlNameList).toEqual(['用户列表', '用户信息', '收货订单']);
33 | });
34 |
35 | it('a path', () => {
36 | const urlNameList = urlToList('/userinfo').map(url => getBreadcrumb(routerData, url).name);
37 | expect(urlNameList).toEqual(['用户列表']);
38 | });
39 | it('Secondary path', () => {
40 | const urlNameList = urlToList('/userinfo/2144').map(url => getBreadcrumb(routerData, url).name);
41 | expect(urlNameList).toEqual(['用户列表', '用户信息']);
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/antdProClient/src/layouts/UserLayout.less:
--------------------------------------------------------------------------------
1 | @import '~antd/lib/style/themes/default.less';
2 |
3 | .container {
4 | display: flex;
5 | flex-direction: column;
6 | height: 100vh;
7 | overflow: auto;
8 | background: @layout-body-background;
9 | }
10 |
11 | .lang {
12 | text-align: right;
13 | width: 100%;
14 | height: 40px;
15 | line-height: 44px;
16 | :global(.ant-dropdown-trigger) {
17 | margin-right: 24px;
18 | }
19 | }
20 |
21 | .content {
22 | padding: 32px 0;
23 | flex: 1;
24 | }
25 |
26 | @media (min-width: @screen-md-min) {
27 | .container {
28 | background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
29 | background-repeat: no-repeat;
30 | background-position: center 110px;
31 | background-size: 100%;
32 | }
33 |
34 | .content {
35 | padding: 72px 0 24px 0;
36 | }
37 | }
38 |
39 | .top {
40 | text-align: center;
41 | }
42 |
43 | .header {
44 | height: 44px;
45 | line-height: 44px;
46 | a {
47 | text-decoration: none;
48 | }
49 | }
50 |
51 | .logo {
52 | height: 44px;
53 | vertical-align: top;
54 | margin-right: 16px;
55 | }
56 |
57 | .title {
58 | font-size: 33px;
59 | color: @heading-color;
60 | font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif;
61 | font-weight: 600;
62 | position: relative;
63 | top: 2px;
64 | }
65 |
66 | .desc {
67 | font-size: @font-size-base;
68 | color: @text-color-secondary;
69 | margin-top: 12px;
70 | margin-bottom: 40px;
71 | }
72 |
--------------------------------------------------------------------------------
/antdProClient/src/e2e/login.e2e.js:
--------------------------------------------------------------------------------
1 | import puppeteer from 'puppeteer';
2 |
3 | describe('Login', () => {
4 | let browser;
5 | let page;
6 |
7 | beforeAll(async () => {
8 | browser = await puppeteer.launch({ args: ['--no-sandbox'] });
9 | });
10 |
11 | beforeEach(async () => {
12 | page = await browser.newPage();
13 | await page.goto('http://localhost:8000/user/login', { waitUntil: 'networkidle2' });
14 | await page.evaluate(() => window.localStorage.setItem('cnuip-company-patent-store-authority', 'guest'));
15 | });
16 |
17 | afterEach(() => page.close());
18 |
19 | it('should login with failure', async () => {
20 | await page.waitForSelector('#userName', {
21 | timeout: 2000,
22 | });
23 | await page.type('#userName', 'mockuser');
24 | await page.type('#password', 'wrong_password');
25 | await page.click('button[type="submit"]');
26 | await page.waitForSelector('.ant-alert-error'); // should display error
27 | });
28 |
29 | it('should login successfully', async () => {
30 | await page.waitForSelector('#userName', {
31 | timeout: 2000,
32 | });
33 | await page.type('#userName', 'admin');
34 | await page.type('#password', '888888');
35 | await page.click('button[type="submit"]');
36 | await page.waitForSelector('.ant-layout-sider h1'); // should display error
37 | const text = await page.evaluate(() => document.body.innerHTML);
38 | expect(text).toContain('Ant Design Pro
');
39 | });
40 |
41 | afterAll(() => browser.close());
42 | });
43 |
--------------------------------------------------------------------------------
/antdProClient/src/components/Login/map.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Icon } from 'antd';
3 | import styles from './index.less';
4 |
5 | export default {
6 | UserName: {
7 | props: {
8 | size: 'large',
9 | prefix: ,
10 | placeholder: 'admin',
11 | },
12 | rules: [
13 | {
14 | required: true,
15 | message: 'Please enter username!',
16 | },
17 | ],
18 | },
19 | Password: {
20 | props: {
21 | size: 'large',
22 | prefix: ,
23 | type: 'password',
24 | placeholder: '888888',
25 | },
26 | rules: [
27 | {
28 | required: true,
29 | message: 'Please enter password!',
30 | },
31 | ],
32 | },
33 | Mobile: {
34 | props: {
35 | size: 'large',
36 | prefix: ,
37 | placeholder: 'mobile number',
38 | },
39 | rules: [
40 | {
41 | required: true,
42 | message: 'Please enter mobile number!',
43 | },
44 | {
45 | pattern: /^1\d{10}$/,
46 | message: 'Wrong mobile number format!',
47 | },
48 | ],
49 | },
50 | Captcha: {
51 | props: {
52 | size: 'large',
53 | prefix: ,
54 | placeholder: 'captcha',
55 | },
56 | rules: [
57 | {
58 | required: true,
59 | message: 'Please enter Captcha!',
60 | },
61 | ],
62 | },
63 | };
64 |
--------------------------------------------------------------------------------
/antdProClient/src/components/Charts/autoHeight.js:
--------------------------------------------------------------------------------
1 | /* eslint eqeqeq: 0 */
2 | import React from 'react';
3 |
4 | function computeHeight(node) {
5 | const totalHeight = parseInt(getComputedStyle(node).height, 10);
6 | const padding =
7 | parseInt(getComputedStyle(node).paddingTop, 10) +
8 | parseInt(getComputedStyle(node).paddingBottom, 10);
9 | return totalHeight - padding;
10 | }
11 |
12 | function getAutoHeight(n) {
13 | if (!n) {
14 | return 0;
15 | }
16 |
17 | let node = n;
18 |
19 | let height = computeHeight(node);
20 |
21 | while (!height) {
22 | node = node.parentNode;
23 | if (node) {
24 | height = computeHeight(node);
25 | } else {
26 | break;
27 | }
28 | }
29 |
30 | return height;
31 | }
32 |
33 | const autoHeight = () => WrappedComponent =>
34 | class extends React.Component {
35 | state = {
36 | computedHeight: 0,
37 | };
38 |
39 | componentDidMount() {
40 | const { height } = this.props;
41 | if (!height) {
42 | const h = getAutoHeight(this.root);
43 | // eslint-disable-next-line
44 | this.setState({ computedHeight: h });
45 | }
46 | }
47 |
48 | handleRoot = node => {
49 | this.root = node;
50 | };
51 |
52 | render() {
53 | const { height } = this.props;
54 | const { computedHeight } = this.state;
55 | const h = height || computedHeight;
56 | return (
57 | {h > 0 && }
58 | );
59 | }
60 | };
61 |
62 | export default autoHeight;
63 |
--------------------------------------------------------------------------------
/antdProClient/src/components/StandardFormRow/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/lib/style/themes/default.less';
2 |
3 | .standardFormRow {
4 | border-bottom: 1px dashed @border-color-split;
5 | padding-bottom: 16px;
6 | margin-bottom: 16px;
7 | display: flex;
8 | :global {
9 | .ant-form-item {
10 | margin-right: 24px;
11 | }
12 | .ant-form-item-label label {
13 | color: @text-color;
14 | margin-right: 0;
15 | }
16 | .ant-form-item-label,
17 | .ant-form-item-control {
18 | padding: 0;
19 | line-height: 32px;
20 | }
21 | }
22 | .label {
23 | color: @heading-color;
24 | font-size: @font-size-base;
25 | margin-right: 24px;
26 | flex: 0 0 auto;
27 | text-align: right;
28 | & > span {
29 | display: inline-block;
30 | height: 32px;
31 | line-height: 32px;
32 | &:after {
33 | content: ':';
34 | }
35 | }
36 | }
37 | .content {
38 | flex: 1 1 0;
39 | :global {
40 | .ant-form-item:last-child {
41 | margin-right: 0;
42 | }
43 | }
44 | }
45 | }
46 |
47 | .standardFormRowLast {
48 | border: none;
49 | padding-bottom: 0;
50 | margin-bottom: 0;
51 | }
52 |
53 | .standardFormRowBlock {
54 | :global {
55 | .ant-form-item,
56 | div.ant-form-item-control-wrapper {
57 | display: block;
58 | }
59 | }
60 | }
61 |
62 | .standardFormRowGrid {
63 | :global {
64 | .ant-form-item,
65 | div.ant-form-item-control-wrapper {
66 | display: block;
67 | }
68 | .ant-form-item-label {
69 | float: left;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/antdProClient/src/components/SettingDrawer/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/lib/style/themes/default.less';
2 |
3 | .content {
4 | min-height: 100%;
5 | background: #fff;
6 | position: relative;
7 | }
8 |
9 | .blockChecbox {
10 | display: flex;
11 | .item {
12 | margin-right: 16px;
13 | position: relative;
14 | // box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
15 | border-radius: @border-radius-base;
16 | cursor: pointer;
17 | img {
18 | width: 48px;
19 | }
20 | }
21 | .selectIcon {
22 | position: absolute;
23 | top: 0;
24 | right: 0;
25 | width: 100%;
26 | padding-top: 15px;
27 | padding-left: 24px;
28 | height: 100%;
29 | color: @primary-color;
30 | font-size: 14px;
31 | font-weight: bold;
32 | }
33 | }
34 |
35 | .color_block {
36 | width: 38px;
37 | height: 22px;
38 | margin: 4px;
39 | border-radius: 4px;
40 | cursor: pointer;
41 | margin-right: 12px;
42 | display: inline-block;
43 | vertical-align: middle;
44 | }
45 |
46 | .title {
47 | font-size: 14px;
48 | color: @heading-color;
49 | line-height: 22px;
50 | margin-bottom: 12px;
51 | }
52 |
53 | .handle {
54 | position: absolute;
55 | top: 240px;
56 | background: @primary-color;
57 | width: 48px;
58 | height: 48px;
59 | right: 300px;
60 | display: flex;
61 | justify-content: center;
62 | align-items: center;
63 | cursor: pointer;
64 | pointer-events: auto;
65 | z-index: 0;
66 | text-align: center;
67 | font-size: 16px;
68 | border-radius: 4px 0 0 4px;
69 | }
70 |
71 | .productionHint {
72 | font-size: 12px;
73 | margin-top: 16px;
74 | }
75 |
--------------------------------------------------------------------------------
/antdProClient/src/components/DescriptionList/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/lib/style/themes/default.less';
2 |
3 | .descriptionList {
4 | // offset the padding-bottom of last row
5 | :global {
6 | .ant-row {
7 | margin-bottom: -16px;
8 | overflow: hidden;
9 | }
10 | }
11 |
12 | .title {
13 | font-size: 14px;
14 | color: @heading-color;
15 | font-weight: 500;
16 | margin-bottom: 16px;
17 | }
18 |
19 | .term {
20 | // Line-height is 22px IE dom height will calculate error
21 | line-height: 20px;
22 | padding-bottom: 16px;
23 | margin-right: 8px;
24 | color: @heading-color;
25 | white-space: nowrap;
26 | display: table-cell;
27 |
28 | &:after {
29 | content: ':';
30 | margin: 0 8px 0 2px;
31 | position: relative;
32 | top: -0.5px;
33 | }
34 | }
35 |
36 | .detail {
37 | line-height: 20px;
38 | width: 100%;
39 | padding-bottom: 16px;
40 | color: @text-color;
41 | display: table-cell;
42 | }
43 |
44 | &.small {
45 | // offset the padding-bottom of last row
46 | :global {
47 | .ant-row {
48 | margin-bottom: -8px;
49 | }
50 | }
51 | .title {
52 | margin-bottom: 12px;
53 | color: @text-color;
54 | }
55 | .term,
56 | .detail {
57 | padding-bottom: 8px;
58 | }
59 | }
60 |
61 | &.large {
62 | .title {
63 | font-size: 16px;
64 | }
65 | }
66 |
67 | &.vertical {
68 | .term {
69 | padding-bottom: 8px;
70 | display: block;
71 | }
72 |
73 | .detail {
74 | display: block;
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/antdProClient/src/components/Charts/ChartCard/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/lib/style/themes/default.less';
2 |
3 | .chartCard {
4 | position: relative;
5 | .chartTop {
6 | position: relative;
7 | overflow: hidden;
8 | width: 100%;
9 | }
10 | .chartTopMargin {
11 | margin-bottom: 12px;
12 | }
13 | .chartTopHasMargin {
14 | margin-bottom: 20px;
15 | }
16 | .metaWrap {
17 | float: left;
18 | }
19 | .avatar {
20 | position: relative;
21 | top: 4px;
22 | float: left;
23 | margin-right: 20px;
24 | img {
25 | border-radius: 100%;
26 | }
27 | }
28 | .meta {
29 | color: @text-color-secondary;
30 | font-size: @font-size-base;
31 | line-height: 22px;
32 | height: 22px;
33 | }
34 | .action {
35 | cursor: pointer;
36 | position: absolute;
37 | top: 0;
38 | right: 0;
39 | }
40 | .total {
41 | overflow: hidden;
42 | text-overflow: ellipsis;
43 | word-break: break-all;
44 | white-space: nowrap;
45 | color: @heading-color;
46 | margin-top: 4px;
47 | margin-bottom: 0;
48 | font-size: 30px;
49 | line-height: 38px;
50 | height: 38px;
51 | }
52 | .content {
53 | margin-bottom: 12px;
54 | position: relative;
55 | width: 100%;
56 | }
57 | .contentFixed {
58 | position: absolute;
59 | left: 0;
60 | bottom: 0;
61 | width: 100%;
62 | }
63 | .footer {
64 | border-top: 1px solid @border-color-split;
65 | padding-top: 9px;
66 | margin-top: 8px;
67 | & > * {
68 | position: relative;
69 | }
70 | }
71 | .footerMargin {
72 | margin-top: 20px;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/server/pages/5.async.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([[5],{GsTM:function(e,t,r){"use strict";var n=r("g09b"),a=r("tAuX");Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,r("IzEo");var u=n(r("bx4M"));r("T2oS");var o=n(r("W9HT"));r("+L6B");var i,l,d,f=n(r("2/Rp")),c=n(r("2Taf")),g=n(r("vZ4D")),s=n(r("l4Ni")),p=n(r("ujKo")),v=n(r("MhPg")),y=a(r("q1tI")),E=r("MuoO"),h=n(r("uUKN")),m=(i=(0,E.connect)(function(e){return{isloading:e.error.isloading}}),i((d=function(e){function t(){var e,r;(0,c.default)(this,t);for(var n=arguments.length,a=new Array(n),u=0;u span {
34 | color: @heading-color;
35 | display: inline-block;
36 | line-height: 32px;
37 | height: 32px;
38 | font-size: 24px;
39 | margin-right: 32px;
40 | }
41 | .subTotal {
42 | color: @text-color-secondary;
43 | font-size: @font-size-lg;
44 | vertical-align: top;
45 | margin-right: 0;
46 | i {
47 | font-size: 12px;
48 | transform: scale(0.82);
49 | margin-left: 4px;
50 | }
51 | :global {
52 | .anticon-caret-up {
53 | color: @red-6;
54 | }
55 | .anticon-caret-down {
56 | color: @green-6;
57 | }
58 | }
59 | }
60 | }
61 | }
62 | .numberInfolight {
63 | .numberInfoValue {
64 | & > span {
65 | color: @text-color;
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/antdProClient/src/components/DescriptionList/index.en-US.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: DescriptionList
3 | cols: 1
4 | order: 4
5 | ---
6 |
7 | Groups display multiple read-only fields, which are common to informational displays on detail pages.
8 |
9 | ## API
10 |
11 | ### DescriptionList
12 |
13 | | Property | Description | Type | Default |
14 | |----------|------------------------------------------|-------------|---------|
15 | | layout | type of layout | Enum{'horizontal', 'vertical'} | 'horizontal' |
16 | | col | specify the maximum number of columns to display, the final columns number is determined by col setting combined with [Responsive Rules](/components/DescriptionList#Responsive-Rules) | number(0 < col <= 4) | 3 |
17 | | title | title | ReactNode | - |
18 | | gutter | specify the distance between two items, unit is `px` | number | 32 |
19 | | size | size of list | Enum{'large', 'small'} | - |
20 |
21 | #### Responsive Rules
22 |
23 | | Window Width | Columns Number |
24 | |---------------------|---------------------------------------------|
25 | | `≥768px` | `col` |
26 | | `≥576px` | `col < 2 ? col : 2` |
27 | | `<576px` | `1` |
28 |
29 | ### DescriptionList.Description
30 |
31 | | Property | Description | Type | Default |
32 | |----------|------------------------------------------|-------------|-------|
33 | | term | item title | ReactNode | - |
34 |
--------------------------------------------------------------------------------
/antdProClient/src/components/PageHeader/demo/structure.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: Structure
4 | ---
5 |
6 | 基本结构,具备响应式布局功能,主要断点为 768px 和 576px,拖动窗口改变大小试试看。
7 |
8 | ````jsx
9 | import PageHeader from 'ant-design-pro/lib/PageHeader';
10 |
11 | const breadcrumbList = [{
12 | title: '面包屑',
13 | }];
14 |
15 | const tabList = [{
16 | key: '1',
17 | tab: '页签一',
18 | }, {
19 | key: '2',
20 | tab: '页签二',
21 | }, {
22 | key: '3',
23 | tab: '页签三',
24 | }];
25 |
26 | ReactDOM.render(
27 | }
31 | logo={logo
}
32 | action={action
}
33 | content={content
}
34 | extraContent={extraContent
}
35 | breadcrumbList={breadcrumbList}
36 | tabList={tabList}
37 | tabActiveKey="1"
38 | />
39 |
40 | , mountNode);
41 | ````
42 |
43 |
69 |
--------------------------------------------------------------------------------
/antdProClient/src/components/TopNavHeader/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import Link from 'umi/link';
3 | import RightContent from '../GlobalHeader/RightContent';
4 | import BaseMenu from '../SiderMenu/BaseMenu';
5 | import styles from './index.less';
6 |
7 | export default class TopNavHeader extends PureComponent {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | maxWidth: (props.contentWidth === 'Fixed' ? 1200 : window.innerWidth) - 330 - 165 - 4 - 36,
13 | };
14 | }
15 |
16 | static getDerivedStateFromProps(props) {
17 | return {
18 | maxWidth: (props.contentWidth === 'Fixed' ? 1200 : window.innerWidth) - 330 - 165 - 4 - 36,
19 | };
20 | }
21 |
22 | render() {
23 | const { theme, contentWidth, logo } = this.props;
24 | const { maxWidth } = this.state;
25 | return (
26 |
27 |
{
29 | this.maim = ref;
30 | }}
31 | className={`${styles.main} ${contentWidth === 'Fixed' ? styles.wide : ''}`}
32 | >
33 |
34 |
35 |
36 |

37 |
阿里云OSS
38 |
39 |
40 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/antdProClient/src/components/Exception/index.js:
--------------------------------------------------------------------------------
1 | import React, { createElement } from 'react';
2 | import classNames from 'classnames';
3 | import { Button } from 'antd';
4 | import config from './typeConfig';
5 | import styles from './index.less';
6 |
7 | class Exception extends React.PureComponent {
8 | static defaultProps = {
9 | backText: 'back to home',
10 | redirect: '/',
11 | };
12 |
13 | constructor(props) {
14 | super(props);
15 | this.state = {};
16 | }
17 |
18 | render() {
19 | const {
20 | className,
21 | backText,
22 | linkElement = 'a',
23 | type,
24 | title,
25 | desc,
26 | img,
27 | actions,
28 | redirect,
29 | ...rest
30 | } = this.props;
31 | const pageType = type in config ? type : '404';
32 | const clsString = classNames(styles.exception, className);
33 | return (
34 |
35 |
41 |
42 |
{title || config[pageType].title}
43 |
{desc || config[pageType].desc}
44 |
45 | {actions ||
46 | createElement(
47 | linkElement,
48 | {
49 | to: redirect,
50 | href: redirect,
51 | },
52 |
53 | )}
54 |
55 |
56 |
57 | );
58 | }
59 | }
60 |
61 | export default Exception;
62 |
--------------------------------------------------------------------------------
/antdProClient/src/components/NoticeIcon/NoticeList.less:
--------------------------------------------------------------------------------
1 | @import '~antd/lib/style/themes/default.less';
2 |
3 | .list {
4 | max-height: 400px;
5 | overflow: auto;
6 | .item {
7 | transition: all 0.3s;
8 | overflow: hidden;
9 | cursor: pointer;
10 | padding-left: 24px;
11 | padding-right: 24px;
12 |
13 | .meta {
14 | width: 100%;
15 | }
16 |
17 | .avatar {
18 | background: #fff;
19 | margin-top: 4px;
20 | }
21 | .iconElement {
22 | font-size: 32px;
23 | }
24 |
25 | &.read {
26 | opacity: 0.4;
27 | }
28 | &:last-child {
29 | border-bottom: 0;
30 | }
31 | &:hover {
32 | background: @primary-1;
33 | }
34 | .title {
35 | font-weight: normal;
36 | margin-bottom: 8px;
37 | }
38 | .description {
39 | font-size: 12px;
40 | line-height: @line-height-base;
41 | }
42 | .datetime {
43 | font-size: 12px;
44 | margin-top: 4px;
45 | line-height: @line-height-base;
46 | }
47 | .extra {
48 | float: right;
49 | color: @text-color-secondary;
50 | font-weight: normal;
51 | margin-right: 0;
52 | margin-top: -1.5px;
53 | }
54 | }
55 | }
56 |
57 | .notFound {
58 | text-align: center;
59 | padding: 73px 0 88px 0;
60 | color: @text-color-secondary;
61 | img {
62 | display: inline-block;
63 | margin-bottom: 16px;
64 | height: 76px;
65 | }
66 | }
67 |
68 | .clear {
69 | height: 46px;
70 | line-height: 46px;
71 | text-align: center;
72 | color: @text-color;
73 | border-radius: 0 0 @border-radius-base @border-radius-base;
74 | border-top: 1px solid @border-color-split;
75 | transition: all 0.3s;
76 | cursor: pointer;
77 |
78 | &:hover {
79 | color: @heading-color;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/antdProClient/src/components/Authorized/PromiseRender.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Spin } from 'antd';
3 |
4 | export default class PromiseRender extends React.PureComponent {
5 | state = {
6 | component: null,
7 | };
8 |
9 | componentDidMount() {
10 | this.setRenderComponent(this.props);
11 | }
12 |
13 | componentDidUpdate(nextProps) {
14 | // new Props enter
15 | this.setRenderComponent(nextProps);
16 | }
17 |
18 | // set render Component : ok or error
19 | setRenderComponent(props) {
20 | const ok = this.checkIsInstantiation(props.ok);
21 | const error = this.checkIsInstantiation(props.error);
22 | props.promise
23 | .then(() => {
24 | this.setState({
25 | component: ok,
26 | });
27 | })
28 | .catch(() => {
29 | this.setState({
30 | component: error,
31 | });
32 | });
33 | }
34 |
35 | // Determine whether the incoming component has been instantiated
36 | // AuthorizedRoute is already instantiated
37 | // Authorized render is already instantiated, children is no instantiated
38 | // Secured is not instantiated
39 | checkIsInstantiation = target => {
40 | if (!React.isValidElement(target)) {
41 | return target;
42 | }
43 | return () => target;
44 | };
45 |
46 | render() {
47 | const { component: Component } = this.state;
48 | const { ok, error, promise, ...rest } = this.props;
49 | return Component ? (
50 |
51 | ) : (
52 |
61 |
62 |
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/antdProClient/src/e2e/layout.e2e.js:
--------------------------------------------------------------------------------
1 | import puppeteer from 'puppeteer';
2 | import RouterConfig from '../../config/router.config';
3 |
4 | function formatter(data) {
5 | return data
6 | .reduce((pre, item) => {
7 | if (item.routes) {
8 | return pre.concat(formatter(item.routes));
9 | }
10 | pre.push(item.path);
11 | return pre;
12 | }, [])
13 | .filter(item => item);
14 | }
15 |
16 | describe('Homepage', () => {
17 | let browser;
18 | let page;
19 |
20 | const testAllPage = async layout =>
21 | new Promise(async (resolve, reject) => {
22 | const loadPage = async index => {
23 | const path = layout[index];
24 | try {
25 | await page.goto(`http://localhost:8000${path}`, { waitUntil: 'networkidle2' });
26 | const haveFooter = await page.evaluate(
27 | () => document.getElementsByTagName('footer').length > 0
28 | );
29 |
30 | expect(haveFooter).toBeTruthy();
31 |
32 | if (index < layout.length - 1) {
33 | loadPage(index + 1);
34 | } else {
35 | resolve('ok');
36 | }
37 | } catch (error) {
38 | reject(error);
39 | }
40 | };
41 | loadPage(0);
42 | });
43 |
44 | beforeAll(async () => {
45 | browser = await puppeteer.launch({ args: ['--no-sandbox'] });
46 | page = await browser.newPage();
47 | jest.setTimeout(1000000);
48 | });
49 |
50 | it('test user layout', async () => {
51 | const userLayout = formatter(RouterConfig[0].routes);
52 | await testAllPage(userLayout);
53 | });
54 |
55 | it('test base layout', async () => {
56 | const baseLayout = formatter(RouterConfig[1].routes);
57 | await testAllPage(baseLayout);
58 | });
59 |
60 | afterAll(() => browser.close());
61 | });
62 |
--------------------------------------------------------------------------------