c653ed071d
- 所有 lucide: 前缀的图标字符串替换为 lucide-vue-next 组件直接引用
- 动态图标绑定改为 <component :is> 渲染
- h(Icon, { icon: 'lucide:xxx' }) 改为 h(Xxx, { class: ... })
- app-card.vue 的 icon_url 改为 <img> 标签(实际是图片路径)
- 修复 Webhook 标识符冲突(重命名为 WebhookIcon)
- payment-channels 保留 @iconify/vue 用于品牌图标(支付宝、微信、Stripe、PayPal、USDT)
124 lines
4.2 KiB
JavaScript
124 lines
4.2 KiB
JavaScript
import { readFileSync, writeFileSync, readdirSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
|
|
const SRC_DIR = 'D:\\Code\\verify\\frontend\\src'
|
|
|
|
function getAllFiles(dir, exts) {
|
|
const results = []
|
|
const entries = readdirSync(dir, { withFileTypes: true })
|
|
for (const entry of entries) {
|
|
const fullPath = join(dir, entry.name)
|
|
if (entry.isDirectory()) {
|
|
results.push(...getAllFiles(fullPath, exts))
|
|
} else if (exts.some(ext => entry.name.endsWith(ext))) {
|
|
results.push(fullPath)
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
function lucideToPascal(kebab) {
|
|
return kebab
|
|
.split('-')
|
|
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
.join('')
|
|
}
|
|
|
|
function processFile(filePath) {
|
|
let content = readFileSync(filePath, 'utf-8')
|
|
const originalContent = content
|
|
|
|
if (!content.includes('@iconify/vue') && !content.includes('icon="lucide:') && !content.includes("icon='lucide:") && !content.includes('h(Icon')) {
|
|
return null
|
|
}
|
|
|
|
const usedIcons = new Set()
|
|
|
|
// Step 1: Replace <Icon icon="lucide:xxx" ... /> (single line)
|
|
content = content.replace(/<Icon\s+icon="lucide:([a-z0-9-]+)"([^>]*)\/>/g, (match, iconName, restAttrs) => {
|
|
const pascalName = lucideToPascal(iconName)
|
|
usedIcons.add(pascalName)
|
|
const trimmedAttrs = restAttrs.trim()
|
|
if (trimmedAttrs) {
|
|
return `<${pascalName} ${trimmedAttrs} />`
|
|
}
|
|
return `<${pascalName} />`
|
|
})
|
|
|
|
// Step 2: Replace multiline <Icon\n icon="lucide:xxx"\n .../>
|
|
content = content.replace(/<Icon\s+([^>]*?)icon="lucide:([a-z0-9-]+)"([^>]*?)\/>/gs, (match, before, iconName, after) => {
|
|
const pascalName = lucideToPascal(iconName)
|
|
usedIcons.add(pascalName)
|
|
const newAttrs = (before + after).replace(/\s+/g, ' ').trim()
|
|
if (newAttrs) {
|
|
return `<${pascalName} ${newAttrs} />`
|
|
}
|
|
return `<${pascalName} />`
|
|
})
|
|
|
|
// Step 3: Replace h(Icon, { icon: 'lucide:xxx', ... }) in columns.ts files
|
|
content = content.replace(/h\(\s*Icon\s*,\s*\{\s*icon:\s*['"]lucide:([a-z0-9-]+)['"]\s*,?\s*([^}]*)\}\s*\)/g, (match, iconName, restProps) => {
|
|
const pascalName = lucideToPascal(iconName)
|
|
usedIcons.add(pascalName)
|
|
const trimmedProps = restProps.trim().replace(/,\s*$/, '')
|
|
if (trimmedProps) {
|
|
return `h(${pascalName}, { ${trimmedProps} })`
|
|
}
|
|
return `h(${pascalName})`
|
|
})
|
|
|
|
// Step 4: Replace import { Icon } from '@iconify/vue'
|
|
content = content.replace(/import\s*\{\s*Icon\s*\}\s*from\s*['"]@iconify\/vue['"]\s*\n?/g, '')
|
|
|
|
// Step 5: Add lucide-vue-next import if we used any icons
|
|
if (usedIcons.size > 0 && !content.includes("from 'lucide-vue-next'") && !content.includes('from "lucide-vue-next"')) {
|
|
const sortedIcons = [...usedIcons].sort()
|
|
const importLine = `import { ${sortedIcons.join(', ')} } from 'lucide-vue-next'\n`
|
|
|
|
const firstImportMatch = content.match(/^import\s/m)
|
|
if (firstImportMatch) {
|
|
const insertPos = content.indexOf(firstImportMatch[0])
|
|
content = content.slice(0, insertPos) + importLine + content.slice(insertPos)
|
|
} else {
|
|
content = content.replace(/(<script[^>]*>)/, `$1\n${importLine}`)
|
|
}
|
|
} else if (usedIcons.size > 0) {
|
|
const existingImportMatch = content.match(/import\s*\{([^}]*)\}\s*from\s*['"]lucide-vue-next['"]/s)
|
|
if (existingImportMatch) {
|
|
const existingIcons = existingImportMatch[1]
|
|
.split(',')
|
|
.map(s => s.trim())
|
|
.filter(Boolean)
|
|
|
|
const newIcons = [...usedIcons].filter(icon => !existingIcons.includes(icon))
|
|
|
|
if (newIcons.length > 0) {
|
|
const allIcons = [...existingIcons, ...newIcons].sort()
|
|
const newImport = `import { ${allIcons.join(', ')} } from 'lucide-vue-next'`
|
|
content = content.replace(existingImportMatch[0], newImport)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (content !== originalContent) {
|
|
writeFileSync(filePath, content, 'utf-8')
|
|
return { file: filePath, icons: [...usedIcons] }
|
|
}
|
|
return null
|
|
}
|
|
|
|
const files = getAllFiles(SRC_DIR, ['.vue', '.ts'])
|
|
const results = []
|
|
|
|
for (const file of files) {
|
|
const result = processFile(file)
|
|
if (result) {
|
|
results.push(result)
|
|
}
|
|
}
|
|
|
|
console.log(`\nProcessed ${results.length} files:`)
|
|
for (const r of results) {
|
|
console.log(` ${r.file}: ${r.icons.join(', ')}`)
|
|
}
|