From c653ed071d0635d7e301bcf9295c8107e25a78b3 Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 3 May 2026 04:25:34 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=B0=86=20@iconify/vue=20?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E6=9B=BF=E6=8D=A2=E4=B8=BA=20lucide-vue-next?= =?UTF-8?q?=20=E6=9C=AC=E5=9C=B0=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 所有 lucide: 前缀的图标字符串替换为 lucide-vue-next 组件直接引用 - 动态图标绑定改为 渲染 - h(Icon, { icon: 'lucide:xxx' }) 改为 h(Xxx, { class: ... }) - app-card.vue 的 icon_url 改为 标签(实际是图片路径) - 修复 Webhook 标识符冲突(重命名为 WebhookIcon) - payment-channels 保留 @iconify/vue 用于品牌图标(支付宝、微信、Stripe、PayPal、USDT) --- frontend/package-lock.json | 19 +++ frontend/package.json | 1 + frontend/replace-icons3.mjs | 123 ++++++++++++++++++ .../components/admin-sidebar/nav-footer.vue | 7 +- .../src/components/app-sidebar/nav-team.vue | 19 +-- .../components/app-sidebar/team-switcher.vue | 4 +- .../components/custom-theme/theme-popover.vue | 4 +- frontend/src/components/language-change.vue | 12 +- .../marketing-layout/the-footer.vue | 6 +- .../marketing-layout/the-header.vue | 84 ++++++------ frontend/src/components/toggle-theme.vue | 8 +- .../components/ui/date-picker/DatePicker.vue | 4 +- .../ui/date-picker/DateTimePicker.vue | 4 +- .../components/ui/date-picker/TimePicker.vue | 4 +- frontend/src/layouts/agent.vue | 7 +- frontend/src/main.ts | 3 +- .../admin/agent-apps/[id]/card-types.vue | 12 +- .../src/pages/admin/agent-apps/[id]/edit.vue | 10 +- .../pages/admin/agent-apps/[id]/recharge.vue | 8 +- frontend/src/pages/admin/agent-apps/index.vue | 20 +-- .../src/pages/admin/agent-apps/invite.vue | 6 +- .../src/pages/admin/agent-apps/request.vue | 6 +- frontend/src/pages/admin/agents/[id].vue | 14 +- .../admin/agents/components/data-table.vue | 7 +- frontend/src/pages/admin/agents/create.vue | 12 +- frontend/src/pages/admin/agents/index.vue | 12 +- .../src/pages/admin/announcements/[id].vue | 12 +- .../admin/announcements/components/columns.ts | 9 +- .../src/pages/admin/announcements/create.vue | 10 +- .../src/pages/admin/announcements/index.vue | 12 +- frontend/src/pages/admin/applications.vue | 12 +- .../admin/applications/[id]/security.vue | 8 +- .../admin/applications/[id]/settings.vue | 12 +- .../applications/components/app-card.vue | 6 +- .../src/pages/admin/applications/create.vue | 48 ++++--- frontend/src/pages/admin/card-types/[id].vue | 14 +- .../src/pages/admin/card-types/create.vue | 12 +- frontend/src/pages/admin/card-types/index.vue | 12 +- frontend/src/pages/admin/cards/create.vue | 10 +- frontend/src/pages/admin/cards/index.vue | 12 +- .../src/pages/admin/cloud-constants/[id].vue | 18 +-- .../pages/admin/cloud-constants/create.vue | 16 +-- .../src/pages/admin/cloud-constants/index.vue | 12 +- .../src/pages/admin/cloud-variables/[id].vue | 28 ++-- .../admin/cloud-variables/[id]/records.vue | 6 +- .../pages/admin/cloud-variables/create.vue | 26 ++-- .../src/pages/admin/cloud-variables/index.vue | 12 +- .../devices/components/data-table-toolbar.vue | 12 +- frontend/src/pages/admin/devices/index.vue | 10 +- frontend/src/pages/admin/dynamic-code.vue | 10 +- .../src/pages/admin/dynamic-code/[id].vue | 12 +- .../src/pages/admin/dynamic-code/create.vue | 10 +- .../src/pages/admin/dynamic-code/index.vue | 12 +- .../src/pages/admin/email-settings/[id].vue | 10 +- .../email-settings/components/columns.ts | 10 +- .../src/pages/admin/email-settings/create.vue | 10 +- .../src/pages/admin/email-settings/index.vue | 16 +-- .../pages/admin/extension/api-keys/create.vue | 10 +- frontend/src/pages/admin/extension/index.vue | 18 +-- .../pages/admin/extension/webhooks/create.vue | 10 +- frontend/src/pages/admin/finance/index.vue | 20 +-- frontend/src/pages/admin/index.vue | 34 ++--- frontend/src/pages/admin/logs.vue | 10 +- frontend/src/pages/admin/logs/index.vue | 10 +- .../src/pages/admin/payment-channels/[id].vue | 30 +++-- .../payment-channels/components/columns.ts | 23 +++- .../pages/admin/payment-channels/create.vue | 30 +++-- .../pages/admin/payment-channels/index.vue | 12 +- .../src/pages/admin/risk-control/[id].vue | 44 +++---- .../src/pages/admin/risk-control/create.vue | 42 +++--- .../src/pages/admin/risk-control/index.vue | 12 +- frontend/src/pages/admin/sessions/index.vue | 10 +- .../src/pages/admin/sms-settings/[id].vue | 18 +-- .../admin/sms-settings/components/columns.ts | 20 +-- .../src/pages/admin/sms-settings/create.vue | 18 +-- .../src/pages/admin/sms-settings/index.vue | 16 +-- .../src/pages/admin/storage-configs/[id].vue | 28 ++-- .../storage-configs/components/columns.ts | 22 ++-- .../pages/admin/storage-configs/create.vue | 28 ++-- .../src/pages/admin/storage-configs/index.vue | 12 +- .../src/pages/admin/system-settings/index.vue | 24 ++-- frontend/src/pages/admin/tickets.vue | 12 +- frontend/src/pages/admin/tickets/[id].vue | 34 ++--- frontend/src/pages/admin/tickets/create.vue | 34 ++--- frontend/src/pages/admin/users/[id].vue | 12 +- frontend/src/pages/admin/users/create.vue | 10 +- frontend/src/pages/admin/users/index.vue | 12 +- frontend/src/pages/admin/versions/[id].vue | 26 ++-- .../admin/versions/components/columns.ts | 8 +- frontend/src/pages/admin/versions/create.vue | 24 ++-- frontend/src/pages/admin/versions/index.vue | 12 +- frontend/src/pages/agent/apps/[id].vue | 8 +- frontend/src/pages/agent/apps/index.vue | 8 +- frontend/src/pages/agent/cards/create.vue | 4 +- frontend/src/pages/agent/cards/index.vue | 8 +- frontend/src/pages/agent/finance/index.vue | 10 +- frontend/src/pages/agent/index.vue | 20 +-- frontend/src/pages/agent/users/index.vue | 6 +- frontend/src/pages/auth/login.vue | 16 +-- frontend/src/pages/auth/register.vue | 14 +- frontend/src/pages/profile/index.vue | 65 +++++---- 101 files changed, 910 insertions(+), 769 deletions(-) create mode 100644 frontend/replace-icons3.mjs diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a334e58..5f9f37c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -47,6 +47,7 @@ "vue-input-otp": "^0.3.2", "vue-router": "^5.0.3", "vue-sonner": "^2.0.9", + "vue3-flag-icons": "^0.1.4", "zod": "^4.3.6" }, "devDependencies": { @@ -7449,6 +7450,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flag-icons": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-7.5.0.tgz", + "integrity": "sha512-kd+MNXviFIg5hijH766tt+3x76ele1AXlo4zDdCxIvqWZhKt4T83bOtxUOOMlTx/EcFdUMH5yvQgYlFh1EqqFg==", + "license": "MIT" + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -12517,6 +12524,18 @@ "typescript": ">=5.0.0" } }, + "node_modules/vue3-flag-icons": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/vue3-flag-icons/-/vue3-flag-icons-0.1.4.tgz", + "integrity": "sha512-bJrrZuVHcs5XCW1CiXG3gpZnkzczk3gXlLgVmKt4XTeKRb3U/qt57hNtTP2ybXEXaigE26WxhO9rVXF9gsL7Dg==", + "license": "MIT", + "dependencies": { + "flag-icons": "^7.2.3" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, "node_modules/webpack-virtual-modules": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7ad00ee..b84b94f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -68,6 +68,7 @@ "vue-input-otp": "^0.3.2", "vue-router": "^5.0.3", "vue-sonner": "^2.0.9", + "vue3-flag-icons": "^0.1.4", "zod": "^4.3.6" }, "devDependencies": { diff --git a/frontend/replace-icons3.mjs b/frontend/replace-icons3.mjs new file mode 100644 index 0000000..83cbbb3 --- /dev/null +++ b/frontend/replace-icons3.mjs @@ -0,0 +1,123 @@ +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 (single line) + content = content.replace(/]*)\/>/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 + content = content.replace(/]*?)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(/(]*>)/, `$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(', ')}`) +} diff --git a/frontend/src/components/admin-sidebar/nav-footer.vue b/frontend/src/components/admin-sidebar/nav-footer.vue index 53e0c78..93c2bd9 100644 --- a/frontend/src/components/admin-sidebar/nav-footer.vue +++ b/frontend/src/components/admin-sidebar/nav-footer.vue @@ -1,6 +1,5 @@ @@ -35,7 +35,7 @@ const currentFlag = computed(() => { - + 切换语言 @@ -47,11 +47,11 @@ const currentFlag = computed(() => { @update:model-value="handleLocaleChange" > - + English - + 中文 diff --git a/frontend/src/components/marketing-layout/the-footer.vue b/frontend/src/components/marketing-layout/the-footer.vue index 6d92920..9d735ca 100644 --- a/frontend/src/components/marketing-layout/the-footer.vue +++ b/frontend/src/components/marketing-layout/the-footer.vue @@ -1,5 +1,5 @@