Browse Source

ci: integrate react-doctor for PR health checks (#11777)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
pull/11778/head
Alex Zhu 3 weeks ago
committed by GitHub
parent
commit
dc15dfb3a4
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 20
      .github/workflows/react-doctor.yml
  2. 1
      .gitignore
  3. 1503
      package-lock.json
  4. 2
      package.json
  5. 21
      react-doctor.config.json
  6. 12
      src/components/AvatarList/index.tsx
  7. 23
      src/components/OfflineBanner/index.tsx
  8. 7
      src/components/RightContent/AvatarDropdown.tsx
  9. 21
      src/components/TagSelect/index.tsx
  10. 4
      src/pages/Welcome.tsx
  11. 2
      src/pages/account/center/components/Applications/index.tsx
  12. 2
      src/pages/account/center/components/Projects/index.tsx
  13. 132
      src/pages/account/center/index.tsx
  14. 18
      src/pages/account/settings/components/binding.tsx
  15. 7
      src/pages/account/settings/components/notification.tsx
  16. 30
      src/pages/account/settings/components/security.tsx
  17. 59
      src/pages/account/settings/index.tsx
  18. 140
      src/pages/dashboard/analysis/components/Charts/ChartCard/index.tsx
  19. 10
      src/pages/dashboard/analysis/components/Trend/index.tsx
  20. 2
      src/pages/dashboard/monitor/components/ActiveChart/index.tsx
  21. 20
      src/pages/dashboard/monitor/components/Map/index.style.ts
  22. 26
      src/pages/dashboard/monitor/components/Map/index.tsx
  23. 6
      src/pages/dashboard/workplace/index.tsx
  24. 15
      src/pages/form/advanced-form/index.tsx
  25. 4
      src/pages/form/basic-form/index.tsx
  26. 11
      src/pages/list/basic-list/index.tsx
  27. 16
      src/pages/list/card-list/index.tsx
  28. 5
      src/pages/list/search/applications/index.tsx
  29. 15
      src/pages/list/search/articles/index.tsx
  30. 2
      src/pages/list/search/projects/index.tsx
  31. 4
      src/pages/profile/advanced/index.tsx
  32. 2
      src/pages/result/fail/index.tsx
  33. 20
      src/pages/result/success/index.tsx
  34. 4
      src/pages/table-list/components/CreateForm.tsx
  35. 8
      src/pages/table-list/components/UpdateForm.tsx
  36. 14
      src/pages/table-list/index.tsx
  37. 2
      src/pages/user/login/__snapshots__/login.test.tsx.snap
  38. 6
      src/pages/user/login/index.tsx
  39. 2
      src/pages/user/register-result/index.tsx

20
.github/workflows/react-doctor.yml

@ -0,0 +1,20 @@
name: 🏥 React Doctor
on: push
permissions:
contents: read
pull-requests: write
jobs:
react-doctor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v5
with:
node-version: 22
- uses: millionco/react-doctor@main
with:
diff: true
github-token: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore

@ -34,6 +34,7 @@ functions/*
.umi .umi
.umi-production .umi-production
.umi-test .umi-test
.umi-undefined
.turbopack .turbopack
# screenshot # screenshot

1503
package-lock.json

File diff suppressed because it is too large

2
package.json

@ -27,6 +27,7 @@
"test": "jest", "test": "jest",
"test:coverage": "jest --coverage", "test:coverage": "jest --coverage",
"test:update": "jest -u", "test:update": "jest -u",
"doctor": "react-doctor .",
"tsc": "tsc --noEmit" "tsc": "tsc --noEmit"
}, },
"browserslist": [ "browserslist": [
@ -73,6 +74,7 @@
"@umijs/max-plugin-openapi": "^2.0.3", "@umijs/max-plugin-openapi": "^2.0.3",
"@umijs/request-record": "^1.1.4", "@umijs/request-record": "^1.1.4",
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"react-doctor": "^0.1.2",
"express": "^5.2.1", "express": "^5.2.1",
"geojson": "^0.5.0", "geojson": "^0.5.0",
"gh-pages": "^6.3.0", "gh-pages": "^6.3.0",

21
react-doctor.config.json

@ -0,0 +1,21 @@
{
"ignore": {
"files": [
"config/**",
"mock/**",
"public/**",
"scripts/**",
"types/**",
"cloudflare-worker/**",
"src/.umi*/**",
"src/services/**",
"src/service-worker.js",
"**/_mock.ts",
"**/*.md",
"**/*.less",
"**/*.css"
]
},
"failOn": "error",
"adoptExistingLintConfig": true
}

12
src/components/AvatarList/index.tsx

@ -40,7 +40,17 @@ const Item: React.FC<AvatarItemProps> = ({
const { styles } = useStyles(); const { styles } = useStyles();
const cls = avatarSizeToClassName(styles, size); const cls = avatarSizeToClassName(styles, size);
return ( return (
<li className={cls} onClick={onClick}> <li
className={cls}
onClick={onClick}
onKeyDown={
onClick
? (e) => {
if (e.key === 'Enter') onClick();
}
: undefined
}
>
{tips ? ( {tips ? (
<Tooltip title={tips}> <Tooltip title={tips}>
<Avatar <Avatar

23
src/components/OfflineBanner/index.tsx

@ -1,15 +1,22 @@
import { getIntl } from '@umijs/max'; import { getIntl } from '@umijs/max';
import { Alert } from 'antd'; import { Alert } from 'antd';
import { useEffect, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
const OfflineBanner: React.FC = () => { const OfflineBanner: React.FC = () => {
const [isOnline, setIsOnline] = useState( const isOnlineRef = useRef(true);
typeof navigator !== 'undefined' ? navigator.onLine : true, const [, forceUpdate] = useState<number>(0);
);
useEffect(() => { useEffect(() => {
const handleOnline = () => setIsOnline(true); isOnlineRef.current = navigator.onLine;
const handleOffline = () => setIsOnline(false); forceUpdate((n) => n + 1);
const handleOnline = () => {
isOnlineRef.current = true;
forceUpdate((n: number) => n + 1);
};
const handleOffline = () => {
isOnlineRef.current = false;
forceUpdate((n: number) => n + 1);
};
window.addEventListener('online', handleOnline); window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline); window.addEventListener('offline', handleOffline);
return () => { return () => {
@ -18,7 +25,7 @@ const OfflineBanner: React.FC = () => {
}; };
}, []); }, []);
if (isOnline) return null; if (isOnlineRef.current) return null;
return ( return (
<Alert <Alert
@ -30,7 +37,7 @@ const OfflineBanner: React.FC = () => {
top: 8, top: 8,
left: '50%', left: '50%',
transform: 'translateX(-50%)', transform: 'translateX(-50%)',
zIndex: 999, zIndex: 10,
maxWidth: 480, maxWidth: 480,
}} }}
message={getIntl().formatMessage({ message={getIntl().formatMessage({

7
src/components/RightContent/AvatarDropdown.tsx

@ -6,12 +6,11 @@ import {
import { history, useModel } from '@umijs/max'; import { history, useModel } from '@umijs/max';
import type { MenuProps } from 'antd'; import type { MenuProps } from 'antd';
import { Spin } from 'antd'; import { Spin } from 'antd';
import React from 'react'; import React, { startTransition } from 'react';
import { flushSync } from 'react-dom';
import { outLogin } from '@/services/ant-design-pro/api'; import { outLogin } from '@/services/ant-design-pro/api';
import HeaderDropdown from '../HeaderDropdown'; import HeaderDropdown from '../HeaderDropdown';
export type GlobalHeaderRightProps = { type GlobalHeaderRightProps = {
children?: React.ReactNode; children?: React.ReactNode;
}; };
@ -38,7 +37,7 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
const onMenuClick: MenuProps['onClick'] = (event) => { const onMenuClick: MenuProps['onClick'] = (event) => {
const { key } = event; const { key } = event;
if (key === 'logout') { if (key === 'logout') {
flushSync(() => { startTransition(() => {
setInitialState((s) => ({ ...s, currentUser: undefined })); setInitialState((s) => ({ ...s, currentUser: undefined }));
}); });
loginOut(); loginOut();

21
src/components/TagSelect/index.tsx

@ -6,7 +6,7 @@ import React, { type FC, useMemo, useState } from 'react';
import useStyles from './index.style'; import useStyles from './index.style';
const { CheckableTag } = Tag; const { CheckableTag } = Tag;
export interface TagSelectOptionProps { interface TagSelectOptionProps {
value: string | number; value: string | number;
style?: React.CSSProperties; style?: React.CSSProperties;
checked?: boolean; checked?: boolean;
@ -32,7 +32,7 @@ type TagSelectOptionElement = React.ReactElement<
typeof TagSelectOption typeof TagSelectOption
>; >;
export interface TagSelectProps { interface TagSelectProps {
onChange?: (value: (string | number)[]) => void; onChange?: (value: (string | number)[]) => void;
expandable?: boolean; expandable?: boolean;
value?: (string | number)[]; value?: (string | number)[];
@ -81,9 +81,10 @@ const TagSelect: FC<TagSelectProps> & {
const childrenArray = React.Children.toArray( const childrenArray = React.Children.toArray(
children, children,
) as TagSelectOptionElement[]; ) as TagSelectOptionElement[];
return childrenArray return childrenArray.reduce<(string | number)[]>((acc, child) => {
.filter((child) => isTagSelectOption(child)) if (isTagSelectOption(child)) acc.push(child.props.value);
.map((child) => child.props.value); return acc;
}, []);
}, [children]); }, [children]);
// Use Set for O(1) lookups // Use Set for O(1) lookups
@ -137,9 +138,17 @@ const TagSelect: FC<TagSelectProps> & {
{expandable && ( {expandable && (
<a <a
className={styles.trigger} className={styles.trigger}
onClick={() => { href="#"
onClick={(e) => {
e.preventDefault();
setExpand(!expand); setExpand(!expand);
}} }}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
setExpand(!expand);
}
}}
> >
{expand ? ( {expand ? (
<> <>

4
src/pages/Welcome.tsx

@ -23,12 +23,12 @@ const InfoCard: React.FC<InfoCardProps> = ({ title, index, desc, href }) => (
<a href={href} target="_blank" rel="noopener noreferrer" aria-label={title}> <a href={href} target="_blank" rel="noopener noreferrer" aria-label={title}>
<Card hoverable size="small"> <Card hoverable size="small">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-[#1677ff] text-base font-bold text-white"> <div className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-[#1677ff] text-base font-bold text-white">
{index} {index}
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<h4 className="mb-1 mt-0 text-sm font-semibold">{title}</h4> <h4 className="mb-1 mt-0 text-sm font-semibold">{title}</h4>
<p className="mb-0 line-clamp-2 text-xs text-gray-500">{desc}</p> <p className="mb-0 line-clamp-2 text-xs text-zinc-500">{desc}</p>
</div> </div>
</div> </div>
</Card> </Card>

2
src/pages/account/center/components/Applications/index.tsx

@ -12,7 +12,7 @@ import type { ListItemDataType } from '../../data.d';
import { queryFakeList } from '../../service'; import { queryFakeList } from '../../service';
import useStyles from './index.style'; import useStyles from './index.style';
export function formatWan(val: number) { function formatWan(val: number) {
const v = val * 1; const v = val * 1;
if (!v || Number.isNaN(v)) return ''; if (!v || Number.isNaN(v)) return '';
let result: React.ReactNode = val; let result: React.ReactNode = val;

2
src/pages/account/center/components/Projects/index.tsx

@ -36,7 +36,7 @@ const Projects: React.FC = () => {
cover={<img alt={item.title} src={item.cover} />} cover={<img alt={item.title} src={item.cover} />}
> >
<Card.Meta <Card.Meta
title={<a>{item.title}</a>} title={<a href="#">{item.title}</a>}
description={item.subDescription} description={item.subDescription}
/> />
<div className={styles.cardItemContent}> <div className={styles.cardItemContent}>

132
src/pages/account/center/index.tsx

@ -141,6 +141,68 @@ const TagList: React.FC<{
</div> </div>
); );
}; };
const UserInfo: React.FC<{ user: Partial<CurrentUser> }> = ({ user }) => {
const { styles } = useStyles();
return (
<div className={styles.detail}>
<p>
<ContactsOutlined
style={{
marginRight: 8,
}}
/>
{user.title}
</p>
<p>
<ClusterOutlined
style={{
marginRight: 8,
}}
/>
{user.group}
</p>
<p>
<HomeOutlined
style={{
marginRight: 8,
}}
/>
{
(
user.geographic || {
province: {
label: '',
},
}
).province.label
}
{
(
user.geographic || {
city: {
label: '',
},
}
).city.label
}
</p>
</div>
);
};
const TabContent: React.FC<{ tabValue: tabKeyType }> = ({ tabValue }) => {
if (tabValue === 'projects') {
return <Projects />;
}
if (tabValue === 'applications') {
return <Applications />;
}
if (tabValue === 'articles') {
return <Articles />;
}
return null;
};
const Center: React.FC = () => { const Center: React.FC = () => {
const { styles } = useStyles(); const { styles } = useStyles();
const [tabKey, setTabKey] = useState<tabKeyType>('articles'); const [tabKey, setTabKey] = useState<tabKeyType>('articles');
@ -151,72 +213,6 @@ const Center: React.FC = () => {
queryFn: () => queryCurrent().then((res) => res.data), queryFn: () => queryCurrent().then((res) => res.data),
}); });
// 渲染用户信息
const renderUserInfo = ({
title,
group,
geographic,
}: Partial<CurrentUser>) => {
return (
<div className={styles.detail}>
<p>
<ContactsOutlined
style={{
marginRight: 8,
}}
/>
{title}
</p>
<p>
<ClusterOutlined
style={{
marginRight: 8,
}}
/>
{group}
</p>
<p>
<HomeOutlined
style={{
marginRight: 8,
}}
/>
{
(
geographic || {
province: {
label: '',
},
}
).province.label
}
{
(
geographic || {
city: {
label: '',
},
}
).city.label
}
</p>
</div>
);
};
// 渲染tab切换
const renderChildrenByTabKey = (tabValue: tabKeyType) => {
if (tabValue === 'projects') {
return <Projects />;
}
if (tabValue === 'applications') {
return <Applications />;
}
if (tabValue === 'articles') {
return <Articles />;
}
return null;
};
return ( return (
<GridContent> <GridContent>
<Row gutter={24}> <Row gutter={24}>
@ -235,7 +231,7 @@ const Center: React.FC = () => {
<div className={styles.name}>{currentUser.name}</div> <div className={styles.name}>{currentUser.name}</div>
<div>{currentUser?.signature}</div> <div>{currentUser?.signature}</div>
</div> </div>
{renderUserInfo(currentUser)} <UserInfo user={currentUser} />
<Divider dashed /> <Divider dashed />
<TagList tags={currentUser.tags || []} /> <TagList tags={currentUser.tags || []} />
<Divider <Divider
@ -271,7 +267,7 @@ const Center: React.FC = () => {
setTabKey(_tabKey as tabKeyType); setTabKey(_tabKey as tabKeyType);
}} }}
> >
{renderChildrenByTabKey(tabKey)} <TabContent tabValue={tabKey} />
</Card> </Card>
</Col> </Col>
</Row> </Row>

18
src/pages/account/settings/components/binding.tsx

@ -11,19 +11,31 @@ const BindingView: React.FC = () => {
{ {
title: '绑定淘宝', title: '绑定淘宝',
description: '当前未绑定淘宝账号', description: '当前未绑定淘宝账号',
actions: [<a key="Bind"></a>], actions: [
<a key="Bind" href="#">
</a>,
],
avatar: <TaobaoOutlined className="taobao" />, avatar: <TaobaoOutlined className="taobao" />,
}, },
{ {
title: '绑定支付宝', title: '绑定支付宝',
description: '当前未绑定支付宝账号', description: '当前未绑定支付宝账号',
actions: [<a key="Bind"></a>], actions: [
<a key="Bind" href="#">
</a>,
],
avatar: <AlipayOutlined className="alipay" />, avatar: <AlipayOutlined className="alipay" />,
}, },
{ {
title: '绑定钉钉', title: '绑定钉钉',
description: '当前未绑定钉钉账号', description: '当前未绑定钉钉账号',
actions: [<a key="Bind"></a>], actions: [
<a key="Bind" href="#">
</a>,
],
avatar: <DingdingOutlined className="dingding" />, avatar: <DingdingOutlined className="dingding" />,
}, },
]; ];

7
src/pages/account/settings/components/notification.tsx

@ -3,11 +3,12 @@ import React from 'react';
type Unpacked<T> = T extends (infer U)[] ? U : T; type Unpacked<T> = T extends (infer U)[] ? U : T;
const Action = (
<Switch checkedChildren="开" unCheckedChildren="关" defaultChecked />
);
const NotificationView: React.FC = () => { const NotificationView: React.FC = () => {
const getData = () => { const getData = () => {
const Action = (
<Switch checkedChildren="开" unCheckedChildren="关" defaultChecked />
);
return [ return [
{ {
title: '用户消息', title: '用户消息',

30
src/pages/account/settings/components/security.tsx

@ -19,27 +19,47 @@ const SecurityView: React.FC = () => {
{passwordStrength.strong} {passwordStrength.strong}
</> </>
), ),
actions: [<a key="Modify"></a>], actions: [
<a key="Modify" href="#">
</a>,
],
}, },
{ {
title: '密保手机', title: '密保手机',
description: `已绑定手机:138****8293`, description: `已绑定手机:138****8293`,
actions: [<a key="Modify"></a>], actions: [
<a key="Modify" href="#">
</a>,
],
}, },
{ {
title: '密保问题', title: '密保问题',
description: '未设置密保问题,密保问题可有效保护账户安全', description: '未设置密保问题,密保问题可有效保护账户安全',
actions: [<a key="Set"></a>], actions: [
<a key="Set" href="#">
</a>,
],
}, },
{ {
title: '备用邮箱', title: '备用邮箱',
description: `已绑定邮箱:ant***sign.com`, description: `已绑定邮箱:ant***sign.com`,
actions: [<a key="Modify"></a>], actions: [
<a key="Modify" href="#">
</a>,
],
}, },
{ {
title: 'MFA 设备', title: 'MFA 设备',
description: '未绑定 MFA 设备,绑定后,可以进行二次确认', description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
actions: [<a key="bind"></a>], actions: [
<a key="bind" href="#">
</a>,
],
}, },
]; ];

59
src/pages/account/settings/index.tsx

@ -1,6 +1,6 @@
import { GridContent } from '@ant-design/pro-components'; import { GridContent } from '@ant-design/pro-components';
import { Menu } from 'antd'; import { Menu } from 'antd';
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react'; import React, { useLayoutEffect, useRef, useState } from 'react';
import BaseView from './components/base'; import BaseView from './components/base';
import BindingView from './components/binding'; import BindingView from './components/binding';
import NotificationView from './components/notification'; import NotificationView from './components/notification';
@ -12,6 +12,24 @@ type SettingsState = {
mode: 'inline' | 'horizontal'; mode: 'inline' | 'horizontal';
selectKey: SettingsStateKeys; selectKey: SettingsStateKeys;
}; };
const SettingsContent: React.FC<{ selectKey: SettingsStateKeys }> = ({
selectKey,
}) => {
switch (selectKey) {
case 'base':
return <BaseView />;
case 'security':
return <SecurityView />;
case 'binding':
return <BindingView />;
case 'notification':
return <NotificationView />;
default:
return null;
}
};
const Settings: React.FC = () => { const Settings: React.FC = () => {
const { styles } = useStyles(); const { styles } = useStyles();
const menuMap: Record<string, React.ReactNode> = { const menuMap: Record<string, React.ReactNode> = {
@ -26,7 +44,7 @@ const Settings: React.FC = () => {
}); });
const dom = useRef<HTMLDivElement>(null); const dom = useRef<HTMLDivElement>(null);
const resize = useCallback(() => { const resize = () => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
if (!dom.current) { if (!dom.current) {
return; return;
@ -44,36 +62,25 @@ const Settings: React.FC = () => {
mode: mode as SettingsState['mode'], mode: mode as SettingsState['mode'],
})); }));
}); });
}, []); };
const resizeRef = useRef(resize);
resizeRef.current = resize;
useLayoutEffect(() => { useLayoutEffect(() => {
window.addEventListener('resize', resize); const handler = () => resizeRef.current();
resize(); window.addEventListener('resize', handler);
handler();
return () => { return () => {
window.removeEventListener('resize', resize); window.removeEventListener('resize', handler);
}; };
}, [resize]); }, []);
const getMenu = () => { const getMenu = () => {
return Object.keys(menuMap).map((item) => ({ return Object.keys(menuMap).map((item) => ({
key: item, key: item,
label: menuMap[item], label: menuMap[item],
})); }));
}; };
const renderChildren = () => {
const { selectKey } = initConfig;
switch (selectKey) {
case 'base':
return <BaseView />;
case 'security':
return <SecurityView />;
case 'binding':
return <BindingView />;
case 'notification':
return <NotificationView />;
default:
return null;
}
};
return ( return (
<GridContent> <GridContent>
<div <div
@ -89,17 +96,17 @@ const Settings: React.FC = () => {
mode={initConfig.mode} mode={initConfig.mode}
selectedKeys={[initConfig.selectKey]} selectedKeys={[initConfig.selectKey]}
onClick={({ key }) => { onClick={({ key }) => {
setInitConfig({ setInitConfig((prev) => ({
...initConfig, ...prev,
selectKey: key as SettingsStateKeys, selectKey: key as SettingsStateKeys,
}); }));
}} }}
items={getMenu()} items={getMenu()}
/> />
</div> </div>
<div className={styles.right}> <div className={styles.right}>
<div className={styles.title}>{menuMap[initConfig.selectKey]}</div> <div className={styles.title}>{menuMap[initConfig.selectKey]}</div>
{renderChildren()} <SettingsContent selectKey={initConfig.selectKey} />
</div> </div>
</div> </div>
</GridContent> </GridContent>

140
src/pages/dashboard/analysis/components/Charts/ChartCard/index.tsx

@ -7,7 +7,7 @@ import useStyles from './index.style';
type totalType = () => React.ReactNode; type totalType = () => React.ReactNode;
export type ChartCardProps = { type ChartCardProps = {
title: React.ReactNode; title: React.ReactNode;
action?: React.ReactNode; action?: React.ReactNode;
total?: React.ReactNode | number | (() => React.ReactNode | number); total?: React.ReactNode | number | (() => React.ReactNode | number);
@ -17,80 +17,76 @@ export type ChartCardProps = {
style?: React.CSSProperties; style?: React.CSSProperties;
} & CardProps; } & CardProps;
const ChartCard: React.FC<ChartCardProps> = (props) => { const ChartCardTotal: React.FC<{
const { styles } = useStyles(); total?: number | totalType | React.ReactNode;
const renderTotal = (total?: number | totalType | React.ReactNode) => { totalClassName: string;
if (!total && total !== 0) { }> = ({ total, totalClassName }) => {
if (!total && total !== 0) {
return null;
}
switch (typeof total) {
case 'undefined':
return null; return null;
} case 'function':
let totalDom: React.ReactNode | null = null; return <div className={totalClassName}>{total()}</div>;
switch (typeof total) { default:
case 'undefined': return <div className={totalClassName}>{total}</div>;
totalDom = null; }
break; };
case 'function':
totalDom = <div className={styles.total}>{total()}</div>; const ChartCardContent: React.FC<
break; ChartCardProps & { styles: Record<string, string> }
default: > = ({
totalDom = <div className={styles.total}>{total}</div>; contentHeight,
} title,
return totalDom; avatar,
}; action,
const renderContent = () => { total,
const { footer,
contentHeight, children,
title, styles,
avatar, }) => (
action, <div className={styles.chartCard}>
total, <div
footer, className={clsx(styles.chartTop, {
children, [styles.chartTopMargin]: !children && !footer,
loading, })}
} = props; >
if (loading) { <div className={styles.avatar}>{avatar}</div>
return false; <div className={styles.metaWrap}>
} <div className={styles.meta}>
return ( <span>{title}</span>
<div className={styles.chartCard}> <span className={styles.action}>{action}</span>
<div
className={clsx(styles.chartTop, {
[styles.chartTopMargin]: !children && !footer,
})}
>
<div className={styles.avatar}>{avatar}</div>
<div className={styles.metaWrap}>
<div className={styles.meta}>
<span>{title}</span>
<span className={styles.action}>{action}</span>
</div>
{renderTotal(total)}
</div>
</div> </div>
{children && ( <ChartCardTotal total={total} totalClassName={styles.total} />
<div
className={styles.content}
style={{
height: contentHeight || 'auto',
}}
>
<div className={contentHeight ? styles.contentFixed : undefined}>
{children}
</div>
</div>
)}
{footer && (
<div
className={clsx(styles.footer, {
[styles.footerMargin]: !children,
})}
>
{footer}
</div>
)}
</div> </div>
); </div>
}; {children && (
<div
className={styles.content}
style={{
height: contentHeight || 'auto',
}}
>
<div className={contentHeight ? styles.contentFixed : undefined}>
{children}
</div>
</div>
)}
{footer && (
<div
className={clsx(styles.footer, {
[styles.footerMargin]: !children,
})}
>
{footer}
</div>
)}
</div>
);
const ChartCard: React.FC<ChartCardProps> = (props) => {
const { styles } = useStyles();
const { loading = false, ...rest } = props; const { loading = false, ...rest } = props;
const cardProps = omit(rest, ['total', 'contentHeight', 'action']); const cardProps = omit(rest, ['total', 'contentHeight', 'action']);
return ( return (
@ -103,7 +99,7 @@ const ChartCard: React.FC<ChartCardProps> = (props) => {
}} }}
{...cardProps} {...cardProps}
> >
{renderContent()} {loading ? false : <ChartCardContent {...props} styles={styles} />}
</Card> </Card>
); );
}; };

10
src/pages/dashboard/analysis/components/Trend/index.tsx

@ -3,13 +3,14 @@ import { clsx } from 'clsx';
import React from 'react'; import React from 'react';
import useStyles from './index.style'; import useStyles from './index.style';
export type TrendProps = { type TrendProps = {
colorful?: boolean; colorful?: boolean;
flag: 'up' | 'down'; flag: 'up' | 'down';
style?: React.CSSProperties; style?: React.CSSProperties;
reverseColor?: boolean; reverseColor?: boolean;
className?: string; className?: string;
children?: React.ReactNode; children?: React.ReactNode;
title?: string;
}; };
const Trend: React.FC<TrendProps> = ({ const Trend: React.FC<TrendProps> = ({
@ -18,6 +19,7 @@ const Trend: React.FC<TrendProps> = ({
flag, flag,
children, children,
className, className,
title = '',
...rest ...rest
}) => { }) => {
const { styles } = useStyles(); const { styles } = useStyles();
@ -30,11 +32,7 @@ const Trend: React.FC<TrendProps> = ({
className, className,
); );
return ( return (
<div <div {...rest} className={classString} title={title}>
{...rest}
className={classString}
title={typeof children === 'string' ? children : ''}
>
<span>{children}</span> <span>{children}</span>
{flag && ( {flag && (
<span className={styles[flag]}> <span className={styles[flag]}>

2
src/pages/dashboard/monitor/components/ActiveChart/index.tsx

@ -38,7 +38,7 @@ const ActiveChart = () => {
// Memoize max and median to avoid double sort on every render // Memoize max and median to avoid double sort on every render
const { maxValue, medianValue } = useMemo(() => { const { maxValue, medianValue } = useMemo(() => {
if (!activeData.length) return { maxValue: 0, medianValue: 0 }; if (!activeData.length) return { maxValue: 0, medianValue: 0 };
const sorted = [...activeData].sort((a, b) => a.y - b.y); const sorted = activeData.toSorted((a, b) => a.y - b.y);
return { return {
maxValue: sorted[sorted.length - 1]?.y ?? 0, maxValue: sorted[sorted.length - 1]?.y ?? 0,
medianValue: sorted[Math.floor(sorted.length / 2)]?.y ?? 0, medianValue: sorted[Math.floor(sorted.length / 2)]?.y ?? 0,

20
src/pages/dashboard/monitor/components/Map/index.style.ts

@ -0,0 +1,20 @@
import { createStyles } from 'antd-style';
const useStyles = createStyles({
tooltip: {
position: 'absolute',
background: '#fff',
color: '#334155',
padding: '8px 12px',
borderRadius: '8px',
fontSize: '12px',
border: '1px solid #e2e8f0',
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
pointerEvents: 'none',
opacity: 0,
transition: 'opacity 0.2s',
zIndex: 10,
},
});
export default useStyles;

26
src/pages/dashboard/monitor/components/Map/index.tsx

@ -4,6 +4,7 @@ import * as d3 from 'd3';
import type { Feature, FeatureCollection, Geometry } from 'geojson'; import type { Feature, FeatureCollection, Geometry } from 'geojson';
import { useEffect, useMemo, useRef } from 'react'; import { useEffect, useMemo, useRef } from 'react';
import { feature } from 'topojson-client'; import { feature } from 'topojson-client';
import useStyles from './index.style';
const DATA_COLORS = [ const DATA_COLORS = [
'#ede9fe', '#ede9fe',
@ -103,6 +104,7 @@ function buildLandBitmap(
} }
export default function MonitorMap() { export default function MonitorMap() {
const { styles } = useStyles();
const svgRef = useRef<SVGSVGElement>(null); const svgRef = useRef<SVGSVGElement>(null);
const tooltipRef = useRef<HTMLDivElement>(null); const tooltipRef = useRef<HTMLDivElement>(null);
@ -284,8 +286,10 @@ export default function MonitorMap() {
.on('mousemove', (event) => { .on('mousemove', (event) => {
if (tooltipRef.current) { if (tooltipRef.current) {
const [x, y] = d3.pointer(event, svgRef.current); const [x, y] = d3.pointer(event, svgRef.current);
tooltipRef.current.style.left = `${x + 12}px`; Object.assign(tooltipRef.current.style, {
tooltipRef.current.style.top = `${y - 12}px`; left: `${x + 12}px`,
top: `${y - 12}px`,
});
} }
}) })
.on('mouseleave', function (_event, d) { .on('mouseleave', function (_event, d) {
@ -374,23 +378,7 @@ export default function MonitorMap() {
borderRadius: 8, borderRadius: 8,
}} }}
/> />
<div <div ref={tooltipRef} className={styles.tooltip} />
ref={tooltipRef}
style={{
position: 'absolute',
background: '#fff',
color: '#334155',
padding: '8px 12px',
borderRadius: '8px',
fontSize: '12px',
border: '1px solid #e2e8f0',
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
pointerEvents: 'none',
opacity: 0,
transition: 'opacity 0.2s',
zIndex: 10,
}}
/>
</div> </div>
); );
} }

6
src/pages/dashboard/workplace/index.tsx

@ -118,7 +118,9 @@ const Workplace: FC = () => {
avatar={<Avatar src={item.user.avatar} />} avatar={<Avatar src={item.user.avatar} />}
title={ title={
<span> <span>
<a className={styles.username}>{item.user.name}</a> <a className={styles.username} href="#">
{item.user.name}
</a>
&nbsp; &nbsp;
<span className={styles.event}>{events}</span> <span className={styles.event}>{events}</span>
</span> </span>
@ -279,7 +281,7 @@ const Workplace: FC = () => {
{projectNotice.map((item) => { {projectNotice.map((item) => {
return ( return (
<Col span={12} key={`members-item-${item.id}`}> <Col span={12} key={`members-item-${item.id}`}>
<a> <a href="#">
<Avatar src={item.logo} size="small" /> <Avatar src={item.logo} size="small" />
<span className={styles.member}> <span className={styles.member}>
{item.member.substring(0, 3)} {item.member.substring(0, 3)}

15
src/pages/form/advanced-form/index.tsx

@ -12,7 +12,7 @@ import {
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { Card, Col, message, Popover, Row } from 'antd'; import { Card, Col, message, Popover, Row } from 'antd';
import type { FC } from 'react'; import type { FC } from 'react';
import { useState } from 'react'; import { useRef, useState } from 'react';
import { fakeSubmitForm } from './service'; import { fakeSubmitForm } from './service';
import useStyles from './style.style'; import useStyles from './style.style';
@ -66,6 +66,7 @@ interface ErrorField {
const AdvancedForm: FC<Record<string, any>> = () => { const AdvancedForm: FC<Record<string, any>> = () => {
const { styles } = useStyles(); const { styles } = useStyles();
const [error, setError] = useState<ErrorField[]>([]); const [error, setError] = useState<ErrorField[]>([]);
const keyCounter = useRef(0);
const getErrorInfo = (errors: ErrorField[]) => { const getErrorInfo = (errors: ErrorField[]) => {
const errorCount = errors.filter((item) => item.errors.length > 0).length; const errorCount = errors.filter((item) => item.errors.length > 0).length;
if (!errors || errorCount === 0) { if (!errors || errorCount === 0) {
@ -89,15 +90,16 @@ const AdvancedForm: FC<Record<string, any>> = () => {
| 'dateRange' | 'dateRange'
| 'type'; | 'type';
return ( return (
<li <button
key={key} key={key}
type="button"
className={styles.errorListItem} className={styles.errorListItem}
onClick={() => scrollToField(key)} onClick={() => scrollToField(key)}
> >
<CloseCircleOutlined className={styles.errorIcon} /> <CloseCircleOutlined className={styles.errorIcon} />
<div>{err.errors[0]}</div> <div>{err.errors[0]}</div>
<div className={styles.errorField}>{fieldLabels[key]}</div> <div className={styles.errorField}>{fieldLabels[key]}</div>
</li> </button>
); );
}); });
return ( return (
@ -161,7 +163,9 @@ const AdvancedForm: FC<Record<string, any>> = () => {
return [ return [
<a <a
key="eidit" key="eidit"
onClick={() => { href="#"
onClick={(e) => {
e.preventDefault();
action?.startEditable(record.key); action?.startEditable(record.key);
}} }}
> >
@ -534,8 +538,9 @@ const AdvancedForm: FC<Record<string, any>> = () => {
<EditableProTable<TableFormDateType> <EditableProTable<TableFormDateType>
recordCreatorProps={{ recordCreatorProps={{
record: () => { record: () => {
keyCounter.current += 1;
return { return {
key: `0${Date.now()}`, key: `new-${keyCounter.current}`,
}; };
}, },
}} }}

4
src/pages/form/basic-form/index.tsx

@ -9,7 +9,7 @@ import {
ProFormText, ProFormText,
ProFormTextArea, ProFormTextArea,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { useMutation } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Card, message } from 'antd'; import { Card, message } from 'antd';
import type { FC } from 'react'; import type { FC } from 'react';
import { fakeSubmitForm } from './service'; import { fakeSubmitForm } from './service';
@ -17,10 +17,12 @@ import useStyles from './style.style';
const BasicForm: FC<Record<string, any>> = () => { const BasicForm: FC<Record<string, any>> = () => {
const { styles } = useStyles(); const { styles } = useStyles();
const queryClient = useQueryClient();
const { mutate: run } = useMutation({ const { mutate: run } = useMutation({
mutationFn: fakeSubmitForm, mutationFn: fakeSubmitForm,
onSuccess: () => { onSuccess: () => {
message.success('提交成功'); message.success('提交成功');
queryClient.invalidateQueries({ queryKey: ['basic-form'] });
}, },
}); });
const onFinish = async (values: Record<string, any>) => { const onFinish = async (values: Record<string, any>) => {

11
src/pages/list/basic-list/index.tsx

@ -1,6 +1,6 @@
import { DownOutlined, PlusOutlined } from '@ant-design/icons'; import { DownOutlined, PlusOutlined } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components'; import { PageContainer } from '@ant-design/pro-components';
import { useMutation, useQuery } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { import {
Avatar, Avatar,
Button, Button,
@ -71,7 +71,7 @@ const ListContent = ({
</div> </div>
); );
}; };
export const BasicList: FC = () => { const BasicList: FC = () => {
const { styles } = useStyles(); const { styles } = useStyles();
const [done, setDone] = useState<boolean>(false); const [done, setDone] = useState<boolean>(false);
const [open, setVisible] = useState<boolean>(false); const [open, setVisible] = useState<boolean>(false);
@ -83,6 +83,7 @@ export const BasicList: FC = () => {
queryFn: () => queryFakeList({ count: 50 }).then((res) => res.data), queryFn: () => queryFakeList({ count: 50 }).then((res) => res.data),
}); });
const queryClient = useQueryClient();
const { mutate: postRun } = useMutation({ const { mutate: postRun } = useMutation({
mutationFn: async ({ method, params }: { method: string; params: any }) => { mutationFn: async ({ method, params }: { method: string; params: any }) => {
if (method === 'remove') { if (method === 'remove') {
@ -93,6 +94,9 @@ export const BasicList: FC = () => {
} }
return addFakeList(params); return addFakeList(params);
}, },
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['basic-list'] });
},
}); });
// Wrapper to handle the original calling convention // Wrapper to handle the original calling convention
@ -167,7 +171,7 @@ export const BasicList: FC = () => {
], ],
}} }}
> >
<a> <a href="#">
<DownOutlined /> <DownOutlined />
</a> </a>
</Dropdown> </Dropdown>
@ -227,6 +231,7 @@ export const BasicList: FC = () => {
actions={[ actions={[
<a <a
key="edit" key="edit"
href="#"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
showEditModal(item); showEditModal(item);

16
src/pages/list/card-list/index.tsx

@ -22,21 +22,21 @@ const CardList = () => {
</p> </p>
<div className={styles.contentLink}> <div className={styles.contentLink}>
<a> <a href="#">
<img <img
alt="" alt=""
src="https://gw.alipayobjects.com/zos/rmsportal/MjEImQtenlyueSmVEfUD.svg" src="https://gw.alipayobjects.com/zos/rmsportal/MjEImQtenlyueSmVEfUD.svg"
/>{' '} />{' '}
</a> </a>
<a> <a href="#">
<img <img
alt="" alt=""
src="https://gw.alipayobjects.com/zos/rmsportal/NbuDUAuBlIApFuDvWiND.svg" src="https://gw.alipayobjects.com/zos/rmsportal/NbuDUAuBlIApFuDvWiND.svg"
/>{' '} />{' '}
</a> </a>
<a> <a href="#">
<img <img
alt="" alt=""
src="https://gw.alipayobjects.com/zos/rmsportal/ohOEPSYdDTNnyMbGuyLb.svg" src="https://gw.alipayobjects.com/zos/rmsportal/ohOEPSYdDTNnyMbGuyLb.svg"
@ -79,8 +79,12 @@ const CardList = () => {
hoverable hoverable
className={styles.card} className={styles.card}
actions={[ actions={[
<a key="option1"></a>, <a key="option1" href="#">
<a key="option2"></a>,
</a>,
<a key="option2" href="#">
</a>,
]} ]}
> >
<Card.Meta <Card.Meta
@ -91,7 +95,7 @@ const CardList = () => {
src={item.avatar} src={item.avatar}
/> />
} }
title={<a>{item.title}</a>} title={<a href="#">{item.title}</a>}
description={ description={
<Paragraph <Paragraph
className={styles.item} className={styles.item}

5
src/pages/list/search/applications/index.tsx

@ -24,7 +24,8 @@ import { categoryOptions } from '../../mock';
import type { ListItemDataType } from './data.d'; import type { ListItemDataType } from './data.d';
import { queryFakeList } from './service'; import { queryFakeList } from './service';
import useStyles from './style.style'; import useStyles from './style.style';
export function formatWan(val: number) {
function formatWan(val: number) {
const v = val * 1; const v = val * 1;
if (!v || Number.isNaN(v)) return ''; if (!v || Number.isNaN(v)) return '';
let result: React.ReactNode = val; let result: React.ReactNode = val;
@ -76,7 +77,7 @@ const CardInfo: React.FC<{
</div> </div>
); );
}; };
export const Applications: FC<Record<string, any>> = () => { const Applications: FC<Record<string, any>> = () => {
const { styles } = useStyles(); const { styles } = useStyles();
const { const {
data, data,

15
src/pages/list/search/articles/index.tsx

@ -183,7 +183,20 @@ const Articles: FC = () => {
options={ownerOptions} options={ownerOptions}
/> />
</FormItem> </FormItem>
<a className={styles.selfTrigger} onClick={setOwner}> <a
className={styles.selfTrigger}
href="#"
onClick={(e) => {
e.preventDefault();
setOwner();
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
setOwner();
}
}}
>
</a> </a>
</StandardFormRow> </StandardFormRow>

2
src/pages/list/search/projects/index.tsx

@ -50,7 +50,7 @@ const Projects: FC = () => {
cover={<img alt={item.title} src={item.cover} />} cover={<img alt={item.title} src={item.cover} />}
> >
<Card.Meta <Card.Meta
title={<a>{item.title}</a>} title={<a href="#">{item.title}</a>}
description={ description={
<Paragraph <Paragraph
ellipsis={{ ellipsis={{

4
src/pages/profile/advanced/index.tsx

@ -150,7 +150,7 @@ const descriptionItems: DescriptionsProps['items'] = [
{ key: '1', label: '创建人', children: '曲丽丽' }, { key: '1', label: '创建人', children: '曲丽丽' },
{ key: '2', label: '订购产品', children: 'XX 服务' }, { key: '2', label: '订购产品', children: 'XX 服务' },
{ key: '3', label: '创建时间', children: '2017-07-07' }, { key: '3', label: '创建时间', children: '2017-07-07' },
{ key: '4', label: '关联单据', children: <a href="">12421</a> }, { key: '4', label: '关联单据', children: <a href="#">12421</a> },
{ key: '5', label: '生效日期', children: '2017-07-07 ~ 2017-08-08' }, { key: '5', label: '生效日期', children: '2017-07-07 ~ 2017-08-08' },
{ key: '6', label: '备注', children: '请于两个工作日内确认' }, { key: '6', label: '备注', children: '请于两个工作日内确认' },
]; ];
@ -305,7 +305,7 @@ const Advanced: FC = () => {
}} }}
/> />
<div> <div>
<a href=""></a> <a href="#"></a>
</div> </div>
</div> </div>
); );

2
src/pages/result/fail/index.tsx

@ -23,6 +23,7 @@ export default () => {
/> />
<span></span> <span></span>
<a <a
href="#"
style={{ style={{
marginLeft: 16, marginLeft: 16,
}} }}
@ -40,6 +41,7 @@ export default () => {
/> />
<span></span> <span></span>
<a <a
href="#"
style={{ style={{
marginLeft: 16, marginLeft: 16,
}} }}

20
src/pages/result/success/index.tsx

@ -1,6 +1,7 @@
import { DingdingOutlined } from '@ant-design/icons'; import { DingdingOutlined } from '@ant-design/icons';
import { GridContent } from '@ant-design/pro-components'; import { GridContent } from '@ant-design/pro-components';
import { Button, Card, Descriptions, Result, Steps } from 'antd'; import { Button, Card, Descriptions, Result, Steps } from 'antd';
import React from 'react';
import useStyles from './index.style'; import useStyles from './index.style';
const descriptionItems = [ const descriptionItems = [
@ -9,6 +10,14 @@ const descriptionItems = [
{ key: 'time', label: '生效时间', children: '2016-12-12 ~ 2017-12-12' }, { key: 'time', label: '生效时间', children: '2016-12-12 ~ 2017-12-12' },
]; ];
const extra = (
<>
<Button type="primary"></Button>
<Button></Button>
<Button></Button>
</>
);
const Success: React.FC = () => { const Success: React.FC = () => {
const { styles } = useStyles(); const { styles } = useStyles();
const desc1 = ( const desc1 = (
@ -42,7 +51,7 @@ const Success: React.FC = () => {
}} }}
> >
<span></span> <span></span>
<a href=""> <a href="#">
<DingdingOutlined <DingdingOutlined
style={{ style={{
color: '#00A0E9', color: '#00A0E9',
@ -112,20 +121,13 @@ const Success: React.FC = () => {
/> />
</> </>
); );
const extra = (
<>
<Button type="primary"></Button>
<Button></Button>
<Button></Button>
</>
);
return ( return (
<GridContent> <GridContent>
<Card variant="borderless"> <Card variant="borderless">
<Result <Result
status="success" status="success"
title="提交成功" title="提交成功"
subTitle="提交结果页用于反馈一系列操作任务的处理结果, 如果仅是简单操作,使用 Message 全局提示反馈即可。 本文字区域可以展示简单的补充说明,如果有类似展示 “单据”的需求,下面这个灰色区域可以呈现比较复杂的内容。" subTitle='提交结果页用于反馈一系列操作任务的处理结果, 如果仅是简单操作,使用 Message 全局提示反馈即可。 本文字区域可以展示简单的补充说明,如果有类似展示 "单据"的需求,下面这个灰色区域可以呈现比较复杂的内容。'
extra={extra} extra={extra}
style={{ style={{
marginBottom: 16, marginBottom: 16,

4
src/pages/table-list/components/CreateForm.tsx

@ -5,7 +5,7 @@ import {
ProFormText, ProFormText,
ProFormTextArea, ProFormTextArea,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { useMutation } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { FormattedMessage, useIntl } from '@umijs/max'; import { FormattedMessage, useIntl } from '@umijs/max';
import { Button, message } from 'antd'; import { Button, message } from 'antd';
import type { FC } from 'react'; import type { FC } from 'react';
@ -19,6 +19,7 @@ const CreateForm: FC<CreateFormProps> = (props) => {
const { reload } = props; const { reload } = props;
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
const queryClient = useQueryClient();
/** /**
* @en-US International configuration * @en-US International configuration
* @zh-CN * @zh-CN
@ -29,6 +30,7 @@ const CreateForm: FC<CreateFormProps> = (props) => {
mutationFn: addRule, mutationFn: addRule,
onSuccess: () => { onSuccess: () => {
messageApi.success('Added successfully'); messageApi.success('Added successfully');
queryClient.invalidateQueries({ queryKey: ['rule'] });
reload?.(); reload?.();
}, },
onError: () => { onError: () => {

8
src/pages/table-list/components/UpdateForm.tsx

@ -6,13 +6,13 @@ import {
ProFormTextArea, ProFormTextArea,
StepsForm, StepsForm,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { useMutation } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { FormattedMessage, useIntl } from '@umijs/max'; import { FormattedMessage, useIntl } from '@umijs/max';
import { Modal, message } from 'antd'; import { Modal, message } from 'antd';
import React, { cloneElement, useCallback, useState } from 'react'; import React, { cloneElement, useCallback, useState } from 'react';
import { updateRule } from '@/services/ant-design-pro/api'; import { updateRule } from '@/services/ant-design-pro/api';
export type FormValueType = { type FormValueType = {
target?: string; target?: string;
template?: string; template?: string;
type?: string; type?: string;
@ -20,7 +20,7 @@ export type FormValueType = {
frequency?: string; frequency?: string;
} & Partial<API.RuleListItem>; } & Partial<API.RuleListItem>;
export type UpdateFormProps = { type UpdateFormProps = {
trigger?: React.ReactElement<any>; trigger?: React.ReactElement<any>;
onOk?: () => void; onOk?: () => void;
values: Partial<API.RuleListItem>; values: Partial<API.RuleListItem>;
@ -30,6 +30,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
const { onOk, values, trigger } = props; const { onOk, values, trigger } = props;
const intl = useIntl(); const intl = useIntl();
const queryClient = useQueryClient();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@ -39,6 +40,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
mutationFn: updateRule, mutationFn: updateRule,
onSuccess: () => { onSuccess: () => {
messageApi.success('Configuration is successful'); messageApi.success('Configuration is successful');
queryClient.invalidateQueries({ queryKey: ['rule'] });
onOk?.(); onOk?.();
}, },
onError: () => { onError: () => {

14
src/pages/table-list/index.tsx

@ -9,7 +9,7 @@ import {
ProDescriptions, ProDescriptions,
ProTable, ProTable,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { useMutation } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { FormattedMessage, useIntl } from '@umijs/max'; import { FormattedMessage, useIntl } from '@umijs/max';
import { Button, Drawer, type FormInstance, Input, message } from 'antd'; import { Button, Drawer, type FormInstance, Input, message } from 'antd';
import React, { useCallback, useRef, useState } from 'react'; import React, { useCallback, useRef, useState } from 'react';
@ -19,6 +19,7 @@ import UpdateForm from './components/UpdateForm';
const TableList: React.FC = () => { const TableList: React.FC = () => {
const actionRef = useRef<ActionType | null>(null); const actionRef = useRef<ActionType | null>(null);
const queryClient = useQueryClient();
const [showDetail, setShowDetail] = useState<boolean>(false); const [showDetail, setShowDetail] = useState<boolean>(false);
const [currentRow, setCurrentRow] = useState<API.RuleListItem>(); const [currentRow, setCurrentRow] = useState<API.RuleListItem>();
@ -37,6 +38,7 @@ const TableList: React.FC = () => {
onSuccess: () => { onSuccess: () => {
setSelectedRows([]); setSelectedRows([]);
actionRef.current?.reloadAndRest?.(); actionRef.current?.reloadAndRest?.();
queryClient.invalidateQueries({ queryKey: ['rule'] });
messageApi.success('Deleted successfully and will refresh soon'); messageApi.success('Deleted successfully and will refresh soon');
}, },
@ -57,7 +59,9 @@ const TableList: React.FC = () => {
render: (dom, entity) => { render: (dom, entity) => {
return ( return (
<a <a
onClick={() => { href="#"
onClick={(e) => {
e.preventDefault();
setCurrentRow(entity); setCurrentRow(entity);
setShowDetail(true); setShowDetail(true);
}} }}
@ -193,7 +197,7 @@ const TableList: React.FC = () => {
render: (_, record) => [ render: (_, record) => [
<UpdateForm <UpdateForm
trigger={ trigger={
<a> <a href="#">
<FormattedMessage <FormattedMessage
id="pages.searchTable.config" id="pages.searchTable.config"
defaultMessage="Configuration" defaultMessage="Configuration"
@ -269,7 +273,9 @@ const TableList: React.FC = () => {
id="pages.searchTable.chosen" id="pages.searchTable.chosen"
defaultMessage="Chosen" defaultMessage="Chosen"
/>{' '} />{' '}
<a style={{ fontWeight: 600 }}>{selectedRowsState.length}</a>{' '} <span style={{ fontWeight: 600 }}>
{selectedRowsState.length}
</span>{' '}
<FormattedMessage <FormattedMessage
id="pages.searchTable.item" id="pages.searchTable.item"
defaultMessage="项" defaultMessage="项"

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

@ -373,6 +373,7 @@ exports[`Login Page should login success 1`] = `
</span> </span>
</label> </label>
<a <a
href="#"
style="float: right;" style="float: right;"
> >
Forgot Password ? Forgot Password ?
@ -935,6 +936,7 @@ exports[`Login Page should show login form 1`] = `
</span> </span>
</label> </label>
<a <a
href="#"
style="float: right;" style="float: right;"
> >
Forgot Password ? Forgot Password ?

6
src/pages/user/login/index.tsx

@ -21,8 +21,7 @@ import {
} from '@umijs/max'; } from '@umijs/max';
import { Alert, App, Tabs } from 'antd'; import { Alert, App, Tabs } from 'antd';
import { createStyles } from 'antd-style'; import { createStyles } from 'antd-style';
import React, { useState } from 'react'; import React, { startTransition, useState } from 'react';
import { flushSync } from 'react-dom';
import { Footer } from '@/components'; import { Footer } from '@/components';
import { login } from '@/services/ant-design-pro/api'; import { login } from '@/services/ant-design-pro/api';
import { getFakeCaptcha } from '@/services/ant-design-pro/login'; import { getFakeCaptcha } from '@/services/ant-design-pro/login';
@ -142,7 +141,7 @@ const Login: React.FC = () => {
const fetchUserInfo = async () => { const fetchUserInfo = async () => {
const userInfo = await initialState?.fetchUserInfo?.(); const userInfo = await initialState?.fetchUserInfo?.();
if (userInfo) { if (userInfo) {
flushSync(() => { startTransition(() => {
setInitialState((s) => ({ setInitialState((s) => ({
...s, ...s,
currentUser: userInfo, currentUser: userInfo,
@ -399,6 +398,7 @@ const Login: React.FC = () => {
/> />
</ProFormCheckbox> </ProFormCheckbox>
<a <a
href="#"
style={{ style={{
float: 'right', float: 'right',
}} }}

2
src/pages/user/register-result/index.tsx

@ -9,7 +9,7 @@ const RegisterResult: React.FC<Record<string, unknown>> = () => {
const actions = ( const actions = (
<div className={styles.actions}> <div className={styles.actions}>
<a href=""> <a href="#">
<Button size="large" type="primary"> <Button size="large" type="primary">
<span></span> <span></span>
</Button> </Button>

Loading…
Cancel
Save