@@ -19,29 +19,17 @@ interface ChallengeDescriptionProps {
1919// 测试图片 - 1x1像素透明PNG
2020const 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