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 @@