From 41719aa59fad6582248e133df3a450cbdf3848bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B8=85?= Date: Wed, 29 May 2019 10:55:27 +0800 Subject: [PATCH] V4 fetch block (#4238) * first step * fix local error * commit * test serve * fix test * sort package.json * fix typo * fix pageHeader error * new style * change script name * fix copy block url style * new copy code style * add loginout * auto insert pro code * use new layout * add locale to copy button --- .stylelintrc.json | 7 +- config/plugin.config.ts | 21 +- package.json | 25 +- scripts/fetch-blocks.js | 129 ++++++++++ scripts/insertCode.js | 161 ++++++++++++ scripts/repalceRouter.js | 77 ++++++ scripts/router.config.js | 236 ++++++++++++++++++ src/components/CopyBlock/index.less | 29 +++ src/components/CopyBlock/index.tsx | 47 ++++ .../GlobalHeader/AvatarDropdown.tsx | 72 ++++++ .../GlobalHeader/NoticeIconView.tsx | 145 +++++++++++ src/components/GlobalHeader/RightContent.tsx | 205 +++------------ src/e2e/baseLayout.e2e.js | 2 +- src/layouts/BasicLayout.tsx | 93 +++---- src/locales/en-US.ts | 2 +- src/locales/en-US/menu.ts | 43 +++- src/locales/pt-BR.ts | 2 +- src/locales/pt-BR/menu.ts | 42 ++++ src/locales/zh-CN.ts | 2 +- src/locales/zh-CN/menu.ts | 42 ++++ src/locales/zh-TW.ts | 5 +- src/locales/zh-TW/menu.ts | 42 ++++ src/models/login.ts | 64 +++++ src/utils/utils.ts | 12 + 24 files changed, 1253 insertions(+), 252 deletions(-) create mode 100644 scripts/fetch-blocks.js create mode 100644 scripts/insertCode.js create mode 100644 scripts/repalceRouter.js create mode 100644 scripts/router.config.js create mode 100644 src/components/CopyBlock/index.less create mode 100644 src/components/CopyBlock/index.tsx create mode 100644 src/components/GlobalHeader/AvatarDropdown.tsx create mode 100644 src/components/GlobalHeader/NoticeIconView.tsx create mode 100644 src/models/login.ts diff --git a/.stylelintrc.json b/.stylelintrc.json index 215bf081..d366a141 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -5,9 +5,12 @@ "stylelint-config-rational-order", "stylelint-config-prettier" ], - "plugins": ["stylelint-order", "stylelint-declaration-block-no-ignored-properties"], + "plugins": [ + "stylelint-order", + "stylelint-declaration-block-no-ignored-properties" + ], "rules": { "no-descending-specificity": null, "plugin/declaration-block-no-ignored-properties": true } -} +} \ No newline at end of file diff --git a/config/plugin.config.ts b/config/plugin.config.ts index 59ce550f..d9d150bc 100644 --- a/config/plugin.config.ts +++ b/config/plugin.config.ts @@ -4,7 +4,7 @@ import MergeLessPlugin from 'antd-pro-merge-less'; import AntDesignThemePlugin from 'antd-theme-webpack-plugin'; import path from 'path'; -function getModulePackageName(module) { +function getModulePackageName(module: { context: string }) { if (!module.context) return null; const nodeModulesPath = path.join(__dirname, '../node_modules/'); @@ -14,16 +14,16 @@ function getModulePackageName(module) { const moduleRelativePath = module.context.substring(nodeModulesPath.length); const [moduleDirName] = moduleRelativePath.split(path.sep); - let packageName = moduleDirName; + let packageName: string | null = moduleDirName; // handle tree shaking - if (packageName.match('^_')) { + if (packageName && packageName.match('^_')) { // eslint-disable-next-line prefer-destructuring - packageName = packageName.match(/^_(@?[^@]+)/)[1]; + packageName = packageName.match(/^_(@?[^@]+)/)![1]; } return packageName; } -export default config => { +export default (config: any) => { // preview.pro.ant.design only do not use in your production ; preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。 if ( process.env.ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' || @@ -62,18 +62,19 @@ export default config => { minSize: 0, cacheGroups: { vendors: { - test: module => { + test: (module: { context: string }) => { const packageName = getModulePackageName(module); if (packageName) { return ['bizcharts', '@antv_data-set'].indexOf(packageName) >= 0; } return false; }, - name(module) { + name(module: { context: string }) { const packageName = getModulePackageName(module); - - if (['bizcharts', '@antv_data-set'].indexOf(packageName) >= 0) { - return 'viz'; // visualization package + if (packageName) { + if (['bizcharts', '@antv_data-set'].indexOf(packageName) >= 0) { + return 'viz'; // visualization package + } } return 'misc'; }, diff --git a/package.json b/package.json index adcc39b1..c8fedcb1 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up", "docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro", "docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro", + "fetch:blocks": "node ./scripts/fetch-blocks.js", "functions:build": "npm run generateMock && netlify-lambda build ./lambda", "functions:run": "npm run generateMock && cross-env NODE_ENV=dev netlify-lambda serve ./lambda", "generateMock": "node ./scripts/generateMock", @@ -26,7 +27,7 @@ "lint:style": "stylelint --fix 'src/**/*.less' --syntax less", "lint:ts": "tslint -p . -c tslint.yml", "prettier": " check-prettier write", - "site": "umi build && npm run functions:build", + "site": "npm run fetch:blocks && umi build && npm run functions:build", "start": "umi dev", "start:no-mock": "cross-env MOCK=none umi dev", "test": "umi test", @@ -49,18 +50,17 @@ "dependencies": { "@ant-design/pro-layout": "^4.2.0", "@antv/data-set": "^0.10.1", + "@types/qs": "^6.5.3", "antd": "^3.16.1", - "bizcharts": "^3.5.3-beta.0", - "bizcharts-plugin-slider": "^2.1.1-beta.1", "classnames": "^2.2.6", "dva": "^2.4.0", "lodash": "^4.17.10", "lodash-decorators": "^6.0.0", "memoize-one": "^5.0.0", "moment": "^2.22.2", - "numeral": "^2.0.6", "omit.js": "^1.0.0", "path-to-regexp": "^2.4.0", + "prop-types": "^15.7.2", "qs": "^6.7.0", "rc-animate": "^2.4.4", "react": "^16.8.5", @@ -68,9 +68,14 @@ "react-copy-to-clipboard": "^5.0.1", "react-document-title": "^2.0.3", "react-dom": "^16.7.0", - "react-fittext": "^1.0.0", "react-media": "^1.9.2", - "react-media-hook2": "^1.0.2" + "react-media-hook2": "^1.0.2", + "umi": "^2.7.0-beta.2", + "umi-plugin-ga": "^1.1.3", + "umi-plugin-locale": "^2.8.0-beta.1", + "umi-plugin-pro-block": "^1.3.0", + "umi-plugin-react": "^1.8.0-beta.1", + "umi-request": "^1.0.7" }, "devDependencies": { "@types/classnames": "^2.2.7", @@ -106,6 +111,7 @@ "merge-umi-mock-data": "^1.0.4", "mockjs": "^1.0.1-beta3", "netlify-lambda": "^1.4.3", + "node-fetch": "^2.6.0", "prettier": "^1.17.0", "serverless-http": "^2.0.1", "slash2": "^2.0.0", @@ -119,12 +125,7 @@ "tslint": "^5.12.1", "tslint-config-prettier": "^1.17.0", "tslint-eslint-rules": "^5.4.0", - "tslint-react": "^3.6.0", - "umi": "^2.7.0-beta.2", - "umi-plugin-ga": "^1.1.3", - "umi-plugin-pro-block": "^1.3.0", - "umi-plugin-react": "^1.8.0-beta.1", - "umi-request": "^1.0.0" + "tslint-react": "^3.6.0" }, "optionalDependencies": { "puppeteer": "^1.12.1" diff --git a/scripts/fetch-blocks.js b/scripts/fetch-blocks.js new file mode 100644 index 00000000..8fcf44c3 --- /dev/null +++ b/scripts/fetch-blocks.js @@ -0,0 +1,129 @@ +const path = require('path'); +const fs = require('fs'); +const fetch = require('node-fetch'); +const exec = require('child_process').exec; +const getNewRouteCode = require('./repalceRouter'); +const router = require('./router.config'); +const chalk = require('chalk'); +const insertCode = require('./insertCode'); + +const fetchGithubFiles = async () => { + const ignoreFile = ['_scripts']; + const data = await fetch(`https://api.github.com/repos/ant-design/pro-blocks/git/trees/master`); + if (data.status !== 200) { + return; + } + const { tree } = await data.json(); + const files = tree.filter(file => file.type === 'tree' && !ignoreFile.includes(file.path)); + return Promise.resolve(files); +}; + +const relativePath = path.join(__dirname, '../config/config.ts'); + +const findAllInstallRouter = router => { + let routers = []; + router.forEach(item => { + if (item.component && item.path) { + if (item.path !== '/user' || item.path !== '/') { + routers.push({ + ...item, + routes: !!item.routes, + }); + } + } + if (item.routes) { + routers = routers.concat(findAllInstallRouter(item.routes)); + } + }); + return routers; +}; + +const filterParentRouter = (router, layout) => { + return [...router] + .map(item => { + if (item.routes && (!router.component || layout)) { + return { ...item, routes: filterParentRouter(item.routes, false) }; + } + if (item.redirect) { + return item; + } + return null; + }) + .filter(item => item); +}; +const firstUpperCase = pathString => { + return pathString + .replace('.', '') + .split(/\/|\-/) + .map(s => s.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase())) + .filter(s => s) + .join(''); +}; + +const execCmd = shell => { + return new Promise((resolve, reject) => { + exec(shell, { encoding: 'utf8' }, (error, statusbar) => { + if (error) { + console.log(error); + return reject(error); + } + console.log(statusbar); + resolve(); + }); + }); +}; + +// replace router config +const parentRouter = filterParentRouter(router, true); +const { routesPath, code } = getNewRouteCode(relativePath, parentRouter); +// write ParentRouter +fs.writeFileSync(routesPath, code); + +const installBlock = async () => { + let gitFiles = await fetchGithubFiles(); + const installRouters = findAllInstallRouter(router); + const installBlockIteration = async i => { + const item = installRouters[i]; + + if (!item || !item.path) { + return Promise.resolve(); + } + const gitPath = firstUpperCase(item.path); + // 如果这个区块在 git 上存在 + if (gitFiles.find(file => file.path === gitPath)) { + console.log('install ' + chalk.green(item.name) + ' to: ' + chalk.yellow(item.path)); + gitFiles = gitFiles.filter(file => file.path !== gitPath); + const skipModifyRouter = item.routes ? '--skip-modify-routes' : ''; + const cmd = `umi block add https://github.com/ant-design/pro-blocks/tree/master/${gitPath} --npm-client=cnpm --path=${ + item.path + } ${skipModifyRouter}`; + try { + await execCmd(cmd); + console.log(`install ${chalk.hex('#1890ff')(item.name)} success`); + } catch (error) { + console.error(error); + } + } + return installBlockIteration(i + 1); + }; + // 安装路由中设置的区块 + await installBlockIteration(0); + + const installGitFile = async i => { + const item = gitFiles[i]; + if (!item || !item.path) { + return Promise.resolve(); + } + console.log('install ' + chalk.green(item.path)); + const cmd = `umi block add https://github.com/ant-design/pro-blocks/tree/master/${item.path}`; + await execCmd(cmd); + return installBlockIteration(1); + }; + + // 安装 router 中没有的剩余区块. + installGitFile(0); +}; +installBlock(); + +// 插入 pro 需要的演示代码 +insertCode(); diff --git a/scripts/insertCode.js b/scripts/insertCode.js new file mode 100644 index 00000000..1dc3d9ad --- /dev/null +++ b/scripts/insertCode.js @@ -0,0 +1,161 @@ +const parser = require('@babel/parser'); +const traverse = require('@babel/traverse'); +const generate = require('@babel/generator'); +const t = require('@babel/types'); +const fs = require('fs'); +const path = require('path'); +const prettier = require('prettier'); +const chalk = require('chalk'); + +const parseCode = code => { + return parser.parse(code, { + sourceType: 'module', + plugins: ['typescript', 'jsx'], + }).program.body[0]; +}; + +/** + * 生成代码 + * @param {*} ast + */ +function generateCode(ast) { + const newCode = generate.default(ast, {}).code; + return prettier.format(newCode, { + // format same as ant-design-pro + singleQuote: true, + trailingComma: 'es5', + printWidth: 100, + parser: 'typescript', + }); +} + +const SettingCodeString = ` + + dispatch!({ + type: 'settings/changeSetting', + payload: config, + }) + } + /> +`; + +const mapAst = (configPath, callBack) => { + const ast = parser.parse(fs.readFileSync(configPath, 'utf-8'), { + sourceType: 'module', + plugins: ['typescript', 'jsx'], + }); + // 查询当前配置文件是否导出 routes 属性 + traverse.default(ast, { + Program({ node }) { + const { body } = node; + callBack(body); + }, + }); + return generateCode(ast); +}; + +const insertBasicLayout = configPath => { + return mapAst(configPath, body => { + const index = body.findIndex(item => { + return item.type !== 'ImportDeclaration'; + }); + + body.forEach(item => { + // 从包中导出 SettingDrawer + if (item.type === 'ImportDeclaration') { + if (item.source.value === '@ant-design/pro-layout') { + item.specifiers.push(parseCode(`SettingDrawer`).expression); + } + } + if (item.type === 'VariableDeclaration') { + const { + id, + init: { body }, + } = item.declarations[0]; + // 给 BasicLayout 中插入 button 和 设置抽屉 + if (id.name === `BasicLayout`) { + body.body.forEach(node => { + if (node.type === 'ReturnStatement') { + const JSXFragment = parseCode(`<>`).expression; + JSXFragment.children.push({ ...node.argument }); + JSXFragment.children.push(parseCode(SettingCodeString).expression); + node.argument = JSXFragment; + } + }); + } + } + }); + }); +}; + +const insertBlankLayout = configPath => { + return mapAst(configPath, body => { + const index = body.findIndex(item => { + return item.type !== 'ImportDeclaration'; + }); + // 从组件中导入 CopyBlock + body.splice( + index, + 0, + parseCode(`import CopyBlock from '@/components/CopyBlock'; + `), + ); + body.forEach(item => { + if (item.type === 'VariableDeclaration') { + const { id, init } = item.declarations[0]; + // 给 BasicLayout 中插入 button 和 设置抽屉 + if (id.name === `Layout`) { + const JSXFragment = parseCode(`<>`).expression; + JSXFragment.children.push({ ...init.body }); + JSXFragment.children.push(parseCode(` `).expression); + init.body = JSXFragment; + } + } + }); + }); +}; + +const insertRightContent = configPath => { + return mapAst(configPath, body => { + const index = body.findIndex(item => { + return item.type !== 'ImportDeclaration'; + }); + // 从组件中导入 CopyBlock + body.splice(index, 0, parseCode(`import NoticeIconView from './NoticeIconView';`)); + + body.forEach(item => { + if (item.type === 'ClassDeclaration') { + const classBody = item.body.body[0].body; + classBody.body.forEach(node => { + if (node.type === 'ReturnStatement') { + const index = node.argument.children.findIndex(item => { + if (item.type === 'JSXElement') { + if (item.openingElement.name.name === 'Avatar') { + return true; + } + } + }); + node.argument.children.splice(index, 1, parseCode(``).expression); + node.argument.children.splice(index, 0, parseCode(``).expression); + } + }); + } + }); + }); +}; + +module.exports = () => { + const basicLayoutPath = path.join(__dirname, '../src/layouts/BasicLayout.tsx'); + fs.writeFileSync(basicLayoutPath, insertBasicLayout(basicLayoutPath)); + console.log(`insert ${chalk.hex('#1890ff')('BasicLayout')} success`); + + const rightContentPath = path.join(__dirname, '../src/components/GlobalHeader/RightContent.tsx'); + fs.writeFileSync(rightContentPath, insertRightContent(rightContentPath)); + console.log(`insert ${chalk.hex('#1890ff')('RightContent')} success`); + + const blankLayoutPath = path.join(__dirname, '../src/layouts/BlankLayout.tsx'); + fs.writeFileSync(blankLayoutPath, insertBlankLayout(blankLayoutPath)); + console.log(`insert ${chalk.hex('#1890ff')('blankLayoutPath')} success`); +}; diff --git a/scripts/repalceRouter.js b/scripts/repalceRouter.js new file mode 100644 index 00000000..4bb3c4f4 --- /dev/null +++ b/scripts/repalceRouter.js @@ -0,0 +1,77 @@ +const parser = require('@babel/parser'); +const traverse = require('@babel/traverse'); +const generate = require('@babel/generator'); +const t = require('@babel/types'); +const fs = require('fs'); +const prettier = require('prettier'); + +const getNewRouteCode = (configPath, newRoute) => { + const ast = parser.parse(fs.readFileSync(configPath, 'utf-8'), { + sourceType: 'module', + plugins: ['typescript'], + }); + let routesNode = null; + const importModules = []; + // 查询当前配置文件是否导出 routes 属性 + traverse.default(ast, { + Program({ node }) { + // find import + const { body } = node; + body.forEach(item => { + if (t.isImportDeclaration(item)) { + const { specifiers } = item; + const defaultEpecifier = specifiers.find(s => { + return t.isImportDefaultSpecifier(s) && t.isIdentifier(s.local); + }); + if (defaultEpecifier && t.isStringLiteral(item.source)) { + importModules.push({ + identifierName: defaultEpecifier.local.name, + modulePath: item.source.value, + }); + } + } + }); + }, + ObjectExpression({ node, parent }) { + // find routes on object, like { routes: [] } + if (t.isArrayExpression(parent)) { + // children routes + return; + } + const { properties } = node; + properties.forEach(p => { + const { key, value } = p; + if (t.isObjectProperty(p) && t.isIdentifier(key) && key.name === 'routes') { + if (value) { + // find json file program expression + (p.value = parser.parse(JSON.stringify(newRoute)).program.body[0].expression), + (routesNode = value); + } + } + }); + }, + }); + if (routesNode) { + const code = generateCode(ast); + return { code, routesPath: configPath }; + } else { + throw new Error('route array config not found.'); + } +}; + +/** + * 生成代码 + * @param {*} ast + */ +function generateCode(ast) { + const newCode = generate.default(ast, {}).code; + return prettier.format(newCode, { + // format same as ant-design-pro + singleQuote: true, + trailingComma: 'es5', + printWidth: 100, + parser: 'typescript', + }); +} + +module.exports = getNewRouteCode; diff --git a/scripts/router.config.js b/scripts/router.config.js new file mode 100644 index 00000000..38a90a1b --- /dev/null +++ b/scripts/router.config.js @@ -0,0 +1,236 @@ +module.exports = [ + { + path: '/', + component: '../layouts/BlankLayout', + routes: [ + // user + { + path: '/user', + component: '../layouts/UserLayout', + routes: [ + { path: '/user/login', name: 'login', component: './User/Login' }, + { path: '/user/register', name: 'register', component: './User/Register' }, + { + path: '/user/register-result', + name: 'register.result', + component: './User/RegisterResult', + }, + { path: '/user', redirect: '/user/login' }, + { + component: '404', + }, + ], + }, + // app + { + path: '/', + component: '../layouts/BasicLayout', + Routes: ['src/pages/Authorized'], + authority: ['admin', 'user'], + routes: [ + // dashboard + { + path: '/dashboard', + name: 'dashboard', + icon: 'dashboard', + routes: [ + { + path: '/dashboard/analysis', + name: 'analysis', + component: './Dashboard/Analysis', + }, + { + path: '/dashboard/monitor', + name: 'monitor', + component: './Dashboard/Monitor', + }, + { + path: '/dashboard/workplace', + name: 'workplace', + component: './Dashboard/Workplace', + }, + ], + }, + // forms + { + path: '/form', + icon: 'form', + name: 'form', + routes: [ + { + path: '/form/basic-form', + name: 'basicform', + component: './Form/BasicForm', + }, + { + path: '/form/step-form', + name: 'stepform', + component: './Form/StepForm', + }, + { + path: '/form/advanced-form', + name: 'advancedform', + authority: ['admin'], + component: './Form/AdvancedForm', + }, + ], + }, + // list + { + path: '/list', + icon: 'table', + name: 'list', + routes: [ + { + path: '/list/table-list', + name: 'searchtable', + component: './list/Tablelist', + }, + { + path: '/list/basic-list', + name: 'basiclist', + component: './list/Basiclist', + }, + { + path: '/list/card-list', + name: 'cardlist', + component: './list/Cardlist', + }, + { + path: '/list/search', + name: 'search-list', + component: './list/search', + routes: [ + { + path: '/list/search/articles', + name: 'articles', + component: './list/Articles', + }, + { + path: '/list/search/projects', + name: 'projects', + component: './list/Projects', + }, + { + path: '/list/search/applications', + name: 'applications', + component: './list/Applications', + }, + { + path: '/list/search', + redirect: '/list/search/articles', + }, + ], + }, + ], + }, + { + path: '/profile', + name: 'profile', + icon: 'profile', + routes: [ + // profile + { + path: '/profile/basic', + name: 'basic', + component: './Profile/BasicProfile', + }, + { + path: '/profile/basic/:id', + hideInMenu: true, + component: './Profile/BasicProfile', + }, + { + path: '/profile/advanced', + name: 'advanced', + authority: ['admin'], + component: './Profile/AdvancedProfile', + }, + ], + }, + { + name: 'result', + icon: 'check-circle-o', + path: '/result', + routes: [ + // result + { + path: '/result/success', + name: 'success', + component: './Result/Success', + }, + { path: '/result/fail', name: 'fail', component: './Result/Error' }, + ], + }, + { + name: 'exception', + icon: 'warning', + path: '/exception', + routes: [ + // exception + { + path: '/exception/403', + name: 'not-permission', + component: './Exception/403', + }, + { + path: '/exception/404', + name: 'not-find', + component: './Exception/404', + }, + { + path: '/exception/500', + name: 'server-error', + component: './Exception/500', + }, + ], + }, + { + name: 'account', + icon: 'user', + path: '/account', + routes: [ + { + path: '/account/center', + name: 'center', + component: './Account/Center/Center', + }, + { + path: '/account/settings', + name: 'settings', + component: './Account/Settings/Info', + }, + ], + }, + // editor + { + name: 'editor', + icon: 'highlight', + path: '/editor', + routes: [ + { + path: '/editor/flow', + name: 'flow', + component: './Editor/GGEditor/Flow', + }, + { + path: '/editor/mind', + name: 'mind', + component: './Editor/GGEditor/Mind', + }, + { + path: '/editor/koni', + name: 'koni', + component: './Editor/GGEditor/Koni', + }, + ], + }, + { path: '/', redirect: '/dashboard/analysis', authority: ['admin', 'user'] }, + { + component: '404', + }, + ], + }, + ], + }, +]; diff --git a/src/components/CopyBlock/index.less b/src/components/CopyBlock/index.less new file mode 100644 index 00000000..83d899a1 --- /dev/null +++ b/src/components/CopyBlock/index.less @@ -0,0 +1,29 @@ +.copy-block { + position: fixed; + right: 80px; + bottom: 40px; + z-index: 99; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + font-size: 20px; + background: #fff; + border-radius: 40px; + box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), + 0 1px 10px 0 rgba(0, 0, 0, 0.12); + cursor: pointer; +} + +.copy-block-view { + position: relative; + .copy-block-code { + display: inline-block; + margin: 0 0.2em; + padding: 0.2em 0.4em 0.1em; + font-size: 85%; + border-radius: 3px; + } +} diff --git a/src/components/CopyBlock/index.tsx b/src/components/CopyBlock/index.tsx new file mode 100644 index 00000000..682e77cf --- /dev/null +++ b/src/components/CopyBlock/index.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Icon, Typography, Popover } from 'antd'; +import styles from './index.less'; +import { connect } from 'dva'; +import * as H from 'history'; +import { FormattedMessage } from 'umi-plugin-react/locale'; + +const firstUpperCase = (pathString: string) => { + return pathString + .replace('.', '') + .split(/\/|\-/) + .map(s => s.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase())) + .filter(s => s) + .join(''); +}; +const BlockCodeView: React.SFC<{ + url: string; +}> = ({ url }) => { + const blockUrl = `npx umi block add ant-design-pro/${firstUpperCase(url)} --path=${url}`; + return ( +
+ + {blockUrl} + +
+ ); +}; + +type RoutingType = { location: H.Location }; + +export default connect(({ routing }: { routing: RoutingType }) => ({ + location: routing.location, +}))(({ location }: RoutingType) => { + const url = location.pathname; + return ( + } + placement="topLeft" + content={} + trigger="click" + > +
+ +
+
+ ); +}); diff --git a/src/components/GlobalHeader/AvatarDropdown.tsx b/src/components/GlobalHeader/AvatarDropdown.tsx new file mode 100644 index 00000000..4454ccac --- /dev/null +++ b/src/components/GlobalHeader/AvatarDropdown.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { Avatar, Menu, Spin, Icon } from 'antd'; +import { FormattedMessage } from 'umi-plugin-react/locale'; +import { ClickParam } from 'antd/lib/menu'; +import { ConnectProps, ConnectState } from '@/models/connect'; +import { CurrentUser } from '@/models/user'; +import { connect } from 'dva'; +import router from 'umi/router'; +import HeaderDropdown from '../HeaderDropdown'; +import styles from './index.less'; + +export interface GlobalHeaderRightProps extends ConnectProps { + currentUser?: CurrentUser; + menu?: boolean; +} + +class AvatarDropdown extends React.Component { + onMenuClick = (event: ClickParam) => { + const { key } = event; + + if (key === 'logout') { + const { dispatch } = this.props; + dispatch!({ + type: 'login/logout', + }); + return; + } + router.push(`/account/${key}`); + }; + render() { + const { currentUser = {}, menu } = this.props; + if (!menu) { + return ( + + + {currentUser.name} + + ); + } + const menuHeaderDropdown = ( + + + + + + + + + + + + + + + + ); + + return currentUser && currentUser.name ? ( + + + + {currentUser.name} + + + ) : ( + + ); + } +} +export default connect(({ user }: ConnectState) => ({ + currentUser: user.currentUser, +}))(AvatarDropdown); diff --git a/src/components/GlobalHeader/NoticeIconView.tsx b/src/components/GlobalHeader/NoticeIconView.tsx new file mode 100644 index 00000000..f79429aa --- /dev/null +++ b/src/components/GlobalHeader/NoticeIconView.tsx @@ -0,0 +1,145 @@ +import { ConnectProps, ConnectState } from '@/models/connect'; +import { NoticeItem } from '@/models/global'; +import { CurrentUser } from '@/models/user'; +import React, { Component } from 'react'; +import { Tag, message } from 'antd'; +import { formatMessage } from 'umi-plugin-react/locale'; +import moment from 'moment'; +import groupBy from 'lodash/groupBy'; +import NoticeIcon from '../NoticeIcon'; +import styles from './index.less'; +import { connect } from 'dva'; + +export interface GlobalHeaderRightProps extends ConnectProps { + notices?: NoticeItem[]; + currentUser?: CurrentUser; + fetchingNotices?: boolean; + onNoticeVisibleChange?: (visible: boolean) => void; + onNoticeClear?: (tabName?: string) => void; +} + +class GlobalHeaderRight extends Component { + getNoticeData = (): { [key: string]: NoticeItem[] } => { + const { notices = [] } = this.props; + if (notices.length === 0) { + return {}; + } + const newNotices = notices.map(notice => { + const newNotice = { ...notice }; + if (newNotice.datetime) { + newNotice.datetime = moment(notice.datetime as string).fromNow(); + } + if (newNotice.id) { + newNotice.key = newNotice.id; + } + if (newNotice.extra && newNotice.status) { + const color = { + todo: '', + processing: 'blue', + urgent: 'red', + doing: 'gold', + }[newNotice.status]; + newNotice.extra = ( + + {newNotice.extra} + + ); + } + return newNotice; + }); + return groupBy(newNotices, 'type'); + }; + + getUnreadData = (noticeData: { [key: string]: NoticeItem[] }) => { + const unreadMsg: { [key: string]: number } = {}; + Object.entries(noticeData).forEach(([key, value]) => { + if (!unreadMsg[key]) { + unreadMsg[key] = 0; + } + if (Array.isArray(value)) { + unreadMsg[key] = value.filter(item => !item.read).length; + } + }); + return unreadMsg; + }; + + changeReadState = (clickedItem: NoticeItem) => { + const { id } = clickedItem; + const { dispatch } = this.props; + dispatch!({ + type: 'global/changeNoticeReadState', + payload: id, + }); + }; + componentDidMount() { + const { dispatch } = this.props; + dispatch!({ + type: 'global/fetchNotices', + }); + } + handleNoticeClear = (title: string, key: string) => { + const { dispatch } = this.props; + message.success(`${formatMessage({ id: 'component.noticeIcon.cleared' })} ${title}`); + if (dispatch) { + dispatch({ + type: 'global/clearNotices', + payload: key, + }); + } + }; + render() { + const { currentUser, fetchingNotices, onNoticeVisibleChange } = this.props; + const noticeData = this.getNoticeData(); + const unreadMsg = this.getUnreadData(noticeData); + + return ( + { + this.changeReadState(item as NoticeItem); + }} + loading={fetchingNotices} + clearText={formatMessage({ id: 'component.noticeIcon.clear' })} + viewMoreText={formatMessage({ id: 'component.noticeIcon.view-more' })} + onClear={this.handleNoticeClear} + onPopupVisibleChange={onNoticeVisibleChange} + onViewMore={() => message.info('Click on view more')} + clearClose + > + + + + + ); + } +} + +export default connect(({ user, global, loading }: ConnectState) => ({ + currentUser: user.currentUser, + collapsed: global.collapsed, + fetchingMoreNotices: loading.effects['global/fetchMoreNotices'], + fetchingNotices: loading.effects['global/fetchNotices'], + notices: global.notices, +}))(GlobalHeaderRight); diff --git a/src/components/GlobalHeader/RightContent.tsx b/src/components/GlobalHeader/RightContent.tsx index f2ca00f9..65fabe44 100644 --- a/src/components/GlobalHeader/RightContent.tsx +++ b/src/components/GlobalHeader/RightContent.tsx @@ -1,138 +1,44 @@ import { ConnectProps, ConnectState } from '@/models/connect'; -import { NoticeItem } from '@/models/global'; -import { CurrentUser } from '@/models/user'; import React, { Component } from 'react'; -import { Spin, Tag, Menu, Icon, Avatar, Tooltip, message } from 'antd'; -import { ClickParam } from 'antd/es/menu'; -import { FormattedMessage, formatMessage } from 'umi-plugin-react/locale'; -import moment from 'moment'; -import groupBy from 'lodash/groupBy'; -import NoticeIcon from '../NoticeIcon'; +import { Icon, Tooltip } from 'antd'; +import { formatMessage } from 'umi-plugin-react/locale'; import HeaderSearch from '../HeaderSearch'; -import HeaderDropdown from '../HeaderDropdown'; import SelectLang from '../SelectLang'; import styles from './index.less'; +import Avatar from './AvatarDropdown'; import { connect } from 'dva'; - export type SiderTheme = 'light' | 'dark'; - export interface GlobalHeaderRightProps extends ConnectProps { - notices?: NoticeItem[]; - currentUser?: CurrentUser; - fetchingNotices?: boolean; - onNoticeVisibleChange?: (visible: boolean) => void; - onMenuClick?: (param: ClickParam) => void; - onNoticeClear?: (tabName?: string) => void; theme?: SiderTheme; + layout: 'sidemenu' | 'topmenu'; } class GlobalHeaderRight extends Component { - getNoticeData = (): { [key: string]: NoticeItem[] } => { - const { notices = [] } = this.props; - if (notices.length === 0) { - return {}; - } - const newNotices = notices.map(notice => { - const newNotice = { ...notice }; - if (newNotice.datetime) { - newNotice.datetime = moment(notice.datetime as string).fromNow(); - } - if (newNotice.id) { - newNotice.key = newNotice.id; - } - if (newNotice.extra && newNotice.status) { - const color = { - todo: '', - processing: 'blue', - urgent: 'red', - doing: 'gold', - }[newNotice.status]; - newNotice.extra = ( - - {newNotice.extra} - - ); - } - return newNotice; - }); - return groupBy(newNotices, 'type'); - }; - - getUnreadData = (noticeData: { [key: string]: NoticeItem[] }) => { - const unreadMsg: { [key: string]: number } = {}; - Object.entries(noticeData).forEach(([key, value]) => { - if (!unreadMsg[key]) { - unreadMsg[key] = 0; - } - if (Array.isArray(value)) { - unreadMsg[key] = value.filter(item => !item.read).length; - } - }); - return unreadMsg; - }; - - changeReadState = (clickedItem: NoticeItem) => { - const { id } = clickedItem; - const { dispatch } = this.props; - dispatch!({ - type: 'global/changeNoticeReadState', - payload: id, - }); - }; - componentDidMount() { - const { dispatch } = this.props; - dispatch!({ - type: 'global/fetchNotices', - }); - } - handleNoticeClear = (title: string, key: string) => { - const { dispatch } = this.props; - message.success(`${formatMessage({ id: 'component.noticeIcon.cleared' })} ${title}`); - if (dispatch) { - dispatch({ - type: 'global/clearNotices', - payload: key, - }); - } - }; render() { - const { currentUser, fetchingNotices, onNoticeVisibleChange, onMenuClick, theme } = this.props; - const menu = ( - - - - - - - - - - - - - - - - - - - - ); - const noticeData = this.getNoticeData(); - const unreadMsg = this.getUnreadData(noticeData); + const { theme, layout } = this.props; let className = styles.right; - if (theme === 'dark') { + + if (theme === 'dark' && layout === 'topmenu') { className = `${styles.right} ${styles.dark}`; } + return (
{ console.log('input', value); // tslint:disable-line no-console @@ -141,7 +47,11 @@ class GlobalHeaderRight extends Component { console.log('enter', value); // tslint:disable-line no-console }} /> - + { - - { - this.changeReadState(item as NoticeItem); - }} - loading={fetchingNotices} - clearText={formatMessage({ id: 'component.noticeIcon.clear' })} - viewMoreText={formatMessage({ id: 'component.noticeIcon.view-more' })} - onClear={this.handleNoticeClear} - onPopupVisibleChange={onNoticeVisibleChange} - onViewMore={() => message.info('Click on view more')} - clearClose - > - - - - - {currentUser && currentUser.name ? ( - - - - {currentUser.name} - - - ) : ( - - )} +
); } } -export default connect(({ user, global, loading }: ConnectState) => ({ - currentUser: user.currentUser, - collapsed: global.collapsed, - fetchingMoreNotices: loading.effects['global/fetchMoreNotices'], - fetchingNotices: loading.effects['global/fetchNotices'], - notices: global.notices, +export default connect(({ settings }: ConnectState) => ({ + theme: settings.navTheme, + layout: settings.layout, }))(GlobalHeaderRight); diff --git a/src/e2e/baseLayout.e2e.js b/src/e2e/baseLayout.e2e.js index 2b7ddee4..63f58a5a 100644 --- a/src/e2e/baseLayout.e2e.js +++ b/src/e2e/baseLayout.e2e.js @@ -33,7 +33,7 @@ describe('Homepage', () => { jest.setTimeout(1000000); await page.setCacheEnabled(false); }); - const routers = formatter(RouterConfig[1].routes); + const routers = formatter(RouterConfig); routers.forEach(route => { it(`test pages ${route}`, testPage(route)); }); diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx index e2162901..a5ead002 100644 --- a/src/layouts/BasicLayout.tsx +++ b/src/layouts/BasicLayout.tsx @@ -16,74 +16,81 @@ import { BasicLayoutProps as BasicLayoutComponentsProps, MenuDataItem, Settings, - SettingDrawer, } from '@ant-design/pro-layout'; import Link from 'umi/link'; - export interface BasicLayoutProps extends BasicLayoutComponentsProps, ConnectProps { - breadcrumbNameMap: { [path: string]: MenuDataItem }; + breadcrumbNameMap: { + [path: string]: MenuDataItem; + }; settings: Settings; } - export type BasicLayoutContext = { [K in 'location']: BasicLayoutProps[K] } & { - breadcrumbNameMap: { [path: string]: MenuDataItem }; + breadcrumbNameMap: { + [path: string]: MenuDataItem; + }; }; - /** - * default menuLocal + * use Authorized check all menu item */ -const filterMenuData = (menuList: MenuDataItem[], locale: boolean) => { + +const menuDataRender = (menuList: MenuDataItem[]): MenuDataItem[] => { return menuList.map(item => { - const localItem = { - ...item, - name: item.locale && locale ? formatMessage({ id: item.locale }) : item.name, - }; + const localItem = { ...item, children: item.children ? menuDataRender(item.children) : [] }; return Authorized.check(item.authority, localItem, null) as MenuDataItem; }); }; const BasicLayout: React.FC = props => { - const { dispatch, children, settings } = props; + const { dispatch, children, settings, location } = props; /** * constructor */ + useState(() => { - dispatch!({ type: 'user/fetchCurrent' }); - dispatch!({ type: 'settings/getSetting' }); + dispatch!({ + type: 'user/fetchCurrent', + }); + dispatch!({ + type: 'settings/getSetting', + }); }); /** * init variables */ + const handleMenuCollapse = (payload: boolean) => - dispatch!({ type: 'global/changeLayoutCollapsed', payload }); - const { - menu: { locale }, - } = settings; + dispatch!({ + type: 'global/changeLayoutCollapsed', + payload, + }); + return ( - <> - { - return {defaultDom}; - }} - filterMenuData={menuList => filterMenuData(menuList, locale)} - rightContentRender={rightProps => } - {...props} - {...settings} - > - {children} - - - dispatch!({ - type: 'settings/changeSetting', - payload: config, - }) - } - /> - + { + return {defaultDom}; + }} + breadcrumbRender={(routers = []) => { + return [ + { + path: '/', + breadcrumbName: formatMessage({ + id: 'menu.home', + defaultMessage: 'Home', + }), + }, + ...routers, + ]; + }} + menuDataRender={menuDataRender} + formatMessage={formatMessage} + rightContentRender={rightProps => } + {...props} + {...settings} + > + {children} + ); }; diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index e8733e3b..4ecc7fa7 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -10,7 +10,7 @@ export default { 'layout.user.link.help': 'Help', 'layout.user.link.privacy': 'Privacy', 'layout.user.link.terms': 'Terms', - 'app.home.introduce': 'introduce', + 'app.preview.down.block': 'Download this page to your local project', ...globalHeader, ...menu, ...settingDrawer, diff --git a/src/locales/en-US/menu.ts b/src/locales/en-US/menu.ts index 7a651780..8e0026fe 100644 --- a/src/locales/en-US/menu.ts +++ b/src/locales/en-US/menu.ts @@ -1,9 +1,50 @@ export default { 'menu.welcome': 'Welcome', 'menu.more-blocks': 'More Blocks', - + 'menu.home': 'Home', + 'menu.login': 'Login', + 'menu.register': 'Register', + 'menu.register.result': 'Register Result', + 'menu.dashboard': 'Dashboard', + 'menu.dashboard.analysis': 'Analysis', + 'menu.dashboard.monitor': 'Monitor', + 'menu.dashboard.workplace': 'Workplace', + 'menu.exception.403': '403', + 'menu.exception.404': '404', + 'menu.exception.500': '500', + 'menu.form': 'Form', + 'menu.form.basic-form': 'Basic Form', + 'menu.form.step-form': 'Step Form', + 'menu.form.step-form.info': 'Step Form(write transfer information)', + 'menu.form.step-form.confirm': 'Step Form(confirm transfer information)', + 'menu.form.step-form.result': 'Step Form(finished)', + 'menu.form.advanced-form': 'Advanced Form', + 'menu.list': 'List', + 'menu.list.table-list': 'Search Table', + 'menu.list.basic-list': 'Basic List', + 'menu.list.card-list': 'Card List', + 'menu.list.search-list': 'Search List', + 'menu.list.search-list.articles': 'Search List(articles)', + 'menu.list.search-list.projects': 'Search List(projects)', + 'menu.list.search-list.applications': 'Search List(applications)', + 'menu.profile': 'Profile', + 'menu.profile.basic': 'Basic Profile', + 'menu.profile.advanced': 'Advanced Profile', + 'menu.result': 'Result', + 'menu.result.success': 'Success', + 'menu.result.fail': 'Fail', + 'menu.exception': 'Exception', + 'menu.exception.not-permission': '403', + 'menu.exception.not-find': '404', + 'menu.exception.server-error': '500', + 'menu.exception.trigger': 'Trigger', + 'menu.account': 'Account', 'menu.account.center': 'Account Center', 'menu.account.settings': 'Account Settings', 'menu.account.trigger': 'Trigger Error', 'menu.account.logout': 'Logout', + 'menu.editor': 'Graphic Editor', + 'menu.editor.flow': 'Flow Editor', + 'menu.editor.mind': 'Mind Editor', + 'menu.editor.koni': 'Koni Editor', }; diff --git a/src/locales/pt-BR.ts b/src/locales/pt-BR.ts index 4e6ac9d4..3ba8ef13 100644 --- a/src/locales/pt-BR.ts +++ b/src/locales/pt-BR.ts @@ -10,7 +10,7 @@ export default { 'layout.user.link.help': 'ajuda', 'layout.user.link.privacy': 'política de privacidade', 'layout.user.link.terms': 'termos de serviços', - 'app.home.introduce': 'introduzir', + 'app.preview.down.block': 'Download this page to your local project', ...globalHeader, ...menu, ...settingDrawer, diff --git a/src/locales/pt-BR/menu.ts b/src/locales/pt-BR/menu.ts index b4259807..3666b6b6 100644 --- a/src/locales/pt-BR/menu.ts +++ b/src/locales/pt-BR/menu.ts @@ -2,8 +2,50 @@ export default { 'menu.welcome': 'Welcome', 'menu.more-blocks': 'More Blocks', + 'menu.home': 'Início', + 'menu.login': 'Login', + 'menu.register': 'Registro', + 'menu.register.result': 'Resultado de registro', + 'menu.dashboard': 'Dashboard', + 'menu.dashboard.analysis': 'Análise', + 'menu.dashboard.monitor': 'Monitor', + 'menu.dashboard.workplace': 'Ambiente de Trabalho', + 'menu.exception.403': '403', + 'menu.exception.404': '404', + 'menu.exception.500': '500', + 'menu.form': 'Formulário', + 'menu.form.basic-form': 'Formulário Básico', + 'menu.form.step-form': 'Formulário Assistido', + 'menu.form.step-form.info': 'Formulário Assistido(gravar informações de transferência)', + 'menu.form.step-form.confirm': 'Formulário Assistido(confirmar informações de transferência)', + 'menu.form.step-form.result': 'Formulário Assistido(finalizado)', + 'menu.form.advanced-form': 'Formulário Avançado', + 'menu.list': 'Lista', + 'menu.list.table-list': 'Tabela de Busca', + 'menu.list.basic-list': 'Lista Básica', + 'menu.list.card-list': 'Lista de Card', + 'menu.list.search-list': 'Lista de Busca', + 'menu.list.search-list.articles': 'Lista de Busca(artigos)', + 'menu.list.search-list.projects': 'Lista de Busca(projetos)', + 'menu.list.search-list.applications': 'Lista de Busca(aplicações)', + 'menu.profile': 'Perfil', + 'menu.profile.basic': 'Perfil Básico', + 'menu.profile.advanced': 'Perfil Avançado', + 'menu.result': 'Resultado', + 'menu.result.success': 'Sucesso', + 'menu.result.fail': 'Falha', + 'menu.exception': 'Exceção', + 'menu.exception.not-permission': '403', + 'menu.exception.not-find': '404', + 'menu.exception.server-error': '500', + 'menu.exception.trigger': 'Disparar', + 'menu.account': 'Conta', 'menu.account.center': 'Central da Conta', 'menu.account.settings': 'Configurar Conta', 'menu.account.trigger': 'Disparar Erro', 'menu.account.logout': 'Sair', + 'menu.editor': 'Graphic Editor', + 'menu.editor.flow': 'Flow Editor', + 'menu.editor.mind': 'Mind Editor', + 'menu.editor.koni': 'Koni Editor', }; diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index ee669f3b..ec02f5e3 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -10,7 +10,7 @@ export default { 'layout.user.link.help': '帮助', 'layout.user.link.privacy': '隐私', 'layout.user.link.terms': '条款', - 'app.home.introduce': '介绍', + 'app.preview.down.block': '下载此页面到本地项目', ...globalHeader, ...menu, ...settingDrawer, diff --git a/src/locales/zh-CN/menu.ts b/src/locales/zh-CN/menu.ts index 550dfed1..f851fe96 100644 --- a/src/locales/zh-CN/menu.ts +++ b/src/locales/zh-CN/menu.ts @@ -1,8 +1,50 @@ export default { 'menu.welcome': '欢迎', 'menu.more-blocks': '更多区块', + 'menu.home': '首页', + 'menu.login': '登录', + 'menu.register': '注册', + 'menu.register.result': '注册结果', + 'menu.dashboard': 'Dashboard', + 'menu.dashboard.analysis': '分析页', + 'menu.dashboard.monitor': '监控页', + 'menu.dashboard.workplace': '工作台', + 'menu.exception.403': '403', + 'menu.exception.404': '404', + 'menu.exception.500': '500', + 'menu.form': '表单页', + 'menu.form.basic-form': '基础表单', + 'menu.form.step-form': '分步表单', + 'menu.form.step-form.info': '分步表单(填写转账信息)', + 'menu.form.step-form.confirm': '分步表单(确认转账信息)', + 'menu.form.step-form.result': '分步表单(完成)', + 'menu.form.advanced-form': '高级表单', + 'menu.list': '列表页', + 'menu.list.table-list': '查询表格', + 'menu.list.basic-list': '标准列表', + 'menu.list.card-list': '卡片列表', + 'menu.list.search-list': '搜索列表', + 'menu.list.search-list.articles': '搜索列表(文章)', + 'menu.list.search-list.projects': '搜索列表(项目)', + 'menu.list.search-list.applications': '搜索列表(应用)', + 'menu.profile': '详情页', + 'menu.profile.basic': '基础详情页', + 'menu.profile.advanced': '高级详情页', + 'menu.result': '结果页', + 'menu.result.success': '成功页', + 'menu.result.fail': '失败页', + 'menu.exception': '异常页', + 'menu.exception.not-permission': '403', + 'menu.exception.not-find': '404', + 'menu.exception.server-error': '500', + 'menu.exception.trigger': '触发错误', + 'menu.account': '个人页', 'menu.account.center': '个人中心', 'menu.account.settings': '个人设置', 'menu.account.trigger': '触发报错', 'menu.account.logout': '退出登录', + 'menu.editor': '图形编辑器', + 'menu.editor.flow': '流程编辑器', + 'menu.editor.mind': '脑图编辑器', + 'menu.editor.koni': '拓扑编辑器', }; diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 62471d91..1eeaffe2 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -10,10 +10,7 @@ export default { 'layout.user.link.help': '幫助', 'layout.user.link.privacy': '隱私', 'layout.user.link.terms': '條款', - 'app.home.introduce': '介紹', - 'app.forms.basic.title': '基礎表單', - 'app.forms.basic.description': - '表單頁用於向用戶收集或驗證信息,基礎表單常見於數據項較少的表單場景。', + 'app.preview.down.block': '下載此頁面到本地項目', ...globalHeader, ...menu, ...settingDrawer, diff --git a/src/locales/zh-TW/menu.ts b/src/locales/zh-TW/menu.ts index 1b87bd0b..b0160286 100644 --- a/src/locales/zh-TW/menu.ts +++ b/src/locales/zh-TW/menu.ts @@ -2,8 +2,50 @@ export default { 'menu.welcome': '歡迎', 'menu.more-blocks': '更多區塊', + 'menu.home': '首頁', + 'menu.login': '登錄', + 'menu.exception.403': '403', + 'menu.exception.404': '404', + 'menu.exception.500': '500', + 'menu.register': '註冊', + 'menu.register.resultt': '註冊結果', + 'menu.dashboard': 'Dashboard', + 'menu.dashboard.analysis': '分析頁', + 'menu.dashboard.monitor': '監控頁', + 'menu.dashboard.workplace': '工作臺', + 'menu.form': '表單頁', + 'menu.form.basic-form': '基礎表單', + 'menu.form.step-form': '分步表單', + 'menu.form.step-form.info': '分步表單(填寫轉賬信息)', + 'menu.form.step-form.confirm': '分步表單(確認轉賬信息)', + 'menu.form.step-form.result': '分步表單(完成)', + 'menu.form.advanced-form': '高級表單', + 'menu.list': '列表頁', + 'menu.list.table-list': '查詢表格', + 'menu.list.basic-list': '標淮列表', + 'menu.list.card-list': '卡片列表', + 'menu.list.search-list': '搜索列表', + 'menu.list.search-list.articles': '搜索列表(文章)', + 'menu.list.search-list.projects': '搜索列表(項目)', + 'menu.list.search-list.applications': '搜索列表(應用)', + 'menu.profile': '詳情頁', + 'menu.profile.basic': '基礎詳情頁', + 'menu.profile.advanced': '高級詳情頁', + 'menu.result': '結果頁', + 'menu.result.success': '成功頁', + 'menu.result.fail': '失敗頁', + 'menu.account': '個人頁', 'menu.account.center': '個人中心', 'menu.account.settings': '個人設置', 'menu.account.trigger': '觸發報錯', 'menu.account.logout': '退出登錄', + 'menu.exception': '异常页', + 'menu.exception.not-permission': '403', + 'menu.exception.not-find': '404', + 'menu.exception.server-error': '500', + 'menu.exception.trigger': '触发错误', + 'menu.editor': '圖形編輯器', + 'menu.editor.flow': '流程編輯器', + 'menu.editor.mind': '腦圖編輯器', + 'menu.editor.koni': '拓撲編輯器', }; diff --git a/src/models/login.ts b/src/models/login.ts new file mode 100644 index 00000000..e7cf877b --- /dev/null +++ b/src/models/login.ts @@ -0,0 +1,64 @@ +import { routerRedux } from 'dva/router'; +import { Reducer } from 'redux'; +import { EffectsCommandMap } from 'dva'; +import { AnyAction } from 'redux'; +import { stringify, parse } from 'qs'; + +export function getPageQuery() { + return parse(window.location.href.split('?')[1]); +} + +export interface IStateType {} + +export type Effect = ( + action: AnyAction, + effects: EffectsCommandMap & { select: (func: (state: IStateType) => T) => T }, +) => void; + +export interface ModelType { + namespace: string; + state: IStateType; + effects: { + logout: Effect; + }; + reducers: { + changeLoginStatus: Reducer; + }; +} + +const Model: ModelType = { + namespace: 'login', + + state: { + status: undefined, + }, + + effects: { + *logout(_, { put }) { + const { redirect } = getPageQuery(); + // redirect + if (window.location.pathname !== '/user/login' && !redirect) { + yield put( + routerRedux.replace({ + pathname: '/user/login', + search: stringify({ + redirect: window.location.href, + }), + }), + ); + } + }, + }, + + reducers: { + changeLoginStatus(state, { payload }) { + return { + ...state, + status: payload.status, + type: payload.type, + }; + }, + }, +}; + +export default Model; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 8ab8c89b..480064c3 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -4,3 +4,15 @@ const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|( export function isUrl(path: string) { return reg.test(path); } + +// 给官方演示站点用,用于关闭真实开发环境不需要使用的特性 +export function isAntDesignProOrDev() { + const { NODE_ENV } = process.env; + if (ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') { + return true; + } + if (NODE_ENV === 'development') { + return true; + } + return window.location.hostname === 'preview.pro.ant.design'; +} -- GitLab