Browse Source

fix ts file error for eslint

pull/4336/head
陈帅 7 years ago
parent
commit
9770eccb10
  1. 44
      .eslintrc.js
  2. 3
      .gitignore
  3. 42
      package.json
  4. 14
      src/components/Authorized/Authorized.tsx
  5. 4
      src/components/Authorized/AuthorizedRoute.tsx
  6. 18
      src/components/Authorized/PromiseRender.tsx
  7. 5
      src/components/Authorized/renderAuthorize.ts
  8. 17
      src/components/CopyBlock/index.tsx
  9. 4
      src/components/GlobalFooter/index.tsx
  10. 3
      src/components/GlobalHeader/AvatarDropdown.tsx
  11. 62
      src/components/GlobalHeader/NoticeIconView.tsx
  12. 102
      src/components/GlobalHeader/RightContent.tsx
  13. 5
      src/components/HeaderSearch/index.tsx
  14. 77
      src/components/NoticeIcon/index.tsx
  15. 2
      src/components/SelectLang/index.tsx
  16. 13
      src/global.tsx
  17. 3
      src/layouts/BasicLayout.tsx
  18. 80
      src/layouts/UserLayout.tsx
  19. 37
      src/models/connect.d.ts
  20. 24
      src/models/global.ts
  21. 2
      src/models/login.ts
  22. 5
      src/models/setting.ts
  23. 8
      src/pages/Authorized.tsx
  24. 2
      src/pages/Welcome.tsx
  25. 5
      src/service-worker.js
  26. 9
      src/utils/Authorized.ts
  27. 3
      src/utils/authority.test.ts
  28. 4
      src/utils/authority.ts
  29. 24
      src/utils/request.ts
  30. 9
      src/utils/utils.test.ts
  31. 4
      src/utils/utils.ts
  32. 6
      tests/run-tests.js
  33. 88
      tslint.yml

44
.eslintrc.js

@ -1,6 +1,17 @@
module.exports = {
parser: 'babel-eslint',
extends: ['airbnb', 'prettier', 'plugin:compat/recommended'],
extends: [
'airbnb',
'prettier',
'plugin:compat/recommended',
'airbnb-typescript',
'plugin:@typescript-eslint/recommended',
'plugin:eslint-comments/recommended',
'plugin:jest/recommended',
'plugin:promise/recommended',
'prettier',
'prettier/react',
'prettier/@typescript-eslint',
],
env: {
browser: true,
node: true,
@ -10,8 +21,8 @@ module.exports = {
jasmine: true,
},
globals: {
page: true,
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true,
page: true,
},
rules: {
'react/jsx-filename-extension': [1, { extensions: ['.js'] }],
@ -27,16 +38,39 @@ module.exports = {
devDependencies: ['**/tests/**.js', '/mock/**/**.js', '**/**.test.js'],
},
],
'import/no-cycle': 0,
'jsx-a11y/no-noninteractive-element-interactions': 0,
'jsx-a11y/click-events-have-key-events': 0,
'jsx-a11y/no-static-element-interactions': 0,
'jsx-a11y/anchor-is-valid': 0,
'linebreak-style': 0,
// Too restrictive, writing ugly code to defend against a very unlikely scenario: https://eslint.org/docs/rules/no-prototype-builtins
'no-prototype-builtins': 'off',
'import/prefer-default-export': 'off',
'import/no-default-export': [true, 'camel-case'],
// Too restrictive: https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/destructuring-assignment.md
'react/destructuring-assignment': 'off',
// No jsx extension: https://github.com/facebook/create-react-app/issues/87#issuecomment-234627904
'react/jsx-filename-extension': 'off',
// Use function hoisting to improve code readability
'no-use-before-define': ['error', { functions: false, classes: true, variables: true }],
// Makes no sense to allow type inferrence for expression parameters, but require typing the response
'@typescript-eslint/explicit-function-return-type': [
'off',
{ allowTypedFunctionExpressions: true },
],
'@typescript-eslint/no-use-before-define': [
'error',
{ functions: false, classes: true, variables: true, typedefs: true },
],
// Common abbreviations are known and readable
'unicorn/prevent-abbreviations': 'off',
'@typescript-eslint/explicit-member-accessibility': 0,
'import/no-cycle': 0,
},
plugins: ['@typescript-eslint', 'eslint-comments', 'jest', 'promise', 'unicorn'],
settings: {
// support import modules from TypeScript files in JavaScript files
'import/resolver': { node: { extensions: ['.js', '.ts', '.tsx'] } },
polyfills: ['fetch', 'promises', 'url', 'object-assign'],
polyfills: ['fetch', 'Promise', 'URL', 'object-assign'],
},
};

3
.gitignore

@ -34,4 +34,5 @@ functions/*
# screenshot
screenshot
.firebase
.firebase
.eslintcache

42
package.json

@ -19,21 +19,18 @@
"generateMock": "node ./scripts/generateMock",
"lint": "npm run lint:js && npm run lint:ts && npm run lint:style && npm run lint:prettier",
"lint-staged": "lint-staged",
"lint-staged:js": "eslint --ext .js",
"lint-staged:ts": "tslint",
"lint:fix": "eslint --fix --ext .js src tests && npm run lint:style && npm run tslint:fix",
"lint:js": "eslint --ext .js src tests",
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ",
"lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src && npm run lint:style && npm run tslint:fix",
"lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
"lint:prettier": "check-prettier lint",
"lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
"lint:ts": "tslint -p . -c tslint.yml",
"prettier": " check-prettier write",
"site": "npm run fetch:blocks && npm run functions:build && umi build",
"start": "umi dev",
"start:no-mock": "cross-env MOCK=none umi dev",
"test": "umi test",
"test:all": "node ./tests/run-tests.js",
"test:component": "umi test ./src/components",
"tslint:fix": "tslint --fix \"src/**/*.ts*\""
"test:component": "umi test ./src/components"
},
"husky": {
"hooks": {
@ -42,12 +39,11 @@
},
"lint-staged": {
"**/*.less": "stylelint --syntax less",
"**/*.{js,jsx}": "npm run lint-staged:js",
"**/*.{js,ts,tsx}": "npm run lint-staged:js",
"**/*.{js,ts,tsx,md,json,jsx,less}": [
"npm run prettier",
"git add"
],
"**/*.{ts,tsx}": "npm run lint-staged:ts"
]
},
"browserslist": [
"> 1%",
@ -76,6 +72,7 @@
"react-dom": "^16.8.6",
"react-media": "^1.9.2",
"react-media-hook2": "^1.0.5",
"redux": "^4.0.1",
"umi": "^2.7.0-beta.2",
"umi-plugin-ga": "^1.1.3",
"umi-plugin-locale": "^2.8.0-beta.1",
@ -92,6 +89,7 @@
"@types/react": "^16.8.19",
"@types/react-document-title": "^2.0.3",
"@types/react-dom": "^16.8.4",
"@typescript-eslint/eslint-plugin": "^1.9.0",
"antd-pro-merge-less": "^1.0.0",
"antd-theme-webpack-plugin": "^1.2.0",
"babel-eslint": "^10.0.1",
@ -102,16 +100,26 @@
"enzyme": "^3.9.0",
"eslint": "^5.16.0",
"eslint-config-airbnb": "^17.1.0",
"eslint-config-prettier": "^4.3.0",
"eslint-config-airbnb-typescript": "^4.0.0",
"eslint-config-prettier": "^4.1.0",
"eslint-formatter-pretty": "^2.1.1",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-compat": "^3.1.1",
"eslint-plugin-eslint-comments": "^3.1.1",
"eslint-plugin-import": "^2.17.3",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-jest": "^22.4.1",
"eslint-plugin-jsx-a11y": "^6.2.0",
"eslint-plugin-markdown": "^1.0.0",
"eslint-plugin-react": "^7.13.0",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-react": "^7.12.4",
"eslint-plugin-unicorn": "^8.0.1",
"express": "^4.17.1",
"gh-pages": "^2.0.1",
"husky": "^2.3.0",
"import-sort-cli": "^6.0.0",
"import-sort-parser-babylon": "^6.0.0",
"import-sort-parser-typescript": "^6.0.0",
"import-sort-style-module": "^6.0.0",
"jest-puppeteer": "^4.2.0",
"jsdom-global": "^3.0.2",
"less": "^3.9.0",
@ -128,11 +136,7 @@
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^18.3.0",
"stylelint-declaration-block-no-ignored-properties": "^2.1.0",
"stylelint-order": "^3.0.0",
"tslint": "^5.17.0",
"tslint-config-prettier": "^1.18.0",
"tslint-eslint-rules": "^5.4.0",
"tslint-react": "^4.0.0"
"stylelint-order": "^3.0.0"
},
"optionalDependencies": {
"puppeteer": "^1.17.0"
@ -175,4 +179,4 @@
"create-umi"
]
}
}
}

14
src/components/Authorized/Authorized.tsx

@ -1,28 +1,26 @@
import CheckPermissions from './CheckPermissions';
import { IAuthorityType } from './CheckPermissions';
import React from 'react';
import Secured from './Secured';
import check from './CheckPermissions';
import check, { IAuthorityType } from './CheckPermissions';
import AuthorizedRoute from './AuthorizedRoute';
import React from 'react';
interface IAuthorizedProps {
interface AuthorizedProps {
authority: IAuthorityType;
noMatch?: React.ReactNode;
}
type IAuthorizedType = React.FunctionComponent<IAuthorizedProps> & {
type IAuthorizedType = React.FunctionComponent<AuthorizedProps> & {
Secured: typeof Secured;
check: typeof check;
AuthorizedRoute: typeof AuthorizedRoute;
};
const Authorized: React.FunctionComponent<IAuthorizedProps> = ({
const Authorized: React.FunctionComponent<AuthorizedProps> = ({
children,
authority,
noMatch = null,
}) => {
const childrenRender: React.ReactNode = typeof children === 'undefined' ? null : children;
const dom = CheckPermissions(authority, childrenRender, noMatch);
const dom = check(authority, childrenRender, noMatch);
return <>{dom}</>;
};

4
src/components/Authorized/AuthorizedRoute.tsx

@ -3,7 +3,7 @@ import { Route, Redirect } from 'umi';
import Authorized from './Authorized';
import { IAuthorityType } from './CheckPermissions';
interface IAuthorizedRoutePops {
interface AuthorizedRoutePops {
currentAuthority: string;
component: React.ComponentClass<any, any>;
render: (props: any) => React.ReactNode;
@ -11,7 +11,7 @@ interface IAuthorizedRoutePops {
authority: IAuthorityType;
}
const AuthorizedRoute: React.SFC<IAuthorizedRoutePops> = ({
const AuthorizedRoute: React.SFC<AuthorizedRoutePops> = ({
component: Component,
render,
authority,

18
src/components/Authorized/PromiseRender.tsx

@ -4,21 +4,21 @@ import React from 'react';
// eslint-disable-next-line import/no-cycle
import { isComponentClass } from './Secured';
interface IPromiseRenderProps<T, K> {
interface PromiseRenderProps<T, K> {
ok: T;
error: K;
promise: Promise<any>;
}
interface IPromiseRenderState {
interface PromiseRenderState {
component: React.ComponentClass<any, any> | React.FunctionComponent<any>;
}
export default class PromiseRender<T, K> extends React.Component<
IPromiseRenderProps<T, K>,
IPromiseRenderState
PromiseRenderProps<T, K>,
PromiseRenderState
> {
state: IPromiseRenderState = {
state: PromiseRenderState = {
component: () => null,
};
@ -26,10 +26,7 @@ export default class PromiseRender<T, K> extends React.Component<
this.setRenderComponent(this.props);
}
shouldComponentUpdate = (
nextProps: IPromiseRenderProps<T, K>,
nextState: IPromiseRenderState,
) => {
shouldComponentUpdate = (nextProps: PromiseRenderProps<T, K>, nextState: PromiseRenderState) => {
const { component } = this.state;
if (!isEqual(nextProps, this.props)) {
this.setRenderComponent(nextProps);
@ -39,7 +36,7 @@ export default class PromiseRender<T, K> extends React.Component<
};
// set render Component : ok or error
setRenderComponent(props: IPromiseRenderProps<T, K>) {
setRenderComponent(props: PromiseRenderProps<T, K>) {
const ok = this.checkIsInstantiation(props.ok);
const error = this.checkIsInstantiation(props.error);
props.promise
@ -47,6 +44,7 @@ export default class PromiseRender<T, K> extends React.Component<
this.setState({
component: ok,
});
return true;
})
.catch(() => {
this.setState({

5
src/components/Authorized/renderAuthorize.ts

@ -1,4 +1,7 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable import/no-mutable-exports */
let CURRENT: string | string[] = 'NULL';
type CurrentAuthorityType = string | string[] | (() => typeof CURRENT);
/**
* use authority or getAuthority
@ -6,7 +9,7 @@ type CurrentAuthorityType = string | string[] | (() => typeof CURRENT);
*/
const renderAuthorize = <T>(Authorized: T): ((currentAuthority: CurrentAuthorityType) => T) => (
currentAuthority: CurrentAuthorityType,
) => {
): T => {
if (currentAuthority) {
if (typeof currentAuthority === 'function') {
CURRENT = currentAuthority();

17
src/components/CopyBlock/index.tsx

@ -1,16 +1,15 @@
import React from 'react';
import { Icon, Typography, Popover } from 'antd';
import styles from './index.less';
import { connect } from 'dva';
import * as H from 'history';
import { FormattedMessage } from 'umi-plugin-react/locale';
import styles from './index.less';
const firstUpperCase = (pathString: string) => {
const firstUpperCase = (pathString: string): string => {
return pathString
.replace('.', '')
.split(/\/|\-/)
.map(s => s.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase()))
.filter(s => s)
.split(/\/|-/)
.map((s): string => s.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase()))
.filter((s): boolean => !!s)
.join('');
};
const BlockCodeView: React.SFC<{
@ -26,7 +25,11 @@ const BlockCodeView: React.SFC<{
);
};
type RoutingType = { location: H.Location };
interface RoutingType {
location: {
pathname: string;
};
}
export default connect(({ routing }: { routing: RoutingType }) => ({
location: routing.location,

4
src/components/GlobalFooter/index.tsx

@ -3,12 +3,12 @@ import classNames from 'classnames';
import styles from './index.less';
export interface GlobalFooterProps {
links?: Array<{
links?: {
key?: string;
title: React.ReactNode;
href: string;
blankTarget?: boolean;
}>;
}[];
copyright?: React.ReactNode;
style?: React.CSSProperties;
className?: string;

3
src/components/GlobalHeader/AvatarDropdown.tsx

@ -30,7 +30,8 @@ class AvatarDropdown extends React.Component<GlobalHeaderRightProps> {
}
router.push(`/account/${key}`);
};
render() {
render(): React.ReactNode {
const { currentUser = {}, menu } = this.props;
if (!menu) {
return (

62
src/components/GlobalHeader/NoticeIconView.tsx

@ -19,6 +19,37 @@ export interface GlobalHeaderRightProps extends ConnectProps {
}
class GlobalHeaderRight extends Component<GlobalHeaderRightProps> {
componentDidMount() {
const { dispatch } = this.props;
if (dispatch) {
dispatch({
type: 'global/fetchNotices',
});
}
}
changeReadState = (clickedItem: NoticeItem): void => {
const { id } = clickedItem;
const { dispatch } = this.props;
if (dispatch) {
dispatch({
type: 'global/changeNoticeReadState',
payload: id,
});
}
};
handleNoticeClear = (title: string, key: string) => {
const { dispatch } = this.props;
message.success(`${formatMessage({ id: 'component.noticeIcon.cleared' })} ${title}`);
if (dispatch) {
dispatch({
type: 'global/clearNotices',
payload: key,
});
}
};
getNoticeData = (): { [key: string]: NoticeItem[] } => {
const { notices = [] } = this.props;
if (notices.length === 0) {
@ -52,7 +83,8 @@ class GlobalHeaderRight extends Component<GlobalHeaderRightProps> {
getUnreadData = (noticeData: { [key: string]: NoticeItem[] }) => {
const unreadMsg: { [key: string]: number } = {};
Object.entries(noticeData).forEach(([key, value]) => {
Object.keys(noticeData).forEach(key => {
const value = noticeData[key];
if (!unreadMsg[key]) {
unreadMsg[key] = 0;
}
@ -63,34 +95,6 @@ class GlobalHeaderRight extends Component<GlobalHeaderRightProps> {
return unreadMsg;
};
changeReadState = (clickedItem: NoticeItem) => {
const { id } = clickedItem;
const { dispatch } = this.props;
if (dispatch) {
dispatch({
type: 'global/changeNoticeReadState',
payload: id,
});
}
};
componentDidMount() {
const { dispatch } = this.props;
if (dispatch) {
dispatch({
type: 'global/fetchNotices',
});
}
}
handleNoticeClear = (title: string, key: string) => {
const { dispatch } = this.props;
message.success(`${formatMessage({ id: 'component.noticeIcon.cleared' })} ${title}`);
if (dispatch) {
dispatch({
type: 'global/clearNotices',
payload: key,
});
}
};
render() {
const { currentUser, fetchingNotices, onNoticeVisibleChange } = this.props;
const noticeData = this.getNoticeData();

102
src/components/GlobalHeader/RightContent.tsx

@ -1,5 +1,5 @@
import { ConnectProps, ConnectState } from '@/models/connect';
import React, { Component } from 'react';
import React from 'react';
import { Icon, Tooltip } from 'antd';
import { formatMessage } from 'umi-plugin-react/locale';
import HeaderSearch from '../HeaderSearch';
@ -14,60 +14,58 @@ export interface GlobalHeaderRightProps extends ConnectProps {
layout: 'sidemenu' | 'topmenu';
}
class GlobalHeaderRight extends Component<GlobalHeaderRightProps> {
render() {
const { theme, layout } = this.props;
let className = styles.right;
const GlobalHeaderRight: React.SFC<GlobalHeaderRightProps> = props => {
const { theme, layout } = props;
let className = styles.right;
if (theme === 'dark' && layout === 'topmenu') {
className = `${styles.right} ${styles.dark}`;
}
if (theme === 'dark' && layout === 'topmenu') {
className = `${styles.right} ${styles.dark}`;
}
return (
<div className={className}>
<HeaderSearch
className={`${styles.action} ${styles.search}`}
placeholder={formatMessage({
id: 'component.globalHeader.search',
})}
dataSource={[
formatMessage({
id: 'component.globalHeader.search.example1',
}),
formatMessage({
id: 'component.globalHeader.search.example2',
}),
formatMessage({
id: 'component.globalHeader.search.example3',
}),
]}
onSearch={value => {
console.log('input', value); // tslint:disable-line no-console
}}
onPressEnter={value => {
console.log('enter', value); // tslint:disable-line no-console
}}
/>
<Tooltip
title={formatMessage({
id: 'component.globalHeader.help',
})}
return (
<div className={className}>
<HeaderSearch
className={`${styles.action} ${styles.search}`}
placeholder={formatMessage({
id: 'component.globalHeader.search',
})}
dataSource={[
formatMessage({
id: 'component.globalHeader.search.example1',
}),
formatMessage({
id: 'component.globalHeader.search.example2',
}),
formatMessage({
id: 'component.globalHeader.search.example3',
}),
]}
onSearch={value => {
console.log('input', value);
}}
onPressEnter={value => {
console.log('enter', value);
}}
/>
<Tooltip
title={formatMessage({
id: 'component.globalHeader.help',
})}
>
<a
target="_blank"
href="https://pro.ant.design/docs/getting-started"
rel="noopener noreferrer"
className={styles.action}
>
<a
target="_blank"
href="https://pro.ant.design/docs/getting-started"
rel="noopener noreferrer"
className={styles.action}
>
<Icon type="question-circle-o" />
</a>
</Tooltip>
<Avatar />
<SelectLang className={styles.action} />
</div>
);
}
}
<Icon type="question-circle-o" />
</a>
</Tooltip>
<Avatar />
<SelectLang className={styles.action} />
</div>
);
};
export default connect(({ settings }: ConnectState) => ({
theme: settings.navTheme,

5
src/components/HeaderSearch/index.tsx

@ -45,7 +45,8 @@ export default class HeaderSearch extends Component<HeaderSearchProps, HeaderSea
return null;
}
private timeout: NodeJS.Timeout = null!;
private timeout: number | undefined = undefined;
private inputRef: Input | null = null;
constructor(props: HeaderSearchProps) {
@ -68,7 +69,7 @@ export default class HeaderSearch extends Component<HeaderSearchProps, HeaderSea
if (e.key === 'Enter') {
const { onPressEnter } = this.props;
const { value } = this.state;
this.timeout = setTimeout(() => {
this.timeout = window.setTimeout(() => {
onPressEnter(value); // Fix duplicate onPressEnter
}, 0);
}

77
src/components/NoticeIcon/index.tsx

@ -40,11 +40,11 @@ export default class NoticeIcon extends Component<NoticeIconProps> {
public static Tab: typeof NoticeList = NoticeList;
static defaultProps = {
onItemClick: () => {},
onPopupVisibleChange: () => {},
onTabChange: () => {},
onClear: () => {},
onViewMore: () => {},
onItemClick: (): void => {},
onPopupVisibleChange: (): void => {},
onTabChange: (): void => {},
onClear: (): void => {},
onViewMore: (): void => {},
loading: false,
clearClose: false,
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
@ -54,64 +54,67 @@ export default class NoticeIcon extends Component<NoticeIconProps> {
visible: false,
};
onItemClick = (item: NoticeIconData, tabProps: NoticeIconTabProps) => {
onItemClick = (item: NoticeIconData, tabProps: NoticeIconTabProps): void => {
const { onItemClick } = this.props;
if (onItemClick) {
onItemClick(item, tabProps);
}
};
onClear = (name: string, key: string) => {
onClear = (name: string, key: string): void => {
const { onClear } = this.props;
if (onClear) {
onClear(name, key);
}
};
onTabChange = (tabType: string) => {
onTabChange = (tabType: string): void => {
const { onTabChange } = this.props;
if (onTabChange) {
onTabChange(tabType);
}
};
onViewMore = (tabProps: NoticeIconTabProps, event: MouseEvent) => {
onViewMore = (tabProps: NoticeIconTabProps, event: MouseEvent): void => {
const { onViewMore } = this.props;
if (onViewMore) {
onViewMore(tabProps, event);
}
};
getNotificationBox() {
getNotificationBox(): React.ReactNode {
const { children, loading, clearText, viewMoreText } = this.props;
if (!children) {
return null;
}
const panes = React.Children.map(children, (child: React.ReactElement<NoticeIconTabProps>) => {
if (!child) {
return null;
}
const { list, title, count, tabKey, showClear, showViewMore } = child.props;
const len = list && list.length ? list.length : 0;
const msgCount = count || count === 0 ? count : len;
const tabTitle: string = msgCount > 0 ? `${title} (${msgCount})` : title;
return (
<TabPane tab={tabTitle} key={title}>
<NoticeList
clearText={clearText}
viewMoreText={viewMoreText}
data={list}
onClear={() => this.onClear(title, tabKey)}
onClick={item => this.onItemClick(item, child.props)}
onViewMore={event => this.onViewMore(child.props, event)}
showClear={showClear}
showViewMore={showViewMore}
title={title}
{...child.props}
/>
</TabPane>
);
});
const panes = React.Children.map(
children,
(child: React.ReactElement<NoticeIconTabProps>): React.ReactNode => {
if (!child) {
return null;
}
const { list, title, count, tabKey, showClear, showViewMore } = child.props;
const len = list && list.length ? list.length : 0;
const msgCount = count || count === 0 ? count : len;
const tabTitle: string = msgCount > 0 ? `${title} (${msgCount})` : title;
return (
<TabPane tab={tabTitle} key={title}>
<NoticeList
clearText={clearText}
viewMoreText={viewMoreText}
data={list}
onClear={(): void => this.onClear(title, tabKey)}
onClick={(item): void => this.onItemClick(item, child.props)}
onViewMore={(event): void => this.onViewMore(child.props, event)}
showClear={showClear}
showViewMore={showViewMore}
title={title}
{...child.props}
/>
</TabPane>
);
},
);
return (
<Spin spinning={loading} delay={0}>
<Tabs className={styles.tabs} onChange={this.onTabChange}>
@ -121,7 +124,7 @@ export default class NoticeIcon extends Component<NoticeIconProps> {
);
}
handleVisibleChange = (visible: boolean) => {
handleVisibleChange = (visible: boolean): void => {
const { onPopupVisibleChange } = this.props;
this.setState({ visible });
if (onPopupVisibleChange) {
@ -129,7 +132,7 @@ export default class NoticeIcon extends Component<NoticeIconProps> {
}
};
render() {
render(): React.ReactNode {
const { className, count, popupVisible, bell } = this.props;
const { visible } = this.state;
const noticeButtonClass = classNames(className, styles.noticeButton);

2
src/components/SelectLang/index.tsx

@ -12,7 +12,7 @@ interface SelectLangProps {
const SelectLang: React.FC<SelectLangProps> = props => {
const { className } = props;
const selectedLang = getLocale();
const changeLang = ({ key }: ClickParam) => setLocale(key, false);
const changeLang = ({ key }: ClickParam): void => setLocale(key, false);
const locales = ['zh-CN', 'zh-TW', 'en-US', 'pt-BR'];
const languageLabels = {
'zh-CN': '简体中文',

13
src/global.tsx

@ -19,7 +19,7 @@ if (pwa) {
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
const worker = e.detail && e.detail.waiting;
if (!worker) {
return Promise.resolve();
return true;
}
// Send skip-waiting event to waiting SW with MessageChannel
await new Promise((resolve, reject) => {
@ -59,7 +59,12 @@ if (pwa) {
});
} else if ('serviceWorker' in navigator) {
// eslint-disable-next-line compat/compat
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
navigator.serviceWorker.ready
.then(registration => {
registration.unregister();
return true;
})
.catch(() => {
console.log('serviceWorker unregister error');
});
}

3
src/layouts/BasicLayout.tsx

@ -18,6 +18,7 @@ import {
Settings,
} from '@ant-design/pro-layout';
import Link from 'umi/link';
export interface BasicLayoutProps extends ProLayoutComponentsProps, ConnectProps {
breadcrumbNameMap: {
[path: string]: MenuDataItem;
@ -60,7 +61,7 @@ const BasicLayout: React.FC<BasicLayoutProps> = props => {
/**
* init variables
*/
const handleMenuCollapse = (payload: boolean) =>
const handleMenuCollapse = (payload: boolean): void =>
dispatch &&
dispatch({
type: 'global/changeLayoutCollapsed',

80
src/layouts/UserLayout.tsx

@ -2,7 +2,7 @@ import SelectLang from '@/components/SelectLang';
import GlobalFooter from '@/components/GlobalFooter';
import { ConnectProps } from '@/models/connect';
import { Icon } from 'antd';
import React, { Component, Fragment } from 'react';
import React, { Fragment } from 'react';
import DocumentTitle from 'react-document-title';
import { formatMessage } from 'umi-plugin-locale';
import Link from 'umi/link';
@ -36,48 +36,48 @@ const copyright = (
export interface UserLayoutProps extends ConnectProps {
breadcrumbNameMap: { [path: string]: MenuDataItem };
navTheme: string;
navTheme: 'dark' | 'light';
}
class UserLayout extends Component<UserLayoutProps> {
render() {
const {
route = {
routes: [],
},
} = this.props;
const { routes = [] } = route;
const { children, location } = this.props;
const { breadcrumb } = getMenuData(routes, this.props);
return (
<DocumentTitle
title={getPageTitle({
pathname: location!.pathname,
breadcrumb,
formatMessage,
})}
>
<div className={styles.container}>
<div className={styles.lang}>
<SelectLang />
</div>
<div className={styles.content}>
<div className={styles.top}>
<div className={styles.header}>
<Link to="/">
<img alt="logo" className={styles.logo} src={logo} />
<span className={styles.title}>Ant Design</span>
</Link>
</div>
<div className={styles.desc}>Ant Design 西 Web </div>
const UserLayout: React.SFC<UserLayoutProps> = props => {
const {
route = {
routes: [],
},
children,
location = {
pathname: '',
},
} = props;
const { routes = [] } = route;
const { breadcrumb } = getMenuData(routes, props);
return (
<DocumentTitle
title={getPageTitle({
pathname: location.pathname,
breadcrumb,
formatMessage,
})}
>
<div className={styles.container}>
<div className={styles.lang}>
<SelectLang />
</div>
<div className={styles.content}>
<div className={styles.top}>
<div className={styles.header}>
<Link to="/">
<img alt="logo" className={styles.logo} src={logo} />
<span className={styles.title}>Ant Design</span>
</Link>
</div>
{children}
<div className={styles.desc}>Ant Design 西 Web </div>
</div>
<GlobalFooter links={links} copyright={copyright} />
{children}
</div>
</DocumentTitle>
);
}
}
<GlobalFooter links={links} copyright={copyright} />
</div>
</DocumentTitle>
);
};
export default UserLayout;

37
src/models/connect.d.ts

@ -1,27 +1,12 @@
import { EffectsCommandMap } from 'dva';
import { AnyAction } from 'redux';
import { RouterTypes } from 'umi';
import { MenuDataItem } from '@ant-design/pro-layout';
import { GlobalModelState } from './global';
import { UserModelState } from './user';
import { DefaultSettings as SettingModelState } from '../../config/defaultSettings';
import { MenuDataItem } from '@ant-design/pro-layout';
export { GlobalModelState, SettingModelState, UserModelState };
export type Effect = (
action: AnyAction,
effects: EffectsCommandMap & { select: <T>(func: (state: ConnectState) => T) => T },
) => void;
/**
* @type P: Type of payload
* @type C: Type of callback
*/
export type Dispatch = <P = any, C = (payload: P) => void>(action: {
type: string;
payload?: P;
callback?: C;
[key: string]: any;
}) => any;
export { GlobalModelState, SettingModelState, UserModelState };
export interface Loading {
global: boolean;
@ -41,6 +26,22 @@ export interface ConnectState {
user: UserModelState;
}
export type Effect = (
action: AnyAction,
effects: EffectsCommandMap & { select: <T>(func: (state: ConnectState) => T) => T },
) => void;
/**
* @type P: Type of payload
* @type C: Type of callback
*/
export type Dispatch = <P = any, C = (payload: P) => void>(action: {
type: string;
payload?: P;
callback?: C;
[key: string]: any;
}) => any;
export interface Route extends MenuDataItem {
routes?: Route[];
}
@ -52,5 +53,3 @@ export interface ConnectProps<T extends { [key: string]: any } = {}>
extends Partial<RouterTypes<Route>> {
dispatch?: Dispatch;
}
export default ConnectState;

24
src/models/global.ts

@ -1,8 +1,8 @@
import { queryNotices } from '@/services/user';
import { Subscription } from 'dva';
import { Reducer } from 'redux';
import { Effect } from './connect';
import { NoticeIconData } from '@/components/NoticeIcon';
import { Effect } from './connect.d';
export interface NoticeItem extends NoticeIconData {
id: string;
@ -99,36 +99,38 @@ const GlobalModel: GlobalModelType = {
},
reducers: {
changeLayoutCollapsed(state = { notices: [], collapsed: true }, { payload }) {
changeLayoutCollapsed(state = { notices: [], collapsed: true }, { payload }): GlobalModelState {
return {
...state,
collapsed: payload,
};
},
saveNotices(state, { payload }) {
saveNotices(state, { payload }): GlobalModelState {
return {
collapsed: false,
...state,
notices: payload,
};
},
saveClearedNotices(state = { notices: [], collapsed: true }, { payload }) {
saveClearedNotices(state = { notices: [], collapsed: true }, { payload }): GlobalModelState {
return {
collapsed: false,
...state,
notices: state.notices.filter(item => item.type !== payload),
notices: state.notices.filter((item): boolean => item.type !== payload),
};
},
},
subscriptions: {
setup({ history }) {
setup({ history }): void {
// Subscribe history(url) change, trigger `load` action if pathname is `/`
return history.listen(({ pathname, search }) => {
if (typeof (window as any).ga !== 'undefined') {
(window as any).ga('send', 'pageview', pathname + search);
}
});
history.listen(
({ pathname, search }): void => {
if (typeof (window as any).ga !== 'undefined') {
(window as any).ga('send', 'pageview', pathname + search);
}
},
);
},
},
};

2
src/models/login.ts

@ -3,7 +3,7 @@ import { Reducer, AnyAction } from 'redux';
import { EffectsCommandMap } from 'dva';
import { stringify, parse } from 'qs';
export function getPageQuery() {
export function getPageQuery(): string {
return parse(window.location.href.split('?')[1]);
}

5
src/models/setting.ts

@ -26,8 +26,8 @@ const updateTheme: (primaryColor?: string) => void = primaryColor => {
const hideMessage = message.loading('正在编译主题!', 0);
function buildIt() {
if (!(window as any).less) {
// tslint:disable-next-line no-console
return console.log('no less');
console.log('no less');
return;
}
setTimeout(() => {
(window as any).less
@ -36,6 +36,7 @@ const updateTheme: (primaryColor?: string) => void = primaryColor => {
})
.then(() => {
hideMessage();
return true;
})
.catch(() => {
message.error('Failed to update theme');

8
src/pages/Authorized.tsx

@ -10,7 +10,7 @@ interface AuthComponentProps extends ConnectProps {
}
const getRouteAuthority = (path: string, routeData: Route[]) => {
let authorities: string[] | string | undefined = undefined;
let authorities: string[] | string | undefined;
routeData.forEach(route => {
// match prefix
if (pathToRegexp(`${route.path}(.*)`).test(path)) {
@ -29,7 +29,9 @@ const AuthComponent: React.FC<AuthComponentProps> = ({
route = {
routes: [],
},
location,
location = {
pathname: '',
},
user,
}) => {
const { currentUser } = user;
@ -37,7 +39,7 @@ const AuthComponent: React.FC<AuthComponentProps> = ({
const isLogin = currentUser && currentUser.name;
return (
<Authorized
authority={getRouteAuthority(location!.pathname, routes)!}
authority={getRouteAuthority(location.pathname, routes) || ''}
noMatch={isLogin ? <Redirect to="/exception/403" /> : <Redirect to="/user/login" />}
>
{children}

2
src/pages/Welcome.tsx

@ -1,6 +1,6 @@
import React from 'react';
export default () => (
export default (): React.ReactNode => (
<p style={{ textAlign: 'center' }}>
Want to add more pages? Please refer to{' '}
<a href="https://umijs.org/guide/block.html" target="_blank" rel="noopener noreferrer">

5
src/service-worker.js

@ -1,5 +1,7 @@
/* globals workbox */
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-restricted-globals */
/* eslint-disable no-underscore-dangle */
/* globals workbox */
workbox.core.setCacheNameDetails({
prefix: 'antd-pro',
suffix: 'v1',
@ -11,7 +13,6 @@ workbox.clientsClaim();
* Use precaching list generated by workbox in build process.
* https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
*/
/* eslint-disable no-underscore-dangle */
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
/**

9
src/utils/Authorized.ts

@ -1,10 +1,11 @@
import { default as RenderAuthorize } from '@/components/Authorized';
import RenderAuthorize from '@/components/Authorized';
import { getAuthority } from './authority';
let Authorized = RenderAuthorize(getAuthority()); // eslint-disable-line
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable import/no-mutable-exports */
let Authorized = RenderAuthorize(getAuthority());
// Reload the rights component
const reloadAuthorized = () => {
const reloadAuthorized = (): void => {
Authorized = RenderAuthorize(getAuthority());
};

3
src/utils/authority.test.ts

@ -1,9 +1,8 @@
import 'jest';
import { getAuthority } from './authority';
describe('getAuthority should be strong', () => {
it('empty', () => {
expect(getAuthority(null)).toEqual(null); // default value
expect(getAuthority(null as any)).toEqual(null); // default value
});
it('string', () => {
expect(getAuthority('admin')).toEqual(['admin']);

4
src/utils/authority.ts

@ -6,7 +6,9 @@ export function getAuthority(str?: string): any {
// authorityString could be admin, "admin", ["admin"]
let authority;
try {
authority = JSON.parse(authorityString!);
if (authorityString) {
authority = JSON.parse(authorityString);
}
} catch (e) {
authority = authorityString;
}

24
src/utils/request.ts

@ -5,12 +5,6 @@
import { extend } from 'umi-request';
import { notification } from 'antd';
interface ResponseError<D = any> extends Error {
name: string;
data: D;
response: Response;
}
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
@ -32,15 +26,17 @@ const codeMessage = {
/**
*
*/
const errorHandler = (error: ResponseError) => {
const { response = {} as Response } = error;
const errortext = codeMessage[response.status] || response.statusText;
const { status, url } = response;
const errorHandler = (error: { response: Response }): void => {
const { response } = error;
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText;
const { status, url } = response;
notification.error({
message: `请求错误 ${status}: ${url}`,
description: errortext,
});
notification.error({
message: `请求错误 ${status}: ${url}`,
description: errorText,
});
}
};
/**

9
src/utils/utils.test.ts

@ -1,8 +1,7 @@
import 'jest';
import { isUrl } from './utils';
describe('isUrl tests', () => {
it('should return false for invalid and corner case inputs', () => {
describe('isUrl tests', (): void => {
it('should return false for invalid and corner case inputs', (): void => {
expect(isUrl([] as any)).toBeFalsy();
expect(isUrl({} as any)).toBeFalsy();
expect(isUrl(false as any)).toBeFalsy();
@ -13,7 +12,7 @@ describe('isUrl tests', () => {
expect(isUrl('')).toBeFalsy();
});
it('should return false for invalid URLs', () => {
it('should return false for invalid URLs', (): void => {
expect(isUrl('foo')).toBeFalsy();
expect(isUrl('bar')).toBeFalsy();
expect(isUrl('bar/test')).toBeFalsy();
@ -21,7 +20,7 @@ describe('isUrl tests', () => {
expect(isUrl('ttp://example.com/')).toBeFalsy();
});
it('should return true for valid URLs', () => {
it('should return true for valid URLs', (): void => {
expect(isUrl('http://example.com/')).toBeTruthy();
expect(isUrl('https://example.com/')).toBeTruthy();
expect(isUrl('http://example.com/test/123')).toBeTruthy();

4
src/utils/utils.ts

@ -1,12 +1,12 @@
/* eslint no-useless-escape:0 import/prefer-default-export:0 */
const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
export function isUrl(path: string) {
export function isUrl(path: string): string {
return reg.test(path);
}
// 给官方演示站点用,用于关闭真实开发环境不需要使用的特性
export function isAntDesignProOrDev() {
export function isAntDesignProOrDev(): boolean {
const { NODE_ENV } = process.env;
if (ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') {
return true;

6
tests/run-tests.js

@ -1,4 +1,6 @@
/* eslint-disable no-console */
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable eslint-comments/no-unlimited-disable */
const { spawn } = require('child_process');
const { kill } = require('cross-port-killer');
@ -37,7 +39,7 @@ startServer.stdout.on('data', data => {
['test', '--', '--maxWorkers=1', '--runInBand'],
{
stdio: 'inherit',
}
},
);
testCmd.on('exit', code => {
startServer.kill();

88
tslint.yml

@ -1,88 +0,0 @@
defaultSeverity: error
globals:
- ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true
extends:
- tslint-react
- tslint-eslint-rules
- tslint-config-prettier
jsRules:
rules:
class-name: true
eofline: true
forin: true
jsdoc-format: false
label-position: true
member-ordering:
- true
- order: statics-first
new-parens: true
no-arg: true
no-bitwise: true
no-conditional-assignment: true
no-consecutive-blank-lines: true
no-construct: true
no-debugger: true
no-duplicate-variable: true
no-eval: true
no-internal-module: true
no-multi-spaces: true
no-namespace: true
no-reference: true
no-shadowed-variable: true
no-string-literal: true
no-trailing-whitespace: true
no-unused-expression: true
no-var-keyword: true
one-variable-per-declaration:
- true
- ignore-for-loop
prefer-const:
- true
- destructuring: all
radix: true
space-in-parens: true
switch-default: true
trailing-comma:
- true
- singleline: never
multiline: always
esSpecCompliant: true
triple-equals:
- true
- allow-null-check
typedef-whitespace:
- true
- call-signature: nospace
index-signature: nospace
parameter: nospace
property-declaration: nospace
variable-declaration: nospace
- call-signature: onespace
index-signature: onespace
parameter: onespace
property-declaration: onespace
variable-declaration: onespace
use-isnan: true
variable-name:
- true
- allow-leading-underscore
- ban-keywords
- check-format
- allow-pascal-case
jsx-no-lambda: false
jsx-no-string-ref: false
jsx-boolean-value:
- true
- never
jsx-no-multiline-js: false
whitespace:
- true
- check-branch
- check-decl
- check-operator
- check-module
- check-separator
- check-rest-spread
- check-type
- check-type-operator
- check-preblock
Loading…
Cancel
Save