Browse Source
* add test lib * add more fix * prettier * prettier * prettier * fix(test):更好的类型优化 * chore: update lock * update * remove e2e * try remove less * update to antd@5 * chore: support antd@5 * fix: update snapshot * fix: update snapshot * remove fabric * feat: request record * fix: support jest * fix ci * fix ci * fix ci * fix ci * fix ci * fix ci * fix ci * fix ci * remove plugin Co-authored-by: xiefengnian.xfn <xiefengnian.xfn@antgroup.com>pull/10440/head
committed by
GitHub
50 changed files with 19047 additions and 1383 deletions
@ -0,0 +1,27 @@ |
|||||
|
name: coverage CI |
||||
|
|
||||
|
on: [push, pull_request] |
||||
|
|
||||
|
permissions: |
||||
|
contents: read |
||||
|
|
||||
|
jobs: |
||||
|
build: |
||||
|
runs-on: ubuntu-latest |
||||
|
steps: |
||||
|
- uses: actions/checkout@v1 |
||||
|
- name: Use Node.js 16.x |
||||
|
uses: actions/setup-node@v1 |
||||
|
with: |
||||
|
node-version: 16.x |
||||
|
- run: echo ${{github.ref}} |
||||
|
- run: curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7 |
||||
|
- run: pnpm config set store-dir ~/.pnpm-store |
||||
|
- run: pnpm install --strict-peer-dependencies=false |
||||
|
- run: yarn run test:coverage |
||||
|
env: |
||||
|
CI: true |
||||
|
PROGRESS: none |
||||
|
NODE_ENV: test |
||||
|
NODE_OPTIONS: --max_old_space_size=4096 |
||||
|
- run: bash <(curl -s https://codecov.io/bash) |
||||
@ -1,5 +1,21 @@ |
|||||
const fabric = require('@umijs/fabric'); |
|
||||
|
|
||||
module.exports = { |
module.exports = { |
||||
...fabric.prettier, |
singleQuote: true, |
||||
|
trailingComma: 'all', |
||||
|
printWidth: 100, |
||||
|
proseWrap: 'never', |
||||
|
endOfLine: 'lf', |
||||
|
overrides: [ |
||||
|
{ |
||||
|
files: '.prettierrc', |
||||
|
options: { |
||||
|
parser: 'json', |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
files: 'document.ejs', |
||||
|
options: { |
||||
|
parser: 'html', |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
}; |
}; |
||||
|
|||||
@ -0,0 +1,23 @@ |
|||||
|
import { configUmiAlias, createConfig } from '@umijs/max/test'; |
||||
|
|
||||
|
export default async () => { |
||||
|
const config = await configUmiAlias({ |
||||
|
...createConfig({ |
||||
|
target: 'browser', |
||||
|
}), |
||||
|
}); |
||||
|
|
||||
|
console.log(); |
||||
|
return { |
||||
|
...config, |
||||
|
testEnvironmentOptions: { |
||||
|
...(config?.testEnvironmentOptions || {}), |
||||
|
url: 'http://localhost:8000', |
||||
|
}, |
||||
|
setupFiles: [...(config.setupFiles || []), './tests/setupTests.jsx'], |
||||
|
globals: { |
||||
|
...config.globals, |
||||
|
localStorage: null, |
||||
|
}, |
||||
|
}; |
||||
|
}; |
||||
@ -0,0 +1,324 @@ |
|||||
|
module.exports = { |
||||
|
'GET /api/currentUser': { |
||||
|
data: { |
||||
|
name: 'Serati Ma', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', |
||||
|
userid: '00000001', |
||||
|
email: 'antdesign@alipay.com', |
||||
|
signature: '海纳百川,有容乃大', |
||||
|
title: '交互专家', |
||||
|
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', |
||||
|
tags: [ |
||||
|
{ key: '0', label: '很有想法的' }, |
||||
|
{ key: '1', label: '专注设计' }, |
||||
|
{ key: '2', label: '辣~' }, |
||||
|
{ key: '3', label: '大长腿' }, |
||||
|
{ key: '4', label: '川妹子' }, |
||||
|
{ key: '5', label: '海纳百川' }, |
||||
|
], |
||||
|
notifyCount: 12, |
||||
|
unreadCount: 11, |
||||
|
country: 'China', |
||||
|
geographic: { |
||||
|
province: { label: '浙江省', key: '330000' }, |
||||
|
city: { label: '杭州市', key: '330100' }, |
||||
|
}, |
||||
|
address: '西湖区工专路 77 号', |
||||
|
phone: '0752-268888888', |
||||
|
}, |
||||
|
}, |
||||
|
'GET /api/rule': { |
||||
|
data: [ |
||||
|
{ |
||||
|
key: 99, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 99', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 503, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 81, |
||||
|
}, |
||||
|
{ |
||||
|
key: 98, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 98', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 164, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 12, |
||||
|
}, |
||||
|
{ |
||||
|
key: 97, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 97', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 174, |
||||
|
status: '1', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 81, |
||||
|
}, |
||||
|
{ |
||||
|
key: 96, |
||||
|
disabled: true, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 96', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 914, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 7, |
||||
|
}, |
||||
|
{ |
||||
|
key: 95, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 95', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 698, |
||||
|
status: '2', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 82, |
||||
|
}, |
||||
|
{ |
||||
|
key: 94, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 94', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 488, |
||||
|
status: '1', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 14, |
||||
|
}, |
||||
|
{ |
||||
|
key: 93, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 93', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 580, |
||||
|
status: '2', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 77, |
||||
|
}, |
||||
|
{ |
||||
|
key: 92, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 92', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 244, |
||||
|
status: '3', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 58, |
||||
|
}, |
||||
|
{ |
||||
|
key: 91, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 91', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 959, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 66, |
||||
|
}, |
||||
|
{ |
||||
|
key: 90, |
||||
|
disabled: true, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 90', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 958, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 72, |
||||
|
}, |
||||
|
{ |
||||
|
key: 89, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 89', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 301, |
||||
|
status: '2', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 2, |
||||
|
}, |
||||
|
{ |
||||
|
key: 88, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 88', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 277, |
||||
|
status: '1', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 12, |
||||
|
}, |
||||
|
{ |
||||
|
key: 87, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 87', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 810, |
||||
|
status: '1', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 82, |
||||
|
}, |
||||
|
{ |
||||
|
key: 86, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 86', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 780, |
||||
|
status: '3', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 22, |
||||
|
}, |
||||
|
{ |
||||
|
key: 85, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 85', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 705, |
||||
|
status: '3', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 12, |
||||
|
}, |
||||
|
{ |
||||
|
key: 84, |
||||
|
disabled: true, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 84', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 203, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 79, |
||||
|
}, |
||||
|
{ |
||||
|
key: 83, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 83', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 491, |
||||
|
status: '2', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 59, |
||||
|
}, |
||||
|
{ |
||||
|
key: 82, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 82', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 73, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 100, |
||||
|
}, |
||||
|
{ |
||||
|
key: 81, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 81', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 406, |
||||
|
status: '3', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 61, |
||||
|
}, |
||||
|
{ |
||||
|
key: 80, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 80', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 112, |
||||
|
status: '2', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 20, |
||||
|
}, |
||||
|
], |
||||
|
total: 100, |
||||
|
success: true, |
||||
|
pageSize: 20, |
||||
|
current: 1, |
||||
|
}, |
||||
|
'POST /api/login/outLogin': { data: {}, success: true }, |
||||
|
'POST /api/login/account': { |
||||
|
status: 'ok', |
||||
|
type: 'account', |
||||
|
currentAuthority: 'admin', |
||||
|
}, |
||||
|
}; |
||||
@ -1,22 +0,0 @@ |
|||||
// playwright.config.ts
|
|
||||
import type { PlaywrightTestConfig } from '@playwright/test'; |
|
||||
import { devices } from '@playwright/test'; |
|
||||
|
|
||||
const config: PlaywrightTestConfig = { |
|
||||
forbidOnly: !!process.env.CI, |
|
||||
retries: process.env.CI ? 2 : 0, |
|
||||
use: { |
|
||||
trace: 'on-first-retry', |
|
||||
}, |
|
||||
projects: [ |
|
||||
{ |
|
||||
name: 'chromium', |
|
||||
use: { ...devices['Desktop Chrome'] }, |
|
||||
}, |
|
||||
{ |
|
||||
name: 'firefox', |
|
||||
use: { ...devices['Desktop Firefox'] }, |
|
||||
}, |
|
||||
], |
|
||||
}; |
|
||||
export default config; |
|
||||
File diff suppressed because it is too large
@ -1,16 +0,0 @@ |
|||||
@import (reference) '~antd/es/style/themes/index'; |
|
||||
|
|
||||
.container > * { |
|
||||
background-color: @popover-bg; |
|
||||
border-radius: 4px; |
|
||||
box-shadow: @shadow-1-down; |
|
||||
} |
|
||||
|
|
||||
@media screen and (max-width: @screen-xs) { |
|
||||
.container { |
|
||||
width: 100% !important; |
|
||||
} |
|
||||
.container > * { |
|
||||
border-radius: 0 !important; |
|
||||
} |
|
||||
} |
|
||||
@ -1,21 +1,29 @@ |
|||||
import { Dropdown } from 'antd'; |
import { Dropdown } from 'antd'; |
||||
import type { DropDownProps } from 'antd/es/dropdown'; |
import type { DropDownProps } from 'antd/es/dropdown'; |
||||
import classNames from 'classnames'; |
|
||||
import React from 'react'; |
import React from 'react'; |
||||
import styles from './index.less'; |
import { useEmotionCss } from '@ant-design/use-emotion-css'; |
||||
|
import classNames from 'classnames'; |
||||
|
|
||||
export type HeaderDropdownProps = { |
export type HeaderDropdownProps = { |
||||
overlayClassName?: string; |
overlayClassName?: string; |
||||
overlay: React.ReactNode | (() => React.ReactNode) | any; |
|
||||
placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter'; |
placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter'; |
||||
} & Omit<DropDownProps, 'overlay'>; |
} & Omit<DropDownProps, 'overlay'>; |
||||
|
|
||||
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => ( |
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => { |
||||
<Dropdown |
const className = useEmotionCss(({ token }) => { |
||||
overlayClassName={classNames(styles.container, cls)} |
return { |
||||
getPopupContainer={(target) => target.parentElement || document.body} |
[`@media screen and (max-width: ${token.screenXS})`]: { |
||||
{...restProps} |
width: '100%', |
||||
/> |
}, |
||||
); |
}; |
||||
|
}); |
||||
|
return ( |
||||
|
<Dropdown |
||||
|
overlayClassName={classNames(className, cls)} |
||||
|
getPopupContainer={(target) => target.parentElement || document.body} |
||||
|
{...restProps} |
||||
|
/> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
export default HeaderDropdown; |
export default HeaderDropdown; |
||||
|
|||||
@ -1,25 +0,0 @@ |
|||||
@import (reference) '~antd/es/style/themes/index'; |
|
||||
|
|
||||
.headerSearch { |
|
||||
display: inline-flex; |
|
||||
align-items: center; |
|
||||
.input { |
|
||||
width: 0; |
|
||||
min-width: 0; |
|
||||
overflow: hidden; |
|
||||
background: transparent; |
|
||||
border-radius: 0; |
|
||||
transition: width 0.3s, margin-left 0.3s; |
|
||||
:global(.ant-select-selection) { |
|
||||
background: transparent; |
|
||||
} |
|
||||
input { |
|
||||
box-shadow: none !important; |
|
||||
} |
|
||||
|
|
||||
&.show { |
|
||||
width: 210px; |
|
||||
margin-left: 8px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,101 +0,0 @@ |
|||||
import { SearchOutlined } from '@ant-design/icons'; |
|
||||
import type { InputRef } from 'antd'; |
|
||||
import { AutoComplete, Input } from 'antd'; |
|
||||
import type { AutoCompleteProps } from 'antd/es/auto-complete'; |
|
||||
import classNames from 'classnames'; |
|
||||
import useMergedState from 'rc-util/es/hooks/useMergedState'; |
|
||||
import React, { useRef } from 'react'; |
|
||||
import styles from './index.less'; |
|
||||
|
|
||||
export type HeaderSearchProps = { |
|
||||
onSearch?: (value?: string) => void; |
|
||||
onChange?: (value?: string) => void; |
|
||||
onVisibleChange?: (b: boolean) => void; |
|
||||
className?: string; |
|
||||
placeholder?: string; |
|
||||
options: AutoCompleteProps['options']; |
|
||||
defaultVisible?: boolean; |
|
||||
visible?: boolean; |
|
||||
defaultValue?: string; |
|
||||
value?: string; |
|
||||
}; |
|
||||
|
|
||||
const HeaderSearch: React.FC<HeaderSearchProps> = (props) => { |
|
||||
const { |
|
||||
className, |
|
||||
defaultValue, |
|
||||
onVisibleChange, |
|
||||
placeholder, |
|
||||
visible, |
|
||||
defaultVisible, |
|
||||
...restProps |
|
||||
} = props; |
|
||||
|
|
||||
const inputRef = useRef<InputRef | null>(null); |
|
||||
|
|
||||
const [value, setValue] = useMergedState<string | undefined>(defaultValue, { |
|
||||
value: props.value, |
|
||||
onChange: props.onChange, |
|
||||
}); |
|
||||
|
|
||||
const [searchMode, setSearchMode] = useMergedState(defaultVisible ?? false, { |
|
||||
value: props.visible, |
|
||||
onChange: onVisibleChange, |
|
||||
}); |
|
||||
|
|
||||
const inputClass = classNames(styles.input, { |
|
||||
[styles.show]: searchMode, |
|
||||
}); |
|
||||
return ( |
|
||||
<div |
|
||||
className={classNames(className, styles.headerSearch)} |
|
||||
onClick={() => { |
|
||||
setSearchMode(true); |
|
||||
if (inputRef.current) { |
|
||||
inputRef.current.focus(); |
|
||||
} |
|
||||
}} |
|
||||
onTransitionEnd={({ propertyName }) => { |
|
||||
if (propertyName === 'width' && !searchMode) { |
|
||||
if (onVisibleChange) { |
|
||||
onVisibleChange(searchMode); |
|
||||
} |
|
||||
} |
|
||||
}} |
|
||||
> |
|
||||
<SearchOutlined |
|
||||
key="Icon" |
|
||||
style={{ |
|
||||
cursor: 'pointer', |
|
||||
}} |
|
||||
/> |
|
||||
<AutoComplete |
|
||||
key="AutoComplete" |
|
||||
className={inputClass} |
|
||||
value={value} |
|
||||
options={restProps.options} |
|
||||
onChange={(completeValue) => setValue(completeValue)} |
|
||||
> |
|
||||
<Input |
|
||||
size="small" |
|
||||
ref={inputRef} |
|
||||
defaultValue={defaultValue} |
|
||||
aria-label={placeholder} |
|
||||
placeholder={placeholder} |
|
||||
onKeyDown={(e) => { |
|
||||
if (e.key === 'Enter') { |
|
||||
if (restProps.onSearch) { |
|
||||
restProps.onSearch(value); |
|
||||
} |
|
||||
} |
|
||||
}} |
|
||||
onBlur={() => { |
|
||||
setSearchMode(false); |
|
||||
}} |
|
||||
/> |
|
||||
</AutoComplete> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default HeaderSearch; |
|
||||
@ -1,126 +0,0 @@ |
|||||
import { BellOutlined } from '@ant-design/icons'; |
|
||||
import { Badge, Spin, Tabs } from 'antd'; |
|
||||
import classNames from 'classnames'; |
|
||||
import useMergedState from 'rc-util/es/hooks/useMergedState'; |
|
||||
import React from 'react'; |
|
||||
import HeaderDropdown from '../HeaderDropdown'; |
|
||||
import styles from './index.less'; |
|
||||
import type { NoticeIconTabProps } from './NoticeList'; |
|
||||
import NoticeList from './NoticeList'; |
|
||||
|
|
||||
const { TabPane } = Tabs; |
|
||||
|
|
||||
export type NoticeIconProps = { |
|
||||
count?: number; |
|
||||
bell?: React.ReactNode; |
|
||||
className?: string; |
|
||||
loading?: boolean; |
|
||||
onClear?: (tabName: string, tabKey: string) => void; |
|
||||
onItemClick?: (item: API.NoticeIconItem, tabProps: NoticeIconTabProps) => void; |
|
||||
onViewMore?: (tabProps: NoticeIconTabProps, e: MouseEvent) => void; |
|
||||
onTabChange?: (tabTile: string) => void; |
|
||||
style?: React.CSSProperties; |
|
||||
onPopupVisibleChange?: (visible: boolean) => void; |
|
||||
popupVisible?: boolean; |
|
||||
clearText?: string; |
|
||||
viewMoreText?: string; |
|
||||
clearClose?: boolean; |
|
||||
emptyImage?: string; |
|
||||
children?: React.ReactElement<NoticeIconTabProps>[]; |
|
||||
}; |
|
||||
|
|
||||
const NoticeIcon: React.FC<NoticeIconProps> & { |
|
||||
Tab: typeof NoticeList; |
|
||||
} = (props) => { |
|
||||
const getNotificationBox = (): React.ReactNode => { |
|
||||
const { |
|
||||
children, |
|
||||
loading, |
|
||||
onClear, |
|
||||
onTabChange, |
|
||||
onItemClick, |
|
||||
onViewMore, |
|
||||
clearText, |
|
||||
viewMoreText, |
|
||||
} = props; |
|
||||
if (!children) { |
|
||||
return null; |
|
||||
} |
|
||||
const panes: React.ReactNode[] = []; |
|
||||
React.Children.forEach(children, (child: React.ReactElement<NoticeIconTabProps>): void => { |
|
||||
if (!child) { |
|
||||
return; |
|
||||
} |
|
||||
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; |
|
||||
panes.push( |
|
||||
<TabPane tab={tabTitle} key={tabKey}> |
|
||||
<NoticeList |
|
||||
clearText={clearText} |
|
||||
viewMoreText={viewMoreText} |
|
||||
list={list} |
|
||||
tabKey={tabKey} |
|
||||
onClear={(): void => onClear && onClear(title, tabKey)} |
|
||||
onClick={(item): void => onItemClick && onItemClick(item, child.props)} |
|
||||
onViewMore={(event): void => onViewMore && onViewMore(child.props, event)} |
|
||||
showClear={showClear} |
|
||||
showViewMore={showViewMore} |
|
||||
title={title} |
|
||||
/> |
|
||||
</TabPane>, |
|
||||
); |
|
||||
}); |
|
||||
return ( |
|
||||
<> |
|
||||
<Spin spinning={loading} delay={300}> |
|
||||
<Tabs className={styles.tabs} onChange={onTabChange}> |
|
||||
{panes} |
|
||||
</Tabs> |
|
||||
</Spin> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const { className, count, bell } = props; |
|
||||
|
|
||||
const [visible, setVisible] = useMergedState<boolean>(false, { |
|
||||
value: props.popupVisible, |
|
||||
onChange: props.onPopupVisibleChange, |
|
||||
}); |
|
||||
const noticeButtonClass = classNames(className, styles.noticeButton); |
|
||||
const notificationBox = getNotificationBox(); |
|
||||
const NoticeBellIcon = bell || <BellOutlined className={styles.icon} />; |
|
||||
const trigger = ( |
|
||||
<span className={classNames(noticeButtonClass, { opened: visible })}> |
|
||||
<Badge count={count} style={{ boxShadow: 'none' }} className={styles.badge}> |
|
||||
{NoticeBellIcon} |
|
||||
</Badge> |
|
||||
</span> |
|
||||
); |
|
||||
if (!notificationBox) { |
|
||||
return trigger; |
|
||||
} |
|
||||
|
|
||||
return ( |
|
||||
<HeaderDropdown |
|
||||
placement="bottomRight" |
|
||||
overlay={notificationBox} |
|
||||
overlayClassName={styles.popover} |
|
||||
trigger={['click']} |
|
||||
visible={visible} |
|
||||
onVisibleChange={setVisible} |
|
||||
> |
|
||||
{trigger} |
|
||||
</HeaderDropdown> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
NoticeIcon.defaultProps = { |
|
||||
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg', |
|
||||
}; |
|
||||
|
|
||||
NoticeIcon.Tab = NoticeList; |
|
||||
|
|
||||
export default NoticeIcon; |
|
||||
@ -1,103 +0,0 @@ |
|||||
@import (reference) '~antd/es/style/themes/index'; |
|
||||
|
|
||||
.list { |
|
||||
max-height: 400px; |
|
||||
overflow: auto; |
|
||||
&::-webkit-scrollbar { |
|
||||
display: none; |
|
||||
} |
|
||||
.item { |
|
||||
padding-right: 24px; |
|
||||
padding-left: 24px; |
|
||||
overflow: hidden; |
|
||||
cursor: pointer; |
|
||||
transition: all 0.3s; |
|
||||
|
|
||||
.meta { |
|
||||
width: 100%; |
|
||||
} |
|
||||
|
|
||||
.avatar { |
|
||||
margin-top: 4px; |
|
||||
background: @component-background; |
|
||||
} |
|
||||
.iconElement { |
|
||||
font-size: 32px; |
|
||||
} |
|
||||
|
|
||||
&.read { |
|
||||
opacity: 0.4; |
|
||||
} |
|
||||
&:last-child { |
|
||||
border-bottom: 0; |
|
||||
} |
|
||||
&:hover { |
|
||||
background: @primary-1; |
|
||||
} |
|
||||
.title { |
|
||||
margin-bottom: 8px; |
|
||||
font-weight: normal; |
|
||||
} |
|
||||
.description { |
|
||||
font-size: 12px; |
|
||||
line-height: @line-height-base; |
|
||||
} |
|
||||
.datetime { |
|
||||
margin-top: 4px; |
|
||||
font-size: 12px; |
|
||||
line-height: @line-height-base; |
|
||||
} |
|
||||
.extra { |
|
||||
float: right; |
|
||||
margin-top: -1.5px; |
|
||||
margin-right: 0; |
|
||||
color: @text-color-secondary; |
|
||||
font-weight: normal; |
|
||||
} |
|
||||
} |
|
||||
.loadMore { |
|
||||
padding: 8px 0; |
|
||||
color: @primary-6; |
|
||||
text-align: center; |
|
||||
cursor: pointer; |
|
||||
&.loadedAll { |
|
||||
color: rgba(0, 0, 0, 0.25); |
|
||||
cursor: unset; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.notFound { |
|
||||
padding: 73px 0 88px; |
|
||||
color: @text-color-secondary; |
|
||||
text-align: center; |
|
||||
img { |
|
||||
display: inline-block; |
|
||||
height: 76px; |
|
||||
margin-bottom: 16px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.bottomBar { |
|
||||
height: 46px; |
|
||||
color: @text-color; |
|
||||
line-height: 46px; |
|
||||
text-align: center; |
|
||||
border-top: 1px solid @border-color-split; |
|
||||
border-radius: 0 0 @border-radius-base @border-radius-base; |
|
||||
transition: all 0.3s; |
|
||||
div { |
|
||||
display: inline-block; |
|
||||
width: 50%; |
|
||||
cursor: pointer; |
|
||||
transition: all 0.3s; |
|
||||
user-select: none; |
|
||||
|
|
||||
&:only-child { |
|
||||
width: 100%; |
|
||||
} |
|
||||
&:not(:only-child):last-child { |
|
||||
border-left: 1px solid @border-color-split; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,112 +0,0 @@ |
|||||
import { Avatar, List } from 'antd'; |
|
||||
import classNames from 'classnames'; |
|
||||
import React from 'react'; |
|
||||
import styles from './NoticeList.less'; |
|
||||
|
|
||||
export type NoticeIconTabProps = { |
|
||||
count?: number; |
|
||||
showClear?: boolean; |
|
||||
showViewMore?: boolean; |
|
||||
style?: React.CSSProperties; |
|
||||
title: string; |
|
||||
tabKey: API.NoticeIconItemType; |
|
||||
onClick?: (item: API.NoticeIconItem) => void; |
|
||||
onClear?: () => void; |
|
||||
emptyText?: string; |
|
||||
clearText?: string; |
|
||||
viewMoreText?: string; |
|
||||
list: API.NoticeIconItem[]; |
|
||||
onViewMore?: (e: any) => void; |
|
||||
}; |
|
||||
const NoticeList: React.FC<NoticeIconTabProps> = ({ |
|
||||
list = [], |
|
||||
onClick, |
|
||||
onClear, |
|
||||
title, |
|
||||
onViewMore, |
|
||||
emptyText, |
|
||||
showClear = true, |
|
||||
clearText, |
|
||||
viewMoreText, |
|
||||
showViewMore = false, |
|
||||
}) => { |
|
||||
if (!list || list.length === 0) { |
|
||||
return ( |
|
||||
<div className={styles.notFound}> |
|
||||
<img |
|
||||
src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg" |
|
||||
alt="not found" |
|
||||
/> |
|
||||
<div>{emptyText}</div> |
|
||||
</div> |
|
||||
); |
|
||||
} |
|
||||
return ( |
|
||||
<div> |
|
||||
<List<API.NoticeIconItem> |
|
||||
className={styles.list} |
|
||||
dataSource={list} |
|
||||
renderItem={(item, i) => { |
|
||||
const itemCls = classNames(styles.item, { |
|
||||
[styles.read]: item.read, |
|
||||
}); |
|
||||
// eslint-disable-next-line no-nested-ternary
|
|
||||
const leftIcon = item.avatar ? ( |
|
||||
typeof item.avatar === 'string' ? ( |
|
||||
<Avatar className={styles.avatar} src={item.avatar} /> |
|
||||
) : ( |
|
||||
<span className={styles.iconElement}>{item.avatar}</span> |
|
||||
) |
|
||||
) : null; |
|
||||
|
|
||||
return ( |
|
||||
<div |
|
||||
onClick={() => { |
|
||||
onClick?.(item); |
|
||||
}} |
|
||||
> |
|
||||
<List.Item className={itemCls} key={item.key || i}> |
|
||||
<List.Item.Meta |
|
||||
className={styles.meta} |
|
||||
avatar={leftIcon} |
|
||||
title={ |
|
||||
<div className={styles.title}> |
|
||||
{item.title} |
|
||||
<div className={styles.extra}>{item.extra}</div> |
|
||||
</div> |
|
||||
} |
|
||||
description={ |
|
||||
<div> |
|
||||
<div className={styles.description}>{item.description}</div> |
|
||||
<div className={styles.datetime}>{item.datetime}</div> |
|
||||
</div> |
|
||||
} |
|
||||
/> |
|
||||
</List.Item> |
|
||||
</div> |
|
||||
); |
|
||||
}} |
|
||||
/> |
|
||||
<div className={styles.bottomBar}> |
|
||||
{showClear ? ( |
|
||||
<div onClick={onClear}> |
|
||||
{clearText} {title} |
|
||||
</div> |
|
||||
) : null} |
|
||||
{showViewMore ? ( |
|
||||
<div |
|
||||
onClick={(e) => { |
|
||||
if (onViewMore) { |
|
||||
onViewMore(e); |
|
||||
} |
|
||||
}} |
|
||||
> |
|
||||
{viewMoreText} |
|
||||
</div> |
|
||||
) : null} |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default NoticeList; |
|
||||
@ -1,35 +0,0 @@ |
|||||
@import (reference) '~antd/es/style/themes/index'; |
|
||||
|
|
||||
.popover { |
|
||||
position: relative; |
|
||||
width: 336px; |
|
||||
} |
|
||||
|
|
||||
.noticeButton { |
|
||||
display: inline-block; |
|
||||
cursor: pointer; |
|
||||
transition: all 0.3s; |
|
||||
} |
|
||||
.icon { |
|
||||
padding: 4px; |
|
||||
vertical-align: middle; |
|
||||
} |
|
||||
|
|
||||
.badge { |
|
||||
font-size: 16px; |
|
||||
} |
|
||||
|
|
||||
.tabs { |
|
||||
:global { |
|
||||
.ant-tabs-nav-list { |
|
||||
margin: auto; |
|
||||
} |
|
||||
|
|
||||
.ant-tabs-nav-scroll { |
|
||||
text-align: center; |
|
||||
} |
|
||||
.ant-tabs-nav { |
|
||||
margin-bottom: 0; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,152 +0,0 @@ |
|||||
import { getNotices } from '@/services/ant-design-pro/api'; |
|
||||
import { useModel, useRequest } from '@umijs/max'; |
|
||||
import { message, Tag } from 'antd'; |
|
||||
import { groupBy } from 'lodash'; |
|
||||
import moment from 'moment'; |
|
||||
import { useEffect, useState } from 'react'; |
|
||||
import styles from './index.less'; |
|
||||
import NoticeIcon from './NoticeIcon'; |
|
||||
|
|
||||
export type GlobalHeaderRightProps = { |
|
||||
fetchingNotices?: boolean; |
|
||||
onNoticeVisibleChange?: (visible: boolean) => void; |
|
||||
onNoticeClear?: (tabName?: string) => void; |
|
||||
}; |
|
||||
|
|
||||
const getNoticeData = (notices: API.NoticeIconItem[]): Record<string, API.NoticeIconItem[]> => { |
|
||||
if (!notices || notices.length === 0 || !Array.isArray(notices)) { |
|
||||
return {}; |
|
||||
} |
|
||||
|
|
||||
const newNotices = notices.map((notice) => { |
|
||||
const newNotice = { ...notice }; |
|
||||
|
|
||||
if (newNotice.datetime) { |
|
||||
newNotice.datetime = moment(notice.datetime as string).fromNow(); |
|
||||
} |
|
||||
|
|
||||
if (newNotice.id) { |
|
||||
newNotice.key = newNotice.id; |
|
||||
} |
|
||||
|
|
||||
if (newNotice.extra && newNotice.status) { |
|
||||
const color = { |
|
||||
todo: '', |
|
||||
processing: 'blue', |
|
||||
urgent: 'red', |
|
||||
doing: 'gold', |
|
||||
}[newNotice.status]; |
|
||||
newNotice.extra = ( |
|
||||
<Tag |
|
||||
color={color} |
|
||||
style={{ |
|
||||
marginRight: 0, |
|
||||
}} |
|
||||
> |
|
||||
{newNotice.extra} |
|
||||
</Tag> |
|
||||
) as any; |
|
||||
} |
|
||||
|
|
||||
return newNotice; |
|
||||
}); |
|
||||
return groupBy(newNotices, 'type'); |
|
||||
}; |
|
||||
|
|
||||
const getUnreadData = (noticeData: Record<string, API.NoticeIconItem[]>) => { |
|
||||
const unreadMsg: Record<string, number> = {}; |
|
||||
Object.keys(noticeData).forEach((key) => { |
|
||||
const value = noticeData[key]; |
|
||||
|
|
||||
if (!unreadMsg[key]) { |
|
||||
unreadMsg[key] = 0; |
|
||||
} |
|
||||
|
|
||||
if (Array.isArray(value)) { |
|
||||
unreadMsg[key] = value.filter((item) => !item.read).length; |
|
||||
} |
|
||||
}); |
|
||||
return unreadMsg; |
|
||||
}; |
|
||||
|
|
||||
const NoticeIconView: React.FC = () => { |
|
||||
const { initialState } = useModel('@@initialState'); |
|
||||
const { currentUser } = initialState || {}; |
|
||||
const [notices, setNotices] = useState<API.NoticeIconItem[]>([]); |
|
||||
const { data } = useRequest(getNotices); |
|
||||
|
|
||||
useEffect(() => { |
|
||||
setNotices(data || []); |
|
||||
}, [data]); |
|
||||
|
|
||||
const noticeData = getNoticeData(notices); |
|
||||
const unreadMsg = getUnreadData(noticeData || {}); |
|
||||
|
|
||||
const changeReadState = (id: string) => { |
|
||||
setNotices( |
|
||||
notices.map((item) => { |
|
||||
const notice = { ...item }; |
|
||||
if (notice.id === id) { |
|
||||
notice.read = true; |
|
||||
} |
|
||||
return notice; |
|
||||
}), |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const clearReadState = (title: string, key: string) => { |
|
||||
setNotices( |
|
||||
notices.map((item) => { |
|
||||
const notice = { ...item }; |
|
||||
if (notice.type === key) { |
|
||||
notice.read = true; |
|
||||
} |
|
||||
return notice; |
|
||||
}), |
|
||||
); |
|
||||
message.success(`${'清空了'} ${title}`); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<NoticeIcon |
|
||||
className={styles.action} |
|
||||
count={currentUser && currentUser.unreadCount} |
|
||||
onItemClick={(item) => { |
|
||||
changeReadState(item.id!); |
|
||||
}} |
|
||||
onClear={(title: string, key: string) => clearReadState(title, key)} |
|
||||
loading={false} |
|
||||
clearText="清空" |
|
||||
viewMoreText="查看更多" |
|
||||
onViewMore={() => message.info('Click on view more')} |
|
||||
clearClose |
|
||||
> |
|
||||
<NoticeIcon.Tab |
|
||||
tabKey="notification" |
|
||||
count={unreadMsg.notification} |
|
||||
list={noticeData.notification} |
|
||||
title="通知" |
|
||||
emptyText="你已查看所有通知" |
|
||||
showViewMore |
|
||||
/> |
|
||||
<NoticeIcon.Tab |
|
||||
tabKey="message" |
|
||||
count={unreadMsg.message} |
|
||||
list={noticeData.message} |
|
||||
title="消息" |
|
||||
emptyText="您已读完所有消息" |
|
||||
showViewMore |
|
||||
/> |
|
||||
<NoticeIcon.Tab |
|
||||
tabKey="event" |
|
||||
title="待办" |
|
||||
emptyText="你已完成所有待办" |
|
||||
count={unreadMsg.event} |
|
||||
list={noticeData.event} |
|
||||
showViewMore |
|
||||
/> |
|
||||
</NoticeIcon> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default NoticeIconView; |
|
||||
@ -1,82 +0,0 @@ |
|||||
@import (reference) '~antd/es/style/themes/index'; |
|
||||
|
|
||||
@pro-header-hover-bg: rgba(0, 0, 0, 0.025); |
|
||||
|
|
||||
.menu { |
|
||||
:global(.anticon) { |
|
||||
margin-right: 8px; |
|
||||
} |
|
||||
:global(.ant-dropdown-menu-item) { |
|
||||
min-width: 160px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.right { |
|
||||
display: flex; |
|
||||
float: right; |
|
||||
height: 48px; |
|
||||
margin-left: auto; |
|
||||
overflow: hidden; |
|
||||
|
|
||||
.name { |
|
||||
width: 70px; |
|
||||
height: 48px; |
|
||||
overflow: hidden; |
|
||||
line-height: 48px; |
|
||||
white-space: nowrap; |
|
||||
text-overflow: ellipsis; |
|
||||
} |
|
||||
.action { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
height: 48px; |
|
||||
padding: 0 12px; |
|
||||
cursor: pointer; |
|
||||
transition: all 0.3s; |
|
||||
> span { |
|
||||
vertical-align: middle; |
|
||||
} |
|
||||
&:hover { |
|
||||
background: @pro-header-hover-bg; |
|
||||
} |
|
||||
&:global(.opened) { |
|
||||
background: @pro-header-hover-bg; |
|
||||
} |
|
||||
} |
|
||||
.search { |
|
||||
padding: 0 12px; |
|
||||
&:hover { |
|
||||
background: transparent; |
|
||||
} |
|
||||
} |
|
||||
.account { |
|
||||
.avatar { |
|
||||
margin-right: 8px; |
|
||||
color: @primary-color; |
|
||||
vertical-align: top; |
|
||||
background: rgba(255, 255, 255, 0.85); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@media only screen and (max-width: @screen-md) { |
|
||||
:global(.ant-divider-vertical) { |
|
||||
vertical-align: unset; |
|
||||
} |
|
||||
.name { |
|
||||
display: none; |
|
||||
} |
|
||||
.right { |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
right: 12px; |
|
||||
.account { |
|
||||
.avatar { |
|
||||
margin-right: 0; |
|
||||
} |
|
||||
} |
|
||||
.search { |
|
||||
display: none; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,267 +0,0 @@ |
|||||
--- |
|
||||
title: 业务组件 |
|
||||
sidemenu: false |
|
||||
--- |
|
||||
|
|
||||
> 此功能由[dumi](https://d.umijs.org/zh-CN/guide/advanced#umi-%E9%A1%B9%E7%9B%AE%E9%9B%86%E6%88%90%E6%A8%A1%E5%BC%8F)提供,dumi 是一个 📖 为组件开发场景而生的文档工具,用过的都说好。 |
|
||||
|
|
||||
# 业务组件 |
|
||||
|
|
||||
这里列举了 Pro 中所有用到的组件,这些组件不适合作为组件库,但是在业务中却真实需要。所以我们准备了这个文档,来指导大家是否需要使用这个组件。 |
|
||||
|
|
||||
## Footer 页脚组件 |
|
||||
|
|
||||
这个组件自带了一些 Pro 的配置,你一般都需要改掉它的信息。 |
|
||||
|
|
||||
```tsx |
|
||||
/** |
|
||||
* background: '#f0f2f5' |
|
||||
*/ |
|
||||
import Footer from '@/components/Footer'; |
|
||||
|
|
||||
export default () => <Footer />; |
|
||||
``` |
|
||||
|
|
||||
## HeaderDropdown 头部下拉列表 |
|
||||
|
|
||||
HeaderDropdown 是 antd Dropdown 的封装,但是增加了移动端的特殊处理,用法也是相同的。 |
|
||||
|
|
||||
```tsx |
|
||||
/** |
|
||||
* background: '#f0f2f5' |
|
||||
*/ |
|
||||
import HeaderDropdown from '@/components/HeaderDropdown'; |
|
||||
import { Button, Menu } from 'antd'; |
|
||||
|
|
||||
export default () => { |
|
||||
const menuHeaderDropdown = ( |
|
||||
<Menu selectedKeys={[]}> |
|
||||
<Menu.Item key="center">个人中心</Menu.Item> |
|
||||
<Menu.Item key="settings">个人设置</Menu.Item> |
|
||||
<Menu.Divider /> |
|
||||
<Menu.Item key="logout">退出登录</Menu.Item> |
|
||||
</Menu> |
|
||||
); |
|
||||
return ( |
|
||||
<HeaderDropdown overlay={menuHeaderDropdown}> |
|
||||
<Button>hover 展示菜单</Button> |
|
||||
</HeaderDropdown> |
|
||||
); |
|
||||
}; |
|
||||
``` |
|
||||
|
|
||||
## HeaderSearch 头部搜索框 |
|
||||
|
|
||||
一个带补全数据的输入框,支持收起和展开 Input |
|
||||
|
|
||||
```tsx |
|
||||
/** |
|
||||
* background: '#f0f2f5' |
|
||||
*/ |
|
||||
import HeaderSearch from '@/components/HeaderSearch'; |
|
||||
|
|
||||
export default () => { |
|
||||
return ( |
|
||||
<HeaderSearch |
|
||||
placeholder="站内搜索" |
|
||||
defaultValue="umi ui" |
|
||||
options={[ |
|
||||
{ label: 'Ant Design Pro', value: 'Ant Design Pro' }, |
|
||||
{ |
|
||||
label: 'Ant Design', |
|
||||
value: 'Ant Design', |
|
||||
}, |
|
||||
{ |
|
||||
label: 'Pro Table', |
|
||||
value: 'Pro Table', |
|
||||
}, |
|
||||
{ |
|
||||
label: 'Pro Layout', |
|
||||
value: 'Pro Layout', |
|
||||
}, |
|
||||
]} |
|
||||
onSearch={(value) => { |
|
||||
console.log('input', value); |
|
||||
}} |
|
||||
/> |
|
||||
); |
|
||||
}; |
|
||||
``` |
|
||||
|
|
||||
### API |
|
||||
|
|
||||
| 参数 | 说明 | 类型 | 默认值 | |
|
||||
| --------------- | ---------------------------------- | ---------------------------- | ------ | |
|
||||
| value | 输入框的值 | `string` | - | |
|
||||
| onChange | 值修改后触发 | `(value?: string) => void` | - | |
|
||||
| onSearch | 查询后触发 | `(value?: string) => void` | - | |
|
||||
| options | 选项菜单的的列表 | `{label,value}[]` | - | |
|
||||
| defaultVisible | 输入框默认是否显示,只有第一次生效 | `boolean` | - | |
|
||||
| visible | 输入框是否显示 | `boolean` | - | |
|
||||
| onVisibleChange | 输入框显示隐藏的回调函数 | `(visible: boolean) => void` | - | |
|
||||
|
|
||||
## NoticeIcon 通知工具 |
|
||||
|
|
||||
通知工具提供一个展示多种通知信息的界面。 |
|
||||
|
|
||||
```tsx |
|
||||
/** |
|
||||
* background: '#f0f2f5' |
|
||||
*/ |
|
||||
import NoticeIcon from '@/components/NoticeIcon/NoticeIcon'; |
|
||||
import { message } from 'antd'; |
|
||||
|
|
||||
export default () => { |
|
||||
const list = [ |
|
||||
{ |
|
||||
id: '000000001', |
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', |
|
||||
title: '你收到了 14 份新周报', |
|
||||
datetime: '2017-08-09', |
|
||||
type: 'notification', |
|
||||
}, |
|
||||
{ |
|
||||
id: '000000002', |
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png', |
|
||||
title: '你推荐的 曲妮妮 已通过第三轮面试', |
|
||||
datetime: '2017-08-08', |
|
||||
type: 'notification', |
|
||||
}, |
|
||||
]; |
|
||||
return ( |
|
||||
<NoticeIcon |
|
||||
count={10} |
|
||||
onItemClick={(item) => { |
|
||||
message.info(`${item.title} 被点击了`); |
|
||||
}} |
|
||||
onClear={(title: string, key: string) => message.info('点击了清空更多')} |
|
||||
loading={false} |
|
||||
clearText="清空" |
|
||||
viewMoreText="查看更多" |
|
||||
onViewMore={() => message.info('点击了查看更多')} |
|
||||
clearClose |
|
||||
> |
|
||||
<NoticeIcon.Tab |
|
||||
tabKey="notification" |
|
||||
count={2} |
|
||||
list={list} |
|
||||
title="通知" |
|
||||
emptyText="你已查看所有通知" |
|
||||
showViewMore |
|
||||
/> |
|
||||
<NoticeIcon.Tab |
|
||||
tabKey="message" |
|
||||
count={2} |
|
||||
list={list} |
|
||||
title="消息" |
|
||||
emptyText="您已读完所有消息" |
|
||||
showViewMore |
|
||||
/> |
|
||||
<NoticeIcon.Tab |
|
||||
tabKey="event" |
|
||||
title="待办" |
|
||||
emptyText="你已完成所有待办" |
|
||||
count={2} |
|
||||
list={list} |
|
||||
showViewMore |
|
||||
/> |
|
||||
</NoticeIcon> |
|
||||
); |
|
||||
}; |
|
||||
``` |
|
||||
|
|
||||
### NoticeIcon API |
|
||||
|
|
||||
| 参数 | 说明 | 类型 | 默认值 | |
|
||||
| --- | --- | --- | --- | |
|
||||
| count | 有多少未读通知 | `number` | - | |
|
||||
| bell | 铃铛的图表 | `ReactNode` | - | |
|
||||
| onClear | 点击清空数据按钮 | `(tabName: string, tabKey: string) => void` | - | |
|
||||
| onItemClick | 未读消息列被点击 | `(item: API.NoticeIconData, tabProps: NoticeIconTabProps) => void` | - | |
|
||||
| onViewMore | 查看更多的按钮点击 | `(tabProps: NoticeIconTabProps, e: MouseEvent) => void` | - | |
|
||||
| onTabChange | 通知 Tab 的切换 | `(tabTile: string) => void;` | - | |
|
||||
| popupVisible | 通知显示是否展示 | `boolean` | - | |
|
||||
| onPopupVisibleChange | 通知信息显示隐藏的回调函数 | `(visible: boolean) => void` | - | |
|
||||
| clearText | 清空按钮的文字 | `string` | - | |
|
||||
| viewMoreText | 查看更多的按钮文字 | `string` | - | |
|
||||
| clearClose | 展示清空按钮 | `boolean` | - | |
|
||||
| emptyImage | 列表为空时的兜底展示 | `ReactNode` | - | |
|
||||
|
|
||||
### NoticeIcon.Tab API |
|
||||
|
|
||||
| 参数 | 说明 | 类型 | 默认值 | |
|
||||
| ------------ | ------------------ | ------------------------------------ | ------ | |
|
||||
| count | 有多少未读通知 | `number` | - | |
|
||||
| title | 通知 Tab 的标题 | `ReactNode` | - | |
|
||||
| showClear | 展示清除按钮 | `boolean` | `true` | |
|
||||
| showViewMore | 展示加载更 | `boolean` | `true` | |
|
||||
| tabKey | Tab 的唯一 key | `string` | - | |
|
||||
| onClick | 子项的单击事件 | `(item: API.NoticeIconData) => void` | - | |
|
||||
| onClear | 清楚按钮的点击 | `()=>void` | - | |
|
||||
| emptyText | 为空的时候测试 | `()=>void` | - | |
|
||||
| viewMoreText | 查看更多的按钮文字 | `string` | - | |
|
||||
| onViewMore | 查看更多的按钮点击 | `( e: MouseEvent) => void` | - | |
|
||||
| list | 通知信息的列表 | `API.NoticeIconData` | - | |
|
||||
|
|
||||
### NoticeIconData |
|
||||
|
|
||||
```tsx | pure |
|
||||
export type NoticeIconData { |
|
||||
id: string; |
|
||||
key: string; |
|
||||
avatar: string; |
|
||||
title: string; |
|
||||
datetime: string; |
|
||||
type: string; |
|
||||
read?: boolean; |
|
||||
description: string; |
|
||||
clickClose?: boolean; |
|
||||
extra: any; |
|
||||
status: string; |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
## RightContent |
|
||||
|
|
||||
RightContent 是以上几个组件的组合,同时新增了 plugins 的 `SelectLang` 插件。 |
|
||||
|
|
||||
```tsx | pure |
|
||||
<Space> |
|
||||
<HeaderSearch |
|
||||
placeholder="站内搜索" |
|
||||
defaultValue="umi ui" |
|
||||
options={[ |
|
||||
{ label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>, value: 'umi ui' }, |
|
||||
{ |
|
||||
label: <a href="next.ant.design">Ant Design</a>, |
|
||||
value: 'Ant Design', |
|
||||
}, |
|
||||
{ |
|
||||
label: <a href="https://protable.ant.design/">Pro Table</a>, |
|
||||
value: 'Pro Table', |
|
||||
}, |
|
||||
{ |
|
||||
label: <a href="https://prolayout.ant.design/">Pro Layout</a>, |
|
||||
value: 'Pro Layout', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
<Tooltip title="使用文档"> |
|
||||
<span |
|
||||
className={styles.action} |
|
||||
onClick={() => { |
|
||||
window.location.href = 'https://pro.ant.design/docs/getting-started'; |
|
||||
}} |
|
||||
> |
|
||||
<QuestionCircleOutlined /> |
|
||||
</span> |
|
||||
</Tooltip> |
|
||||
<Avatar /> |
|
||||
{REACT_APP_ENV && ( |
|
||||
<span> |
|
||||
<Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag> |
|
||||
</span> |
|
||||
)} |
|
||||
<SelectLang className={styles.action} /> |
|
||||
</Space> |
|
||||
``` |
|
||||
@ -1,44 +0,0 @@ |
|||||
import type { Page } from '@playwright/test'; |
|
||||
import { expect, test } from '@playwright/test'; |
|
||||
const RouterConfig = require('../../config/routes').default; |
|
||||
|
|
||||
const BASE_URL = `http://localhost:${process.env.PORT || 8001}`; |
|
||||
|
|
||||
function formatter(routes: any, parentPath = ''): string[] { |
|
||||
const fixedParentPath = parentPath.replace(/\/{1,}/g, '/'); |
|
||||
let result: string[] = []; |
|
||||
routes.forEach((item: { path: string; routes: string }) => { |
|
||||
if (item.path && !item.path.startsWith('/')) { |
|
||||
result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/')); |
|
||||
} |
|
||||
if (item.path && item.path.startsWith('/')) { |
|
||||
result.push(`${item.path}`.replace(/\/{1,}/g, '/')); |
|
||||
} |
|
||||
if (item.routes) { |
|
||||
result = result.concat( |
|
||||
formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath), |
|
||||
); |
|
||||
} |
|
||||
}); |
|
||||
return [...new Set(result.filter((item) => !!item))]; |
|
||||
} |
|
||||
|
|
||||
const testPage = (path: string, page: Page) => async () => { |
|
||||
await page.evaluate(() => { |
|
||||
localStorage.setItem('antd-pro-authority', '["admin"]'); |
|
||||
}); |
|
||||
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(); |
|
||||
}; |
|
||||
|
|
||||
const routers = formatter(RouterConfig); |
|
||||
|
|
||||
routers.forEach((route) => { |
|
||||
test(`test route page ${route}`, async ({ page }) => { |
|
||||
await testPage(route, page); |
|
||||
}); |
|
||||
}); |
|
||||
File diff suppressed because it is too large
@ -1,48 +0,0 @@ |
|||||
@import (reference) '~antd/es/style/themes/index'; |
|
||||
|
|
||||
.container { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
height: 100vh; |
|
||||
overflow: auto; |
|
||||
background: @layout-body-background; |
|
||||
} |
|
||||
|
|
||||
.lang { |
|
||||
width: 100%; |
|
||||
height: 40px; |
|
||||
line-height: 44px; |
|
||||
text-align: right; |
|
||||
:global(.ant-dropdown-trigger) { |
|
||||
margin-right: 24px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.content { |
|
||||
flex: 1; |
|
||||
padding: 32px 0; |
|
||||
} |
|
||||
|
|
||||
@media (min-width: @screen-md-min) { |
|
||||
.container { |
|
||||
background-image: url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr'); |
|
||||
background-size: cover; |
|
||||
} |
|
||||
|
|
||||
.content { |
|
||||
padding: 32px 0 24px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.icon { |
|
||||
margin-left: 8px; |
|
||||
color: rgba(0, 0, 0, 0.2); |
|
||||
font-size: 24px; |
|
||||
vertical-align: middle; |
|
||||
cursor: pointer; |
|
||||
transition: color 0.3s; |
|
||||
|
|
||||
&:hover { |
|
||||
color: @primary-color; |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,95 @@ |
|||||
|
import { render, fireEvent, act } from '@testing-library/react'; |
||||
|
import React from 'react'; |
||||
|
import { TestBrowser } from '@@/testBrowser'; |
||||
|
|
||||
|
import { startMock } from '@@/requestRecordMock'; |
||||
|
|
||||
|
const waitTime = (time: number = 100) => { |
||||
|
return new Promise((resolve) => { |
||||
|
setTimeout(() => { |
||||
|
resolve(true); |
||||
|
}, time); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
let server: { |
||||
|
close: () => void; |
||||
|
}; |
||||
|
|
||||
|
describe('Login Page', () => { |
||||
|
beforeAll(async () => { |
||||
|
server = await startMock({ |
||||
|
port: 8000, |
||||
|
scene: 'login', |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
afterAll(() => { |
||||
|
server?.close(); |
||||
|
}); |
||||
|
|
||||
|
it('should show login form', async () => { |
||||
|
const historyRef = React.createRef<any>(); |
||||
|
const rootContainer = render( |
||||
|
<TestBrowser |
||||
|
historyRef={historyRef} |
||||
|
location={{ |
||||
|
pathname: '/user/login', |
||||
|
}} |
||||
|
/>, |
||||
|
); |
||||
|
|
||||
|
await rootContainer.findAllByText('Ant Design'); |
||||
|
|
||||
|
act(() => { |
||||
|
historyRef.current?.push('/user/login'); |
||||
|
}); |
||||
|
|
||||
|
expect(rootContainer.baseElement?.querySelector('.ant-pro-form-login-desc')?.textContent).toBe( |
||||
|
'Ant Design is the most influential web design specification in Xihu district', |
||||
|
); |
||||
|
|
||||
|
expect(rootContainer.asFragment()).toMatchSnapshot(); |
||||
|
|
||||
|
rootContainer.unmount(); |
||||
|
}); |
||||
|
|
||||
|
it('should login success', async () => { |
||||
|
const historyRef = React.createRef<any>(); |
||||
|
const rootContainer = render( |
||||
|
<TestBrowser |
||||
|
historyRef={historyRef} |
||||
|
location={{ |
||||
|
pathname: '/user/login', |
||||
|
}} |
||||
|
/>, |
||||
|
); |
||||
|
|
||||
|
await rootContainer.findAllByText('Ant Design'); |
||||
|
|
||||
|
const userNameInput = await rootContainer.findByPlaceholderText('Username: admin or user'); |
||||
|
|
||||
|
act(() => { |
||||
|
fireEvent.change(userNameInput, { target: { value: 'admin' } }); |
||||
|
}); |
||||
|
|
||||
|
const passwordInput = await rootContainer.findByPlaceholderText('Password: ant.design'); |
||||
|
|
||||
|
act(() => { |
||||
|
fireEvent.change(passwordInput, { target: { value: 'ant.design' } }); |
||||
|
}); |
||||
|
|
||||
|
await (await rootContainer.findByText('Login')).click(); |
||||
|
|
||||
|
// 等待接口返回结果
|
||||
|
await waitTime(5000); |
||||
|
|
||||
|
await rootContainer.findAllByText('Ant Design Pro'); |
||||
|
|
||||
|
expect(rootContainer.asFragment()).toMatchSnapshot(); |
||||
|
|
||||
|
await waitTime(2000); |
||||
|
|
||||
|
rootContainer.unmount(); |
||||
|
}); |
||||
|
}); |
||||
@ -1,47 +0,0 @@ |
|||||
/* eslint-disable @typescript-eslint/no-var-requires */ |
|
||||
const { spawn } = require('child_process'); |
|
||||
const { kill } = require('cross-port-killer'); |
|
||||
|
|
||||
const env = Object.create(process.env); |
|
||||
env.BROWSER = 'none'; |
|
||||
env.TEST = true; |
|
||||
env.UMI_UI = 'none'; |
|
||||
env.PROGRESS = 'none'; |
|
||||
// flag to prevent multiple test
|
|
||||
let once = false; |
|
||||
|
|
||||
const startServer = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['run', 'serve'], { |
|
||||
env, |
|
||||
}); |
|
||||
|
|
||||
startServer.stderr.on('data', (data) => { |
|
||||
// eslint-disable-next-line
|
|
||||
console.log(data.toString()); |
|
||||
}); |
|
||||
|
|
||||
startServer.on('exit', () => { |
|
||||
kill(process.env.PORT || 8000); |
|
||||
}); |
|
||||
|
|
||||
console.log('Starting development server for e2e tests...'); |
|
||||
startServer.stdout.on('data', (data) => { |
|
||||
console.log(data.toString()); |
|
||||
// hack code , wait umi
|
|
||||
if (!once && data.toString().indexOf('Serving your umi project!') >= 0) { |
|
||||
// eslint-disable-next-line
|
|
||||
once = true; |
|
||||
console.log('Development server is started, ready to run tests.'); |
|
||||
const testCmd = spawn( |
|
||||
/^win/.test(process.platform) ? 'npm.cmd' : 'npm', |
|
||||
['run', 'playwright'], |
|
||||
{ |
|
||||
stdio: 'inherit', |
|
||||
}, |
|
||||
); |
|
||||
testCmd.on('exit', (code) => { |
|
||||
console.log('服务已经退出,退出码:', code); |
|
||||
startServer.kill(); |
|
||||
process.exit(code); |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
@ -1,10 +0,0 @@ |
|||||
// do some test init
|
|
||||
|
|
||||
const localStorageMock = { |
|
||||
getItem: jest.fn(), |
|
||||
setItem: jest.fn(), |
|
||||
removeItem: jest.fn(), |
|
||||
clear: jest.fn(), |
|
||||
}; |
|
||||
|
|
||||
global.localStorage = localStorageMock; |
|
||||
@ -0,0 +1,64 @@ |
|||||
|
const localStorageMock = { |
||||
|
getItem: jest.fn(), |
||||
|
setItem: jest.fn(), |
||||
|
removeItem: jest.fn(), |
||||
|
clear: jest.fn(), |
||||
|
}; |
||||
|
|
||||
|
global.localStorage = localStorageMock; |
||||
|
|
||||
|
Object.defineProperty(URL, 'createObjectURL', { |
||||
|
writable: true, |
||||
|
value: jest.fn(), |
||||
|
}); |
||||
|
|
||||
|
class Worker { |
||||
|
constructor(stringUrl) { |
||||
|
this.url = stringUrl; |
||||
|
this.onmessage = () => {}; |
||||
|
} |
||||
|
|
||||
|
postMessage(msg) { |
||||
|
this.onmessage(msg); |
||||
|
} |
||||
|
} |
||||
|
window.Worker = Worker; |
||||
|
|
||||
|
/* eslint-disable global-require */ |
||||
|
if (typeof window !== 'undefined') { |
||||
|
// ref: https://github.com/ant-design/ant-design/issues/18774 |
||||
|
if (!window.matchMedia) { |
||||
|
Object.defineProperty(global.window, 'matchMedia', { |
||||
|
writable: true, |
||||
|
configurable: true, |
||||
|
value: jest.fn(() => ({ |
||||
|
matches: false, |
||||
|
addListener: jest.fn(), |
||||
|
removeListener: jest.fn(), |
||||
|
})), |
||||
|
}); |
||||
|
} |
||||
|
if (!window.matchMedia) { |
||||
|
Object.defineProperty(global.window, 'matchMedia', { |
||||
|
writable: true, |
||||
|
configurable: true, |
||||
|
value: jest.fn((query) => ({ |
||||
|
matches: query.includes('max-width'), |
||||
|
addListener: jest.fn(), |
||||
|
removeListener: jest.fn(), |
||||
|
})), |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
const errorLog = console.error; |
||||
|
Object.defineProperty(global.window.console, 'error', { |
||||
|
writable: true, |
||||
|
configurable: true, |
||||
|
value: (...rest) => { |
||||
|
const logStr = rest.join(''); |
||||
|
if (logStr.includes('Warning: An update to %s inside a test was not wrapped in act(...)')) { |
||||
|
return; |
||||
|
} |
||||
|
errorLog(...rest); |
||||
|
}, |
||||
|
}); |
||||
@ -1,41 +1,23 @@ |
|||||
{ |
{ |
||||
"compilerOptions": { |
"compilerOptions": { |
||||
"outDir": "build/dist", |
|
||||
"module": "esnext", |
|
||||
"target": "esnext", |
"target": "esnext", |
||||
"lib": ["esnext", "dom"], |
"module": "esnext", |
||||
"sourceMap": true, |
|
||||
"baseUrl": ".", |
|
||||
"jsx": "react-jsx", |
|
||||
"resolveJsonModule": true, |
|
||||
"allowSyntheticDefaultImports": true, |
|
||||
"moduleResolution": "node", |
"moduleResolution": "node", |
||||
"forceConsistentCasingInFileNames": true, |
"importHelpers": true, |
||||
"noImplicitReturns": true, |
"jsx": "preserve", |
||||
"suppressImplicitAnyIndexErrors": true, |
"esModuleInterop": true, |
||||
"noUnusedLocals": true, |
"sourceMap": true, |
||||
"allowJs": true, |
"baseUrl": "./", |
||||
"skipLibCheck": true, |
"skipLibCheck": true, |
||||
"experimentalDecorators": true, |
"experimentalDecorators": true, |
||||
"strict": true, |
"strict": true, |
||||
|
"resolveJsonModule": true, |
||||
|
"allowSyntheticDefaultImports": true, |
||||
"paths": { |
"paths": { |
||||
"@/*": ["./src/*"], |
"@/*": ["./src/*"], |
||||
"@@/*": ["./src/.umi/*"] |
"@@/*": ["./src/.umi/*"], |
||||
|
"@@test/*": ["./src/.umi-test/*"] |
||||
} |
} |
||||
}, |
}, |
||||
"include": [ |
"include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"] |
||||
"mock/**/*", |
|
||||
"src/**/*", |
|
||||
"playwright.config.ts", |
|
||||
"tests/**/*", |
|
||||
"test/**/*", |
|
||||
"__test__/**/*", |
|
||||
"typings/**/*", |
|
||||
"config/**/*", |
|
||||
".eslintrc.js", |
|
||||
".prettierrc.js", |
|
||||
"jest.config.js", |
|
||||
"mock/*" |
|
||||
], |
|
||||
"exclude": ["node_modules", "build", "dist", "scripts", "src/.umi/*", "webpack", "jest"] |
|
||||
} |
} |
||||
|
|||||
@ -0,0 +1 @@ |
|||||
|
{} |
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,324 @@ |
|||||
|
module.exports = { |
||||
|
'GET /api/currentUser': { |
||||
|
data: { |
||||
|
name: 'Serati Ma', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', |
||||
|
userid: '00000001', |
||||
|
email: 'antdesign@alipay.com', |
||||
|
signature: '海纳百川,有容乃大', |
||||
|
title: '交互专家', |
||||
|
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', |
||||
|
tags: [ |
||||
|
{ key: '0', label: '很有想法的' }, |
||||
|
{ key: '1', label: '专注设计' }, |
||||
|
{ key: '2', label: '辣~' }, |
||||
|
{ key: '3', label: '大长腿' }, |
||||
|
{ key: '4', label: '川妹子' }, |
||||
|
{ key: '5', label: '海纳百川' }, |
||||
|
], |
||||
|
notifyCount: 12, |
||||
|
unreadCount: 11, |
||||
|
country: 'China', |
||||
|
geographic: { |
||||
|
province: { label: '浙江省', key: '330000' }, |
||||
|
city: { label: '杭州市', key: '330100' }, |
||||
|
}, |
||||
|
address: '西湖区工专路 77 号', |
||||
|
phone: '0752-268888888', |
||||
|
}, |
||||
|
}, |
||||
|
'GET /api/rule': { |
||||
|
data: [ |
||||
|
{ |
||||
|
key: 99, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 99', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 503, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 81, |
||||
|
}, |
||||
|
{ |
||||
|
key: 98, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 98', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 164, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 12, |
||||
|
}, |
||||
|
{ |
||||
|
key: 97, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 97', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 174, |
||||
|
status: '1', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 81, |
||||
|
}, |
||||
|
{ |
||||
|
key: 96, |
||||
|
disabled: true, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 96', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 914, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 7, |
||||
|
}, |
||||
|
{ |
||||
|
key: 95, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 95', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 698, |
||||
|
status: '2', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 82, |
||||
|
}, |
||||
|
{ |
||||
|
key: 94, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 94', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 488, |
||||
|
status: '1', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 14, |
||||
|
}, |
||||
|
{ |
||||
|
key: 93, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 93', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 580, |
||||
|
status: '2', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 77, |
||||
|
}, |
||||
|
{ |
||||
|
key: 92, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 92', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 244, |
||||
|
status: '3', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 58, |
||||
|
}, |
||||
|
{ |
||||
|
key: 91, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 91', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 959, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 66, |
||||
|
}, |
||||
|
{ |
||||
|
key: 90, |
||||
|
disabled: true, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 90', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 958, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 72, |
||||
|
}, |
||||
|
{ |
||||
|
key: 89, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 89', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 301, |
||||
|
status: '2', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 2, |
||||
|
}, |
||||
|
{ |
||||
|
key: 88, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 88', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 277, |
||||
|
status: '1', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 12, |
||||
|
}, |
||||
|
{ |
||||
|
key: 87, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 87', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 810, |
||||
|
status: '1', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 82, |
||||
|
}, |
||||
|
{ |
||||
|
key: 86, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 86', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 780, |
||||
|
status: '3', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 22, |
||||
|
}, |
||||
|
{ |
||||
|
key: 85, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 85', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 705, |
||||
|
status: '3', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 12, |
||||
|
}, |
||||
|
{ |
||||
|
key: 84, |
||||
|
disabled: true, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 84', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 203, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 79, |
||||
|
}, |
||||
|
{ |
||||
|
key: 83, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 83', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 491, |
||||
|
status: '2', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 59, |
||||
|
}, |
||||
|
{ |
||||
|
key: 82, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 82', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 73, |
||||
|
status: '0', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 100, |
||||
|
}, |
||||
|
{ |
||||
|
key: 81, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
|
name: 'TradeCode 81', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 406, |
||||
|
status: '3', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 61, |
||||
|
}, |
||||
|
{ |
||||
|
key: 80, |
||||
|
disabled: false, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
|
name: 'TradeCode 80', |
||||
|
owner: '曲丽丽', |
||||
|
desc: '这是一段描述', |
||||
|
callNo: 112, |
||||
|
status: '2', |
||||
|
updatedAt: '2022-12-06T05:00:57.040Z', |
||||
|
createdAt: '2022-12-06T05:00:57.040Z', |
||||
|
progress: 20, |
||||
|
}, |
||||
|
], |
||||
|
total: 100, |
||||
|
success: true, |
||||
|
pageSize: 20, |
||||
|
current: 1, |
||||
|
}, |
||||
|
'POST /api/login/outLogin': { data: {}, success: true }, |
||||
|
'POST /api/login/account': { |
||||
|
status: 'ok', |
||||
|
type: 'account', |
||||
|
currentAuthority: 'admin', |
||||
|
}, |
||||
|
}; |
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue