Commit e0b20cb7 authored by Yu's avatar Yu Committed by 陈帅

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)

* 🆙 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

* 🐛 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
parent 0a9094ec
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
...@@ -24,7 +24,7 @@ module.exports = { ...@@ -24,7 +24,7 @@ module.exports = {
2, 2,
{ {
optionalDependencies: true, optionalDependencies: true,
devDependencies: ['**/tests/**.js', '/mock/**.js', '**/**.test.js'], devDependencies: ['**/tests/**.js', '/mock/**/**.js', '**/**.test.js'],
}, },
], ],
'jsx-a11y/no-noninteractive-element-interactions': 0, 'jsx-a11y/no-noninteractive-element-interactions': 0,
......
**/*.md **/*.md
**/*.svg **/*.svg
**/*.ejs
**/*.html
package.json package.json
.umi .umi
.umi-production .umi-production
{ {
"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": { "rules": {
"declaration-empty-line-before": null,
"no-descending-specificity": null, "no-descending-specificity": null,
"selector-pseudo-class-no-unknown": null, "plugin/declaration-block-no-ignored-properties": true
"selector-pseudo-element-colon-notation": null
} }
} }
...@@ -6,13 +6,12 @@ English | [简体中文](./README.zh-CN.md) | [Русский](./README.ru-RU.md ...@@ -6,13 +6,12 @@ English | [简体中文](./README.zh-CN.md) | [Русский](./README.ru-RU.md
An out-of-box UI solution for enterprise applications as a React boilerplate. An out-of-box UI solution for enterprise applications as a React boilerplate.
[![CircleCI Status](https://circleci.com/gh/ant-design/ant-design-pro.svg?style=svg)](https://circleci.com/gh/ant-design/ant-design-pro/) [![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/)
[![Build status](https://ci.appveyor.com/api/projects/status/67fxu2by3ibvqtat/branch/master?svg=true)](https://ci.appveyor.com/project/afc163/ant-design-pro/branch/master) [![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)
[![Dependencies](https://img.shields.io/david/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro) [![Dependencies](https://img.shields.io/david/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro)
[![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro?type=dev) [![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro?type=dev)
[![Gitter](https://img.shields.io/gitter/room/ant-design/pro-english.svg)](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)(🇺🇸) [![Gitter](https://img.shields.io/gitter/room/ant-design/pro-english.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D)](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Gitter](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)(🇨🇳) [![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png) ![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)
......
...@@ -6,11 +6,12 @@ ...@@ -6,11 +6,12 @@
UI-решение "из коробки" для корпоративных приложений как React boilerplate UI-решение "из коробки" для корпоративных приложений как React boilerplate
[![CircleCI Status](https://circleci.com/gh/ant-design/ant-design-pro.svg?style=svg)](https://circleci.com/gh/ant-design/ant-design-pro/) [![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/)
[![Build status](https://ci.appveyor.com/api/projects/status/67fxu2by3ibvqtat/branch/master?svg=true)](https://ci.appveyor.com/project/afc163/ant-design-pro/branch/master) [![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)
[![Dependencies](https://img.shields.io/david/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro) [![Dependencies](https://img.shields.io/david/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro)
[![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro?type=dev) [![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro?type=dev)
[![Gitter](https://badges.gitter.im/ant-design/ant-design-pro.svg)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitter](https://img.shields.io/gitter/room/ant-design/pro-english.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D)](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png) ![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)
......
...@@ -6,11 +6,11 @@ ...@@ -6,11 +6,11 @@
开箱即用的中台前端/设计解决方案。 开箱即用的中台前端/设计解决方案。
[![CircleCI Status](https://circleci.com/gh/ant-design/ant-design-pro.svg?style=svg)](https://circleci.com/gh/ant-design/ant-design-pro/) [![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/)
[![Build status](https://ci.appveyor.com/api/projects/status/67fxu2by3ibvqtat/branch/master?svg=true)](https://ci.appveyor.com/project/afc163/ant-design-pro/branch/master) [![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)
[![Dependencies](https://img.shields.io/david/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro) [![Dependencies](https://img.shields.io/david/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro)
[![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro?type=dev) [![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro?type=dev)
[![Gitter](https://badges.gitter.im/ant-design/ant-design-pro.svg)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png) ![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)
...@@ -84,8 +84,8 @@ $ npm start # 访问 http://localhost:8000 ...@@ -84,8 +84,8 @@ $ npm start # 访问 http://localhost:8000
```bash ```bash
# preview # preview
$ docker pull chenshuai2144/ant-design-pro $ docker pull antdesign/ant-design-pro
$ docker run -p 80:80 chenshuai2144/ant-design-pro $ docker run -p 80:80 antdesign/ant-design-pro
# open http://localhost # open http://localhost
# dev # dev
......
# 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
# 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
...@@ -4,6 +4,9 @@ import webpackPlugin from './plugin.config'; ...@@ -4,6 +4,9 @@ import webpackPlugin from './plugin.config';
import defaultSettings from '../src/defaultSettings'; import defaultSettings from '../src/defaultSettings';
import slash from 'slash2'; import slash from 'slash2';
const { pwa, primaryColor } = defaultSettings;
const { NODE_ENV, APP_TYPE, TEST } = process.env;
const plugins = [ const plugins = [
[ [
'umi-plugin-react', 'umi-plugin-react',
...@@ -20,6 +23,7 @@ const plugins = [ ...@@ -20,6 +23,7 @@ const plugins = [
dynamicImport: { dynamicImport: {
loadingComponent: './components/PageLoading/index', loadingComponent: './components/PageLoading/index',
webpackChunkName: true, webpackChunkName: true,
level: 3,
}, },
pwa: { pwa: {
workboxPluginMode: 'InjectManifest', workboxPluginMode: 'InjectManifest',
...@@ -51,7 +55,7 @@ const plugins = [ ...@@ -51,7 +55,7 @@ const plugins = [
// 针对 preview.pro.ant.design 的 GA 统计代码 // 针对 preview.pro.ant.design 的 GA 统计代码
// 业务上不需要这个 // 业务上不需要这个
if (process.env.APP_TYPE === 'site') { if (APP_TYPE === 'site') {
plugins.push([ plugins.push([
'umi-plugin-ga', 'umi-plugin-ga',
{ {
...@@ -64,7 +68,7 @@ export default { ...@@ -64,7 +68,7 @@ export default {
// add for transfer to umi // add for transfer to umi
plugins, plugins,
define: { define: {
APP_TYPE: process.env.APP_TYPE || '', APP_TYPE: APP_TYPE || '',
}, },
treeShaking: true, treeShaking: true,
targets: { targets: {
...@@ -96,10 +100,11 @@ export default { ...@@ -96,10 +100,11 @@ export default {
// Theme for antd // Theme for antd
// https://ant.design/docs/react/customize-theme-cn // https://ant.design/docs/react/customize-theme-cn
theme: { theme: {
'primary-color': defaultSettings.primaryColor, 'primary-color': primaryColor,
}, },
externals: { externals: {
'@antv/data-set': 'DataSet', '@antv/data-set': 'DataSet',
bizcharts: 'BizCharts',
}, },
// proxy: { // proxy: {
// '/server/api/': { // '/server/api/': {
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
"firebase-functions": "^2.1.0", "firebase-functions": "^2.1.0",
"mockjs": "^1.0.1-beta3", "mockjs": "^1.0.1-beta3",
"moment": "^2.22.2", "moment": "^2.22.2",
"path-to-regexp": "^2.2.1" "path-to-regexp": "^3.0.0"
}, },
"private": true "private": true
} }
// ps https://github.com/GoogleChrome/puppeteer/issues/3120 // ps https://github.com/GoogleChrome/puppeteer/issues/3120
module.exports = { module.exports = {
launch: { launch: {
headless: true, args: ['--disable-gpu', '--disable-dev-shm-usage', '--no-first-run', '--no-zygote'],
args: [
'--disable-gpu',
'--disable-dev-shm-usage',
'--disable-setuid-sandbox',
'--no-first-run',
'--no-sandbox',
'--no-zygote',
'--single-process',
],
}, },
}; };
const fakeNotices = [ const getNotices = (req, res) =>
{ res.json([
id: '000000001', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', id: '000000001',
title: '你收到了 14 份新周报', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
datetime: '2017-08-09', title: '你收到了 14 份新周报',
type: 'notification', datetime: '2017-08-09',
}, type: 'notification',
{ },
id: '000000002', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png', id: '000000002',
title: '你推荐的 曲妮妮 已通过第三轮面试', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
datetime: '2017-08-08', title: '你推荐的 曲妮妮 已通过第三轮面试',
type: 'notification', datetime: '2017-08-08',
}, type: 'notification',
{ },
id: '000000003', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png', id: '000000003',
title: '这种模板可以区分多种通知类型', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
datetime: '2017-08-07', title: '这种模板可以区分多种通知类型',
read: true, datetime: '2017-08-07',
type: 'notification', read: true,
}, type: 'notification',
{ },
id: '000000004', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', id: '000000004',
title: '左侧图标用于区分不同的类型', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
datetime: '2017-08-07', title: '左侧图标用于区分不同的类型',
type: 'notification', datetime: '2017-08-07',
}, type: 'notification',
{ },
id: '000000005', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', id: '000000005',
title: '内容不要超过两行字,超出时自动截断', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
datetime: '2017-08-07', title: '内容不要超过两行字,超出时自动截断',
type: 'notification', datetime: '2017-08-07',
}, type: 'notification',
{ },
id: '000000006', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', id: '000000006',
title: '曲丽丽 评论了你', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
description: '描述信息描述信息描述信息', title: '曲丽丽 评论了你',
datetime: '2017-08-07', description: '描述信息描述信息描述信息',
type: 'message', datetime: '2017-08-07',
clickClose: true, type: 'message',
}, clickClose: true,
{ },
id: '000000007', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', id: '000000007',
title: '朱偏右 回复了你', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', title: '朱偏右 回复了你',
datetime: '2017-08-07', description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
type: 'message', datetime: '2017-08-07',
clickClose: true, type: 'message',
}, clickClose: true,
{ },
id: '000000008', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', id: '000000008',
title: '标题', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', title: '标题',
datetime: '2017-08-07', description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
type: 'message', datetime: '2017-08-07',
clickClose: true, type: 'message',
}, clickClose: true,
{ },
id: '000000009', {
title: '任务名称', id: '000000009',
description: '任务需要在 2017-01-12 20:00 前启动', title: '任务名称',
extra: '未开始', description: '任务需要在 2017-01-12 20:00 前启动',
status: 'todo', extra: '未开始',
type: 'event', status: 'todo',
}, type: 'event',
{ },
id: '000000010', {
title: '第三方紧急代码变更', id: '000000010',
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', title: '第三方紧急代码变更',
extra: '马上到期', description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
status: 'urgent', extra: '马上到期',
type: 'event', status: 'urgent',
}, type: 'event',
{ },
id: '000000011', {
title: '信息安全考试', id: '000000011',
description: '指派竹尔于 2017-01-09 前完成更新并发布', title: '信息安全考试',
extra: '已耗时 8 天', description: '指派竹尔于 2017-01-09 前完成更新并发布',
status: 'doing', extra: '已耗时 8 天',
type: 'event', status: 'doing',
}, type: 'event',
{ },
id: '000000012', {
title: 'ABCD 版本发布', id: '000000012',
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', title: 'ABCD 版本发布',
extra: '进行中', description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
status: 'processing', extra: '进行中',
type: 'event', 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);
};
export default { export default {
'GET /api/notices': getNotices, 'GET /api/notices': getNotices,
......
...@@ -3,7 +3,7 @@ export default { ...@@ -3,7 +3,7 @@ export default {
// 支持值为 Object 和 Array // 支持值为 Object 和 Array
'GET /api/currentUser': { 'GET /api/currentUser': {
name: 'Serati Ma', name: 'Serati Ma',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
userid: '00000001', userid: '00000001',
email: 'antdesign@alipay.com', email: 'antdesign@alipay.com',
signature: '海纳百川,有容乃大', signature: '海纳百川,有容乃大',
......
{ {
"name": "ant-design-pro", "name": "ant-design-pro",
"version": "2.2.0", "version": "2.2.1",
"description": "An out-of-box UI solution for enterprise applications", "description": "An out-of-box UI solution for enterprise applications",
"private": true, "private": true,
"scripts": { "scripts": {
...@@ -11,10 +11,10 @@ ...@@ -11,10 +11,10 @@
"dev:no-mock": "cross-env MOCK=none umi dev", "dev:no-mock": "cross-env MOCK=none umi dev",
"build": "umi build", "build": "umi build",
"analyze": "cross-env ANALYZE=1 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:prettier": "check-prettier lint",
"lint": "eslint --ext .js src mock tests && npm run lint:style && npm run lint:prettier", "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": "lint-staged",
"lint-staged:js": "eslint --ext .js", "lint-staged:js": "eslint --ext .js",
"test": "umi test", "test": "umi test",
...@@ -50,46 +50,51 @@ ...@@ -50,46 +50,51 @@
"umi-request": "^1.0.0" "umi-request": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^16.7.7", "@types/react": "^16.8.1",
"@types/react-dom": "^16.0.10", "@types/react-dom": "^16.0.11",
"antd-pro-merge-less": "^1.0.0", "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", "babel-eslint": "^10.0.1",
"chalk": "^2.4.2",
"check-prettier": "^1.0.1", "check-prettier": "^1.0.1",
"cross-env": "^5.1.1", "cross-env": "^5.2.0",
"cross-port-killer": "^1.0.1", "cross-port-killer": "^1.0.1",
"enzyme": "3.7.0", "enzyme": "^3.9.0",
"eslint": "^5.4.0", "eslint": "^5.13.0",
"eslint-config-airbnb": "^17.0.0", "eslint-config-airbnb": "^17.1.0",
"eslint-config-prettier": "^3.0.1", "eslint-config-prettier": "^4.0.0",
"eslint-plugin-babel": "^5.3.0", "eslint-plugin-babel": "^5.3.0",
"eslint-plugin-compat": "^2.6.2", "eslint-plugin-compat": "^2.6.3",
"eslint-plugin-import": "^2.14.0", "eslint-plugin-import": "^2.16.0",
"eslint-plugin-jsx-a11y": "^6.1.2", "eslint-plugin-jsx-a11y": "^6.2.0",
"eslint-plugin-markdown": "^1.0.0-beta.6", "eslint-plugin-markdown": "^1.0.0",
"eslint-plugin-react": "^7.11.1", "eslint-plugin-react": "^7.12.4",
"husky": "^1.2.0", "gh-pages": "^2.0.1",
"jest-puppeteer": "^3.5.1", "husky": "^1.3.1",
"jest-puppeteer": "^4.0.0",
"less": "^3.9.0", "less": "^3.9.0",
"lint-staged": "^8.1.0", "lint-staged": "^8.1.1",
"merge-umi-mock-data": "^1.0.4", "merge-umi-mock-data": "^1.0.4",
"mockjs": "^1.0.1-beta3", "mockjs": "^1.0.1-beta3",
"prettier": "1.15.2", "prettier": "^1.16.4",
"pro-download": "^1.0.1",
"slash2": "^2.0.0", "slash2": "^2.0.0",
"stylelint": "^9.8.0", "stylelint": "^9.10.1",
"stylelint-config-prettier": "^4.0.0", "stylelint-config-css-modules": "^1.3.0",
"stylelint-config-standard": "^18.0.0", "stylelint-config-prettier": "^5.0.0",
"tslint": "^5.10.0", "stylelint-config-rational-order": "^0.0.4",
"tslint-config-prettier": "^1.10.0", "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", "tslint-react": "^3.6.0",
"umi": "^2.4.2", "umi": "^2.4.4",
"umi-plugin-ga": "^1.1.3", "umi-plugin-ga": "^1.1.3",
"umi-plugin-react": "^1.3.4", "umi-plugin-react": "^1.3.4",
"umi-plugin-pro-block": "^1.2.0" "umi-plugin-pro-block": "^1.2.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"puppeteer": "^1.10.0" "puppeteer": "^1.12.1"
}, },
"lint-staged": { "lint-staged": {
"**/*.{js,ts,tsx,json,jsx,less}": [ "**/*.{js,ts,tsx,json,jsx,less}": [
......
...@@ -8,11 +8,12 @@ export const dva = { ...@@ -8,11 +8,12 @@ export const dva = {
}, },
}; };
let authRoutes = null; let authRoutes = {};
function ergodicRoutes(routes, authKey, authority) { function ergodicRoutes(routes, authKey, authority) {
routes.forEach(element => { routes.forEach(element => {
if (element.path === authKey) { if (element.path === authKey) {
if (!element.authority) element.authority = []; // eslint-disable-line
Object.assign(element.authority, authority || []); Object.assign(element.authority, authority || []);
} else if (element.routes) { } else if (element.routes) {
ergodicRoutes(element.routes, authKey, authority); ergodicRoutes(element.routes, authKey, authority);
...@@ -31,8 +32,13 @@ export function patchRoutes(routes) { ...@@ -31,8 +32,13 @@ export function patchRoutes(routes) {
export function render(oldRender) { export function render(oldRender) {
fetch('/api/auth_routes') fetch('/api/auth_routes')
.then(res => res.json()) .then(res => res.json())
.then(ret => { .then(
authRoutes = ret; ret => {
oldRender(); authRoutes = ret;
}); oldRender();
},
() => {
oldRender();
}
);
} }
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { FormattedMessage, formatMessage } from 'umi/locale'; 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 moment from 'moment';
import groupBy from 'lodash/groupBy'; import groupBy from 'lodash/groupBy';
import { NoticeIcon } from 'ant-design-pro'; import { NoticeIcon } from 'ant-design-pro';
...@@ -63,30 +63,13 @@ export default class GlobalHeaderRight extends PureComponent { ...@@ -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() { render() {
const { const {
currentUser, currentUser,
fetchingMoreNotices,
fetchingNotices, fetchingNotices,
loadedAllNotices,
onNoticeVisibleChange, onNoticeVisibleChange,
onMenuClick, onMenuClick,
onNoticeClear, onNoticeClear,
skeletonCount,
theme, theme,
} = this.props; } = this.props;
const menu = ( const menu = (
...@@ -110,11 +93,6 @@ export default class GlobalHeaderRight extends PureComponent { ...@@ -110,11 +93,6 @@ export default class GlobalHeaderRight extends PureComponent {
</Menu.Item> </Menu.Item>
</Menu> </Menu>
); );
const loadMoreProps = {
skeletonCount,
loadedAll: loadedAllNotices,
loading: fetchingMoreNotices,
};
const noticeData = this.getNoticeData(); const noticeData = this.getNoticeData();
const unreadMsg = this.getUnreadData(noticeData); const unreadMsg = this.getUnreadData(noticeData);
let className = styles.right; let className = styles.right;
...@@ -155,44 +133,43 @@ export default class GlobalHeaderRight extends PureComponent { ...@@ -155,44 +133,43 @@ export default class GlobalHeaderRight extends PureComponent {
console.log(item, tabProps); // eslint-disable-line console.log(item, tabProps); // eslint-disable-line
this.changeReadState(item, tabProps); this.changeReadState(item, tabProps);
}} }}
loading={fetchingNotices}
locale={{ locale={{
emptyText: formatMessage({ id: 'component.noticeIcon.empty' }), emptyText: formatMessage({ id: 'component.noticeIcon.empty' }),
clear: formatMessage({ id: 'component.noticeIcon.clear' }), clear: formatMessage({ id: 'component.noticeIcon.clear' }),
loadedAll: formatMessage({ id: 'component.noticeIcon.loaded' }), viewMore: formatMessage({ id: 'component.noticeIcon.view-more' }),
loadMore: formatMessage({ id: 'component.noticeIcon.loading-more' }), notification: formatMessage({ id: 'component.globalHeader.notification' }),
message: formatMessage({ id: 'component.globalHeader.message' }),
event: formatMessage({ id: 'component.globalHeader.event' }),
}} }}
onClear={onNoticeClear} onClear={onNoticeClear}
onLoadMore={this.fetchMoreNotices}
onPopupVisibleChange={onNoticeVisibleChange} onPopupVisibleChange={onNoticeVisibleChange}
loading={fetchingNotices} onViewMore={() => message.info('Click on view more')}
clearClose clearClose
> >
<NoticeIcon.Tab <NoticeIcon.Tab
count={unreadMsg.notification} count={unreadMsg.notification}
list={noticeData.notification} list={noticeData.notification}
title={formatMessage({ id: 'component.globalHeader.notification' })} title="notification"
name="notification"
emptyText={formatMessage({ id: 'component.globalHeader.notification.empty' })} emptyText={formatMessage({ id: 'component.globalHeader.notification.empty' })}
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg" emptyImage="https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg"
{...loadMoreProps} showViewMore
/> />
<NoticeIcon.Tab <NoticeIcon.Tab
count={unreadMsg.message} count={unreadMsg.message}
list={noticeData.message} list={noticeData.message}
title={formatMessage({ id: 'component.globalHeader.message' })} title="message"
name="message"
emptyText={formatMessage({ id: 'component.globalHeader.message.empty' })} emptyText={formatMessage({ id: 'component.globalHeader.message.empty' })}
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg" emptyImage="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
{...loadMoreProps} showViewMore
/> />
<NoticeIcon.Tab <NoticeIcon.Tab
count={unreadMsg.event} count={unreadMsg.event}
list={noticeData.event} list={noticeData.event}
title={formatMessage({ id: 'component.globalHeader.event' })} title="event"
name="event"
emptyText={formatMessage({ id: 'component.globalHeader.event.empty' })} emptyText={formatMessage({ id: 'component.globalHeader.event.empty' })}
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg" emptyImage="https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg"
{...loadMoreProps} showViewMore
/> />
</NoticeIcon> </NoticeIcon>
{currentUser.name ? ( {currentUser.name ? (
......
...@@ -3,21 +3,21 @@ ...@@ -3,21 +3,21 @@
@pro-header-hover-bg: rgba(0, 0, 0, 0.025); @pro-header-hover-bg: rgba(0, 0, 0, 0.025);
.header { .header {
position: relative;
height: @layout-header-height; height: @layout-header-height;
padding: 0; padding: 0;
background: #fff; background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
position: relative;
} }
.logo { .logo {
display: inline-block;
height: @layout-header-height; height: @layout-header-height;
padding: 0 0 0 24px;
font-size: 20px;
line-height: @layout-header-height; line-height: @layout-header-height;
vertical-align: top; vertical-align: top;
display: inline-block;
padding: 0 0 0 24px;
cursor: pointer; cursor: pointer;
font-size: 20px;
img { img {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
...@@ -34,11 +34,11 @@ ...@@ -34,11 +34,11 @@
} }
.trigger { .trigger {
font-size: 20px;
height: @layout-header-height; height: @layout-header-height;
padding: ~'calc((@{layout-header-height} - 20px) / 2)' 24px;
font-size: 20px;
cursor: pointer; cursor: pointer;
transition: all 0.3s, padding 0s; transition: all 0.3s, padding 0s;
padding: ~'calc((@{layout-header-height} - 20px) / 2)' 24px;
&:hover { &:hover {
background: @pro-header-hover-bg; background: @pro-header-hover-bg;
} }
...@@ -49,14 +49,14 @@ ...@@ -49,14 +49,14 @@
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
.action { .action {
cursor: pointer;
padding: 0 12px;
display: inline-block; display: inline-block;
transition: all 0.3s;
height: 100%; height: 100%;
padding: 0 12px;
cursor: pointer;
transition: all 0.3s;
> i { > i {
vertical-align: middle;
color: @text-color; color: @text-color;
vertical-align: middle;
} }
&:hover { &:hover {
background: @pro-header-hover-bg; background: @pro-header-hover-bg;
...@@ -76,8 +76,8 @@ ...@@ -76,8 +76,8 @@
margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0; margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
margin-right: 8px; margin-right: 8px;
color: @primary-color; color: @primary-color;
background: rgba(255, 255, 255, 0.85);
vertical-align: top; vertical-align: top;
background: rgba(255, 255, 255, 0.85);
} }
} }
} }
...@@ -111,14 +111,14 @@ ...@@ -111,14 +111,14 @@
padding: 22px 12px; padding: 22px 12px;
} }
.logo { .logo {
padding-left: 12px;
padding-right: 12px;
position: relative; position: relative;
padding-right: 12px;
padding-left: 12px;
} }
.right { .right {
position: absolute; position: absolute;
right: 12px;
top: 0; top: 0;
right: 12px;
background: #fff; background: #fff;
.account { .account {
.avatar { .avatar {
......
import * as React from 'react';
export default class HeaderDropdown extends React.Component<any, any> {}
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
.container > * { .container > * {
background-color: #fff; background-color: #fff;
box-shadow: @shadow-1-down;
border-radius: 4px; border-radius: 4px;
box-shadow: @shadow-1-down;
} }
@media screen and (max-width: @screen-xs) { @media screen and (max-width: @screen-xs) {
......
--- ---
title: title: HeaderSearch
en-US: HeaderSearch subtitle:
zh-CN: HeaderSearch
subtitle: Top search box
cols: 1 cols: 1
order: 8 order: 8
--- ---
...@@ -21,4 +19,4 @@ onSelect | Called when a option is selected. param is option's value and option ...@@ -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) | - onPressEnter | Callback when pressing Enter | function(value) | -
onVisibleChange | Show or hide the callback of the text box | 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 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
\ No newline at end of file \ No newline at end of file
...@@ -2,21 +2,21 @@ ...@@ -2,21 +2,21 @@
.headerSearch { .headerSearch {
:global(.anticon-search) { :global(.anticon-search) {
cursor: pointer;
font-size: 16px; font-size: 16px;
cursor: pointer;
} }
.input { .input {
transition: width 0.3s, margin-left 0.3s;
width: 0; width: 0;
background: transparent; background: transparent;
border-radius: 0; border-radius: 0;
transition: width 0.3s, margin-left 0.3s;
:global(.ant-select-selection) { :global(.ant-select-selection) {
background: transparent; background: transparent;
} }
input { input {
border: 0;
padding-left: 0;
padding-right: 0; padding-right: 0;
padding-left: 0;
border: 0;
box-shadow: none !important; box-shadow: none !important;
} }
&, &,
......
--- ---
title: title: HeaderSearch
en-US: HeaderSearch
zh-CN: HeaderSearch
subtitle: 顶部搜索框 subtitle: 顶部搜索框
cols: 1 cols: 1
order: 8 order: 8
...@@ -21,4 +19,4 @@ onSelect | 被选中时调用,参数为选中项的 value 值 | function(value ...@@ -21,4 +19,4 @@ onSelect | 被选中时调用,参数为选中项的 value 值 | function(value
onPressEnter | 按下回车时的回调 | function(value) | - onPressEnter | 按下回车时的回调 | function(value) | -
onVisibleChange | 显示或隐藏文本框的回调 | function(value) |- onVisibleChange | 显示或隐藏文本框的回调 | function(value) |-
defaultOpen | 输入框首次显示是否显示 | boolean | false defaultOpen | 输入框首次显示是否显示 | boolean | false
open | 控制输入框是否显示 | booelan |false open | 控制输入框是否显示 | boolean |false
\ No newline at end of file \ No newline at end of file
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 });
...@@ -10,11 +10,11 @@ ...@@ -10,11 +10,11 @@
} }
.dropDown { .dropDown {
cursor: pointer;
vertical-align: top;
line-height: @layout-header-height; line-height: @layout-header-height;
vertical-align: top;
cursor: pointer;
> i { > i {
font-size: 14px !important; font-size: 16px !important;
transform: none !important; transform: none !important;
svg { svg {
position: relative; position: relative;
......
.themeColor { .themeColor {
overflow: hidden;
margin-top: 24px; margin-top: 24px;
overflow: hidden;
.title { .title {
font-size: 14px; margin-bottom: 12px;
color: rgba(0, 0, 0, 0.65); color: rgba(0, 0, 0, 0.65);
font-size: 14px;
line-height: 22px; line-height: 22px;
margin-bottom: 12px;
} }
.colorBlock { .colorBlock {
float: left;
width: 20px; width: 20px;
height: 20px; height: 20px;
border-radius: 2px;
float: left;
cursor: pointer;
margin-right: 8px; margin-right: 8px;
text-align: center;
color: #fff; color: #fff;
font-weight: bold; font-weight: bold;
text-align: center;
border-radius: 2px;
cursor: pointer;
} }
} }
...@@ -137,7 +137,7 @@ class SettingDrawer extends PureComponent { ...@@ -137,7 +137,7 @@ class SettingDrawer extends PureComponent {
onClose={this.togglerContent} onClose={this.togglerContent}
placement="right" placement="right"
handler={ handler={
<div className={styles.handle}> <div className={styles.handle} onClick={this.togglerContent}>
<Icon <Icon
type={collapse ? 'close' : 'setting'} type={collapse ? 'close' : 'setting'}
style={{ style={{
...@@ -147,7 +147,6 @@ class SettingDrawer extends PureComponent { ...@@ -147,7 +147,6 @@ class SettingDrawer extends PureComponent {
/> />
</div> </div>
} }
onHandleClick={this.togglerContent}
style={{ style={{
zIndex: 999, zIndex: 999,
}} }}
......
@import '~antd/lib/style/themes/default.less'; @import '~antd/lib/style/themes/default.less';
.content { .content {
position: relative;
min-height: 100%; min-height: 100%;
background: #fff; background: #fff;
position: relative;
} }
.blockChecbox { .blockChecbox {
display: flex; display: flex;
.item { .item {
margin-right: 16px;
position: relative; position: relative;
margin-right: 16px;
// box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); // box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
border-radius: @border-radius-base; border-radius: @border-radius-base;
cursor: pointer; cursor: pointer;
...@@ -23,52 +23,52 @@ ...@@ -23,52 +23,52 @@
top: 0; top: 0;
right: 0; right: 0;
width: 100%; width: 100%;
height: 100%;
padding-top: 15px; padding-top: 15px;
padding-left: 24px; padding-left: 24px;
height: 100%;
color: @primary-color; color: @primary-color;
font-size: 14px;
font-weight: bold; font-weight: bold;
font-size: 14px;
} }
} }
.color_block { .color_block {
display: inline-block;
width: 38px; width: 38px;
height: 22px; height: 22px;
margin: 4px; margin: 4px;
border-radius: 4px;
cursor: pointer;
margin-right: 12px; margin-right: 12px;
display: inline-block;
vertical-align: middle; vertical-align: middle;
border-radius: 4px;
cursor: pointer;
} }
.title { .title {
font-size: 14px; margin-bottom: 12px;
color: @heading-color; color: @heading-color;
font-size: 14px;
line-height: 22px; line-height: 22px;
margin-bottom: 12px;
} }
.handle { .handle {
position: absolute; position: absolute;
top: 240px; top: 240px;
background: @primary-color;
width: 48px;
height: 48px;
right: 300px; right: 300px;
z-index: 0;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
cursor: pointer; width: 48px;
pointer-events: auto; height: 48px;
z-index: 0;
text-align: center;
font-size: 16px; font-size: 16px;
text-align: center;
background: @primary-color;
border-radius: 4px 0 0 4px; border-radius: 4px 0 0 4px;
cursor: pointer;
pointer-events: auto;
} }
.productionHint { .productionHint {
font-size: 12px;
margin-top: 16px; margin-top: 16px;
font-size: 12px;
} }
...@@ -6,18 +6,23 @@ import { urlToList } from '../_utils/pathTools'; ...@@ -6,18 +6,23 @@ import { urlToList } from '../_utils/pathTools';
import { getMenuMatches } from './SiderMenuUtils'; import { getMenuMatches } from './SiderMenuUtils';
import { isUrl } from '@/utils/utils'; import { isUrl } from '@/utils/utils';
import styles from './index.less'; import styles from './index.less';
import IconFont from '@/components/IconFont';
const { SubMenu } = Menu; const { SubMenu } = Menu;
// Allow menu.js config icon as string or ReactNode // Allow menu.js config icon as string or ReactNode
// icon: 'setting', // icon: 'setting',
// icon: 'icon-geren' #For Iconfont ,
// icon: 'http://demo.com/icon.png', // icon: 'http://demo.com/icon.png',
// icon: <Icon type="setting" />, // icon: <Icon type="setting" />,
const getIcon = icon => { const getIcon = icon => {
if (typeof icon === 'string' && isUrl(icon)) {
return <img src={icon} alt="icon" className={styles.icon} />;
}
if (typeof icon === 'string') { 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 type={icon} />;
} }
return icon; return icon;
...@@ -28,13 +33,13 @@ export default class BaseMenu extends PureComponent { ...@@ -28,13 +33,13 @@ export default class BaseMenu extends PureComponent {
* 获得菜单子节点 * 获得菜单子节点
* @memberof SiderMenu * @memberof SiderMenu
*/ */
getNavMenuItems = (menusData, parent) => { getNavMenuItems = menusData => {
if (!menusData) { if (!menusData) {
return []; return [];
} }
return menusData return menusData
.filter(item => item.name && !item.hideInMenu) .filter(item => item.name && !item.hideInMenu)
.map(item => this.getSubMenuOrItem(item, parent)) .map(item => this.getSubMenuOrItem(item))
.filter(item => item); .filter(item => item);
}; };
......
...@@ -5,10 +5,13 @@ import Link from 'umi/link'; ...@@ -5,10 +5,13 @@ import Link from 'umi/link';
import styles from './index.less'; import styles from './index.less';
import PageLoading from '../PageLoading'; import PageLoading from '../PageLoading';
import { getDefaultCollapsedSubMenus } from './SiderMenuUtils'; import { getDefaultCollapsedSubMenus } from './SiderMenuUtils';
import { title } from '../../defaultSettings';
const BaseMenu = React.lazy(() => import('./BaseMenu')); const BaseMenu = React.lazy(() => import('./BaseMenu'));
const { Sider } = Layout; const { Sider } = Layout;
let firstMount = true;
export default class SiderMenu extends PureComponent { export default class SiderMenu extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
...@@ -17,11 +20,16 @@ export default class SiderMenu extends PureComponent { ...@@ -17,11 +20,16 @@ export default class SiderMenu extends PureComponent {
}; };
} }
componentDidMount() {
firstMount = false;
}
static getDerivedStateFromProps(props, state) { static getDerivedStateFromProps(props, state) {
const { pathname } = state; const { pathname, flatMenuKeysLen } = state;
if (props.location.pathname !== pathname) { if (props.location.pathname !== pathname || props.flatMenuKeys.length !== flatMenuKeysLen) {
return { return {
pathname: props.location.pathname, pathname: props.location.pathname,
flatMenuKeysLen: props.flatMenuKeys.length,
openKeys: getDefaultCollapsedSubMenus(props), openKeys: getDefaultCollapsedSubMenus(props),
}; };
} }
...@@ -46,12 +54,12 @@ export default class SiderMenu extends PureComponent { ...@@ -46,12 +54,12 @@ export default class SiderMenu extends PureComponent {
}; };
render() { render() {
const { logo, collapsed, onCollapse, fixSiderbar, theme } = this.props; const { logo, collapsed, onCollapse, fixSiderbar, theme, isMobile } = this.props;
const { openKeys } = this.state; const { openKeys } = this.state;
const defaultProps = collapsed ? {} : { openKeys }; const defaultProps = collapsed ? {} : { openKeys };
const siderClassName = classNames(styles.sider, { const siderClassName = classNames(styles.sider, {
[styles.fixSiderbar]: fixSiderbar, [styles.fixSiderBar]: fixSiderbar,
[styles.light]: theme === 'light', [styles.light]: theme === 'light',
}); });
return ( return (
...@@ -60,7 +68,11 @@ export default class SiderMenu extends PureComponent { ...@@ -60,7 +68,11 @@ export default class SiderMenu extends PureComponent {
collapsible collapsible
collapsed={collapsed} collapsed={collapsed}
breakpoint="lg" breakpoint="lg"
onCollapse={onCollapse} onCollapse={collapse => {
if (firstMount || !isMobile) {
onCollapse(collapse);
}
}}
width={256} width={256}
theme={theme} theme={theme}
className={siderClassName} className={siderClassName}
...@@ -68,7 +80,7 @@ export default class SiderMenu extends PureComponent { ...@@ -68,7 +80,7 @@ export default class SiderMenu extends PureComponent {
<div className={styles.logo} id="logo"> <div className={styles.logo} id="logo">
<Link to="/"> <Link to="/">
<img src={logo} alt="logo" /> <img src={logo} alt="logo" />
<h1>Ant Design Pro</h1> <h1>{title}</h1>
</Link> </Link>
</div> </div>
<Suspense fallback={<PageLoading />}> <Suspense fallback={<PageLoading />}>
......
...@@ -35,5 +35,6 @@ export const getDefaultCollapsedSubMenus = props => { ...@@ -35,5 +35,6 @@ export const getDefaultCollapsedSubMenus = props => {
} = props; } = props;
return urlToList(pathname) return urlToList(pathname)
.map(item => getMenuMatches(flatMenuKeys, item)[0]) .map(item => getMenuMatches(flatMenuKeys, item)[0])
.filter(item => item); .filter(item => item)
.reduce((acc, curr) => [...acc, curr], ['/']);
}; };
...@@ -3,46 +3,55 @@ ...@@ -3,46 +3,55 @@
@nav-header-height: @layout-header-height; @nav-header-height: @layout-header-height;
.logo { .logo {
height: @nav-header-height;
position: relative; position: relative;
line-height: @nav-header-height; height: @nav-header-height;
padding-left: (@menu-collapsed-width - 32px) / 2; padding-left: (@menu-collapsed-width - 32px) / 2;
transition: all 0.3s;
background: #002140;
overflow: hidden; overflow: hidden;
line-height: @nav-header-height;
background: #002140;
transition: all 0.3s;
img { img {
display: inline-block; display: inline-block;
vertical-align: middle;
height: 32px; height: 32px;
vertical-align: middle;
} }
h1 { h1 {
color: white;
display: inline-block; display: inline-block;
vertical-align: middle;
font-size: 20px;
margin: 0 0 0 12px; margin: 0 0 0 12px;
font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif; color: white;
font-weight: 600; font-weight: 600;
font-size: 20px;
font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
vertical-align: middle;
} }
} }
.sider { .sider {
min-height: 100vh;
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
position: relative; position: relative;
z-index: 10; z-index: 10;
&.fixSiderbar { min-height: 100vh;
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
&.fixSiderBar {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
:global(.ant-menu-root) { box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
overflow-y: auto; :global {
height: ~'calc(100vh - @{nav-header-height})'; .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 { &.light {
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
background-color: white; background-color: white;
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
.logo { .logo {
background: white; background: white;
box-shadow: 1px 1px 0 0 @border-color-split; box-shadow: 1px 1px 0 0 @border-color-split;
...@@ -58,7 +67,7 @@ ...@@ -58,7 +67,7 @@
.icon { .icon {
width: 14px; width: 14px;
margin-right: 10px; vertical-align: baseline;
} }
:global { :global {
...@@ -78,15 +87,15 @@ ...@@ -78,15 +87,15 @@
.sider-menu-item-img .sider-menu-item-img
+ span, + span,
& > .ant-menu-submenu > .ant-menu-submenu-title .sider-menu-item-img + span { & > .ant-menu-submenu > .ant-menu-submenu-title .sider-menu-item-img + span {
max-width: 0;
display: inline-block; display: inline-block;
max-width: 0;
opacity: 0; opacity: 0;
} }
} }
.ant-menu-item .sider-menu-item-img + span, .ant-menu-item .sider-menu-item-img + span,
.ant-menu-submenu-title .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; opacity: 1;
transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out;
} }
.ant-drawer-left { .ant-drawer-left {
.ant-drawer-body { .ant-drawer-body {
......
...@@ -4,6 +4,7 @@ import RightContent from '../GlobalHeader/RightContent'; ...@@ -4,6 +4,7 @@ import RightContent from '../GlobalHeader/RightContent';
import BaseMenu from '../SiderMenu/BaseMenu'; import BaseMenu from '../SiderMenu/BaseMenu';
import { getFlatMenuKeys } from '../SiderMenu/SiderMenuUtils'; import { getFlatMenuKeys } from '../SiderMenu/SiderMenuUtils';
import styles from './index.less'; import styles from './index.less';
import { title } from '../../defaultSettings';
export default class TopNavHeader extends PureComponent { export default class TopNavHeader extends PureComponent {
state = { state = {
...@@ -32,7 +33,7 @@ export default class TopNavHeader extends PureComponent { ...@@ -32,7 +33,7 @@ export default class TopNavHeader extends PureComponent {
<div className={styles.logo} key="logo" id="logo"> <div className={styles.logo} key="logo" id="logo">
<Link to="/"> <Link to="/">
<img src={logo} alt="logo" /> <img src={logo} alt="logo" />
<h1>Ant Design Pro</h1> <h1>{title}</h1>
</Link> </Link>
</div> </div>
<div <div
......
@import '~antd/lib/style/themes/default.less'; @import '~antd/lib/style/themes/default.less';
.head { .head {
position: relative;
width: 100%; width: 100%;
transition: background 0.3s, width 0.2s;
height: @layout-header-height; height: @layout-header-height;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
position: relative; transition: background 0.3s, width 0.2s;
:global { :global {
.ant-menu-submenu.ant-menu-submenu-horizontal { .ant-menu-submenu.ant-menu-submenu-horizontal {
line-height: @layout-header-height;
height: 100%; height: 100%;
line-height: @layout-header-height;
.ant-menu-submenu-title { .ant-menu-submenu-title {
height: 100%; height: 100%;
} }
...@@ -28,8 +28,8 @@ ...@@ -28,8 +28,8 @@
padding-left: 0; padding-left: 0;
} }
.left { .left {
flex: 1;
display: flex; display: flex;
flex: 1;
} }
.right { .right {
width: 324px; width: 324px;
...@@ -38,24 +38,24 @@ ...@@ -38,24 +38,24 @@
} }
.logo { .logo {
position: relative;
width: 165px; width: 165px;
height: @layout-header-height; height: @layout-header-height;
position: relative; overflow: hidden;
line-height: @layout-header-height; line-height: @layout-header-height;
transition: all 0.3s; transition: all 0.3s;
overflow: hidden;
img { img {
display: inline-block; display: inline-block;
vertical-align: middle;
height: 32px; height: 32px;
vertical-align: middle;
} }
h1 { h1 {
color: #fff;
display: inline-block; display: inline-block;
vertical-align: top;
font-size: 16px;
margin: 0 0 0 12px; margin: 0 0 0 12px;
color: #fff;
font-weight: 400; font-weight: 400;
font-size: 16px;
vertical-align: top;
} }
} }
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
} }
.menu { .menu {
border: none;
height: @layout-header-height; height: @layout-header-height;
line-height: @layout-header-height; line-height: @layout-header-height;
border: none;
} }
...@@ -6,5 +6,13 @@ module.exports = { ...@@ -6,5 +6,13 @@ module.exports = {
fixedHeader: false, // sticky header fixedHeader: false, // sticky header
autoHideHeader: false, // auto hide header autoHideHeader: false, // auto hide header
fixSiderbar: false, // sticky siderbar fixSiderbar: false, // sticky siderbar
menu: {
disableLocal: false,
},
title: 'Ant Design Pro', title: 'Ant Design Pro',
pwa: true,
// your iconfont Symbol Scrip Url
// eg://at.alicdn.com/t/font_1039637_btcrd5co4w.js
// 注意:如果需要图标多色,Iconfont图标项目里要进行批量去色处理
iconfontUrl: '',
}; };
import React from 'react'; import React from 'react';
import { notification, Button, message } from 'antd'; import { notification, Button, message } from 'antd';
import { formatMessage } from 'umi/locale'; import { formatMessage } from 'umi/locale';
import defaultSettings from './defaultSettings';
// Notify user if offline now window.React = React;
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 const { pwa } = defaultSettings;
window.addEventListener('sw.updated', e => { // if pwa is true
const reloadSW = async () => { if (pwa) {
// Check if there is sw whose state is waiting in ServiceWorkerRegistration // Notify user if offline now
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration window.addEventListener('sw.offline', () => {
const worker = e.detail && e.detail.waiting; message.warning(formatMessage({ id: 'app.pwa.offline' }));
if (!worker) { });
return Promise.resolve();
} // Pop up a prompt on the page asking the user if they want to use the latest version
// Send skip-waiting event to waiting SW with MessageChannel window.addEventListener('sw.updated', e => {
await new Promise((resolve, reject) => { const reloadSW = async () => {
const channel = new MessageChannel(); // Check if there is sw whose state is waiting in ServiceWorkerRegistration
channel.port1.onmessage = event => { // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
if (event.data.error) { const worker = e.detail && e.detail.waiting;
reject(event.data.error); if (!worker) {
} else { return Promise.resolve();
resolve(event.data); }
} // Send skip-waiting event to waiting SW with MessageChannel
}; await new Promise((resolve, reject) => {
worker.postMessage({ type: 'skip-waiting' }, [channel.port2]); 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 () => {},
}); });
}); }
@import '~antd/lib/style/themes/default.less';
html, html,
body, body,
#root { #root {
...@@ -31,3 +33,20 @@ ul, ...@@ -31,3 +33,20 @@ ul,
ol { ol {
list-style: none; 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;
}
}
}
}
}
import React, { Suspense } from 'react'; import React, { Suspense } from 'react';
import { Layout } from 'antd'; import { Layout } from 'antd';
import DocumentTitle from 'react-document-title'; import DocumentTitle from 'react-document-title';
import isEqual from 'lodash/isEqual';
import memoizeOne from 'memoize-one';
import { connect } from 'dva'; import { connect } from 'dva';
import { ContainerQuery } from 'react-container-query'; import { ContainerQuery } from 'react-container-query';
import classNames from 'classnames'; import classNames from 'classnames';
import pathToRegexp from 'path-to-regexp';
import Media from 'react-media'; import Media from 'react-media';
import { formatMessage } from 'umi/locale';
import Authorized from '@/utils/Authorized';
import logo from '../assets/logo.svg'; import logo from '../assets/logo.svg';
import Footer from './Footer'; import Footer from './Footer';
import Header from './Header'; import Header from './Header';
import Context from './MenuContext'; import Context from './MenuContext';
import PageLoading from '@/components/PageLoading'; import PageLoading from '@/components/PageLoading';
import SiderMenu from '@/components/SiderMenu'; import SiderMenu from '@/components/SiderMenu';
import { title } from '../defaultSettings'; import getPageTitle from '@/utils/getPageTitle';
import styles from './BasicLayout.less'; import styles from './BasicLayout.less';
// lazy load SettingDrawer // lazy load SettingDrawer
...@@ -49,13 +44,7 @@ const query = { ...@@ -49,13 +44,7 @@ const query = {
}, },
}; };
class BasicLayout extends React.PureComponent { class BasicLayout extends React.Component {
constructor(props) {
super(props);
this.getPageTitle = memoizeOne(this.getPageTitle);
this.matchParamsPath = memoizeOne(this.matchParamsPath, isEqual);
}
componentDidMount() { componentDidMount() {
const { const {
dispatch, dispatch,
...@@ -73,15 +62,6 @@ class BasicLayout extends React.PureComponent { ...@@ -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() { getContext() {
const { location, breadcrumbNameMap } = this.props; const { location, breadcrumbNameMap } = this.props;
return { return {
...@@ -90,41 +70,6 @@ class BasicLayout extends React.PureComponent { ...@@ -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 = () => { getLayoutStyle = () => {
const { fixSiderbar, isMobile, collapsed, layout } = this.props; const { fixSiderbar, isMobile, collapsed, layout } = this.props;
if (fixSiderbar && layout !== 'topmenu' && !isMobile) { if (fixSiderbar && layout !== 'topmenu' && !isMobile) {
...@@ -161,12 +106,10 @@ class BasicLayout extends React.PureComponent { ...@@ -161,12 +106,10 @@ class BasicLayout extends React.PureComponent {
isMobile, isMobile,
menuData, menuData,
breadcrumbNameMap, breadcrumbNameMap,
route: { routes },
fixedHeader, fixedHeader,
} = this.props; } = this.props;
const isTop = PropsLayout === 'topmenu'; const isTop = PropsLayout === 'topmenu';
const routerConfig = this.getRouterAuthority(pathname, routes);
const contentStyle = !fixedHeader ? { paddingTop: 0 } : {}; const contentStyle = !fixedHeader ? { paddingTop: 0 } : {};
const layout = ( const layout = (
<Layout> <Layout>
...@@ -194,9 +137,7 @@ class BasicLayout extends React.PureComponent { ...@@ -194,9 +137,7 @@ class BasicLayout extends React.PureComponent {
{...this.props} {...this.props}
/> />
<Content className={styles.content} style={contentStyle}> <Content className={styles.content} style={contentStyle}>
<Authorized authority={routerConfig} noMatch={<p>Exception403</p>}> {children}
{children}
</Authorized>
</Content> </Content>
<Footer /> <Footer />
</Layout> </Layout>
...@@ -204,7 +145,7 @@ class BasicLayout extends React.PureComponent { ...@@ -204,7 +145,7 @@ class BasicLayout extends React.PureComponent {
); );
return ( return (
<React.Fragment> <React.Fragment>
<DocumentTitle title={this.getPageTitle(pathname, breadcrumbNameMap)}> <DocumentTitle title={getPageTitle(pathname, breadcrumbNameMap)}>
<ContainerQuery query={query}> <ContainerQuery query={query}>
{params => ( {params => (
<Context.Provider value={this.getContext()}> <Context.Provider value={this.getContext()}>
...@@ -219,11 +160,11 @@ class BasicLayout extends React.PureComponent { ...@@ -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, collapsed: global.collapsed,
layout: setting.layout, layout: setting.layout,
menuData: menu.menuData, menuData: menuModel.menuData,
breadcrumbNameMap: menu.breadcrumbNameMap, breadcrumbNameMap: menuModel.breadcrumbNameMap,
...setting, ...setting,
}))(props => ( }))(props => (
<Media query="(max-width: 599px)"> <Media query="(max-width: 599px)">
......
import React from 'react'; import React from 'react';
export default props => <div {...props} />; export default ({ children }) => <div>{children}</div>;
import React, { PureComponent } from 'react'; import React, { Component } from 'react';
import { formatMessage } from 'umi/locale'; import { formatMessage } from 'umi/locale';
import { Layout, message } from 'antd'; import { Layout, message } from 'antd';
import Animate from 'rc-animate'; import Animate from 'rc-animate';
...@@ -10,7 +10,7 @@ import styles from './Header.less'; ...@@ -10,7 +10,7 @@ import styles from './Header.less';
const { Header } = Layout; const { Header } = Layout;
class HeaderView extends PureComponent { class HeaderView extends Component {
state = { state = {
visible: true, visible: true,
}; };
...@@ -155,7 +155,6 @@ export default connect(({ user, global, setting, loading }) => ({ ...@@ -155,7 +155,6 @@ export default connect(({ user, global, setting, loading }) => ({
collapsed: global.collapsed, collapsed: global.collapsed,
fetchingMoreNotices: loading.effects['global/fetchMoreNotices'], fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
fetchingNotices: loading.effects['global/fetchNotices'], fetchingNotices: loading.effects['global/fetchNotices'],
loadedAllNotices: global.loadedAllNotices,
notices: global.notices, notices: global.notices,
setting, setting,
}))(HeaderView); }))(HeaderView);
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
position: fixed; position: fixed;
top: 0; top: 0;
right: 0; right: 0;
width: 100%;
z-index: 9; z-index: 9;
width: 100%;
transition: width 0.2s; transition: width 0.2s;
} }
import React, { Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import { formatMessage } from 'umi/locale'; import { formatMessage } from 'umi/locale';
import { connect } from 'dva';
import Link from 'umi/link'; import Link from 'umi/link';
import { Icon } from 'antd'; import { Icon } from 'antd';
import { GlobalFooter } from 'ant-design-pro'; import { GlobalFooter } from 'ant-design-pro';
import DocumentTitle from 'react-document-title';
import SelectLang from '@/components/SelectLang'; import SelectLang from '@/components/SelectLang';
import styles from './UserLayout.less'; import styles from './UserLayout.less';
import logo from '../assets/logo.svg'; import logo from '../assets/logo.svg';
import getPageTitle from '@/utils/getPageTitle';
const links = [ const links = [
{ {
...@@ -31,42 +34,50 @@ const copyright = ( ...@@ -31,42 +34,50 @@ const copyright = (
</Fragment> </Fragment>
); );
class UserLayout extends React.PureComponent { class UserLayout extends Component {
// @TODO title componentDidMount() {
// getPageTitle() { const {
// const { routerData, location } = this.props; dispatch,
// const { pathname } = location; route: { routes, authority },
// let title = 'Ant Design Pro'; } = this.props;
// if (routerData[pathname] && routerData[pathname].name) { dispatch({
// title = `${routerData[pathname].name} - Ant Design Pro`; type: 'menu/getMenuData',
// } payload: { routes, authority },
// return title; });
// } }
render() { render() {
const { children } = this.props; const {
children,
location: { pathname },
breadcrumbNameMap,
} = this.props;
return ( return (
// @TODO <DocumentTitle title={this.getPageTitle()}> <DocumentTitle title={getPageTitle(pathname, breadcrumbNameMap)}>
<div className={styles.container}> <div className={styles.container}>
<div className={styles.lang}> <div className={styles.lang}>
<SelectLang /> <SelectLang />
</div> </div>
<div className={styles.content}> <div className={styles.content}>
<div className={styles.top}> <div className={styles.top}>
<div className={styles.header}> <div className={styles.header}>
<Link to="/"> <Link to="/">
<img alt="logo" className={styles.logo} src={logo} /> <img alt="logo" className={styles.logo} src={logo} />
<span className={styles.title}>Ant Design</span> <span className={styles.title}>Ant Design</span>
</Link> </Link>
</div>
<div className={styles.desc}>Ant Design 是西湖区最具影响力的 Web 设计规范</div>
</div> </div>
<div className={styles.desc}>Ant Design 是西湖区最具影响力的 Web 设计规范</div> {children}
</div> </div>
{children} <GlobalFooter links={links} copyright={copyright} />
</div> </div>
<GlobalFooter links={links} copyright={copyright} /> </DocumentTitle>
</div>
); );
} }
} }
export default UserLayout; export default connect(({ menu: menuModel }) => ({
menuData: menuModel.menuData,
breadcrumbNameMap: menuModel.breadcrumbNameMap,
}))(UserLayout);
...@@ -9,18 +9,18 @@ ...@@ -9,18 +9,18 @@
} }
.lang { .lang {
text-align: right;
width: 100%; width: 100%;
height: 40px; height: 40px;
line-height: 44px; line-height: 44px;
text-align: right;
:global(.ant-dropdown-trigger) { :global(.ant-dropdown-trigger) {
margin-right: 24px; margin-right: 24px;
} }
} }
.content { .content {
padding: 32px 0;
flex: 1; flex: 1;
padding: 32px 0;
} }
@media (min-width: @screen-md-min) { @media (min-width: @screen-md-min) {
...@@ -50,22 +50,22 @@ ...@@ -50,22 +50,22 @@
.logo { .logo {
height: 44px; height: 44px;
vertical-align: top;
margin-right: 16px; margin-right: 16px;
vertical-align: top;
} }
.title { .title {
font-size: 33px;
color: @heading-color;
font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
font-weight: 600;
position: relative; position: relative;
top: 2px; top: 2px;
color: @heading-color;
font-weight: 600;
font-size: 33px;
font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
} }
.desc { .desc {
font-size: @font-size-base;
color: @text-color-secondary;
margin-top: 12px; margin-top: 12px;
margin-bottom: 40px; margin-bottom: 40px;
color: @text-color-secondary;
font-size: @font-size-base;
} }
...@@ -3,6 +3,7 @@ import menu from './en-US/menu'; ...@@ -3,6 +3,7 @@ import menu from './en-US/menu';
import settingDrawer from './en-US/settingDrawer'; import settingDrawer from './en-US/settingDrawer';
import settings from './en-US/settings'; import settings from './en-US/settings';
import pwa from './en-US/pwa'; import pwa from './en-US/pwa';
import component from './en-US/component';
export default { export default {
'navBar.lang': 'Languages', 'navBar.lang': 'Languages',
...@@ -15,4 +16,5 @@ export default { ...@@ -15,4 +16,5 @@ export default {
...settingDrawer, ...settingDrawer,
...settings, ...settings,
...pwa, ...pwa,
...component,
}; };
export default {
'component.tagSelect.expand': 'Expand',
'component.tagSelect.collapse': 'Collapse',
'component.tagSelect.all': 'All',
};
...@@ -13,6 +13,5 @@ export default { ...@@ -13,6 +13,5 @@ export default {
'component.noticeIcon.clear': 'Clear', 'component.noticeIcon.clear': 'Clear',
'component.noticeIcon.cleared': 'Cleared', 'component.noticeIcon.cleared': 'Cleared',
'component.noticeIcon.empty': 'No notifications', 'component.noticeIcon.empty': 'No notifications',
'component.noticeIcon.loaded': 'Loaded', 'component.noticeIcon.view-more': 'View more',
'component.noticeIcon.loading-more': 'Loading more',
}; };
...@@ -25,14 +25,14 @@ export default { ...@@ -25,14 +25,14 @@ export default {
'app.settings.security.medium': 'Medium', 'app.settings.security.medium': 'Medium',
'app.settings.security.weak': 'Weak', 'app.settings.security.weak': 'Weak',
'app.settings.security.password': 'Account Password', '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': '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': 'Security Question',
'app.settings.security.question-description': 'app.settings.security.question-description':
'The security question is not set, and the security policy can effectively protect the account security', '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': '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': 'MFA Device',
'app.settings.security.mfa-description': 'app.settings.security.mfa-description':
'Unbound MFA device, after binding, can be confirmed twice', 'Unbound MFA device, after binding, can be confirmed twice',
......
...@@ -3,6 +3,7 @@ import menu from './pt-BR/menu'; ...@@ -3,6 +3,7 @@ import menu from './pt-BR/menu';
import settingDrawer from './pt-BR/settingDrawer'; import settingDrawer from './pt-BR/settingDrawer';
import settings from './pt-BR/settings'; import settings from './pt-BR/settings';
import pwa from './pt-BR/pwa'; import pwa from './pt-BR/pwa';
import component from './pt-BR/component';
export default { export default {
'navBar.lang': 'Idiomas', 'navBar.lang': 'Idiomas',
...@@ -15,4 +16,5 @@ export default { ...@@ -15,4 +16,5 @@ export default {
...settingDrawer, ...settingDrawer,
...settings, ...settings,
...pwa, ...pwa,
...component,
}; };
export default {
'component.tagSelect.expand': 'Expandir',
'component.tagSelect.collapse': 'Diminuir',
'component.tagSelect.all': 'Todas',
};
...@@ -14,5 +14,5 @@ export default { ...@@ -14,5 +14,5 @@ export default {
'component.noticeIcon.cleared': 'Limpo', 'component.noticeIcon.cleared': 'Limpo',
'component.noticeIcon.empty': 'Sem notificações', 'component.noticeIcon.empty': 'Sem notificações',
'component.noticeIcon.loaded': 'Carregado', 'component.noticeIcon.loaded': 'Carregado',
'component.noticeIcon.loading-more': 'Carregar mais', 'component.noticeIcon.view-more': 'Veja mais',
}; };
...@@ -3,6 +3,7 @@ import menu from './zh-CN/menu'; ...@@ -3,6 +3,7 @@ import menu from './zh-CN/menu';
import settingDrawer from './zh-CN/settingDrawer'; import settingDrawer from './zh-CN/settingDrawer';
import settings from './zh-CN/settings'; import settings from './zh-CN/settings';
import pwa from './zh-CN/pwa'; import pwa from './zh-CN/pwa';
import component from './zh-CN/component';
export default { export default {
'navBar.lang': '语言', 'navBar.lang': '语言',
...@@ -15,4 +16,5 @@ export default { ...@@ -15,4 +16,5 @@ export default {
...settingDrawer, ...settingDrawer,
...settings, ...settings,
...pwa, ...pwa,
...component,
}; };
export default {
'component.tagSelect.expand': '展开',
'component.tagSelect.collapse': '收起',
'component.tagSelect.all': '全部',
};
...@@ -13,6 +13,5 @@ export default { ...@@ -13,6 +13,5 @@ export default {
'component.noticeIcon.clear': '清空', 'component.noticeIcon.clear': '清空',
'component.noticeIcon.cleared': '清空了', 'component.noticeIcon.cleared': '清空了',
'component.noticeIcon.empty': '暂无数据', 'component.noticeIcon.empty': '暂无数据',
'component.noticeIcon.loaded': '加载完毕', 'component.noticeIcon.view-more': '查看更多',
'component.noticeIcon.loading-more': '加载更多',
}; };
...@@ -25,13 +25,13 @@ export default { ...@@ -25,13 +25,13 @@ export default {
'app.settings.security.medium': '', 'app.settings.security.medium': '',
'app.settings.security.weak': '', 'app.settings.security.weak': '',
'app.settings.security.password': '账户密码', 'app.settings.security.password': '账户密码',
'app.settings.security.password-description': '当前密码强度', 'app.settings.security.password-description': '当前密码强度',
'app.settings.security.phone': '密保手机', 'app.settings.security.phone': '密保手机',
'app.settings.security.phone-description': '已绑定手机', 'app.settings.security.phone-description': '已绑定手机',
'app.settings.security.question': '密保问题', 'app.settings.security.question': '密保问题',
'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全', 'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全',
'app.settings.security.email': '备用邮箱', 'app.settings.security.email': '备用邮箱',
'app.settings.security.email-description': '已绑定邮箱', 'app.settings.security.email-description': '已绑定邮箱',
'app.settings.security.mfa': 'MFA 设备', 'app.settings.security.mfa': 'MFA 设备',
'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认', 'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认',
'app.settings.security.modify': '修改', 'app.settings.security.modify': '修改',
......
...@@ -3,6 +3,7 @@ import menu from './zh-TW/menu'; ...@@ -3,6 +3,7 @@ import menu from './zh-TW/menu';
import settingDrawer from './zh-TW/settingDrawer'; import settingDrawer from './zh-TW/settingDrawer';
import settings from './zh-TW/settings'; import settings from './zh-TW/settings';
import pwa from './zh-TW/pwa'; import pwa from './zh-TW/pwa';
import component from './zh-TW/component';
export default { export default {
'navBar.lang': '語言', 'navBar.lang': '語言',
...@@ -18,4 +19,5 @@ export default { ...@@ -18,4 +19,5 @@ export default {
...settingDrawer, ...settingDrawer,
...settings, ...settings,
...pwa, ...pwa,
...component,
}; };
export default {
'component.tagSelect.expand': '展開',
'component.tagSelect.collapse': '收起',
'component.tagSelect.all': '全部',
};
...@@ -3,7 +3,7 @@ export default { ...@@ -3,7 +3,7 @@ export default {
'component.globalHeader.search.example1': '搜索提示壹', 'component.globalHeader.search.example1': '搜索提示壹',
'component.globalHeader.search.example2': '搜索提示二', 'component.globalHeader.search.example2': '搜索提示二',
'component.globalHeader.search.example3': '搜索提示三', 'component.globalHeader.search.example3': '搜索提示三',
'component.globalHeader.help': '使用文檔', 'component.globalHeader.help': '使用手冊',
'component.globalHeader.notification': '通知', 'component.globalHeader.notification': '通知',
'component.globalHeader.notification.empty': '妳已查看所有通知', 'component.globalHeader.notification.empty': '妳已查看所有通知',
'component.globalHeader.message': '消息', 'component.globalHeader.message': '消息',
...@@ -12,7 +12,6 @@ export default { ...@@ -12,7 +12,6 @@ export default {
'component.globalHeader.event.empty': '妳已完成所有待辦', 'component.globalHeader.event.empty': '妳已完成所有待辦',
'component.noticeIcon.clear': '清空', 'component.noticeIcon.clear': '清空',
'component.noticeIcon.cleared': '清空了', 'component.noticeIcon.cleared': '清空了',
'component.noticeIcon.empty': '暫無數據', 'component.noticeIcon.empty': '暫無資料',
'component.noticeIcon.loaded': '加載完畢', 'component.noticeIcon.view-more': '查看更多',
'component.noticeIcon.loading-more': '加載更多',
}; };
...@@ -25,13 +25,13 @@ export default { ...@@ -25,13 +25,13 @@ export default {
'app.settings.security.medium': '', 'app.settings.security.medium': '',
'app.settings.security.weak': '', 'app.settings.security.weak': '',
'app.settings.security.password': '賬戶密碼', 'app.settings.security.password': '賬戶密碼',
'app.settings.security.password-description': '當前密碼強度', 'app.settings.security.password-description': '當前密碼強度',
'app.settings.security.phone': '密保手機', 'app.settings.security.phone': '密保手機',
'app.settings.security.phone-description': '已綁定手機', 'app.settings.security.phone-description': '已綁定手機',
'app.settings.security.question': '密保問題', 'app.settings.security.question': '密保問題',
'app.settings.security.question-description': '未設置密保問題,密保問題可有效保護賬戶安全', 'app.settings.security.question-description': '未設置密保問題,密保問題可有效保護賬戶安全',
'app.settings.security.email': '備用郵箱', 'app.settings.security.email': '備用郵箱',
'app.settings.security.email-description': '已綁定郵箱', 'app.settings.security.email-description': '已綁定郵箱',
'app.settings.security.mfa': 'MFA 設備', 'app.settings.security.mfa': 'MFA 設備',
'app.settings.security.mfa-description': '未綁定 MFA 設備,綁定後,可以進行二次確認', 'app.settings.security.mfa-description': '未綁定 MFA 設備,綁定後,可以進行二次確認',
'app.settings.security.modify': '修改', 'app.settings.security.modify': '修改',
......
...@@ -6,42 +6,14 @@ export default { ...@@ -6,42 +6,14 @@ export default {
state: { state: {
collapsed: false, collapsed: false,
notices: [], notices: [],
loadedAllNotices: false,
}, },
effects: { effects: {
*fetchNotices(_, { call, put, select }) { *fetchNotices(_, { call, put, select }) {
const data = yield call(queryNotices); const data = yield call(queryNotices);
const loadedAllNotices = data && data.length && data[data.length - 1] === null;
yield put({
type: 'setLoadedStatus',
payload: loadedAllNotices,
});
yield put({ yield put({
type: 'saveNotices', type: 'saveNotices',
payload: data.filter(item => item), payload: data,
});
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),
}); });
const unreadCount = yield select( const unreadCount = yield select(
state => state.global.notices.filter(item => !item.read).length state => state.global.notices.filter(item => !item.read).length
...@@ -114,18 +86,6 @@ export default { ...@@ -114,18 +86,6 @@ export default {
notices: state.notices.filter(item => item.type !== payload), 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: { subscriptions: {
......
...@@ -2,6 +2,7 @@ import memoizeOne from 'memoize-one'; ...@@ -2,6 +2,7 @@ import memoizeOne from 'memoize-one';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import { formatMessage } from 'umi/locale'; import { formatMessage } from 'umi/locale';
import Authorized from '@/utils/Authorized'; import Authorized from '@/utils/Authorized';
import { menu } from '../defaultSettings';
const { check } = Authorized; const { check } = Authorized;
...@@ -19,10 +20,14 @@ function formatter(data, parentAuthority, parentName) { ...@@ -19,10 +20,14 @@ function formatter(data, parentAuthority, parentName) {
} else { } else {
locale = `menu.${item.name}`; 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 = { const result = {
...item, ...item,
name: formatMessage({ id: locale, defaultMessage: item.name }), name,
locale, locale,
authority: item.authority || parentAuthority, authority: item.authority || parentAuthority,
}; };
...@@ -92,17 +97,19 @@ export default { ...@@ -92,17 +97,19 @@ export default {
state: { state: {
menuData: [], menuData: [],
routerData: [],
breadcrumbNameMap: {}, breadcrumbNameMap: {},
}, },
effects: { effects: {
*getMenuData({ payload }, { put }) { *getMenuData({ payload }, { put }) {
const { routes, authority } = payload; const { routes, authority } = payload;
const menuData = filterMenuData(memoizeOneFormatter(routes, authority)); const originalMenuData = memoizeOneFormatter(routes, authority);
const breadcrumbNameMap = memoizeOneGetBreadcrumbNameMap(menuData); const menuData = filterMenuData(originalMenuData);
const breadcrumbNameMap = memoizeOneGetBreadcrumbNameMap(originalMenuData);
yield put({ yield put({
type: 'save', type: 'save',
payload: { menuData, breadcrumbNameMap }, payload: { menuData, breadcrumbNameMap, routerData: routes },
}); });
}, },
}, },
......
import React from 'react'; import React from 'react';
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
import { getAuthority } from '@/utils/authority';
import Redirect from 'umi/redirect'; import Redirect from 'umi/redirect';
import pathToRegexp from 'path-to-regexp';
import { connect } from 'dva';
import Authorized from '@/utils/Authorized';
const Authority = getAuthority(); function AuthComponent({ children, location, routerData, currentCuser }) {
const Authorized = RenderAuthorized(Authority); const isLogin = currentCuser && currentCuser.name;
export default ({ children }) => ( const getRouteAuthority = (pathname, routeData) => {
<Authorized authority={children.props.route.authority} noMatch={<Redirect to="/user/login" />}> const routes = routeData.slice(); // clone
{children}
</Authorized> 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);
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta
<title>Ant Design Pro</title> name="viewport"
<link rel="icon" href="/favicon.png" type="image/x-icon"> content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
<script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.data-set-0.9.6/dist/data-set.min.js"></script> />
</head> <title>Ant Design Pro</title>
<body> <link rel="icon" href="/favicon.png" type="image/x-icon" />
<noscript>Sorry, we need js to run correctly!</noscript> </head>
<div id="root"></div> <body>
</body> <noscript>Sorry, we need js to run correctly!</noscript>
<div id="root"></div>
</body>
</html> </html>
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);
.textOverflow() { .textOverflow() {
overflow: hidden; overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
word-break: break-all; word-break: break-all;
white-space: nowrap;
} }
.textOverflowMulti(@line: 3, @bg: #fff) { .textOverflowMulti(@line: 3, @bg: #fff) {
overflow: hidden;
position: relative; position: relative;
line-height: 1.5em;
max-height: @line * 1.5em; max-height: @line * 1.5em;
text-align: justify;
margin-right: -1em; margin-right: -1em;
padding-right: 1em; padding-right: 1em;
&:before { overflow: hidden;
background: @bg; line-height: 1.5em;
content: '...'; text-align: justify;
padding: 0 1px; &::before {
position: absolute; position: absolute;
right: 14px; right: 14px;
bottom: 0; bottom: 0;
padding: 0 1px;
background: @bg;
content: '...';
} }
&:after { &::after {
background: white;
content: '';
margin-top: 0.2em;
position: absolute; position: absolute;
right: 14px; right: 14px;
width: 1em; width: 1em;
height: 1em; height: 1em;
margin-top: 0.2em;
background: white;
content: '';
} }
} }
...@@ -36,15 +36,15 @@ ...@@ -36,15 +36,15 @@
// ------------------------ // ------------------------
.clearfix() { .clearfix() {
zoom: 1; zoom: 1;
&:before, &::before,
&:after { &::after {
content: ' '; content: ' ';
display: table; display: table;
} }
&:after { &::after {
clear: both; clear: both;
visibility: hidden;
font-size: 0;
height: 0; height: 0;
font-size: 0;
visibility: hidden;
} }
} }
...@@ -24,7 +24,11 @@ startServer.on('exit', () => { ...@@ -24,7 +24,11 @@ startServer.on('exit', () => {
console.log('Starting development server for e2e tests...'); console.log('Starting development server for e2e tests...');
startServer.stdout.on('data', data => { startServer.stdout.on('data', data => {
console.log(data.toString()); 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 // eslint-disable-next-line
once = true; once = true;
console.log('Development server is started, ready to run tests.'); console.log('Development server is started, ready to run tests.');
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment