diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..a6e49b6e63d49884b73b85bdbdcbc42550ed38ac --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,23 @@ +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 diff --git a/package.json b/package.json index c59ae17e48a1ce91469f9a7877e512eaffc103e6..33cecf335c47e93b5415424ee14aa6680bf93195 100755 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/components/Authorized/CheckPermissions.js b/src/components/Authorized/CheckPermissions.js index a79e28591eb7440c586d1c04ff83eb7b38702cba..8aaad1ce5b01a913aa38e1954d8fd149c23bd406 100644 --- a/src/components/Authorized/CheckPermissions.js +++ b/src/components/Authorized/CheckPermissions.js @@ -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; } diff --git a/src/components/Authorized/CheckPermissions.test.js b/src/components/Authorized/CheckPermissions.test.js index b4b5e2ce4abac1bd691c0a6da339b4fccceac447..1e66cb9edac708ba803cf3a916e54ca36e54b329 100644 --- a/src/components/Authorized/CheckPermissions.test.js +++ b/src/components/Authorized/CheckPermissions.test.js @@ -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'); + }); }); diff --git a/src/components/Authorized/Secured.js b/src/components/Authorized/Secured.js index 1012da46c9e02c219e53b77b2cdfa9f624b502b0..d31cc5650233cb3d7d25b9d51bacc7d265b04451 100644 --- a/src/components/Authorized/Secured.js +++ b/src/components/Authorized/Secured.js @@ -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); }; }; diff --git a/src/components/Authorized/renderAuthorize.js b/src/components/Authorized/renderAuthorize.js index 8e34c6942a2977b9d0a183c9ba277e9cb1ca4ddc..16177edece45ef253789db30d32895344857d5de 100644 --- a/src/components/Authorized/renderAuthorize.js +++ b/src/components/Authorized/renderAuthorize.js @@ -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 { diff --git a/src/components/Charts/Bar/index.js b/src/components/Charts/Bar/index.js index ee188f1bc1294e6bc3b29935c114f8f5c5f6dd28..f0cb65ffee74014dafdb286d2dcc42043ec13c08 100644 --- a/src/components/Charts/Bar/index.js +++ b/src/components/Charts/Bar/index.js @@ -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, diff --git a/src/components/Charts/Pie/index.js b/src/components/Charts/Pie/index.js index 0e07c92b1694f9097794c4e4007b27e6da7cbe5f..134a093425a23c3e53d1a1a8772e61711e9a6849 100644 --- a/src/components/Charts/Pie/index.js +++ b/src/components/Charts/Pie/index.js @@ -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; diff --git a/src/components/Charts/Radar/index.js b/src/components/Charts/Radar/index.js index a2e44ddbdc8a3bc4287540fcab45f21ea248ca44..beb88b77c9fe663342d138973cf728e82696821a 100644 --- a/src/components/Charts/Radar/index.js +++ b/src/components/Charts/Radar/index.js @@ -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(); } } diff --git a/src/components/Charts/TagCloud/index.js b/src/components/Charts/TagCloud/index.js index 5e8ca5842c73eb627de760a5292c465cdb9c58b6..5c299e46927ef2ab62d787527ca23f1016b3a571 100644 --- a/src/components/Charts/TagCloud/index.js +++ b/src/components/Charts/TagCloud/index.js @@ -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); } } diff --git a/src/components/HeaderSearch/index.js b/src/components/HeaderSearch/index.js index 42a7692daa550c87a1c53f42f348e11c6b9e5b51..a01abeb4024dd9b9036e6ff7a308b9b4c9c83532 100644 --- a/src/components/HeaderSearch/index.js +++ b/src/components/HeaderSearch/index.js @@ -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, }); diff --git a/src/components/Login/index.js b/src/components/Login/index.js index f09c146d5c471a2f9796be75563caa2f978ed07b..8a4cf4b533d78d4aa18c46ec78e35463af792c3b 100644 --- a/src/components/Login/index.js +++ b/src/components/Login/index.js @@ -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); }); diff --git a/src/components/PageHeader/index.js b/src/components/PageHeader/index.js index 80de10519830b1ae760a7decd441e47c810d5398..a3a861436b0fb70e265c9ecdc13dae59845a3f65 100644 --- a/src/components/PageHeader/index.js +++ b/src/components/PageHeader/index.js @@ -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 ( {breadcrumb} diff --git a/src/components/TopNavHeader/index.js b/src/components/TopNavHeader/index.js index 1ca29ba38e8f1311298e815feaf799bfbabca805..dc4a9e5797da8011e0747360429c9d37cc6fcf39 100644 --- a/src/components/TopNavHeader/index.js +++ b/src/components/TopNavHeader/index.js @@ -6,13 +6,14 @@ import styles from './index.less'; export default class TopNavHeader extends PureComponent { render() { + const { theme, grid, logo } = this.props; return ( -
-
+
+
diff --git a/src/e2e/login.e2e.js b/src/e2e/login.e2e.js index a102a351160a21eb4ff6805ebf4b821a0dc60ac7..298dbca9d00afc89cbff20bf600f6dc6927ab957 100644 --- a/src/e2e/login.e2e.js +++ b/src/e2e/login.e2e.js @@ -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"]'); diff --git a/src/index.ejs b/src/index.ejs index 56a9660aea31d6efc31cdf387591c9d741f65d01..e7bbc9d2ab24f4c62bd02e057598c9c422ff8306 100644 --- a/src/index.ejs +++ b/src/index.ejs @@ -7,6 +7,7 @@ Ant Design Pro + diff --git a/src/index.js b/src/index.js index cfa30d2246a92a3aa8bab697bee2e0d021fcc538..27a33c83374bc8081bcaf361a2fe44ed8fc7f079 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,4 @@ -import '@babel/polyfill'; -import 'url-polyfill'; +import './polyfill'; import dva from 'dva'; import createHistory from 'history/createHashHistory'; diff --git a/src/layouts/BasicLayout.js b/src/layouts/BasicLayout.js index 599038996d5cbfe822f71a47ee67a632412fce98..608d62c164e489512ff5bed1f3917d9a3fde1cce 100644 --- a/src/layouts/BasicLayout.js +++ b/src/layouts/BasicLayout.js @@ -56,6 +56,10 @@ const query = { }, 'screen-xl': { minWidth: 1200, + maxWidth: 1599, + }, + 'screen-xxl': { + minWidth: 1600, }, }; diff --git a/src/layouts/GridContent.js b/src/layouts/GridContent.js index 09af81397cd53883dd0456452d2d942c93cd69fa..68fa027f0d8d883d45a2e7e075700e951831782f 100644 --- a/src/layouts/GridContent.js +++ b/src/layouts/GridContent.js @@ -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
{this.props.children}
; + return
{children}
; } } diff --git a/src/layouts/UserLayout.js b/src/layouts/UserLayout.js index 447f44fbb5eb23217b443fb95c11da1e9c9b34ee..bcc45a9a885a5c46bb0ea87b45b4a7a09ffd04a4 100644 --- a/src/layouts/UserLayout.js +++ b/src/layouts/UserLayout.js @@ -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 = ( ); +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} /> ))} - +
diff --git a/src/models/login.js b/src/models/login.js index b4b1d11ab624fd090c0bac6a84478771eeb15b8d..7daa1f0210849a51add2e76388c342d9c9cfb7b1 100644 --- a/src/models/login.js +++ b/src/models/login.js @@ -1,6 +1,8 @@ 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: { diff --git a/src/polyfill.js b/src/polyfill.js new file mode 100644 index 0000000000000000000000000000000000000000..ad1eb6b9a483f1ee5ad2950729109058360d3aa5 --- /dev/null +++ b/src/polyfill.js @@ -0,0 +1,12 @@ +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; diff --git a/src/router.js b/src/router.js index 3c3a9786c54fe131ac2d0bae101d69e85e904937..15e2634ad76f8d090f55ff8fda34021d8007866b 100644 --- a/src/router.js +++ b/src/router.js @@ -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 => } authority={['admin', 'user']} - redirectPath="/user/login" + redirectPath={getQueryPath('/user/login', { + redirect: window.location.href, + })} /> diff --git a/src/routes/Forms/AdvancedForm.js b/src/routes/Forms/AdvancedForm.js index 73750be3fd740d3fdd6bff77ba83d84f1ab5d0f3..7fea0d717b3b787b4c949442c8603aab145f03f3 100644 --- a/src/routes/Forms/AdvancedForm.js +++ b/src/routes/Forms/AdvancedForm.js @@ -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 }); + } } }); }; diff --git a/src/utils/authority.js b/src/utils/authority.js index dc2856556239516566520214f00a7106218dce4c..424496b2f9dcc6c3352483a1ce7c68b1587fdbdd 100644 --- a/src/utils/authority.js +++ b/src/utils/authority.js @@ -1,5 +1,6 @@ // 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'; } diff --git a/src/utils/utils.js b/src/utils/utils.js index add0a935bea0068f1dc8e89019894269ba72a150..841f9b7dcbc5872ea2b1cde3706d1c3a1ddf5005 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -1,5 +1,6 @@ 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]*))?)$/; diff --git a/tests/fix_puppeteer.sh b/tests/fix_puppeteer.sh new file mode 100644 index 0000000000000000000000000000000000000000..99e78bd081a5281f054e8247b10d2ead9e509a5c --- /dev/null +++ b/tests/fix_puppeteer.sh @@ -0,0 +1,8 @@ +#!/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