20260902173133

This commit is contained in:
oneao committed 2026-09-02 17:31:35 +08:00
1 parent 6b9cda5424
commit db7813a2d2
16 files changed
+1350 -675

No files matched your search

+91 -5
View File
@@ -1,3 +1,4 @@
import { defineComponent, h } from 'vue'
import * as LucideIcons from '@lucide/vue'
let iconMap = null
@@ -19,11 +20,96 @@ function loadIcons() {
}
/**
* 同步获取图标组件,传入 null/空字符串时返回 null。
* @param {string | null | undefined} name lucide 图标名(首字母大写),如 "LayoutGrid"
* 解析图标值,返回可渲染的 Vue 组件:
* - 以 "lucide:" 开头(如 "lucide:chevron-down")→ 去掉前缀,名称归一化后匹配 lucide 图标
* - 其他非空字符串 → 先尝试按 lucide 图标名匹配;匹配不到时仅当看起来是有效 SVG 标记才渲染
* - 无效内容(既非 lucide 名也非 SVG 标记)→ 返回 null,不渲染
* 传入 null/空字符串时返回 null。
* @param {string | null | undefined} value
*/
export function getIcon(name) {
export function getIcon(value) {
if (!value || typeof value !== 'string') return null
const trimmed = value.trim()
if (!trimmed) return null
if (!iconMap) loadIcons()
if (!name || !iconMap) return null
return iconMap[name] || null
// lucide 前缀:显式指定图标名,匹配不到返回 null
if (trimmed.startsWith('lucide:')) {
const name = normalizeIconName(trimmed.slice('lucide:'.length))
return iconMap[name] || null
}
// 无前缀:先尝试 lucide 图标名匹配
const normalized = normalizeIconName(trimmed)
if (iconMap[normalized]) return iconMap[normalized]
// 既非 lucide 名:仅当是有效 SVG 标记才渲染,否则视为无效不渲染
return looksLikeSvg(trimmed) ? getSvgComponent(trimmed) : null
}
// 把各种命名风格归一化为 lucide 的 PascalCase 导出名:
// "chevron-down" / "chevron_down" / "chevronDown" / "CHEVRON DOWN" → "ChevronDown"
function normalizeIconName(name) {
const parts = String(name || '').split(/[-_\s]+/).filter(Boolean)
if (parts.length === 0) return ''
// 无分隔符(camelCase / 已是大写开头):直接首字母大写
if (parts.length === 1) {
const single = parts[0]
return single.charAt(0).toUpperCase() + single.slice(1)
}
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('')
}
// 判断字符串是否为有效 SVG 标记:用 DOMParser 按 SVG 文档解析,
// 解析失败(存在 parsererror)或根元素不是 <svg> 时视为无效。
// 无 DOMParser 环境(SSR/测试)回退到基础启发式判断。
function looksLikeSvg(content) {
const trimmed = String(content || '').trim()
if (!trimmed) return false
if (typeof DOMParser === 'undefined') {
return /^<svg[\s>]/i.test(trimmed)
}
try {
const doc = new DOMParser().parseFromString(trimmed, 'image/svg+xml')
if (doc.querySelector('parsererror')) return false
const root = doc.documentElement
return root && root.tagName.toLowerCase() === 'svg'
} catch {
return false
}
}
const svgComponentCache = new Map()
function getSvgComponent(svg) {
let component = svgComponentCache.get(svg)
if (!component) {
component = defineComponent({
name: 'RawSvgIcon',
render() {
return h('span', {
class: 'raw-svg-icon',
style: {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
lineHeight: 0,
},
innerHTML: svg,
})
},
})
svgComponentCache.set(svg, component)
}
return component
}
/**
* 获取所有 lucide 图标名称(按字母排序)。
* @returns {string[]}
*/
export function getIconNames() {
if (!iconMap) loadIcons()
return Object.keys(iconMap).toSorted()
}