Commit 92293e44 authored by ddcat1115's avatar ddcat1115

Merge branch 'master' into v2

parents 509e432c ebd6f2ff
......@@ -2,6 +2,10 @@
environment:
nodejs_version: "8"
# this is how to allow failing jobs in the matrix
matrix:
fast_finish: true # set this flag to immediately finish build once one of the jobs fails.
# Install scripts. (runs after repo cloning)
install:
# Get the latest stable version of Node.js or io.js
......
......@@ -45,6 +45,7 @@
"react-document-title": "^2.0.3",
"react-dom": "^16.2.0",
"react-fittext": "^1.0.0",
"rollbar": "^2.3.4",
"url-polyfill": "^1.0.10"
},
"devDependencies": {
......@@ -72,7 +73,6 @@
"regenerator-runtime": "^0.11.1",
"roadhog": "^2.1.0",
"roadhog-api-doc": "^0.3.4",
"rollbar": "^2.3.4",
"stylelint": "^8.4.0",
"stylelint-config-standard": "^18.0.0"
},
......
......@@ -115,7 +115,7 @@ const menuData = [{
}],
}];
function formatter(data, parentPath = '', parentAuthority) {
function formatter(data, parentPath = '/', parentAuthority) {
return data.map((item) => {
let { path } = item;
if (!isUrl(path)) {
......
......@@ -92,12 +92,15 @@ export const getRouterData = (app) => {
component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm')),
},
'/form/step-form/info': {
name: '分步表单(填写转账信息)',
component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm/Step1')),
},
'/form/step-form/confirm': {
name: '分步表单(确认转账信息)',
component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm/Step2')),
},
'/form/step-form/result': {
name: '分步表单(完成)',
component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm/Step3')),
},
'/form/advanced-form': {
......@@ -193,7 +196,7 @@ export const getRouterData = (app) => {
// Regular match item name
// eg. router /user/:id === /user/chen
const pathRegexp = pathToRegexp(path);
const menuKey = Object.keys(menuData).find(key => pathRegexp.test(`/${key}`));
const menuKey = Object.keys(menuData).find(key => pathRegexp.test(`${key}`));
let menuItem = {};
// If menuKey is not empty
if (menuKey) {
......
......@@ -6,9 +6,17 @@ export default class PromiseRender extends React.PureComponent {
component: null,
};
componentDidMount() {
const ok = this.checkIsInstantiation(this.props.ok);
const error = this.checkIsInstantiation(this.props.error);
this.props.promise
this.setRenderComponent(this.props);
}
componentWillReceiveProps(nextProps) {
// new Props enter
this.setRenderComponent(nextProps);
}
// set render Component : ok or error
setRenderComponent(props) {
const ok = this.checkIsInstantiation(props.ok);
const error = this.checkIsInstantiation(props.error);
props.promise
.then(() => {
this.setState({
component: ok,
......
......@@ -51,9 +51,8 @@ class CountDown extends Component {
}
lastTime = targetTime - new Date().getTime();
return {
lastTime,
lastTime: lastTime < 0 ? 0 : lastTime,
};
}
// defaultFormat = time => (
......@@ -63,11 +62,11 @@ class CountDown extends Component {
const hours = 60 * 60 * 1000;
const minutes = 60 * 1000;
const h = fixedZero(Math.floor(time / hours));
const m = fixedZero(Math.floor((time - (h * hours)) / minutes));
const s = fixedZero(Math.floor((time - (h * hours) - (m * minutes)) / 1000));
const h = Math.floor(time / hours);
const m = Math.floor((time - (h * hours)) / minutes);
const s = Math.floor((time - (h * hours) - (m * minutes)) / 1000);
return (
<span>{h}:{m}:{s}</span>
<span>{fixedZero(h)}:{fixedZero(m)}:{fixedZero(s)}</span>
);
}
tick = () => {
......@@ -96,9 +95,8 @@ class CountDown extends Component {
}
render() {
const { format = this.defaultFormat, ...rest } = this.props;
const { format = this.defaultFormat, onEnd, ...rest } = this.props;
const { lastTime } = this.state;
const result = format(lastTime);
return (<span {...rest}>{result}</span>);
......
......@@ -4,7 +4,7 @@ import pathToRegexp from 'path-to-regexp';
import { Breadcrumb, Tabs } from 'antd';
import classNames from 'classnames';
import styles from './index.less';
import { urlToList } from '../utils/pathTools';
const { TabPane } = Tabs;
export function getBreadcrumb(breadcrumbNameMap, url) {
......@@ -19,14 +19,6 @@ export function getBreadcrumb(breadcrumbNameMap, url) {
return breadcrumb || {};
}
// /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id']
export function urlToList(url) {
const urllist = url.split('/').filter(i => i);
return urllist.map((urlItem, index) => {
return `/${urllist.slice(0, index + 1).join('/')}`;
});
}
export default class PageHeader extends PureComponent {
static contextTypes = {
routes: PropTypes.array,
......@@ -44,29 +36,35 @@ export default class PageHeader extends PureComponent {
routes: this.props.routes || this.context.routes,
params: this.props.params || this.context.params,
routerLocation: this.props.location || this.context.location,
breadcrumbNameMap: this.props.breadcrumbNameMap || this.context.breadcrumbNameMap,
breadcrumbNameMap:
this.props.breadcrumbNameMap || this.context.breadcrumbNameMap,
};
};
// Generated according to props
conversionFromProps= () => {
conversionFromProps = () => {
const {
breadcrumbList, breadcrumbSeparator, linkElement = 'a',
breadcrumbList,
breadcrumbSeparator,
linkElement = 'a',
} = this.props;
return (
<Breadcrumb
className={styles.breadcrumb}
separator={breadcrumbSeparator}
>
<Breadcrumb className={styles.breadcrumb} separator={breadcrumbSeparator}>
{breadcrumbList.map(item => (
<Breadcrumb.Item key={item.title}>
{item.href ? (createElement(linkElement, {
{item.href
? createElement(
linkElement,
{
[linkElement === 'a' ? 'href' : 'to']: item.href,
}, item.title)) : item.title}
},
item.title,
)
: item.title}
</Breadcrumb.Item>
))}
</Breadcrumb>
);
}
};
conversionFromLocation = (routerLocation, breadcrumbNameMap) => {
const { breadcrumbSeparator, linkElement = 'a' } = this.props;
// Convert the url to an array
......@@ -74,7 +72,8 @@ export default class PageHeader extends PureComponent {
// Loop data mosaic routing
const extraBreadcrumbItems = pathSnippets.map((url, index) => {
const currentBreadcrumb = getBreadcrumb(breadcrumbNameMap, url);
const isLinkable = (index !== pathSnippets.length - 1) && currentBreadcrumb.component;
const isLinkable =
index !== pathSnippets.length - 1 && currentBreadcrumb.component;
return currentBreadcrumb.name && !currentBreadcrumb.hideInBreadcrumb ? (
<Breadcrumb.Item key={url}>
{createElement(
......@@ -88,26 +87,33 @@ export default class PageHeader extends PureComponent {
// Add home breadcrumbs to your head
extraBreadcrumbItems.unshift(
<Breadcrumb.Item key="home">
{createElement(linkElement, {
[linkElement === 'a' ? 'href' : 'to']: '/' }, '首页')}
</Breadcrumb.Item>
{createElement(
linkElement,
{
[linkElement === 'a' ? 'href' : 'to']: '/',
},
'首页',
)}
</Breadcrumb.Item>,
);
return (
<Breadcrumb
className={styles.breadcrumb}
separator={breadcrumbSeparator}
>
<Breadcrumb className={styles.breadcrumb} separator={breadcrumbSeparator}>
{extraBreadcrumbItems}
</Breadcrumb>
);
}
};
/**
* 将参数转化为面包屑
* Convert parameters into breadcrumbs
*/
conversionBreadcrumbList = () => {
const { breadcrumbList, breadcrumbSeparator } = this.props;
const { routes, params, routerLocation, breadcrumbNameMap } = this.getBreadcrumbProps();
const {
routes,
params,
routerLocation,
breadcrumbNameMap,
} = this.getBreadcrumbProps();
if (breadcrumbList && breadcrumbList.length) {
return this.conversionFromProps();
}
......@@ -126,28 +132,41 @@ export default class PageHeader extends PureComponent {
}
// 根据 location 生成 面包屑
// Generate breadcrumbs based on location
if (location && location.pathname) {
if (routerLocation && routerLocation.pathname) {
return this.conversionFromLocation(routerLocation, breadcrumbNameMap);
}
return null;
}
};
// 渲染Breadcrumb 子节点
// Render the Breadcrumb child node
itemRender = (route, params, routes, paths) => {
const { linkElement = 'a' } = this.props;
const last = routes.indexOf(route) === routes.length - 1;
return (last || !route.component)
? <span>{route.breadcrumbName}</span>
: createElement(linkElement, {
return last || !route.component ? (
<span>{route.breadcrumbName}</span>
) : (
createElement(
linkElement,
{
href: paths.join('/') || '/',
to: paths.join('/') || '/',
}, route.breadcrumbName);
}
},
route.breadcrumbName,
)
);
};
render() {
const {
title, logo, action, content, extraContent,
tabList, className, tabActiveKey, tabBarExtraContent,
title,
logo,
action,
content,
extraContent,
tabList,
className,
tabActiveKey,
tabBarExtraContent,
} = this.props;
const clsString = classNames(styles.pageHeader, className);
......@@ -175,12 +194,13 @@ export default class PageHeader extends PureComponent {
</div>
<div className={styles.row}>
{content && <div className={styles.content}>{content}</div>}
{extraContent && <div className={styles.extraContent}>{extraContent}</div>}
{extraContent && (
<div className={styles.extraContent}>{extraContent}</div>
)}
</div>
</div>
</div>
{
tabList &&
{tabList &&
tabList.length && (
<Tabs
className={styles.tabs}
......@@ -188,12 +208,9 @@ export default class PageHeader extends PureComponent {
onChange={this.onChange}
tabBarExtraContent={tabBarExtraContent}
>
{
tabList.map(item => <TabPane tab={item.tab} key={item.key} />)
}
{tabList.map(item => <TabPane tab={item.tab} key={item.key} />)}
</Tabs>
)
}
)}
</div>
);
}
......
import { getBreadcrumb, urlToList } from './index';
describe('test urlToList', () => {
it('A path', () => {
expect(urlToList('/userinfo')).toEqual(['/userinfo']);
});
it('Secondary path', () => {
expect(urlToList('/userinfo/2144')).toEqual([
'/userinfo',
'/userinfo/2144',
]);
});
it('Three paths', () => {
expect(urlToList('/userinfo/2144/addr')).toEqual([
'/userinfo',
'/userinfo/2144',
'/userinfo/2144/addr',
]);
});
});
import { getBreadcrumb } from './index';
import { urlToList } from '../utils/pathTools';
const routerData = {
'/dashboard/analysis': {
......@@ -36,17 +18,17 @@ const routerData = {
describe('test getBreadcrumb', () => {
it('Simple url', () => {
expect(getBreadcrumb(routerData, '/dashboard/analysis').name).toEqual(
'分析页'
'分析页',
);
});
it('Parameters url', () => {
expect(getBreadcrumb(routerData, '/userinfo/2144').name).toEqual(
'用户信息'
'用户信息',
);
});
it('The middle parameter url', () => {
expect(getBreadcrumb(routerData, '/userinfo/2144/addr').name).toEqual(
'收货订单'
'收货订单',
);
});
it('Loop through the parameters', () => {
......
......@@ -3,6 +3,7 @@ import { Layout, Menu, Icon } from 'antd';
import pathToRegexp from 'path-to-regexp';
import { Link } from 'dva/router';
import styles from './index.less';
import { urlToList } from '../utils/pathTools';
const { Sider } = Layout;
const { SubMenu } = Menu;
......@@ -21,10 +22,17 @@ const getIcon = (icon) => {
return icon;
};
export const getMeunMatcheys = (flatMenuKeys, path) => {
return flatMenuKeys.filter((item) => {
return pathToRegexp(item).test(path);
});
};
export default class SiderMenu extends PureComponent {
constructor(props) {
super(props);
this.menus = props.menuData;
this.flatMenuKeys = this.getFlatMenuKeys(props.menuData);
this.state = {
openKeys: this.getDefaultCollapsedSubMenus(props),
};
......@@ -43,30 +51,11 @@ export default class SiderMenu extends PureComponent {
*/
getDefaultCollapsedSubMenus(props) {
const { location: { pathname } } = props || this.props;
// eg. /list/search/articles = > ['','list','search','articles']
let snippets = pathname.split('/');
// Delete the end
// eg. delete 'articles'
snippets.pop();
// Delete the head
// eg. delete ''
snippets.shift();
// eg. After the operation is completed, the array should be ['list','search']
// eg. Forward the array as ['list','list/search']
snippets = snippets.map((item, index) => {
// If the array length > 1
if (index > 0) {
// eg. search => ['list','search'].join('/')
return snippets.slice(0, index + 1).join('/');
}
// index 0 to not do anything
return item;
});
snippets = snippets.map((item) => {
return this.getSelectedMenuKeys(`/${item}`)[0];
});
// eg. ['list','list/search']
return snippets;
return urlToList(pathname)
.map((item) => {
return getMeunMatcheys(this.flatMenuKeys, item)[0];
})
.filter(item => item);
}
/**
* Recursively flatten the data
......@@ -77,24 +66,12 @@ export default class SiderMenu extends PureComponent {
let keys = [];
menus.forEach((item) => {
if (item.children) {
keys.push(item.path);
keys = keys.concat(this.getFlatMenuKeys(item.children));
} else {
keys.push(item.path);
}
keys.push(item.path);
});
return keys;
}
/**
* Get selected child nodes
* /user/chen => ['user','/user/:id']
*/
getSelectedMenuKeys = (path) => {
const flatMenuKeys = this.getFlatMenuKeys(this.menus);
return flatMenuKeys.filter((item) => {
return pathToRegexp(`/${item}(.*)`).test(path);
});
}
/**
* 判断是否是http链接.返回 Link 或 a
* Judge whether it is http link.return a or Link
......@@ -108,7 +85,8 @@ export default class SiderMenu extends PureComponent {
if (/^https?:\/\//.test(itemPath)) {
return (
<a href={itemPath} target={target}>
{icon}<span>{name}</span>
{icon}
<span>{name}</span>
</a>
);
}
......@@ -117,16 +95,23 @@ export default class SiderMenu extends PureComponent {
to={itemPath}
target={target}
replace={itemPath === this.props.location.pathname}
onClick={this.props.isMobile ? () => { this.props.onCollapse(true); } : undefined}
onClick={
this.props.isMobile
? () => {
this.props.onCollapse(true);
}
: undefined
}
>
{icon}<span>{name}</span>
{icon}
<span>{name}</span>
</Link>
);
}
};
/**
* get SubMenu or Item
*/
getSubMenuOrItem=(item) => {
getSubMenuOrItem = (item) => {
if (item.children && item.children.some(child => child.name)) {
return (
<SubMenu
......@@ -136,7 +121,9 @@ export default class SiderMenu extends PureComponent {
{getIcon(item.icon)}
<span>{item.name}</span>
</span>
) : item.name
) : (
item.name
)
}
key={item.path}
>
......@@ -145,12 +132,10 @@ export default class SiderMenu extends PureComponent {
);
} else {
return (
<Menu.Item key={item.path}>
{this.getMenuItemPath(item)}
</Menu.Item>
<Menu.Item key={item.path}>{this.getMenuItemPath(item)}</Menu.Item>
);
}
}
};
/**
* 获得菜单子节点
* @memberof SiderMenu
......@@ -162,49 +147,60 @@ export default class SiderMenu extends PureComponent {
return menusData
.filter(item => item.name && !item.hideInMenu)
.map((item) => {
// make dom
const ItemDom = this.getSubMenuOrItem(item);
return this.checkPermissionItem(item.authority, ItemDom);
})
.filter(item => !!item);
}
.filter(item => item);
};
// Get the currently selected menu
getSelectedMenuKeys = () => {
const { location: { pathname } } = this.props;
return urlToList(pathname).map(itemPath =>
getMeunMatcheys(this.flatMenuKeys, itemPath).pop(),
);
};
// conversion Path
// 转化路径
conversionPath=(path) => {
conversionPath = (path) => {
if (path && path.indexOf('http') === 0) {
return path;
} else {
return `/${path || ''}`.replace(/\/+/g, '/');
}
}
};
// permission to check
checkPermissionItem = (authority, ItemDom) => {
if (this.props.Authorized && this.props.Authorized.check) {
const { check } = this.props.Authorized;
return check(
authority,
ItemDom
);
return check(authority, ItemDom);
}
return ItemDom;
};
isMainMenu = (key) => {
return this.menus.some(
item =>
key && (item.key === key || item.path === key),
);
}
handleOpenChange = (openKeys) => {
const lastOpenKey = openKeys[openKeys.length - 1];
const isMainMenu = this.menus.some(
item => lastOpenKey && (item.key === lastOpenKey || item.path === lastOpenKey)
);
const moreThanOne = openKeys.filter(openKey => this.isMainMenu(openKey)).length > 1;
this.setState({
openKeys: isMainMenu ? [lastOpenKey] : [...openKeys],
openKeys: moreThanOne ? [lastOpenKey] : [...openKeys],
});
}
};
render() {
const { logo, collapsed, location: { pathname }, onCollapse } = this.props;
const { logo, collapsed, onCollapse } = this.props;
const { openKeys } = this.state;
// Don't show popup menu when it is been collapsed
const menuProps = collapsed ? {} : {
const menuProps = collapsed
? {}
: {
openKeys,
};
// if pathname can't match, use the nearest parent's key
let selectedKeys = this.getSelectedMenuKeys(pathname);
let selectedKeys = this.getSelectedMenuKeys();
if (!selectedKeys.length) {
selectedKeys = [openKeys[openKeys.length - 1]];
}
......
import { getMeunMatcheys } from './SiderMenu';
const meun = [
'/dashboard',
'/userinfo',
'/dashboard/name',
'/userinfo/:id',
'/userinfo/:id/info',
];
describe('test meun match', () => {
it('simple path', () => {
expect(getMeunMatcheys(meun, '/dashboard')).toEqual(['/dashboard']);
});
it('error path', () => {
expect(getMeunMatcheys(meun, '/dashboardname')).toEqual([]);
});
it('Secondary path', () => {
expect(getMeunMatcheys(meun, '/dashboard/name')).toEqual([
'/dashboard/name',
]);
});
it('Parameter path', () => {
expect(getMeunMatcheys(meun, '/userinfo/2144')).toEqual([
'/userinfo/:id',
]);
});
it('three parameter path', () => {
expect(getMeunMatcheys(meun, '/userinfo/2144/info')).toEqual([
'/userinfo/:id/info',
]);
});
});
// /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id']
export function urlToList(url) {
const urllist = url.split('/').filter(i => i);
return urllist.map((urlItem, index) => {
return `/${urllist.slice(0, index + 1).join('/')}`;
});
}
import { urlToList } from './pathTools';
describe('test urlToList', () => {
it('A path', () => {
expect(urlToList('/userinfo')).toEqual(['/userinfo']);
});
it('Secondary path', () => {
expect(urlToList('/userinfo/2144')).toEqual([
'/userinfo',
'/userinfo/2144',
]);
});
it('Three paths', () => {
expect(urlToList('/userinfo/2144/addr')).toEqual([
'/userinfo',
'/userinfo/2144',
'/userinfo/2144/addr',
]);
});
});
......@@ -27,8 +27,8 @@ 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}`,
from: `${item.path}`,
to: `${item.children[0].path}`,
});
item.children.forEach((children) => {
getRedirect(children);
......
......@@ -14,6 +14,9 @@
.ant-card-actions {
background: #f7f9fa;
}
.ant-list .ant-list-item-content-single {
max-width: 100%;
}
}
.cardInfo {
.clearfix();
......
......@@ -25,6 +25,12 @@
.item {
height: 64px;
}
:global {
.ant-list .ant-list-item-content-single {
max-width: 100%;
}
}
}
.extraImg {
......
......@@ -6,6 +6,7 @@ import { Row, Col, Form, Card, Select, List } from 'antd';
import StandardFormRow from '../../components/StandardFormRow';
import TagSelect from '../../components/TagSelect';
import AvatarList from '../../components/AvatarList';
import Ellipsis from '../../components/Ellipsis';
import styles from './Projects.less';
......@@ -54,7 +55,7 @@ export default class CoverCardList extends PureComponent {
<List
rowKey="id"
loading={loading}
grid={{ gutter: 24, lg: 4, md: 3, sm: 2, xs: 1 }}
grid={{ gutter: 24, xl: 4, lg: 3, md: 3, sm: 2, xs: 1 }}
dataSource={list}
renderItem={item => (
<List.Item>
......@@ -65,7 +66,7 @@ export default class CoverCardList extends PureComponent {
>
<Card.Meta
title={<a href="#">{item.title}</a>}
description={item.subDescription}
description={<Ellipsis lines={2}>{item.subDescription}</Ellipsis>}
/>
<div className={styles.cardItemContent}>
<span>{moment(item.updatedAt).fromNow()}</span>
......
......@@ -46,4 +46,10 @@
.cardList {
margin-top: 24px;
}
:global {
.ant-list .ant-list-item-content-single {
max-width: 100%;
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment