|
@@ -11,166 +11,387 @@
|
|
|
type="text/css"
|
|
|
/>
|
|
|
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.5.13/vue.global.min.js"></script>
|
|
|
+ <link
|
|
|
+ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
|
|
|
+ rel="stylesheet"
|
|
|
+ />
|
|
|
<style>
|
|
|
+ @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap");
|
|
|
+
|
|
|
+ * {
|
|
|
+ font-family: "Inter", sans-serif;
|
|
|
+ }
|
|
|
+
|
|
|
.gradient-bg {
|
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
}
|
|
|
+
|
|
|
+ .gradient-bg-2 {
|
|
|
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
|
+ }
|
|
|
+
|
|
|
+ .gradient-bg-3 {
|
|
|
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
|
|
+ }
|
|
|
+
|
|
|
.card-hover {
|
|
|
- transition: all 0.3s ease;
|
|
|
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
}
|
|
|
+
|
|
|
.card-hover:hover {
|
|
|
- transform: translateY(-2px);
|
|
|
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
|
|
+ transform: translateY(-4px);
|
|
|
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
|
|
}
|
|
|
+
|
|
|
.scrollbar-thin {
|
|
|
scrollbar-width: thin;
|
|
|
scrollbar-color: #cbd5e0 #f7fafc;
|
|
|
}
|
|
|
+
|
|
|
.scrollbar-thin::-webkit-scrollbar {
|
|
|
width: 6px;
|
|
|
}
|
|
|
+
|
|
|
.scrollbar-thin::-webkit-scrollbar-track {
|
|
|
background: #f7fafc;
|
|
|
}
|
|
|
+
|
|
|
.scrollbar-thin::-webkit-scrollbar-thumb {
|
|
|
background: #cbd5e0;
|
|
|
border-radius: 3px;
|
|
|
}
|
|
|
+
|
|
|
+ .animate-pulse-slow {
|
|
|
+ animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ .animate-bounce-slow {
|
|
|
+ animation: bounce 2s infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ .glass-effect {
|
|
|
+ background: rgba(255, 255, 255, 0.25);
|
|
|
+ backdrop-filter: blur(10px);
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.18);
|
|
|
+ }
|
|
|
+
|
|
|
+ .neon-glow {
|
|
|
+ box-shadow: 0 0 20px rgba(102, 126, 234, 0.5);
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating {
|
|
|
+ animation: floating 3s ease-in-out infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes floating {
|
|
|
+ 0% {
|
|
|
+ transform: translateY(0px);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ transform: translateY(-10px);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ transform: translateY(0px);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .shimmer {
|
|
|
+ background: linear-gradient(
|
|
|
+ 90deg,
|
|
|
+ transparent,
|
|
|
+ rgba(255, 255, 255, 0.4),
|
|
|
+ transparent
|
|
|
+ );
|
|
|
+ background-size: 200% 100%;
|
|
|
+ animation: shimmer 2s infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes shimmer {
|
|
|
+ 0% {
|
|
|
+ background-position: -200% 0;
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ background-position: 200% 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-card {
|
|
|
+ background: linear-gradient(
|
|
|
+ 135deg,
|
|
|
+ rgba(255, 255, 255, 0.1) 0%,
|
|
|
+ rgba(255, 255, 255, 0.05) 100%
|
|
|
+ );
|
|
|
+ backdrop-filter: blur(10px);
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
+ }
|
|
|
+
|
|
|
+ .reward-item {
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ color: white;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .reward-item:hover {
|
|
|
+ transform: scale(1.05);
|
|
|
+ box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
|
|
|
+ }
|
|
|
+
|
|
|
+ .address-item {
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .address-item:hover {
|
|
|
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
|
+ color: white;
|
|
|
+ transform: translateX(5px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-glow {
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-glow::before {
|
|
|
+ content: "";
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: -100%;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(
|
|
|
+ 90deg,
|
|
|
+ transparent,
|
|
|
+ rgba(255, 255, 255, 0.4),
|
|
|
+ transparent
|
|
|
+ );
|
|
|
+ transition: left 0.5s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-glow:hover::before {
|
|
|
+ left: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-icon {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+ transition: transform 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-icon:hover {
|
|
|
+ transform: scale(1.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .icon-container {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: rgba(255, 255, 255, 0.1);
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
+ border-radius: 50%;
|
|
|
+ overflow: hidden;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .icon-container:hover {
|
|
|
+ background: rgba(255, 255, 255, 0.2);
|
|
|
+ border-color: rgba(255, 255, 255, 0.4);
|
|
|
+ }
|
|
|
</style>
|
|
|
</head>
|
|
|
- <body class="min-h-screen bg-gradient-to-br from-blue-50 to-purple-50">
|
|
|
+ <body
|
|
|
+ class="min-h-screen bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-900"
|
|
|
+ >
|
|
|
<div id="app" class="container mx-auto p-4 md:p-8">
|
|
|
+ <!-- Animated Background -->
|
|
|
+ <div class="fixed inset-0 overflow-hidden pointer-events-none">
|
|
|
+ <div
|
|
|
+ class="absolute -top-40 -right-40 w-80 h-80 bg-purple-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-bounce-slow"
|
|
|
+ ></div>
|
|
|
+ <div
|
|
|
+ class="absolute -bottom-40 -left-40 w-80 h-80 bg-pink-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-bounce-slow"
|
|
|
+ style="animation-delay: 1s"
|
|
|
+ ></div>
|
|
|
+ <div
|
|
|
+ class="absolute top-40 left-40 w-80 h-80 bg-blue-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-bounce-slow"
|
|
|
+ style="animation-delay: 2s"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<!-- Header -->
|
|
|
- <div class="text-center mb-8">
|
|
|
- <h1 class="text-4xl font-bold text-gray-800 mb-2">POG Airdrop</h1>
|
|
|
- <p class="text-gray-600">批量奖励发放系统</p>
|
|
|
+ <div class="text-center mb-12 relative z-10">
|
|
|
+ <div class="floating mb-4">
|
|
|
+ <i class="fas fa-rocket text-6xl text-white mb-4"></i>
|
|
|
+ </div>
|
|
|
+ <h1 class="text-5xl font-bold text-white mb-4 neon-glow">
|
|
|
+ POG Airdrop
|
|
|
+ </h1>
|
|
|
+ <p class="text-xl text-purple-200 shimmer">
|
|
|
+ Mass Reward Distribution System
|
|
|
+ </p>
|
|
|
+ <div class="flex justify-center mt-6 space-x-4">
|
|
|
+ <div class="stat-card rounded-lg p-4 text-white">
|
|
|
+ <div class="text-2xl font-bold">{{ addressList.length }}</div>
|
|
|
+ <div class="text-sm opacity-80">Addresses</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card rounded-lg p-4 text-white">
|
|
|
+ <div class="text-2xl font-bold">{{ totalRewardCount }}</div>
|
|
|
+ <div class="text-sm opacity-80">Rewards</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card rounded-lg p-4 text-white">
|
|
|
+ <div class="text-2xl font-bold">
|
|
|
+ {{ totalRewardCount * addressList.length }}
|
|
|
+ </div>
|
|
|
+ <div class="text-sm opacity-80">Total</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
|
- <!-- 左侧:奖励管理 -->
|
|
|
- <div class="space-y-6">
|
|
|
- <!-- 等待发送的奖励列表 -->
|
|
|
- <div class="card bg-white shadow-lg card-hover">
|
|
|
- <div class="card-body">
|
|
|
- <div class="flex justify-between items-center mb-4">
|
|
|
- <h2 class="card-title text-xl">
|
|
|
- <span class="text-primary">📦</span>
|
|
|
- 奖励管理
|
|
|
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 relative z-10">
|
|
|
+ <!-- Left: Reward Management -->
|
|
|
+ <div class="space-y-8">
|
|
|
+ <!-- Reward Management -->
|
|
|
+ <div class="glass-effect rounded-2xl shadow-2xl card-hover">
|
|
|
+ <div class="p-6">
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
+ <h2 class="text-2xl font-bold text-white">
|
|
|
+ <i class="fas fa-gift text-purple-300 mr-3"></i>
|
|
|
+ Reward Management
|
|
|
</h2>
|
|
|
- <div class="badge badge-primary">
|
|
|
- {{ waitAddRewardList.length }} 种
|
|
|
+ <div class="badge badge-primary badge-lg animate-pulse-slow">
|
|
|
+ {{ waitAddRewardList.length }} Types
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
v-if="waitAddRewardList.length === 0"
|
|
|
- class="text-center py-8"
|
|
|
+ class="text-center py-12"
|
|
|
>
|
|
|
- <div class="text-6xl mb-4">🎁</div>
|
|
|
- <p class="text-gray-500">暂无奖励,点击下方按钮添加</p>
|
|
|
+ <div class="text-8xl mb-6 animate-bounce-slow">🎁</div>
|
|
|
+ <p class="text-purple-200 text-lg">No rewards added yet</p>
|
|
|
+ <p class="text-purple-300 text-sm mt-2">
|
|
|
+ Click the button below to add rewards
|
|
|
+ </p>
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
v-else
|
|
|
- class="space-y-3 max-h-64 overflow-y-auto scrollbar-thin"
|
|
|
+ class="space-y-4 max-h-80 overflow-y-auto scrollbar-thin"
|
|
|
>
|
|
|
<div
|
|
|
v-for="reward in waitAddRewardList"
|
|
|
:key="reward.id"
|
|
|
- class="flex justify-between items-center p-3 bg-base-100 rounded-lg border"
|
|
|
+ class="reward-item rounded-xl p-4 border border-white/20"
|
|
|
>
|
|
|
- <div class="flex items-center space-x-3">
|
|
|
- <div
|
|
|
- class="w-8 h-8 bg-primary rounded-full flex items-center justify-center text-white text-sm font-bold"
|
|
|
- >
|
|
|
- {{ getItemName(reward.id).charAt(0) }}
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <div class="font-medium">
|
|
|
- {{ getItemName(reward.id) }}
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div class="flex items-center space-x-4">
|
|
|
+ <div class="w-12 h-12 icon-container backdrop-blur-sm">
|
|
|
+ <img
|
|
|
+ :src="getItemIcon(reward.id)"
|
|
|
+ :alt="getItemName(reward.id)"
|
|
|
+ class="w-8 h-8 item-icon"
|
|
|
+ @error="handleImageError"
|
|
|
+ />
|
|
|
</div>
|
|
|
- <div class="text-sm text-gray-500">
|
|
|
- 每个地址 {{ reward.count }} 个
|
|
|
+ <div>
|
|
|
+ <div class="font-semibold text-lg">
|
|
|
+ {{ getItemName(reward.id) }}
|
|
|
+ </div>
|
|
|
+ <div class="text-sm opacity-80">
|
|
|
+ {{ reward.count }} per address
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- <div class="flex items-center space-x-2">
|
|
|
- <input
|
|
|
- type="number"
|
|
|
- v-model="reward.count"
|
|
|
- min="1"
|
|
|
- class="input input-bordered input-sm w-16 text-center"
|
|
|
- @change="updateRewardCount(reward.id, reward.count)"
|
|
|
- />
|
|
|
- <button
|
|
|
- class="btn btn-sm btn-error btn-circle"
|
|
|
- @click="removeWaitReward(reward.id)"
|
|
|
- >
|
|
|
- ✕
|
|
|
- </button>
|
|
|
+ <div class="flex items-center space-x-3">
|
|
|
+ <input
|
|
|
+ type="number"
|
|
|
+ v-model="reward.count"
|
|
|
+ min="1"
|
|
|
+ class="input input-bordered input-sm w-20 text-center bg-white/20 border-white/30 text-white placeholder-white/50"
|
|
|
+ @change="updateRewardCount(reward.id, reward.count)"
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ class="btn btn-circle btn-sm bg-red-500 border-0 hover:bg-red-600"
|
|
|
+ @click="removeWaitReward(reward.id)"
|
|
|
+ >
|
|
|
+ <i class="fas fa-times"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="card-actions justify-center mt-4">
|
|
|
+ <div class="text-center mt-6">
|
|
|
<button
|
|
|
- class="btn btn-primary"
|
|
|
+ class="btn btn-primary btn-lg gradient-bg-2 border-0 text-white shadow-lg btn-glow"
|
|
|
@click="showAddRewardDialog = true"
|
|
|
>
|
|
|
- <span class="mr-2">➕</span>
|
|
|
- 添加奖励
|
|
|
+ <i class="fas fa-plus mr-2"></i>
|
|
|
+ Add Reward
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 发送预览 -->
|
|
|
- <div class="card bg-white shadow-lg card-hover">
|
|
|
- <div class="card-body">
|
|
|
- <h2 class="card-title text-xl mb-4">
|
|
|
- <span class="text-primary">📊</span>
|
|
|
- 发送预览
|
|
|
+ <!-- Send Preview -->
|
|
|
+ <div class="glass-effect rounded-2xl shadow-2xl card-hover">
|
|
|
+ <div class="p-6">
|
|
|
+ <h2 class="text-2xl font-bold text-white mb-6">
|
|
|
+ <i class="fas fa-chart-line text-blue-300 mr-3"></i>
|
|
|
+ Send Preview
|
|
|
</h2>
|
|
|
|
|
|
- <!-- 总体统计 -->
|
|
|
- <div class="grid grid-cols-3 gap-4 mb-6">
|
|
|
- <div class="stat bg-base-100 rounded-lg p-4 text-center">
|
|
|
- <div class="stat-title text-sm">地址数</div>
|
|
|
- <div class="stat-value text-2xl text-primary">
|
|
|
+ <!-- Statistics -->
|
|
|
+ <div class="grid grid-cols-3 gap-4 mb-8">
|
|
|
+ <div class="stat-card rounded-xl p-4 text-center">
|
|
|
+ <div class="text-3xl font-bold text-white mb-1">
|
|
|
{{ addressList.length }}
|
|
|
</div>
|
|
|
+ <div class="text-sm text-purple-200">Addresses</div>
|
|
|
</div>
|
|
|
- <div class="stat bg-base-100 rounded-lg p-4 text-center">
|
|
|
- <div class="stat-title text-sm">奖励数</div>
|
|
|
- <div class="stat-value text-2xl text-secondary">
|
|
|
+ <div class="stat-card rounded-xl p-4 text-center">
|
|
|
+ <div class="text-3xl font-bold text-white mb-1">
|
|
|
{{ totalRewardCount }}
|
|
|
</div>
|
|
|
+ <div class="text-sm text-purple-200">Rewards</div>
|
|
|
</div>
|
|
|
- <div class="stat bg-base-100 rounded-lg p-4 text-center">
|
|
|
- <div class="stat-title text-sm">总发送</div>
|
|
|
- <div class="stat-value text-2xl text-accent">
|
|
|
+ <div class="stat-card rounded-xl p-4 text-center">
|
|
|
+ <div class="text-3xl font-bold text-white mb-1">
|
|
|
{{ totalRewardCount * addressList.length }}
|
|
|
</div>
|
|
|
+ <div class="text-sm text-purple-200">Total</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 物品汇总 -->
|
|
|
+ <!-- Item Summary -->
|
|
|
<div v-if="waitAddRewardList.length > 0">
|
|
|
- <h3 class="text-lg font-semibold mb-3">物品汇总</h3>
|
|
|
- <div class="space-y-2 max-h-48 overflow-y-auto scrollbar-thin">
|
|
|
+ <h3 class="text-xl font-semibold text-white mb-4">
|
|
|
+ Item Summary
|
|
|
+ </h3>
|
|
|
+ <div class="space-y-3 max-h-60 overflow-y-auto scrollbar-thin">
|
|
|
<div
|
|
|
v-for="reward in waitAddRewardList"
|
|
|
:key="reward.id"
|
|
|
- class="flex justify-between items-center p-2 bg-base-100 rounded"
|
|
|
+ class="flex justify-between items-center p-3 bg-white/10 rounded-lg backdrop-blur-sm"
|
|
|
>
|
|
|
- <span class="font-medium"
|
|
|
- >{{ getItemName(reward.id) }}</span
|
|
|
- >
|
|
|
+ <div class="flex items-center space-x-3">
|
|
|
+ <div class="w-8 h-8 icon-container">
|
|
|
+ <img
|
|
|
+ :src="getItemIcon(reward.id)"
|
|
|
+ :alt="getItemName(reward.id)"
|
|
|
+ class="w-6 h-6 item-icon"
|
|
|
+ @error="handleImageError"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <span class="font-medium text-white"
|
|
|
+ >{{ getItemName(reward.id) }}</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
<div class="flex items-center space-x-2">
|
|
|
- <span class="text-sm text-gray-600"
|
|
|
+ <span class="text-sm text-purple-200"
|
|
|
>{{ reward.count }} × {{ addressList.length }}</span
|
|
|
>
|
|
|
- <span class="font-bold text-primary"
|
|
|
+ <span class="font-bold text-yellow-300"
|
|
|
>= {{ reward.count * addressList.length }}</span
|
|
|
>
|
|
|
</div>
|
|
@@ -181,97 +402,113 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 右侧:地址管理 -->
|
|
|
- <div class="space-y-6">
|
|
|
- <!-- 钱包地址输入 -->
|
|
|
- <div class="card bg-white shadow-lg card-hover">
|
|
|
- <div class="card-body">
|
|
|
- <h2 class="card-title text-xl mb-4">
|
|
|
- <span class="text-primary">👥</span>
|
|
|
- 地址管理
|
|
|
+ <!-- Right: Address Management -->
|
|
|
+ <div class="space-y-8">
|
|
|
+ <!-- Address Input -->
|
|
|
+ <div class="glass-effect rounded-2xl shadow-2xl card-hover">
|
|
|
+ <div class="p-6">
|
|
|
+ <h2 class="text-2xl font-bold text-white mb-6">
|
|
|
+ <i class="fas fa-users text-green-300 mr-3"></i>
|
|
|
+ Address Management
|
|
|
</h2>
|
|
|
|
|
|
<div class="form-control">
|
|
|
<label class="label">
|
|
|
- <span class="label-text font-medium">钱包地址列表</span>
|
|
|
- <span class="label-text-alt text-primary"
|
|
|
- >{{ addressList.length }} 个地址</span
|
|
|
+ <span class="label-text font-medium text-white"
|
|
|
+ >Wallet Address List</span
|
|
|
+ >
|
|
|
+ <span class="label-text-alt text-purple-300"
|
|
|
+ >{{ addressList.length }} addresses</span
|
|
|
>
|
|
|
</label>
|
|
|
<textarea
|
|
|
v-model="waitInputAddresses"
|
|
|
- rows="6"
|
|
|
- placeholder="请输入钱包地址,每行一个地址 例如: 0x1234567890abcdef... 0xabcdef1234567890..."
|
|
|
- class="textarea textarea-bordered w-full font-mono text-sm"
|
|
|
+ rows="8"
|
|
|
+ placeholder="Enter wallet addresses, one per line Example: 0x1234567890abcdef... 0xabcdef1234567890..."
|
|
|
+ class="textarea textarea-bordered w-full font-mono text-sm bg-white/10 border-white/30 text-white placeholder-white/50"
|
|
|
@input="parseAddresses"
|
|
|
></textarea>
|
|
|
<label class="label">
|
|
|
- <span class="label-text-alt text-gray-500"
|
|
|
- >支持批量粘贴,每行一个地址</span
|
|
|
+ <span class="label-text-alt text-purple-200"
|
|
|
+ >Support batch paste, one address per line</span
|
|
|
>
|
|
|
</label>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 快速操作 -->
|
|
|
- <div class="flex space-x-2 mt-4">
|
|
|
- <button class="btn btn-sm btn-outline" @click="clearAddresses">
|
|
|
- 🗑️ 清空
|
|
|
+ <!-- Quick Actions -->
|
|
|
+ <div class="flex flex-wrap gap-2 mt-6">
|
|
|
+ <button
|
|
|
+ class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
|
|
|
+ @click="clearAddresses"
|
|
|
+ >
|
|
|
+ <i class="fas fa-trash mr-1"></i> Clear
|
|
|
</button>
|
|
|
- <button class="btn btn-sm btn-outline" @click="exportAddresses">
|
|
|
- 📋 导出
|
|
|
+ <button
|
|
|
+ class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
|
|
|
+ @click="exportAddresses"
|
|
|
+ >
|
|
|
+ <i class="fas fa-download mr-1"></i> Export
|
|
|
</button>
|
|
|
- <button class="btn btn-sm btn-outline" @click="importAddresses">
|
|
|
- 📥 导入
|
|
|
+ <button
|
|
|
+ class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
|
|
|
+ @click="importAddresses"
|
|
|
+ >
|
|
|
+ <i class="fas fa-upload mr-1"></i> Import
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 地址列表显示 -->
|
|
|
- <div class="card bg-white shadow-lg card-hover">
|
|
|
- <div class="card-body">
|
|
|
- <div class="flex justify-between items-center mb-4">
|
|
|
- <h2 class="card-title text-xl">
|
|
|
- <span class="text-primary">📋</span>
|
|
|
- 地址列表
|
|
|
+ <!-- Address List -->
|
|
|
+ <div class="glass-effect rounded-2xl shadow-2xl card-hover">
|
|
|
+ <div class="p-6">
|
|
|
+ <div class="flex justify-between items-center mb-6">
|
|
|
+ <h2 class="text-2xl font-bold text-white">
|
|
|
+ <i class="fas fa-list text-orange-300 mr-3"></i>
|
|
|
+ Address List
|
|
|
</h2>
|
|
|
- <div class="flex items-center space-x-2">
|
|
|
+ <div class="flex items-center space-x-3">
|
|
|
<input
|
|
|
type="text"
|
|
|
v-model="searchAddress"
|
|
|
- placeholder="搜索地址..."
|
|
|
- class="input input-bordered input-sm w-40"
|
|
|
+ placeholder="Search addresses..."
|
|
|
+ class="input input-bordered input-sm w-48 bg-white/10 border-white/30 text-white placeholder-white/50"
|
|
|
/>
|
|
|
- <div class="badge badge-outline">
|
|
|
+ <div class="badge badge-outline border-white/30 text-white">
|
|
|
{{ filteredAddresses.length }}/{{ addressList.length }}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div v-if="addressList.length === 0" class="text-center py-8">
|
|
|
- <div class="text-6xl mb-4">👤</div>
|
|
|
- <p class="text-gray-500">暂无地址,请在上方输入地址</p>
|
|
|
+ <div v-if="addressList.length === 0" class="text-center py-12">
|
|
|
+ <div class="text-8xl mb-6 animate-bounce-slow">👤</div>
|
|
|
+ <p class="text-purple-200 text-lg">No addresses yet</p>
|
|
|
+ <p class="text-purple-300 text-sm mt-2">
|
|
|
+ Please enter addresses above
|
|
|
+ </p>
|
|
|
</div>
|
|
|
|
|
|
- <div v-else class="max-h-64 overflow-y-auto scrollbar-thin">
|
|
|
+ <div v-else class="max-h-80 overflow-y-auto scrollbar-thin">
|
|
|
<div
|
|
|
v-for="(address, index) in filteredAddresses"
|
|
|
:key="index"
|
|
|
- class="flex justify-between items-center p-2 hover:bg-base-100 rounded"
|
|
|
+ class="address-item flex justify-between items-center p-3 rounded-lg mb-2 bg-white/5 backdrop-blur-sm"
|
|
|
>
|
|
|
- <div class="flex items-center space-x-3">
|
|
|
+ <div class="flex items-center space-x-4">
|
|
|
<div
|
|
|
- class="w-6 h-6 bg-gray-200 rounded-full flex items-center justify-center text-xs font-bold text-gray-600"
|
|
|
+ class="w-8 h-8 bg-gradient-to-r from-purple-400 to-pink-400 rounded-full flex items-center justify-center text-xs font-bold text-white"
|
|
|
>
|
|
|
{{ index + 1 }}
|
|
|
</div>
|
|
|
- <div class="font-mono text-sm">{{ address }}</div>
|
|
|
+ <div class="font-mono text-sm text-white">
|
|
|
+ {{ address }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
<button
|
|
|
- class="btn btn-sm btn-error btn-circle"
|
|
|
+ class="btn btn-circle btn-sm bg-red-500 border-0 hover:bg-red-600"
|
|
|
@click="removeAddress(index)"
|
|
|
>
|
|
|
- ✕
|
|
|
+ <i class="fas fa-times"></i>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -280,63 +517,94 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 底部发送按钮 -->
|
|
|
- <div class="mt-8 text-center">
|
|
|
+ <!-- Bottom Send Button -->
|
|
|
+ <div class="mt-12 text-center relative z-10">
|
|
|
<button
|
|
|
- class="btn btn-primary btn-lg gradient-bg border-0 text-white shadow-lg"
|
|
|
+ class="btn btn-primary btn-xl gradient-bg-3 border-0 text-white shadow-2xl btn-glow neon-glow"
|
|
|
@click="sendAllRewards"
|
|
|
:disabled="addressList.length === 0 || waitAddRewardList.length === 0"
|
|
|
>
|
|
|
- <span class="mr-2">🚀</span>
|
|
|
- 开始发送 ({{ totalRewardCount * addressList.length }} 个奖励)
|
|
|
+ <i class="fas fa-rocket mr-3 animate-bounce-slow"></i>
|
|
|
+ Launch Airdrop ({{ totalRewardCount * addressList.length }} rewards)
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 增加奖励 Modal -->
|
|
|
+ <!-- Add Reward Modal -->
|
|
|
<div class="modal" :class="{ 'modal-open': showAddRewardDialog }">
|
|
|
- <div class="modal-box max-w-md">
|
|
|
- <h3 class="font-bold text-lg mb-4">🎁 添加奖励</h3>
|
|
|
- <div class="space-y-4">
|
|
|
+ <div class="modal-box max-w-md glass-effect border border-white/20">
|
|
|
+ <h3 class="font-bold text-2xl mb-6 text-white">
|
|
|
+ <i class="fas fa-gift text-purple-300 mr-2"></i>
|
|
|
+ Add Reward
|
|
|
+ </h3>
|
|
|
+ <div class="space-y-6">
|
|
|
<div class="form-control">
|
|
|
<label class="label">
|
|
|
- <span class="label-text font-medium">选择物品</span>
|
|
|
+ <span class="label-text font-medium text-white"
|
|
|
+ >Select Item</span
|
|
|
+ >
|
|
|
</label>
|
|
|
- <select
|
|
|
- v-model="selectedReward"
|
|
|
- class="select select-bordered w-full"
|
|
|
+ <div
|
|
|
+ class="grid grid-cols-2 gap-2 max-h-48 overflow-y-auto scrollbar-thin"
|
|
|
>
|
|
|
- <option value="">请选择物品...</option>
|
|
|
- <option
|
|
|
+ <div
|
|
|
v-for="item in staticGoods"
|
|
|
:key="item.id"
|
|
|
- :value="item.id"
|
|
|
+ @click="selectedReward = item.id"
|
|
|
+ :class="[
|
|
|
+ 'p-3 rounded-lg border-2 cursor-pointer transition-all duration-200',
|
|
|
+ selectedReward == item.id
|
|
|
+ ? 'border-purple-400 bg-purple-500/20'
|
|
|
+ : 'border-white/20 bg-white/5 hover:bg-white/10'
|
|
|
+ ]"
|
|
|
>
|
|
|
- {{ item.name }} (库存: {{ item.count }})
|
|
|
- </option>
|
|
|
- </select>
|
|
|
+ <div class="flex items-center space-x-3">
|
|
|
+ <div class="w-8 h-8 icon-container">
|
|
|
+ <img
|
|
|
+ :src="getItemIcon(item.id)"
|
|
|
+ :alt="item.name"
|
|
|
+ class="w-6 h-6 item-icon"
|
|
|
+ @error="handleImageError"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="font-medium text-white text-sm">
|
|
|
+ {{ item.name }}
|
|
|
+ </div>
|
|
|
+ <div class="text-xs text-purple-200">
|
|
|
+ Stock: {{ item.count }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
<div class="form-control">
|
|
|
<label class="label">
|
|
|
- <span class="label-text font-medium">每个地址数量</span>
|
|
|
+ <span class="label-text font-medium text-white"
|
|
|
+ >Quantity per Address</span
|
|
|
+ >
|
|
|
</label>
|
|
|
<input
|
|
|
type="number"
|
|
|
v-model="rewardQuantity"
|
|
|
min="1"
|
|
|
- class="input input-bordered w-full"
|
|
|
- placeholder="请输入数量"
|
|
|
+ class="input input-bordered w-full bg-white/10 border-white/30 text-white placeholder-white/50"
|
|
|
+ placeholder="Enter quantity"
|
|
|
/>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="modal-action">
|
|
|
<button
|
|
|
- class="btn btn-primary"
|
|
|
+ class="btn btn-primary gradient-bg-2 border-0 text-white"
|
|
|
@click="addRewardConfirm"
|
|
|
:disabled="!selectedReward || !rewardQuantity || rewardQuantity <= 0"
|
|
|
>
|
|
|
- 确定添加
|
|
|
+ <i class="fas fa-check mr-2"></i>
|
|
|
+ Add Reward
|
|
|
+ </button>
|
|
|
+ <button class="btn btn-ghost text-white" @click="closeModal">
|
|
|
+ Cancel
|
|
|
</button>
|
|
|
- <button class="btn btn-ghost" @click="closeModal">取消</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="modal-backdrop" @click="closeModal"></div>
|
|
@@ -344,9 +612,9 @@
|
|
|
|
|
|
<!-- Toast Container -->
|
|
|
<div class="toast toast-top toast-end">
|
|
|
- <div v-if="toast.show" :class="['alert', toast.type]">
|
|
|
+ <div v-if="toast.show" :class="['alert', toast.type, 'glass-effect']">
|
|
|
<div>
|
|
|
- <span>{{ toast.message }}</span>
|
|
|
+ <span class="text-white">{{ toast.message }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -359,9 +627,9 @@
|
|
|
const Toast = {
|
|
|
template: `
|
|
|
<div class="toast toast-top toast-end">
|
|
|
- <div v-if="show" :class="['alert', type]">
|
|
|
+ <div v-if="show" :class="['alert', type, 'glass-effect']">
|
|
|
<div>
|
|
|
- <span>{{ message }}</span>
|
|
|
+ <span class="text-white">{{ message }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -405,7 +673,7 @@
|
|
|
Toast,
|
|
|
},
|
|
|
methods: {
|
|
|
- // 解析地址
|
|
|
+ // Parse addresses
|
|
|
parseAddresses() {
|
|
|
if (this.waitInputAddresses.trim()) {
|
|
|
const addresses = this.waitInputAddresses
|
|
@@ -418,29 +686,29 @@
|
|
|
}
|
|
|
},
|
|
|
|
|
|
- // 删除地址
|
|
|
+ // Remove address
|
|
|
removeAddress(index) {
|
|
|
this.addressList.splice(index, 1);
|
|
|
this.updateAddressInput();
|
|
|
- this.showToast(`删除了地址`, "alert-info");
|
|
|
+ this.showToast(`Address removed`, "alert-info");
|
|
|
},
|
|
|
|
|
|
- // 更新地址输入框
|
|
|
+ // Update address input
|
|
|
updateAddressInput() {
|
|
|
this.waitInputAddresses = this.addressList.join("\n");
|
|
|
},
|
|
|
|
|
|
- // 清空地址
|
|
|
+ // Clear addresses
|
|
|
clearAddresses() {
|
|
|
this.addressList = [];
|
|
|
this.waitInputAddresses = "";
|
|
|
- this.showToast("已清空所有地址", "alert-info");
|
|
|
+ this.showToast("All addresses cleared", "alert-info");
|
|
|
},
|
|
|
|
|
|
- // 导出地址
|
|
|
+ // Export addresses
|
|
|
exportAddresses() {
|
|
|
if (this.addressList.length === 0) {
|
|
|
- this.showToast("暂无地址可导出", "alert-error");
|
|
|
+ this.showToast("No addresses to export", "alert-error");
|
|
|
return;
|
|
|
}
|
|
|
const text = this.addressList.join("\n");
|
|
@@ -454,12 +722,12 @@
|
|
|
a.click();
|
|
|
URL.revokeObjectURL(url);
|
|
|
this.showToast(
|
|
|
- `已导出 ${this.addressList.length} 个地址`,
|
|
|
+ `Exported ${this.addressList.length} addresses`,
|
|
|
"alert-success"
|
|
|
);
|
|
|
},
|
|
|
|
|
|
- // 导入地址
|
|
|
+ // Import addresses
|
|
|
importAddresses() {
|
|
|
const input = document.createElement("input");
|
|
|
input.type = "file";
|
|
@@ -473,7 +741,7 @@
|
|
|
this.waitInputAddresses = content;
|
|
|
this.parseAddresses();
|
|
|
this.showToast(
|
|
|
- `已导入 ${this.addressList.length} 个地址`,
|
|
|
+ `Imported ${this.addressList.length} addresses`,
|
|
|
"alert-success"
|
|
|
);
|
|
|
};
|
|
@@ -483,7 +751,7 @@
|
|
|
input.click();
|
|
|
},
|
|
|
|
|
|
- // 增加奖励确认
|
|
|
+ // Add reward confirmation
|
|
|
addRewardConfirm() {
|
|
|
if (
|
|
|
this.selectedReward &&
|
|
@@ -493,26 +761,26 @@
|
|
|
const itemId = parseInt(this.selectedReward);
|
|
|
const count = parseInt(this.rewardQuantity);
|
|
|
|
|
|
- // 检查是否已存在该物品
|
|
|
+ // Check if item already exists
|
|
|
const existingIndex = this.waitAddRewardList.findIndex(
|
|
|
(item) => item.id === itemId
|
|
|
);
|
|
|
|
|
|
if (existingIndex > -1) {
|
|
|
- // 如果已存在,增加数量
|
|
|
+ // If exists, increase quantity
|
|
|
this.waitAddRewardList[existingIndex].count += count;
|
|
|
this.showToast(
|
|
|
- `增加了 ${count} 个 ${this.getItemName(itemId)}`,
|
|
|
+ `Added ${count} more ${this.getItemName(itemId)}`,
|
|
|
"alert-success"
|
|
|
);
|
|
|
} else {
|
|
|
- // 如果不存在,添加新项
|
|
|
+ // If not exists, add new item
|
|
|
this.waitAddRewardList.push({
|
|
|
id: itemId,
|
|
|
count: count,
|
|
|
});
|
|
|
this.showToast(
|
|
|
- `添加了 ${count} 个 ${this.getItemName(itemId)}`,
|
|
|
+ `Added ${count} ${this.getItemName(itemId)}`,
|
|
|
"alert-success"
|
|
|
);
|
|
|
}
|
|
@@ -521,7 +789,7 @@
|
|
|
}
|
|
|
},
|
|
|
|
|
|
- // 更新奖励数量
|
|
|
+ // Update reward count
|
|
|
updateRewardCount(id, count) {
|
|
|
const reward = this.waitAddRewardList.find(
|
|
|
(item) => item.id === id
|
|
@@ -529,13 +797,13 @@
|
|
|
if (reward && count > 0) {
|
|
|
reward.count = parseInt(count);
|
|
|
this.showToast(
|
|
|
- `更新了 ${this.getItemName(id)} 的数量为 ${count}`,
|
|
|
+ `Updated ${this.getItemName(id)} quantity to ${count}`,
|
|
|
"alert-info"
|
|
|
);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
- // 删除等待发送的奖励
|
|
|
+ // Remove reward
|
|
|
removeWaitReward(id) {
|
|
|
const index = this.waitAddRewardList.findIndex(
|
|
|
(item) => item.id === id
|
|
@@ -543,31 +811,54 @@
|
|
|
if (index > -1) {
|
|
|
const removedReward = this.waitAddRewardList.splice(index, 1)[0];
|
|
|
this.showToast(
|
|
|
- `删除了 ${removedReward.count} 个 ${this.getItemName(id)}`,
|
|
|
+ `Removed ${removedReward.count} ${this.getItemName(id)}`,
|
|
|
"alert-info"
|
|
|
);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
- // 获取物品名称
|
|
|
+ // Get item name
|
|
|
getItemName(id) {
|
|
|
const item = this.staticGoods.find((item) => item.id == id);
|
|
|
- return item ? item.name : `未知物品(${id})`;
|
|
|
+ return item ? item.name : `Unknown Item (${id})`;
|
|
|
+ },
|
|
|
+
|
|
|
+ // Get item icon
|
|
|
+ getItemIcon(id) {
|
|
|
+ const item = this.staticGoods.find((item) => item.id == id);
|
|
|
+ if (item && item.icon) {
|
|
|
+ return `img/${item.icon}.png`;
|
|
|
+ }
|
|
|
+ return "img/default_icon.png";
|
|
|
},
|
|
|
|
|
|
- // 全部发送
|
|
|
+ // Handle image error
|
|
|
+ handleImageError(event) {
|
|
|
+ // Replace with fallback icon or hide the image
|
|
|
+ event.target.style.display = "none";
|
|
|
+ const parent = event.target.parentElement;
|
|
|
+ if (parent) {
|
|
|
+ parent.innerHTML =
|
|
|
+ '<i class="fas fa-question text-white/50"></i>';
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // Send all rewards
|
|
|
sendAllRewards() {
|
|
|
if (this.addressList.length === 0) {
|
|
|
- this.showToast("请先添加钱包地址", "alert-error");
|
|
|
+ this.showToast(
|
|
|
+ "Please add wallet addresses first",
|
|
|
+ "alert-error"
|
|
|
+ );
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (this.waitAddRewardList.length === 0) {
|
|
|
- this.showToast("请先添加奖励", "alert-error");
|
|
|
+ this.showToast("Please add rewards first", "alert-error");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 构建发送数据
|
|
|
+ // Build send data
|
|
|
const sendData = {
|
|
|
addresses: this.addressList,
|
|
|
rewards: this.waitAddRewardList,
|
|
@@ -576,19 +867,22 @@
|
|
|
rewardTypes: this.waitAddRewardList.length,
|
|
|
};
|
|
|
|
|
|
- console.log("发送数据:", sendData);
|
|
|
+ console.log("Send data:", sendData);
|
|
|
|
|
|
- // 这里可以添加实际的API调用
|
|
|
- // 示例:调用发送API
|
|
|
+ // Here you can add actual API call
|
|
|
+ // Example: call send API
|
|
|
this.showToast(
|
|
|
- `准备发送 ${this.totalRewardCount} 个奖励到 ${this.addressList.length} 个地址`,
|
|
|
+ `Preparing to send ${this.totalRewardCount} rewards to ${this.addressList.length} addresses`,
|
|
|
"alert-info"
|
|
|
);
|
|
|
|
|
|
- // 模拟发送过程
|
|
|
+ // Simulate sending process
|
|
|
setTimeout(() => {
|
|
|
- this.showToast("发送完成!", "alert-success");
|
|
|
- // 发送完成后可以清空列表或保留数据
|
|
|
+ this.showToast(
|
|
|
+ "Airdrop completed successfully!",
|
|
|
+ "alert-success"
|
|
|
+ );
|
|
|
+ // You can clear lists or keep data after sending
|
|
|
}, 2000);
|
|
|
},
|
|
|
|
|
@@ -615,34 +909,42 @@
|
|
|
1: {
|
|
|
name: "POG",
|
|
|
count: 100,
|
|
|
+ icon: "icon_pog",
|
|
|
},
|
|
|
2: {
|
|
|
name: "Diamond",
|
|
|
count: 100,
|
|
|
+ icon: "icon_gem",
|
|
|
},
|
|
|
3: {
|
|
|
- name: "GAME_PASS_SHARD",
|
|
|
+ name: "GAME_SHARD",
|
|
|
count: 100,
|
|
|
+ icon: "icon_puzzle",
|
|
|
},
|
|
|
5: {
|
|
|
- name: "POG_CRITICAL_CARD",
|
|
|
+ name: "CRIT_CARD",
|
|
|
count: 100,
|
|
|
+ icon: "icon_crit",
|
|
|
},
|
|
|
6: {
|
|
|
name: "FREE_ITEM_BOX",
|
|
|
count: 100,
|
|
|
+ icon: "icon_item_box",
|
|
|
},
|
|
|
7: {
|
|
|
name: "ITEM_BOX",
|
|
|
count: 100,
|
|
|
+ icon: "icon_item_box",
|
|
|
},
|
|
|
8: {
|
|
|
name: "FREE_POG_BOX",
|
|
|
count: 100,
|
|
|
+ icon: "icon_pog_box",
|
|
|
},
|
|
|
9: {
|
|
|
name: "POG_BOX",
|
|
|
count: 100,
|
|
|
+ icon: "icon_pog_box",
|
|
|
},
|
|
|
};
|
|
|
this.staticGoods = [];
|