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
+38 -27
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { Activity, ArrowDownLeft, ArrowRight, ArrowUpRight, Calendar, Camera, Copy, Crown, Eye, EyeOff, HardDrive, Key, Loader2, Mail, Receipt, RefreshCw, RotateCcw, Save, Shield, User, Zap } from 'lucide-vue-next'
import { computed, onMounted, ref } from 'vue'
import { toast } from 'vue-sonner'
@@ -346,7 +346,7 @@ onMounted(() => {
<div class="min-h-screen bg-background">
<div class="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8 py-8">
<div v-if="loading" class="flex items-center justify-center py-20">
<Icon icon="lucide:loader-2" class="size-10 animate-spin text-muted-foreground" />
<Loader2 class="size-10 animate-spin text-muted-foreground" />
</div>
<template v-else>
@@ -364,22 +364,22 @@ onMounted(() => {
alt="Avatar"
class="size-full object-cover"
>
<Icon v-else icon="lucide:user" class="size-12 text-muted-foreground" />
<User v-else class="size-12 text-muted-foreground" />
<div class="absolute inset-0 bg-black/50 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<Icon icon="lucide:camera" class="size-8 text-white" />
<Camera class="size-8 text-white" />
</div>
</div>
<div
v-if="uploadingAvatar"
class="absolute inset-0 rounded-2xl bg-black/50 flex items-center justify-center"
>
<Icon icon="lucide:loader-2" class="size-8 animate-spin text-white" />
<Loader2 class="size-8 animate-spin text-white" />
</div>
</div>
</div>
<div class="absolute -bottom-12 right-8 flex items-center gap-2">
<UiButton variant="outline" size="sm" @click="showPasswordDialog = true">
<Icon icon="lucide:key" class="mr-2 h-4 w-4" />
<Key class="mr-2 h-4 w-4" />
修改密码
</UiButton>
</div>
@@ -403,15 +403,15 @@ onMounted(() => {
</div>
<div class="flex items-center gap-4 mt-2 text-sm text-muted-foreground">
<span class="flex items-center gap-1">
<Icon icon="lucide:shield" class="size-4" />
<Shield class="size-4" />
{{ getRoleName(user?.role || '') }}
</span>
<span class="flex items-center gap-1">
<Icon icon="lucide:mail" class="size-4" />
<Mail class="size-4" />
{{ user?.email || '-' }}
</span>
<span class="flex items-center gap-1">
<Icon icon="lucide:calendar" class="size-4" />
<Calendar class="size-4" />
加入于 {{ formatDateShort(user?.created_at || '') }}
</span>
</div>
@@ -422,7 +422,7 @@ onMounted(() => {
<UiCard class="overflow-hidden">
<div class="bg-muted/30 px-6 py-4 border-b">
<h2 class="font-semibold flex items-center gap-2">
<Icon icon="lucide:user" class="size-5 text-primary" />
<User class="size-5 text-primary" />
基本信息
</h2>
</div>
@@ -472,8 +472,8 @@ onMounted(() => {
:disabled="saving || !isProfileFormValid"
@click="handleUpdateProfile"
>
<Icon v-if="saving" icon="lucide:loader-2" class="mr-2 h-4 w-4 animate-spin" />
<Icon v-else icon="lucide:save" class="mr-2 h-4 w-4" />
<Loader2 v-if="saving" class="mr-2 h-4 w-4 animate-spin" />
<Save v-else class="mr-2 h-4 w-4" />
保存修改
</UiButton>
</div>
@@ -483,7 +483,7 @@ onMounted(() => {
<UiCard class="overflow-hidden">
<div class="bg-muted/30 px-6 py-4 border-b">
<h2 class="font-semibold flex items-center gap-2">
<Icon icon="lucide:key" class="size-5 text-primary" />
<Key class="size-5 text-primary" />
API Token
</h2>
</div>
@@ -504,7 +504,8 @@ onMounted(() => {
size="icon"
@click="showApiToken = !showApiToken"
>
<Icon :icon="showApiToken ? 'lucide:eye-off' : 'lucide:eye'" class="size-4" />
<EyeOff v-if="showApiToken" class="size-4" />
<Eye v-else class="size-4" />
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent>
@@ -521,7 +522,7 @@ onMounted(() => {
:disabled="!user?.api_token"
@click="copyApiToken"
>
<Icon icon="lucide:copy" class="size-4" />
<Copy class="size-4" />
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent>
@@ -536,8 +537,8 @@ onMounted(() => {
:disabled="regeneratingToken"
@click="handleRegenerateApiToken"
>
<Icon v-if="regeneratingToken" icon="lucide:loader-2" class="mr-2 h-4 w-4 animate-spin" />
<Icon v-else icon="lucide:refresh-cw" class="mr-2 h-4 w-4" />
<Loader2 v-if="regeneratingToken" class="mr-2 h-4 w-4 animate-spin" />
<RefreshCw v-else class="mr-2 h-4 w-4" />
{{ user?.api_token ? '重新生成' : '生成 Token' }}
</UiButton>
</div>
@@ -548,13 +549,13 @@ onMounted(() => {
<UiCard class="overflow-hidden">
<div class="bg-muted/30 px-6 py-4 border-b flex items-center justify-between">
<h2 class="font-semibold flex items-center gap-2">
<Icon icon="lucide:receipt" class="size-5 text-primary" />
<Receipt class="size-5 text-primary" />
最近交易
</h2>
<UiButton variant="ghost" size="sm" as-child>
<router-link to="/admin/finance" class="text-primary">
查看全部
<Icon icon="lucide:arrow-right" class="ml-1 h-4 w-4" />
<ArrowRight class="ml-1 h-4 w-4" />
</router-link>
</UiButton>
</div>
@@ -571,8 +572,18 @@ onMounted(() => {
class="size-10 rounded-xl flex items-center justify-center"
:class="tx.type === 'recharge' ? 'bg-green-500/10' : tx.type === 'refund' ? 'bg-blue-500/10' : 'bg-red-500/10'"
>
<Icon
:icon="tx.type === 'recharge' ? 'lucide:arrow-down-left' : tx.type === 'refund' ? 'lucide:rotate-ccw' : 'lucide:arrow-up-right'"
<ArrowDownLeft
v-if="tx.type === 'recharge'"
:class="getTransactionTypeColor(tx.type)"
class="size-5"
/>
<RotateCcw
v-else-if="tx.type === 'refund'"
:class="getTransactionTypeColor(tx.type)"
class="size-5"
/>
<ArrowUpRight
v-else
:class="getTransactionTypeColor(tx.type)"
class="size-5"
/>
@@ -592,7 +603,7 @@ onMounted(() => {
</div>
</div>
<div v-else class="text-center py-12 text-muted-foreground">
<Icon icon="lucide:receipt" class="size-16 mx-auto mb-3 opacity-30" />
<Receipt class="size-16 mx-auto mb-3 opacity-30" />
<p>暂无交易记录</p>
</div>
</UiCardContent>
@@ -612,7 +623,7 @@ onMounted(() => {
</p>
</div>
<div class="size-12 rounded-xl bg-primary/10 flex items-center justify-center">
<Icon icon="lucide:crown" class="size-6 text-primary" />
<Crown class="size-6 text-primary" />
</div>
</div>
<p class="text-sm text-muted-foreground mt-2">
@@ -628,7 +639,7 @@ onMounted(() => {
<div class="space-y-2">
<div class="flex justify-between text-sm">
<span class="text-muted-foreground flex items-center gap-1">
<Icon icon="lucide:zap" class="size-4" />
<Zap class="size-4" />
API 调用
</span>
<span class="font-medium">{{ formatNumber(subscription?.api_used || 0) }} / {{ formatNumber(subscription?.api_quota || 10000) }}</span>
@@ -645,7 +656,7 @@ onMounted(() => {
<div class="space-y-2">
<div class="flex justify-between text-sm">
<span class="text-muted-foreground flex items-center gap-1">
<Icon icon="lucide:hard-drive" class="size-4" />
<HardDrive class="size-4" />
存储空间
</span>
<span class="font-medium">{{ formatBytes(subscription?.storage_used || 0) }} / {{ formatBytes(subscription?.storage_quota || 100 * 1024 * 1024) }}</span>
@@ -664,7 +675,7 @@ onMounted(() => {
<UiCard class="overflow-hidden">
<div class="bg-muted/30 px-6 py-4 border-b">
<h2 class="font-semibold flex items-center gap-2">
<Icon icon="lucide:activity" class="size-5 text-primary" />
<Activity class="size-5 text-primary" />
账户统计
</h2>
</div>
@@ -745,7 +756,7 @@ onMounted(() => {
:disabled="changingPassword || !isPasswordFormValid"
@click="handleChangePassword"
>
<Icon v-if="changingPassword" icon="lucide:loader-2" class="mr-2 h-4 w-4 animate-spin" />
<Loader2 v-if="changingPassword" class="mr-2 h-4 w-4 animate-spin" />
确认修改
</UiButton>
</UiDialogFooter>