Procházet zdrojové kódy

feat: merge master to v4 (#3666)

* fix: error-processing-request (#3402)

* Close: #3398

* error authority is undefined

* feat: add new config "menu.enableLocale" (#3310)

* feat: add new config "layoutLocal"

* Use enableLayoutLocale instead of layoutLocale

* Use enableMenuLocale instead of enableMenuLocale

* use menu.disableLocal

* Fix an issue in list item Add (#3423)

* try fix test error (#3424)

* try fix test error

* try fix ci error in windows

* feat: Routing is in the root directory can also matchs. (#3364)

* fix viewport content

Error parsing a meta element's content: ';' is not a valid key-value pair separator. Please use ',' instead.

* Set up CI with Azure Pipelines (#3268)

* Set up CI with Azure Pipelines

* change some js filemod to 644 (#3447)

* fix #2851 (#3440)

* add responsive-table (#3472)

* remove PureComponent (#3470)

* remove PureComponent

* fix typo

* chore: upgrade jest-puppeteer and remove some puppeteer config

* html prettier (#3473)

* prettier html

* update dependencies

* centering icon

* set icon size

* edit text

* Removed redundant colon - zh-CN (#3480)

* Removed redundant colon - zh-CN

* Removed redundant colon - zh-TW

* Removed redundant colon - en-US

* fix: horizontal scroll bar appears on SiderMenu with light navTheme (#3381)

* fix: horizontal scroll bar appears on SiderMenu with light navTheme

* a better solution

* fix Badge error (#3488)

* fix tag error

* Move flags to badges in README

* Make "home" optional in breadcrumb (#3416)

* fixed submenu collapsed when refresh page (#3494)

* perf: add checkout config in azure (#3498)

* :up: upgrade deps (#3503)

* fix: unresponsive collapse btn on mobile (#3504)

* style: prettier SiderMenu.js (#3501)

* style: prettier SiderMenu.js

* new config file

* fix ci error

* feat: support pwa config (#3508)

* feat: support pwa config

* style: change code style

* reset ci (#3507)

* add actionsText prop to TagSelect, add locales to List page #3367 (#3442)

* add actionsText prop to TagSelect, add locales to List page

* add actionsText prop to TagSelect, add locales to List page

* 修正繁体中文文案 (#3511)

* Route authority attribute behavior (#3514)

* fix customize menu icon bug (#3509)

* fix customize menu icon bug

当采用自定义菜单图标(即通过url引用方式)且菜单折叠时,文字不隐藏

* fix customize menu icon bug

修改样式

* feat: add a demo that jump to details (#3502)

* feat: add a demo that jump to details

* feat: hide progress in coi

* remove trigger config

* remove fetchDepth: 1

* refactor: userinfo and application  from api

* style: fix code style

* Enhance stylelint rules, fix propTypes error of TagSelect (#3518)

* add stylelint shareable config for css modules

* fix CSS pseudo element with double colon

* support stylelint declaration-block-no-ignored-properties rule

* support sorted CSS properties order for readability and consistency

* autofix order of all styles by lint:fix script

* fix propTypes error of TagSelect component

* Revert "autofix order of all styles by lint:fix script"

This reverts commit 51cb9d055f607489a259fd878d80c6b6e70c61de.

* Revert "support sorted CSS properties order for readability and consistency"

This reverts commit ff6c24d263cf5d3f73880bd7a5866efb4911de9f.

* make lint:fix work for stylelint

* Revert "Revert "autofix order of all styles by lint:fix script""

This reverts commit 946ed0a351542904e70680c166e47a1f5080d1ca.

* Revert "Revert "support sorted CSS properties order for readability and consistency""

This reverts commit 31b557e3826e6e5c5cf9b591fc97e24b9b2abb6e.

* Update README.zh-CN.md

* doc: add umi-badge (#3538)

* doc: add umi-badge

* Update README.ru-RU.md (#3539)

add umi badge

* feat: Officially traded will use cdn to optimize bizchart (#3535)

* route authority attribute behavior no use while (#3522)

* route authority attribute behavior no use while

* i18n pt-BR: analysis & component (#3540)

* Translation of form and monitor (pt-BR)

* Change flag of Portugal (pt-PT) to Brazil (pt-BR)

* i18n pt-BR: analysis & component

* Fix missing export default  (#3525)

* fix types missing export default

* Update package.json

* Update index.d.ts

* Update index.d.ts

* Update index.d.ts

* 增加IconFont组件、菜单图标可以使用自己的IconFont项目图标 (#3517)

* feature:
1.add iconfont component;
2.menu can add iconfont icon.

* fix: 调整菜单引入iconfont的方式为String.
1. 新增IconFont组件,需在组件内配置自己的IconFont图标项目地址;
2. 然后,菜单图标可以引入自己的IconFont图标,图标字符串以icon-开头.

* ajust: put the IconFont Script Url into defaultSetting.js

* 调整iconfontUrl名称

* fix:注释更新

* 留空iconfontUrl

* Site title use defaultSettings (#3546)

* Site title od top use defaultSettings (#3551)

* pref: optimize performance (#3542)

* pref: optimize performance

* pref: use less img

* pref: use less img

* fix: Eslint warning of Mock dependence (#3554)

* perf: use requestAnimationFrame

* Fix: onPressEnter trigger twice login request in IE11

* Fix: onPressEnter trigger twice login request in IE11

* Login title (#3564)

* Added document title to UserLayout by identifying the current route object and using its name kaey to set the title

* Adjusment to document title

* feat: use same getPageTile function

* when select 3 item ,text branch

* style: use standard frontmatter

* doc: remove subtitle in en-Us

* better demo md

* fix PageHeader no title bug (#3583)

* fix PageHeader no title bug

* default value

* fix: React does not recognize the `staticContext` prop on a DOM element. (#3582)

* doc: better demo

* remove drawer onHandleClick, ant-design/ant-design#15051 (#3602)

* Use Umi Permission Routing (#3587)

Use Umi Permission Routing

* fix the problem that breadcrumbNameMap does not contain hidden menus. (#3606)

* Update enzyme to version 3.9.0

* Update package.json

* Update jest-puppeteer to version 4.0.0

* Update prettier to version 1.16.4

* Update package.json

* :bug: fix TagCloud style override bug (#3632)

* Update stylelint-config-prettier to version 5.0.0

* Update index.md (#3644)

* dead code (#3639)

* dead code
Close: #3637

* delete dead props

* [NoticeIcon] Replace `LoadMore` with `ViewMore` button (#3439)

* enhance LoadMore: Debounce

* enhance LoadMore: debounce

* use Tag instead of div

* rewrite margin-right of Tag

* hide LoadMore in NoticeList without onLoadMore

* another style

* fix a mistake

* remove local config

* fix a bug

* user-select: none

* remover local config

* replace global/fetchMoreNotices with global/fetchNotices

* replace LoadMore with ViewMore

* remove prop `name` in NoticeIcon

* fix: tab title does not show correct text

* Fix margin top style error of Description List following Description List (#3653)

* fix login model statu
Yu před 7 roky
rodič
revize
e0b20cb79d
65 změnil soubory, kde provedl 696 přidání a 627 odebrání
  1. 0 25
      .circleci/config.yml
  2. 1 1
      .eslintrc.js
  3. 0 2
      .prettierignore
  4. 8 4
      .stylelintrc.json
  5. 4 5
      README.md
  6. 4 3
      README.ru-RU.md
  7. 5 5
      README.zh-CN.md
  8. 0 26
      appveyor.yml
  9. 74 0
      azure-pipelines.yml
  10. 8 3
      config/config.js
  11. 1 1
      functions/package.json
  12. 1 10
      jest-puppeteer.config.js
  13. 98 111
      mock/notices.js
  14. 1 1
      mock/user.js
  15. 33 28
      package.json
  16. 11 5
      src/app.js
  17. 13 36
      src/components/GlobalHeader/RightContent.js
  18. 14 14
      src/components/GlobalHeader/index.less
  19. 2 0
      src/components/HeaderDropdown/index.d.ts
  20. 1 1
      src/components/HeaderDropdown/index.less
  21. 3 5
      src/components/HeaderSearch/index.en-US.md
  22. 4 4
      src/components/HeaderSearch/index.less
  23. 2 4
      src/components/HeaderSearch/index.zh-CN.md
  24. 7 0
      src/components/IconFont/index.js
  25. 3 3
      src/components/SelectLang/index.less
  26. 7 7
      src/components/SettingDrawer/ThemeColor.less
  27. 1 2
      src/components/SettingDrawer/index.js
  28. 17 17
      src/components/SettingDrawer/index.less
  29. 10 5
      src/components/SiderMenu/BaseMenu.js
  30. 18 6
      src/components/SiderMenu/SiderMenu.js
  31. 2 1
      src/components/SiderMenu/SiderMenuUtils.js
  32. 29 20
      src/components/SiderMenu/index.less
  33. 2 1
      src/components/TopNavHeader/index.js
  34. 11 11
      src/components/TopNavHeader/index.less
  35. 8 0
      src/defaultSettings.js
  36. 54 47
      src/global.js
  37. 19 0
      src/global.less
  38. 7 66
      src/layouts/BasicLayout.js
  39. 1 1
      src/layouts/BlankLayout.js
  40. 2 3
      src/layouts/Header.js
  41. 1 1
      src/layouts/Header.less
  42. 41 30
      src/layouts/UserLayout.js
  43. 9 9
      src/layouts/UserLayout.less
  44. 2 0
      src/locales/en-US.js
  45. 5 0
      src/locales/en-US/component.js
  46. 1 2
      src/locales/en-US/globalHeader.js
  47. 3 3
      src/locales/en-US/settings.js
  48. 2 0
      src/locales/pt-BR.js
  49. 5 0
      src/locales/pt-BR/component.js
  50. 1 1
      src/locales/pt-BR/globalHeader.js
  51. 2 0
      src/locales/zh-CN.js
  52. 5 0
      src/locales/zh-CN/component.js
  53. 1 2
      src/locales/zh-CN/globalHeader.js
  54. 3 3
      src/locales/zh-CN/settings.js
  55. 2 0
      src/locales/zh-TW.js
  56. 5 0
      src/locales/zh-TW/component.js
  57. 3 4
      src/locales/zh-TW/globalHeader.js
  58. 3 3
      src/locales/zh-TW/settings.js
  59. 1 41
      src/models/global.js
  60. 12 5
      src/models/menu.js
  61. 40 9
      src/pages/Authorized.js
  62. 14 12
      src/pages/document.ejs
  63. 27 0
      src/utils/getPageTitle.js
  64. 17 17
      src/utils/utils.less
  65. 5 1
      tests/run-tests.js

+ 0 - 25
.circleci/config.yml

@@ -1,25 +0,0 @@
-version: 2
-jobs:
-  build:
-    docker:
-      - image: circleci/node:latest
-    steps:
-      - checkout
-      - run: npm install
-      - run: npm run lint
-      - run: npm run build
-  test:
-    docker:
-      - image: circleci/node:latest-browsers
-    steps:
-      - checkout
-      - run: npm install
-      - run:
-          command: npm run test:all
-          no_output_timeout: 30m
-workflows:
-  version: 2
-  build_and_test:
-    jobs:
-      - build
-      - test

+ 1 - 1
.eslintrc.js

@@ -24,7 +24,7 @@ module.exports = {
       2,
       {
         optionalDependencies: true,
-        devDependencies: ['**/tests/**.js', '/mock/**.js', '**/**.test.js'],
+        devDependencies: ['**/tests/**.js', '/mock/**/**.js', '**/**.test.js'],
       },
     ],
     'jsx-a11y/no-noninteractive-element-interactions': 0,

+ 0 - 2
.prettierignore

@@ -1,7 +1,5 @@
 **/*.md
 **/*.svg
-**/*.ejs
-**/*.html
 package.json
 .umi
 .umi-production

+ 8 - 4
.stylelintrc.json

@@ -1,9 +1,13 @@
 {
-  "extends": ["stylelint-config-standard", "stylelint-config-prettier"],
+  "extends": [
+    "stylelint-config-standard",
+    "stylelint-config-css-modules",
+    "stylelint-config-rational-order",
+    "stylelint-config-prettier"
+  ],
+  "plugins": ["stylelint-order", "stylelint-declaration-block-no-ignored-properties"],
   "rules": {
-    "declaration-empty-line-before": null,
     "no-descending-specificity": null,
-    "selector-pseudo-class-no-unknown": null,
-    "selector-pseudo-element-colon-notation": null
+    "plugin/declaration-block-no-ignored-properties": true
   }
 }

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 4 - 5
README.md


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 4 - 3
README.ru-RU.md


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 5 - 5
README.zh-CN.md


+ 0 - 26
appveyor.yml

@@ -1,26 +0,0 @@
-# Test against the latest version of this Node.js version
-environment:
-  nodejs_version: '10'
-
-# this is how to allow failing jobs in the matrix
-matrix:
-  fast_finish: true # set this flag to immediately finish build once one of the jobs fails.
-
-# Install scripts. (runs after repo cloning)
-install:
-  # Get the latest stable version of Node.js or io.js
-  - ps: Install-Product node $env:nodejs_version
-  # install modules
-  - npm install
-  # Output useful info for debugging.
-  - node --version
-  - npm --version
-
-# Post-install test scripts.
-test_script:
-  - npm run lint
-  - npm run test:all
-  - npm run build
-
-# Don't actually build.
-build: off

+ 74 - 0
azure-pipelines.yml

@@ -0,0 +1,74 @@
+# Node.js
+# Build a general Node.js project with npm.
+# Add steps that analyze code, save build artifacts, deploy, and more:
+# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
+name: ant design pro
+
+trigger:
+  - master
+
+jobs:
+  - job: lintAndBuild
+
+    pool:
+      vmImage: 'Ubuntu-16.04'
+
+    steps:
+      - checkout: self
+        clean: false
+      - script: yarn install
+        displayName: install
+      - script: npm run lint
+        displayName: lint
+      - script: npm run build
+        env:
+          PROGRESS: none
+        displayName: build
+
+  - job: test
+    pool:
+      vmImage: 'Ubuntu-16.04'
+
+    container:
+      image: circleci/node:latest-browsers
+      options: '-u root'
+
+    steps:
+      - script: yarn install
+        displayName: install
+      - script: npm run test:all
+        env:
+          PROGRESS: none
+        displayName: test
+
+  - job: Windows
+    pool:
+      vmImage: 'vs2017-win2016'
+    steps:
+      - task: NodeTool@0
+        inputs:
+          versionSpec: '11.x'
+      - script: yarn install
+        displayName: install
+      - script: npm run lint
+        displayName: lint
+      - script: npm run build
+        env:
+          PROGRESS: none
+        displayName: build
+
+  - job: MacOS
+    pool:
+      vmImage: 'macOS-10.13'
+    steps:
+      - task: NodeTool@0
+        inputs:
+          versionSpec: '11.x'
+      - script: yarn install
+        displayName: install
+      - script: npm run lint
+        displayName: lint
+      - script: npm run
+        env:
+          PROGRESS: none
+        displayName: build

+ 8 - 3
config/config.js

@@ -4,6 +4,9 @@ import webpackPlugin from './plugin.config';
 import defaultSettings from '../src/defaultSettings';
 import slash from 'slash2';
 
+const { pwa, primaryColor } = defaultSettings;
+const { NODE_ENV, APP_TYPE, TEST } = process.env;
+
 const plugins = [
   [
     'umi-plugin-react',
@@ -20,6 +23,7 @@ const plugins = [
       dynamicImport: {
         loadingComponent: './components/PageLoading/index',
         webpackChunkName: true,
+        level: 3,
       },
       pwa: {
         workboxPluginMode: 'InjectManifest',
@@ -51,7 +55,7 @@ const plugins = [
 
 // 针对 preview.pro.ant.design 的 GA 统计代码
 // 业务上不需要这个
-if (process.env.APP_TYPE === 'site') {
+if (APP_TYPE === 'site') {
   plugins.push([
     'umi-plugin-ga',
     {
@@ -64,7 +68,7 @@ export default {
   // add for transfer to umi
   plugins,
   define: {
-    APP_TYPE: process.env.APP_TYPE || '',
+    APP_TYPE: APP_TYPE || '',
   },
   treeShaking: true,
   targets: {
@@ -96,10 +100,11 @@ export default {
   // Theme for antd
   // https://ant.design/docs/react/customize-theme-cn
   theme: {
-    'primary-color': defaultSettings.primaryColor,
+    'primary-color': primaryColor,
   },
   externals: {
     '@antv/data-set': 'DataSet',
+    bizcharts: 'BizCharts',
   },
   // proxy: {
   //   '/server/api/': {

+ 1 - 1
functions/package.json

@@ -17,7 +17,7 @@
     "firebase-functions": "^2.1.0",
     "mockjs": "^1.0.1-beta3",
     "moment": "^2.22.2",
-    "path-to-regexp": "^2.2.1"
+    "path-to-regexp": "^3.0.0"
   },
   "private": true
 }

+ 1 - 10
jest-puppeteer.config.js

@@ -1,15 +1,6 @@
 // ps https://github.com/GoogleChrome/puppeteer/issues/3120
 module.exports = {
   launch: {
-    headless: true,
-    args: [
-      '--disable-gpu',
-      '--disable-dev-shm-usage',
-      '--disable-setuid-sandbox',
-      '--no-first-run',
-      '--no-sandbox',
-      '--no-zygote',
-      '--single-process',
-    ],
+    args: ['--disable-gpu', '--disable-dev-shm-usage', '--no-first-run', '--no-zygote'],
   },
 };

+ 98 - 111
mock/notices.js

@@ -1,114 +1,101 @@
-const fakeNotices = [
-  {
-    id: '000000001',
-    avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
-    title: '你收到了 14 份新周报',
-    datetime: '2017-08-09',
-    type: 'notification',
-  },
-  {
-    id: '000000002',
-    avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
-    title: '你推荐的 曲妮妮 已通过第三轮面试',
-    datetime: '2017-08-08',
-    type: 'notification',
-  },
-  {
-    id: '000000003',
-    avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
-    title: '这种模板可以区分多种通知类型',
-    datetime: '2017-08-07',
-    read: true,
-    type: 'notification',
-  },
-  {
-    id: '000000004',
-    avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
-    title: '左侧图标用于区分不同的类型',
-    datetime: '2017-08-07',
-    type: 'notification',
-  },
-  {
-    id: '000000005',
-    avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
-    title: '内容不要超过两行字,超出时自动截断',
-    datetime: '2017-08-07',
-    type: 'notification',
-  },
-  {
-    id: '000000006',
-    avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
-    title: '曲丽丽 评论了你',
-    description: '描述信息描述信息描述信息',
-    datetime: '2017-08-07',
-    type: 'message',
-    clickClose: true,
-  },
-  {
-    id: '000000007',
-    avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
-    title: '朱偏右 回复了你',
-    description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
-    datetime: '2017-08-07',
-    type: 'message',
-    clickClose: true,
-  },
-  {
-    id: '000000008',
-    avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
-    title: '标题',
-    description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
-    datetime: '2017-08-07',
-    type: 'message',
-    clickClose: true,
-  },
-  {
-    id: '000000009',
-    title: '任务名称',
-    description: '任务需要在 2017-01-12 20:00 前启动',
-    extra: '未开始',
-    status: 'todo',
-    type: 'event',
-  },
-  {
-    id: '000000010',
-    title: '第三方紧急代码变更',
-    description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
-    extra: '马上到期',
-    status: 'urgent',
-    type: 'event',
-  },
-  {
-    id: '000000011',
-    title: '信息安全考试',
-    description: '指派竹尔于 2017-01-09 前完成更新并发布',
-    extra: '已耗时 8 天',
-    status: 'doing',
-    type: 'event',
-  },
-  {
-    id: '000000012',
-    title: 'ABCD 版本发布',
-    description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
-    extra: '进行中',
-    status: 'processing',
-    type: 'event',
-  },
-];
-
-const getNotices = (req, res) => {
-  if (req.query && req.query.type) {
-    const startFrom = parseInt(req.query.lastItemId, 10) + 1;
-    const result = fakeNotices
-      .filter(({ type }) => type === req.query.type)
-      .map((notice, index) => ({
-        ...notice,
-        id: `0000000${startFrom + index}`,
-      }));
-    return res.json(startFrom > 24 ? result.concat(null) : result);
-  }
-  return res.json(fakeNotices);
-};
+const getNotices = (req, res) =>
+  res.json([
+    {
+      id: '000000001',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
+      title: '你收到了 14 份新周报',
+      datetime: '2017-08-09',
+      type: 'notification',
+    },
+    {
+      id: '000000002',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
+      title: '你推荐的 曲妮妮 已通过第三轮面试',
+      datetime: '2017-08-08',
+      type: 'notification',
+    },
+    {
+      id: '000000003',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
+      title: '这种模板可以区分多种通知类型',
+      datetime: '2017-08-07',
+      read: true,
+      type: 'notification',
+    },
+    {
+      id: '000000004',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
+      title: '左侧图标用于区分不同的类型',
+      datetime: '2017-08-07',
+      type: 'notification',
+    },
+    {
+      id: '000000005',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
+      title: '内容不要超过两行字,超出时自动截断',
+      datetime: '2017-08-07',
+      type: 'notification',
+    },
+    {
+      id: '000000006',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+      title: '曲丽丽 评论了你',
+      description: '描述信息描述信息描述信息',
+      datetime: '2017-08-07',
+      type: 'message',
+      clickClose: true,
+    },
+    {
+      id: '000000007',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+      title: '朱偏右 回复了你',
+      description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+      datetime: '2017-08-07',
+      type: 'message',
+      clickClose: true,
+    },
+    {
+      id: '000000008',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+      title: '标题',
+      description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+      datetime: '2017-08-07',
+      type: 'message',
+      clickClose: true,
+    },
+    {
+      id: '000000009',
+      title: '任务名称',
+      description: '任务需要在 2017-01-12 20:00 前启动',
+      extra: '未开始',
+      status: 'todo',
+      type: 'event',
+    },
+    {
+      id: '000000010',
+      title: '第三方紧急代码变更',
+      description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+      extra: '马上到期',
+      status: 'urgent',
+      type: 'event',
+    },
+    {
+      id: '000000011',
+      title: '信息安全考试',
+      description: '指派竹尔于 2017-01-09 前完成更新并发布',
+      extra: '已耗时 8 天',
+      status: 'doing',
+      type: 'event',
+    },
+    {
+      id: '000000012',
+      title: 'ABCD 版本发布',
+      description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+      extra: '进行中',
+      status: 'processing',
+      type: 'event',
+    },
+  ]);
 
 export default {
   'GET /api/notices': getNotices,

+ 1 - 1
mock/user.js

@@ -3,7 +3,7 @@ export default {
   // 支持值为 Object 和 Array
   'GET /api/currentUser': {
     name: 'Serati Ma',
-    avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
+    avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
     userid: '00000001',
     email: 'antdesign@alipay.com',
     signature: '海纳百川,有容乃大',

+ 33 - 28
package.json

@@ -1,6 +1,6 @@
 {
   "name": "ant-design-pro",
-  "version": "2.2.0",
+  "version": "2.2.1",
   "description": "An out-of-box UI solution for enterprise applications",
   "private": true,
   "scripts": {
@@ -11,10 +11,10 @@
     "dev:no-mock": "cross-env MOCK=none umi dev",
     "build": "umi build",
     "analyze": "cross-env ANALYZE=1 umi build",
-    "lint:style": "stylelint \"src/**/*.less\" --syntax less",
+    "lint:style": "stylelint 'src/**/*.less' --syntax less",
     "lint:prettier": "check-prettier lint",
     "lint": "eslint --ext .js src mock tests && npm run lint:style && npm run lint:prettier",
-    "lint:fix": "eslint --fix --ext .js src mock tests && npm run lint:style",
+    "lint:fix": "eslint --fix --ext .js src mock tests && stylelint --fix 'src/**/*.less' --syntax less",
     "lint-staged": "lint-staged",
     "lint-staged:js": "eslint --ext .js",
     "test": "umi test",
@@ -50,46 +50,51 @@
     "umi-request": "^1.0.0"
   },
   "devDependencies": {
-    "@types/react": "^16.7.7",
-    "@types/react-dom": "^16.0.10",
+    "@types/react": "^16.8.1",
+    "@types/react-dom": "^16.0.11",
     "antd-pro-merge-less": "^1.0.0",
-    "antd-theme-webpack-plugin": "^1.1.8",
+    "antd-theme-webpack-plugin": "^1.2.0",
     "babel-eslint": "^10.0.1",
+    "chalk": "^2.4.2",
     "check-prettier": "^1.0.1",
-    "cross-env": "^5.1.1",
+    "cross-env": "^5.2.0",
     "cross-port-killer": "^1.0.1",
-    "enzyme": "3.7.0",
-    "eslint": "^5.4.0",
-    "eslint-config-airbnb": "^17.0.0",
-    "eslint-config-prettier": "^3.0.1",
+    "enzyme": "^3.9.0",
+    "eslint": "^5.13.0",
+    "eslint-config-airbnb": "^17.1.0",
+    "eslint-config-prettier": "^4.0.0",
     "eslint-plugin-babel": "^5.3.0",
-    "eslint-plugin-compat": "^2.6.2",
-    "eslint-plugin-import": "^2.14.0",
-    "eslint-plugin-jsx-a11y": "^6.1.2",
-    "eslint-plugin-markdown": "^1.0.0-beta.6",
-    "eslint-plugin-react": "^7.11.1",
-    "husky": "^1.2.0",
-    "jest-puppeteer": "^3.5.1",
+    "eslint-plugin-compat": "^2.6.3",
+    "eslint-plugin-import": "^2.16.0",
+    "eslint-plugin-jsx-a11y": "^6.2.0",
+    "eslint-plugin-markdown": "^1.0.0",
+    "eslint-plugin-react": "^7.12.4",
+    "gh-pages": "^2.0.1",
+    "husky": "^1.3.1",
+    "jest-puppeteer": "^4.0.0",
     "less": "^3.9.0",
-    "lint-staged": "^8.1.0",
+    "lint-staged": "^8.1.1",
     "merge-umi-mock-data": "^1.0.4",
     "mockjs": "^1.0.1-beta3",
-    "prettier": "1.15.2",
-    "pro-download": "^1.0.1",
+    "prettier": "^1.16.4",
     "slash2": "^2.0.0",
-    "stylelint": "^9.8.0",
-    "stylelint-config-prettier": "^4.0.0",
-    "stylelint-config-standard": "^18.0.0",
-    "tslint": "^5.10.0",
-    "tslint-config-prettier": "^1.10.0",
+    "stylelint": "^9.10.1",
+    "stylelint-config-css-modules": "^1.3.0",
+    "stylelint-config-prettier": "^5.0.0",
+    "stylelint-config-rational-order": "^0.0.4",
+    "stylelint-config-standard": "^18.2.0",
+    "stylelint-declaration-block-no-ignored-properties": "^1.1.0",
+    "stylelint-order": "^2.0.0",
+    "tslint": "^5.12.1",
+    "tslint-config-prettier": "^1.17.0",
     "tslint-react": "^3.6.0",
-    "umi": "^2.4.2",
+    "umi": "^2.4.4",
     "umi-plugin-ga": "^1.1.3",
     "umi-plugin-react": "^1.3.4",
     "umi-plugin-pro-block": "^1.2.0"
   },
   "optionalDependencies": {
-    "puppeteer": "^1.10.0"
+    "puppeteer": "^1.12.1"
   },
   "lint-staged": {
     "**/*.{js,ts,tsx,json,jsx,less}": [

+ 11 - 5
src/app.js

@@ -8,11 +8,12 @@ export const dva = {
   },
 };
 
-let authRoutes = null;
+let authRoutes = {};
 
 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);
@@ -31,8 +32,13 @@ export function patchRoutes(routes) {
 export function render(oldRender) {
   fetch('/api/auth_routes')
     .then(res => res.json())
-    .then(ret => {
-      authRoutes = ret;
-      oldRender();
-    });
+    .then(
+      ret => {
+        authRoutes = ret;
+        oldRender();
+      },
+      () => {
+        oldRender();
+      }
+    );
 }

+ 13 - 36
src/components/GlobalHeader/RightContent.js

@@ -1,6 +1,6 @@
 import React, { PureComponent } from 'react';
 import { FormattedMessage, formatMessage } from 'umi/locale';
-import { Spin, Tag, Menu, Icon, Avatar, Tooltip } from 'antd';
+import { Spin, Tag, Menu, Icon, Avatar, Tooltip, message } from 'antd';
 import moment from 'moment';
 import groupBy from 'lodash/groupBy';
 import { NoticeIcon } from 'ant-design-pro';
@@ -63,30 +63,13 @@ export default class GlobalHeaderRight extends PureComponent {
     });
   };
 
-  fetchMoreNotices = tabProps => {
-    const { list, name } = tabProps;
-    const { dispatch, notices = [] } = this.props;
-    const lastItemId = notices[notices.length - 1].id;
-    dispatch({
-      type: 'global/fetchMoreNotices',
-      payload: {
-        lastItemId,
-        type: name,
-        offset: list.length,
-      },
-    });
-  };
-
   render() {
     const {
       currentUser,
-      fetchingMoreNotices,
       fetchingNotices,
-      loadedAllNotices,
       onNoticeVisibleChange,
       onMenuClick,
       onNoticeClear,
-      skeletonCount,
       theme,
     } = this.props;
     const menu = (
@@ -110,11 +93,6 @@ export default class GlobalHeaderRight extends PureComponent {
         </Menu.Item>
       </Menu>
     );
-    const loadMoreProps = {
-      skeletonCount,
-      loadedAll: loadedAllNotices,
-      loading: fetchingMoreNotices,
-    };
     const noticeData = this.getNoticeData();
     const unreadMsg = this.getUnreadData(noticeData);
     let className = styles.right;
@@ -155,44 +133,43 @@ export default class GlobalHeaderRight extends PureComponent {
             console.log(item, tabProps); // eslint-disable-line
             this.changeReadState(item, tabProps);
           }}
+          loading={fetchingNotices}
           locale={{
             emptyText: formatMessage({ id: 'component.noticeIcon.empty' }),
             clear: formatMessage({ id: 'component.noticeIcon.clear' }),
-            loadedAll: formatMessage({ id: 'component.noticeIcon.loaded' }),
-            loadMore: formatMessage({ id: 'component.noticeIcon.loading-more' }),
+            viewMore: formatMessage({ id: 'component.noticeIcon.view-more' }),
+            notification: formatMessage({ id: 'component.globalHeader.notification' }),
+            message: formatMessage({ id: 'component.globalHeader.message' }),
+            event: formatMessage({ id: 'component.globalHeader.event' }),
           }}
           onClear={onNoticeClear}
-          onLoadMore={this.fetchMoreNotices}
           onPopupVisibleChange={onNoticeVisibleChange}
-          loading={fetchingNotices}
+          onViewMore={() => message.info('Click on view more')}
           clearClose
         >
           <NoticeIcon.Tab
             count={unreadMsg.notification}
             list={noticeData.notification}
-            title={formatMessage({ id: 'component.globalHeader.notification' })}
-            name="notification"
+            title="notification"
             emptyText={formatMessage({ id: 'component.globalHeader.notification.empty' })}
             emptyImage="https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg"
-            {...loadMoreProps}
+            showViewMore
           />
           <NoticeIcon.Tab
             count={unreadMsg.message}
             list={noticeData.message}
-            title={formatMessage({ id: 'component.globalHeader.message' })}
-            name="message"
+            title="message"
             emptyText={formatMessage({ id: 'component.globalHeader.message.empty' })}
             emptyImage="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
-            {...loadMoreProps}
+            showViewMore
           />
           <NoticeIcon.Tab
             count={unreadMsg.event}
             list={noticeData.event}
-            title={formatMessage({ id: 'component.globalHeader.event' })}
-            name="event"
+            title="event"
             emptyText={formatMessage({ id: 'component.globalHeader.event.empty' })}
             emptyImage="https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg"
-            {...loadMoreProps}
+            showViewMore
           />
         </NoticeIcon>
         {currentUser.name ? (

+ 14 - 14
src/components/GlobalHeader/index.less

@@ -3,21 +3,21 @@
 @pro-header-hover-bg: rgba(0, 0, 0, 0.025);
 
 .header {
+  position: relative;
   height: @layout-header-height;
   padding: 0;
   background: #fff;
   box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
-  position: relative;
 }
 
 .logo {
+  display: inline-block;
   height: @layout-header-height;
+  padding: 0 0 0 24px;
+  font-size: 20px;
   line-height: @layout-header-height;
   vertical-align: top;
-  display: inline-block;
-  padding: 0 0 0 24px;
   cursor: pointer;
-  font-size: 20px;
   img {
     display: inline-block;
     vertical-align: middle;
@@ -34,11 +34,11 @@
 }
 
 .trigger {
-  font-size: 20px;
   height: @layout-header-height;
+  padding: ~'calc((@{layout-header-height} - 20px) / 2)' 24px;
+  font-size: 20px;
   cursor: pointer;
   transition: all 0.3s, padding 0s;
-  padding: ~'calc((@{layout-header-height} - 20px) / 2)' 24px;
   &:hover {
     background: @pro-header-hover-bg;
   }
@@ -49,14 +49,14 @@
   height: 100%;
   overflow: hidden;
   .action {
-    cursor: pointer;
-    padding: 0 12px;
     display: inline-block;
-    transition: all 0.3s;
     height: 100%;
+    padding: 0 12px;
+    cursor: pointer;
+    transition: all 0.3s;
     > i {
-      vertical-align: middle;
       color: @text-color;
+      vertical-align: middle;
     }
     &:hover {
       background: @pro-header-hover-bg;
@@ -76,8 +76,8 @@
       margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
       margin-right: 8px;
       color: @primary-color;
-      background: rgba(255, 255, 255, 0.85);
       vertical-align: top;
+      background: rgba(255, 255, 255, 0.85);
     }
   }
 }
@@ -111,14 +111,14 @@
       padding: 22px 12px;
     }
     .logo {
-      padding-left: 12px;
-      padding-right: 12px;
       position: relative;
+      padding-right: 12px;
+      padding-left: 12px;
     }
     .right {
       position: absolute;
-      right: 12px;
       top: 0;
+      right: 12px;
       background: #fff;
       .account {
         .avatar {

+ 2 - 0
src/components/HeaderDropdown/index.d.ts

@@ -0,0 +1,2 @@
+import * as React from 'react';
+export default class HeaderDropdown extends React.Component<any, any> {}

+ 1 - 1
src/components/HeaderDropdown/index.less

@@ -2,8 +2,8 @@
 
 .container > * {
   background-color: #fff;
-  box-shadow: @shadow-1-down;
   border-radius: 4px;
+  box-shadow: @shadow-1-down;
 }
 
 @media screen and (max-width: @screen-xs) {

+ 3 - 5
src/components/HeaderSearch/index.en-US.md

@@ -1,8 +1,6 @@
 ---
-title:
-  en-US: HeaderSearch
-  zh-CN: HeaderSearch
-subtitle: Top search box
+title: HeaderSearch
+subtitle:
 cols: 1
 order: 8
 ---
@@ -21,4 +19,4 @@ onSelect | Called when a option is selected. param is option's value and option
 onPressEnter | Callback when pressing Enter | function(value) | -
 onVisibleChange | Show or hide the callback of the text box | function(value) |-
 defaultOpen | The input box is displayed for the first time. | boolean | false
-open | The input box is displayed | booelan |false
+open | The input box is displayed | boolean |false

+ 4 - 4
src/components/HeaderSearch/index.less

@@ -2,21 +2,21 @@
 
 .headerSearch {
   :global(.anticon-search) {
-    cursor: pointer;
     font-size: 16px;
+    cursor: pointer;
   }
   .input {
-    transition: width 0.3s, margin-left 0.3s;
     width: 0;
     background: transparent;
     border-radius: 0;
+    transition: width 0.3s, margin-left 0.3s;
     :global(.ant-select-selection) {
       background: transparent;
     }
     input {
-      border: 0;
-      padding-left: 0;
       padding-right: 0;
+      padding-left: 0;
+      border: 0;
       box-shadow: none !important;
     }
     &,

+ 2 - 4
src/components/HeaderSearch/index.zh-CN.md

@@ -1,7 +1,5 @@
 ---
-title:
-  en-US: HeaderSearch
-  zh-CN: HeaderSearch
+title: HeaderSearch
 subtitle: 顶部搜索框
 cols: 1
 order: 8
@@ -21,4 +19,4 @@ onSelect | 被选中时调用,参数为选中项的 value 值 | function(value
 onPressEnter | 按下回车时的回调 | function(value) | -
 onVisibleChange | 显示或隐藏文本框的回调 | function(value) |-
 defaultOpen | 输入框首次显示是否显示  | boolean | false
-open | 控制输入框是否显示 | booelan |false
+open | 控制输入框是否显示 | boolean |false

+ 7 - 0
src/components/IconFont/index.js

@@ -0,0 +1,7 @@
+import { Icon } from 'antd';
+import { iconfontUrl as scriptUrl } from '../../defaultSettings';
+
+// 使用:
+// import IconFont from '@/components/IconFont';
+// <IconFont type='icon-demo' className='xxx-xxx' />
+export default Icon.createFromIconfontCN({ scriptUrl });

+ 3 - 3
src/components/SelectLang/index.less

@@ -10,11 +10,11 @@
 }
 
 .dropDown {
-  cursor: pointer;
-  vertical-align: top;
   line-height: @layout-header-height;
+  vertical-align: top;
+  cursor: pointer;
   > i {
-    font-size: 14px !important;
+    font-size: 16px !important;
     transform: none !important;
     svg {
       position: relative;

+ 7 - 7
src/components/SettingDrawer/ThemeColor.less

@@ -1,21 +1,21 @@
 .themeColor {
-  overflow: hidden;
   margin-top: 24px;
+  overflow: hidden;
   .title {
-    font-size: 14px;
+    margin-bottom: 12px;
     color: rgba(0, 0, 0, 0.65);
+    font-size: 14px;
     line-height: 22px;
-    margin-bottom: 12px;
   }
   .colorBlock {
+    float: left;
     width: 20px;
     height: 20px;
-    border-radius: 2px;
-    float: left;
-    cursor: pointer;
     margin-right: 8px;
-    text-align: center;
     color: #fff;
     font-weight: bold;
+    text-align: center;
+    border-radius: 2px;
+    cursor: pointer;
   }
 }

+ 1 - 2
src/components/SettingDrawer/index.js

@@ -137,7 +137,7 @@ class SettingDrawer extends PureComponent {
         onClose={this.togglerContent}
         placement="right"
         handler={
-          <div className={styles.handle}>
+          <div className={styles.handle} onClick={this.togglerContent}>
             <Icon
               type={collapse ? 'close' : 'setting'}
               style={{
@@ -147,7 +147,6 @@ class SettingDrawer extends PureComponent {
             />
           </div>
         }
-        onHandleClick={this.togglerContent}
         style={{
           zIndex: 999,
         }}

+ 17 - 17
src/components/SettingDrawer/index.less

@@ -1,16 +1,16 @@
 @import '~antd/lib/style/themes/default.less';
 
 .content {
+  position: relative;
   min-height: 100%;
   background: #fff;
-  position: relative;
 }
 
 .blockChecbox {
   display: flex;
   .item {
-    margin-right: 16px;
     position: relative;
+    margin-right: 16px;
     // box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
     border-radius: @border-radius-base;
     cursor: pointer;
@@ -23,52 +23,52 @@
     top: 0;
     right: 0;
     width: 100%;
+    height: 100%;
     padding-top: 15px;
     padding-left: 24px;
-    height: 100%;
     color: @primary-color;
-    font-size: 14px;
     font-weight: bold;
+    font-size: 14px;
   }
 }
 
 .color_block {
+  display: inline-block;
   width: 38px;
   height: 22px;
   margin: 4px;
-  border-radius: 4px;
-  cursor: pointer;
   margin-right: 12px;
-  display: inline-block;
   vertical-align: middle;
+  border-radius: 4px;
+  cursor: pointer;
 }
 
 .title {
-  font-size: 14px;
+  margin-bottom: 12px;
   color: @heading-color;
+  font-size: 14px;
   line-height: 22px;
-  margin-bottom: 12px;
 }
 
 .handle {
   position: absolute;
   top: 240px;
-  background: @primary-color;
-  width: 48px;
-  height: 48px;
   right: 300px;
+  z-index: 0;
   display: flex;
   justify-content: center;
   align-items: center;
-  cursor: pointer;
-  pointer-events: auto;
-  z-index: 0;
-  text-align: center;
+  width: 48px;
+  height: 48px;
   font-size: 16px;
+  text-align: center;
+  background: @primary-color;
   border-radius: 4px 0 0 4px;
+  cursor: pointer;
+  pointer-events: auto;
 }
 
 .productionHint {
-  font-size: 12px;
   margin-top: 16px;
+  font-size: 12px;
 }

+ 10 - 5
src/components/SiderMenu/BaseMenu.js

@@ -6,18 +6,23 @@ import { urlToList } from '../_utils/pathTools';
 import { getMenuMatches } from './SiderMenuUtils';
 import { isUrl } from '@/utils/utils';
 import styles from './index.less';
+import IconFont from '@/components/IconFont';
 
 const { SubMenu } = Menu;
 
 // Allow menu.js config icon as string or ReactNode
 //   icon: 'setting',
+//   icon: 'icon-geren' #For Iconfont ,
 //   icon: 'http://demo.com/icon.png',
 //   icon: <Icon type="setting" />,
 const getIcon = icon => {
-  if (typeof icon === 'string' && isUrl(icon)) {
-    return <img src={icon} alt="icon" className={styles.icon} />;
-  }
   if (typeof icon === 'string') {
+    if (isUrl(icon)) {
+      return <Icon component={() => <img src={icon} alt="icon" className={styles.icon} />} />;
+    }
+    if (icon.startsWith('icon-')) {
+      return <IconFont type={icon} />;
+    }
     return <Icon type={icon} />;
   }
   return icon;
@@ -28,13 +33,13 @@ export default class BaseMenu extends PureComponent {
    * 获得菜单子节点
    * @memberof SiderMenu
    */
-  getNavMenuItems = (menusData, parent) => {
+  getNavMenuItems = menusData => {
     if (!menusData) {
       return [];
     }
     return menusData
       .filter(item => item.name && !item.hideInMenu)
-      .map(item => this.getSubMenuOrItem(item, parent))
+      .map(item => this.getSubMenuOrItem(item))
       .filter(item => item);
   };
 

+ 18 - 6
src/components/SiderMenu/SiderMenu.js

@@ -5,10 +5,13 @@ import Link from 'umi/link';
 import styles from './index.less';
 import PageLoading from '../PageLoading';
 import { getDefaultCollapsedSubMenus } from './SiderMenuUtils';
+import { title } from '../../defaultSettings';
 
 const BaseMenu = React.lazy(() => import('./BaseMenu'));
 const { Sider } = Layout;
 
+let firstMount = true;
+
 export default class SiderMenu extends PureComponent {
   constructor(props) {
     super(props);
@@ -17,11 +20,16 @@ export default class SiderMenu extends PureComponent {
     };
   }
 
+  componentDidMount() {
+    firstMount = false;
+  }
+
   static getDerivedStateFromProps(props, state) {
-    const { pathname } = state;
-    if (props.location.pathname !== pathname) {
+    const { pathname, flatMenuKeysLen } = state;
+    if (props.location.pathname !== pathname || props.flatMenuKeys.length !== flatMenuKeysLen) {
       return {
         pathname: props.location.pathname,
+        flatMenuKeysLen: props.flatMenuKeys.length,
         openKeys: getDefaultCollapsedSubMenus(props),
       };
     }
@@ -46,12 +54,12 @@ export default class SiderMenu extends PureComponent {
   };
 
   render() {
-    const { logo, collapsed, onCollapse, fixSiderbar, theme } = this.props;
+    const { logo, collapsed, onCollapse, fixSiderbar, theme, isMobile } = this.props;
     const { openKeys } = this.state;
     const defaultProps = collapsed ? {} : { openKeys };
 
     const siderClassName = classNames(styles.sider, {
-      [styles.fixSiderbar]: fixSiderbar,
+      [styles.fixSiderBar]: fixSiderbar,
       [styles.light]: theme === 'light',
     });
     return (
@@ -60,7 +68,11 @@ export default class SiderMenu extends PureComponent {
         collapsible
         collapsed={collapsed}
         breakpoint="lg"
-        onCollapse={onCollapse}
+        onCollapse={collapse => {
+          if (firstMount || !isMobile) {
+            onCollapse(collapse);
+          }
+        }}
         width={256}
         theme={theme}
         className={siderClassName}
@@ -68,7 +80,7 @@ export default class SiderMenu extends PureComponent {
         <div className={styles.logo} id="logo">
           <Link to="/">
             <img src={logo} alt="logo" />
-            <h1>Ant Design Pro</h1>
+            <h1>{title}</h1>
           </Link>
         </div>
         <Suspense fallback={<PageLoading />}>

+ 2 - 1
src/components/SiderMenu/SiderMenuUtils.js

@@ -35,5 +35,6 @@ export const getDefaultCollapsedSubMenus = props => {
   } = props;
   return urlToList(pathname)
     .map(item => getMenuMatches(flatMenuKeys, item)[0])
-    .filter(item => item);
+    .filter(item => item)
+    .reduce((acc, curr) => [...acc, curr], ['/']);
 };

+ 29 - 20
src/components/SiderMenu/index.less

@@ -3,46 +3,55 @@
 @nav-header-height: @layout-header-height;
 
 .logo {
-  height: @nav-header-height;
   position: relative;
-  line-height: @nav-header-height;
+  height: @nav-header-height;
   padding-left: (@menu-collapsed-width - 32px) / 2;
-  transition: all 0.3s;
-  background: #002140;
   overflow: hidden;
+  line-height: @nav-header-height;
+  background: #002140;
+  transition: all 0.3s;
   img {
     display: inline-block;
-    vertical-align: middle;
     height: 32px;
+    vertical-align: middle;
   }
   h1 {
-    color: white;
     display: inline-block;
-    vertical-align: middle;
-    font-size: 20px;
     margin: 0 0 0 12px;
-    font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
+    color: white;
     font-weight: 600;
+    font-size: 20px;
+    font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
+    vertical-align: middle;
   }
 }
-
 .sider {
-  min-height: 100vh;
-  box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
   position: relative;
   z-index: 10;
-  &.fixSiderbar {
+  min-height: 100vh;
+  box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
+  &.fixSiderBar {
     position: fixed;
     top: 0;
     left: 0;
-    :global(.ant-menu-root) {
-      overflow-y: auto;
-      height: ~'calc(100vh - @{nav-header-height})';
+    box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
+    :global {
+      .ant-menu-root {
+        height: ~'calc(100vh - @{nav-header-height})';
+        overflow-y: auto;
+      }
+      .ant-menu-inline {
+        border-right: 0;
+        .ant-menu-item,
+        .ant-menu-submenu-title {
+          width: 100%;
+        }
+      }
     }
   }
   &.light {
-    box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
     background-color: white;
+    box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
     .logo {
       background: white;
       box-shadow: 1px 1px 0 0 @border-color-split;
@@ -58,7 +67,7 @@
 
 .icon {
   width: 14px;
-  margin-right: 10px;
+  vertical-align: baseline;
 }
 
 :global {
@@ -78,15 +87,15 @@
       .sider-menu-item-img
       + span,
     & > .ant-menu-submenu > .ant-menu-submenu-title .sider-menu-item-img + span {
-      max-width: 0;
       display: inline-block;
+      max-width: 0;
       opacity: 0;
     }
   }
   .ant-menu-item .sider-menu-item-img + span,
   .ant-menu-submenu-title .sider-menu-item-img + span {
-    transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out;
     opacity: 1;
+    transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out;
   }
   .ant-drawer-left {
     .ant-drawer-body {

+ 2 - 1
src/components/TopNavHeader/index.js

@@ -4,6 +4,7 @@ import RightContent from '../GlobalHeader/RightContent';
 import BaseMenu from '../SiderMenu/BaseMenu';
 import { getFlatMenuKeys } from '../SiderMenu/SiderMenuUtils';
 import styles from './index.less';
+import { title } from '../../defaultSettings';
 
 export default class TopNavHeader extends PureComponent {
   state = {
@@ -32,7 +33,7 @@ export default class TopNavHeader extends PureComponent {
             <div className={styles.logo} key="logo" id="logo">
               <Link to="/">
                 <img src={logo} alt="logo" />
-                <h1>Ant Design Pro</h1>
+                <h1>{title}</h1>
               </Link>
             </div>
             <div

+ 11 - 11
src/components/TopNavHeader/index.less

@@ -1,15 +1,15 @@
 @import '~antd/lib/style/themes/default.less';
 
 .head {
+  position: relative;
   width: 100%;
-  transition: background 0.3s, width 0.2s;
   height: @layout-header-height;
   box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
-  position: relative;
+  transition: background 0.3s, width 0.2s;
   :global {
     .ant-menu-submenu.ant-menu-submenu-horizontal {
-      line-height: @layout-header-height;
       height: 100%;
+      line-height: @layout-header-height;
       .ant-menu-submenu-title {
         height: 100%;
       }
@@ -28,8 +28,8 @@
       padding-left: 0;
     }
     .left {
-      flex: 1;
       display: flex;
+      flex: 1;
     }
     .right {
       width: 324px;
@@ -38,24 +38,24 @@
 }
 
 .logo {
+  position: relative;
   width: 165px;
   height: @layout-header-height;
-  position: relative;
+  overflow: hidden;
   line-height: @layout-header-height;
   transition: all 0.3s;
-  overflow: hidden;
   img {
     display: inline-block;
-    vertical-align: middle;
     height: 32px;
+    vertical-align: middle;
   }
   h1 {
-    color: #fff;
     display: inline-block;
-    vertical-align: top;
-    font-size: 16px;
     margin: 0 0 0 12px;
+    color: #fff;
     font-weight: 400;
+    font-size: 16px;
+    vertical-align: top;
   }
 }
 
@@ -66,7 +66,7 @@
 }
 
 .menu {
-  border: none;
   height: @layout-header-height;
   line-height: @layout-header-height;
+  border: none;
 }

+ 8 - 0
src/defaultSettings.js

@@ -6,5 +6,13 @@ module.exports = {
   fixedHeader: false, // sticky header
   autoHideHeader: false, // auto hide header
   fixSiderbar: false, // sticky siderbar
+  menu: {
+    disableLocal: false,
+  },
   title: 'Ant Design Pro',
+  pwa: true,
+  // your iconfont Symbol Scrip Url
+  // eg://at.alicdn.com/t/font_1039637_btcrd5co4w.js
+  // 注意:如果需要图标多色,Iconfont图标项目里要进行批量去色处理
+  iconfontUrl: '',
 };

+ 54 - 47
src/global.js

@@ -1,54 +1,61 @@
 import React from 'react';
 import { notification, Button, message } from 'antd';
 import { formatMessage } from 'umi/locale';
+import defaultSettings from './defaultSettings';
 
-// Notify user if offline now
-window.addEventListener('sw.offline', () => {
-  message.warning(formatMessage({ id: 'app.pwa.offline' }));
-});
+window.React = React;
 
-// 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]);
+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 = (
+      <Button
+        type="primary"
+        onClick={() => {
+          notification.close(key);
+          reloadSW();
+        }}
+      >
+        {formatMessage({ id: 'app.pwa.serviceworker.updated.ok' })}
+      </Button>
+    );
+    notification.open({
+      message: formatMessage({ id: 'app.pwa.serviceworker.updated' }),
+      description: formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
+      btn,
+      key,
+      onClose: async () => {},
     });
-    // 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 = (
-    <Button
-      type="primary"
-      onClick={() => {
-        notification.close(key);
-        reloadSW();
-      }}
-    >
-      {formatMessage({ id: 'app.pwa.serviceworker.updated.ok' })}
-    </Button>
-  );
-  notification.open({
-    message: formatMessage({ id: 'app.pwa.serviceworker.updated' }),
-    description: formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
-    btn,
-    key,
-    onClose: async () => {},
   });
-});
+}

+ 19 - 0
src/global.less

@@ -1,3 +1,5 @@
+@import '~antd/lib/style/themes/default.less';
+
 html,
 body,
 #root {
@@ -31,3 +33,20 @@ 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;
+        }
+      }
+    }
+  }
+}

+ 7 - 66
src/layouts/BasicLayout.js

@@ -1,22 +1,17 @@
 import React, { Suspense } from 'react';
 import { Layout } from 'antd';
 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 PageLoading from '@/components/PageLoading';
 import SiderMenu from '@/components/SiderMenu';
-import { title } from '../defaultSettings';
+import getPageTitle from '@/utils/getPageTitle';
 import styles from './BasicLayout.less';
 
 // lazy load SettingDrawer
@@ -49,13 +44,7 @@ const query = {
   },
 };
 
-class BasicLayout extends React.PureComponent {
-  constructor(props) {
-    super(props);
-    this.getPageTitle = memoizeOne(this.getPageTitle);
-    this.matchParamsPath = memoizeOne(this.matchParamsPath, isEqual);
-  }
-
+class BasicLayout extends React.Component {
   componentDidMount() {
     const {
       dispatch,
@@ -73,15 +62,6 @@ class BasicLayout extends React.PureComponent {
     });
   }
 
-  componentDidUpdate(preProps) {
-    // After changing to phone mode,
-    // if collapsed is true, you need to click twice to display
-    const { collapsed, isMobile } = this.props;
-    if (isMobile && !preProps.isMobile && !collapsed) {
-      this.handleMenuCollapse(false);
-    }
-  }
-
   getContext() {
     const { location, breadcrumbNameMap } = this.props;
     return {
@@ -90,41 +70,6 @@ class BasicLayout extends React.PureComponent {
     };
   }
 
-  matchParamsPath = (pathname, breadcrumbNameMap) => {
-    const pathKey = Object.keys(breadcrumbNameMap).find(key => pathToRegexp(key).test(pathname));
-    return breadcrumbNameMap[pathKey];
-  };
-
-  getRouterAuthority = (pathname, routeData) => {
-    let routeAuthority = ['noAuthority'];
-    const getAuthority = (key, routes) => {
-      routes.forEach(route => {
-        if (route.path && pathToRegexp(route.path).test(key)) {
-          routeAuthority = route.authority;
-        } else if (route.routes) {
-          routeAuthority = getAuthority(key, route.routes);
-        }
-        return route;
-      });
-      return routeAuthority;
-    };
-    return getAuthority(pathname, routeData);
-  };
-
-  getPageTitle = (pathname, breadcrumbNameMap) => {
-    const currRouterData = this.matchParamsPath(pathname, breadcrumbNameMap);
-
-    if (!currRouterData) {
-      return title;
-    }
-    const pageName = 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) {
@@ -161,12 +106,10 @@ class BasicLayout extends React.PureComponent {
       isMobile,
       menuData,
       breadcrumbNameMap,
-      route: { routes },
       fixedHeader,
     } = this.props;
 
     const isTop = PropsLayout === 'topmenu';
-    const routerConfig = this.getRouterAuthority(pathname, routes);
     const contentStyle = !fixedHeader ? { paddingTop: 0 } : {};
     const layout = (
       <Layout>
@@ -194,9 +137,7 @@ class BasicLayout extends React.PureComponent {
             {...this.props}
           />
           <Content className={styles.content} style={contentStyle}>
-            <Authorized authority={routerConfig} noMatch={<p>Exception403</p>}>
-              {children}
-            </Authorized>
+            {children}
           </Content>
           <Footer />
         </Layout>
@@ -204,7 +145,7 @@ class BasicLayout extends React.PureComponent {
     );
     return (
       <React.Fragment>
-        <DocumentTitle title={this.getPageTitle(pathname, breadcrumbNameMap)}>
+        <DocumentTitle title={getPageTitle(pathname, breadcrumbNameMap)}>
           <ContainerQuery query={query}>
             {params => (
               <Context.Provider value={this.getContext()}>
@@ -219,11 +160,11 @@ class BasicLayout extends React.PureComponent {
   }
 }
 
-export default connect(({ global, setting, menu }) => ({
+export default connect(({ global, setting, menu: menuModel }) => ({
   collapsed: global.collapsed,
   layout: setting.layout,
-  menuData: menu.menuData,
-  breadcrumbNameMap: menu.breadcrumbNameMap,
+  menuData: menuModel.menuData,
+  breadcrumbNameMap: menuModel.breadcrumbNameMap,
   ...setting,
 }))(props => (
   <Media query="(max-width: 599px)">

+ 1 - 1
src/layouts/BlankLayout.js

@@ -1,3 +1,3 @@
 import React from 'react';
 
-export default props => <div {...props} />;
+export default ({ children }) => <div>{children}</div>;

+ 2 - 3
src/layouts/Header.js

@@ -1,4 +1,4 @@
-import React, { PureComponent } from 'react';
+import React, { Component } from 'react';
 import { formatMessage } from 'umi/locale';
 import { Layout, message } from 'antd';
 import Animate from 'rc-animate';
@@ -10,7 +10,7 @@ import styles from './Header.less';
 
 const { Header } = Layout;
 
-class HeaderView extends PureComponent {
+class HeaderView extends Component {
   state = {
     visible: true,
   };
@@ -155,7 +155,6 @@ export default connect(({ user, global, setting, loading }) => ({
   collapsed: global.collapsed,
   fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
   fetchingNotices: loading.effects['global/fetchNotices'],
-  loadedAllNotices: global.loadedAllNotices,
   notices: global.notices,
   setting,
 }))(HeaderView);

+ 1 - 1
src/layouts/Header.less

@@ -2,7 +2,7 @@
   position: fixed;
   top: 0;
   right: 0;
-  width: 100%;
   z-index: 9;
+  width: 100%;
   transition: width 0.2s;
 }

+ 41 - 30
src/layouts/UserLayout.js

@@ -1,11 +1,14 @@
-import React, { Fragment } from 'react';
+import React, { Component, Fragment } from 'react';
 import { formatMessage } from 'umi/locale';
+import { connect } from 'dva';
 import Link from 'umi/link';
 import { Icon } from 'antd';
 import { GlobalFooter } from 'ant-design-pro';
+import DocumentTitle from 'react-document-title';
 import SelectLang from '@/components/SelectLang';
 import styles from './UserLayout.less';
 import logo from '../assets/logo.svg';
+import getPageTitle from '@/utils/getPageTitle';
 
 const links = [
   {
@@ -31,42 +34,50 @@ const copyright = (
   </Fragment>
 );
 
-class UserLayout extends React.PureComponent {
-  // @TODO title
-  // getPageTitle() {
-  //   const { routerData, location } = this.props;
-  //   const { pathname } = location;
-  //   let title = 'Ant Design Pro';
-  //   if (routerData[pathname] && routerData[pathname].name) {
-  //     title = `${routerData[pathname].name} - Ant Design Pro`;
-  //   }
-  //   return title;
-  // }
+class UserLayout extends Component {
+  componentDidMount() {
+    const {
+      dispatch,
+      route: { routes, authority },
+    } = this.props;
+    dispatch({
+      type: 'menu/getMenuData',
+      payload: { routes, authority },
+    });
+  }
 
   render() {
-    const { children } = this.props;
+    const {
+      children,
+      location: { pathname },
+      breadcrumbNameMap,
+    } = this.props;
     return (
-      // @TODO <DocumentTitle title={this.getPageTitle()}>
-      <div className={styles.container}>
-        <div className={styles.lang}>
-          <SelectLang />
-        </div>
-        <div className={styles.content}>
-          <div className={styles.top}>
-            <div className={styles.header}>
-              <Link to="/">
-                <img alt="logo" className={styles.logo} src={logo} />
-                <span className={styles.title}>Ant Design</span>
-              </Link>
+      <DocumentTitle title={getPageTitle(pathname, breadcrumbNameMap)}>
+        <div className={styles.container}>
+          <div className={styles.lang}>
+            <SelectLang />
+          </div>
+          <div className={styles.content}>
+            <div className={styles.top}>
+              <div className={styles.header}>
+                <Link to="/">
+                  <img alt="logo" className={styles.logo} src={logo} />
+                  <span className={styles.title}>Ant Design</span>
+                </Link>
+              </div>
+              <div className={styles.desc}>Ant Design 是西湖区最具影响力的 Web 设计规范</div>
             </div>
-            <div className={styles.desc}>Ant Design 是西湖区最具影响力的 Web 设计规范</div>
+            {children}
           </div>
-          {children}
+          <GlobalFooter links={links} copyright={copyright} />
         </div>
-        <GlobalFooter links={links} copyright={copyright} />
-      </div>
+      </DocumentTitle>
     );
   }
 }
 
-export default UserLayout;
+export default connect(({ menu: menuModel }) => ({
+  menuData: menuModel.menuData,
+  breadcrumbNameMap: menuModel.breadcrumbNameMap,
+}))(UserLayout);

+ 9 - 9
src/layouts/UserLayout.less

@@ -9,18 +9,18 @@
 }
 
 .lang {
-  text-align: right;
   width: 100%;
   height: 40px;
   line-height: 44px;
+  text-align: right;
   :global(.ant-dropdown-trigger) {
     margin-right: 24px;
   }
 }
 
 .content {
-  padding: 32px 0;
   flex: 1;
+  padding: 32px 0;
 }
 
 @media (min-width: @screen-md-min) {
@@ -50,22 +50,22 @@
 
 .logo {
   height: 44px;
-  vertical-align: top;
   margin-right: 16px;
+  vertical-align: top;
 }
 
 .title {
-  font-size: 33px;
-  color: @heading-color;
-  font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
-  font-weight: 600;
   position: relative;
   top: 2px;
+  color: @heading-color;
+  font-weight: 600;
+  font-size: 33px;
+  font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
 }
 
 .desc {
-  font-size: @font-size-base;
-  color: @text-color-secondary;
   margin-top: 12px;
   margin-bottom: 40px;
+  color: @text-color-secondary;
+  font-size: @font-size-base;
 }

+ 2 - 0
src/locales/en-US.js

@@ -3,6 +3,7 @@ import menu from './en-US/menu';
 import settingDrawer from './en-US/settingDrawer';
 import settings from './en-US/settings';
 import pwa from './en-US/pwa';
+import component from './en-US/component';
 
 export default {
   'navBar.lang': 'Languages',
@@ -15,4 +16,5 @@ export default {
   ...settingDrawer,
   ...settings,
   ...pwa,
+  ...component,
 };

+ 5 - 0
src/locales/en-US/component.js

@@ -0,0 +1,5 @@
+export default {
+  'component.tagSelect.expand': 'Expand',
+  'component.tagSelect.collapse': 'Collapse',
+  'component.tagSelect.all': 'All',
+};

+ 1 - 2
src/locales/en-US/globalHeader.js

@@ -13,6 +13,5 @@ export default {
   'component.noticeIcon.clear': 'Clear',
   'component.noticeIcon.cleared': 'Cleared',
   'component.noticeIcon.empty': 'No notifications',
-  'component.noticeIcon.loaded': 'Loaded',
-  'component.noticeIcon.loading-more': 'Loading more',
+  'component.noticeIcon.view-more': 'View more',
 };

+ 3 - 3
src/locales/en-US/settings.js

@@ -25,14 +25,14 @@ export default {
   'app.settings.security.medium': 'Medium',
   'app.settings.security.weak': 'Weak',
   'app.settings.security.password': 'Account Password',
-  'app.settings.security.password-description': 'Current password strength',
+  'app.settings.security.password-description': 'Current password strength',
   'app.settings.security.phone': 'Security Phone',
-  'app.settings.security.phone-description': 'Bound phone',
+  'app.settings.security.phone-description': 'Bound phone',
   'app.settings.security.question': 'Security Question',
   'app.settings.security.question-description':
     'The security question is not set, and the security policy can effectively protect the account security',
   'app.settings.security.email': 'Backup Email',
-  'app.settings.security.email-description': 'Bound Email',
+  'app.settings.security.email-description': 'Bound Email',
   'app.settings.security.mfa': 'MFA Device',
   'app.settings.security.mfa-description':
     'Unbound MFA device, after binding, can be confirmed twice',

+ 2 - 0
src/locales/pt-BR.js

@@ -3,6 +3,7 @@ import menu from './pt-BR/menu';
 import settingDrawer from './pt-BR/settingDrawer';
 import settings from './pt-BR/settings';
 import pwa from './pt-BR/pwa';
+import component from './pt-BR/component';
 
 export default {
   'navBar.lang': 'Idiomas',
@@ -15,4 +16,5 @@ export default {
   ...settingDrawer,
   ...settings,
   ...pwa,
+  ...component,
 };

+ 5 - 0
src/locales/pt-BR/component.js

@@ -0,0 +1,5 @@
+export default {
+  'component.tagSelect.expand': 'Expandir',
+  'component.tagSelect.collapse': 'Diminuir',
+  'component.tagSelect.all': 'Todas',
+};

+ 1 - 1
src/locales/pt-BR/globalHeader.js

@@ -14,5 +14,5 @@ export default {
   'component.noticeIcon.cleared': 'Limpo',
   'component.noticeIcon.empty': 'Sem notificações',
   'component.noticeIcon.loaded': 'Carregado',
-  'component.noticeIcon.loading-more': 'Carregar mais',
+  'component.noticeIcon.view-more': 'Veja mais',
 };

+ 2 - 0
src/locales/zh-CN.js

@@ -3,6 +3,7 @@ import menu from './zh-CN/menu';
 import settingDrawer from './zh-CN/settingDrawer';
 import settings from './zh-CN/settings';
 import pwa from './zh-CN/pwa';
+import component from './zh-CN/component';
 
 export default {
   'navBar.lang': '语言',
@@ -15,4 +16,5 @@ export default {
   ...settingDrawer,
   ...settings,
   ...pwa,
+  ...component,
 };

+ 5 - 0
src/locales/zh-CN/component.js

@@ -0,0 +1,5 @@
+export default {
+  'component.tagSelect.expand': '展开',
+  'component.tagSelect.collapse': '收起',
+  'component.tagSelect.all': '全部',
+};

+ 1 - 2
src/locales/zh-CN/globalHeader.js

@@ -13,6 +13,5 @@ export default {
   'component.noticeIcon.clear': '清空',
   'component.noticeIcon.cleared': '清空了',
   'component.noticeIcon.empty': '暂无数据',
-  'component.noticeIcon.loaded': '加载完毕',
-  'component.noticeIcon.loading-more': '加载更多',
+  'component.noticeIcon.view-more': '查看更多',
 };

+ 3 - 3
src/locales/zh-CN/settings.js

@@ -25,13 +25,13 @@ export default {
   'app.settings.security.medium': '中',
   'app.settings.security.weak': '弱',
   'app.settings.security.password': '账户密码',
-  'app.settings.security.password-description': '当前密码强度',
+  'app.settings.security.password-description': '当前密码强度',
   'app.settings.security.phone': '密保手机',
-  'app.settings.security.phone-description': '已绑定手机',
+  'app.settings.security.phone-description': '已绑定手机',
   'app.settings.security.question': '密保问题',
   'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全',
   'app.settings.security.email': '备用邮箱',
-  'app.settings.security.email-description': '已绑定邮箱',
+  'app.settings.security.email-description': '已绑定邮箱',
   'app.settings.security.mfa': 'MFA 设备',
   'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认',
   'app.settings.security.modify': '修改',

+ 2 - 0
src/locales/zh-TW.js

@@ -3,6 +3,7 @@ import menu from './zh-TW/menu';
 import settingDrawer from './zh-TW/settingDrawer';
 import settings from './zh-TW/settings';
 import pwa from './zh-TW/pwa';
+import component from './zh-TW/component';
 
 export default {
   'navBar.lang': '語言',
@@ -18,4 +19,5 @@ export default {
   ...settingDrawer,
   ...settings,
   ...pwa,
+  ...component,
 };

+ 5 - 0
src/locales/zh-TW/component.js

@@ -0,0 +1,5 @@
+export default {
+  'component.tagSelect.expand': '展開',
+  'component.tagSelect.collapse': '收起',
+  'component.tagSelect.all': '全部',
+};

+ 3 - 4
src/locales/zh-TW/globalHeader.js

@@ -3,7 +3,7 @@ export default {
   'component.globalHeader.search.example1': '搜索提示壹',
   'component.globalHeader.search.example2': '搜索提示二',
   'component.globalHeader.search.example3': '搜索提示三',
-  'component.globalHeader.help': '使用文檔',
+  'component.globalHeader.help': '使用手冊',
   'component.globalHeader.notification': '通知',
   'component.globalHeader.notification.empty': '妳已查看所有通知',
   'component.globalHeader.message': '消息',
@@ -12,7 +12,6 @@ export default {
   'component.globalHeader.event.empty': '妳已完成所有待辦',
   'component.noticeIcon.clear': '清空',
   'component.noticeIcon.cleared': '清空了',
-  'component.noticeIcon.empty': '暫無數據',
-  'component.noticeIcon.loaded': '加載完畢',
-  'component.noticeIcon.loading-more': '加載更多',
+  'component.noticeIcon.empty': '暫無資料',
+  'component.noticeIcon.view-more': '查看更多',
 };

+ 3 - 3
src/locales/zh-TW/settings.js

@@ -25,13 +25,13 @@ export default {
   'app.settings.security.medium': '中',
   'app.settings.security.weak': '弱',
   'app.settings.security.password': '賬戶密碼',
-  'app.settings.security.password-description': '當前密碼強度',
+  'app.settings.security.password-description': '當前密碼強度',
   'app.settings.security.phone': '密保手機',
-  'app.settings.security.phone-description': '已綁定手機',
+  'app.settings.security.phone-description': '已綁定手機',
   'app.settings.security.question': '密保問題',
   'app.settings.security.question-description': '未設置密保問題,密保問題可有效保護賬戶安全',
   'app.settings.security.email': '備用郵箱',
-  'app.settings.security.email-description': '已綁定郵箱',
+  'app.settings.security.email-description': '已綁定郵箱',
   'app.settings.security.mfa': 'MFA 設備',
   'app.settings.security.mfa-description': '未綁定 MFA 設備,綁定後,可以進行二次確認',
   'app.settings.security.modify': '修改',

+ 1 - 41
src/models/global.js

@@ -6,42 +6,14 @@ export default {
   state: {
     collapsed: false,
     notices: [],
-    loadedAllNotices: false,
   },
 
   effects: {
     *fetchNotices(_, { call, put, select }) {
       const data = yield call(queryNotices);
-      const loadedAllNotices = data && data.length && data[data.length - 1] === null;
-      yield put({
-        type: 'setLoadedStatus',
-        payload: loadedAllNotices,
-      });
       yield put({
         type: 'saveNotices',
-        payload: data.filter(item => item),
-      });
-      const unreadCount = yield select(
-        state => state.global.notices.filter(item => !item.read).length
-      );
-      yield put({
-        type: 'user/changeNotifyCount',
-        payload: {
-          totalCount: data.length,
-          unreadCount,
-        },
-      });
-    },
-    *fetchMoreNotices({ payload }, { call, put, select }) {
-      const data = yield call(queryNotices, payload);
-      const loadedAllNotices = data && data.length && data[data.length - 1] === null;
-      yield put({
-        type: 'setLoadedStatus',
-        payload: loadedAllNotices,
-      });
-      yield put({
-        type: 'pushNotices',
-        payload: data.filter(item => item),
+        payload: data,
       });
       const unreadCount = yield select(
         state => state.global.notices.filter(item => !item.read).length
@@ -114,18 +86,6 @@ export default {
         notices: state.notices.filter(item => item.type !== payload),
       };
     },
-    pushNotices(state, { payload }) {
-      return {
-        ...state,
-        notices: [...state.notices, ...payload],
-      };
-    },
-    setLoadedStatus(state, { payload }) {
-      return {
-        ...state,
-        loadedAllNotices: payload,
-      };
-    },
   },
 
   subscriptions: {

+ 12 - 5
src/models/menu.js

@@ -2,6 +2,7 @@ import memoizeOne from 'memoize-one';
 import isEqual from 'lodash/isEqual';
 import { formatMessage } from 'umi/locale';
 import Authorized from '@/utils/Authorized';
+import { menu } from '../defaultSettings';
 
 const { check } = Authorized;
 
@@ -19,10 +20,14 @@ function formatter(data, parentAuthority, parentName) {
       } else {
         locale = `menu.${item.name}`;
       }
-
+      // if enableMenuLocale use item.name,
+      // close menu international
+      const name = menu.disableLocal
+        ? item.name
+        : formatMessage({ id: locale, defaultMessage: item.name });
       const result = {
         ...item,
-        name: formatMessage({ id: locale, defaultMessage: item.name }),
+        name,
         locale,
         authority: item.authority || parentAuthority,
       };
@@ -92,17 +97,19 @@ export default {
 
   state: {
     menuData: [],
+    routerData: [],
     breadcrumbNameMap: {},
   },
 
   effects: {
     *getMenuData({ payload }, { put }) {
       const { routes, authority } = payload;
-      const menuData = filterMenuData(memoizeOneFormatter(routes, authority));
-      const breadcrumbNameMap = memoizeOneGetBreadcrumbNameMap(menuData);
+      const originalMenuData = memoizeOneFormatter(routes, authority);
+      const menuData = filterMenuData(originalMenuData);
+      const breadcrumbNameMap = memoizeOneGetBreadcrumbNameMap(originalMenuData);
       yield put({
         type: 'save',
-        payload: { menuData, breadcrumbNameMap },
+        payload: { menuData, breadcrumbNameMap, routerData: routes },
       });
     },
   },

+ 40 - 9
src/pages/Authorized.js

@@ -1,13 +1,44 @@
 import React from 'react';
-import RenderAuthorized from 'ant-design-pro/lib/Authorized';
-import { getAuthority } from '@/utils/authority';
 import Redirect from 'umi/redirect';
+import pathToRegexp from 'path-to-regexp';
+import { connect } from 'dva';
+import Authorized from '@/utils/Authorized';
 
-const Authority = getAuthority();
-const Authorized = RenderAuthorized(Authority);
+function AuthComponent({ children, location, routerData, currentCuser }) {
+  const isLogin = currentCuser && currentCuser.name;
 
-export default ({ children }) => (
-  <Authorized authority={children.props.route.authority} noMatch={<Redirect to="/user/login" />}>
-    {children}
-  </Authorized>
-);
+  const getRouteAuthority = (pathname, routeData) => {
+    const routes = routeData.slice(); // clone
+
+    const getAuthority = (routeDatas, path) => {
+      let authorities;
+      routeDatas.forEach(route => {
+        // check partial route
+        if (pathToRegexp(`${route.path}(.*)`).test(path)) {
+          if (route.authority) {
+            authorities = route.authority;
+          }
+          // is exact route?
+          if (!pathToRegexp(route.path).test(path) && route.routes) {
+            authorities = getAuthority(route.routes, path);
+          }
+        }
+      });
+      return authorities;
+    };
+
+    return getAuthority(routes, pathname);
+  };
+  return (
+    <Authorized
+      authority={getRouteAuthority(location.pathname, routerData)}
+      noMatch={isLogin ? <Redirect to="/exception/403" /> : <Redirect to="/user/login" />}
+    >
+      {children}
+    </Authorized>
+  );
+}
+export default connect(({ menu: menuModel, user: userModel }) => ({
+  routerData: menuModel.routerData,
+  currentCuser: userModel.currentCuser,
+}))(AuthComponent);

+ 14 - 12
src/pages/document.ejs

@@ -1,15 +1,17 @@
 <!DOCTYPE html>
 <html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1">
-  <title>Ant Design Pro</title>
-  <link rel="icon" href="/favicon.png" type="image/x-icon">
-  <script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.data-set-0.9.6/dist/data-set.min.js"></script>
-</head>
-<body>
-  <noscript>Sorry, we need js to run correctly!</noscript>
-  <div id="root"></div>
-</body>
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
+    />
+    <title>Ant Design Pro</title>
+    <link rel="icon" href="/favicon.png" type="image/x-icon" />
+  </head>
+  <body>
+    <noscript>Sorry, we need js to run correctly!</noscript>
+    <div id="root"></div>
+  </body>
 </html>

+ 27 - 0
src/utils/getPageTitle.js

@@ -0,0 +1,27 @@
+import { formatMessage } from 'umi/locale';
+import pathToRegexp from 'path-to-regexp';
+import isEqual from 'lodash/isEqual';
+import memoizeOne from 'memoize-one';
+import { menu, title } from '../defaultSettings';
+
+export const matchParamsPath = (pathname, breadcrumbNameMap) => {
+  const pathKey = Object.keys(breadcrumbNameMap).find(key => pathToRegexp(key).test(pathname));
+  return breadcrumbNameMap[pathKey];
+};
+
+const getPageTitle = (pathname, breadcrumbNameMap) => {
+  const currRouterData = 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}`;
+};
+
+export default memoizeOne(getPageTitle, isEqual);

+ 17 - 17
src/utils/utils.less

@@ -1,34 +1,34 @@
 .textOverflow() {
   overflow: hidden;
+  white-space: nowrap;
   text-overflow: ellipsis;
   word-break: break-all;
-  white-space: nowrap;
 }
 
 .textOverflowMulti(@line: 3, @bg: #fff) {
-  overflow: hidden;
   position: relative;
-  line-height: 1.5em;
   max-height: @line * 1.5em;
-  text-align: justify;
   margin-right: -1em;
   padding-right: 1em;
-  &:before {
-    background: @bg;
-    content: '...';
-    padding: 0 1px;
+  overflow: hidden;
+  line-height: 1.5em;
+  text-align: justify;
+  &::before {
     position: absolute;
     right: 14px;
     bottom: 0;
+    padding: 0 1px;
+    background: @bg;
+    content: '...';
   }
-  &:after {
-    background: white;
-    content: '';
-    margin-top: 0.2em;
+  &::after {
     position: absolute;
     right: 14px;
     width: 1em;
     height: 1em;
+    margin-top: 0.2em;
+    background: white;
+    content: '';
   }
 }
 
@@ -36,15 +36,15 @@
 // ------------------------
 .clearfix() {
   zoom: 1;
-  &:before,
-  &:after {
+  &::before,
+  &::after {
     content: ' ';
     display: table;
   }
-  &:after {
+  &::after {
     clear: both;
-    visibility: hidden;
-    font-size: 0;
     height: 0;
+    font-size: 0;
+    visibility: hidden;
   }
 }

+ 5 - 1
tests/run-tests.js

@@ -24,7 +24,11 @@ startServer.on('exit', () => {
 console.log('Starting development server for e2e tests...');
 startServer.stdout.on('data', data => {
   console.log(data.toString());
-  if (!once && data.toString().indexOf('Compiled successfully') >= 0) {
+  // hack code , wait umi
+  if (
+    (!once && data.toString().indexOf('Compiled successfully') >= 0) ||
+    data.toString().indexOf('Theme generated successfully') >= 0
+  ) {
     // eslint-disable-next-line
     once = true;
     console.log('Development server is started, ready to run tests.');