refactor: 将 @iconify/vue 图标替换为 lucide-vue-next 本地组件

- 所有 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)
This commit is contained in:
2026-05-03 04:25:34 +08:00
parent 1f7dfd17a7
commit c653ed071d
101 changed files with 910 additions and 769 deletions
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { Icon } from '@iconify/vue'
import { BookOpen, Boxes, ChevronDown, Code, CreditCard, DollarSign, FileLock, Gauge, GitBranch, Hash, Home, Key, LayoutDashboard, LogOut, Megaphone, Menu, MessageSquare, Monitor, Moon, Plug, ScrollText, Share2, Shield, Sun, User, Users, Variable, Wifi } from 'lucide-vue-next'
import { useColorMode } from '@vueuse/core'
import { computed, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -66,42 +66,42 @@ const adminMenus = computed(() => ({
main: {
title: t('nav.mainFeatures'),
items: [
{ title: t('nav.console'), url: '/admin', icon: 'lucide:gauge' },
{ title: t('nav.applications'), url: '/admin/applications', icon: 'lucide:boxes' },
{ title: t('nav.cardTypes'), url: '/admin/card-types', icon: 'lucide:hash' },
{ title: t('nav.cards'), url: '/admin/cards', icon: 'lucide:key' },
{ title: t('nav.announcements'), url: '/admin/announcements', icon: 'lucide:megaphone' },
{ title: t('nav.versions'), url: '/admin/versions', icon: 'lucide:git-branch' },
{ title: t('nav.users'), url: '/admin/users', icon: 'lucide:users' },
{ title: t('nav.devices'), url: '/admin/devices', icon: 'lucide:monitor' },
{ title: t('nav.sessions'), url: '/admin/sessions', icon: 'lucide:wifi' },
{ title: t('nav.agentApps'), url: '/admin/agent-apps', icon: 'lucide:share-2' },
{ title: t('nav.console'), url: '/admin', icon: Gauge },
{ title: t('nav.applications'), url: '/admin/applications', icon: Boxes },
{ title: t('nav.cardTypes'), url: '/admin/card-types', icon: Hash },
{ title: t('nav.cards'), url: '/admin/cards', icon: Key },
{ title: t('nav.announcements'), url: '/admin/announcements', icon: Megaphone },
{ title: t('nav.versions'), url: '/admin/versions', icon: GitBranch },
{ title: t('nav.users'), url: '/admin/users', icon: Users },
{ title: t('nav.devices'), url: '/admin/devices', icon: Monitor },
{ title: t('nav.sessions'), url: '/admin/sessions', icon: Wifi },
{ title: t('nav.agentApps'), url: '/admin/agent-apps', icon: Share2 },
],
},
business: {
title: t('nav.businessManagement'),
items: [
{ title: t('nav.finance'), url: '/admin/finance', icon: 'lucide:dollar-sign' },
{ title: t('nav.logs'), url: '/admin/logs', icon: 'lucide:scroll-text' },
{ title: t('nav.tickets'), url: '/admin/tickets', icon: 'lucide:message-square' },
{ title: t('nav.finance'), url: '/admin/finance', icon: DollarSign },
{ title: t('nav.logs'), url: '/admin/logs', icon: ScrollText },
{ title: t('nav.tickets'), url: '/admin/tickets', icon: MessageSquare },
],
},
advanced: {
title: t('nav.advancedFeatures'),
items: [
{ title: t('nav.cloudConstants'), url: '/admin/cloud-constants', icon: 'lucide:file-lock' },
{ title: t('nav.cloudVariables'), url: '/admin/cloud-variables', icon: 'lucide:variable' },
{ title: t('nav.cloudFunction'), url: '/admin/cloud-function', icon: 'lucide:code' },
{ title: t('nav.riskControl'), url: '/admin/risk-control', icon: 'lucide:shield' },
{ title: t('nav.extension'), url: '/admin/extension', icon: 'lucide:plug' },
{ title: t('nav.cloudConstants'), url: '/admin/cloud-constants', icon: FileLock },
{ title: t('nav.cloudVariables'), url: '/admin/cloud-variables', icon: Variable },
{ title: t('nav.cloudFunction'), url: '/admin/cloud-function', icon: Code },
{ title: t('nav.riskControl'), url: '/admin/risk-control', icon: Shield },
{ title: t('nav.extension'), url: '/admin/extension', icon: Plug },
],
},
}))
const publicMenus = computed(() => [
{ title: t('nav.home'), url: '/', icon: 'lucide:home' },
{ title: t('nav.docs'), url: '/docs', icon: 'lucide:book-open' },
{ title: t('nav.pricing'), url: '/pricing', icon: 'lucide:credit-card' },
{ title: t('nav.home'), url: '/', icon: Home },
{ title: t('nav.docs'), url: '/docs', icon: BookOpen },
{ title: t('nav.pricing'), url: '/pricing', icon: CreditCard },
])
function isMenuActive(items: { url: string }[]) {
@@ -207,7 +207,7 @@ watch(route, () => {
:class="isMenuActive(adminMenus.main.items) ? 'text-foreground bg-accent' : 'text-foreground/60'"
>
{{ adminMenus.main.title }}
<Icon icon="lucide:chevron-down" class="h-4 w-4" />
<ChevronDown class="h-4 w-4" />
</button>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent align="start" class="w-40">
@@ -217,7 +217,7 @@ watch(route, () => {
:class="route.path === item.url ? 'bg-accent' : ''"
@click="router.push(item.url)"
>
<Icon :icon="item.icon" class="mr-2 h-4 w-4" />
<component :is="item.icon" class="mr-2 h-4 w-4" />
{{ item.title }}
</UiDropdownMenuItem>
</UiDropdownMenuContent>
@@ -230,7 +230,7 @@ watch(route, () => {
:class="isMenuActive(adminMenus.business.items) ? 'text-foreground bg-accent' : 'text-foreground/60'"
>
{{ adminMenus.business.title }}
<Icon icon="lucide:chevron-down" class="h-4 w-4" />
<ChevronDown class="h-4 w-4" />
</button>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent align="start" class="w-40">
@@ -240,7 +240,7 @@ watch(route, () => {
:class="route.path === item.url ? 'bg-accent' : ''"
@click="router.push(item.url)"
>
<Icon :icon="item.icon" class="mr-2 h-4 w-4" />
<component :is="item.icon" class="mr-2 h-4 w-4" />
{{ item.title }}
</UiDropdownMenuItem>
</UiDropdownMenuContent>
@@ -253,7 +253,7 @@ watch(route, () => {
:class="isMenuActive(adminMenus.advanced.items) ? 'text-foreground bg-accent' : 'text-foreground/60'"
>
{{ adminMenus.advanced.title }}
<Icon icon="lucide:chevron-down" class="h-4 w-4" />
<ChevronDown class="h-4 w-4" />
</button>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent align="start" class="w-40">
@@ -263,7 +263,7 @@ watch(route, () => {
:class="route.path === item.url ? 'bg-accent' : ''"
@click="router.push(item.url)"
>
<Icon :icon="item.icon" class="mr-2 h-4 w-4" />
<component :is="item.icon" class="mr-2 h-4 w-4" />
{{ item.title }}
</UiDropdownMenuItem>
</UiDropdownMenuContent>
@@ -276,7 +276,7 @@ watch(route, () => {
class="md:hidden mr-2 flex items-center justify-center size-9 rounded-md hover:bg-accent"
@click="mobileMenuOpen = true"
>
<Icon icon="lucide:menu" class="size-5" />
<Menu class="size-5" />
</button>
<router-link to="/" class="mr-6 flex items-center space-x-2 md:hidden">
@@ -295,12 +295,10 @@ watch(route, () => {
class="h-9 w-9"
@click="mode = mode === 'dark' ? 'light' : 'dark'"
>
<Icon
icon="lucide:sun"
<Sun
class="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
/>
<Icon
icon="lucide:moon"
<Moon
class="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
/>
<span class="sr-only">切换主题</span>
@@ -331,20 +329,20 @@ watch(route, () => {
</UiDropdownMenuLabel>
<UiDropdownMenuSeparator />
<UiDropdownMenuItem @click="router.push('/profile')">
<Icon icon="lucide:user" class="mr-2 h-4 w-4" />
<User class="mr-2 h-4 w-4" />
<span>{{ t('nav.profile') }}</span>
</UiDropdownMenuItem>
<UiDropdownMenuItem v-if="currentUser?.role === 'admin'" @click="router.push('/admin')">
<Icon icon="lucide:layout-dashboard" class="mr-2 h-4 w-4" />
<LayoutDashboard class="mr-2 h-4 w-4" />
<span>{{ t('nav.adminDashboard') }}</span>
</UiDropdownMenuItem>
<UiDropdownMenuItem v-if="currentUser?.role === 'agent'" @click="router.push('/agent')">
<Icon icon="lucide:layout-dashboard" class="mr-2 h-4 w-4" />
<LayoutDashboard class="mr-2 h-4 w-4" />
<span>{{ t('nav.agentDashboard') }}</span>
</UiDropdownMenuItem>
<UiDropdownMenuSeparator />
<UiDropdownMenuItem @click="handleLogout">
<Icon icon="lucide:log-out" class="mr-2 h-4 w-4" />
<LogOut class="mr-2 h-4 w-4" />
<span>{{ t('nav.logout') }}</span>
</UiDropdownMenuItem>
</UiDropdownMenuContent>
@@ -389,7 +387,7 @@ watch(route, () => {
:class="isActive(item.url) ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent hover:text-foreground'"
@click="navigateTo(item.url)"
>
<Icon :icon="item.icon" class="size-4" />
<component :is="item.icon" class="size-4" />
{{ item.title }}
</button>
</div>
@@ -406,7 +404,7 @@ watch(route, () => {
:class="isActive(item.url) ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent hover:text-foreground'"
@click="navigateTo(item.url)"
>
<Icon :icon="item.icon" class="size-4" />
<component :is="item.icon" class="size-4" />
{{ item.title }}
</button>
</div>
@@ -422,7 +420,7 @@ watch(route, () => {
:class="isActive(item.url) ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent hover:text-foreground'"
@click="navigateTo(item.url)"
>
<Icon :icon="item.icon" class="size-4" />
<component :is="item.icon" class="size-4" />
{{ item.title }}
</button>
</div>
@@ -438,8 +436,8 @@ watch(route, () => {
:class="isActive(item.url) ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent hover:text-foreground'"
@click="navigateTo(item.url)"
>
<Icon :icon="item.icon" class="size-4" />
{{ item.title }}
<component :is="item.icon" class="size-4" />
{{ item.title }}
</button>
</div>
</template>
@@ -474,7 +472,7 @@ watch(route, () => {
</div>
</div>
<UiButton variant="outline" class="w-full mt-2" @click="handleLogout">
<Icon icon="lucide:log-out" class="mr-2 h-4 w-4" />
<LogOut class="mr-2 h-4 w-4" />
{{ t('nav.logout') }}
</UiButton>
</div>