Commit e3a73309 authored by 陈帅's avatar 陈帅

Merge branch 'master' into v2

parents 902bdcb6 67f686a2
version: 2
jobs:
build:
docker:
- image: circleci/node:8.11.2
steps:
- checkout
- run: npm install
- run: npm run build
test:
docker:
- image: circleci/node:8.11.2
steps:
- checkout
- run: sh ./tests/fix_puppeteer.sh
- run: npm install
- run: npm run test:all
workflows:
version: 2
build_and_test:
jobs:
- build
- test
\ No newline at end of file
......@@ -48,6 +48,7 @@
"rollbar": "^2.3.4",
"rollup": "^0.62.0",
"rollup-plugin-json": "^3.0.0",
"setprototypeof": "^1.1.0",
"url-polyfill": "^1.0.10"
},
"devDependencies": {
......
......@@ -29,6 +29,14 @@ const checkPermissions = (authority, currentAuthority, target, Exception) => {
if (authority.indexOf(currentAuthority) >= 0) {
return target;
}
if (Array.isArray(currentAuthority)) {
for (let i = 0; i < currentAuthority.length; i += 1) {
const element = currentAuthority[i];
if (authority.indexOf(element) >= 0) {
return target;
}
}
}
return Exception;
}
......@@ -37,6 +45,14 @@ const checkPermissions = (authority, currentAuthority, target, Exception) => {
if (authority === currentAuthority) {
return target;
}
if (Array.isArray(currentAuthority)) {
for (let i = 0; i < currentAuthority.length; i += 1) {
const element = currentAuthority[i];
if (authority.indexOf(element) >= 0) {
return target;
}
}
}
return Exception;
}
......
......@@ -34,4 +34,22 @@ describe('test CheckPermissions', () => {
it('Correct Function permission authentication', () => {
expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok');
});
it('authority is string, currentAuthority is array, return ok', () => {
expect(checkPermissions('user', ['user'], target, error)).toEqual('ok');
});
it('authority is string, currentAuthority is array, return ok', () => {
expect(checkPermissions('user', ['user', 'admin'], target, error)).toEqual('ok');
});
it('authority is array, currentAuthority is array, return ok', () => {
expect(checkPermissions(['user', 'admin'], ['user', 'admin'], target, error)).toEqual('ok');
});
it('Wrong Function permission authentication', () => {
expect(checkPermissions(() => false, ['user'], target, error)).toEqual('error');
});
it('Correct Function permission authentication', () => {
expect(checkPermissions(() => true, ['user'], target, error)).toEqual('ok');
});
it('authority is undefined , return ok', () => {
expect(checkPermissions(null, ['user'], target, error)).toEqual('ok');
});
});
......@@ -46,8 +46,8 @@ const authorize = (authority, error) => {
if (!authority) {
throw new Error('authority is required');
}
return function decideAuthority(targer) {
const component = CheckPermissions(authority, targer, classError || Exception403);
return function decideAuthority(target) {
const component = CheckPermissions(authority, target, classError || Exception403);
return checkIsInstantiation(component);
};
};
......
......@@ -10,7 +10,10 @@ const renderAuthorize = Authorized => {
if (currentAuthority.constructor.name === 'Function') {
CURRENT = currentAuthority();
}
if (currentAuthority.constructor.name === 'String') {
if (
currentAuthority.constructor.name === 'String' ||
currentAuthority.constructor.name === 'Array'
) {
CURRENT = currentAuthority;
}
} else {
......
......@@ -19,6 +19,14 @@ class Bar extends Component {
window.removeEventListener('resize', this.resize);
}
handleRoot = n => {
this.root = n;
};
handleRef = n => {
this.node = n;
};
@Bind()
@Debounce(400)
resize() {
......@@ -46,14 +54,6 @@ class Bar extends Component {
}
}
handleRoot = n => {
this.root = n;
};
handleRef = n => {
this.node = n;
};
render() {
const {
height,
......
......@@ -139,8 +139,17 @@ export default class Pie extends Component {
[styles.legendBlock]: legendBlock,
});
const {
data: propsData,
selected: propsSelected = true,
tooltip: propsTooltip = true,
} = this.props;
let data = propsData || [];
let selected = propsSelected;
let tooltip = propsTooltip;
const defaultColors = colors;
let { data, selected, tooltip } = this.props;
data = data || [];
selected = selected || true;
tooltip = tooltip || true;
......
......@@ -16,7 +16,8 @@ export default class Radar extends Component {
}
componentDidUpdate(preProps) {
if (this.props.data !== preProps.data) {
const { data } = this.props;
if (data !== preProps.data) {
this.getLegendData();
}
}
......
......@@ -27,7 +27,8 @@ class TagCloud extends Component {
}
componentDidUpdate(preProps) {
if (JSON.stringify(preProps.data) !== JSON.stringify(this.props.data)) {
const { data } = this.props;
if (JSON.stringify(preProps.data) !== JSON.stringify(data)) {
this.renderChart(this.props);
}
}
......
......@@ -87,8 +87,8 @@ export default class HeaderSearch extends PureComponent {
render() {
const { className, placeholder, ...restProps } = this.props;
delete restProps.defaultOpen; // for rc-select not affected
const { searchMode, value } = this.state;
delete restProps.defaultOpen; // for rc-select not affected
const inputClass = classNames(styles.input, {
[styles.show]: searchMode,
});
......
......@@ -74,8 +74,8 @@ class Login extends Component {
handleSubmit = e => {
e.preventDefault();
const { active, type } = this.state;
const activeFileds = active[type];
const { form, onSubmit } = this.props;
const activeFileds = active[type];
form.validateFields(activeFileds, { force: true }, (err, values) => {
onSubmit(err, values);
});
......
......@@ -180,6 +180,7 @@ export default class PageHeader extends PureComponent {
tabBarExtraContent,
loading = false,
} = this.props;
const { breadcrumb } = this.state;
const clsString = classNames(styles.pageHeader, className);
const activeKeyProps = {};
......@@ -189,7 +190,6 @@ export default class PageHeader extends PureComponent {
if (tabActiveKey !== undefined) {
activeKeyProps.activeKey = tabActiveKey;
}
const { breadcrumb } = this.state;
return (
<Card className={clsString} bodyStyle={{ padding: 0 }} loading={loading}>
{breadcrumb}
......
......@@ -6,13 +6,14 @@ import styles from './index.less';
export default class TopNavHeader extends PureComponent {
render() {
const { theme, grid, logo } = this.props;
return (
<div className={`${styles.head} ${this.props.theme === 'light' ? styles.light : ''}`}>
<div className={`${styles.main} ${this.props.grid === 'Wide' ? styles.wide : ''}`}>
<div className={`${styles.head} ${theme === 'light' ? styles.light : ''}`}>
<div className={`${styles.main} ${grid === 'Wide' ? styles.wide : ''}`}>
<div className={styles.left}>
<div className={styles.logo} key="logo" id="logo">
<Link to="/">
<img src={this.props.logo} alt="logo" />
<img src={logo} alt="logo" />
<h1>Ant Design Pro</h1>
</Link>
</div>
......
......@@ -17,6 +17,9 @@ describe('Login', () => {
afterEach(() => page.close());
it('should login with failure', async () => {
await page.waitForSelector('#userName', {
timeout: 2000,
});
await page.type('#userName', 'mockuser');
await page.type('#password', 'wrong_password');
await page.click('button[type="submit"]');
......@@ -24,6 +27,9 @@ describe('Login', () => {
});
it('should login successfully', async () => {
await page.waitForSelector('#userName', {
timeout: 2000,
});
await page.type('#userName', 'admin');
await page.type('#password', '888888');
await page.click('button[type="submit"]');
......
......@@ -7,6 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ant Design Pro</title>
<link rel="icon" href="/favicon.png" type="image/x-icon">
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
<script src="https://gw.alipayobjects.com/os/rmsportal/nGVBgVyXzzmbAqevIAPy.js">
</script>
<script src=" https://gw.alipayobjects.com/os/rmsportal/TKSqiyoUxzrHoMwjViwA.js "></script>
......
import '@babel/polyfill';
import 'url-polyfill';
import './polyfill';
import dva from 'dva';
import createHistory from 'history/createHashHistory';
......
......@@ -56,6 +56,10 @@ const query = {
},
'screen-xl': {
minWidth: 1200,
maxWidth: 1599,
},
'screen-xxl': {
minWidth: 1600,
},
};
......
......@@ -4,11 +4,12 @@ import styles from './GridContent.less';
class GridContent extends PureComponent {
render() {
const { grid, children } = this.props;
let className = `${styles.main}`;
if (this.props.grid === 'Wide') {
if (grid === 'Wide') {
className = `${styles.main} ${styles.wide}`;
}
return <div className={className}>{this.props.children}</div>;
return <div className={className}>{children}</div>;
}
}
......
......@@ -5,7 +5,7 @@ import { Icon } from 'antd';
import GlobalFooter from '../components/GlobalFooter';
import styles from './UserLayout.less';
import logo from '../assets/logo.svg';
import { getRoutes } from '../utils/utils';
import { getRoutes, getPageQuery, getQueryPath } from '../utils/utils';
const links = [
{
......@@ -31,6 +31,14 @@ const copyright = (
</Fragment>
);
function getLoginPathWithRedirectPath() {
const params = getPageQuery();
const { redirect } = params;
return getQueryPath('/user/login', {
redirect,
});
}
class UserLayout extends React.PureComponent {
getPageTitle() {
const { routerData, location } = this.props;
......@@ -66,7 +74,7 @@ class UserLayout extends React.PureComponent {
exact={item.exact}
/>
))}
<Redirect exact from="/user" to="/user/login" />
<Redirect from="/user" to={getLoginPathWithRedirectPath()} />
</Switch>
</div>
<GlobalFooter links={links} copyright={copyright} />
......
import { routerRedux } from 'dva/router';
import { stringify } from 'qs';
import { fakeAccountLogin, getFakeCaptcha } from '../services/api';
import { setAuthority } from '../utils/authority';
import { getPageQuery } from '../utils/utils';
import { reloadAuthorized } from '../utils/Authorized';
export default {
......@@ -20,32 +22,45 @@ export default {
// Login successfully
if (response.status === 'ok') {
reloadAuthorized();
yield put(routerRedux.push('/'));
}
},
*logout(_, { put, select }) {
try {
// get location pathname
const urlParams = new URL(window.location.href);
const pathname = yield select(state => state.routing.location.pathname);
// add the parameters in the url
urlParams.searchParams.set('redirect', pathname);
window.history.replaceState(null, 'login', urlParams.href);
} finally {
yield put({
type: 'changeLoginStatus',
payload: {
status: false,
currentAuthority: 'guest',
},
});
reloadAuthorized();
yield put(routerRedux.push('/user/login'));
const params = getPageQuery();
let { redirect } = params;
if (redirect) {
const redirectUrlParams = new URL(redirect);
if (redirectUrlParams.origin === urlParams.origin) {
redirect = redirect.substr(urlParams.origin.length);
if (redirect.startsWith('/#')) {
redirect = redirect.substr(2);
}
} else {
window.location.href = redirect;
return;
}
}
yield put(routerRedux.replace(redirect || '/'));
}
},
*getCaptcha({ payload }, { call }) {
yield call(getFakeCaptcha, payload);
},
*logout(_, { put }) {
yield put({
type: 'changeLoginStatus',
payload: {
status: false,
currentAuthority: 'guest',
},
});
reloadAuthorized();
yield put(
routerRedux.push({
pathname: '/user/login',
search: stringify({
redirect: window.location.href,
}),
})
);
},
},
reducers: {
......
import '@babel/polyfill';
import 'url-polyfill';
import setprototypeof from 'setprototypeof';
// React depends on set/map/requestAnimationFrame
// https://reactjs.org/docs/javascript-environment-requirements.html
// import 'core-js/es6/set';
// import 'core-js/es6/map';
// import 'raf/polyfill'; 只兼容到IE10不需要,况且fetch的polyfill whatwg-fetch也只兼容到IE10
// https://github.com/umijs/umi/issues/413
Object.setPrototypeOf = setprototypeof;
......@@ -5,6 +5,7 @@ import zhCN from 'antd/lib/locale-provider/zh_CN';
import dynamic from 'dva/dynamic';
import { getRouterData } from './common/router';
import Authorized from './utils/Authorized';
import { getQueryPath } from './utils/utils';
import styles from './index.less';
const { ConnectedRouter } = routerRedux;
......@@ -26,7 +27,9 @@ function RouterConfig({ history, app }) {
path="/"
render={props => <BasicLayout {...props} />}
authority={['admin', 'user']}
redirectPath="/user/login"
redirectPath={getQueryPath('/user/login', {
redirect: window.location.href,
})}
/>
</Switch>
</ConnectedRouter>
......
......@@ -73,10 +73,12 @@ class AdvancedForm extends PureComponent {
resizeFooterToolbar = () => {
requestAnimationFrame(() => {
const sider = document.querySelectorAll('.ant-layout-sider')[0];
const width = `calc(100% - ${sider.style.width})`;
const { width: stateWidth } = this.state;
if (stateWidth !== width) {
this.setState({ width });
if (sider) {
const width = `calc(100% - ${sider.style.width})`;
const { width: stateWidth } = this.state;
if (stateWidth !== width) {
this.setState({ width });
}
}
});
};
......
// use localStorage to store the authority info, which might be sent from server in actual project.
export function getAuthority() {
// return localStorage.getItem('antd-pro-authority') || ['admin', 'user'];
return localStorage.getItem('antd-pro-authority') || 'admin';
}
......
import moment from 'moment';
import React from 'react';
import { parse, stringify } from 'qs';
export function fixedZero(val) {
return val * 1 < 10 ? `0${val}` : val;
......@@ -162,6 +163,18 @@ export function getRoutes(path, routerData) {
return renderRoutes;
}
export function getPageQuery() {
return parse(window.location.href.split('?')[1]);
}
export function getQueryPath(path = '', query = {}) {
const search = stringify(query);
if (search.length) {
return `${path}?${search}`;
}
return path;
}
/* eslint no-useless-escape:0 */
const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
......
#!/bin/bash
sudo apt-get update
sudo apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \
libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
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