Skip to content

Commit a9680bb

Browse files
committed
feat: update compontens
1 parent 73f4d45 commit a9680bb

File tree

43 files changed

+1590
-1169
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1590
-1169
lines changed

.stylelintrc.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
{
1616
"ignorePseudoClasses": ["global"]
1717
}
18+
],
19+
"at-rule-no-unknown": [
20+
true,
21+
{
22+
"ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen", "layer", "theme", "utility"]
23+
}
1824
]
1925
}
2026
}

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"tryer",
3030
"urlparams"
3131
],
32+
"css.lint.unknownAtRules": "ignore",
3233
"files.autoSave": "off",
3334
"npm-scripts.showStartNotification": false,
3435
"editor.suggest.snippetsPreventQuickSuggestions": false,

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<a href="https://deepwiki.com/wkylin/pro-react-admin"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki" /></a>
1313
</div>
1414

15-
# 🚀 Pro React Admin
15+
## 🚀 Pro React Admin
1616

1717
**Pro React Admin** 是一款基于 **React v19** 的高性能、企业级中后台前端解决方案。深度整合 **RBAC 动态权限****KeepAlive 缓存****多标签页****AI 智能助手**。提供开箱即用的国际化、暗黑模式、Mock 数据与 E2E 测试体系,助力开发者快速构建稳健、安全的 SaaS 平台与数据可视化系统。
1818

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@
326326
"markmap-common": "^0.18.9",
327327
"markmap-lib": "^0.18.12",
328328
"markmap-view": "^0.18.12",
329-
"mermaid": "^11.12.1",
329+
"mermaid": "^11.12.2",
330330
"motion": "^12.23.24",
331331
"nanoid": "^5.1.6",
332332
"number-flow": "^0.5.8",

src/components/index.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ export { default as KeepAlive } from './KeepAlive'
55
export { default as ResponsiveTable } from './ResponsiveTable'
66
export { default as GlobalSearch } from './GlobalSearch'
77

8+
// Auth 组件
9+
export * from './auth'
10+
11+
// Stateful 组件
12+
export { default as CheckableTags } from './stateful/CheckableTags'
13+
export { default as MarkmapHooks } from './stateful/markmap'
14+
export { default as MermaidHooks } from './stateful/mermaidHooks'
15+
export { default as TreeList } from './stateful/TreeList'
16+
817
// Stateless 组件
918
export { default as AdvancedCodeBlock } from './stateless/AdvancedCodeBlock'
1019
export { default as AlignCenter } from './stateless/AlignCenter'
@@ -26,7 +35,7 @@ export { default as AvatarCard } from './stateless/AvatarCard'
2635
export { default as BackgroundBeams } from './stateless/BackgroundBeams'
2736
export { default as BackgroundBoxes } from './stateless/BackgroundBoxes'
2837
export { default as BlurFade } from './stateless/BlurFade'
29-
// export { default as BlurText } from './stateless/BlurText';
38+
export { default as BlurText } from './stateless/BlurText'
3039
export { default as BorderBeam } from './stateless/BorderBeam'
3140
export { default as BreatheText } from './stateless/BreatheText'
3241
export { default as ClockFace } from './stateless/ClockFace'
@@ -35,6 +44,7 @@ export { default as ColorfulText } from './stateless/ColorfulText'
3544
export { default as CompareAll } from './stateless/CompareAll'
3645
export { default as ContentPlaceholder } from './stateless/ContentPlaceholder'
3746
export { default as CopyToClipboard } from './stateless/CopyToClipboard'
47+
export { default as CubeSpinner } from './stateless/CubeSpinner'
3848
export { default as CustomSwitch } from './stateless/CustomSwitch'
3949
export { default as DescBox } from './stateless/DescBox'
4050
export { default as DonutCharts } from './stateless/DonutCharts'
@@ -54,6 +64,7 @@ export { default as FloatAny } from './stateless/FloatAny'
5464
export { default as FloatingIcon } from './stateless/FloatingIcon'
5565
export { default as Footer } from './stateless/Footer'
5666
export { default as GradientAnimation } from './stateless/GradientAnimation'
67+
export { default as GradientStats } from './stateless/GradientStats'
5768
export { default as GradientTracking } from './stateless/GradientTracking'
5869
export { default as GradualSpacing } from './stateless/GradualSpacing'
5970
export { default as HorizontalScroll } from './stateless/HorizontalScroll'
@@ -69,6 +80,7 @@ export { default as LineBordered } from './stateless/LineBordered'
6980
export { default as Loading } from './stateless/Loading'
7081
export { default as LogoSlider } from './stateless/LogoSlider'
7182
export { default as MagicTrail } from './stateless/MagicTrail'
83+
export { default as MarketingHero } from './stateless/MarketingHero'
7284
export { default as MemoizedStars } from './stateless/MemoizedStars'
7385
export { default as MeshGradientBackground } from './stateless/MeshGradientBackground'
7486
export { default as Meteors } from './stateless/Meteors'
@@ -98,7 +110,7 @@ export { default as ScriptView } from './stateless/ScriptView'
98110
export { default as ScrollAnimation } from './stateless/ScrollAnimation'
99111
export { default as ScrollToTop } from './stateless/ScrollToTop'
100112
export { default as ScrollVelocity } from './stateless/ScrollVelocity'
101-
// export { default as SearchForm } from './stateless/SearchForm';
113+
export { default as SearchForm } from './stateless/SearchForm'
102114
export { default as SettingDrawer } from './stateless/SettingDrawer'
103115
export { default as ShiCode } from './stateless/ShiCode'
104116
export { default as ShiftingCard } from './stateless/ShiftingCard'
@@ -123,6 +135,7 @@ export { default as TagCard } from './stateless/TagCard'
123135
export { default as TagCloud } from './stateless/TagCloud'
124136
export { default as TestimonialCarousel } from './stateless/TestimonialCarousel'
125137
export { default as TextClip } from './stateless/TextClip'
138+
export { default as TextComponent } from './stateless/TextComponent'
126139
export { default as TextLoader } from './stateless/TextLoader'
127140
export { default as TextReveal } from './stateless/TextReveal'
128141
export { default as ThreeDCard } from './stateless/ThreeDCard'
@@ -133,8 +146,5 @@ export { default as TypeWriter } from './stateless/TypeWriter'
133146
export { default as UserIP } from './stateless/UserIP'
134147
export { default as Video } from './stateless/Video'
135148
export { default as Watermark } from './stateless/Watermark'
149+
export { default as WaveBackground } from './stateless/WaveBackground'
136150
export { default as WordRotate } from './stateless/WordRotate'
137-
138-
// Stateful 组件
139-
export { default as CheckableTags } from './stateful/CheckableTags'
140-
export { default as TreeList } from './stateful/TreeList'
Lines changed: 98 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,111 @@
11
import React, { useRef, useEffect } from 'react'
2-
import { Transformer } from 'markmap-lib'
3-
import { Markmap } from 'markmap-view'
42

5-
const transformer = new Transformer()
3+
/*
4+
Optimized Markmap React wrapper:
5+
- dynamic imports for `markmap-lib` and `markmap-view` to avoid SSR/bundle issues
6+
- reuse transformer instance per component via ref
7+
- debounce heavy operations with requestAnimationFrame
8+
- robust error handling and cleanup
9+
*/
610

7-
const MarkmapHooks = ({ markmap }) => {
8-
const refSvg = useRef()
9-
const refMm = useRef()
11+
const MarkmapHooks = ({ markmap, debounceDelay = 200 }) => {
12+
const refSvg = useRef(null)
13+
const mmRef = useRef(null)
14+
const transformerRef = useRef(null)
15+
const rafRef = useRef(null)
16+
const debounceRef = useRef(null)
17+
const [isReady, setIsReady] = React.useState(false)
1018

19+
// Initialize markmap and transformer on client only
1120
useEffect(() => {
12-
const mm = Markmap.create(refSvg.current)
13-
refMm.current = mm
21+
let cancelled = false
22+
23+
const init = async () => {
24+
try {
25+
const [{ Transformer }, { Markmap }] = await Promise.all([import('markmap-lib'), import('markmap-view')])
26+
27+
if (cancelled) return
28+
29+
// create transformer once per component
30+
transformerRef.current = transformerRef.current || new Transformer()
31+
32+
// create markmap instance if svg exists
33+
if (refSvg.current && !mmRef.current) {
34+
try {
35+
mmRef.current = Markmap.create(refSvg.current)
36+
setIsReady(true) // Trigger re-run of the effect below
37+
} catch (err) {
38+
console.error('Markmap.create error:', err)
39+
}
40+
}
41+
} catch (err) {
42+
console.error('Failed to load markmap libraries:', err)
43+
}
44+
}
45+
46+
init()
47+
1448
return () => {
15-
mm.destroy()
49+
cancelled = true
50+
if (rafRef.current) {
51+
cancelAnimationFrame(rafRef.current)
52+
rafRef.current = null
53+
}
54+
if (debounceRef.current) {
55+
clearTimeout(debounceRef.current)
56+
debounceRef.current = null
57+
}
58+
if (mmRef.current) {
59+
try {
60+
mmRef.current.destroy()
61+
} catch (err) {
62+
console.warn('Error destroying markmap instance:', err)
63+
}
64+
mmRef.current = null
65+
}
1666
}
1767
}, [])
1868

69+
// Update markmap when `markmap` prop changes or when libraries are ready.
1970
useEffect(() => {
20-
const mm = refMm.current
21-
if (!mm) return
22-
const { root } = transformer.transform(markmap)
23-
mm.setData(root)
24-
mm.fit()
25-
}, [markmap])
26-
27-
return (
28-
<>
29-
<svg style={{ width: '100%', height: '400px' }} ref={refSvg} />
30-
</>
31-
)
71+
if (!isReady || !transformerRef.current || !mmRef.current) return
72+
73+
if (debounceRef.current) {
74+
clearTimeout(debounceRef.current)
75+
}
76+
77+
debounceRef.current = setTimeout(() => {
78+
if (rafRef.current) {
79+
cancelAnimationFrame(rafRef.current)
80+
}
81+
82+
rafRef.current = requestAnimationFrame(() => {
83+
try {
84+
const { root } = transformerRef.current.transform(markmap || '')
85+
mmRef.current.setData(root)
86+
// fit may cause layout thrashing; keep it but guard with try/catch
87+
try {
88+
mmRef.current.fit()
89+
} catch (err) {
90+
console.warn('markmap fit failed:', err)
91+
}
92+
} catch (err) {
93+
console.error('Markmap render error:', err)
94+
}
95+
})
96+
}, debounceDelay)
97+
98+
return () => {
99+
if (debounceRef.current) {
100+
clearTimeout(debounceRef.current)
101+
}
102+
if (rafRef.current) {
103+
cancelAnimationFrame(rafRef.current)
104+
}
105+
}
106+
}, [markmap, debounceDelay, isReady])
107+
108+
return <svg aria-hidden="true" style={{ width: '100%', height: 400 }} ref={refSvg} />
32109
}
33110

34111
export default MarkmapHooks

src/components/stateful/mermaidHooks/index.jsx

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,77 @@
1-
import React, { useEffect } from 'react'
2-
import mermaid from 'mermaid'
1+
import React, { useEffect, useState, useRef, useId } from 'react'
32

4-
mermaid.initialize({
5-
startOnLoad: true,
6-
theme: 'dark',
7-
securityLevel: 'loose',
8-
fontFamily: 'monospace',
9-
})
3+
const MermaidHooks = ({ chart, config }) => {
4+
const [svg, setSvg] = useState('')
5+
const [error, setError] = useState(null)
6+
const [loading, setLoading] = useState(true)
7+
// Generate a unique ID for mermaid to use for rendering
8+
// useId returns string with colons which might be problematic for selectors, strip them
9+
const id = useId().replace(/:/g, '')
10+
const mermaidId = `mermaid-${id}`
1011

11-
const MermaidHooks = ({ chart }) => {
1212
useEffect(() => {
13-
mermaid.contentLoaded()
14-
}, [])
13+
let isMounted = true
14+
15+
const renderChart = async () => {
16+
if (!chart) return
17+
18+
setLoading(true)
19+
setError(null)
20+
21+
try {
22+
// Dynamic import for performance
23+
const mermaid = (await import('mermaid')).default
24+
25+
// Initialize with default config or props
26+
// Note: initialize is global, but safe to call repeatedly with same config
27+
mermaid.initialize({
28+
startOnLoad: false,
29+
theme: 'default',
30+
securityLevel: 'loose',
31+
fontFamily: 'monospace',
32+
...config,
33+
})
34+
35+
// Attempt to render
36+
// mermaid.render returns an object { svg } in newer versions
37+
const { svg: renderedSvg } = await mermaid.render(mermaidId, chart)
38+
39+
if (isMounted) {
40+
setSvg(renderedSvg)
41+
setLoading(false)
42+
}
43+
} catch (err) {
44+
console.error('Mermaid render error:', err)
45+
if (isMounted) {
46+
setError(err.message || 'Failed to render chart')
47+
setLoading(false)
48+
}
49+
}
50+
}
51+
52+
// Debounce rendering slightly to avoid thrashing on rapid input
53+
const timeoutId = setTimeout(renderChart, 200)
54+
55+
return () => {
56+
isMounted = false
57+
clearTimeout(timeoutId)
58+
}
59+
}, [chart, config, mermaidId])
60+
1561
return (
16-
<div className="mermaid" style={{ minHeight: 0, background: '#fff' }}>
17-
{chart}
62+
<div className="mermaid-wrapper" style={{ minHeight: 100, position: 'relative' }}>
63+
{loading && !svg && (
64+
<div className="absolute inset-0 flex items-center justify-center text-gray-400">Loading...</div>
65+
)}
66+
67+
{error ? (
68+
<div className="rounded border border-red-200 bg-red-50 p-4 text-red-500">
69+
<p className="font-bold">Syntax Error:</p>
70+
<pre className="mt-2 overflow-auto text-xs">{error}</pre>
71+
</div>
72+
) : (
73+
<div className="mermaid-output" dangerouslySetInnerHTML={{ __html: svg }} />
74+
)}
1875
</div>
1976
)
2077
}

0 commit comments

Comments
 (0)