diff --git a/config/config.js b/config/config.ts similarity index 95% rename from config/config.js rename to config/config.ts index a86d1efd421deec9179335ae8e20760aac646403..ea5ba918e3aa97b4f75c8f24aaa1f1cb0f7f485c 100644 --- a/config/config.js +++ b/config/config.ts @@ -1,13 +1,14 @@ // https://umijs.org/config/ import os from 'os'; -import webpackPlugin from './plugin.config'; -import defaultSettings from '../src/defaultSettings'; import slash from 'slash2'; +import { IPlugin } from 'umi-types'; +import defaultSettings from './defaultSettings'; +import webpackPlugin from './plugin.config'; const { pwa, primaryColor } = defaultSettings; -const { NODE_ENV, APP_TYPE, TEST } = process.env; +const { APP_TYPE, TEST } = process.env; -const plugins = [ +const plugins: IPlugin[] = [ [ 'umi-plugin-react', { diff --git a/src/defaultSettings.js b/config/defaultSettings.ts similarity index 60% rename from src/defaultSettings.js rename to config/defaultSettings.ts index 4ba7eb390c49069b68053d9823f194a76f99391d..9aa47e026541402b5624c6cbd98d779f4c52c9ad 100644 --- a/src/defaultSettings.js +++ b/config/defaultSettings.ts @@ -1,4 +1,21 @@ -module.exports = { +export declare type SiderTheme = 'light' | 'dark'; + +export interface DefaultSettings { + navTheme: string | SiderTheme; + primaryColor: string; + layout: string; + contentWidth: string; + fixedHeader: boolean; + autoHideHeader: boolean; + fixSiderbar: boolean; + menu: { disableLocal: boolean }; + title: string; + pwa: boolean; + iconfontUrl: string; + colorWeak: boolean; +} + +export default { navTheme: 'dark', // theme for nav menu primaryColor: '#1890FF', // primary color of ant design layout: 'sidemenu', // nav menu position: sidemenu or topmenu @@ -6,6 +23,7 @@ module.exports = { fixedHeader: false, // sticky header autoHideHeader: false, // auto hide header fixSiderbar: false, // sticky siderbar + colorWeak: false, menu: { disableLocal: false, }, @@ -15,4 +33,4 @@ module.exports = { // eg://at.alicdn.com/t/font_1039637_btcrd5co4w.js // 注意:如果需要图标多色,Iconfont图标项目里要进行批量去色处理 iconfontUrl: '', -}; +} as DefaultSettings; diff --git a/config/plugin.config.js b/config/plugin.config.ts similarity index 100% rename from config/plugin.config.js rename to config/plugin.config.ts diff --git a/e2e/baseLayout.e2e.js b/e2e/baseLayout.e2e.js deleted file mode 100644 index 456861f94137714a43b162fd1fdcfa8404e2090a..0000000000000000000000000000000000000000 --- a/e2e/baseLayout.e2e.js +++ /dev/null @@ -1,36 +0,0 @@ -import config from '../config/config'; - -const RouterConfig = config.routes; - -const BASE_URL = `http://localhost:${process.env.PORT || 8000}`; - -function formatter(data) { - return data - .reduce((pre, item) => { - pre.push(item.path); - return pre; - }, []) - .filter(item => item); -} - -describe('Homepage', async () => { - const testPage = path => async () => { - await page.goto(`${BASE_URL}${path}`); - await page.waitForSelector('footer', { - timeout: 2000, - }); - const haveFooter = await page.evaluate( - () => document.getElementsByTagName('footer').length > 0 - ); - expect(haveFooter).toBeTruthy(); - }; - - beforeAll(async () => { - jest.setTimeout(1000000); - await page.setCacheEnabled(false); - }); - const routers = formatter(RouterConfig[1].routes); - routers.forEach(route => { - it(`test pages ${route}`, testPage(route)); - }); -}); diff --git a/e2e/home.e2e.js b/e2e/home.e2e.js deleted file mode 100644 index 0531d5f4b549d477d51f980df92b016bf8e00cc2..0000000000000000000000000000000000000000 --- a/e2e/home.e2e.js +++ /dev/null @@ -1,15 +0,0 @@ -const BASE_URL = `http://localhost:${process.env.PORT || 8000}`; - -describe('Homepage', () => { - beforeAll(async () => { - jest.setTimeout(1000000); - }); - it('it should have logo text', async () => { - await page.goto(BASE_URL); - await page.waitForSelector('h1', { - timeout: 5000, - }); - const text = await page.evaluate(() => document.getElementsByTagName('h1')[0].innerText); - expect(text).toContain('Ant Design Pro'); - }); -}); diff --git a/e2e/topMenu.e2e.js b/e2e/topMenu.e2e.js deleted file mode 100644 index 51ff9f35c632e3c6471b153b45f76b40e9a11cc9..0000000000000000000000000000000000000000 --- a/e2e/topMenu.e2e.js +++ /dev/null @@ -1,18 +0,0 @@ -const BASE_URL = `http://localhost:${process.env.PORT || 8000}`; - -describe('Homepage', () => { - beforeAll(async () => { - jest.setTimeout(1000000); - }); - it('topmenu should have footer', async () => { - const params = '/form/basic-form?navTheme=light&layout=topmenu'; - await page.goto(`${BASE_URL}${params}`); - await page.waitForSelector('footer', { - timeout: 2000, - }); - const haveFooter = await page.evaluate( - () => document.getElementsByTagName('footer').length > 0 - ); - expect(haveFooter).toBeTruthy(); - }); -}); diff --git a/e2e/userLayout.e2e.js b/e2e/userLayout.e2e.js deleted file mode 100644 index 14b60416e2f09038020c4d373a1f52b2b46c81e5..0000000000000000000000000000000000000000 --- a/e2e/userLayout.e2e.js +++ /dev/null @@ -1,34 +0,0 @@ -import config from '../config/config'; - -const RouterConfig = config.routes; - -const BASE_URL = `http://localhost:${process.env.PORT || 8000}`; - -function formatter(data) { - return data - .reduce((pre, item) => { - pre.push(item.path); - return pre; - }, []) - .filter(item => item); -} - -describe('Homepage', () => { - const testPage = path => async () => { - await page.goto(`${BASE_URL}${path}`); - await page.waitForSelector('footer', { - timeout: 2000, - }); - const haveFooter = await page.evaluate( - () => document.getElementsByTagName('footer').length > 0 - ); - expect(haveFooter).toBeTruthy(); - }; - - beforeAll(async () => { - jest.setTimeout(1000000); - }); - formatter(RouterConfig[0].routes).forEach(route => { - it(`test pages ${route}`, testPage(route)); - }); -}); diff --git a/mock/notices.js b/mock/notices.ts similarity index 100% rename from mock/notices.js rename to mock/notices.ts diff --git a/mock/route.js b/mock/route.ts similarity index 100% rename from mock/route.js rename to mock/route.ts diff --git a/mock/user.js b/mock/user.ts similarity index 100% rename from mock/user.js rename to mock/user.ts diff --git a/package.json b/package.json index 551ef36fecb866990eb987b5138e5f4d59feb910..82256900d9c5fa6d374e2aba14f76de87f851d69 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "analyze": "cross-env ANALYZE=1 umi build", "lint:style": "stylelint 'src/**/*.less' --syntax less", "lint:prettier": "check-prettier lint", - "lint": "eslint --ext .js src mock tests && npm run lint:style && npm run lint:prettier", - "lint:fix": "eslint --fix --ext .js src mock tests && stylelint --fix 'src/**/*.less' --syntax less", + "lint": "eslint --ext .js src tests && npm run lint:style && npm run lint:prettier", + "lint:fix": "eslint --fix --ext .js src tests && stylelint --fix 'src/**/*.less' --syntax less", "lint-staged": "lint-staged", "lint-staged:js": "eslint --ext .js", "test": "umi test", @@ -47,9 +47,12 @@ "react-copy-to-clipboard": "^5.0.1", "react-document-title": "^2.0.3", "react-media": "^1.8.0", - "umi-request": "^1.0.0" + "react-media-hook2": "^1.0.2", + "umi-request": "^1.0.0", + "umi-types": "^0.2.0" }, "devDependencies": { + "@types/jest": "^24.0.11", "@types/react": "^16.8.1", "@types/react-dom": "^16.0.11", "antd-pro-merge-less": "^1.0.0", @@ -90,8 +93,8 @@ "tslint-react": "^3.6.0", "umi": "^2.4.4", "umi-plugin-ga": "^1.1.3", - "umi-plugin-react": "^1.3.4", - "umi-plugin-pro-block": "^1.2.0" + "umi-plugin-pro-block": "^1.2.0", + "umi-plugin-react": "^1.3.4" }, "optionalDependencies": { "puppeteer": "^1.12.1" diff --git a/src/app.js b/src/app.ts similarity index 96% rename from src/app.js rename to src/app.ts index 0f35ff9af6376bcc3c3606f219ae6bcd1af6b769..22236327597681fef16028dff1165e904f1330e7 100644 --- a/src/app.js +++ b/src/app.ts @@ -26,7 +26,7 @@ export function patchRoutes(routes) { Object.keys(authRoutes).map(authKey => ergodicRoutes(routes, authKey, authRoutes[authKey].authority) ); - window.g_routes = routes; + (window as any).g_routes = routes; } export function render(oldRender) { diff --git a/src/components/GlobalHeader/RightContent.js b/src/components/GlobalHeader/RightContent.tsx similarity index 83% rename from src/components/GlobalHeader/RightContent.js rename to src/components/GlobalHeader/RightContent.tsx index dc63084c05b8ff31ce147fd0686104dc705678f4..0d7689873cf3271ab5b80bed55e1a66f837b434e 100644 --- a/src/components/GlobalHeader/RightContent.js +++ b/src/components/GlobalHeader/RightContent.tsx @@ -1,6 +1,7 @@ -import React, { PureComponent } from 'react'; -import { FormattedMessage, formatMessage } from 'umi/locale'; +import React, { Component } from 'react'; +import { FormattedMessage, formatMessage } from 'umi-plugin-locale'; import { Spin, Tag, Menu, Icon, Avatar, Tooltip, message } from 'antd'; +import { ClickParam } from 'antd/es/menu'; import moment from 'moment'; import groupBy from 'lodash/groupBy'; import { NoticeIcon } from 'ant-design-pro'; @@ -9,7 +10,29 @@ import HeaderDropdown from '../HeaderDropdown'; import SelectLang from '../SelectLang'; import styles from './index.less'; -export default class GlobalHeaderRight extends PureComponent { +export declare type SiderTheme = 'light' | 'dark'; + +interface GlobalHeaderRightProps { + notices?: any[]; + dispatch?: (args: any) => void; + // wait for https://github.com/umijs/umi/pull/2036 + currentUser?: { + avatar?: string; + name?: string; + title?: string; + group?: string; + signature?: string; + geographic?: any; + tags?: any[]; + unreadCount: number; + }; + fetchingNotices?: boolean; + onNoticeVisibleChange?: (visible: boolean) => void; + onMenuClick?: (param: ClickParam) => void; + onNoticeClear?: (tabName: string) => void; + theme?: SiderTheme; +} +export default class GlobalHeaderRight extends Component { getNoticeData() { const { notices = [] } = this.props; if (notices.length === 0) { @@ -41,7 +64,7 @@ export default class GlobalHeaderRight extends PureComponent { return groupBy(newNotices, 'type'); } - getUnreadData = noticeData => { + getUnreadData: (noticeData: object) => any = noticeData => { const unreadMsg = {}; Object.entries(noticeData).forEach(([key, value]) => { if (!unreadMsg[key]) { @@ -126,25 +149,26 @@ export default class GlobalHeaderRight extends PureComponent { + { console.log(item, tabProps); // eslint-disable-line - this.changeReadState(item, tabProps); + this.changeReadState(item); }} loading={fetchingNotices} locale={{ emptyText: formatMessage({ id: 'component.noticeIcon.empty' }), clear: formatMessage({ id: 'component.noticeIcon.clear' }), - viewMore: formatMessage({ id: 'component.noticeIcon.view-more' }), + viewMore: formatMessage({ id: 'component.noticeIcon.view-more' }), // todo:node_modules/ant-design-pro/lib/NoticeIcon/index.d.ts 21 [key: string]: string; notification: formatMessage({ id: 'component.globalHeader.notification' }), message: formatMessage({ id: 'component.globalHeader.message' }), event: formatMessage({ id: 'component.globalHeader.event' }), }} onClear={onNoticeClear} onPopupVisibleChange={onNoticeVisibleChange} - onViewMore={() => message.info('Click on view more')} + onViewMore={() => message.info('Click on view more')} // todo:onViewMore?: (tabProps: INoticeIconProps) => void; clearClose > void; + isMobile?: boolean; + logo?: string; + onNoticeClear?: (type: string) => void; + onMenuClick?: ({ key: string }) => void; + onNoticeVisibleChange?: (b: boolean) => void; +} + +export default class GlobalHeader extends Component { componentWillUnmount() { this.triggerResizeEvent.cancel(); } - /* eslint-disable*/ - @Debounce(600) - triggerResizeEvent() { + triggerResizeEvent = debounce(() => { // eslint-disable-line const event = document.createEvent('HTMLEvents'); event.initEvent('resize', true, false); window.dispatchEvent(event); - } + }); toggle = () => { const { collapsed, onCollapse } = this.props; onCollapse(!collapsed); diff --git a/src/components/HeaderDropdown/index.d.ts b/src/components/HeaderDropdown/index.d.ts deleted file mode 100644 index e9dac7e532570e2f85a6a220d5d4ff9b97194927..0000000000000000000000000000000000000000 --- a/src/components/HeaderDropdown/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import * as React from 'react'; -export default class HeaderDropdown extends React.Component {} diff --git a/src/components/HeaderDropdown/index.js b/src/components/HeaderDropdown/index.js deleted file mode 100644 index a19c471ac19c61933d6c4c79e0ced4b8f8b43fba..0000000000000000000000000000000000000000 --- a/src/components/HeaderDropdown/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import React, { PureComponent } from 'react'; -import { Dropdown } from 'antd'; -import classNames from 'classnames'; -import styles from './index.less'; - -export default class HeaderDropdown extends PureComponent { - render() { - const { overlayClassName, ...props } = this.props; - return ( - - ); - } -} diff --git a/src/components/HeaderDropdown/index.tsx b/src/components/HeaderDropdown/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b311061b0f06e54d14e085dc1f4d04754a9d8a8b --- /dev/null +++ b/src/components/HeaderDropdown/index.tsx @@ -0,0 +1,22 @@ +import React, { Component } from 'react'; +import { Dropdown } from 'antd'; +import classNames from 'classnames'; +import styles from './index.less'; + +declare type OverlayFunc = () => React.ReactNode; + +interface HeaderDropdownProps { + overlayClassName?: string; + overlay: React.ReactNode | OverlayFunc; + placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter'; +} + +export default class HeaderDropdown extends Component { + render() { + const { overlayClassName, ...props } = this.props; + + return ( + + ); + } +} diff --git a/src/components/HeaderSearch/index.d.ts b/src/components/HeaderSearch/index.d.ts deleted file mode 100644 index d78fde471ccd522d821f77b790647f5620efdb7e..0000000000000000000000000000000000000000 --- a/src/components/HeaderSearch/index.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from 'react'; -export interface IHeaderSearchProps { - placeholder?: string; - dataSource?: string[]; - defaultOpen?: boolean; - open?: boolean; - onSearch?: (value: string) => void; - onChange?: (value: string) => void; - onVisibleChange?: (visible: boolean) => void; - onPressEnter?: (value: string) => void; - style?: React.CSSProperties; - className?: string; -} - -export default class HeaderSearch extends React.Component {} diff --git a/src/components/HeaderSearch/index.js b/src/components/HeaderSearch/index.tsx similarity index 83% rename from src/components/HeaderSearch/index.js rename to src/components/HeaderSearch/index.tsx index 04f8b38ec8ff2e1c06455363a91d3e1e8f64aa9e..34de1db7eadbfdb0c2913aa24ca530f812049963 100644 --- a/src/components/HeaderSearch/index.js +++ b/src/components/HeaderSearch/index.tsx @@ -1,24 +1,30 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; +import React, { Component } from 'react'; import { Input, Icon, AutoComplete } from 'antd'; +import InputProps from 'antd/es/input'; + import classNames from 'classnames'; import Debounce from 'lodash-decorators/debounce'; import Bind from 'lodash-decorators/bind'; import styles from './index.less'; -export default class HeaderSearch extends PureComponent { - static propTypes = { - className: PropTypes.string, - placeholder: PropTypes.string, - onSearch: PropTypes.func, - onChange: PropTypes.func, - onPressEnter: PropTypes.func, - defaultActiveFirstOption: PropTypes.bool, - dataSource: PropTypes.array, - defaultOpen: PropTypes.bool, - onVisibleChange: PropTypes.func, - }; +interface HeaderSearchProps { + onPressEnter: (value: string) => void; + onSearch: (value: string) => void; + onChange: (value: string) => void; + onVisibleChange: (b: boolean) => void; + className: string; + placeholder: string; + defaultActiveFirstOption: boolean; + dataSource: any[]; + defaultOpen: boolean; + open?: boolean; +} +interface HeaderSearchState { + value: string; + searchMode: boolean; +} +export default class HeaderSearch extends Component { static defaultProps = { defaultActiveFirstOption: false, onPressEnter: () => {}, @@ -40,6 +46,8 @@ export default class HeaderSearch extends PureComponent { return null; } + timeout: NodeJS.Timeout; + input: InputProps; constructor(props) { super(props); this.state = { diff --git a/src/components/IconFont/index.js b/src/components/IconFont/index.tsx similarity index 59% rename from src/components/IconFont/index.js rename to src/components/IconFont/index.tsx index 0b99dec3a7996eca278585cc961e0683b5d1cbcd..c438b44acb65c4ff5b1be21f7b846cf187c5e611 100644 --- a/src/components/IconFont/index.js +++ b/src/components/IconFont/index.tsx @@ -1,6 +1,8 @@ import { Icon } from 'antd'; -import { iconfontUrl as scriptUrl } from '../../defaultSettings'; +import defaultSettings from '../../../config/defaultSettings'; +const { iconfontUrl } = defaultSettings; +const scriptUrl = iconfontUrl; // 使用: // import IconFont from '@/components/IconFont'; // diff --git a/src/components/PageLoading/index.js b/src/components/PageLoading/index.tsx similarity index 79% rename from src/components/PageLoading/index.js rename to src/components/PageLoading/index.tsx index 77c0f165f9d20b4f974e754efb9cf08606c41a49..24160279df000a38915a9d75b57e4e9d824c5a6c 100644 --- a/src/components/PageLoading/index.js +++ b/src/components/PageLoading/index.tsx @@ -3,8 +3,9 @@ import { Spin } from 'antd'; // loading components from code split // https://umijs.org/plugin/umi-plugin-react.html#dynamicimport -export default () => ( +const PageLoding: React.SFC = () => (
); +export default PageLoding; diff --git a/src/components/SelectLang/index.js b/src/components/SelectLang/index.js deleted file mode 100644 index f6abe2f279ef9c8c34463406f0c2a94503e6650d..0000000000000000000000000000000000000000 --- a/src/components/SelectLang/index.js +++ /dev/null @@ -1,49 +0,0 @@ -import React, { PureComponent } from 'react'; -import { formatMessage, setLocale, getLocale } from 'umi/locale'; -import { Menu, Icon } from 'antd'; -import classNames from 'classnames'; -import HeaderDropdown from '../HeaderDropdown'; -import styles from './index.less'; - -export default class SelectLang extends PureComponent { - changeLang = ({ key }) => { - setLocale(key); - }; - - render() { - const { className } = this.props; - const selectedLang = getLocale(); - const locales = ['zh-CN', 'zh-TW', 'en-US', 'pt-BR']; - const languageLabels = { - 'zh-CN': '简体中文', - 'zh-TW': '繁体中文', - 'en-US': 'English', - 'pt-BR': 'Português', - }; - const languageIcons = { - 'zh-CN': '🇨🇳', - 'zh-TW': '🇭🇰', - 'en-US': '🇬🇧', - 'pt-BR': '🇧🇷', - }; - const langMenu = ( - - {locales.map(locale => ( - - - {languageIcons[locale]} - {' '} - {languageLabels[locale]} - - ))} - - ); - return ( - - - - - - ); - } -} diff --git a/src/components/SelectLang/index.tsx b/src/components/SelectLang/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b145e39bb591fcdac93955956c98643b1194e9ed --- /dev/null +++ b/src/components/SelectLang/index.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { formatMessage, setLocale, getLocale } from 'umi-plugin-locale'; +import { Menu, Icon } from 'antd'; +import classNames from 'classnames'; +import HeaderDropdown from '../HeaderDropdown'; +import styles from './index.less'; + +interface SelectLangProps { + className?: string; +} +const SelectLang: React.SFC = props => { + const { className } = props; + const selectedLang = getLocale(); + const changeLang = ({ key }) => { + setLocale(key); + }; + const locales = ['zh-CN', 'zh-TW', 'en-US', 'pt-BR']; + const languageLabels = { + 'zh-CN': '简体中文', + 'zh-TW': '繁体中文', + 'en-US': 'English', + 'pt-BR': 'Português', + }; + const languageIcons = { + 'zh-CN': '🇨🇳', + 'zh-TW': '🇭🇰', + 'en-US': '🇬🇧', + 'pt-BR': '🇧🇷', + }; + const langMenu = ( + + {locales.map(locale => ( + + + {languageIcons[locale]} + {' '} + {languageLabels[locale]} + + ))} + + ); + return ( + + + + + + ); +}; + +export default SelectLang; diff --git a/src/components/SettingDrawer/BlockCheckbox.js b/src/components/SettingDrawer/BlockCheckbox.tsx similarity index 77% rename from src/components/SettingDrawer/BlockCheckbox.js rename to src/components/SettingDrawer/BlockCheckbox.tsx index 49af42c730224cc76b9cac47f87c91cca0ef6da9..84800f89456f0f7316fd0e2e58da031cd7a442f8 100644 --- a/src/components/SettingDrawer/BlockCheckbox.js +++ b/src/components/SettingDrawer/BlockCheckbox.tsx @@ -2,7 +2,12 @@ import React from 'react'; import { Tooltip, Icon } from 'antd'; import style from './index.less'; -const BlockChecbox = ({ value, onChange, list }) => ( +interface BlockChecboxProps { + value: string; + onChange: (key: string) => void; + list: any[]; +} +const BlockChecbox: React.SFC = ({ value, onChange, list }) => (
{list.map(item => ( diff --git a/src/components/SettingDrawer/ThemeColor.js b/src/components/SettingDrawer/ThemeColor.tsx similarity index 76% rename from src/components/SettingDrawer/ThemeColor.js rename to src/components/SettingDrawer/ThemeColor.tsx index e5d66d4be5cb29a2684eff46734e06fba17d6923..ac6cd2bbbc619f05a6ff478d6b49b109cbc41249 100644 --- a/src/components/SettingDrawer/ThemeColor.js +++ b/src/components/SettingDrawer/ThemeColor.tsx @@ -1,9 +1,15 @@ import React from 'react'; import { Tooltip, Icon } from 'antd'; -import { formatMessage } from 'umi/locale'; +import { formatMessage } from 'umi-plugin-locale'; import styles from './ThemeColor.less'; -const Tag = ({ color, check, ...rest }) => ( +interface TagProps { + color: string; + check: boolean; + className?: string; + onClick?: () => void; +} +const Tag: React.SFC = ({ color, check, ...rest }) => (
(
); -const ThemeColor = ({ colors, title, value, onChange }) => { +interface ThemeColorProps { + colors?: any[]; + title?: string; + value: string; + onChange: (color: string) => void; +} + +const ThemeColor: React.SFC = ({ colors, title, value, onChange }) => { let colorList = colors; if (!colors) { colorList = [ diff --git a/src/components/SettingDrawer/index.js b/src/components/SettingDrawer/index.tsx similarity index 92% rename from src/components/SettingDrawer/index.js rename to src/components/SettingDrawer/index.tsx index cdb28aa3c8f29fca78d2bf9dc296d711794ca3be..d4932d967a3561604c0369042afd7d0dca556602 100644 --- a/src/components/SettingDrawer/index.js +++ b/src/components/SettingDrawer/index.tsx @@ -1,16 +1,21 @@ -import React, { PureComponent } from 'react'; +import React, { Component } from 'react'; import { Select, message, Drawer, List, Switch, Divider, Icon, Button, Alert, Tooltip } from 'antd'; -import { formatMessage } from 'umi/locale'; +import { formatMessage } from 'umi-plugin-locale'; import { CopyToClipboard } from 'react-copy-to-clipboard'; import { connect } from 'dva'; import omit from 'omit.js'; import styles from './index.less'; import ThemeColor from './ThemeColor'; import BlockCheckbox from './BlockCheckbox'; +import { DefaultSettings } from '../../../config/defaultSettings'; const { Option } = Select; +interface BodyProps { + title: string; + style?: React.CSSProperties; +} -const Body = ({ children, title, style }) => ( +const Body: React.SFC = ({ children, title, style }) => (
(
); +interface SettingDrawerProps { + setting?: DefaultSettings; + dispatch?: (args: any) => void; +} +interface SettingDrawerState {} + @connect(({ setting }) => ({ setting })) -class SettingDrawer extends PureComponent { +class SettingDrawer extends Component { state = { collapse: false, }; @@ -120,7 +131,7 @@ class SettingDrawer extends PureComponent { return ( - {item.title} + {item.title} ); diff --git a/src/components/SiderMenu/BaseMenu.js b/src/components/SiderMenu/BaseMenu.tsx similarity index 80% rename from src/components/SiderMenu/BaseMenu.js rename to src/components/SiderMenu/BaseMenu.tsx index 9ab66ec3c03111edb1920d7d61e14dfef92838a2..991f03cfe161ee30a57bbd9e55ee19a51df58364 100644 --- a/src/components/SiderMenu/BaseMenu.js +++ b/src/components/SiderMenu/BaseMenu.tsx @@ -1,12 +1,13 @@ -import React, { PureComponent } from 'react'; +import IconFont from '@/components/IconFont'; +import { isUrl } from '@/utils/utils'; +import { Icon, Menu } from 'antd'; import classNames from 'classnames'; -import { Menu, Icon } from 'antd'; +import * as H from 'history'; +import React, { Component } from 'react'; import Link from 'umi/link'; import { urlToList } from '../_utils/pathTools'; -import { getMenuMatches } from './SiderMenuUtils'; -import { isUrl } from '@/utils/utils'; import styles from './index.less'; -import IconFont from '@/components/IconFont'; +import { getMenuMatches } from './SiderMenuUtils'; const { SubMenu } = Menu; @@ -28,12 +29,39 @@ const getIcon = icon => { return icon; }; -export default class BaseMenu extends PureComponent { +export declare type CollapseType = 'clickTrigger' | 'responsive'; +export declare type SiderTheme = 'light' | 'dark'; +export declare type MenuMode = + | 'vertical' + | 'vertical-left' + | 'vertical-right' + | 'horizontal' + | 'inline'; + +interface BaseMenuProps { + flatMenuKeys?: any[]; + location?: H.Location; + onCollapse?: (collapsed: boolean, type?: CollapseType) => void; + isMobile?: boolean; + openKeys?: any; + theme?: SiderTheme; + mode?: MenuMode; + className?: string; + collapsed?: boolean; + handleOpenChange?: (openKeys: any[]) => void; + menuData?: any[]; + style?: React.CSSProperties; + onOpenChange?: (openKeys: string[]) => void; +} + +interface BaseMenuState {} + +export default class BaseMenu extends Component { /** * 获得菜单子节点 * @memberof SiderMenu */ - getNavMenuItems = menusData => { + getNavMenuItems: (menusData: any[]) => any[] = menusData => { if (!menusData) { return []; } @@ -131,6 +159,9 @@ export default class BaseMenu extends PureComponent { location: { pathname }, className, collapsed, + handleOpenChange, + style, + menuData, } = this.props; // if pathname can't match, use the nearest parent's key let selectedKeys = this.getSelectedMenuKeys(pathname); @@ -143,7 +174,6 @@ export default class BaseMenu extends PureComponent { openKeys: openKeys.length === 0 ? [...selectedKeys] : openKeys, }; } - const { handleOpenChange, style, menuData } = this.props; const cls = classNames(className, { 'top-nav-menu': mode === 'horizontal', }); diff --git a/src/components/SiderMenu/SiderMenu.test.js b/src/components/SiderMenu/SiderMenu.test.js deleted file mode 100644 index 3d280da08a8510cf056d46d9e239fbbdacc44001..0000000000000000000000000000000000000000 --- a/src/components/SiderMenu/SiderMenu.test.js +++ /dev/null @@ -1,39 +0,0 @@ -import { getFlatMenuKeys } from './SiderMenuUtils'; - -const menu = [ - { - path: '/dashboard', - children: [ - { - path: '/dashboard/name', - }, - ], - }, - { - path: '/userinfo', - children: [ - { - path: '/userinfo/:id', - children: [ - { - path: '/userinfo/:id/info', - }, - ], - }, - ], - }, -]; - -const flatMenuKeys = getFlatMenuKeys(menu); - -describe('test convert nested menu to flat menu', () => { - it('simple menu', () => { - expect(flatMenuKeys).toEqual([ - '/dashboard', - '/dashboard/name', - '/userinfo', - '/userinfo/:id', - '/userinfo/:id/info', - ]); - }); -}); diff --git a/src/components/SiderMenu/SiderMenu.js b/src/components/SiderMenu/SiderMenu.tsx similarity index 72% rename from src/components/SiderMenu/SiderMenu.js rename to src/components/SiderMenu/SiderMenu.tsx index da9520faa8af841447db173ef98c2f9c544d2f4a..8a55f897d65ab31b249bcf688a1ae33858e3f619 100644 --- a/src/components/SiderMenu/SiderMenu.js +++ b/src/components/SiderMenu/SiderMenu.tsx @@ -1,29 +1,39 @@ -import React, { PureComponent, Suspense } from 'react'; import { Layout } from 'antd'; import classNames from 'classnames'; +import * as H from 'history'; +import React, { Component, Suspense } from 'react'; import Link from 'umi/link'; -import styles from './index.less'; +import defaultSettings from '../../../config/defaultSettings'; import PageLoading from '../PageLoading'; +import styles from './index.less'; import { getDefaultCollapsedSubMenus } from './SiderMenuUtils'; -import { title } from '../../defaultSettings'; const BaseMenu = React.lazy(() => import('./BaseMenu')); const { Sider } = Layout; +const { title } = defaultSettings; +let firstMount: boolean = true; -let firstMount = true; +export declare type CollapseType = 'clickTrigger' | 'responsive'; +export declare type SiderTheme = 'light' | 'dark'; -export default class SiderMenu extends PureComponent { - constructor(props) { - super(props); - this.state = { - openKeys: getDefaultCollapsedSubMenus(props), - }; - } +interface SiderMenuProps { + menuData: any[]; + location?: H.Location; + flatMenuKeys?: any[]; + logo?: string; + collapsed: boolean; + onCollapse: (collapsed: boolean, type?: CollapseType) => void; + fixSiderbar?: boolean; + theme?: SiderTheme; + isMobile: boolean; +} - componentDidMount() { - firstMount = false; - } +interface SiderMenuState { + openKeys: any; + flatMenuKeysLen?: number; +} +export default class SiderMenu extends Component { static getDerivedStateFromProps(props, state) { const { pathname, flatMenuKeysLen } = state; if (props.location.pathname !== pathname || props.flatMenuKeys.length !== flatMenuKeysLen) { @@ -35,8 +45,18 @@ export default class SiderMenu extends PureComponent { } return null; } + constructor(props: SiderMenuProps) { + super(props); + this.state = { + openKeys: getDefaultCollapsedSubMenus(props), + }; + } + + componentDidMount() { + firstMount = false; + } - isMainMenu = key => { + isMainMenu: (key: string) => boolean = key => { const { menuData } = this.props; return menuData.some(item => { if (key) { @@ -46,7 +66,7 @@ export default class SiderMenu extends PureComponent { }); }; - handleOpenChange = openKeys => { + handleOpenChange: (openKeys: any[]) => void = openKeys => { const moreThanOne = openKeys.filter(openKey => this.isMainMenu(openKey)).length > 1; this.setState({ openKeys: moreThanOne ? [openKeys.pop()] : [...openKeys], @@ -65,7 +85,7 @@ export default class SiderMenu extends PureComponent { return ( { diff --git a/src/components/SiderMenu/SiderMenuUtils.js b/src/components/SiderMenu/SiderMenuUtils.ts similarity index 99% rename from src/components/SiderMenu/SiderMenuUtils.js rename to src/components/SiderMenu/SiderMenuUtils.ts index 6e04ec134d76020bc902437632f136fa99a65098..0c398b88efc3bd9910e5e0c984f92307d6a255c7 100644 --- a/src/components/SiderMenu/SiderMenuUtils.js +++ b/src/components/SiderMenu/SiderMenuUtils.ts @@ -24,6 +24,7 @@ export const getMenuMatches = (flatMenuKeys, path) => } return false; }); + /** * 获得菜单子节点 * @memberof SiderMenu diff --git a/src/components/SiderMenu/index.js b/src/components/SiderMenu/index.tsx similarity index 65% rename from src/components/SiderMenu/index.js rename to src/components/SiderMenu/index.tsx index 0be273314d593c01b54cd127b39c97d55833a990..9716eb74e2f0fe512cf801991c0716673c6a5dbe 100644 --- a/src/components/SiderMenu/index.js +++ b/src/components/SiderMenu/index.tsx @@ -3,7 +3,18 @@ import { Drawer } from 'antd'; import SiderMenu from './SiderMenu'; import { getFlatMenuKeys } from './SiderMenuUtils'; -const SiderMenuWrapper = React.memo(props => { +export declare type SiderTheme = 'light' | 'dark'; + +interface SiderMenuProps { + isMobile: boolean; + menuData: any[]; + collapsed: boolean; + logo?: string; + theme?: SiderTheme; + onCollapse: (payload: boolean) => void; +} + +const SiderMenuWrapper: React.SFC = props => { const { isMobile, menuData, collapsed, onCollapse } = props; const flatMenuKeys = getFlatMenuKeys(menuData); return isMobile ? ( @@ -21,6 +32,6 @@ const SiderMenuWrapper = React.memo(props => { ) : ( ); -}); +}; -export default SiderMenuWrapper; +export default React.memo(SiderMenuWrapper); diff --git a/src/components/TopNavHeader/index.js b/src/components/TopNavHeader/index.tsx similarity index 56% rename from src/components/TopNavHeader/index.js rename to src/components/TopNavHeader/index.tsx index 2d0e06561e0ef053594f157cd428b68874cc951d..a1b610b3fdccbee6fb143eddf695ef790fcb1b81 100644 --- a/src/components/TopNavHeader/index.js +++ b/src/components/TopNavHeader/index.tsx @@ -1,16 +1,52 @@ -import React, { PureComponent } from 'react'; +import React, { Component } from 'react'; import Link from 'umi/link'; import RightContent from '../GlobalHeader/RightContent'; import BaseMenu from '../SiderMenu/BaseMenu'; import { getFlatMenuKeys } from '../SiderMenu/SiderMenuUtils'; import styles from './index.less'; -import { title } from '../../defaultSettings'; +import defaultSettings from '../../../config/defaultSettings'; -export default class TopNavHeader extends PureComponent { +export declare type CollapseType = 'clickTrigger' | 'responsive'; +export declare type SiderTheme = 'light' | 'dark'; +export declare type MenuMode = + | 'vertical' + | 'vertical-left' + | 'vertical-right' + | 'horizontal' + | 'inline'; + +const { title } = defaultSettings; +interface TopNavHeaderProps { + theme: SiderTheme; + contentWidth?: string; + menuData?: any[]; + logo?: string; + mode?: MenuMode; + flatMenuKeys?: any[]; + onCollapse?: (collapsed: boolean, type?: CollapseType) => void; + isMobile?: boolean; + openKeys?: any; + className?: string; + collapsed?: boolean; + handleOpenChange?: (openKeys: any[]) => void; + style?: React.CSSProperties; + onOpenChange?: (openKeys: string[]) => void; + onNoticeClear?: (type: string) => void; + onMenuClick?: ({ key: string }) => void; + onNoticeVisibleChange?: (b: boolean) => void; +} + +interface TopNavHeaderState { + maxWidth: undefined | number; +} + +export default class TopNavHeader extends Component { state = { maxWidth: undefined, }; + maim: HTMLDivElement; + static getDerivedStateFromProps(props) { return { maxWidth: (props.contentWidth === 'Fixed' ? 1200 : window.innerWidth) - 280 - 165 - 40, diff --git a/src/components/_utils/pathTools.test.js b/src/components/_utils/pathTools.test.js deleted file mode 100644 index a9b93155176fb9cd8a7eaf088e6ba50c9103e512..0000000000000000000000000000000000000000 --- a/src/components/_utils/pathTools.test.js +++ /dev/null @@ -1,17 +0,0 @@ -import { urlToList } from './pathTools'; - -describe('test urlToList', () => { - it('A path', () => { - expect(urlToList('/userinfo')).toEqual(['/userinfo']); - }); - it('Secondary path', () => { - expect(urlToList('/userinfo/2144')).toEqual(['/userinfo', '/userinfo/2144']); - }); - it('Three paths', () => { - expect(urlToList('/userinfo/2144/addr')).toEqual([ - '/userinfo', - '/userinfo/2144', - '/userinfo/2144/addr', - ]); - }); -}); diff --git a/src/components/_utils/pathTools.js b/src/components/_utils/pathTools.ts similarity index 100% rename from src/components/_utils/pathTools.js rename to src/components/_utils/pathTools.ts diff --git a/src/global.js b/src/global.tsx similarity index 90% rename from src/global.js rename to src/global.tsx index bebe4829c63d4f4a47f4aa7bf649f23e2deef90d..65e9b0dbe719b223166fa690e05814628202783e 100644 --- a/src/global.js +++ b/src/global.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { notification, Button, message } from 'antd'; -import { formatMessage } from 'umi/locale'; -import defaultSettings from './defaultSettings'; +import { formatMessage } from 'umi-plugin-locale'; +import defaultSettings from '../config/defaultSettings'; -window.React = React; +(window as any).React = React; const { pwa } = defaultSettings; // if pwa is true @@ -14,7 +14,7 @@ if (pwa) { }); // Pop up a prompt on the page asking the user if they want to use the latest version - window.addEventListener('sw.updated', e => { + window.addEventListener('sw.updated', (e: CustomEvent) => { const reloadSW = async () => { // Check if there is sw whose state is waiting in ServiceWorkerRegistration // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration diff --git a/src/layouts/BasicLayout.js b/src/layouts/BasicLayout.js deleted file mode 100644 index 06cc41fce542df11b491a8f244f559a8936f7b1e..0000000000000000000000000000000000000000 --- a/src/layouts/BasicLayout.js +++ /dev/null @@ -1,173 +0,0 @@ -import React, { Suspense } from 'react'; -import { Layout } from 'antd'; -import DocumentTitle from 'react-document-title'; -import { connect } from 'dva'; -import { ContainerQuery } from 'react-container-query'; -import classNames from 'classnames'; -import Media from 'react-media'; -import logo from '../assets/logo.svg'; -import Footer from './Footer'; -import Header from './Header'; -import Context from './MenuContext'; -import PageLoading from '@/components/PageLoading'; -import SiderMenu from '@/components/SiderMenu'; -import getPageTitle from '@/utils/getPageTitle'; -import styles from './BasicLayout.less'; - -// lazy load SettingDrawer -const SettingDrawer = React.lazy(() => import('@/components/SettingDrawer')); - -const { Content } = Layout; - -const query = { - 'screen-xs': { - maxWidth: 575, - }, - 'screen-sm': { - minWidth: 576, - maxWidth: 767, - }, - 'screen-md': { - minWidth: 768, - maxWidth: 991, - }, - 'screen-lg': { - minWidth: 992, - maxWidth: 1199, - }, - 'screen-xl': { - minWidth: 1200, - maxWidth: 1599, - }, - 'screen-xxl': { - minWidth: 1600, - }, -}; - -class BasicLayout extends React.Component { - componentDidMount() { - const { - dispatch, - route: { routes, authority }, - } = this.props; - dispatch({ - type: 'user/fetchCurrent', - }); - dispatch({ - type: 'setting/getSetting', - }); - dispatch({ - type: 'menu/getMenuData', - payload: { routes, authority }, - }); - } - - getContext() { - const { location, breadcrumbNameMap } = this.props; - return { - location, - breadcrumbNameMap, - }; - } - - getLayoutStyle = () => { - const { fixSiderbar, isMobile, collapsed, layout } = this.props; - if (fixSiderbar && layout !== 'topmenu' && !isMobile) { - return { - paddingLeft: collapsed ? '80px' : '256px', - }; - } - return null; - }; - - handleMenuCollapse = collapsed => { - const { dispatch } = this.props; - dispatch({ - type: 'global/changeLayoutCollapsed', - payload: collapsed, - }); - }; - - renderSettingDrawer = () => { - // Do not render SettingDrawer in production - // unless it is deployed in preview.pro.ant.design as demo - if (process.env.NODE_ENV === 'production' && APP_TYPE !== 'site') { - return null; - } - return ; - }; - - render() { - const { - navTheme, - layout: PropsLayout, - children, - location: { pathname }, - isMobile, - menuData, - breadcrumbNameMap, - fixedHeader, - } = this.props; - - const isTop = PropsLayout === 'topmenu'; - const contentStyle = !fixedHeader ? { paddingTop: 0 } : {}; - const layout = ( - - {isTop && !isMobile ? null : ( - - )} - -
- - {children} - -