Skip to content

Commit 27d2de8

Browse files
committed
feat(projects): 添加antd的首页布局
1 parent d6a787b commit 27d2de8

30 files changed

+3368
-31
lines changed

.dumi/hooks/useDark.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createContext, useContext } from 'react';
2+
3+
export const DarkContext = createContext(false);
4+
5+
export default function useDark() {
6+
return useContext(DarkContext);
7+
}

.dumi/hooks/useLocale.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useLocale as useDumiLocale } from 'dumi';
2+
3+
export interface LocaleMap<
4+
K extends PropertyKey = PropertyKey,
5+
V extends string | ((...params: any[]) => string) = string,
6+
> {
7+
cn: Record<K, V>;
8+
en: Record<K, V>;
9+
}
10+
11+
const useLocale = <
12+
K extends PropertyKey = PropertyKey,
13+
V extends string | ((...params: any[]) => string) = string,
14+
>(
15+
localeMap?: LocaleMap<K, V>,
16+
): [Record<K, V>, 'cn' | 'en'] => {
17+
const { id } = useDumiLocale();
18+
const localeType = id === 'zh-CN' ? 'cn' : 'en';
19+
return [localeMap?.[localeType]!, localeType] as const;
20+
};
21+
22+
export default useLocale;

.dumi/pages/404/index.tsx

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { HomeOutlined } from '@ant-design/icons';
2+
import { Button, Result } from 'antd';
3+
import { Link, useLocation } from 'dumi';
4+
import React, { useEffect } from 'react';
5+
import * as utils from '../../theme/utils';
6+
7+
export interface NotFoundProps {
8+
router: {
9+
push: (pathname: string) => void;
10+
replace: (pathname: string) => void;
11+
};
12+
}
13+
14+
const DIRECT_MAP: Record<string, string> = {
15+
'docs/spec/download': 'docs/resources',
16+
'docs/spec/work-with-us': 'docs/resources',
17+
};
18+
19+
const NotFoundPage: React.FC<NotFoundProps> = ({ router }) => {
20+
const { pathname } = useLocation();
21+
22+
const isZhCN = utils.isZhCN(pathname);
23+
24+
useEffect(() => {
25+
const directLinks = Object.keys(DIRECT_MAP);
26+
for (let i = 0; i < directLinks.length; i += 1) {
27+
const matchPath = directLinks[i];
28+
if (pathname.includes(matchPath)) {
29+
router.replace(
30+
utils.getLocalizedPathname(`/${DIRECT_MAP[matchPath]}`, isZhCN)
31+
.pathname,
32+
);
33+
}
34+
}
35+
36+
// Report if necessary
37+
const { yuyanMonitor } = window as any;
38+
yuyanMonitor?.log({
39+
code: 11,
40+
msg: `Page not found: ${location.href}; Source: ${document.referrer}`,
41+
});
42+
}, []);
43+
44+
return (
45+
<Result
46+
status="404"
47+
title="404"
48+
subTitle={
49+
isZhCN
50+
? '你访问的页面貌似不存在?'
51+
: 'Sorry, the page you visited does not exist.'
52+
}
53+
extra={
54+
<Link to={utils.getLocalizedPathname('/', isZhCN)}>
55+
<Button type="primary" icon={<HomeOutlined />}>
56+
{isZhCN ? '返回 Ant Design 首页' : 'Back to home page'}
57+
</Button>
58+
</Link>
59+
}
60+
/>
61+
);
62+
};
63+
64+
export default NotFoundPage;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import { Badge, Carousel, Skeleton, Typography } from 'antd';
2+
import { createStyles, useTheme } from 'antd-style';
3+
import classNames from 'classnames';
4+
import React, { useContext } from 'react';
5+
6+
import useLocale from '../../../hooks/useLocale';
7+
import SiteContext from '../../../theme/slots/SiteContext';
8+
import type { Extra, Icon } from './util';
9+
import { getCarouselStyle, useSiteData } from './util';
10+
11+
const useStyle = createStyles(({ token, css, cx }) => {
12+
const { carousel } = getCarouselStyle();
13+
14+
const itemBase = css`
15+
display: flex;
16+
flex: 1 1 0;
17+
flex-direction: column;
18+
align-items: stretch;
19+
text-decoration: none;
20+
background: ${token.colorBgContainer};
21+
border: ${token.lineWidth}px solid ${token.colorBorderSecondary};
22+
border-radius: ${token.borderRadiusLG}px;
23+
transition: all ${token.motionDurationSlow};
24+
padding-block: ${token.paddingMD}px;
25+
padding-inline: ${token.paddingLG}px;
26+
box-sizing: border-box;
27+
`;
28+
29+
return {
30+
itemBase,
31+
ribbon: css`
32+
& > .${cx(itemBase)} {
33+
height: 100%;
34+
}
35+
`,
36+
cardItem: css`
37+
&:hover {
38+
box-shadow: ${token.boxShadowCard};
39+
}
40+
`,
41+
sliderItem: css`
42+
margin: 0 ${token.margin}px;
43+
text-align: start;
44+
`,
45+
container: css`
46+
display: flex;
47+
width: 100%;
48+
max-width: 100%;
49+
margin-inline: auto;
50+
box-sizing: border-box;
51+
column-gap: ${token.paddingMD * 2}px;
52+
align-items: stretch;
53+
text-align: start;
54+
min-height: 178px;
55+
> * {
56+
width: calc((100% - ${token.marginXXL * 2}px) / 3);
57+
}
58+
`,
59+
carousel,
60+
};
61+
});
62+
63+
interface RecommendItemProps {
64+
extra: Extra;
65+
index: number;
66+
icons: Icon[];
67+
className?: string;
68+
}
69+
const RecommendItem: React.FC<RecommendItemProps> = ({
70+
extra,
71+
index,
72+
icons,
73+
className,
74+
}) => {
75+
const token = useTheme();
76+
const { styles } = useStyle();
77+
78+
if (!extra) {
79+
return <Skeleton key={index} />;
80+
}
81+
const icon = icons.find((i) => i.name === extra.source);
82+
83+
const card = (
84+
<a
85+
key={extra?.title}
86+
href={extra.href}
87+
target="_blank"
88+
className={classNames(styles.itemBase, className)}
89+
rel="noreferrer"
90+
>
91+
<Typography.Title level={5}>{extra?.title}</Typography.Title>
92+
<Typography.Paragraph type="secondary" style={{ flex: 'auto' }}>
93+
{extra.description}
94+
</Typography.Paragraph>
95+
<div
96+
style={{
97+
display: 'flex',
98+
justifyContent: 'space-between',
99+
alignItems: 'center',
100+
}}
101+
>
102+
<Typography.Text>{extra.date}</Typography.Text>
103+
{icon && (
104+
<img
105+
src={icon.href}
106+
style={{ height: token.fontSize }}
107+
alt="banner"
108+
/>
109+
)}
110+
</div>
111+
</a>
112+
);
113+
114+
if (index === 0) {
115+
return (
116+
<Badge.Ribbon text="HOT" color="red" rootClassName={styles.ribbon}>
117+
{card}
118+
</Badge.Ribbon>
119+
);
120+
}
121+
122+
return card;
123+
};
124+
125+
export const BannerRecommendsFallback: React.FC = () => {
126+
const { isMobile } = useContext(SiteContext);
127+
const { styles } = useStyle();
128+
129+
const list = Array(3).fill(1);
130+
131+
return isMobile ? (
132+
<Carousel className={styles.carousel}>
133+
{list.map((_, index) => (
134+
<div key={index} className={styles.itemBase}>
135+
<Skeleton active style={{ padding: '0 24px' }} />
136+
</div>
137+
))}
138+
</Carousel>
139+
) : (
140+
<div className={styles.container}>
141+
{list.map((_, index) => (
142+
<div key={index} className={styles.itemBase}>
143+
<Skeleton active />
144+
</div>
145+
))}
146+
</div>
147+
);
148+
};
149+
150+
const BannerRecommends: React.FC = () => {
151+
const { styles } = useStyle();
152+
const [, lang] = useLocale();
153+
const { isMobile } = React.useContext(SiteContext);
154+
const data = useSiteData();
155+
const extras = data?.extras?.[lang];
156+
const icons = data?.icons || [];
157+
const first3 =
158+
!extras || extras.length === 0 ? Array(3).fill(null) : extras.slice(0, 3);
159+
160+
if (!data) {
161+
return <BannerRecommendsFallback />;
162+
}
163+
164+
if (isMobile) {
165+
return (
166+
<Carousel className={styles.carousel}>
167+
{first3.map((extra, index) => (
168+
<div key={index}>
169+
<RecommendItem
170+
extra={extra}
171+
index={index}
172+
icons={icons}
173+
className={styles.sliderItem}
174+
/>
175+
</div>
176+
))}
177+
</Carousel>
178+
);
179+
}
180+
181+
return (
182+
<div className={styles.container}>
183+
{first3.map((extra, index) => (
184+
<RecommendItem
185+
extra={extra}
186+
index={index}
187+
icons={icons}
188+
className={styles.cardItem}
189+
key={index}
190+
/>
191+
))}
192+
</div>
193+
);
194+
};
195+
196+
export default BannerRecommends;

0 commit comments

Comments
 (0)