小程序适配
All checks were successful
Deploy to Server / deploy (push) Successful in 58s

This commit is contained in:
jeremygan2021
2026-02-26 15:10:52 +08:00
parent 66cfbdd75b
commit 9215ec3b42
11 changed files with 453 additions and 150 deletions

View File

@@ -0,0 +1,42 @@
import React, { useState } from 'react'
import { View, Text, ScrollView } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { AtIcon } from 'taro-ui'
import './index.scss'
interface Props {
code: string
language?: string
}
const CodeBlock: React.FC<Props> = ({ code, language }) => {
const [copied, setCopied] = useState(false)
const handleCopy = (e) => {
e.stopPropagation()
Taro.setClipboardData({
data: code,
success: () => {
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
})
}
return (
<View className='markdown-code-block'>
<View className='code-header'>
<Text className='language'>{language || 'text'}</Text>
<View className='copy-btn' onClick={handleCopy}>
<AtIcon value='copy' size='14' color='#ccc' />
<Text className='copy-text'>{copied ? '已复制' : '复制'}</Text>
</View>
</View>
<ScrollView scrollX scrollY className='code-content'>
<Text userSelect className='code-text'>{code}</Text>
</ScrollView>
</View>
)
}
export default CodeBlock

View File

@@ -0,0 +1,75 @@
.markdown-reader {
.markdown-text {
/* Inherit font styles and color from parent */
font-size: inherit;
line-height: inherit;
color: inherit;
/* Ensure rich text images are responsive */
image {
max-width: 100%;
}
}
}
.markdown-code-block {
margin: 16px 0;
border-radius: 8px;
overflow: hidden;
background-color: #1e1e1e;
border: 1px solid #333;
.code-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background-color: #252526;
border-bottom: 1px solid #333;
.language {
color: #9cdcfe;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
}
.copy-btn {
display: flex;
align-items: center;
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.1);
transition: background-color 0.2s;
&:active {
background-color: rgba(255, 255, 255, 0.2);
}
.copy-text {
color: #ccc;
font-size: 12px;
margin-left: 4px;
}
}
}
.code-content {
padding: 12px;
background-color: #1e1e1e;
max-height: 400px;
box-sizing: border-box;
.code-text {
color: #d4d4d4;
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
line-height: 1.5;
white-space: pre;
display: block;
width: max-content;
min-width: 100%;
}
}
}

View File

@@ -0,0 +1,90 @@
import React, { useMemo } from 'react'
import { View, RichText } from '@tarojs/components'
import { marked, Renderer } from 'marked'
import CodeBlock from './CodeBlock'
import './index.scss'
interface Props {
content: string
themeColor?: string
}
const MarkdownReader: React.FC<Props> = ({ content, themeColor = '#00b96b' }) => {
const elements = useMemo(() => {
if (!content) return []
const tokens = marked.lexer(content)
const result: React.ReactNode[] = []
let currentTokens: any[] = []
// Configure renderer
const renderer = new Renderer()
renderer.table = (header, body) => {
return `<div style="overflow-x: auto; width: 100%; -webkit-overflow-scrolling: touch;">
<table style="width: 100%; min-width: 600px; border-collapse: collapse; margin: 16px 0; font-size: 14px;">
<thead>${header}</thead>
<tbody>${body}</tbody>
</table>
</div>`
}
renderer.tablecell = (content, flags) => {
const type = flags.header ? 'th' : 'td'
const style = [
'border: 1px solid rgba(255,255,255,0.1)',
'padding: 10px',
flags.header ? 'background-color: rgba(255,255,255,0.05); font-weight: 700; color: #fff;' : 'color: #ddd;',
flags.align ? `text-align: ${flags.align}` : 'text-align: left'
].join(';')
return `<${type} style="${style}">${content}</${type}>`
}
renderer.image = (href, title, text) => {
return `<img src="${href}" style="max-width:100%;border-radius:8px;margin:10px 0;box-shadow: 0 4px 12px rgba(0,0,0,0.3);" title="${title || ''}" alt="${text || ''}" />`
}
renderer.link = (href, title, text) => {
return `<a href="${href}" style="color: ${themeColor}; text-decoration: none;">${text}</a>`
}
// Process tokens
tokens.forEach((token, index) => {
if (token.type === 'code') {
// Flush accumulated tokens
if (currentTokens.length > 0) {
// preserve links if any
(currentTokens as any).links = (tokens as any).links
const html = marked.parser(currentTokens as any, { renderer, breaks: true })
result.push(<RichText key={`rt-${index}`} nodes={html} className='markdown-text' />)
currentTokens = []
}
// Add code block
result.push(
<View key={`cb-${index}`} className='code-block-wrapper'>
<CodeBlock
code={token.text}
language={token.lang}
/>
</View>
)
} else {
currentTokens.push(token)
}
})
// Flush remaining tokens
if (currentTokens.length > 0) {
(currentTokens as any).links = (tokens as any).links
const html = marked.parser(currentTokens as any, { renderer, breaks: true })
result.push(<RichText key={`rt-end`} nodes={html} className='markdown-text' />)
}
return result
}, [content, themeColor])
return <View className='markdown-reader'>{elements}</View>
}
export default MarkdownReader