diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..ecd70ca Binary files /dev/null and b/public/favicon.png differ diff --git a/public/icons/icon-128x128.png b/public/icons/icon-128x128.png new file mode 100644 index 0000000..ecd70ca Binary files /dev/null and b/public/icons/icon-128x128.png differ diff --git a/public/icons/icon-192x192.png b/public/icons/icon-192x192.png new file mode 100644 index 0000000..ecd70ca Binary files /dev/null and b/public/icons/icon-192x192.png differ diff --git a/public/icons/icon-512x512.png b/public/icons/icon-512x512.png new file mode 100644 index 0000000..ecd70ca Binary files /dev/null and b/public/icons/icon-512x512.png differ diff --git a/scripts/generateMock.js b/scripts/generateMock.js new file mode 100644 index 0000000..54c7d6d --- /dev/null +++ b/scripts/generateMock.js @@ -0,0 +1,3 @@ +const generateMock = require('merge-umi-mock-data'); +const path = require('path'); +generateMock(path.join(__dirname, '../mock'), path.join(__dirname, '../functions/mock/index.js')); diff --git a/scripts/getPrettierFiles.js b/scripts/getPrettierFiles.js new file mode 100644 index 0000000..dda20c9 --- /dev/null +++ b/scripts/getPrettierFiles.js @@ -0,0 +1,23 @@ +const glob = require('glob'); + +const getPrettierFiles = () => { + let files = []; + const configFiles = glob.sync('config/**/*.js*', { ignore: ['**/node_modules/**', 'build/**'] }); + const mockFiles = glob.sync('mock/**/*.js*', { ignore: ['**/node_modules/**', 'build/**'] }); + const jsFiles = glob.sync('src/**/*.js*', { ignore: ['**/node_modules/**', 'build/**'] }); + const scriptFiles = glob.sync('scripts/**/*.js'); + const tsFiles = glob.sync('src/**/*.ts*', { ignore: ['**/node_modules/**', 'build/**'] }); + const lessFiles = glob.sync('src/**/*.less*', { ignore: ['**/node_modules/**', 'build/**'] }); + files = files.concat(configFiles); + files = files.concat(mockFiles); + files = files.concat(jsFiles); + files = files.concat(scriptFiles); + files = files.concat(tsFiles); + files = files.concat(lessFiles); + if (!files.length) { + return; + } + return files; +}; + +module.exports = getPrettierFiles; diff --git a/scripts/lint-prettier.js b/scripts/lint-prettier.js new file mode 100644 index 0000000..677f793 --- /dev/null +++ b/scripts/lint-prettier.js @@ -0,0 +1,50 @@ +/** + * copy to https://github.com/facebook/react/blob/master/scripts/prettier/index.js + * prettier api doc https://prettier.io/docs/en/api.html + *----------*****-------------- + * lint file is prettier + *----------*****-------------- + */ + +const prettier = require('prettier'); +const fs = require('fs'); +const chalk = require('chalk'); +const prettierConfigPath = require.resolve('../.prettierrc'); + +const files = process.argv.slice(2); + +let didError = false; + +files.forEach(file => { + Promise.all([ + prettier.resolveConfig(file, { + config: prettierConfigPath, + }), + prettier.getFileInfo(file), + ]) + .then(resolves => { + const [options, fileInfo] = resolves; + if (fileInfo.ignored) { + return; + } + const input = fs.readFileSync(file, 'utf8'); + const withParserOptions = { + ...options, + parser: fileInfo.inferredParser, + }; + const output = prettier.format(input, withParserOptions); + if (output !== input) { + fs.writeFileSync(file, output, 'utf8'); + console.log(chalk.green(`${file} is prettier`)); + } + }) + .catch(e => { + didError = true; + }) + .finally(() => { + if (didError) { + process.exit(1); + } + console.log(chalk.hex('#1890FF')('prettier success!')); + }); +}); diff --git a/scripts/prettier.js b/scripts/prettier.js new file mode 100644 index 0000000..17ded6c --- /dev/null +++ b/scripts/prettier.js @@ -0,0 +1,46 @@ +/** + * copy to https://github.com/facebook/react/blob/master/scripts/prettier/index.js + * prettier api doc https://prettier.io/docs/en/api.html + *----------*****-------------- + * prettier all js and all ts. + *----------*****-------------- + */ + +const prettier = require('prettier'); +const fs = require('fs'); +const getPrettierFiles = require('./getPrettierFiles'); +const prettierConfigPath = require.resolve('../.prettierrc'); +const chalk = require('chalk'); + +let didError = false; + +const files = getPrettierFiles(); + +files.forEach(file => { + const options = prettier.resolveConfig.sync(file, { + config: prettierConfigPath, + }); + const fileInfo = prettier.getFileInfo.sync(file); + if (fileInfo.ignored) { + return; + } + try { + const input = fs.readFileSync(file, 'utf8'); + const withParserOptions = { + ...options, + parser: fileInfo.inferredParser, + }; + const output = prettier.format(input, withParserOptions); + if (output !== input) { + fs.writeFileSync(file, output, 'utf8'); + console.log(chalk.green(`${file} is prettier`)); + } + } catch (e) { + didError = true; + } +}); + +if (didError) { + process.exit(1); +} +console.log(chalk.hex('#1890FF')('prettier success!')); diff --git a/src/actions/client.js b/src/actions/client.js new file mode 100644 index 0000000..c8b55da --- /dev/null +++ b/src/actions/client.js @@ -0,0 +1,36 @@ +export const CLIENT_NAMESPACE = 'client'; + +export function CLIENT_LIST(payload) { + return { + type: `${CLIENT_NAMESPACE}/fetchList`, + payload, + }; +} + +export function CLIENT_DETAIL(id) { + return { + type: `${CLIENT_NAMESPACE}/fetchDetail`, + payload: { id }, + }; +} + +export function CLIENT_CLEAR_DETAIL() { + return { + type: `${CLIENT_NAMESPACE}/clearDetail`, + payload: {}, + }; +} + +export function CLIENT_SUBMIT(payload) { + return { + type: `${CLIENT_NAMESPACE}/submit`, + payload, + }; +} + +export function CLIENT_REMOVE(payload) { + return { + type: `${CLIENT_NAMESPACE}/remove`, + payload, + }; +} diff --git a/src/actions/code.js b/src/actions/code.js new file mode 100644 index 0000000..a4f5998 --- /dev/null +++ b/src/actions/code.js @@ -0,0 +1,43 @@ +export const CODE_NAMESPACE = 'code'; + +export function CODE_LIST(payload) { + return { + type: `${CODE_NAMESPACE}/fetchList`, + payload, + }; +} + +export function CODE_INIT() { + return { + type: `${CODE_NAMESPACE}/fetchInit`, + payload: { code: 'yes_no' }, + }; +} + +export function CODE_DETAIL(id) { + return { + type: `${CODE_NAMESPACE}/fetchDetail`, + payload: { id }, + }; +} + +export function CODE_CLEAR_DETAIL() { + return { + type: `${CODE_NAMESPACE}/clearDetail`, + payload: {}, + }; +} + +export function CODE_SUBMIT(payload) { + return { + type: `${CODE_NAMESPACE}/submit`, + payload, + }; +} + +export function CODE_REMOVE(payload) { + return { + type: `${CODE_NAMESPACE}/remove`, + payload, + }; +} diff --git a/src/actions/datasource.js b/src/actions/datasource.js new file mode 100644 index 0000000..45ba363 --- /dev/null +++ b/src/actions/datasource.js @@ -0,0 +1,36 @@ +export const DATASOURCE_NAMESPACE = 'datasource'; + +export function DATASOURCE_LIST(payload) { + return { + type: `${DATASOURCE_NAMESPACE}/fetchList`, + payload, + }; +} + +export function DATASOURCE_DETAIL(id) { + return { + type: `${DATASOURCE_NAMESPACE}/fetchDetail`, + payload: { id }, + }; +} + +export function DATASOURCE_CLEAR_DETAIL() { + return { + type: `${DATASOURCE_NAMESPACE}/clearDetail`, + payload: {}, + }; +} + +export function DATASOURCE_SUBMIT(payload) { + return { + type: `${DATASOURCE_NAMESPACE}/submit`, + payload, + }; +} + +export function DATASOURCE_REMOVE(payload) { + return { + type: `${DATASOURCE_NAMESPACE}/remove`, + payload, + }; +} diff --git a/src/actions/dept.js b/src/actions/dept.js new file mode 100644 index 0000000..d8cbf3d --- /dev/null +++ b/src/actions/dept.js @@ -0,0 +1,43 @@ +export const DEPT_NAMESPACE = 'dept'; + +export function DEPT_LIST(payload) { + return { + type: `${DEPT_NAMESPACE}/fetchList`, + payload, + }; +} + +export function DEPT_INIT() { + return { + type: `${DEPT_NAMESPACE}/fetchInit`, + payload: {}, + }; +} + +export function DEPT_DETAIL(id) { + return { + type: `${DEPT_NAMESPACE}/fetchDetail`, + payload: { id }, + }; +} + +export function DEPT_CLEAR_DETAIL() { + return { + type: `${DEPT_NAMESPACE}/clearDetail`, + payload: {}, + }; +} + +export function DEPT_SUBMIT(payload) { + return { + type: `${DEPT_NAMESPACE}/submit`, + payload, + }; +} + +export function DEPT_REMOVE(payload) { + return { + type: `${DEPT_NAMESPACE}/remove`, + payload, + }; +} diff --git a/src/actions/dict.js b/src/actions/dict.js new file mode 100644 index 0000000..5c127bd --- /dev/null +++ b/src/actions/dict.js @@ -0,0 +1,43 @@ +export const DICT_NAMESPACE = 'dict'; + +export function DICT_LIST(payload) { + return { + type: `${DICT_NAMESPACE}/fetchList`, + payload, + }; +} + +export function DICT_INIT() { + return { + type: `${DICT_NAMESPACE}/fetchInit`, + payload: { code: 'DICT' }, + }; +} + +export function DICT_DETAIL(id) { + return { + type: `${DICT_NAMESPACE}/fetchDetail`, + payload: { id }, + }; +} + +export function DICT_CLEAR_DETAIL() { + return { + type: `${DICT_NAMESPACE}/clearDetail`, + payload: {}, + }; +} + +export function DICT_SUBMIT(payload) { + return { + type: `${DICT_NAMESPACE}/submit`, + payload, + }; +} + +export function DICT_REMOVE(payload) { + return { + type: `${DICT_NAMESPACE}/remove`, + payload, + }; +} diff --git a/src/actions/log.js b/src/actions/log.js new file mode 100644 index 0000000..f56630a --- /dev/null +++ b/src/actions/log.js @@ -0,0 +1,43 @@ +export const LOG_NAMESPACE = 'log'; + +export function LOG_USUAL_LIST(payload) { + return { + type: `${LOG_NAMESPACE}/fetchUsualList`, + payload, + }; +} + +export function LOG_USUAL_DETAIL(id) { + return { + type: `${LOG_NAMESPACE}/fetchUsualDetail`, + payload: { id }, + }; +} + +export function LOG_API_LIST(payload) { + return { + type: `${LOG_NAMESPACE}/fetchApiList`, + payload, + }; +} + +export function LOG_API_DETAIL(id) { + return { + type: `${LOG_NAMESPACE}/fetchApiDetail`, + payload: { id }, + }; +} + +export function LOG_ERROR_LIST(payload) { + return { + type: `${LOG_NAMESPACE}/fetchErrorList`, + payload, + }; +} + +export function LOG_ERROR_DETAIL(id) { + return { + type: `${LOG_NAMESPACE}/fetchErrorDetail`, + payload: { id }, + }; +} diff --git a/src/actions/menu.js b/src/actions/menu.js new file mode 100644 index 0000000..4c0bdfb --- /dev/null +++ b/src/actions/menu.js @@ -0,0 +1,61 @@ +import { getAuthority } from '../utils/authority'; + +export const MENU_NAMESPACE = 'menu'; + +export function MENU_REFRESH_DATA() { + return { + type: `${MENU_NAMESPACE}/fetchMenuData`, + payload: { authority: getAuthority() }, + }; +} + +export function MENU_LIST(payload) { + return { + type: `${MENU_NAMESPACE}/fetchList`, + payload, + }; +} + +export function MENU_INIT() { + return { + type: `${MENU_NAMESPACE}/fetchInit`, + payload: {}, + }; +} + +export function MENU_DETAIL(id) { + return { + type: `${MENU_NAMESPACE}/fetchDetail`, + payload: { id }, + }; +} + +export function MENU_CLEAR_DETAIL() { + return { + type: `${MENU_NAMESPACE}/clearDetail`, + payload: {}, + }; +} + +export function MENU_SUBMIT(payload) { + return { + type: `${MENU_NAMESPACE}/submit`, + payload, + }; +} + +export function MENU_REMOVE(payload) { + return { + type: `${MENU_NAMESPACE}/remove`, + payload, + }; +} + +export function MENU_SELECT_ICON(icon) { + return { + type: `${MENU_NAMESPACE}/selectIcon`, + payload: { + source: icon, + }, + }; +} diff --git a/src/actions/notice.js b/src/actions/notice.js new file mode 100644 index 0000000..77f0f11 --- /dev/null +++ b/src/actions/notice.js @@ -0,0 +1,36 @@ +export const NOTICE_NAMESPACE = 'notice'; + +export function NOTICE_LIST(payload) { + return { + type: `${NOTICE_NAMESPACE}/fetchList`, + payload, + }; +} + +export function NOTICE_INIT() { + return { + type: `${NOTICE_NAMESPACE}/fetchInit`, + payload: { code: 'notice' }, + }; +} + +export function NOTICE_DETAIL(id) { + return { + type: `${NOTICE_NAMESPACE}/fetchDetail`, + payload: { id }, + }; +} + +export function NOTICE_SUBMIT(payload) { + return { + type: `${NOTICE_NAMESPACE}/submit`, + payload, + }; +} + +export function NOTICE_REMOVE(payload) { + return { + type: `${NOTICE_NAMESPACE}/remove`, + payload, + }; +} diff --git a/src/actions/param.js b/src/actions/param.js new file mode 100644 index 0000000..7191c2f --- /dev/null +++ b/src/actions/param.js @@ -0,0 +1,36 @@ +export const PARAM_NAMESPACE = 'param'; + +export function PARAM_LIST(payload) { + return { + type: `${PARAM_NAMESPACE}/fetchList`, + payload, + }; +} + +export function PARAM_DETAIL(id) { + return { + type: `${PARAM_NAMESPACE}/fetchDetail`, + payload: { id }, + }; +} + +export function PARAM_CLEAR_DETAIL() { + return { + type: `${PARAM_NAMESPACE}/clearDetail`, + payload: {}, + }; +} + +export function PARAM_SUBMIT(payload) { + return { + type: `${PARAM_NAMESPACE}/submit`, + payload, + }; +} + +export function PARAM_REMOVE(payload) { + return { + type: `${PARAM_NAMESPACE}/remove`, + payload, + }; +} diff --git a/src/actions/role.js b/src/actions/role.js new file mode 100644 index 0000000..4f46544 --- /dev/null +++ b/src/actions/role.js @@ -0,0 +1,72 @@ +export const ROLE_NAMESPACE = 'role'; + +export function ROLE_LIST(payload) { + return { + type: `${ROLE_NAMESPACE}/fetchList`, + payload, + }; +} + +export function ROLE_INIT() { + return { + type: `${ROLE_NAMESPACE}/fetchInit`, + payload: {}, + }; +} + +export function ROLE_DETAIL(id) { + return { + type: `${ROLE_NAMESPACE}/fetchDetail`, + payload: { id }, + }; +} + +export function ROLE_CLEAR_DETAIL() { + return { + type: `${ROLE_NAMESPACE}/clearDetail`, + payload: {}, + }; +} + +export function ROLE_GRANT_TREE(payload) { + return { + type: `${ROLE_NAMESPACE}/grantTree`, + payload, + }; +} + +export function ROLE_TREE_KEYS(payload) { + return { + type: `${ROLE_NAMESPACE}/roleTreeKeys`, + payload, + }; +} + +export function ROLE_SET_TREE_KEYS(payload) { + return { + type: `${ROLE_NAMESPACE}/setRoleTreeKeys`, + payload, + }; +} + +export function ROLE_GRANT(payload, callback) { + return { + type: `${ROLE_NAMESPACE}/grant`, + payload, + callback, + }; +} + +export function ROLE_SUBMIT(payload) { + return { + type: `${ROLE_NAMESPACE}/submit`, + payload, + }; +} + +export function ROLE_REMOVE(payload) { + return { + type: `${ROLE_NAMESPACE}/remove`, + payload, + }; +} diff --git a/src/actions/tenant.js b/src/actions/tenant.js new file mode 100644 index 0000000..dbf253c --- /dev/null +++ b/src/actions/tenant.js @@ -0,0 +1,36 @@ +export const TENANT_NAMESPACE = 'tenant'; + +export function TENANT_LIST(payload) { + return { + type: `${TENANT_NAMESPACE}/fetchList`, + payload, + }; +} + +export function TENANT_DETAIL(id) { + return { + type: `${TENANT_NAMESPACE}/fetchDetail`, + payload: { id }, + }; +} + +export function TENANT_CLEAR_DETAIL() { + return { + type: `${TENANT_NAMESPACE}/clearDetail`, + payload: {}, + }; +} + +export function TENANT_SUBMIT(payload) { + return { + type: `${TENANT_NAMESPACE}/submit`, + payload, + }; +} + +export function TENANT_REMOVE(payload) { + return { + type: `${TENANT_NAMESPACE}/remove`, + payload, + }; +} diff --git a/src/actions/user.js b/src/actions/user.js new file mode 100644 index 0000000..0a1fe88 --- /dev/null +++ b/src/actions/user.js @@ -0,0 +1,58 @@ +export const USER_NAMESPACE = 'user'; + +export function USER_LIST(payload) { + return { + type: `${USER_NAMESPACE}/fetchList`, + payload, + }; +} + +export function USER_INIT() { + return { + type: `${USER_NAMESPACE}/fetchInit`, + payload: {}, + }; +} + +export function USER_CHANGE_INIT(payload) { + return { + type: `${USER_NAMESPACE}/fetchChangeInit`, + payload, + }; +} + +export function USER_DETAIL(id) { + return { + type: `${USER_NAMESPACE}/fetchDetail`, + payload: { id }, + }; +} + +export function USER_ROLE_GRANT(payload, callback) { + return { + type: `${USER_NAMESPACE}/grant`, + payload, + callback, + }; +} + +export function USER_SUBMIT(payload) { + return { + type: `${USER_NAMESPACE}/submit`, + payload, + }; +} + +export function USER_UPDATE(payload) { + return { + type: `${USER_NAMESPACE}/update`, + payload, + }; +} + +export function USER_REMOVE(payload) { + return { + type: `${USER_NAMESPACE}/remove`, + payload, + }; +} diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..5507a4a --- /dev/null +++ b/src/app.js @@ -0,0 +1,43 @@ +import { routesAuthority } from './services/menu'; + +export const dva = { + config: { + onError(err) { + err.preventDefault(); + }, + }, +}; + +let authRoutes = { + '/form/advanced-form': { authority: ['admin', 'user'] }, +}; + +function ergodicRoutes(routes, authKey, authority) { + routes.forEach(element => { + if (element.path === authKey) { + if (!element.authority) element.authority = []; // eslint-disable-line + Object.assign(element.authority, authority || []); + } else if (element.routes) { + ergodicRoutes(element.routes, authKey, authority); + } + return element; + }); +} + +export function patchRoutes(routes) { + if (authRoutes !== null && authRoutes !== undefined) { + Object.keys(authRoutes).map(authKey => + ergodicRoutes(routes, authKey, authRoutes[authKey].authority) + ); + window.g_routes = routes; + } +} + +export function render(oldRender) { + routesAuthority().then(response => { + if (response && response.success) { + authRoutes = response.data; + } + oldRender(); + }); +} diff --git a/src/assets/logo.svg b/src/assets/logo.svg new file mode 100644 index 0000000..0a1c688 --- /dev/null +++ b/src/assets/logo.svg @@ -0,0 +1,160 @@ + + + diff --git a/src/components/ActiveChart/index.js b/src/components/ActiveChart/index.js new file mode 100644 index 0000000..3d9fbba --- /dev/null +++ b/src/components/ActiveChart/index.js @@ -0,0 +1,101 @@ +import React, { Component } from 'react'; +import { MiniArea } from '../Charts'; +import NumberInfo from '../NumberInfo'; + +import styles from './index.less'; + +function fixedZero(val) { + return val * 1 < 10 ? `0${val}` : val; +} + +function getActiveData() { + const activeData = []; + for (let i = 0; i < 24; i += 1) { + activeData.push({ + x: `${fixedZero(i)}:00`, + y: Math.floor(Math.random() * 200) + i * 50, + }); + } + return activeData; +} + +export default class ActiveChart extends Component { + state = { + activeData: getActiveData(), + }; + + componentDidMount() { + this.loopData(); + } + + componentWillUnmount() { + clearTimeout(this.timer); + cancelAnimationFrame(this.requestRef); + } + + loopData = () => { + this.requestRef = requestAnimationFrame(() => { + this.timer = setTimeout(() => { + this.setState( + { + activeData: getActiveData(), + }, + () => { + this.loopData(); + } + ); + }, 1000); + }); + }; + + render() { + const { activeData = [] } = this.state; + + return ( +
{[...activeData].sort()[activeData.length - 1].y + 200} 亿元
+{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元
+= + | React.StatelessComponent
+ | React.ComponentClass
+ | React.ClassicComponentClass
;
+
+type Secured = (
+ authority: authority,
+ error?: React.ReactNode
+) => ${title}
+ ${data[0].value * 10}%
+
+
+ {item.name}
+ There were injuries alleged in three cases in 2015, and a fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall. Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content Content 段落示意:蚂蚁金服务设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。 活跃用户 {activeUser} 新增用户 {newUser}
+
+ {currentUser.title}
+
+
+ {currentUser.group}
+
+
+ {currentUser.geographic.province.label}
+ {currentUser.geographic.city.label}
+ {childrenWithProps}
+ {title}
}
+ {subTitle}
}
+ {/* eslint-disable-next-line */}
+ {total && (
+
+ {legendData.map((item, i) => (
+
+ )}
+ {title}
}
+ {item.value}
+ {title}
}
+ {percent}%
+ Show Tooltip
+ {title || config[pageType].title}
+
+ {[...data, ...loadingList].map((item, i) => {
+ const itemCls = classNames(styles.item, {
+ [styles.read]: item.read,
+ });
+ // eslint-disable-next-line no-nested-ternary
+ const leftIcon = item.avatar ? (
+ typeof item.avatar === 'string' ? (
+
this.onClear(name)}
+ onClick={item => this.onItemClick(item, child.props)}
+ onLoadMore={event => this.onLoadMore(child.props, event)}
+ scrollToLoad={scrollToLoad}
+ showClear={showClear}
+ skeletonCount={skeletonCount}
+ skeletonProps={skeletonProps}
+ title={title}
+ visible={visible}
+ />
+
{title}
+ {action && {title}
+ {title}
+ {children}
+
+
+
Sword Admin
+
+
+
Sword Admin
+
+ Ant Design Pro
');
+ });
+});
diff --git a/src/e2e/topMenu.e2e.js b/src/e2e/topMenu.e2e.js
new file mode 100644
index 0000000..51ff9f3
--- /dev/null
+++ b/src/e2e/topMenu.e2e.js
@@ -0,0 +1,18 @@
+const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
+
+describe('Homepage', () => {
+ beforeAll(async () => {
+ jest.setTimeout(1000000);
+ });
+ it('topmenu should have footer', async () => {
+ const params = '/form/basic-form?navTheme=light&layout=topmenu';
+ await page.goto(`${BASE_URL}${params}`);
+ await page.waitForSelector('footer', {
+ timeout: 2000,
+ });
+ const haveFooter = await page.evaluate(
+ () => document.getElementsByTagName('footer').length > 0
+ );
+ expect(haveFooter).toBeTruthy();
+ });
+});
diff --git a/src/e2e/userLayout.e2e.js b/src/e2e/userLayout.e2e.js
new file mode 100644
index 0000000..a2edfc7
--- /dev/null
+++ b/src/e2e/userLayout.e2e.js
@@ -0,0 +1,32 @@
+import RouterConfig from '../../config/router.config';
+
+const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
+
+function formatter(data) {
+ return data
+ .reduce((pre, item) => {
+ pre.push(item.path);
+ return pre;
+ }, [])
+ .filter(item => item);
+}
+
+describe('Homepage', () => {
+ const testPage = path => async () => {
+ await page.goto(`${BASE_URL}${path}`);
+ await page.waitForSelector('footer', {
+ timeout: 2000,
+ });
+ const haveFooter = await page.evaluate(
+ () => document.getElementsByTagName('footer').length > 0
+ );
+ expect(haveFooter).toBeTruthy();
+ };
+
+ beforeAll(async () => {
+ jest.setTimeout(1000000);
+ });
+ formatter(RouterConfig[0].routes).forEach(route => {
+ it(`test pages ${route}`, testPage(route));
+ });
+});
diff --git a/src/global.js b/src/global.js
new file mode 100644
index 0000000..bf60b41
--- /dev/null
+++ b/src/global.js
@@ -0,0 +1,59 @@
+import React from 'react';
+import { notification, Button, message } from 'antd';
+import { formatMessage } from 'umi/locale';
+import defaultSettings from './defaultSettings';
+
+const { pwa } = defaultSettings;
+// if pwa is true
+if (pwa) {
+ // Notify user if offline now
+ window.addEventListener('sw.offline', () => {
+ message.warning(formatMessage({ id: 'app.pwa.offline' }));
+ });
+
+ // Pop up a prompt on the page asking the user if they want to use the latest version
+ window.addEventListener('sw.updated', e => {
+ const reloadSW = async () => {
+ // Check if there is sw whose state is waiting in ServiceWorkerRegistration
+ // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
+ const worker = e.detail && e.detail.waiting;
+ if (!worker) {
+ return Promise.resolve();
+ }
+ // Send skip-waiting event to waiting SW with MessageChannel
+ await new Promise((resolve, reject) => {
+ const channel = new MessageChannel();
+ channel.port1.onmessage = event => {
+ if (event.data.error) {
+ reject(event.data.error);
+ } else {
+ resolve(event.data);
+ }
+ };
+ worker.postMessage({ type: 'skip-waiting' }, [channel.port2]);
+ });
+ // Refresh current page to use the updated HTML and other assets after SW has skiped waiting
+ window.location.reload(true);
+ return true;
+ };
+ const key = `open${Date.now()}`;
+ const btn = (
+
+ );
+ notification.open({
+ message: formatMessage({ id: 'app.pwa.serviceworker.updated' }),
+ description: formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
+ btn,
+ key,
+ onClose: async () => {},
+ });
+ });
+}
diff --git a/src/global.less b/src/global.less
new file mode 100644
index 0000000..1450ba4
--- /dev/null
+++ b/src/global.less
@@ -0,0 +1,52 @@
+@import '~antd/lib/style/themes/default.less';
+
+html,
+body,
+#root {
+ height: 100%;
+}
+
+.colorWeak {
+ filter: invert(80%);
+}
+
+.ant-layout {
+ min-height: 100vh;
+}
+
+canvas {
+ display: block;
+}
+
+body {
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.globalSpin {
+ width: 100%;
+ margin: 40px 0 !important;
+}
+
+ul,
+ol {
+ list-style: none;
+}
+
+@media (max-width: @screen-xs) {
+ .ant-table {
+ width: 100%;
+ overflow-x: auto;
+ &-thead > tr,
+ &-tbody > tr {
+ > th,
+ > td {
+ white-space: pre;
+ > span {
+ display: block;
+ }
+ }
+ }
+ }
+}
diff --git a/src/layouts/BasicLayout.js b/src/layouts/BasicLayout.js
new file mode 100644
index 0000000..dc93402
--- /dev/null
+++ b/src/layouts/BasicLayout.js
@@ -0,0 +1,237 @@
+import React, { Suspense } from 'react';
+import { Layout } from 'antd';
+import 'moment/locale/zh-cn';
+import DocumentTitle from 'react-document-title';
+import isEqual from 'lodash/isEqual';
+import memoizeOne from 'memoize-one';
+import { connect } from 'dva';
+import { ContainerQuery } from 'react-container-query';
+import classNames from 'classnames';
+import pathToRegexp from 'path-to-regexp';
+import Media from 'react-media';
+import { formatMessage } from 'umi/locale';
+import Authorized from '@/utils/Authorized';
+import logo from '../assets/logo.svg';
+import Footer from './Footer';
+import Header from './Header';
+import Context from './MenuContext';
+import Exception403 from '../pages/Exception/403';
+import PageLoading from '@/components/PageLoading';
+import SiderMenu from '@/components/SiderMenu';
+
+import { menu, title } from '../defaultSettings';
+import styles from './BasicLayout.less';
+
+// lazy load SettingDrawer
+const SettingDrawer = React.lazy(() => import('@/components/SettingDrawer'));
+
+const { Content } = Layout;
+
+const query = {
+ 'screen-xs': {
+ maxWidth: 575,
+ },
+ 'screen-sm': {
+ minWidth: 576,
+ maxWidth: 767,
+ },
+ 'screen-md': {
+ minWidth: 768,
+ maxWidth: 991,
+ },
+ 'screen-lg': {
+ minWidth: 992,
+ maxWidth: 1199,
+ },
+ 'screen-xl': {
+ minWidth: 1200,
+ maxWidth: 1599,
+ },
+ 'screen-xxl': {
+ minWidth: 1600,
+ },
+};
+
+class BasicLayout extends React.Component {
+ constructor(props) {
+ super(props);
+ this.getPageTitle = memoizeOne(this.getPageTitle);
+ this.matchParamsPath = memoizeOne(this.matchParamsPath, isEqual);
+ }
+
+ componentDidMount() {
+ const {
+ dispatch,
+ route: { routes, authority },
+ } = this.props;
+ dispatch({
+ type: 'user/fetchCurrent',
+ });
+ dispatch({
+ type: 'setting/getSetting',
+ });
+ dispatch({
+ type: 'menu/fetchMenuData',
+ payload: { routes, authority },
+ });
+ }
+
+ getContext() {
+ const { location, breadcrumbNameMap } = this.props;
+ return {
+ location,
+ breadcrumbNameMap,
+ };
+ }
+
+ matchParamsPath = (pathname, breadcrumbNameMap) => {
+ const pathKey = Object.keys(breadcrumbNameMap).find(key => pathToRegexp(key).test(pathname));
+ return breadcrumbNameMap[pathKey];
+ };
+
+ getRouteAuthority = (pathname, routeData) => {
+ const routes = routeData.slice(); // clone
+ let authorities;
+
+ while (routes.length > 0) {
+ const route = routes.shift();
+ // check partial route
+ if (pathToRegexp(`${route.path}(.*)`).test(pathname)) {
+ if (route.authority) {
+ authorities = route.authority;
+ }
+ // is exact route?
+ if (pathToRegexp(route.path).test(pathname)) {
+ break;
+ }
+
+ if (route.routes) {
+ route.routes.forEach(r => routes.push(r));
+ }
+ }
+ }
+ return authorities;
+ };
+
+ getPageTitle = (pathname, breadcrumbNameMap) => {
+ const currRouterData = this.matchParamsPath(pathname, breadcrumbNameMap);
+
+ if (!currRouterData) {
+ return title;
+ }
+
+ const pageName = menu.disableLocal
+ ? currRouterData.name
+ : formatMessage({
+ id: currRouterData.locale || currRouterData.name,
+ defaultMessage: currRouterData.name,
+ });
+
+ return `${pageName} - ${title}`;
+ };
+
+ getLayoutStyle = () => {
+ const { fixSiderbar, isMobile, collapsed, layout } = this.props;
+ if (fixSiderbar && layout !== 'topmenu' && !isMobile) {
+ return {
+ paddingLeft: collapsed ? '80px' : '256px',
+ };
+ }
+ return null;
+ };
+
+ handleMenuCollapse = collapsed => {
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'global/changeLayoutCollapsed',
+ payload: collapsed,
+ });
+ };
+
+ renderSettingDrawer = () => {
+ // Do not render SettingDrawer in production
+ // unless it is deployed in preview.pro.ant.design as demo
+ if (process.env.NODE_ENV === 'production' && process.env.APP_TYPE !== 'site') {
+ return null;
+ }
+ return (
+
(
+
(
+
(
+
(
+
(
+
+
+
+
+
+ {rankingListData.map((item, i) => (
+
+
+
+
+ {rankingListData.map((item, i) => (
+
+ record.index}
+ size="small"
+ columns={columns}
+ dataSource={searchData}
+ pagination={{
+ style: { marginBottom: 0 },
+ pageSize: 5,
+ }}
+ />
+
+));
+
+export default TopSearch;
diff --git a/src/pages/Dashboard/Workplace.js b/src/pages/Dashboard/Workplace.js
new file mode 100644
index 0000000..3b87443
--- /dev/null
+++ b/src/pages/Dashboard/Workplace.js
@@ -0,0 +1,364 @@
+import React, { PureComponent } from 'react';
+import { Card, Col, Collapse, Row, Divider, Tag } from 'antd';
+import styles from '../../layouts/Sword.less';
+
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
+
+const { Panel } = Collapse;
+
+class Workplace extends PureComponent {
+ render() {
+ return (
+
+
+
+