优化
This commit is contained in:
@@ -66,6 +66,28 @@ function handleReset() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 排序方式 -->
|
||||
<div class="space-y-2">
|
||||
<Label class="text-sm font-medium text-gray-700">排序方式</Label>
|
||||
<RadioGroup v-model="form.sort_type" class="flex gap-2">
|
||||
<div class="inline-flex items-center justify-center px-4 h-9 text-sm font-medium rounded-lg cursor-pointer transition-all"
|
||||
:class="form.sort_type === 0 ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground hover:bg-muted/80'">
|
||||
<RadioGroupItem :value="0" id="sort-default" class="hidden" />
|
||||
<label for="sort-default" class="cursor-pointer">综合排序</label>
|
||||
</div>
|
||||
<div class="inline-flex items-center justify-center px-4 h-9 text-sm font-medium rounded-lg cursor-pointer transition-all"
|
||||
:class="form.sort_type === 1 ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground hover:bg-muted/80'">
|
||||
<RadioGroupItem :value="1" id="sort-likes" class="hidden" />
|
||||
<label for="sort-likes" class="cursor-pointer">最多点赞</label>
|
||||
</div>
|
||||
<div class="inline-flex items-center justify-center px-4 h-9 text-sm font-medium rounded-lg cursor-pointer transition-all"
|
||||
:class="form.sort_type === 2 ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground hover:bg-muted/80'">
|
||||
<RadioGroupItem :value="2" id="sort-latest" class="hidden" />
|
||||
<label for="sort-latest" class="cursor-pointer">最新发布</label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<!-- 分析数量 -->
|
||||
<div class="space-y-2">
|
||||
<Label class="text-sm font-medium text-gray-700">分析数量</Label>
|
||||
|
||||
@@ -58,7 +58,7 @@ function handleSort(key) {
|
||||
}
|
||||
|
||||
// 选择切换
|
||||
function handleSelectAll() {
|
||||
function handleSelectAll(checked) {
|
||||
if (isAllSelected.value) {
|
||||
emit('update:selectedRowKeys', [])
|
||||
} else {
|
||||
@@ -129,12 +129,15 @@ function formatNumber(value) {
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-[40px]">
|
||||
<Checkbox
|
||||
:checked="isAllSelected"
|
||||
@update:checked="handleSelectAll"
|
||||
class="scale-85"
|
||||
/>
|
||||
<TableHead class="w-[60px]">
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox
|
||||
:checked="isAllSelected"
|
||||
@update:checked="handleSelectAll"
|
||||
class="scale-110"
|
||||
/>
|
||||
<span class="text-xs text-muted-foreground">全选</span>
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead
|
||||
v-for="col in columns"
|
||||
@@ -171,11 +174,11 @@ function formatNumber(value) {
|
||||
:key="record.id"
|
||||
class="hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<TableCell class="w-[40px]">
|
||||
<TableCell class="w-[60px]">
|
||||
<Checkbox
|
||||
:checked="selectedRowKeys.includes(String(record.id))"
|
||||
@update:checked="handleSelect(record.id)"
|
||||
class="scale-85"
|
||||
class="scale-110"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
|
||||
@@ -374,7 +374,6 @@ onMounted(() => loadVoiceList())
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<Button @click="handleCreate">
|
||||
<Icon icon="lucide:plus" class="mr-1.5 size-4" />
|
||||
新建配音
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -358,10 +358,10 @@ onMounted(async () => {
|
||||
@border-light: rgba(59, 130, 246, 0.1);
|
||||
@border-medium: rgba(59, 130, 246, 0.2);
|
||||
|
||||
// 蓝紫渐变主题色 (与 VoiceSelector 一致)
|
||||
// 主色 - 使用设计系统变量
|
||||
@accent-blue: #3b82f6;
|
||||
@accent-purple: #8b5cf6;
|
||||
@accent-gradient: linear-gradient(135deg, @accent-blue 0%, @accent-purple 100%);
|
||||
@accent-gradient: var(--primary);
|
||||
@accent-green: #10b981;
|
||||
@accent-red: #ef4444;
|
||||
@accent-orange: #f59e0b;
|
||||
@@ -914,7 +914,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: linear-gradient(135deg, #cbd5e1 0%, #94a3b8 100%);
|
||||
background: var(--muted);
|
||||
cursor: not-allowed;
|
||||
box-shadow: 0 2px 8px rgba(148, 163, 184, 0.25);
|
||||
}
|
||||
|
||||
@@ -19,20 +19,20 @@
|
||||
<div
|
||||
v-for="group in groupList"
|
||||
:key="group.id"
|
||||
class="group flex items-center justify-between px-3 py-2 cursor-pointer rounded-lg mb-0.5 transition-all duration-150"
|
||||
class="group flex items-center justify-between px-4 py-2.5 cursor-pointer rounded-full mb-1 transition-all duration-200"
|
||||
:class="selectedGroupId === group.id
|
||||
? 'bg-primary/10 text-primary'
|
||||
? 'bg-primary/10 text-primary shadow-sm'
|
||||
: 'hover:bg-muted'"
|
||||
@click="handleSelectGroup(group)"
|
||||
>
|
||||
<div class="flex items-center min-w-0 flex-1">
|
||||
<Icon
|
||||
icon="lucide:folder"
|
||||
class="mr-2 text-base shrink-0 transition-colors"
|
||||
class="mr-2.5 text-base shrink-0"
|
||||
:class="selectedGroupId === group.id ? 'text-primary' : 'text-muted-foreground'"
|
||||
/>
|
||||
<template v-if="editingGroupId !== group.id">
|
||||
<span class="text-sm font-medium truncate">{{ group.name }}</span>
|
||||
<span class="text-sm font-medium truncate" :class="selectedGroupId === group.id ? 'text-primary' : 'text-foreground'">{{ group.name }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Input
|
||||
@@ -46,17 +46,19 @@
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex items-center gap-0.5">
|
||||
<div class="flex items-center gap-1">
|
||||
<template v-if="editingGroupId !== group.id">
|
||||
<span class="text-xs text-muted-foreground mr-2">{{ group.fileCount }}</span>
|
||||
<span class="text-xs mr-1" :class="selectedGroupId === group.id ? 'text-primary/80' : 'text-muted-foreground'">{{ group.fileCount }}</span>
|
||||
<Icon
|
||||
icon="lucide:pencil"
|
||||
class="opacity-0 group-hover:opacity-100 p-1 cursor-pointer text-muted-foreground hover:text-primary text-xs transition-all"
|
||||
class="opacity-0 group-hover:opacity-100 p-1 cursor-pointer text-xs transition-all"
|
||||
:class="selectedGroupId === group.id ? 'text-primary/70 hover:text-primary' : 'text-muted-foreground hover:text-primary'"
|
||||
@click.stop="handleEditGroup(group, $event)"
|
||||
/>
|
||||
<Icon
|
||||
icon="lucide:trash-2"
|
||||
class="opacity-0 group-hover:opacity-100 p-1 cursor-pointer text-muted-foreground hover:text-destructive text-xs transition-all"
|
||||
class="opacity-0 group-hover:opacity-100 p-1 cursor-pointer text-xs transition-all"
|
||||
:class="selectedGroupId === group.id ? 'text-primary/70 hover:text-destructive' : 'text-muted-foreground hover:text-destructive'"
|
||||
@click.stop="handleDeleteGroup(group, $event)"
|
||||
/>
|
||||
</template>
|
||||
@@ -72,25 +74,25 @@
|
||||
<!-- 顶部工具栏 -->
|
||||
<div class="flex items-center justify-between px-6 py-4 bg-card border-b border-border gap-6">
|
||||
<!-- 分类切换器 - 胶囊式设计 -->
|
||||
<div class="flex bg-muted rounded-lg p-1 gap-1">
|
||||
<div class="flex bg-muted/50 rounded-full p-1 gap-1">
|
||||
<button
|
||||
class="flex items-center gap-2 px-5 py-2 rounded-lg cursor-pointer text-sm font-medium transition-all duration-200"
|
||||
class="flex items-center gap-2 px-6 py-2 rounded-full cursor-pointer text-sm font-medium transition-all duration-200"
|
||||
:class="activeCategory === 'MIX'
|
||||
? 'bg-card text-primary shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground'"
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-muted'"
|
||||
@click="handleCategoryChange('MIX')"
|
||||
>
|
||||
<Icon icon="lucide:video" class="text-base" :class="activeCategory === 'MIX' ? 'text-primary' : 'text-muted-foreground'" />
|
||||
<Icon icon="lucide:video" class="text-base" />
|
||||
<span>混剪素材</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center gap-2 px-5 py-2 rounded-lg cursor-pointer text-sm font-medium transition-all duration-200"
|
||||
class="flex items-center gap-2 px-6 py-2 rounded-full cursor-pointer text-sm font-medium transition-all duration-200"
|
||||
:class="activeCategory === 'DIGITAL_HUMAN'
|
||||
? 'bg-card text-primary shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground'"
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-muted'"
|
||||
@click="handleCategoryChange('DIGITAL_HUMAN')"
|
||||
>
|
||||
<Icon icon="lucide:user" class="text-base" :class="activeCategory === 'DIGITAL_HUMAN' ? 'text-primary' : 'text-muted-foreground'" />
|
||||
<Icon icon="lucide:user" class="text-base" />
|
||||
<span>数字人素材</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -165,7 +167,7 @@
|
||||
v-for="file in fileList"
|
||||
:key="file.id"
|
||||
:data-file-id="file.id"
|
||||
class="group relative bg-card border border-border rounded-xl overflow-hidden cursor-pointer transition-all duration-300 hover:border-primary hover:shadow-md hover:-translate-y-0.5"
|
||||
class="group relative bg-card border border-border rounded-2xl overflow-hidden cursor-pointer transition-all duration-300 hover:border-primary hover:shadow-lg hover:-translate-y-1"
|
||||
:class="{ 'border-primary shadow-[0_0_0_2px_rgba(59,130,246,0.2),var(--shadow-md)]': selectedFileIds.includes(file.id) }"
|
||||
@click="handleFileClick(file)"
|
||||
>
|
||||
@@ -256,7 +258,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<div class="flex items-center justify-between px-6 py-3 bg-card border-t border-border">
|
||||
<div class="flex items-center justify-between px-6 py-3 border-t border-border">
|
||||
<div class="flex items-center gap-4">
|
||||
<Checkbox
|
||||
:checked="selectedFileIds.length === fileList.length && fileList.length > 0"
|
||||
@@ -828,9 +830,9 @@ onMounted(async () => {
|
||||
.material-list-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
background: var(--color-gray-50);
|
||||
padding: var(--space-5);
|
||||
gap: var(--space-5);
|
||||
background: var(--background);
|
||||
padding: var(--space-4);
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
|
||||
@@ -388,7 +388,7 @@ onMounted(() => {
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">排序</span>
|
||||
<Select v-model="searchParams.sort_type">
|
||||
<SelectTrigger class="h-[26px] w-[88px]">
|
||||
<SelectTrigger class="h-[26px] w-[160px]">
|
||||
<SelectValue placeholder="综合" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -401,7 +401,7 @@ onMounted(() => {
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">时间</span>
|
||||
<Select v-model="searchParams.publish_time">
|
||||
<SelectTrigger class="h-[26px] w-[88px]">
|
||||
<SelectTrigger class="h-[26px] w-[160px]">
|
||||
<SelectValue placeholder="不限" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -415,7 +415,7 @@ onMounted(() => {
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">时长</span>
|
||||
<Select v-model="searchParams.filter_duration">
|
||||
<SelectTrigger class="h-[26px] w-[88px]">
|
||||
<SelectTrigger class="h-[26px] w-[160px]">
|
||||
<SelectValue placeholder="不限" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -677,7 +677,7 @@ onMounted(() => {
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary-500);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
box-shadow: 0 0 0 3px oklch(from var(--primary) l c h / 0.1);
|
||||
}
|
||||
&:disabled { opacity: 0.5; }
|
||||
}
|
||||
@@ -687,10 +687,10 @@ onMounted(() => {
|
||||
height: 38px;
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
color: var(--primary-foreground);
|
||||
background: var(--color-primary-500);
|
||||
border: none;
|
||||
border-radius: var(--radius-base);
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -705,13 +705,13 @@ onMounted(() => {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
border-top-color: #fff;
|
||||
border-top-color: var(--primary-foreground);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
|
||||
&.light {
|
||||
border-color: rgba(255,255,255,0.2);
|
||||
border-top-color: #fff;
|
||||
border-top-color: var(--primary-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,7 +741,7 @@ onMounted(() => {
|
||||
|
||||
&::-webkit-scrollbar { width: 4px; }
|
||||
&::-webkit-scrollbar-track { background: transparent; }
|
||||
&::-webkit-scrollbar-thumb { background: var(--color-gray-300); border-radius: 2px; }
|
||||
&::-webkit-scrollbar-thumb { background: var(--color-gray-300); border-radius: var(--radius-sm); }
|
||||
}
|
||||
|
||||
// 空状态
|
||||
@@ -814,7 +814,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: rgba(59, 130, 246, 0.08);
|
||||
background: oklch(from var(--primary) l c h / 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -854,9 +854,9 @@ onMounted(() => {
|
||||
padding: 0 var(--space-1);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 3px;
|
||||
color: var(--primary-foreground);
|
||||
background: var(--muted);
|
||||
border-radius: var(--radius-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -928,7 +928,7 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-primary-500);
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
background: oklch(from var(--primary) l c h / 0.1);
|
||||
border: none;
|
||||
border-radius: var(--radius-base);
|
||||
cursor: pointer;
|
||||
@@ -938,7 +938,7 @@ onMounted(() => {
|
||||
|
||||
&:hover {
|
||||
background: var(--color-primary-500);
|
||||
color: #fff;
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -953,7 +953,7 @@ onMounted(() => {
|
||||
|
||||
&::-webkit-scrollbar { width: 4px; }
|
||||
&::-webkit-scrollbar-track { background: transparent; }
|
||||
&::-webkit-scrollbar-thumb { background: var(--color-gray-300); border-radius: 2px; }
|
||||
&::-webkit-scrollbar-thumb { background: var(--color-gray-300); border-radius: var(--radius-sm); }
|
||||
}
|
||||
|
||||
.form-block {
|
||||
@@ -1031,13 +1031,13 @@ onMounted(() => {
|
||||
color: var(--color-gray-600);
|
||||
background: var(--color-gray-50);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-base);
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
transition: all var(--duration-fast);
|
||||
|
||||
&:hover { border-color: var(--color-gray-200); color: var(--color-gray-900); }
|
||||
&.active {
|
||||
color: #fff;
|
||||
color: var(--primary-foreground);
|
||||
background: var(--color-primary-500);
|
||||
border-color: var(--color-primary-500);
|
||||
}
|
||||
@@ -1056,7 +1056,7 @@ onMounted(() => {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: var(--color-gray-50);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--radius-sm);
|
||||
outline: none;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
@@ -1070,7 +1070,7 @@ onMounted(() => {
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.15);
|
||||
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.15);
|
||||
box-shadow: 0 0 0 4px oklch(from var(--primary) l c h / 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1081,10 +1081,10 @@ onMounted(() => {
|
||||
height: 44px;
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
color: var(--primary-foreground);
|
||||
background: var(--color-primary-500);
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1098,7 +1098,7 @@ onMounted(() => {
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--color-primary-400);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 6px 20px rgba(59, 130, 246, 0.15);
|
||||
box-shadow: 0 6px 20px oklch(from var(--primary) l c h / 0.15);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@@ -1172,7 +1172,7 @@ onMounted(() => {
|
||||
|
||||
&::-webkit-scrollbar { width: 4px; }
|
||||
&::-webkit-scrollbar-track { background: transparent; }
|
||||
&::-webkit-scrollbar-thumb { background: var(--color-gray-300); border-radius: 2px; }
|
||||
&::-webkit-scrollbar-thumb { background: var(--color-gray-300); border-radius: var(--radius-sm); }
|
||||
}
|
||||
|
||||
// 过渡动画
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, reactive } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { getPointRecordPage } from '@/api/pointRecord'
|
||||
import { redeemCode as redeemCodeApi } from '@/api/redeemCode'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
DialogDescription
|
||||
} from '@/components/ui/dialog'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 兑换码相关
|
||||
const showRedeemDialog = ref(false)
|
||||
const redeemCodeInput = ref('')
|
||||
const redeeming = ref(false)
|
||||
|
||||
// 积分记录数据
|
||||
const pointRecords = ref([])
|
||||
const recordsLoading = ref(false)
|
||||
@@ -129,6 +145,31 @@ function getStatusInfo(status) {
|
||||
return statusMap[status] || { text: status, color: 'default' }
|
||||
}
|
||||
|
||||
// 兑换码兑换
|
||||
async function handleRedeem() {
|
||||
const code = redeemCodeInput.value.trim()
|
||||
if (!code) {
|
||||
toast.error('请输入兑换码')
|
||||
return
|
||||
}
|
||||
|
||||
redeeming.value = true
|
||||
try {
|
||||
await redeemCodeApi(code)
|
||||
toast.success('兑换成功,积分已到账')
|
||||
showRedeemDialog.value = false
|
||||
redeemCodeInput.value = ''
|
||||
// 刷新用户信息和积分记录
|
||||
await userStore.fetchUserProfile()
|
||||
await fetchPointRecords()
|
||||
} catch (e) {
|
||||
const msg = e?.response?.data?.msg || e?.message || '兑换失败'
|
||||
toast.error(msg)
|
||||
} finally {
|
||||
redeeming.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (userStore.isLoggedIn) {
|
||||
// 获取用户基本信息和档案信息
|
||||
@@ -186,7 +227,7 @@ onMounted(async () => {
|
||||
<!-- 右侧:资源统计与活动 -->
|
||||
<div class="lg:col-span-8">
|
||||
<!-- 资源概览卡片 -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<!-- 存储空间 -->
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon-wrapper blue">
|
||||
@@ -222,6 +263,26 @@ onMounted(async () => {
|
||||
<div class="stat-desc">累计充值金额</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 兑换码充值 -->
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon-wrapper green">
|
||||
<Icon icon="lucide:gift" class="text-2xl" />
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">兑换码充值</div>
|
||||
<div class="stat-value" style="font-size: 16px;">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@click="showRedeemDialog = true"
|
||||
>
|
||||
输入兑换码
|
||||
</Button>
|
||||
</div>
|
||||
<div class="stat-desc">使用兑换码获取积分</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 积分记录 -->
|
||||
@@ -291,6 +352,34 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 兑换码弹窗 -->
|
||||
<Dialog v-model:open="showRedeemDialog">
|
||||
<DialogContent class="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>兑换码充值</DialogTitle>
|
||||
<DialogDescription>
|
||||
输入您的兑换码,积分将立即到账
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="py-4">
|
||||
<Input
|
||||
v-model="redeemCodeInput"
|
||||
placeholder="请输入兑换码"
|
||||
class="w-full"
|
||||
@keyup.enter="handleRedeem"
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="showRedeemDialog = false">
|
||||
取消
|
||||
</Button>
|
||||
<Button @click="handleRedeem" :disabled="redeeming">
|
||||
{{ redeeming ? '兑换中...' : '立即兑换' }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -430,6 +519,11 @@ onMounted(async () => {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.stat-icon-wrapper.green {
|
||||
background: rgba(82, 196, 26, 0.1);
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: var(--muted-foreground);
|
||||
|
||||
Reference in New Issue
Block a user