@@ -19,29 +19,17 @@ interface ChallengeDescriptionProps {
19
19
// 测试图片 - 1x1像素透明PNG
20
20
const FALLBACK_IMAGE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' ;
21
21
22
- // 图片组件
23
- const MarkdownImage = ( { node } : { node : any } ) => {
22
+ // 图片组件 - 修复props类型问题
23
+ const MarkdownImage = ( props : any ) => {
24
+ const { src, alt } = props ;
24
25
// 使用传入的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 ;
39
27
40
28
// 使用Ant Design的Image组件,支持点击预览
41
29
return (
42
30
< Image
43
- src = { fullImageSrc }
44
- alt = { node . properties ?. alt || '图片' }
31
+ src = { imageSrc }
32
+ alt = { alt || '图片' }
45
33
style = { {
46
34
maxWidth : '100%' ,
47
35
borderRadius : '4px' ,
@@ -58,6 +46,7 @@ const MarkdownImage = ({ node }: { node: any }) => {
58
46
</ div >
59
47
)
60
48
} }
49
+ fallback = { FALLBACK_IMAGE }
61
50
/>
62
51
) ;
63
52
} ;
@@ -219,28 +208,72 @@ const ChallengeDescription: React.FC<ChallengeDescriptionProps> = ({ challenge,
219
208
220
209
// 如果成功提取了图片URL,使用Ant Design的Image组件显示
221
210
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端布局
222
266
return (
223
267
< 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 >
233
269
234
270
< Card
235
271
bordered = { false }
236
272
style = { {
237
- marginBottom : isMobile ? 16 : 24 ,
273
+ marginBottom : 24 ,
238
274
wordWrap : 'break-word' ,
239
275
overflowWrap : 'break-word'
240
276
} }
241
- bodyStyle = { {
242
- padding : isMobile ? '12px' : '24px'
243
- } }
244
277
>
245
278
< div className = "markdown-content" >
246
279
{ htmlContent && (
@@ -252,18 +285,20 @@ const ChallengeDescription: React.FC<ChallengeDescriptionProps> = ({ challenge,
252
285
style = { {
253
286
maxWidth : '100%' ,
254
287
borderRadius : '4px' ,
255
- margin : isMobile ? '8px 0' : '16px 0' ,
288
+ margin : '16px 0' ,
256
289
display : 'block'
257
290
} }
258
291
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" ,
261
295
toolbarRender : ( ) => (
262
296
< div className = "image-preview-tip" >
263
297
点击图片外区域关闭 | 滚轮缩放 | 左键拖动
264
298
</ div >
265
- ) ,
299
+ )
266
300
} }
301
+ fallback = { FALLBACK_IMAGE }
267
302
/>
268
303
</ div >
269
304
</ Card >
@@ -272,46 +307,99 @@ const ChallengeDescription: React.FC<ChallengeDescriptionProps> = ({ challenge,
272
307
}
273
308
}
274
309
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
276
356
return (
277
357
< 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 >
287
359
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
302
372
rehypePlugins = { [ rehypeRaw ] }
303
373
components = { {
304
- a : MarkdownLink ,
305
374
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
+ )
306
391
} }
307
392
>
308
393
{ displayDescription }
309
394
</ ReactMarkdown >
310
395
</ 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
+ ) }
315
403
</ div >
316
404
) ;
317
405
} ;
0 commit comments