@wzyjs/components 0.2.55 → 0.2.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wzyjs/components",
3
- "version": "0.2.55",
3
+ "version": "0.2.57",
4
4
  "description": "description",
5
5
  "author": "wzy",
6
6
  "main": "src/index.ts",
@@ -22,8 +22,11 @@
22
22
  "react-json-view": "^1.21.3",
23
23
  "react-markdown": "^10.1.0",
24
24
  "react-syntax-highlighter": "^15.5.0",
25
+ "rehype-autolink-headings": "^7.1.0",
26
+ "rehype-slug": "^6.0.0",
25
27
  "rehype-starry-night": "^2.2.0",
26
- "remark-gfm": "^4.0.1"
28
+ "remark-gfm": "^4.0.1",
29
+ "remark-toc": "^9.0.0"
27
30
  },
28
31
  "peerDependencies": {
29
32
  "@wzyjs/antd": "^0.2.37",
@@ -40,7 +43,7 @@
40
43
  "@types/react-grid-layout": "^1.3.5",
41
44
  "@types/react-syntax-highlighter": "^15.5.5"
42
45
  },
43
- "gitHead": "0d197525f11d492c132b2ae376ab33b883237c1c",
46
+ "gitHead": "7603b720b28050ff484e841ae23e60a330db8956",
44
47
  "publishConfig": {
45
48
  "access": "public"
46
49
  }
@@ -0,0 +1,77 @@
1
+ import React from 'react'
2
+ import { AutoComplete, Button, Space } from 'app/src/components'
3
+ import { type Option } from 'app/src/types'
4
+
5
+ export * from './utils'
6
+
7
+ interface DynamicSelectProps {
8
+ value?: string[]
9
+ onChange?: (value: string[]) => void
10
+ options?: Option[]
11
+ }
12
+
13
+ export const DynamicSelect = (props: DynamicSelectProps) => {
14
+ const { value = [''], onChange, options = [] } = props
15
+
16
+ const removeOption = (index: number) => {
17
+ const newOptions = [...value]
18
+ newOptions.splice(index, 1)
19
+ onChange?.(newOptions)
20
+ }
21
+
22
+ const handleChange = (index: number, val: string) => {
23
+ const newValue = [...[...value].slice(0, index), val]
24
+ onChange?.(newValue)
25
+
26
+ if (newValue.every(item => item)) {
27
+ onChange?.([...newValue, ''])
28
+ }
29
+ }
30
+
31
+ const getOptions = (options: Option[], index: number): Option[] => {
32
+ if (index === 0) {
33
+ return options
34
+ }
35
+
36
+ const currentValue = value[value.length - 1 - index]
37
+ if (!currentValue) return []
38
+
39
+ return getOptions(
40
+ options.find(item => item.value === currentValue)?.children || [],
41
+ index - 1,
42
+ )
43
+ }
44
+
45
+ return (
46
+ <Space size={8} align='center'>
47
+ {value.map((val, index) => (
48
+ <Space key={index} size={4} align='center'>
49
+ <AutoComplete
50
+ style={{
51
+ minWidth: '120px',
52
+ maxWidth: '300px',
53
+ width: 'auto',
54
+ }}
55
+ value={val}
56
+ onChange={val => handleChange(index, val)}
57
+ options={getOptions(options, index)}
58
+ placeholder={`请选择第 ${index + 1} 级`}
59
+ />
60
+ {index !== 0 && (
61
+ <Button
62
+ type='text'
63
+ danger
64
+ size='small'
65
+ onClick={() => removeOption(index)}
66
+ >
67
+ 删除
68
+ </Button>
69
+ )}
70
+ {index < value.length - 1 && (
71
+ <span style={{ color: '#999' }}>/</span>
72
+ )}
73
+ </Space>
74
+ ))}
75
+ </Space>
76
+ )
77
+ }
@@ -0,0 +1,47 @@
1
+ import type { Option } from '@wzyjs/types'
2
+
3
+ export const transformOptions = (data: string[][]): Option[] => {
4
+ const result: Option[] = []
5
+
6
+ data.forEach((item = []) => {
7
+ let currentNode: Option | undefined
8
+ let parentNode: Option | undefined
9
+
10
+ for (let i = 0; i < item.length; i++) {
11
+ const currentValue = item[i]
12
+ if (!currentValue) continue
13
+
14
+ if (i === 0) {
15
+ currentNode = result.find(r => r.value === currentValue)
16
+
17
+ if (!currentNode) {
18
+ currentNode = {
19
+ label: currentValue,
20
+ value: currentValue,
21
+ }
22
+ result.push(currentNode)
23
+ }
24
+
25
+ parentNode = currentNode
26
+ } else {
27
+ currentNode = parentNode?.children?.find(c => c.value === currentValue)
28
+
29
+ if (!currentNode) {
30
+ currentNode = {
31
+ label: currentValue,
32
+ value: currentValue,
33
+ }
34
+ if (!parentNode) {
35
+ return
36
+ }
37
+ parentNode.children = parentNode.children || []
38
+ parentNode.children.push(currentNode)
39
+ }
40
+
41
+ parentNode = currentNode
42
+ }
43
+ }
44
+ })
45
+
46
+ return result
47
+ }
@@ -0,0 +1,115 @@
1
+ import React from 'react'
2
+ import {
3
+ Divider,
4
+ Flex,
5
+ Space,
6
+ Typography,
7
+ Steps,
8
+ Tabs,
9
+ Collapse,
10
+ Descriptions,
11
+ Image,
12
+ List,
13
+ Popover,
14
+ Tooltip,
15
+ Card,
16
+ QRCode,
17
+ Segmented,
18
+ Table,
19
+ Tag,
20
+ Timeline,
21
+ Tree,
22
+ Alert,
23
+ Progress,
24
+ } from 'antd'
25
+
26
+ const ComponentMap = {
27
+ // 布局结构类
28
+ Divider,
29
+ Flex,
30
+ Space,
31
+
32
+ // 信息展示类
33
+ Typography,
34
+ 'Typography.Title': Typography.Title,
35
+ 'Typography.Text': Typography.Text,
36
+ 'Typography.Paragraph': Typography.Paragraph,
37
+ 'Typography.Link': Typography.Link,
38
+ Steps,
39
+ Tabs,
40
+ 'Tabs.TabPane': Tabs.TabPane,
41
+ Collapse,
42
+ 'Collapse.Panel': Collapse.Panel,
43
+ Description: Descriptions,
44
+ Image,
45
+ List,
46
+ Popover,
47
+ Tooltip,
48
+ Card,
49
+ QRCode,
50
+ Segmented,
51
+ Table,
52
+ Tag,
53
+ Timeline,
54
+ Tree,
55
+ Alert,
56
+ Progress,
57
+ }
58
+
59
+ export interface Content {
60
+ component: keyof typeof ComponentMap // 对应 Ant Design 组件的枚举值
61
+ props?: Record<string, any> // 对应组件的 props 配置
62
+ children?: Content[] | string // 子节点,可递归嵌套,或直接是字符串
63
+ }
64
+
65
+ interface JsonRendererProps {
66
+ content: Content[]
67
+ }
68
+
69
+ export const JsonRenderer = (props: JsonRendererProps) => {
70
+ const { content } = props
71
+
72
+ // 递归渲染组件的函数
73
+ const renderContent = (item: Content | string) => {
74
+ // 如果是字符串,直接返回
75
+ if (typeof item === 'string') {
76
+ return item
77
+ }
78
+
79
+ const { component, props = {}, children } = item
80
+
81
+ // 获取对应的组件
82
+ const Component = ComponentMap[component] as React.ComponentType<any>
83
+
84
+ if (!Component) {
85
+ return <Alert type='error' message={`未找到组件: ${component}`} />
86
+ }
87
+
88
+ // 处理子节点
89
+ let childrenContent
90
+ if (children) {
91
+ if (Array.isArray(children)) {
92
+ childrenContent = children.map((child, index) => (
93
+ <React.Fragment key={index}>
94
+ {renderContent(child)}
95
+ </React.Fragment>
96
+ ))
97
+ } else {
98
+ childrenContent = renderContent(children)
99
+ }
100
+ }
101
+
102
+ // 渲染组件,传递 props 和 children
103
+ return <Component {...props}>{childrenContent}</Component>
104
+ }
105
+
106
+ return (
107
+ <>
108
+ {content.map((item, index) => (
109
+ <React.Fragment key={index}>
110
+ {renderContent(item)}
111
+ </React.Fragment>
112
+ ))}
113
+ </>
114
+ )
115
+ }
@@ -1,23 +1,150 @@
1
1
  'use client'
2
2
 
3
- import { MarkdownHooks } from 'react-markdown'
4
-
3
+ import { useMemo } from 'react'
4
+ import ReactMarkdown from 'react-markdown'
5
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
6
+ import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'
5
7
  import remarkGfm from 'remark-gfm'
6
- import rehypeStarryNight from 'rehype-starry-night'
8
+ import rehypeSlug from 'rehype-slug'
9
+ import rehypeAutolinkHeadings from 'rehype-autolink-headings'
7
10
 
8
11
  import 'github-markdown-css/github-markdown-light.css'
12
+ import { style } from './style'
9
13
 
10
14
  interface MarkdownProps {
11
15
  content: string
16
+ showToc?: boolean // 是否显示目录
17
+ }
18
+
19
+ interface TocItem {
20
+ id: string
21
+ text: string
22
+ level: number
12
23
  }
13
24
 
14
25
  export const Markdown = (props: MarkdownProps) => {
15
- const { content } = props
26
+ const { content = '', showToc = true } = props
27
+
28
+ // 提取标题生成目录
29
+ const tocItems = useMemo(() => {
30
+ if (!showToc) {
31
+ return []
32
+ }
33
+
34
+ const lines = content.split('\n')
35
+ const items: TocItem[] = []
36
+
37
+ lines.forEach((line) => {
38
+ const match = line.match(/^(#{2,4})\s+(.+)$/)
39
+ if (match) {
40
+ const level = match[1].length
41
+ const text = match[2].trim()
42
+ const id = text
43
+ .toLowerCase()
44
+ .replace(/[^\w\u4e00-\u9fa5\s-]/g, '') // 保留中文、英文、数字、空格和连字符
45
+ .replace(/\s+/g, '-') // 空格替换为连字符
46
+
47
+ items.push({ id, text, level })
48
+ }
49
+ })
50
+
51
+ return items
52
+ }, [content, showToc])
53
+
54
+ // 渲染目录
55
+ const renderToc = () => {
56
+ if (!showToc || tocItems.length === 0) {
57
+ return null
58
+ }
59
+
60
+ return (
61
+ <div
62
+ className='markdown-toc-container'
63
+ style={{
64
+ padding: '10px 20px 10px 0',
65
+ backgroundColor: '#f8f9fa',
66
+ borderLeft: '4px solid #1890ff',
67
+ borderRadius: '4px',
68
+ }}
69
+ >
70
+ <ul style={{ margin: 0, padding: 0 }}>
71
+ {tocItems.map((item, index) => (
72
+ <li
73
+ key={index}
74
+ style={{
75
+ marginBottom: '4px',
76
+ marginLeft: `${(item.level - 1) * 16}px`,
77
+ listStyle: 'none',
78
+ position: 'relative',
79
+ }}
80
+ >
81
+ <a
82
+ href={`#${item.id}`}
83
+ style={{
84
+ textDecoration: 'none',
85
+ color: '#1890ff',
86
+ fontSize: item.level <= 2 ? '14px' : '13px',
87
+ fontWeight: item.level === 1 ? 600 : 400,
88
+ }}
89
+ onClick={(e) => {
90
+ e.preventDefault()
91
+ const element = document.getElementById(item.id)
92
+ if (element) {
93
+ element.scrollIntoView({ behavior: 'smooth' })
94
+ }
95
+ }}
96
+ >
97
+ {item.text}
98
+ </a>
99
+ </li>
100
+ ))}
101
+ </ul>
102
+ </div>
103
+ )
104
+ }
105
+
16
106
  return (
17
- <div className='markdown-body' style={{ backgroundColor: 'transparent' }}>
18
- <MarkdownHooks rehypePlugins={[rehypeStarryNight, remarkGfm]}>
19
- {content}
20
- </MarkdownHooks>
107
+ <div style={{ height: '100%' }}>
108
+ <style>{style}</style>
109
+ <div className='markdown-body markdown-toc' style={{ height: '100%', display: 'flex' }}>
110
+ <div style={{ marginRight: 14 }}>
111
+ {renderToc()}
112
+ </div>
113
+ <div style={{ overflow: 'auto', flex: 1 }}>
114
+ <ReactMarkdown
115
+ remarkPlugins={[remarkGfm]}
116
+ rehypePlugins={[
117
+ rehypeSlug,
118
+ [rehypeAutolinkHeadings, {
119
+ behavior: 'wrap',
120
+ properties: {
121
+ className: ['anchor'],
122
+ },
123
+ }],
124
+ ]}
125
+ components={{
126
+ code(props) {
127
+ const { children, className } = props
128
+ const match = /language-(\w+)/.exec(className || '')
129
+ return match ? (
130
+ <SyntaxHighlighter
131
+ PreTag='div'
132
+ children={String(children).replace(/\n$/, '')}
133
+ language={match[1]}
134
+ style={oneLight}
135
+ />
136
+ ) : (
137
+ <code className={className}>
138
+ {children}
139
+ </code>
140
+ )
141
+ },
142
+ }}
143
+ >
144
+ {content}
145
+ </ReactMarkdown>
146
+ </div>
147
+ </div>
21
148
  </div>
22
149
  )
23
150
  }
@@ -0,0 +1,106 @@
1
+ export const style = `
2
+ code {
3
+ color: #c7254e;
4
+ background-color: #f9f2f4;
5
+ border-radius: 4px;
6
+ }
7
+
8
+ /* 目录容器样式 */
9
+ .markdown-toc .markdown-body {
10
+ position: relative;
11
+ }
12
+
13
+ /* 锚点链接样式 */
14
+ .markdown-toc .anchor {
15
+ text-decoration: none !important;
16
+ color: inherit !important;
17
+ display: contents !important;
18
+ }
19
+
20
+ .markdown-toc .anchor:hover {
21
+ text-decoration: underline !important;
22
+ }
23
+
24
+ /* 标题悬浮效果 */
25
+ .markdown-toc h1:hover .anchor::after,
26
+ .markdown-toc h2:hover .anchor::after,
27
+ .markdown-toc h3:hover .anchor::after,
28
+ .markdown-toc h4:hover .anchor::after,
29
+ .markdown-toc h5:hover .anchor::after,
30
+ .markdown-toc h6:hover .anchor::after {
31
+ content: " 🔗";
32
+ opacity: 0.6;
33
+ font-size: 0.8em;
34
+ }
35
+
36
+ /* 目录标题样式 */
37
+ // .markdown-toc h2:first-of-type {
38
+ // display: block !important;
39
+ // color: #24292f !important;
40
+ // font-size: 18px !important;
41
+ // font-weight: 600 !important;
42
+ // line-height: 1.4 !important;
43
+ // border-bottom: 1px solid #d1d9e0 !important;
44
+ // padding-bottom: 8px !important;
45
+ // margin: 0 0 16px 0 !important;
46
+ // }
47
+
48
+ /* 目录专用样式 - 只对第一个h2下的列表生效 */
49
+ .markdown-toc h2:first-of-type + ul {
50
+ background: #f8f9fa !important;
51
+ border: 1px solid #e1e4e8 !important;
52
+ border-radius: 6px !important;
53
+ padding: 16px !important;
54
+ margin: 16px 0 24px 0 !important;
55
+ list-style: none !important;
56
+ }
57
+
58
+ .markdown-toc h2:first-of-type + ul li {
59
+ margin: 8px 0 !important;
60
+ padding-left: 0 !important;
61
+ list-style: none !important;
62
+ }
63
+
64
+ .markdown-toc h2:first-of-type + ul li::before {
65
+ content: "📖 " !important;
66
+ margin-right: 8px !important;
67
+ }
68
+
69
+ .markdown-toc h2:first-of-type + ul li ul {
70
+ margin: 4px 0 0 0 !important;
71
+ padding-left: 20px !important;
72
+ list-style: none !important;
73
+ }
74
+
75
+ .markdown-toc h2:first-of-type + ul li ul li {
76
+ margin: 4px 0 !important;
77
+ padding-left: 0 !important;
78
+ }
79
+
80
+ .markdown-toc h2:first-of-type + ul li ul li::before {
81
+ content: "▸ " !important;
82
+ margin-right: 6px !important;
83
+ color: #666 !important;
84
+ }
85
+
86
+ .markdown-toc h2:first-of-type + ul li ul li ul {
87
+ padding-left: 16px !important;
88
+ }
89
+
90
+ .markdown-toc h2:first-of-type + ul li ul li ul li::before {
91
+ content: "◦ " !important;
92
+ color: #999 !important;
93
+ }
94
+
95
+ /* 目录链接样式 */
96
+ .markdown-toc h2:first-of-type + ul a {
97
+ color: #0969da !important;
98
+ text-decoration: none !important;
99
+ font-weight: 500 !important;
100
+ }
101
+
102
+ .markdown-toc h2:first-of-type + ul a:hover {
103
+ text-decoration: underline !important;
104
+ color: #0550ae !important;
105
+ }
106
+ `
package/src/index.ts CHANGED
@@ -14,6 +14,8 @@ export * from './GroupLayout'
14
14
  export * from './TextInput'
15
15
  export * from './SectorButton'
16
16
  export * from './Markdown'
17
+ export * from './DynamicSelect'
18
+ export * from './JsonRenderer'
17
19
 
18
20
  // export * from './JsonView'
19
21
  // export * from './Crud'