@will1123/lx-ui-utils
v1.0.11
Published
LX UI - 灵曦平台 AI 组件库通用工具函数库
Downloads
1,970
Maintainers
Readme
@will1123/lx-ui-utils
LX UI 通用工具函数库,提供常用的工具函数和 AI 相关功能。
特性
- 🚀 TypeScript 支持 - 完整的类型定义
- 📦 Tree-Shaking 支持 - 按需引入,减少打包体积
- 🔐 内置认证 - 支持 Token 认证
- 🔧 AI 相关工具 - SSE、深度思考提取、Markdown 渲染
安装
npm install @will1123/lx-ui-utils
# 或
yarn add @will1123/lx-ui-utils
# 或
pnpm add @will1123/lx-ui-utils使用方法
全量导入
import { sse, extractThinking, createMarkdownRender, bindMarkdownCodeBoxEvents } from '@will1123/lx-ui-utils'按需导入(推荐)
import { sse } from '@will1123/lx-ui-utils'
import { extractThinking } from '@will1123/lx-ui-utils'
import {
createMarkdownRender,
getMarkdownCodeTheme,
setMarkdownCodeTheme,
toggleMarkdownCodeTheme,
updateMarkdownCodeBlocksTheme,
bindMarkdownCodeBoxEvents
} from '@will1123/lx-ui-utils'
import { copyToClipboard, isDom } from '@will1123/lx-ui-utils'API 文档
SSE (Server-Sent Events)
基于 @microsoft/fetch-event-source 封装的 SSE 请求,支持流式响应、自动重连和页面可见性控制。
类型定义
interface SSEConfig {
url: string // 请求 URL
method?: 'GET' | 'POST' // 请求方法,默认 POST
headers?: Record<string, string> // 请求头
body?: object // 请求体(对象类型)
signal?: AbortSignal // 用于取消请求
token?: string // 认证 Token
openWhenHidden?: boolean // 页面隐藏时是否保持连接,默认 true
}
interface SSEHandlers {
onOpen?: (response: Response) => void | Promise<void> // 连接打开时触发
onMessage?: (message: EventSourceMessage) => void // 接收到消息时触发
onClose?: () => void // 连接关闭时触发
onError?: (error: Error) => void // 发生错误时触发
}使用示例
import { sse } from '@will1123/lx-ui-utils'
// 基础用法(默认 POST)
await sse({
url: 'https://api.example.com/stream',
body: { prompt: '你好' }
}, {
onMessage(msg) {
console.log('收到消息:', msg)
}
})
// 使用 Token 认证
await sse({
url: 'https://api.example.com/stream',
token: 'Bearer your-token-here',
body: { prompt: '你好' }
}, {
onOpen(response) {
console.log('连接已打开,状态:', response.status)
},
onMessage(msg) {
// msg 是原始对象,包含 data、event、id 等字段
console.log('收到消息:', msg.data)
},
onClose() {
console.log('连接已关闭')
},
onError(err) {
console.error('连接错误:', err)
}
})
// GET 请求
await sse({
url: 'https://api.example.com/stream',
method: 'GET'
}, {
onMessage(msg) {
console.log(msg.data)
}
})
// 取消请求
const ctrl = new AbortController()
await sse({
url: 'https://api.example.com/stream',
signal: ctrl.signal
}, {
onMessage(msg) {
console.log(msg.data)
}
})
// 取消请求
ctrl.abort()深度思考提取
从 AI 响应中提取深度思考内容。
类型定义
interface ThinkingExtractConfig {
tagName?: string // 思考标签名称,默认 'think'
mode?: 'full' | 'end-tag-only' // 解析模式
}
interface ThinkingResult {
thinking: string // 提取的思考内容
content: string // 实际回答内容
}使用示例
import { extractThinking } from '@will1123/lx-ui-utils'
// 模式 1:完整标签(默认)
const text1 = `这是思考内容
这是回答内容`
const result1 = extractThinking(text1)
console.log(result1.thinking) // "这是思考内容"
console.log(result1.content) // "这是回答内容"
// 模式 2:仅结束标签
const text2 = '这是思考内容这是回答内容'
const result2 = extractThinking(text2, { mode: 'end-tag-only' })
console.log(result2.thinking) // "这是思考内容"
console.log(result2.content) // "这是回答内容"
// 模式 3:没有标签
const text3 = '全部是正文内容'
const result3 = extractThinking(text3)
console.log(result3.thinking) // ""
console.log(result3.content) // "全部是正文内容"Markdown 渲染
基于 Marked.js 的增强 Markdown 渲染器,支持代码高亮、主题切换、自定义样式和 KaTeX 数学公式。
特性
- ✅ 链接增强 - 自动添加
target="_blank" - ✅ 表格优化 - 自动包装在可滚动容器中
- ✅ 代码增强 - 显示语言标签、复制按钮、主题切换
- ✅ 主题系统 - 支持 dark/light 主题切换,基于 localStorage
- ✅ 代码高亮 - 集成 highlight.js,支持 190+ 语言
- ✅ GFM 支持 - 表格、任务列表、删除线等
- ✅ 类名前缀 - 所有生成的类名带
lx-ui前缀,避免冲突 - ✅ KaTeX 公式 - 支持数学公式渲染(可选)
类型定义
type CodeTheme = 'dark' | 'light'
interface CreateMarkdownRenderConfig {
codeBoxToolEnable?: boolean // 代码块工具栏是否启用,默认 true
katexEnable?: boolean // 是否启用 KaTeX 公式支持,默认 true
katexOptions?: KatexOptions // KaTeX 配置选项
}使用步骤
1. 引入代码高亮样式(必需)
选择一个 highlight.js 主题并引入:
// 暗色主题(推荐)
import 'highlight.js/styles/base16/outrun-dark.min.css'
import 'highlight.js/styles/atom-one-dark.min.css'
import 'highlight.js/styles/github-dark.min.css'
// 亮色主题
import 'highlight.js/styles/github.min.css'
import 'highlight.js/styles/base16/solarflare-light.min.css'
// 查看所有主题:https://highlightjs.org/examples
// 主题文件位置:node_modules/highlight.js/styles/2. 引入 KaTeX 样式(必需,默认启用公式支持)
// KaTeX 样式(必需,默认启用公式支持)
import 'katex/dist/katex.min.css'3. 创建渲染器
import { createMarkdownRender } from '@will1123/lx-ui-utils'
// 创建渲染器(默认启用工具栏和 KaTeX 公式支持)
const renderer = createMarkdownRender({
codeBoxToolEnable: true,
katexEnable: true,
katexOptions: {
throwOnError: false,
strict: false
}
})
// 创建渲染器(禁用工具栏)
const simpleRenderer = createMarkdownRender({
codeBoxToolEnable: false
})
// 创建渲染器(禁用公式支持)
const noKatexRenderer = createMarkdownRender({
codeBoxToolEnable: true,
katexEnable: false
})4. 渲染 Markdown
const markdown = `# 标题
这是一段**粗体**和*斜体*文字。
## 数学公式
### 行内公式
爱因斯坦质能方程:$E = mc^2$
### 块级公式
$$
\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}
$$
## 代码示例
\`\`\`javascript
function greet(name) {
console.log(\`Hello, \${name}!\`)
return true
}
\`\`\`
## 表格
| 列1 | 列2 | 列3 |
|-----|-----|-----|
| A | B | C |
| D | E | F |
## 任务列表
- [ ] 未完成任务
- [x] 已完成任务
[链接](https://example.com)
`
// 渲染(异步)
const html = await renderer.parse(markdown)
console.log(html)KaTeX 公式语法
支持两种公式语法:
行内公式 - 使用单个 $ 包裹:
爱因斯坦方程:$E = mc^2$块级公式 - 使用双个 $$ 包裹:
$$
\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}
$$注意事项:
- 行内公式不能包含换行符
- 块级公式可以跨多行
- 如果公式渲染失败,会保留原始公式文本
- KaTeX 公式支持默认启用,无需配置即可使用
- 如需禁用公式支持,设置
katexEnable: false
生成的 HTML 结构 - KaTeX 公式
行内公式:
<span class="lx-ui-katex-inline">
<!-- KaTeX 生成的 HTML -->
</span>块级公式:
<div class="lx-ui-katex-block">
<!-- KaTeX 生成的 HTML -->
</div>主题管理
import { getMarkdownCodeTheme, setMarkdownCodeTheme, toggleMarkdownCodeTheme, updateMarkdownCodeBlocksTheme } from '@will1123/lx-ui-utils'
// 获取当前主题
const theme = getMarkdownCodeTheme() // 'dark' | 'light'
// 设置主题
setMarkdownCodeTheme('light')
// 切换主题(只更新 localStorage,不更新 DOM)
const newTheme = toggleMarkdownCodeTheme()
// 更新 DOM 中所有代码块的主题(方式 1:只传主题)
updateMarkdownCodeBlocksTheme('dark')
// 更新指定容器内代码块的主题(方式 2:传容器和主题)
const container = document.querySelector('#markdown-container')
updateMarkdownCodeBlocksTheme(container, 'light')注意:
toggleMarkdownCodeTheme()只切换localStorage中的主题,不会更新 DOMupdateMarkdownCodeBlocksTheme()用于更新 DOM 中代码块的data-theme属性bindMarkdownCodeBoxEvents()内部会自动调用这两个函数updateMarkdownCodeBlocksTheme()支持两种调用方式:- 只传主题字符串:
updateMarkdownCodeBlocksTheme('dark')- 更新 document.body 中的所有代码块 - 传容器和主题:
updateMarkdownCodeBlocksTheme(container, 'light')- 更新指定容器内的代码块
- 只传主题字符串:
绑定代码块工具栏事件(推荐)
bindMarkdownCodeBoxEvents 用于处理代码块工具栏的交互事件(复制、主题切换、折叠)。
类型定义
interface CodeBoxEventsConfig {
onCopySuccess?: (text: string) => void // 复制成功回调
onCopyError?: (error: Error) => void // 复制失败回调
onThemeChange?: (theme: CodeTheme) => void // 主题切换回调
}
// 函数重载
function bindMarkdownCodeBoxEvents(): () => void
function bindMarkdownCodeBoxEvents(config: CodeBoxEventsConfig): () => void
function bindMarkdownCodeBoxEvents(container: HTMLElement | Document): () => void
function bindMarkdownCodeBoxEvents(
container: HTMLElement | Document,
config: CodeBoxEventsConfig
): () => void使用步骤
1. 不传参数(默认 document.body)
import { bindMarkdownCodeBoxEvents } from '@will1123/lx-ui-utils'
const unbind = bindMarkdownCodeBoxEvents()
// 组件销毁时解绑
onUnmounted(() => unbind())2. 只传 config(默认 document.body)
const unbind = bindMarkdownCodeBoxEvents({
onCopySuccess: (text) => console.log('已复制:', text),
onCopyError: (error) => console.error('复制失败:', error),
onThemeChange: (theme) => console.log('主题切换为:', theme)
})3. 只传 container(使用默认配置)
const container = document.querySelector('#markdown-container')
const unbind = bindMarkdownCodeBoxEvents(container)4. 传 container 和 config
const container = document.querySelector('#markdown-container')
const unbind = bindMarkdownCodeBoxEvents(container, {
onCopySuccess: (text) => console.log('已复制:', text),
onThemeChange: (theme) => console.log('主题切换为:', theme)
})5. Vue 组件完整示例
<template>
<div>
<div v-html="html" class="markdown-content"></div>
</div>
</template>
<script>
import { createMarkdownRender, bindMarkdownCodeBoxEvents } from '@will1123/lx-ui-utils'
export default {
data() {
return {
renderer: null,
html: '',
unbindEvents: null
}
},
async mounted() {
// 创建渲染器
this.renderer = createMarkdownRender({
codeBoxToolEnable: true
})
// 渲染 Markdown
this.html = await this.renderer.parse(this.markdown)
// 绑定事件(只传 config,默认 container 为 document.body)
this.unbindEvents = bindMarkdownCodeBoxEvents({
onCopySuccess: (text) => {
this.$message.success('已复制到剪贴板')
},
onCopyError: (error) => {
this.$message.error('复制失败')
},
onThemeChange: (theme) => {
console.log('主题已切换为:', theme)
// 主题切换会自动更新所有代码块的 data-theme
// 无需重新渲染
}
})
},
beforeDestroy() {
// 清理:解绑事件
this.unbindEvents?.()
},
methods: {
async render() {
this.html = await this.renderer.parse(this.markdown)
}
}
}
</script>功能说明
1. 复制按钮
- 点击复制按钮自动复制代码到剪贴板
- 内部使用
copyToClipboard工具函数 - 触发
onCopySuccess或onCopyError回调
2. 主题切换按钮
- 点击主题按钮切换 dark/light 主题
- 自动执行两步操作:
- 调用
toggleMarkdownCodeTheme()切换localStorage中的主题 - 调用
updateMarkdownCodeBlocksTheme()更新指定容器内所有代码块的data-theme属性
- 调用
- 无需重新渲染 - 切换即时生效,性能更好
- 触发
onThemeChange回调 - 容器隔离 - 只更新绑定容器内的代码块,不影响其他区域
3. 折叠按钮
- 点击折叠按钮展开/收起代码块
- 纯前端操作,无需重新渲染
注意事项
- 事件委托:使用事件委托,动态添加的代码块也会生效
- 清理函数:返回的清理函数必须在组件销毁时调用,避免内存泄漏
- 职责分离:
toggleMarkdownCodeTheme()- 只负责切换localStorage中的主题updateMarkdownCodeBlocksTheme()- 只负责更新 DOM 中代码块的data-themebindMarkdownCodeBoxEvents()- 内部自动调用这两个函数完成完整流程
- 函数重载:
bindMarkdownCodeBoxEvents()支持多种调用方式(不传参数、只传 config、只传 container、两个都传) - 容器隔离:指定 container 后,主题切换只影响该容器内的代码块,适合多实例场景
- 兼容性:复制功能支持所有现代浏览器和 IE11+
生成的 HTML 结构
链接 - 自动添加 target="_blank":
<a target="_blank" href="...">链接文本</a>表格 - 包装在可滚动容器中:
<div class="lx-ui-table-wrapper">
<div class="lx-ui-table-box">
<table>...</table>
</div>
</div>代码块(启用工具栏):
<div class="lx-ui-code-box-wrapper">
<div class="lx-ui-code-box" data-theme="dark">
<div class="lx-ui-code-box-header">
<div class="lx-ui-code-box-header-left">
<span class="lx-ui-code-language">javascript</span>
<i class="lx-ui-icon-fold"></i>
</div>
<div class="lx-ui-code-box-header-right">
<i class="lx-ui-icon-copy" title="复制"></i>
<i class="lx-ui-icon-theme" title="切换主题"></i>
</div>
</div>
<div class="lx-ui-code-box-content">
<pre><code class="hljs language-javascript">...</code></pre>
</div>
</div>
</div>代码块(禁用工具栏):
<div class="lx-ui-code-box-wrapper">
<div class="lx-ui-code-box" data-theme="dark">
<div class="lx-ui-code-box-header">
<div class="lx-ui-code-box-header-left">
<span class="lx-ui-code-language">javascript</span>
</div>
</div>
<div class="lx-ui-code-box-content">
<pre><code class="hljs language-javascript">...</code></pre>
</div>
</div>
</div>CSS 样式指南
你需要自行实现以下 CSS 类的样式(所有类名都带 lx-ui 前缀以避免冲突):
/* 表格容器 */
.lx-ui-table-wrapper {
overflow-x: auto;
}
.lx-ui-table-box {
/* 表格内层容器 */
}
/* 代码块容器 */
.lx-ui-code-box-wrapper {
margin-bottom: 20px;
}
.lx-ui-code-box {
border-radius: 4px;
overflow: hidden;
}
/* 代码主题 */
.lx-ui-code-box[data-theme="dark"] {
background: #1e1e1e;
color: #d4d4d4;
}
.lx-ui-code-box[data-theme="light"] {
background: #f5f5f5;
color: #333;
}
/* 代码块工具栏 */
.lx-ui-code-box-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
}
.lx-ui-code-box-header-left,
.lx-ui-code-box-header-right {
display: flex;
align-items: center;
gap: 8px;
}
.lx-ui-code-language {
font-size: 12px;
font-weight: 500;
}
.lx-ui-code-box-content {
padding: 12px;
overflow-x: auto;
}
/* 工具栏图标 */
.lx-ui-icon-copy,
.lx-ui-icon-theme,
.lx-ui-icon-fold {
cursor: pointer;
opacity: 0.6;
transition: opacity 0.2s;
}
.lx-ui-icon-copy:hover,
.lx-ui-icon-theme:hover,
.lx-ui-icon-fold:hover {
opacity: 1;
}
/* KaTeX 公式样式(默认启用公式支持) */
.lx-ui-katex-inline {
padding: 0 4px;
font-size: 1.05em;
}
.lx-ui-katex-block {
margin: 20px 0;
padding: 15px;
overflow-x: auto;
text-align: center;
background: #f5f7fa;
border-radius: 4px;
}完整示例
<template>
<div class="markdown-content" v-html="html"></div>
</template>
<script>
import { createMarkdownRender } from '@will1123/lx-ui-utils'
import 'highlight.js/styles/base16/outrun-dark.min.css'
import 'katex/dist/katex.min.css'
export default {
data() {
return {
renderer: null,
html: ''
}
},
async mounted() {
// 创建渲染器(启用 KaTeX 公式支持)
this.renderer = createMarkdownRender({
codeBoxToolEnable: true,
katexEnable: true
})
// 渲染 Markdown
const markdown = `# Hello World\n\n\`\`\`javascript\nconsole.log('Hello')\n\`\`\``
this.html = await this.renderer.parse(markdown)
}
}
</script>
<style scoped>
/* 实现样式 */
.markdown-content :deep(.lx-ui-code-box) {
background: #1e1e1e;
border-radius: 4px;
}
.markdown-content :deep(.lx-ui-code-box-header) {
display: flex;
justify-content: space-between;
padding: 8px 12px;
background: rgba(255, 255, 255, 0.05);
}
.markdown-content :deep(.lx-ui-icon-copy) {
cursor: pointer;
opacity: 0.7;
}
.markdown-content :deep(.lx-ui-icon-copy:hover) {
opacity: 1;
}
</style>核心优势
SSE 功能
- ✅ 简洁 API - 默认 POST 请求,内置 Token 认证支持
- ✅ 页面可见性 - 自动处理页面隐藏/显示(Page Visibility API)
- ✅ 自动解析 - 无需手动解析 SSE 消息格式
- ✅ 生产级 - 基于 Microsoft 维护的
@microsoft/fetch-event-source - ✅ 代码精简 - 相比手动实现减少 70% 代码量
深度思考提取
- ✅ 灵活解析 - 支持完整标签和仅结束标签两种模式
- ✅ 类型安全 - 完整的 TypeScript 类型定义
- ✅ 简洁 API - 一个函数完成所有解析逻辑
Markdown 渲染
- ✅ 增强功能 - 链接自动新窗口、表格可滚动、代码块工具栏
- ✅ 主题系统 - dark/light 主题切换,基于 localStorage 持久化
- ✅ 代码高亮 - 集成 highlight.js,支持 190+ 语言
- ✅ 标准兼容 - 基于 Marked.js,99% CommonMark 兼容
- ✅ GFM 支持 - 表格、任务列表、删除线等
- ✅ 类名安全 - 所有生成的类名带
lx-ui前缀,避免冲突 - ✅ 按需样式 - 不提供 CSS 文件,用户自行实现样式
- ✅ KaTeX 公式 - 支持 LaTeX 数学公式渲染(可选)
Helper Functions
通用工具函数,可在任何场景使用。
复制到剪贴板
copyToClipboard 是一个通用的复制文本到剪贴板的工具函数。
类型定义
function copyToClipboard(text: string): Promise<boolean>使用示例
import { copyToClipboard } from '@will1123/lx-ui-utils'
// 基础用法
const success = await copyToClipboard('Hello World')
if (success) {
console.log('复制成功')
} else {
console.log('复制失败')
}
// 复制代码
const code = 'console.log("Hello World")'
await copyToClipboard(code)
// 复制 HTML 内容
const html = '<div>Hello</div>'
await copyToClipboard(html)特性
- ✅ 现代 API - 优先使用 Clipboard API
- ✅ 自动降级 - 不支持时自动降级到
document.execCommand - ✅ SSR 安全 - 服务端渲染环境自动返回 false
- ✅ 返回值 - 返回 Promise,成功返回 true,失败返回 false
- ✅ 兼容性好 - 支持所有主流浏览器(包括 IE11+)
- ✅ 通用性 - 可在任何场景使用,不限于 Markdown
判断 DOM 元素
isDom 用于判断值是否为 DOM 元素(HTMLElement 或 Document)。
类型定义
function isDom(val: unknown): val is HTMLElement | Document使用示例
import { isDom } from '@will1123/lx-ui-utils'
// 判断各种值
isDom(document.body) // true
isDom(document) // true
isDom(document.querySelector('div')) // true (如果找到元素)
isDom({}) // false
isDom('div') // false
isDom(null) // false
isDom(undefined) // false
isDom(123) // false
// 在函数重载中使用
function processInput(input: unknown) {
if (isDom(input)) {
// input 在这里被识别为 HTMLElement | Document
input.addEventListener('click', handler)
} else {
console.log('不是 DOM 元素')
}
}特性
- ✅ 类型守卫 - TypeScript 类型守卫,可缩小类型范围
- ✅ 精确判断 - 使用
instanceof判断,支持所有 DOM 元素 - ✅ SSR 安全 - 服务端渲染环境也能正常工作
- ✅ 辅助开发 - 常用于函数重载、参数验证等场景
构建
# 安装依赖
yarn install
# 构建 utils 包
yarn workspace @will1123/lx-ui-utils build许可证
MIT
