Browse Source

fix: add missing basic-list files and update snapshots

- 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
afc163 3 weeks ago
parent
commit
59c14497b1
  1. 17
      .claude/settings.local.json
  2. 2
      jest.config.ts
  3. 165
      src/pages/list/basic-list/_mock.ts
  4. 129
      src/pages/list/basic-list/components/OperationModal.tsx
  5. 29
      src/pages/list/basic-list/data.d.ts
  6. 50
      src/pages/list/basic-list/service.ts
  7. 141
      src/pages/list/basic-list/style.style.ts
  8. 6
      src/pages/list/basic-list/utils/utils.style.ts
  9. 71
      src/pages/user/login/__snapshots__/login.test.tsx.snap

17
.claude/settings.local.json

@ -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:*)"
]
}
}

2
jest.config.ts

@ -1,4 +1,4 @@
import { configUmiAlias, createConfig } from '@umijs/max/test';
import { configUmiAlias, createConfig } from '@umijs/max/test.js';
export default async (): Promise<any> => {
const config = await configUmiAlias({

165
src/pages/list/basic-list/_mock.ts

@ -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,
};

129
src/pages/list/basic-list/components/OperationModal.tsx

@ -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;

29
src/pages/list/basic-list/data.d.ts

@ -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[];
};

50
src/pages/list/basic-list/service.ts

@ -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',
},
});
}

141
src/pages/list/basic-list/style.style.ts

@ -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;

6
src/pages/list/basic-list/utils/utils.style.ts

@ -0,0 +1,6 @@
import { createStyles } from 'antd-style';
const useStyles = createStyles(() => {
return {};
});
export default useStyles;

71
src/pages/user/login/__snapshots__/login.test.tsx.snap

@ -3,7 +3,7 @@
exports[`Login Page should login success 1`] = `
<DocumentFragment>
<div
class="ant-app css-var-_r_7_"
class="ant-app css-var-_r_9_"
>
<div
class="acss-trkbkn"
@ -40,7 +40,7 @@ exports[`Login Page should login success 1`] = `
</span>
</div>
<div
style="flex: 1; padding: 32px 0px;"
style="flex: 1 1 0%; padding: 32px 0px;"
>
<div
class="ant-pro-form-login-container "
@ -77,14 +77,14 @@ exports[`Login Page should login success 1`] = `
>
<form
autocomplete="off"
class="ant-form ant-form-vertical css-var-_r_7_ ant-form-css-var ant-pro-form"
class="ant-form ant-form-vertical css-var-_r_9_ ant-form-css-var ant-pro-form"
>
<input
style="display: none;"
type="text"
/>
<div
class="ant-tabs ant-tabs-top ant-tabs-centered css-var-_r_7_ ant-tabs-css-var"
class="ant-tabs ant-tabs-top ant-tabs-centered css-var-_r_9_ ant-tabs-css-var"
>
<div
aria-orientation="horizontal"
@ -180,19 +180,19 @@ exports[`Login Page should login success 1`] = `
class="ant-tabs-tabpane ant-tabs-tabpane-active"
id="rc-tabs-test-panel-account"
role="tabpanel"
tabindex="0"
tabindex="-1"
/>
</div>
</div>
</div>
<div
class="ant-form-item css-var-_r_7_ ant-form-css-var ant-form-item-has-success ant-form-item-vertical"
class="ant-form-item css-var-_r_9_ ant-form-css-var ant-form-item-has-success ant-form-item-vertical"
>
<div
class="ant-row ant-form-item-row css-var-_r_7_"
class="ant-row ant-form-item-row css-var-_r_9_"
>
<div
class="ant-col ant-form-item-control css-var-_r_7_"
class="ant-col ant-form-item-control css-var-_r_9_"
>
<div
class="ant-form-item-control-input"
@ -201,7 +201,7 @@ exports[`Login Page should login success 1`] = `
class="ant-form-item-control-input-content"
>
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-lg ant-input-outlined ant-input-status-success css-var-_r_7_ ant-input-css-var"
class="ant-input-affix-wrapper ant-input-affix-wrapper-lg ant-input-outlined ant-input-status-success css-var-_r_9_ ant-input-css-var"
>
<span
class="ant-input-prefix"
@ -270,13 +270,13 @@ exports[`Login Page should login success 1`] = `
</div>
</div>
<div
class="ant-form-item css-var-_r_7_ ant-form-css-var ant-form-item-has-success ant-form-item-vertical"
class="ant-form-item css-var-_r_9_ ant-form-css-var ant-form-item-has-success ant-form-item-vertical"
>
<div
class="ant-row ant-form-item-row css-var-_r_7_"
class="ant-row ant-form-item-row css-var-_r_9_"
>
<div
class="ant-col ant-form-item-control css-var-_r_7_"
class="ant-col ant-form-item-control css-var-_r_9_"
>
<div
class="ant-form-item-control-input"
@ -285,7 +285,7 @@ exports[`Login Page should login success 1`] = `
class="ant-form-item-control-input-content"
>
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-lg ant-input-outlined ant-input-status-success ant-input-password ant-input-password-large css-var-_r_7_ ant-input-css-var"
class="ant-input-affix-wrapper ant-input-affix-wrapper-lg ant-input-outlined ant-input-status-success ant-input-password ant-input-password-large css-var-_r_9_ ant-input-css-var"
>
<span
class="ant-input-prefix"
@ -354,7 +354,7 @@ exports[`Login Page should login success 1`] = `
style="margin-bottom: 24px;"
>
<label
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked css-var-_r_7_ ant-checkbox-css-var"
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked css-var-_r_9_ ant-checkbox-css-var"
>
<span
class="ant-checkbox ant-wave-target ant-checkbox-checked"
@ -365,9 +365,6 @@ exports[`Login Page should login success 1`] = `
id="autoLogin"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
<span
class="ant-checkbox-label"
@ -382,33 +379,10 @@ exports[`Login Page should login success 1`] = `
</a>
</div>
<button
class="ant-btn css-var-_r_7_ ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ant-btn-lg ant-btn-loading"
class="ant-btn css-var-_r_9_ ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ant-btn-lg"
style="width: 100%;"
type="button"
>
<span
class="ant-btn-icon ant-btn-loading-icon"
>
<span
aria-label="loading"
class="anticon anticon-loading anticon-spin"
role="img"
>
<svg
aria-hidden="true"
data-icon="loading"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
/>
</svg>
</span>
</span>
<span>
Login
</span>
@ -481,7 +455,7 @@ exports[`Login Page should login success 1`] = `
</div>
</div>
<footer
class="ant-layout-footer css-var-_r_7_"
class="ant-layout-footer"
style="padding: 0px; background: none;"
>
<div
@ -558,7 +532,7 @@ exports[`Login Page should login success 1`] = `
/>
</svg>
</span>
Powered by Ant Desgin
Powered by Ant Design
</div>
</div>
</footer>
@ -607,7 +581,7 @@ exports[`Login Page should show login form 1`] = `
</span>
</div>
<div
style="flex: 1; padding: 32px 0px;"
style="flex: 1 1 0%; padding: 32px 0px;"
>
<div
class="ant-pro-form-login-container "
@ -747,7 +721,7 @@ exports[`Login Page should show login form 1`] = `
class="ant-tabs-tabpane ant-tabs-tabpane-active"
id="rc-tabs-test-panel-account"
role="tabpanel"
tabindex="0"
tabindex="-1"
/>
</div>
</div>
@ -931,9 +905,6 @@ exports[`Login Page should show login form 1`] = `
id="autoLogin"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
<span
class="ant-checkbox-label"
@ -1024,7 +995,7 @@ exports[`Login Page should show login form 1`] = `
</div>
</div>
<footer
class="ant-layout-footer css-var-_r_0_"
class="ant-layout-footer"
style="padding: 0px; background: none;"
>
<div
@ -1101,7 +1072,7 @@ exports[`Login Page should show login form 1`] = `
/>
</svg>
</span>
Powered by Ant Desgin
Powered by Ant Design
</div>
</div>
</footer>

Loading…
Cancel
Save