@will1123/lx-ui-utils 1.0.0 → 1.0.2
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/README.md +747 -120
- package/dist/dist/index.esm.js +69039 -469
- package/dist/dist/index.esm.min.js +62900 -328
- package/dist/dist/index.esm.min.js.map +1 -1
- package/dist/dist/index.umd.js +69038 -468
- package/dist/dist/index.umd.min.js +346 -13
- package/dist/dist/index.umd.min.js.map +1 -1
- package/dist/es/helper.js +59 -0
- package/dist/es/helper.js.map +1 -0
- package/dist/es/index.js +13 -11
- package/dist/es/index.js.map +1 -1
- package/dist/es/markdown.js +248 -267
- package/dist/es/markdown.js.map +1 -1
- package/dist/es/node_modules/katex/dist/katex.js +14455 -0
- package/dist/es/node_modules/katex/dist/katex.js.map +1 -0
- package/dist/es/sse.js +34 -142
- package/dist/es/sse.js.map +1 -1
- package/dist/es/thinking.js +19 -138
- package/dist/es/thinking.js.map +1 -1
- package/dist/lib/helper.js +59 -0
- package/dist/lib/helper.js.map +1 -0
- package/dist/lib/index.js +9 -7
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/markdown.js +249 -268
- package/dist/lib/markdown.js.map +1 -1
- package/dist/lib/node_modules/katex/dist/katex.js +14454 -0
- package/dist/lib/node_modules/katex/dist/katex.js.map +1 -0
- package/dist/lib/sse.js +34 -142
- package/dist/lib/sse.js.map +1 -1
- package/dist/lib/thinking.js +19 -138
- package/dist/lib/thinking.js.map +1 -1
- package/dist/types/helper.d.ts +46 -0
- package/dist/types/helper.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/markdown.d.ts +69 -100
- package/dist/types/markdown.d.ts.map +1 -1
- package/dist/types/sse.d.ts +48 -48
- package/dist/types/sse.d.ts.map +1 -1
- package/dist/types/thinking.d.ts +25 -48
- package/dist/types/thinking.d.ts.map +1 -1
- package/package.json +21 -4
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @lx-ui
|
|
1
|
+
# @will1123/lx-ui-utils
|
|
2
2
|
|
|
3
3
|
LX UI 通用工具函数库,提供常用的工具函数和 AI 相关功能。
|
|
4
4
|
|
|
@@ -6,17 +6,17 @@ LX UI 通用工具函数库,提供常用的工具函数和 AI 相关功能。
|
|
|
6
6
|
|
|
7
7
|
- 🚀 **TypeScript 支持** - 完整的类型定义
|
|
8
8
|
- 📦 **Tree-Shaking 支持** - 按需引入,减少打包体积
|
|
9
|
-
-
|
|
10
|
-
- 🔧 **AI 相关工具** - SSE
|
|
9
|
+
- 🔐 **内置认证** - 支持 Token 认证
|
|
10
|
+
- 🔧 **AI 相关工具** - SSE、深度思考提取、Markdown 渲染
|
|
11
11
|
|
|
12
12
|
## 安装
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
npm install @lx-ui
|
|
15
|
+
npm install @will1123/lx-ui-utils
|
|
16
16
|
# 或
|
|
17
|
-
yarn add @lx-ui
|
|
17
|
+
yarn add @will1123/lx-ui-utils
|
|
18
18
|
# 或
|
|
19
|
-
pnpm add @lx-ui
|
|
19
|
+
pnpm add @will1123/lx-ui-utils
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
## 使用方法
|
|
@@ -24,201 +24,828 @@ pnpm add @lx-ui/utils
|
|
|
24
24
|
### 全量导入
|
|
25
25
|
|
|
26
26
|
```typescript
|
|
27
|
-
import { sse, extractThinking,
|
|
27
|
+
import { sse, extractThinking, createMarkdownRender, bindMarkdownCodeBoxEvents } from '@will1123/lx-ui-utils'
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
### 按需导入(推荐)
|
|
31
31
|
|
|
32
32
|
```typescript
|
|
33
|
-
import { sse } from '@lx-ui
|
|
34
|
-
import { extractThinking } from '@lx-ui
|
|
35
|
-
import {
|
|
33
|
+
import { sse } from '@will1123/lx-ui-utils'
|
|
34
|
+
import { extractThinking } from '@will1123/lx-ui-utils'
|
|
35
|
+
import {
|
|
36
|
+
createMarkdownRender,
|
|
37
|
+
getMarkdownCodeTheme,
|
|
38
|
+
setMarkdownCodeTheme,
|
|
39
|
+
toggleMarkdownCodeTheme,
|
|
40
|
+
updateMarkdownCodeBlocksTheme,
|
|
41
|
+
bindMarkdownCodeBoxEvents
|
|
42
|
+
} from '@will1123/lx-ui-utils'
|
|
43
|
+
import { copyToClipboard, isDom } from '@will1123/lx-ui-utils'
|
|
36
44
|
```
|
|
37
45
|
|
|
38
46
|
## API 文档
|
|
39
47
|
|
|
40
48
|
### SSE (Server-Sent Events)
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
基于 `@microsoft/fetch-event-source` 封装的 SSE 请求,支持流式响应、自动重连和页面可见性控制。
|
|
43
51
|
|
|
44
52
|
#### 类型定义
|
|
45
53
|
|
|
46
54
|
```typescript
|
|
47
55
|
interface SSEConfig {
|
|
48
|
-
url: string
|
|
49
|
-
method?: 'GET' | 'POST'
|
|
50
|
-
headers?: Record<string, string>
|
|
51
|
-
body?:
|
|
52
|
-
|
|
56
|
+
url: string // 请求 URL
|
|
57
|
+
method?: 'GET' | 'POST' // 请求方法,默认 POST
|
|
58
|
+
headers?: Record<string, string> // 请求头
|
|
59
|
+
body?: object // 请求体(对象类型)
|
|
60
|
+
signal?: AbortSignal // 用于取消请求
|
|
61
|
+
token?: string // 认证 Token
|
|
62
|
+
openWhenHidden?: boolean // 页面隐藏时是否保持连接,默认 true
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
interface SSEHandlers {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
onClose?: () => void
|
|
59
|
-
onError?: (error: Error) => void
|
|
66
|
+
onOpen?: (response: Response) => void | Promise<void> // 连接打开时触发
|
|
67
|
+
onMessage?: (message: EventSourceMessage) => void // 接收到消息时触发
|
|
68
|
+
onClose?: () => void // 连接关闭时触发
|
|
69
|
+
onError?: (error: Error) => void // 发生错误时触发
|
|
60
70
|
}
|
|
61
71
|
```
|
|
62
72
|
|
|
63
73
|
#### 使用示例
|
|
64
74
|
|
|
65
75
|
```typescript
|
|
66
|
-
import { sse } from '@lx-ui
|
|
76
|
+
import { sse } from '@will1123/lx-ui-utils'
|
|
77
|
+
|
|
78
|
+
// 基础用法(默认 POST)
|
|
79
|
+
await sse({
|
|
80
|
+
url: 'https://api.example.com/stream',
|
|
81
|
+
body: { prompt: '你好' }
|
|
82
|
+
}, {
|
|
83
|
+
onMessage(msg) {
|
|
84
|
+
console.log('收到消息:', msg)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
67
87
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
88
|
+
// 使用 Token 认证
|
|
89
|
+
await sse({
|
|
90
|
+
url: 'https://api.example.com/stream',
|
|
91
|
+
token: 'Bearer your-token-here',
|
|
92
|
+
body: { prompt: '你好' }
|
|
93
|
+
}, {
|
|
94
|
+
onOpen(response) {
|
|
95
|
+
console.log('连接已打开,状态:', response.status)
|
|
72
96
|
},
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
onOpen: () => {
|
|
78
|
-
console.log('连接已打开')
|
|
79
|
-
},
|
|
80
|
-
onClose: () => {
|
|
81
|
-
console.log('连接已关闭')
|
|
82
|
-
},
|
|
83
|
-
onError: (error) => {
|
|
84
|
-
console.error('连接错误:', error)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
// 关闭连接
|
|
90
|
-
connection.close()
|
|
91
|
-
|
|
92
|
-
// POST 请求(使用 fetch)
|
|
93
|
-
const connection2 = sse(
|
|
94
|
-
{
|
|
95
|
-
url: 'https://api.example.com/stream',
|
|
96
|
-
method: 'POST',
|
|
97
|
-
headers: {
|
|
98
|
-
'Authorization': 'Bearer your-token'
|
|
99
|
-
},
|
|
100
|
-
body: {
|
|
101
|
-
prompt: '你好'
|
|
102
|
-
},
|
|
103
|
-
timeout: 30000
|
|
97
|
+
|
|
98
|
+
onMessage(msg) {
|
|
99
|
+
// msg 是原始对象,包含 data、event、id 等字段
|
|
100
|
+
console.log('收到消息:', msg.data)
|
|
104
101
|
},
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
|
|
103
|
+
onClose() {
|
|
104
|
+
console.log('连接已关闭')
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
onError(err) {
|
|
108
|
+
console.error('连接错误:', err)
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// GET 请求
|
|
113
|
+
await sse({
|
|
114
|
+
url: 'https://api.example.com/stream',
|
|
115
|
+
method: 'GET'
|
|
116
|
+
}, {
|
|
117
|
+
onMessage(msg) {
|
|
118
|
+
console.log(msg.data)
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// 取消请求
|
|
123
|
+
const ctrl = new AbortController()
|
|
124
|
+
await sse({
|
|
125
|
+
url: 'https://api.example.com/stream',
|
|
126
|
+
signal: ctrl.signal
|
|
127
|
+
}, {
|
|
128
|
+
onMessage(msg) {
|
|
129
|
+
console.log(msg.data)
|
|
109
130
|
}
|
|
110
|
-
)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// 取消请求
|
|
134
|
+
ctrl.abort()
|
|
111
135
|
```
|
|
112
136
|
|
|
113
|
-
###
|
|
137
|
+
### 深度思考提取
|
|
114
138
|
|
|
115
|
-
从 AI
|
|
139
|
+
从 AI 响应中提取深度思考内容。
|
|
116
140
|
|
|
117
141
|
#### 类型定义
|
|
118
142
|
|
|
119
143
|
```typescript
|
|
120
144
|
interface ThinkingExtractConfig {
|
|
121
|
-
tagName?: string
|
|
122
|
-
|
|
123
|
-
removeRest?: boolean // 是否移除剩余内容
|
|
145
|
+
tagName?: string // 思考标签名称,默认 'think'
|
|
146
|
+
mode?: 'full' | 'end-tag-only' // 解析模式
|
|
124
147
|
}
|
|
125
148
|
|
|
126
149
|
interface ThinkingResult {
|
|
127
|
-
thinking: string
|
|
128
|
-
|
|
129
|
-
hasThinking: boolean // 是否找到思考内容
|
|
150
|
+
thinking: string // 提取的思考内容
|
|
151
|
+
content: string // 实际回答内容
|
|
130
152
|
}
|
|
131
153
|
```
|
|
132
154
|
|
|
133
155
|
#### 使用示例
|
|
134
156
|
|
|
135
157
|
```typescript
|
|
136
|
-
import { extractThinking
|
|
158
|
+
import { extractThinking } from '@will1123/lx-ui-utils'
|
|
137
159
|
|
|
138
|
-
//
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
</think>
|
|
142
|
-
这是实际回答内容`
|
|
160
|
+
// 模式 1:完整标签(默认)
|
|
161
|
+
const text1 = `这是思考内容
|
|
162
|
+
这是回答内容`
|
|
143
163
|
|
|
144
|
-
const
|
|
145
|
-
console.log(
|
|
146
|
-
console.log(
|
|
147
|
-
console.log(result.hasThinking) // true
|
|
164
|
+
const result1 = extractThinking(text1)
|
|
165
|
+
console.log(result1.thinking) // "这是思考内容"
|
|
166
|
+
console.log(result1.content) // "这是回答内容"
|
|
148
167
|
|
|
149
|
-
//
|
|
150
|
-
const
|
|
151
|
-
tagName: 'think',
|
|
152
|
-
includeTags: false
|
|
153
|
-
})
|
|
168
|
+
// 模式 2:仅结束标签
|
|
169
|
+
const text2 = '这是思考内容这是回答内容'
|
|
154
170
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
console.log(
|
|
171
|
+
const result2 = extractThinking(text2, { mode: 'end-tag-only' })
|
|
172
|
+
console.log(result2.thinking) // "这是思考内容"
|
|
173
|
+
console.log(result2.content) // "这是回答内容"
|
|
158
174
|
|
|
159
|
-
|
|
160
|
-
|
|
175
|
+
// 模式 3:没有标签
|
|
176
|
+
const text3 = '全部是正文内容'
|
|
161
177
|
|
|
162
|
-
const
|
|
163
|
-
console.log(
|
|
164
|
-
console.log(
|
|
178
|
+
const result3 = extractThinking(text3)
|
|
179
|
+
console.log(result3.thinking) // ""
|
|
180
|
+
console.log(result3.content) // "全部是正文内容"
|
|
165
181
|
```
|
|
166
182
|
|
|
167
183
|
### Markdown 渲染
|
|
168
184
|
|
|
169
|
-
|
|
185
|
+
基于 **Marked.js** 的增强 Markdown 渲染器,支持代码高亮、主题切换、自定义样式和 KaTeX 数学公式。
|
|
186
|
+
|
|
187
|
+
#### 特性
|
|
188
|
+
|
|
189
|
+
- ✅ **链接增强** - 自动添加 `target="_blank"`
|
|
190
|
+
- ✅ **表格优化** - 自动包装在可滚动容器中
|
|
191
|
+
- ✅ **代码增强** - 显示语言标签、复制按钮、主题切换
|
|
192
|
+
- ✅ **主题系统** - 支持 dark/light 主题切换,基于 localStorage
|
|
193
|
+
- ✅ **代码高亮** - 集成 highlight.js,支持 190+ 语言
|
|
194
|
+
- ✅ **GFM 支持** - 表格、任务列表、删除线等
|
|
195
|
+
- ✅ **类名前缀** - 所有生成的类名带 `lx-ui` 前缀,避免冲突
|
|
196
|
+
- ✅ **KaTeX 公式** - 支持数学公式渲染(可选)
|
|
170
197
|
|
|
171
198
|
#### 类型定义
|
|
172
199
|
|
|
173
200
|
```typescript
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
image?: boolean // 是否渲染图片,默认 true
|
|
201
|
+
type CodeTheme = 'dark' | 'light'
|
|
202
|
+
|
|
203
|
+
interface CreateMarkdownRenderConfig {
|
|
204
|
+
codeBoxToolEnable?: boolean // 代码块工具栏是否启用,默认 true
|
|
205
|
+
katexEnable?: boolean // 是否启用 KaTeX 公式支持,默认 true
|
|
206
|
+
katexOptions?: KatexOptions // KaTeX 配置选项
|
|
181
207
|
}
|
|
182
208
|
```
|
|
183
209
|
|
|
184
|
-
####
|
|
210
|
+
#### 使用步骤
|
|
211
|
+
|
|
212
|
+
##### 1. 引入代码高亮样式(必需)
|
|
213
|
+
|
|
214
|
+
选择一个 highlight.js 主题并引入:
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
// 暗色主题(推荐)
|
|
218
|
+
import 'highlight.js/styles/base16/outrun-dark.min.css'
|
|
219
|
+
import 'highlight.js/styles/atom-one-dark.min.css'
|
|
220
|
+
import 'highlight.js/styles/github-dark.min.css'
|
|
221
|
+
|
|
222
|
+
// 亮色主题
|
|
223
|
+
import 'highlight.js/styles/github.min.css'
|
|
224
|
+
import 'highlight.js/styles/base16/solarflare-light.min.css'
|
|
225
|
+
|
|
226
|
+
// 查看所有主题:https://highlightjs.org/examples
|
|
227
|
+
// 主题文件位置:node_modules/highlight.js/styles/
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
##### 2. 引入 KaTeX 样式(必需,默认启用公式支持)
|
|
231
|
+
|
|
232
|
+
```javascript
|
|
233
|
+
// KaTeX 样式(必需,默认启用公式支持)
|
|
234
|
+
import 'katex/dist/katex.min.css'
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
##### 3. 创建渲染器
|
|
185
238
|
|
|
186
239
|
```typescript
|
|
187
|
-
import {
|
|
240
|
+
import { createMarkdownRender } from '@will1123/lx-ui-utils'
|
|
241
|
+
|
|
242
|
+
// 创建渲染器(默认启用工具栏和 KaTeX 公式支持)
|
|
243
|
+
const renderer = createMarkdownRender({
|
|
244
|
+
codeBoxToolEnable: true,
|
|
245
|
+
katexEnable: true,
|
|
246
|
+
katexOptions: {
|
|
247
|
+
throwOnError: false,
|
|
248
|
+
strict: false
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
// 创建渲染器(禁用工具栏)
|
|
253
|
+
const simpleRenderer = createMarkdownRender({
|
|
254
|
+
codeBoxToolEnable: false
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
// 创建渲染器(禁用公式支持)
|
|
258
|
+
const noKatexRenderer = createMarkdownRender({
|
|
259
|
+
codeBoxToolEnable: true,
|
|
260
|
+
katexEnable: false
|
|
261
|
+
})
|
|
262
|
+
```
|
|
188
263
|
|
|
189
|
-
|
|
264
|
+
##### 4. 渲染 Markdown
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
190
267
|
const markdown = `# 标题
|
|
191
268
|
|
|
192
269
|
这是一段**粗体**和*斜体*文字。
|
|
193
270
|
|
|
271
|
+
## 数学公式
|
|
272
|
+
|
|
273
|
+
### 行内公式
|
|
274
|
+
爱因斯坦质能方程:$E = mc^2$
|
|
275
|
+
|
|
276
|
+
### 块级公式
|
|
277
|
+
$$
|
|
278
|
+
\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}
|
|
279
|
+
$$
|
|
280
|
+
|
|
281
|
+
## 代码示例
|
|
282
|
+
|
|
283
|
+
\`\`\`javascript
|
|
284
|
+
function greet(name) {
|
|
285
|
+
console.log(\`Hello, \${name}!\`)
|
|
286
|
+
return true
|
|
287
|
+
}
|
|
288
|
+
\`\`\`
|
|
289
|
+
|
|
290
|
+
## 表格
|
|
291
|
+
|
|
292
|
+
| 列1 | 列2 | 列3 |
|
|
293
|
+
|-----|-----|-----|
|
|
294
|
+
| A | B | C |
|
|
295
|
+
| D | E | F |
|
|
296
|
+
|
|
297
|
+
## 任务列表
|
|
298
|
+
|
|
194
299
|
- [ ] 未完成任务
|
|
195
300
|
- [x] 已完成任务
|
|
196
301
|
|
|
197
|
-
| 列1 | 列2 |
|
|
198
|
-
|-----|-----|
|
|
199
|
-
| A | B |
|
|
200
|
-
|
|
201
302
|
[链接](https://example.com)
|
|
202
303
|
`
|
|
203
304
|
|
|
204
|
-
|
|
305
|
+
// 渲染(异步)
|
|
306
|
+
const html = await renderer.parse(markdown)
|
|
205
307
|
console.log(html)
|
|
308
|
+
```
|
|
206
309
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
310
|
+
#### KaTeX 公式语法
|
|
311
|
+
|
|
312
|
+
支持两种公式语法:
|
|
313
|
+
|
|
314
|
+
**行内公式** - 使用单个 `$` 包裹:
|
|
315
|
+
```markdown
|
|
316
|
+
爱因斯坦方程:$E = mc^2$
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**块级公式** - 使用双个 `$$` 包裹:
|
|
320
|
+
```markdown
|
|
321
|
+
$$
|
|
322
|
+
\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}
|
|
323
|
+
$$
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**注意事项**:
|
|
327
|
+
- 行内公式不能包含换行符
|
|
328
|
+
- 块级公式可以跨多行
|
|
329
|
+
- 如果公式渲染失败,会保留原始公式文本
|
|
330
|
+
- KaTeX 公式支持默认启用,无需配置即可使用
|
|
331
|
+
- 如需禁用公式支持,设置 `katexEnable: false`
|
|
332
|
+
|
|
333
|
+
#### 生成的 HTML 结构 - KaTeX 公式
|
|
334
|
+
|
|
335
|
+
**行内公式**:
|
|
336
|
+
```html
|
|
337
|
+
<span class="lx-ui-katex-inline">
|
|
338
|
+
<!-- KaTeX 生成的 HTML -->
|
|
339
|
+
</span>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**块级公式**:
|
|
343
|
+
```html
|
|
344
|
+
<div class="lx-ui-katex-block">
|
|
345
|
+
<!-- KaTeX 生成的 HTML -->
|
|
346
|
+
</div>
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
#### 主题管理
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
import { getMarkdownCodeTheme, setMarkdownCodeTheme, toggleMarkdownCodeTheme, updateMarkdownCodeBlocksTheme } from '@will1123/lx-ui-utils'
|
|
353
|
+
|
|
354
|
+
// 获取当前主题
|
|
355
|
+
const theme = getMarkdownCodeTheme() // 'dark' | 'light'
|
|
356
|
+
|
|
357
|
+
// 设置主题
|
|
358
|
+
setMarkdownCodeTheme('light')
|
|
359
|
+
|
|
360
|
+
// 切换主题(只更新 localStorage,不更新 DOM)
|
|
361
|
+
const newTheme = toggleMarkdownCodeTheme()
|
|
362
|
+
|
|
363
|
+
// 更新 DOM 中所有代码块的主题(方式 1:只传主题)
|
|
364
|
+
updateMarkdownCodeBlocksTheme('dark')
|
|
365
|
+
|
|
366
|
+
// 更新指定容器内代码块的主题(方式 2:传容器和主题)
|
|
367
|
+
const container = document.querySelector('#markdown-container')
|
|
368
|
+
updateMarkdownCodeBlocksTheme(container, 'light')
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**注意**:
|
|
372
|
+
- `toggleMarkdownCodeTheme()` 只切换 `localStorage` 中的主题,不会更新 DOM
|
|
373
|
+
- `updateMarkdownCodeBlocksTheme()` 用于更新 DOM 中代码块的 `data-theme` 属性
|
|
374
|
+
- `bindMarkdownCodeBoxEvents()` 内部会自动调用这两个函数
|
|
375
|
+
- `updateMarkdownCodeBlocksTheme()` 支持两种调用方式:
|
|
376
|
+
- 只传主题字符串:`updateMarkdownCodeBlocksTheme('dark')` - 更新 document.body 中的所有代码块
|
|
377
|
+
- 传容器和主题:`updateMarkdownCodeBlocksTheme(container, 'light')` - 更新指定容器内的代码块
|
|
378
|
+
|
|
379
|
+
#### 绑定代码块工具栏事件(推荐)
|
|
380
|
+
|
|
381
|
+
**`bindMarkdownCodeBoxEvents`** 用于处理代码块工具栏的交互事件(复制、主题切换、折叠)。
|
|
382
|
+
|
|
383
|
+
##### 类型定义
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
interface CodeBoxEventsConfig {
|
|
387
|
+
onCopySuccess?: (text: string) => void // 复制成功回调
|
|
388
|
+
onCopyError?: (error: Error) => void // 复制失败回调
|
|
389
|
+
onThemeChange?: (theme: CodeTheme) => void // 主题切换回调
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// 函数重载
|
|
393
|
+
function bindMarkdownCodeBoxEvents(): () => void
|
|
394
|
+
function bindMarkdownCodeBoxEvents(config: CodeBoxEventsConfig): () => void
|
|
395
|
+
function bindMarkdownCodeBoxEvents(container: HTMLElement | Document): () => void
|
|
396
|
+
function bindMarkdownCodeBoxEvents(
|
|
397
|
+
container: HTMLElement | Document,
|
|
398
|
+
config: CodeBoxEventsConfig
|
|
399
|
+
): () => void
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
##### 使用步骤
|
|
403
|
+
|
|
404
|
+
**1. 不传参数(默认 document.body)**
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { bindMarkdownCodeBoxEvents } from '@will1123/lx-ui-utils'
|
|
408
|
+
|
|
409
|
+
const unbind = bindMarkdownCodeBoxEvents()
|
|
410
|
+
|
|
411
|
+
// 组件销毁时解绑
|
|
412
|
+
onUnmounted(() => unbind())
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**2. 只传 config(默认 document.body)**
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
const unbind = bindMarkdownCodeBoxEvents({
|
|
419
|
+
onCopySuccess: (text) => console.log('已复制:', text),
|
|
420
|
+
onCopyError: (error) => console.error('复制失败:', error),
|
|
421
|
+
onThemeChange: (theme) => console.log('主题切换为:', theme)
|
|
214
422
|
})
|
|
423
|
+
```
|
|
215
424
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
425
|
+
**3. 只传 container(使用默认配置)**
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
const container = document.querySelector('#markdown-container')
|
|
429
|
+
const unbind = bindMarkdownCodeBoxEvents(container)
|
|
220
430
|
```
|
|
221
431
|
|
|
432
|
+
**4. 传 container 和 config**
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
const container = document.querySelector('#markdown-container')
|
|
436
|
+
const unbind = bindMarkdownCodeBoxEvents(container, {
|
|
437
|
+
onCopySuccess: (text) => console.log('已复制:', text),
|
|
438
|
+
onThemeChange: (theme) => console.log('主题切换为:', theme)
|
|
439
|
+
})
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
**5. Vue 组件完整示例**
|
|
443
|
+
|
|
444
|
+
```vue
|
|
445
|
+
<template>
|
|
446
|
+
<div>
|
|
447
|
+
<div v-html="html" class="markdown-content"></div>
|
|
448
|
+
</div>
|
|
449
|
+
</template>
|
|
450
|
+
|
|
451
|
+
<script>
|
|
452
|
+
import { createMarkdownRender, bindMarkdownCodeBoxEvents } from '@will1123/lx-ui-utils'
|
|
453
|
+
|
|
454
|
+
export default {
|
|
455
|
+
data() {
|
|
456
|
+
return {
|
|
457
|
+
renderer: null,
|
|
458
|
+
html: '',
|
|
459
|
+
unbindEvents: null
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
async mounted() {
|
|
463
|
+
// 创建渲染器
|
|
464
|
+
this.renderer = createMarkdownRender({
|
|
465
|
+
codeBoxToolEnable: true
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
// 渲染 Markdown
|
|
469
|
+
this.html = await this.renderer.parse(this.markdown)
|
|
470
|
+
|
|
471
|
+
// 绑定事件(只传 config,默认 container 为 document.body)
|
|
472
|
+
this.unbindEvents = bindMarkdownCodeBoxEvents({
|
|
473
|
+
onCopySuccess: (text) => {
|
|
474
|
+
this.$message.success('已复制到剪贴板')
|
|
475
|
+
},
|
|
476
|
+
onCopyError: (error) => {
|
|
477
|
+
this.$message.error('复制失败')
|
|
478
|
+
},
|
|
479
|
+
onThemeChange: (theme) => {
|
|
480
|
+
console.log('主题已切换为:', theme)
|
|
481
|
+
// 主题切换会自动更新所有代码块的 data-theme
|
|
482
|
+
// 无需重新渲染
|
|
483
|
+
}
|
|
484
|
+
})
|
|
485
|
+
},
|
|
486
|
+
beforeDestroy() {
|
|
487
|
+
// 清理:解绑事件
|
|
488
|
+
this.unbindEvents?.()
|
|
489
|
+
},
|
|
490
|
+
methods: {
|
|
491
|
+
async render() {
|
|
492
|
+
this.html = await this.renderer.parse(this.markdown)
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
</script>
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
##### 功能说明
|
|
500
|
+
|
|
501
|
+
**1. 复制按钮**
|
|
502
|
+
- 点击复制按钮自动复制代码到剪贴板
|
|
503
|
+
- 内部使用 `copyToClipboard` 工具函数
|
|
504
|
+
- 触发 `onCopySuccess` 或 `onCopyError` 回调
|
|
505
|
+
|
|
506
|
+
**2. 主题切换按钮**
|
|
507
|
+
- 点击主题按钮切换 dark/light 主题
|
|
508
|
+
- **自动执行两步操作**:
|
|
509
|
+
1. 调用 `toggleMarkdownCodeTheme()` 切换 `localStorage` 中的主题
|
|
510
|
+
2. 调用 `updateMarkdownCodeBlocksTheme()` 更新指定容器内所有代码块的 `data-theme` 属性
|
|
511
|
+
- **无需重新渲染** - 切换即时生效,性能更好
|
|
512
|
+
- 触发 `onThemeChange` 回调
|
|
513
|
+
- **容器隔离** - 只更新绑定容器内的代码块,不影响其他区域
|
|
514
|
+
|
|
515
|
+
**3. 折叠按钮**
|
|
516
|
+
- 点击折叠按钮展开/收起代码块
|
|
517
|
+
- 纯前端操作,无需重新渲染
|
|
518
|
+
|
|
519
|
+
##### 注意事项
|
|
520
|
+
|
|
521
|
+
- 事件委托:使用事件委托,动态添加的代码块也会生效
|
|
522
|
+
- 清理函数:返回的清理函数必须在组件销毁时调用,避免内存泄漏
|
|
523
|
+
- 职责分离:
|
|
524
|
+
- `toggleMarkdownCodeTheme()` - 只负责切换 `localStorage` 中的主题
|
|
525
|
+
- `updateMarkdownCodeBlocksTheme()` - 只负责更新 DOM 中代码块的 `data-theme`
|
|
526
|
+
- `bindMarkdownCodeBoxEvents()` - 内部自动调用这两个函数完成完整流程
|
|
527
|
+
- 函数重载:`bindMarkdownCodeBoxEvents()` 支持多种调用方式(不传参数、只传 config、只传 container、两个都传)
|
|
528
|
+
- 容器隔离:指定 container 后,主题切换只影响该容器内的代码块,适合多实例场景
|
|
529
|
+
- 兼容性:复制功能支持所有现代浏览器和 IE11+
|
|
530
|
+
|
|
531
|
+
#### 生成的 HTML 结构
|
|
532
|
+
|
|
533
|
+
**链接** - 自动添加 `target="_blank"`:
|
|
534
|
+
```html
|
|
535
|
+
<a target="_blank" href="...">链接文本</a>
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**表格** - 包装在可滚动容器中:
|
|
539
|
+
```html
|
|
540
|
+
<div class="lx-ui-table-wrapper">
|
|
541
|
+
<div class="lx-ui-table-box">
|
|
542
|
+
<table>...</table>
|
|
543
|
+
</div>
|
|
544
|
+
</div>
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
**代码块(启用工具栏)**:
|
|
548
|
+
```html
|
|
549
|
+
<div class="lx-ui-code-box-wrapper">
|
|
550
|
+
<div class="lx-ui-code-box" data-theme="dark">
|
|
551
|
+
<div class="lx-ui-code-box-header">
|
|
552
|
+
<div class="lx-ui-code-box-header-left">
|
|
553
|
+
<span class="lx-ui-code-language">javascript</span>
|
|
554
|
+
<i class="lx-ui-icon-fold"></i>
|
|
555
|
+
</div>
|
|
556
|
+
<div class="lx-ui-code-box-header-right">
|
|
557
|
+
<i class="lx-ui-icon-copy" title="复制"></i>
|
|
558
|
+
<i class="lx-ui-icon-theme" title="切换主题"></i>
|
|
559
|
+
</div>
|
|
560
|
+
</div>
|
|
561
|
+
<div class="lx-ui-code-box-content">
|
|
562
|
+
<pre><code class="hljs language-javascript">...</code></pre>
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
565
|
+
</div>
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
**代码块(禁用工具栏)**:
|
|
569
|
+
```html
|
|
570
|
+
<div class="lx-ui-code-box-wrapper">
|
|
571
|
+
<div class="lx-ui-code-box" data-theme="dark">
|
|
572
|
+
<div class="lx-ui-code-box-header">
|
|
573
|
+
<div class="lx-ui-code-box-header-left">
|
|
574
|
+
<span class="lx-ui-code-language">javascript</span>
|
|
575
|
+
</div>
|
|
576
|
+
</div>
|
|
577
|
+
<div class="lx-ui-code-box-content">
|
|
578
|
+
<pre><code class="hljs language-javascript">...</code></pre>
|
|
579
|
+
</div>
|
|
580
|
+
</div>
|
|
581
|
+
</div>
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
#### CSS 样式指南
|
|
585
|
+
|
|
586
|
+
你需要自行实现以下 CSS 类的样式(所有类名都带 `lx-ui` 前缀以避免冲突):
|
|
587
|
+
|
|
588
|
+
```css
|
|
589
|
+
/* 表格容器 */
|
|
590
|
+
.lx-ui-table-wrapper {
|
|
591
|
+
overflow-x: auto;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.lx-ui-table-box {
|
|
595
|
+
/* 表格内层容器 */
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/* 代码块容器 */
|
|
599
|
+
.lx-ui-code-box-wrapper {
|
|
600
|
+
margin-bottom: 20px;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.lx-ui-code-box {
|
|
604
|
+
border-radius: 4px;
|
|
605
|
+
overflow: hidden;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/* 代码主题 */
|
|
609
|
+
.lx-ui-code-box[data-theme="dark"] {
|
|
610
|
+
background: #1e1e1e;
|
|
611
|
+
color: #d4d4d4;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.lx-ui-code-box[data-theme="light"] {
|
|
615
|
+
background: #f5f5f5;
|
|
616
|
+
color: #333;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/* 代码块工具栏 */
|
|
620
|
+
.lx-ui-code-box-header {
|
|
621
|
+
display: flex;
|
|
622
|
+
justify-content: space-between;
|
|
623
|
+
align-items: center;
|
|
624
|
+
padding: 8px 12px;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.lx-ui-code-box-header-left,
|
|
628
|
+
.lx-ui-code-box-header-right {
|
|
629
|
+
display: flex;
|
|
630
|
+
align-items: center;
|
|
631
|
+
gap: 8px;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.lx-ui-code-language {
|
|
635
|
+
font-size: 12px;
|
|
636
|
+
font-weight: 500;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.lx-ui-code-box-content {
|
|
640
|
+
padding: 12px;
|
|
641
|
+
overflow-x: auto;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/* 工具栏图标 */
|
|
645
|
+
.lx-ui-icon-copy,
|
|
646
|
+
.lx-ui-icon-theme,
|
|
647
|
+
.lx-ui-icon-fold {
|
|
648
|
+
cursor: pointer;
|
|
649
|
+
opacity: 0.6;
|
|
650
|
+
transition: opacity 0.2s;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.lx-ui-icon-copy:hover,
|
|
654
|
+
.lx-ui-icon-theme:hover,
|
|
655
|
+
.lx-ui-icon-fold:hover {
|
|
656
|
+
opacity: 1;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/* KaTeX 公式样式(默认启用公式支持) */
|
|
660
|
+
.lx-ui-katex-inline {
|
|
661
|
+
padding: 0 4px;
|
|
662
|
+
font-size: 1.05em;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.lx-ui-katex-block {
|
|
666
|
+
margin: 20px 0;
|
|
667
|
+
padding: 15px;
|
|
668
|
+
overflow-x: auto;
|
|
669
|
+
text-align: center;
|
|
670
|
+
background: #f5f7fa;
|
|
671
|
+
border-radius: 4px;
|
|
672
|
+
}
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
#### 完整示例
|
|
676
|
+
|
|
677
|
+
```vue
|
|
678
|
+
<template>
|
|
679
|
+
<div class="markdown-content" v-html="html"></div>
|
|
680
|
+
</template>
|
|
681
|
+
|
|
682
|
+
<script>
|
|
683
|
+
import { createMarkdownRender } from '@will1123/lx-ui-utils'
|
|
684
|
+
import 'highlight.js/styles/base16/outrun-dark.min.css'
|
|
685
|
+
import 'katex/dist/katex.min.css'
|
|
686
|
+
|
|
687
|
+
export default {
|
|
688
|
+
data() {
|
|
689
|
+
return {
|
|
690
|
+
renderer: null,
|
|
691
|
+
html: ''
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
async mounted() {
|
|
695
|
+
// 创建渲染器(启用 KaTeX 公式支持)
|
|
696
|
+
this.renderer = createMarkdownRender({
|
|
697
|
+
codeBoxToolEnable: true,
|
|
698
|
+
katexEnable: true
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
// 渲染 Markdown
|
|
702
|
+
const markdown = `# Hello World\n\n\`\`\`javascript\nconsole.log('Hello')\n\`\`\``
|
|
703
|
+
this.html = await this.renderer.parse(markdown)
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
</script>
|
|
707
|
+
|
|
708
|
+
<style scoped>
|
|
709
|
+
/* 实现样式 */
|
|
710
|
+
.markdown-content :deep(.lx-ui-code-box) {
|
|
711
|
+
background: #1e1e1e;
|
|
712
|
+
border-radius: 4px;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
.markdown-content :deep(.lx-ui-code-box-header) {
|
|
716
|
+
display: flex;
|
|
717
|
+
justify-content: space-between;
|
|
718
|
+
padding: 8px 12px;
|
|
719
|
+
background: rgba(255, 255, 255, 0.05);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.markdown-content :deep(.lx-ui-icon-copy) {
|
|
723
|
+
cursor: pointer;
|
|
724
|
+
opacity: 0.7;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
.markdown-content :deep(.lx-ui-icon-copy:hover) {
|
|
728
|
+
opacity: 1;
|
|
729
|
+
}
|
|
730
|
+
</style>
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
## 核心优势
|
|
734
|
+
|
|
735
|
+
### SSE 功能
|
|
736
|
+
|
|
737
|
+
- ✅ **简洁 API** - 默认 POST 请求,内置 Token 认证支持
|
|
738
|
+
- ✅ **页面可见性** - 自动处理页面隐藏/显示(Page Visibility API)
|
|
739
|
+
- ✅ **自动解析** - 无需手动解析 SSE 消息格式
|
|
740
|
+
- ✅ **生产级** - 基于 Microsoft 维护的 `@microsoft/fetch-event-source`
|
|
741
|
+
- ✅ **代码精简** - 相比手动实现减少 70% 代码量
|
|
742
|
+
|
|
743
|
+
### 深度思考提取
|
|
744
|
+
|
|
745
|
+
- ✅ **灵活解析** - 支持完整标签和仅结束标签两种模式
|
|
746
|
+
- ✅ **类型安全** - 完整的 TypeScript 类型定义
|
|
747
|
+
- ✅ **简洁 API** - 一个函数完成所有解析逻辑
|
|
748
|
+
|
|
749
|
+
### Markdown 渲染
|
|
750
|
+
|
|
751
|
+
- ✅ **增强功能** - 链接自动新窗口、表格可滚动、代码块工具栏
|
|
752
|
+
- ✅ **主题系统** - dark/light 主题切换,基于 localStorage 持久化
|
|
753
|
+
- ✅ **代码高亮** - 集成 highlight.js,支持 190+ 语言
|
|
754
|
+
- ✅ **标准兼容** - 基于 Marked.js,99% CommonMark 兼容
|
|
755
|
+
- ✅ **GFM 支持** - 表格、任务列表、删除线等
|
|
756
|
+
- ✅ **类名安全** - 所有生成的类名带 `lx-ui` 前缀,避免冲突
|
|
757
|
+
- ✅ **按需样式** - 不提供 CSS 文件,用户自行实现样式
|
|
758
|
+
- ✅ **KaTeX 公式** - 支持 LaTeX 数学公式渲染(可选)
|
|
759
|
+
|
|
760
|
+
### Helper Functions
|
|
761
|
+
|
|
762
|
+
通用工具函数,可在任何场景使用。
|
|
763
|
+
|
|
764
|
+
#### 复制到剪贴板
|
|
765
|
+
|
|
766
|
+
**`copyToClipboard`** 是一个通用的复制文本到剪贴板的工具函数。
|
|
767
|
+
|
|
768
|
+
##### 类型定义
|
|
769
|
+
|
|
770
|
+
```typescript
|
|
771
|
+
function copyToClipboard(text: string): Promise<boolean>
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
##### 使用示例
|
|
775
|
+
|
|
776
|
+
```typescript
|
|
777
|
+
import { copyToClipboard } from '@will1123/lx-ui-utils'
|
|
778
|
+
|
|
779
|
+
// 基础用法
|
|
780
|
+
const success = await copyToClipboard('Hello World')
|
|
781
|
+
if (success) {
|
|
782
|
+
console.log('复制成功')
|
|
783
|
+
} else {
|
|
784
|
+
console.log('复制失败')
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// 复制代码
|
|
788
|
+
const code = 'console.log("Hello World")'
|
|
789
|
+
await copyToClipboard(code)
|
|
790
|
+
|
|
791
|
+
// 复制 HTML 内容
|
|
792
|
+
const html = '<div>Hello</div>'
|
|
793
|
+
await copyToClipboard(html)
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
##### 特性
|
|
797
|
+
|
|
798
|
+
- ✅ **现代 API** - 优先使用 Clipboard API
|
|
799
|
+
- ✅ **自动降级** - 不支持时自动降级到 `document.execCommand`
|
|
800
|
+
- ✅ **SSR 安全** - 服务端渲染环境自动返回 false
|
|
801
|
+
- ✅ **返回值** - 返回 Promise<boolean>,成功返回 true,失败返回 false
|
|
802
|
+
- ✅ **兼容性好** - 支持所有主流浏览器(包括 IE11+)
|
|
803
|
+
- ✅ **通用性** - 可在任何场景使用,不限于 Markdown
|
|
804
|
+
|
|
805
|
+
#### 判断 DOM 元素
|
|
806
|
+
|
|
807
|
+
**`isDom`** 用于判断值是否为 DOM 元素(HTMLElement 或 Document)。
|
|
808
|
+
|
|
809
|
+
##### 类型定义
|
|
810
|
+
|
|
811
|
+
```typescript
|
|
812
|
+
function isDom(val: unknown): val is HTMLElement | Document
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
##### 使用示例
|
|
816
|
+
|
|
817
|
+
```typescript
|
|
818
|
+
import { isDom } from '@will1123/lx-ui-utils'
|
|
819
|
+
|
|
820
|
+
// 判断各种值
|
|
821
|
+
isDom(document.body) // true
|
|
822
|
+
isDom(document) // true
|
|
823
|
+
isDom(document.querySelector('div')) // true (如果找到元素)
|
|
824
|
+
|
|
825
|
+
isDom({}) // false
|
|
826
|
+
isDom('div') // false
|
|
827
|
+
isDom(null) // false
|
|
828
|
+
isDom(undefined) // false
|
|
829
|
+
isDom(123) // false
|
|
830
|
+
|
|
831
|
+
// 在函数重载中使用
|
|
832
|
+
function processInput(input: unknown) {
|
|
833
|
+
if (isDom(input)) {
|
|
834
|
+
// input 在这里被识别为 HTMLElement | Document
|
|
835
|
+
input.addEventListener('click', handler)
|
|
836
|
+
} else {
|
|
837
|
+
console.log('不是 DOM 元素')
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
##### 特性
|
|
843
|
+
|
|
844
|
+
- ✅ **类型守卫** - TypeScript 类型守卫,可缩小类型范围
|
|
845
|
+
- ✅ **精确判断** - 使用 `instanceof` 判断,支持所有 DOM 元素
|
|
846
|
+
- ✅ **SSR 安全** - 服务端渲染环境也能正常工作
|
|
847
|
+
- ✅ **辅助开发** - 常用于函数重载、参数验证等场景
|
|
848
|
+
|
|
222
849
|
## 构建
|
|
223
850
|
|
|
224
851
|
```bash
|
|
@@ -226,7 +853,7 @@ renderer.complete()
|
|
|
226
853
|
yarn install
|
|
227
854
|
|
|
228
855
|
# 构建 utils 包
|
|
229
|
-
yarn workspace @lx-ui
|
|
856
|
+
yarn workspace @will1123/lx-ui-utils build
|
|
230
857
|
```
|
|
231
858
|
|
|
232
859
|
## 许可证
|