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