Browse Source
- Add missing basic-list components and service files - Fix jest.config.ts import path for @umijs/max/test.js - Update login test snapshots (fix 'Ant Desgin' typo) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>pull/11631/head
9 changed files with 559 additions and 51 deletions
@ -0,0 +1,17 @@ |
|||||
|
{ |
||||
|
"permissions": { |
||||
|
"allow": [ |
||||
|
"Bash(git ls-tree:*)", |
||||
|
"Bash(npm show:*)", |
||||
|
"Bash(git merge:*)", |
||||
|
"Bash(git add:*)", |
||||
|
"Bash(git checkout:*)", |
||||
|
"Bash(ls:*)", |
||||
|
"Bash(git commit:*)", |
||||
|
"Bash(npm test:*)", |
||||
|
"Bash(npm install:*)", |
||||
|
"Bash(npm publish:*)", |
||||
|
"Bash(tnpm publish:*)" |
||||
|
] |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,165 @@ |
|||||
|
import type { Request, Response } from 'express'; |
||||
|
import type { BasicListItemDataType } from './data.d'; |
||||
|
|
||||
|
const titles = [ |
||||
|
'Alipay', |
||||
|
'Angular', |
||||
|
'Ant Design', |
||||
|
'Ant Design Pro', |
||||
|
'Bootstrap', |
||||
|
'React', |
||||
|
'Vue', |
||||
|
'Webpack', |
||||
|
]; |
||||
|
const avatars = [ |
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', // Alipay
|
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', // Angular
|
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', // Ant Design
|
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', // Ant Design Pro
|
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', // Bootstrap
|
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png', // React
|
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', // Vue
|
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png', // Webpack
|
||||
|
]; |
||||
|
|
||||
|
const covers = [ |
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png', |
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png', |
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png', |
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png', |
||||
|
]; |
||||
|
const desc = [ |
||||
|
'那是一种内在的东西, 他们到达不了,也无法触及的', |
||||
|
'希望是一个好东西,也许是最好的,好东西是不会消亡的', |
||||
|
'生命就像一盒巧克力,结果往往出人意料', |
||||
|
'城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', |
||||
|
'那时候我只会想自己想要什么,从不想自己拥有什么', |
||||
|
]; |
||||
|
|
||||
|
const user = [ |
||||
|
'付小小', |
||||
|
'曲丽丽', |
||||
|
'林东东', |
||||
|
'周星星', |
||||
|
'吴加好', |
||||
|
'朱偏右', |
||||
|
'鱼酱', |
||||
|
'乐哥', |
||||
|
'谭小仪', |
||||
|
'仲尼', |
||||
|
]; |
||||
|
|
||||
|
function fakeList(count: number): BasicListItemDataType[] { |
||||
|
const list = []; |
||||
|
for (let i = 0; i < count; i += 1) { |
||||
|
list.push({ |
||||
|
id: `fake-list-${i}`, |
||||
|
owner: user[i % 10], |
||||
|
title: titles[i % 8], |
||||
|
avatar: avatars[i % 8], |
||||
|
cover: |
||||
|
parseInt(`${i / 4}`, 10) % 2 === 0 |
||||
|
? covers[i % 4] |
||||
|
: covers[3 - (i % 4)], |
||||
|
status: ['active', 'exception', 'normal'][i % 3] as |
||||
|
| 'normal' |
||||
|
| 'exception' |
||||
|
| 'active' |
||||
|
| 'success', |
||||
|
percent: Math.ceil(Math.random() * 50) + 50, |
||||
|
logo: avatars[i % 8], |
||||
|
href: 'https://ant.design', |
||||
|
updatedAt: new Date(Date.now() - 1000 * 60 * 60 * 2 * i).getTime(), |
||||
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 2 * i).getTime(), |
||||
|
subDescription: desc[i % 5], |
||||
|
description: |
||||
|
'在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。', |
||||
|
activeUser: Math.ceil(Math.random() * 100000) + 100000, |
||||
|
newUser: Math.ceil(Math.random() * 1000) + 1000, |
||||
|
star: Math.ceil(Math.random() * 100) + 100, |
||||
|
like: Math.ceil(Math.random() * 100) + 100, |
||||
|
message: Math.ceil(Math.random() * 10) + 10, |
||||
|
content: |
||||
|
'段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。', |
||||
|
members: [ |
||||
|
{ |
||||
|
avatar: |
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png', |
||||
|
name: '曲丽丽', |
||||
|
id: 'member1', |
||||
|
}, |
||||
|
{ |
||||
|
avatar: |
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png', |
||||
|
name: '王昭君', |
||||
|
id: 'member2', |
||||
|
}, |
||||
|
{ |
||||
|
avatar: |
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png', |
||||
|
name: '董娜娜', |
||||
|
id: 'member3', |
||||
|
}, |
||||
|
], |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return list; |
||||
|
} |
||||
|
|
||||
|
let sourceData: BasicListItemDataType[] = []; |
||||
|
|
||||
|
function getFakeList(req: Request, res: Response) { |
||||
|
const params = req.query as any; |
||||
|
|
||||
|
const count = Number(params.count) * 1 || 20; |
||||
|
|
||||
|
const result = fakeList(count); |
||||
|
sourceData = result; |
||||
|
return res.json({ |
||||
|
data: { |
||||
|
list: result, |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function postFakeList(req: Request, res: Response) { |
||||
|
const { /* url = '', */ body } = req; |
||||
|
// const params = getUrlParams(url);
|
||||
|
const { method, id } = body; |
||||
|
// const count = (params.count * 1) || 20;
|
||||
|
let result = sourceData || []; |
||||
|
|
||||
|
switch (method) { |
||||
|
case 'delete': |
||||
|
result = result.filter((item) => item.id !== id); |
||||
|
break; |
||||
|
case 'update': |
||||
|
result.forEach((item, i) => { |
||||
|
if (item.id === id) { |
||||
|
result[i] = { ...item, ...body }; |
||||
|
} |
||||
|
}); |
||||
|
break; |
||||
|
case 'post': |
||||
|
result.unshift({ |
||||
|
...body, |
||||
|
id: `fake-list-${result.length}`, |
||||
|
createdAt: Date.now(), |
||||
|
}); |
||||
|
break; |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return res.json({ |
||||
|
data: { |
||||
|
list: result, |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export default { |
||||
|
'GET /api/get_list': getFakeList, |
||||
|
'POST /api/post_fake_list': postFakeList, |
||||
|
}; |
||||
@ -0,0 +1,129 @@ |
|||||
|
import { |
||||
|
ModalForm, |
||||
|
ProFormDateTimePicker, |
||||
|
ProFormSelect, |
||||
|
ProFormText, |
||||
|
ProFormTextArea, |
||||
|
} from '@ant-design/pro-components'; |
||||
|
import { Button, Result } from 'antd'; |
||||
|
import type { FC } from 'react'; |
||||
|
import type { BasicListItemDataType } from '../data.d'; |
||||
|
import useStyles from '../style.style'; |
||||
|
|
||||
|
type OperationModalProps = { |
||||
|
done: boolean; |
||||
|
open: boolean; |
||||
|
current: Partial<BasicListItemDataType> | undefined; |
||||
|
onDone: () => void; |
||||
|
onSubmit: (values: BasicListItemDataType) => void; |
||||
|
children?: React.ReactNode; |
||||
|
}; |
||||
|
const OperationModal: FC<OperationModalProps> = (props) => { |
||||
|
const { styles } = useStyles(); |
||||
|
const { done, open, current, onDone, onSubmit, children } = props; |
||||
|
if (!open) { |
||||
|
return null; |
||||
|
} |
||||
|
return ( |
||||
|
<ModalForm<BasicListItemDataType> |
||||
|
open={open} |
||||
|
title={done ? null : `任务${current ? '编辑' : '添加'}`} |
||||
|
className={styles.standardListForm} |
||||
|
width={640} |
||||
|
onFinish={async (values) => { |
||||
|
onSubmit(values); |
||||
|
}} |
||||
|
initialValues={current} |
||||
|
submitter={{ |
||||
|
render: (_, dom) => (done ? null : dom), |
||||
|
}} |
||||
|
trigger={children} |
||||
|
modalProps={{ |
||||
|
onCancel: () => onDone(), |
||||
|
destroyOnHidden: true, |
||||
|
bodyStyle: done |
||||
|
? { |
||||
|
padding: '72px 0', |
||||
|
} |
||||
|
: {}, |
||||
|
}} |
||||
|
> |
||||
|
{!done ? ( |
||||
|
<> |
||||
|
<ProFormText |
||||
|
name="title" |
||||
|
label="任务名称" |
||||
|
rules={[ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: '请输入任务名称', |
||||
|
}, |
||||
|
]} |
||||
|
placeholder="请输入" |
||||
|
/> |
||||
|
<ProFormDateTimePicker |
||||
|
name="createdAt" |
||||
|
label="开始时间" |
||||
|
rules={[ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: '请选择开始时间', |
||||
|
}, |
||||
|
]} |
||||
|
fieldProps={{ |
||||
|
style: { |
||||
|
width: '100%', |
||||
|
}, |
||||
|
}} |
||||
|
placeholder="请选择" |
||||
|
/> |
||||
|
<ProFormSelect |
||||
|
name="owner" |
||||
|
label="任务负责人" |
||||
|
rules={[ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: '请选择任务负责人', |
||||
|
}, |
||||
|
]} |
||||
|
options={[ |
||||
|
{ |
||||
|
label: '付晓晓', |
||||
|
value: 'xiao', |
||||
|
}, |
||||
|
{ |
||||
|
label: '周毛毛', |
||||
|
value: 'mao', |
||||
|
}, |
||||
|
]} |
||||
|
placeholder="请选择管理员" |
||||
|
/> |
||||
|
<ProFormTextArea |
||||
|
name="subDescription" |
||||
|
label="产品描述" |
||||
|
rules={[ |
||||
|
{ |
||||
|
message: '请输入至少五个字符的产品描述!', |
||||
|
min: 5, |
||||
|
}, |
||||
|
]} |
||||
|
placeholder="请输入至少五个字符" |
||||
|
/> |
||||
|
</> |
||||
|
) : ( |
||||
|
<Result |
||||
|
status="success" |
||||
|
title="操作成功" |
||||
|
subTitle="一系列的信息描述,很短同样也可以带标点。" |
||||
|
extra={ |
||||
|
<Button type="primary" onClick={onDone}> |
||||
|
知道了 |
||||
|
</Button> |
||||
|
} |
||||
|
className={styles.formResult} |
||||
|
/> |
||||
|
)} |
||||
|
</ModalForm> |
||||
|
); |
||||
|
}; |
||||
|
export default OperationModal; |
||||
@ -0,0 +1,29 @@ |
|||||
|
export type Member = { |
||||
|
avatar: string; |
||||
|
name: string; |
||||
|
id: string; |
||||
|
}; |
||||
|
|
||||
|
export type BasicListItemDataType = { |
||||
|
id: string; |
||||
|
owner: string; |
||||
|
title: string; |
||||
|
avatar: string; |
||||
|
cover: string; |
||||
|
status: 'normal' | 'exception' | 'active' | 'success'; |
||||
|
percent: number; |
||||
|
logo: string; |
||||
|
href: string; |
||||
|
body?: any; |
||||
|
updatedAt: number; |
||||
|
createdAt: number; |
||||
|
subDescription: string; |
||||
|
description: string; |
||||
|
activeUser: number; |
||||
|
newUser: number; |
||||
|
star: number; |
||||
|
like: number; |
||||
|
message: number; |
||||
|
content: string; |
||||
|
members: Member[]; |
||||
|
}; |
||||
@ -0,0 +1,50 @@ |
|||||
|
import { request } from '@umijs/max'; |
||||
|
import type { BasicListItemDataType } from './data.d'; |
||||
|
|
||||
|
type ParamsType = { |
||||
|
count?: number; |
||||
|
} & Partial<BasicListItemDataType>; |
||||
|
|
||||
|
export async function queryFakeList( |
||||
|
params: ParamsType, |
||||
|
): Promise<{ data: { list: BasicListItemDataType[] } }> { |
||||
|
return request('/api/get_list', { |
||||
|
params, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export async function removeFakeList( |
||||
|
params: ParamsType, |
||||
|
): Promise<{ data: { list: BasicListItemDataType[] } }> { |
||||
|
return request('/api/post_fake_list', { |
||||
|
method: 'POST', |
||||
|
data: { |
||||
|
...params, |
||||
|
method: 'delete', |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export async function addFakeList( |
||||
|
params: ParamsType, |
||||
|
): Promise<{ data: { list: BasicListItemDataType[] } }> { |
||||
|
return request('/api/post_fake_list', { |
||||
|
method: 'POST', |
||||
|
data: { |
||||
|
...params, |
||||
|
method: 'post', |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export async function updateFakeList( |
||||
|
params: ParamsType, |
||||
|
): Promise<{ data: { list: BasicListItemDataType[] } }> { |
||||
|
return request('/api/post_fake_list', { |
||||
|
method: 'POST', |
||||
|
data: { |
||||
|
...params, |
||||
|
method: 'update', |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,141 @@ |
|||||
|
import { createStyles } from 'antd-style'; |
||||
|
|
||||
|
const useStyles = createStyles(({ token }) => { |
||||
|
return { |
||||
|
standardList: { |
||||
|
'.ant-card-head': { borderBottom: 'none' }, |
||||
|
'.ant-card-head-title': { padding: '24px 0', lineHeight: '32px' }, |
||||
|
'.ant-card-extra': { padding: '24px 0' }, |
||||
|
'.ant-list-pagination': { marginTop: '24px', textAlign: 'right' }, |
||||
|
'.ant-avatar-lg': { width: '48px', height: '48px', lineHeight: '48px' }, |
||||
|
[`@media screen and (max-width: ${token.screenXS}px)`]: { |
||||
|
'.ant-list-item-content': { |
||||
|
display: 'block', |
||||
|
flex: 'none', |
||||
|
width: '100%', |
||||
|
}, |
||||
|
'.ant-list-item-action': { |
||||
|
marginLeft: '0', |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
headerInfo: { |
||||
|
position: 'relative', |
||||
|
textAlign: 'center', |
||||
|
'& > span': { |
||||
|
display: 'inline-block', |
||||
|
marginBottom: '4px', |
||||
|
color: token.colorTextSecondary, |
||||
|
fontSize: token.fontSize, |
||||
|
lineHeight: '22px', |
||||
|
}, |
||||
|
'& > p': { |
||||
|
margin: '0', |
||||
|
color: token.colorTextHeading, |
||||
|
fontSize: '24px', |
||||
|
lineHeight: '32px', |
||||
|
}, |
||||
|
'& > em': { |
||||
|
position: 'absolute', |
||||
|
top: '0', |
||||
|
right: '0', |
||||
|
width: '1px', |
||||
|
height: '56px', |
||||
|
backgroundColor: token.colorSplit, |
||||
|
}, |
||||
|
[`@media screen and (max-width: ${token.screenSM}px)`]: { |
||||
|
marginBottom: '16px', |
||||
|
'& > em': { |
||||
|
display: 'none', |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
listContent: { |
||||
|
fontSize: '0', |
||||
|
[`@media screen and (max-width: ${token.screenXS}px)`]: { |
||||
|
marginLeft: '0', |
||||
|
'& > div': { |
||||
|
marginLeft: '0', |
||||
|
}, |
||||
|
}, |
||||
|
[`@media screen and (max-width: ${token.screenMD}px)`]: { |
||||
|
'& > div': { |
||||
|
display: 'block', |
||||
|
}, |
||||
|
'& > div:last-child': { |
||||
|
top: '0', |
||||
|
width: '100%', |
||||
|
}, |
||||
|
}, |
||||
|
[`@media screen and (max-width: ${token.screenLG}px) and (min-width: @screen-md)`]: |
||||
|
{ |
||||
|
'& > div': { |
||||
|
display: 'block', |
||||
|
}, |
||||
|
'& > div:last-child': { |
||||
|
top: '0', |
||||
|
width: '100%', |
||||
|
}, |
||||
|
}, |
||||
|
[`@media screen and (max-width: ${token.screenXL}px)`]: { |
||||
|
'& > div': { |
||||
|
marginLeft: '24px', |
||||
|
}, |
||||
|
'& > div:last-child': { |
||||
|
top: '0', |
||||
|
}, |
||||
|
}, |
||||
|
'@media screen and (max-width: 1400px)': { |
||||
|
textAlign: 'right', |
||||
|
'& > div:last-child': { |
||||
|
top: '0', |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
listContentItem: { |
||||
|
display: 'inline-block', |
||||
|
marginLeft: '40px', |
||||
|
color: token.colorTextSecondary, |
||||
|
fontSize: token.fontSize, |
||||
|
verticalAlign: 'middle', |
||||
|
'> span': { lineHeight: '20px' }, |
||||
|
'> p': { marginTop: '4px', marginBottom: '0', lineHeight: '22px' }, |
||||
|
}, |
||||
|
extraContentSearch: { |
||||
|
width: '272px', |
||||
|
marginLeft: '16px', |
||||
|
[`@media screen and (max-width: ${token.screenSM}px)`]: { |
||||
|
width: '100%', |
||||
|
marginLeft: '0', |
||||
|
}, |
||||
|
}, |
||||
|
listCard: { |
||||
|
[`@media screen and (max-width: ${token.screenXS}px)`]: { |
||||
|
'.ant-card-head-title': { |
||||
|
overflow: 'open', |
||||
|
}, |
||||
|
}, |
||||
|
[`@media screen and (max-width: ${token.screenMD}px)`]: { |
||||
|
'.ant-radio-group': { |
||||
|
display: 'block', |
||||
|
marginBottom: '8px', |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
standardListForm: { |
||||
|
'.ant-form-item': { |
||||
|
marginBottom: '12px', |
||||
|
'&:last-child': { |
||||
|
marginBottom: '32px', |
||||
|
paddingTop: '4px', |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
formResult: { |
||||
|
width: '100%', |
||||
|
"[class^='title']": { marginBottom: '8px' }, |
||||
|
}, |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
export default useStyles; |
||||
@ -0,0 +1,6 @@ |
|||||
|
import { createStyles } from 'antd-style'; |
||||
|
|
||||
|
const useStyles = createStyles(() => { |
||||
|
return {}; |
||||
|
}); |
||||
|
export default useStyles; |
||||
Loading…
Reference in new issue