Skip to content

Commit 6435643

Browse files
committed
修复挑战详情页面PC端布局显示与内容错乱问题
1 parent a21c79b commit 6435643

File tree

6 files changed

+572
-259
lines changed

6 files changed

+572
-259
lines changed

src/components/ChallengeDetailPage/ChallengeActions.tsx

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { Button } from 'antd';
2-
import { HomeOutlined } from '@ant-design/icons';
3-
import { useNavigate } from 'react-router-dom';
1+
import { Button, Space } from 'antd';
2+
import { HomeOutlined, RightOutlined } from '@ant-design/icons';
3+
import { useNavigate, Link } from 'react-router-dom';
44
import { useTranslation } from 'react-i18next';
5+
import { Challenge } from '../../types/challenge';
56

67
interface ChallengeActionsProps {
7-
challenge: any;
8+
challenge: Challenge;
89
/**
910
* 是否为移动端视图
1011
*/
@@ -18,25 +19,57 @@ const ChallengeActions: React.FC<ChallengeActionsProps> = ({ challenge, isMobile
1819
const navigate = useNavigate();
1920
const { t } = useTranslation();
2021

22+
// 移动端布局
23+
if (isMobile) {
24+
return (
25+
<div style={{
26+
display: 'flex',
27+
justifyContent: 'center',
28+
width: '100%',
29+
marginTop: '8px'
30+
}}>
31+
<Button
32+
type="primary"
33+
icon={<HomeOutlined />}
34+
onClick={() => navigate('/challenges')}
35+
size="middle"
36+
style={{
37+
width: '100%',
38+
maxWidth: '400px'
39+
}}
40+
>
41+
{t('challenge.detail.backToList')}
42+
</Button>
43+
</div>
44+
);
45+
}
46+
47+
// PC端布局
2148
return (
22-
<div style={{
23-
display: 'flex',
24-
justifyContent: 'center',
25-
width: '100%',
26-
marginTop: isMobile ? '8px' : '16px'
27-
}}>
28-
<Button
29-
type="primary"
30-
icon={<HomeOutlined />}
31-
onClick={() => navigate('/challenges')}
32-
size={isMobile ? "middle" : "large"}
33-
style={{
34-
width: isMobile ? '100%' : 'auto',
35-
maxWidth: '400px'
36-
}}
37-
>
49+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
50+
<div>
51+
{challenge.externalLink && (
52+
<a
53+
href={challenge.externalLink}
54+
target="_blank"
55+
rel="noopener noreferrer"
56+
style={{
57+
backgroundColor: '#1890ff',
58+
color: 'white',
59+
padding: '8px 16px',
60+
borderRadius: '4px',
61+
textDecoration: 'none',
62+
display: 'inline-flex',
63+
alignItems: 'center'
64+
}}
65+
>
66+
{t('challenge.detail.startChallenge')} <RightOutlined style={{ marginLeft: '5px' }} />
67+
</a>
68+
)}
69+
</div>
70+
<Link to="/challenges" style={{ color: '#1890ff' }}>
3871
{t('challenge.detail.backToList')}
39-
</Button>
72+
</Link>
4073
</div>
4174
);
4275
};

src/components/ChallengeDetailPage/ChallengeDescription.tsx

Lines changed: 152 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,17 @@ interface ChallengeDescriptionProps {
1919
// 测试图片 - 1x1像素透明PNG
2020
const FALLBACK_IMAGE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
2121

22-
// 图片组件
23-
const MarkdownImage = ({ node }: { node: any }) => {
22+
// 图片组件 - 修复props类型问题
23+
const MarkdownImage = (props: any) => {
24+
const { src, alt } = props;
2425
// 使用传入的src或回退到默认图片
25-
const imageSrc = node.properties?.src || FALLBACK_IMAGE;
26-
27-
// 如果是data:image类型的图片,直接使用原始src
28-
const isDataImage = typeof imageSrc === 'string' && imageSrc.startsWith('data:image');
29-
30-
// 检查图片源是否完整 (data:image格式但很短,可能被截断)
31-
const isTruncatedBase64 = isDataImage && imageSrc.length < 100;
32-
33-
// 解决方案:如果检测到是被截断的data:image,尝试从node属性中提取完整的图片数据
34-
// 这是处理React-Markdown可能截断长字符串的情况
35-
let fullImageSrc = imageSrc;
36-
if (isTruncatedBase64 && node && node.properties && node.properties.src) {
37-
fullImageSrc = node.properties.src;
38-
}
26+
const imageSrc = src || FALLBACK_IMAGE;
3927

4028
// 使用Ant Design的Image组件,支持点击预览
4129
return (
4230
<Image
43-
src={fullImageSrc}
44-
alt={node.properties?.alt || '图片'}
31+
src={imageSrc}
32+
alt={alt || '图片'}
4533
style={{
4634
maxWidth: '100%',
4735
borderRadius: '4px',
@@ -58,6 +46,7 @@ const MarkdownImage = ({ node }: { node: any }) => {
5846
</div>
5947
)
6048
}}
49+
fallback={FALLBACK_IMAGE}
6150
/>
6251
);
6352
};
@@ -219,28 +208,72 @@ const ChallengeDescription: React.FC<ChallengeDescriptionProps> = ({ challenge,
219208

220209
// 如果成功提取了图片URL,使用Ant Design的Image组件显示
221210
if (extractedImageUrl) {
211+
if (isMobile) {
212+
return (
213+
<div>
214+
<Title
215+
level={4}
216+
style={{
217+
marginBottom: '12px',
218+
fontSize: '18px'
219+
}}
220+
>
221+
{t('challenge.detail.description')}
222+
</Title>
223+
224+
<Card
225+
bordered={false}
226+
style={{
227+
marginBottom: 16,
228+
wordWrap: 'break-word',
229+
overflowWrap: 'break-word'
230+
}}
231+
bodyStyle={{
232+
padding: '12px'
233+
}}
234+
>
235+
<div className="markdown-content">
236+
{htmlContent && (
237+
<div dangerouslySetInnerHTML={{ __html: htmlContent }} />
238+
)}
239+
<Image
240+
src={extractedImageUrl}
241+
alt="挑战图片"
242+
style={{
243+
maxWidth: '100%',
244+
borderRadius: '4px',
245+
margin: '8px 0',
246+
display: 'block'
247+
}}
248+
preview={{
249+
mask: '点击查看大图',
250+
maskClassName: 'image-preview-mask',
251+
toolbarRender: () => (
252+
<div className="image-preview-tip">
253+
点击图片外区域关闭 | 滚轮缩放 | 左键拖动
254+
</div>
255+
),
256+
}}
257+
fallback={FALLBACK_IMAGE}
258+
/>
259+
</div>
260+
</Card>
261+
</div>
262+
);
263+
}
264+
265+
// PC端布局
222266
return (
223267
<div>
224-
<Title
225-
level={isMobile ? 4 : 3}
226-
style={{
227-
marginBottom: isMobile ? '12px' : '24px',
228-
fontSize: isMobile ? '18px' : '24px'
229-
}}
230-
>
231-
{t('challenge.detail.description')}
232-
</Title>
268+
<Title level={3}>{t('challenge.detail.description')}</Title>
233269

234270
<Card
235271
bordered={false}
236272
style={{
237-
marginBottom: isMobile ? 16 : 24,
273+
marginBottom: 24,
238274
wordWrap: 'break-word',
239275
overflowWrap: 'break-word'
240276
}}
241-
bodyStyle={{
242-
padding: isMobile ? '12px' : '24px'
243-
}}
244277
>
245278
<div className="markdown-content">
246279
{htmlContent && (
@@ -252,18 +285,20 @@ const ChallengeDescription: React.FC<ChallengeDescriptionProps> = ({ challenge,
252285
style={{
253286
maxWidth: '100%',
254287
borderRadius: '4px',
255-
margin: isMobile ? '8px 0' : '16px 0',
288+
margin: '16px 0',
256289
display: 'block'
257290
}}
258291
preview={{
259-
mask: '点击查看大图',
260-
maskClassName: 'image-preview-mask',
292+
mask: <div className="image-preview-mask">点击查看大图</div>,
293+
maskClassName: "image-preview-mask",
294+
rootClassName: "custom-image-preview",
261295
toolbarRender: () => (
262296
<div className="image-preview-tip">
263297
点击图片外区域关闭 | 滚轮缩放 | 左键拖动
264298
</div>
265-
),
299+
)
266300
}}
301+
fallback={FALLBACK_IMAGE}
267302
/>
268303
</div>
269304
</Card>
@@ -272,46 +307,99 @@ const ChallengeDescription: React.FC<ChallengeDescriptionProps> = ({ challenge,
272307
}
273308
}
274309

275-
// 正常处理Markdown内容
310+
// 针对移动端的Markdown内容处理
311+
if (isMobile) {
312+
return (
313+
<div>
314+
<Title
315+
level={4}
316+
style={{
317+
marginBottom: '12px',
318+
fontSize: '18px'
319+
}}
320+
>
321+
{t('challenge.detail.description')}
322+
</Title>
323+
324+
<Card
325+
bordered={false}
326+
style={{
327+
marginBottom: 16,
328+
wordWrap: 'break-word',
329+
overflowWrap: 'break-word'
330+
}}
331+
bodyStyle={{
332+
padding: '12px'
333+
}}
334+
>
335+
{displayDescription ? (
336+
<div className="markdown-content" style={{ fontSize: '14px' }}>
337+
<ReactMarkdown
338+
rehypePlugins={[rehypeRaw]}
339+
components={{
340+
a: MarkdownLink,
341+
img: MarkdownImage,
342+
}}
343+
>
344+
{displayDescription}
345+
</ReactMarkdown>
346+
</div>
347+
) : (
348+
<Empty description={t('challenge.detail.noDescription')} />
349+
)}
350+
</Card>
351+
</div>
352+
);
353+
}
354+
355+
// PC端默认渲染方式 - 使用ReactMarkdown
276356
return (
277357
<div>
278-
<Title
279-
level={isMobile ? 4 : 3}
280-
style={{
281-
marginBottom: isMobile ? '12px' : '24px',
282-
fontSize: isMobile ? '18px' : '24px'
283-
}}
284-
>
285-
{t('challenge.detail.description')}
286-
</Title>
358+
<Title level={3}>{t('challenge.detail.description')}</Title>
287359

288-
<Card
289-
bordered={false}
290-
style={{
291-
marginBottom: isMobile ? 16 : 24,
292-
wordWrap: 'break-word',
293-
overflowWrap: 'break-word'
294-
}}
295-
bodyStyle={{
296-
padding: isMobile ? '12px' : '24px'
297-
}}
298-
>
299-
{displayDescription ? (
300-
<div className="markdown-content" style={{ fontSize: isMobile ? '14px' : '16px' }}>
301-
<ReactMarkdown
360+
{/* 实际挑战描述 */}
361+
{displayDescription ? (
362+
<Card
363+
bordered={false}
364+
style={{
365+
marginBottom: 24,
366+
wordWrap: 'break-word',
367+
overflowWrap: 'break-word'
368+
}}
369+
>
370+
<div className="markdown-content">
371+
<ReactMarkdown
302372
rehypePlugins={[rehypeRaw]}
303373
components={{
304-
a: MarkdownLink,
305374
img: MarkdownImage,
375+
a: MarkdownLink,
376+
pre: (props: any) => (
377+
<pre style={{
378+
overflowX: 'auto',
379+
whiteSpace: 'pre-wrap',
380+
wordWrap: 'break-word',
381+
maxWidth: '100%'
382+
}} {...props} />
383+
),
384+
code: (props: any) => (
385+
<code style={{
386+
overflowWrap: 'break-word',
387+
wordWrap: 'break-word',
388+
wordBreak: 'break-word'
389+
}} {...props} />
390+
)
306391
}}
307392
>
308393
{displayDescription}
309394
</ReactMarkdown>
310395
</div>
311-
) : (
312-
<Empty description={t('challenge.detail.noDescription')} />
313-
)}
314-
</Card>
396+
</Card>
397+
) : (
398+
<Empty
399+
description={t('challenge.detail.noDescription', '暂无详细描述')}
400+
style={{ marginTop: 24, marginBottom: 24 }}
401+
/>
402+
)}
315403
</div>
316404
);
317405
};

0 commit comments

Comments
 (0)