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