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 = { |
|||
...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 type { DropDownProps } from 'antd/es/dropdown'; |
|||
import classNames from 'classnames'; |
|||
import React from 'react'; |
|||
import styles from './index.less'; |
|||
import { useEmotionCss } from '@ant-design/use-emotion-css'; |
|||
import classNames from 'classnames'; |
|||
|
|||
export type HeaderDropdownProps = { |
|||
overlayClassName?: string; |
|||
overlay: React.ReactNode | (() => React.ReactNode) | any; |
|||
placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter'; |
|||
} & Omit<DropDownProps, 'overlay'>; |
|||
|
|||
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => ( |
|||
<Dropdown |
|||
overlayClassName={classNames(styles.container, cls)} |
|||
getPopupContainer={(target) => target.parentElement || document.body} |
|||
{...restProps} |
|||
/> |
|||
); |
|||
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => { |
|||
const className = useEmotionCss(({ token }) => { |
|||
return { |
|||
[`@media screen and (max-width: ${token.screenXS})`]: { |
|||
width: '100%', |
|||
}, |
|||
}; |
|||
}); |
|||
return ( |
|||
<Dropdown |
|||
overlayClassName={classNames(className, cls)} |
|||
getPopupContainer={(target) => target.parentElement || document.body} |
|||
{...restProps} |
|||
/> |
|||
); |
|||
}; |
|||
|
|||
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": { |
|||
"outDir": "build/dist", |
|||
"module": "esnext", |
|||
"target": "esnext", |
|||
"lib": ["esnext", "dom"], |
|||
"sourceMap": true, |
|||
"baseUrl": ".", |
|||
"jsx": "react-jsx", |
|||
"resolveJsonModule": true, |
|||
"allowSyntheticDefaultImports": true, |
|||
"module": "esnext", |
|||
"moduleResolution": "node", |
|||
"forceConsistentCasingInFileNames": true, |
|||
"noImplicitReturns": true, |
|||
"suppressImplicitAnyIndexErrors": true, |
|||
"noUnusedLocals": true, |
|||
"allowJs": true, |
|||
"importHelpers": true, |
|||
"jsx": "preserve", |
|||
"esModuleInterop": true, |
|||
"sourceMap": true, |
|||
"baseUrl": "./", |
|||
"skipLibCheck": true, |
|||
"experimentalDecorators": true, |
|||
"strict": true, |
|||
"resolveJsonModule": true, |
|||
"allowSyntheticDefaultImports": true, |
|||
"paths": { |
|||
"@/*": ["./src/*"], |
|||
"@@/*": ["./src/.umi/*"] |
|||
"@@/*": ["./src/.umi/*"], |
|||
"@@test/*": ["./src/.umi-test/*"] |
|||
} |
|||
}, |
|||
"include": [ |
|||
"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"] |
|||
"include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"] |
|||
} |
|||
|
|||
@ -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