xxsanksdkfhsjdfbsfs3987cn102pom8923dfsdfhjdsf.html 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478
  1. <!DOCTYPE html>
  2. <html lang="en" data-theme="light">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>POG Airdrop</title>
  7. <link
  8. rel="icon"
  9. type="image/png"
  10. href="https://portal.telgather.com/h/imgs/icon_tog.png"
  11. />
  12. <script src="https://cdn.tailwindcss.com"></script>
  13. <link
  14. href="https://cdn.bootcdn.net/ajax/libs/daisyui/4.12.23/full.css"
  15. rel="stylesheet"
  16. type="text/css"
  17. />
  18. <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.5.13/vue.global.min.js"></script>
  19. <link
  20. href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
  21. rel="stylesheet"
  22. />
  23. <style>
  24. @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap");
  25. * {
  26. font-family: "Inter", sans-serif;
  27. }
  28. .gradient-bg {
  29. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  30. }
  31. .gradient-bg-2 {
  32. background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
  33. }
  34. .gradient-bg-3 {
  35. background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  36. }
  37. .card-hover {
  38. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  39. }
  40. .card-hover:hover {
  41. transform: translateY(-4px);
  42. box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
  43. }
  44. .scrollbar-thin {
  45. scrollbar-width: thin;
  46. scrollbar-color: #cbd5e0 #f7fafc;
  47. }
  48. .scrollbar-thin::-webkit-scrollbar {
  49. width: 6px;
  50. }
  51. .scrollbar-thin::-webkit-scrollbar-track {
  52. background: #f7fafc;
  53. }
  54. .scrollbar-thin::-webkit-scrollbar-thumb {
  55. background: #cbd5e0;
  56. border-radius: 3px;
  57. }
  58. .animate-pulse-slow {
  59. animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
  60. }
  61. .animate-bounce-slow {
  62. animation: bounce 2s infinite;
  63. }
  64. .glass-effect {
  65. background: rgba(255, 255, 255, 0.25);
  66. backdrop-filter: blur(10px);
  67. border: 1px solid rgba(255, 255, 255, 0.18);
  68. }
  69. .neon-glow {
  70. box-shadow: 0 0 20px rgba(102, 126, 234, 0.5);
  71. }
  72. .floating {
  73. animation: floating 3s ease-in-out infinite;
  74. }
  75. @keyframes floating {
  76. 0% {
  77. transform: translateY(0px);
  78. }
  79. 50% {
  80. transform: translateY(-10px);
  81. }
  82. 100% {
  83. transform: translateY(0px);
  84. }
  85. }
  86. .stat-card {
  87. background: linear-gradient(
  88. 135deg,
  89. rgba(255, 255, 255, 0.1) 0%,
  90. rgba(255, 255, 255, 0.05) 100%
  91. );
  92. backdrop-filter: blur(10px);
  93. border: 1px solid rgba(255, 255, 255, 0.2);
  94. }
  95. .reward-item {
  96. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  97. color: white;
  98. transition: all 0.3s ease;
  99. }
  100. .reward-item:hover {
  101. transform: scale(1.05);
  102. box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
  103. }
  104. .address-item {
  105. transition: all 0.3s ease;
  106. }
  107. .address-item:hover {
  108. background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
  109. color: white;
  110. transform: translateX(5px);
  111. }
  112. .btn-glow {
  113. position: relative;
  114. overflow: hidden;
  115. }
  116. .btn-glow::before {
  117. content: "";
  118. position: absolute;
  119. top: 0;
  120. left: -100%;
  121. width: 100%;
  122. height: 100%;
  123. background: linear-gradient(
  124. 90deg,
  125. transparent,
  126. rgba(255, 255, 255, 0.4),
  127. transparent
  128. );
  129. transition: left 0.5s;
  130. }
  131. .btn-glow:hover::before {
  132. left: 100%;
  133. }
  134. .item-icon {
  135. width: 100%;
  136. height: 100%;
  137. object-fit: contain;
  138. transition: transform 0.3s ease;
  139. }
  140. .item-icon:hover {
  141. transform: scale(1.1);
  142. }
  143. .icon-container {
  144. display: flex;
  145. align-items: center;
  146. justify-content: center;
  147. background: rgba(255, 255, 255, 0.1);
  148. border: 1px solid rgba(255, 255, 255, 0.2);
  149. border-radius: 50%;
  150. overflow: hidden;
  151. transition: all 0.3s ease;
  152. }
  153. .icon-container:hover {
  154. background: rgba(255, 255, 255, 0.2);
  155. border-color: rgba(255, 255, 255, 0.4);
  156. }
  157. /* Toast Animation */
  158. .toast-enter-active,
  159. .toast-leave-active {
  160. transition: all 0.3s ease;
  161. }
  162. .toast-enter-from {
  163. opacity: 0;
  164. transform: translateX(100%);
  165. }
  166. .toast-leave-to {
  167. opacity: 0;
  168. transform: translateX(100%);
  169. }
  170. .toast-enter-to,
  171. .toast-leave-from {
  172. opacity: 1;
  173. transform: translateX(0);
  174. }
  175. .animate-slide-in {
  176. animation: slideIn 0.3s ease-out;
  177. }
  178. @keyframes slideIn {
  179. from {
  180. opacity: 0;
  181. transform: translateX(100%);
  182. }
  183. to {
  184. opacity: 1;
  185. transform: translateX(0);
  186. }
  187. }
  188. </style>
  189. </head>
  190. <body
  191. class="min-h-screen bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-900"
  192. >
  193. <div id="app" class="container mx-auto p-4 md:p-8">
  194. <!-- Animated Background -->
  195. <div class="fixed inset-0 overflow-hidden pointer-events-none">
  196. <div
  197. 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"
  198. ></div>
  199. <div
  200. 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"
  201. style="animation-delay: 1s"
  202. ></div>
  203. <div
  204. 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"
  205. style="animation-delay: 2s"
  206. ></div>
  207. </div>
  208. <!-- Header -->
  209. <div class="text-center mb-12 relative z-10">
  210. <div class="floating mb-4">
  211. <i class="fas fa-rocket text-6xl text-white mb-4"></i>
  212. </div>
  213. <h1 class="text-5xl font-bold text-white mb-4">POG Airdrop</h1>
  214. <p class="text-xl text-purple-200">Mass Reward Distribution System</p>
  215. <div class="flex justify-center mt-6 space-x-4">
  216. <div class="stat-card rounded-lg p-4 text-white">
  217. <div class="text-2xl font-bold">{{ addressList.length }}</div>
  218. <div class="text-sm opacity-80">Addresses</div>
  219. </div>
  220. <div class="stat-card rounded-lg p-4 text-white">
  221. <div class="text-2xl font-bold">{{ totalRewardCount }}</div>
  222. <div class="text-sm opacity-80">Rewards</div>
  223. </div>
  224. <div class="stat-card rounded-lg p-4 text-white">
  225. <div class="text-2xl font-bold">
  226. {{ totalRewardCount * addressList.length }}
  227. </div>
  228. <div class="text-sm opacity-80">Total</div>
  229. </div>
  230. </div>
  231. <!-- History Button -->
  232. <div class="mt-6">
  233. <button
  234. class="btn btn-secondary btn-lg gradient-bg border-0 text-white shadow-lg btn-glow"
  235. @click="showHistoryDialog = true"
  236. >
  237. <i class="fas fa-history mr-2"></i>
  238. View History
  239. </button>
  240. </div>
  241. </div>
  242. <div class="space-y-8 relative z-10">
  243. <!-- Step 1: Address Management -->
  244. <div class="glass-effect rounded-2xl shadow-2xl card-hover">
  245. <div class="p-6">
  246. <div class="flex items-center mb-6">
  247. <div
  248. class="w-12 h-12 bg-gradient-to-r from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-white font-bold text-xl mr-4"
  249. >
  250. 1
  251. </div>
  252. <h2 class="text-3xl font-bold text-white">
  253. <i class="fas fa-users text-blue-300 mr-3"></i>
  254. Step 1: Add Wallet Addresses
  255. </h2>
  256. </div>
  257. <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
  258. <!-- Address Input -->
  259. <div>
  260. <div class="form-control">
  261. <label class="label">
  262. <span class="label-text font-semibold text-white text-lg"
  263. >Wallet Address List</span
  264. >
  265. <span class="label-text-alt text-purple-300"
  266. >{{ addressList.length }} addresses</span
  267. >
  268. </label>
  269. <textarea
  270. v-model="waitInputAddresses"
  271. rows="10"
  272. placeholder="Enter wallet addresses, one per line&#10;Example:&#10;0x1234567890abcdef...&#10;0xabcdef1234567890..."
  273. class="textarea textarea-bordered w-full font-mono text-sm bg-white/10 border-white/30 text-white placeholder-white/50"
  274. @input="parseAddresses"
  275. ></textarea>
  276. <label class="label">
  277. <span class="label-text-alt text-purple-200"
  278. >Support batch paste, one address per line</span
  279. >
  280. </label>
  281. </div>
  282. <!-- Quick Actions -->
  283. <div class="flex flex-wrap gap-2 mt-4">
  284. <button
  285. class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
  286. @click="clearAddresses"
  287. >
  288. <i class="fas fa-trash mr-1"></i> Clear
  289. </button>
  290. <button
  291. class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
  292. @click="exportAddresses"
  293. >
  294. <i class="fas fa-download mr-1"></i> Export
  295. </button>
  296. <button
  297. class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
  298. @click="importAddresses"
  299. >
  300. <i class="fas fa-upload mr-1"></i> Import
  301. </button>
  302. </div>
  303. </div>
  304. <!-- Address List -->
  305. <div>
  306. <div class="flex justify-between items-center mb-4">
  307. <h3 class="text-xl font-semibold text-white">
  308. <i class="fas fa-list text-orange-300 mr-2"></i>
  309. Address List
  310. </h3>
  311. <div class="flex items-center space-x-3">
  312. <input
  313. type="text"
  314. v-model="searchAddress"
  315. placeholder="Search addresses..."
  316. class="input input-bordered input-sm w-48 bg-white/10 border-white/30 text-white placeholder-white/50"
  317. />
  318. <div class="badge badge-outline border-white/30 text-white">
  319. {{ filteredAddresses.length }}/{{ addressList.length }}
  320. </div>
  321. </div>
  322. </div>
  323. <div v-if="addressList.length === 0" class="text-center py-8">
  324. <div class="text-6xl mb-4 animate-bounce-slow">👤</div>
  325. <p class="text-purple-200 text-lg">No addresses yet</p>
  326. <p class="text-purple-300 text-sm mt-2">
  327. Please enter addresses on the left
  328. </p>
  329. </div>
  330. <div v-else class="max-h-80 overflow-y-auto scrollbar-thin">
  331. <div
  332. v-for="(address, index) in filteredAddresses"
  333. :key="index"
  334. class="address-item flex justify-between items-center p-3 rounded-lg mb-2 bg-white/5 backdrop-blur-sm"
  335. >
  336. <div class="flex items-center space-x-4">
  337. <div
  338. 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"
  339. >
  340. {{ index + 1 }}
  341. </div>
  342. <div class="font-mono text-sm text-white">
  343. {{ address }}
  344. </div>
  345. </div>
  346. <button
  347. class="btn btn-circle btn-sm bg-red-500 border-0 hover:bg-red-600"
  348. @click="removeAddress(index)"
  349. >
  350. <i class="fas fa-times"></i>
  351. </button>
  352. </div>
  353. </div>
  354. </div>
  355. </div>
  356. </div>
  357. </div>
  358. <!-- Step 2: Reward Management -->
  359. <div class="glass-effect rounded-2xl shadow-2xl card-hover">
  360. <div class="p-6">
  361. <div class="flex items-center mb-6">
  362. <div
  363. class="w-12 h-12 bg-gradient-to-r from-purple-500 to-pink-600 rounded-full flex items-center justify-center text-white font-bold text-xl mr-4"
  364. >
  365. 2
  366. </div>
  367. <h2 class="text-3xl font-bold text-white">
  368. <i class="fas fa-gift text-purple-300 mr-3"></i>
  369. Step 2: Select Rewards
  370. </h2>
  371. <div class="badge badge-primary badge-lg animate-pulse-slow ml-4">
  372. {{ waitAddRewardList.length }} Types
  373. </div>
  374. </div>
  375. <div
  376. v-if="waitAddRewardList.length === 0"
  377. class="text-center py-12"
  378. >
  379. <div class="text-8xl mb-6 animate-bounce-slow">🎁</div>
  380. <p class="text-purple-200 text-lg">No rewards added yet</p>
  381. <p class="text-purple-300 text-sm mt-2">
  382. Click the button below to add rewards
  383. </p>
  384. </div>
  385. <div
  386. v-else
  387. class="space-y-4 max-h-80 overflow-y-auto scrollbar-thin"
  388. >
  389. <div
  390. v-for="reward in waitAddRewardList"
  391. :key="reward.id"
  392. class="reward-item rounded-xl p-4 border border-white/20"
  393. >
  394. <div class="flex justify-between items-center">
  395. <div class="flex items-center space-x-4">
  396. <div class="w-12 h-12 icon-container backdrop-blur-sm">
  397. <img
  398. :src="getItemIcon(reward.id)"
  399. :alt="getItemName(reward.id)"
  400. class="w-8 h-8 item-icon"
  401. @error="handleImageError"
  402. />
  403. </div>
  404. <div>
  405. <div class="font-semibold text-lg">
  406. {{ getItemName(reward.id) }}
  407. </div>
  408. <div class="text-sm opacity-80">
  409. {{ reward.count }} per address
  410. </div>
  411. </div>
  412. </div>
  413. <div class="flex items-center space-x-3">
  414. <input
  415. type="number"
  416. v-model="reward.count"
  417. min="1"
  418. class="input input-bordered input-sm w-20 text-center bg-white/20 border-white/30 text-white placeholder-white/50"
  419. @change="updateRewardCount(reward.id, reward.count)"
  420. />
  421. <button
  422. class="btn btn-circle btn-sm bg-red-500 border-0 hover:bg-red-600"
  423. @click="removeWaitReward(reward.id)"
  424. >
  425. <i class="fas fa-times"></i>
  426. </button>
  427. </div>
  428. </div>
  429. </div>
  430. </div>
  431. <div class="text-center mt-6">
  432. <button
  433. class="btn btn-primary btn-lg gradient-bg-2 border-0 text-white shadow-lg btn-glow"
  434. @click="showAddRewardDialog = true"
  435. >
  436. <i class="fas fa-plus mr-2"></i>
  437. Add Reward
  438. </button>
  439. </div>
  440. </div>
  441. </div>
  442. <!-- Step 3: Send Preview -->
  443. <div class="glass-effect rounded-2xl shadow-2xl card-hover">
  444. <div class="p-6">
  445. <div class="flex items-center mb-6">
  446. <div
  447. class="w-12 h-12 bg-gradient-to-r from-green-500 to-blue-600 rounded-full flex items-center justify-center text-white font-bold text-xl mr-4"
  448. >
  449. 3
  450. </div>
  451. <h2 class="text-3xl font-bold text-white">
  452. <i class="fas fa-chart-line text-green-300 mr-3"></i>
  453. Step 3: Review Summary
  454. </h2>
  455. </div>
  456. <!-- Statistics -->
  457. <div class="grid grid-cols-3 gap-6 mb-8">
  458. <div class="stat-card rounded-xl p-6 text-center">
  459. <div class="text-4xl font-bold text-white mb-2">
  460. {{ addressList.length }}
  461. </div>
  462. <div class="text-lg text-purple-200">Addresses</div>
  463. </div>
  464. <div class="stat-card rounded-xl p-6 text-center">
  465. <div class="text-4xl font-bold text-white mb-2">
  466. {{ totalRewardCount }}
  467. </div>
  468. <div class="text-lg text-purple-200">Rewards</div>
  469. </div>
  470. <div class="stat-card rounded-xl p-6 text-center">
  471. <div class="text-4xl font-bold text-white mb-2">
  472. {{ totalRewardCount * addressList.length }}
  473. </div>
  474. <div class="text-lg text-purple-200">Total</div>
  475. </div>
  476. </div>
  477. <!-- Item Summary -->
  478. <div v-if="waitAddRewardList.length > 0">
  479. <h3 class="text-xl font-semibold text-white mb-4">
  480. Item Summary
  481. </h3>
  482. <div
  483. class="grid grid-cols-1 md:grid-cols-2 gap-4 max-h-60 overflow-y-auto scrollbar-thin"
  484. >
  485. <div
  486. v-for="reward in waitAddRewardList"
  487. :key="reward.id"
  488. class="flex justify-between items-center p-4 bg-white/10 rounded-lg backdrop-blur-sm"
  489. >
  490. <div class="flex items-center space-x-3">
  491. <div class="w-10 h-10 icon-container">
  492. <img
  493. :src="getItemIcon(reward.id)"
  494. :alt="getItemName(reward.id)"
  495. class="w-8 h-8 item-icon"
  496. @error="handleImageError"
  497. />
  498. </div>
  499. <span class="font-medium text-white text-lg"
  500. >{{ getItemName(reward.id) }}</span
  501. >
  502. </div>
  503. <div class="flex items-center space-x-2">
  504. <span class="text-sm text-purple-200"
  505. >{{ reward.count }} × {{ addressList.length }}</span
  506. >
  507. <span class="font-bold text-yellow-300 text-lg"
  508. >= {{ reward.count * addressList.length }}</span
  509. >
  510. </div>
  511. </div>
  512. </div>
  513. </div>
  514. </div>
  515. </div>
  516. <!-- Step 4: Launch Airdrop -->
  517. <div class="glass-effect rounded-2xl shadow-2xl card-hover">
  518. <div class="p-6">
  519. <div class="flex items-center mb-6">
  520. <div
  521. class="w-12 h-12 bg-gradient-to-r from-red-500 to-orange-600 rounded-full flex items-center justify-center text-white font-bold text-xl mr-4"
  522. >
  523. 4
  524. </div>
  525. <h2 class="text-3xl font-bold text-white">
  526. <i class="fas fa-rocket text-red-300 mr-3"></i>
  527. Step 4: Launch Airdrop
  528. </h2>
  529. </div>
  530. <div class="text-center">
  531. <div class="mb-8">
  532. <p class="text-xl text-purple-200 mb-4">
  533. Ready to send rewards to {{ addressList.length }} addresses
  534. </p>
  535. <p class="text-lg text-purple-300">
  536. Total rewards to distribute: {{ totalRewardCount *
  537. addressList.length }}
  538. </p>
  539. </div>
  540. <button
  541. class="btn btn-primary btn-xl gradient-bg-3 border-0 text-white shadow-2xl btn-glow"
  542. @click="sendAllRewards"
  543. :disabled="addressList.length === 0 || waitAddRewardList.length === 0"
  544. >
  545. <i class="fas fa-rocket mr-3 animate-bounce-slow"></i>
  546. Launch Airdrop ({{ totalRewardCount * addressList.length }}
  547. rewards)
  548. </button>
  549. </div>
  550. </div>
  551. </div>
  552. </div>
  553. <!-- Add Reward Modal -->
  554. <div class="modal" :class="{ 'modal-open': showAddRewardDialog }">
  555. <div
  556. class="modal-box w-11/12 max-w-4xl bg-gradient-to-br from-gray-900/95 to-black/95 backdrop-blur-xl border border-white/20 shadow-2xl"
  557. >
  558. <h3 class="font-bold text-3xl mb-8 text-white text-center">
  559. <i class="fas fa-gift text-purple-300 mr-2"></i>
  560. Add Reward
  561. </h3>
  562. <div class="space-y-6 max-h-[80vh] overflow-y-auto">
  563. <div class="form-control">
  564. <label class="label">
  565. <span class="label-text font-semibold text-white text-lg"
  566. >Select Item</span
  567. >
  568. </label>
  569. <div
  570. class="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 max-h-96 overflow-y-auto scrollbar-thin"
  571. >
  572. <div
  573. v-for="item in staticGoods"
  574. :key="item.id"
  575. @click="selectedReward = item.id"
  576. :class="[
  577. 'p-4 rounded-xl border-2 cursor-pointer transition-all duration-200 hover:scale-105',
  578. selectedReward == item.id
  579. ? 'border-purple-400 bg-purple-500/20 shadow-lg shadow-purple-500/30'
  580. : 'border-white/20 bg-white/5 hover:bg-white/10 hover:border-white/40'
  581. ]"
  582. >
  583. <div class="flex flex-col items-center space-y-3 text-center">
  584. <div class="w-16 h-16 icon-container">
  585. <img
  586. :src="getItemIcon(item.id)"
  587. :alt="item.name"
  588. class="w-12 h-12 item-icon"
  589. @error="handleImageError"
  590. />
  591. </div>
  592. <div>
  593. <div class="font-semibold text-white text-base">
  594. {{ item.name }}
  595. </div>
  596. <div class="text-sm text-purple-200 mt-1">
  597. Stock: {{ item.count }}
  598. </div>
  599. </div>
  600. </div>
  601. </div>
  602. </div>
  603. </div>
  604. <div class="form-control">
  605. <label class="label">
  606. <span class="label-text font-semibold text-white text-lg"
  607. >Quantity per Address</span
  608. >
  609. </label>
  610. <input
  611. type="number"
  612. v-model="rewardQuantity"
  613. min="1"
  614. class="input input-bordered w-full bg-white/10 border-white/30 text-white placeholder-white/50 text-lg text-center"
  615. placeholder="Enter quantity"
  616. />
  617. </div>
  618. </div>
  619. <div class="modal-action">
  620. <button
  621. class="btn btn-primary btn-lg gradient-bg-2 border-0 text-white"
  622. @click="addRewardConfirm"
  623. :disabled="!selectedReward || !rewardQuantity || rewardQuantity <= 0"
  624. >
  625. <i class="fas fa-check mr-2"></i>
  626. Add Reward
  627. </button>
  628. <button class="btn btn-ghost btn-lg text-white" @click="closeModal">
  629. Cancel
  630. </button>
  631. </div>
  632. </div>
  633. <div
  634. class="modal-backdrop bg-black/50 backdrop-blur-sm"
  635. @click="closeModal"
  636. ></div>
  637. </div>
  638. <!-- Password Verification Modal -->
  639. <div class="modal" :class="{ 'modal-open': showPasswordDialog }">
  640. <div
  641. class="modal-box max-w-md bg-gradient-to-br from-gray-900/95 to-black/95 backdrop-blur-xl border border-white/20 shadow-2xl"
  642. >
  643. <h3 class="font-bold text-2xl mb-6 text-white text-center">
  644. <i class="fas fa-shield-alt text-red-300 mr-2"></i>
  645. Final Confirmation
  646. </h3>
  647. <div class="space-y-6">
  648. <div class="text-center">
  649. <div class="text-6xl mb-4 animate-bounce-slow">🔐</div>
  650. <p class="text-purple-200 text-lg mb-2">
  651. Confirm Airdrop Operation
  652. </p>
  653. <p class="text-purple-300 text-sm">
  654. This action will send {{ totalRewardCount * addressList.length
  655. }} rewards to {{ addressList.length }} addresses
  656. </p>
  657. </div>
  658. <div class="grid grid-cols-1 gap-6">
  659. <div class="form-control">
  660. <label class="label">
  661. <span class="label-text font-semibold text-white text-lg"
  662. ><i class="fas fa-tag text-blue-300 mr-2"></i>Activity
  663. Name</span
  664. >
  665. </label>
  666. <input
  667. type="text"
  668. v-model="reason"
  669. class="input input-bordered w-full bg-white/10 border-white/30 text-white placeholder-white/50 text-lg text-center"
  670. placeholder="Enter activity name (e.g., Spring Festival Event)"
  671. @keyup.enter="verifyPassword"
  672. />
  673. <label class="label">
  674. <span class="label-text-alt text-purple-200"
  675. >This will be used to identify the airdrop activity</span
  676. >
  677. </label>
  678. </div>
  679. <div class="form-control">
  680. <label class="label">
  681. <span class="label-text font-semibold text-white text-lg"
  682. ><i class="fas fa-lock text-red-300 mr-2"></i>Admin
  683. Password</span
  684. >
  685. </label>
  686. <input
  687. type="password"
  688. v-model="password"
  689. class="input input-bordered w-full bg-white/10 border-white/30 text-white placeholder-white/50 text-lg text-center"
  690. placeholder="Enter password"
  691. @keyup.enter="verifyPassword"
  692. />
  693. <label class="label">
  694. <span class="label-text-alt text-purple-200"
  695. >Press Enter to confirm</span
  696. >
  697. </label>
  698. </div>
  699. </div>
  700. </div>
  701. <div class="modal-action">
  702. <button
  703. class="btn btn-primary btn-lg gradient-bg-3 border-0 text-white"
  704. @click="verifyPassword"
  705. :disabled="!password.trim() || !reason.trim() || isVerifying"
  706. >
  707. <i v-if="isVerifying" class="fas fa-spinner fa-spin mr-2"></i>
  708. <i v-else class="fas fa-rocket mr-2"></i>
  709. {{ isVerifying ? 'Verifying...' : 'Launch Airdrop' }}
  710. </button>
  711. <button
  712. class="btn btn-ghost btn-lg text-white"
  713. @click="closePasswordModal"
  714. >
  715. Cancel
  716. </button>
  717. </div>
  718. </div>
  719. <div
  720. class="modal-backdrop bg-black/50 backdrop-blur-sm"
  721. @click="closePasswordModal"
  722. ></div>
  723. </div>
  724. <!-- History Modal -->
  725. <div class="modal" :class="{ 'modal-open': showHistoryDialog }">
  726. <div
  727. class="modal-box w-11/12 max-w-5xl bg-gradient-to-br from-gray-900/95 to-black/95 backdrop-blur-xl border border-white/20 shadow-2xl"
  728. >
  729. <div class="flex justify-between items-center mb-4">
  730. <h3 class="font-bold text-2xl text-white">
  731. <i class="fas fa-history text-blue-300 mr-2"></i>
  732. Airdrop History
  733. </h3>
  734. <button
  735. class="btn btn-circle btn-sm bg-white/20 border-0 hover:bg-white/30"
  736. @click="closeHistoryModal"
  737. >
  738. <i class="fas fa-times text-white"></i>
  739. </button>
  740. </div>
  741. <!-- Loading State -->
  742. <div v-if="isLoadingHistory" class="text-center py-8">
  743. <div class="text-4xl mb-2 animate-bounce-slow">⏳</div>
  744. <p class="text-purple-200">Loading history...</p>
  745. </div>
  746. <!-- History Content -->
  747. <div
  748. v-else-if="historyData.length > 0"
  749. class="space-y-3 max-h-[75vh] overflow-y-auto"
  750. >
  751. <div
  752. v-for="item in historyData"
  753. :key="item.id"
  754. class="bg-white/10 rounded-lg p-4 backdrop-blur-sm border border-white/20"
  755. >
  756. <!-- Header Row -->
  757. <div class="flex justify-between items-center mb-3">
  758. <div class="flex items-center space-x-3">
  759. <h4 class="text-lg font-bold text-white">
  760. {{ item.reason }}
  761. </h4>
  762. <div
  763. :class="[
  764. 'badge badge-xs',
  765. item.status === 1 ? 'badge-success' : 'badge-error'
  766. ]"
  767. >
  768. {{ item.status === 1 ? '✓' : '✗' }}
  769. </div>
  770. </div>
  771. <div
  772. class="flex items-center space-x-3 text-xs text-purple-300"
  773. >
  774. <span>
  775. <i class="fas fa-calendar mr-1"></i>
  776. {{ formatDate(item.createTime) }}
  777. </span>
  778. <span class="text-purple-400">#{{ item.id.slice(-8) }}</span>
  779. </div>
  780. </div>
  781. <!-- Rewards List - Compact -->
  782. <div class="flex flex-wrap gap-2">
  783. <div
  784. v-for="reward in parseGoodList(item.goodList)"
  785. :key="reward.id"
  786. class="flex items-center space-x-2 px-3 py-2 bg-white/10 rounded-lg border border-white/20"
  787. >
  788. <div class="w-6 h-6 icon-container">
  789. <img
  790. :src="getItemIcon(reward.id)"
  791. :alt="getItemName(reward.id)"
  792. class="w-5 h-5 item-icon"
  793. @error="handleImageError"
  794. />
  795. </div>
  796. <span class="text-sm font-medium text-white">
  797. {{ getItemName(reward.id) }}
  798. </span>
  799. <span
  800. class="text-xs text-purple-200 bg-purple-500/20 px-2 py-1 rounded"
  801. >
  802. ×{{ reward.count }}
  803. </span>
  804. </div>
  805. </div>
  806. </div>
  807. </div>
  808. <!-- Empty State -->
  809. <div v-else class="text-center py-8">
  810. <div class="text-6xl mb-3 animate-bounce-slow">📜</div>
  811. <p class="text-purple-200">No history found</p>
  812. <p class="text-purple-300 text-sm mt-1">
  813. Start your first airdrop to see history here
  814. </p>
  815. </div>
  816. <!-- Pagination - Compact -->
  817. <div
  818. v-if="historyData.length > 0"
  819. class="flex justify-center items-center space-x-3 mt-4 pt-3 border-t border-white/20"
  820. >
  821. <button
  822. class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
  823. @click="changePage(currentPage - 1)"
  824. :disabled="currentPage <= 1"
  825. >
  826. <i class="fas fa-chevron-left"></i>
  827. </button>
  828. <div class="flex items-center space-x-2">
  829. <span class="text-sm text-white">Page</span>
  830. <input
  831. type="number"
  832. v-model.number="currentPage"
  833. min="1"
  834. class="input input-bordered input-xs w-16 text-center bg-white/10 border-white/30 text-white"
  835. @change="loadHistory"
  836. />
  837. <span class="text-sm text-white">/ {{ totalPages }}</span>
  838. </div>
  839. <button
  840. class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
  841. @click="changePage(currentPage + 1)"
  842. :disabled="currentPage >= totalPages"
  843. >
  844. <i class="fas fa-chevron-right"></i>
  845. </button>
  846. </div>
  847. </div>
  848. <div
  849. class="modal-backdrop bg-black/50 backdrop-blur-sm"
  850. @click="closeHistoryModal"
  851. ></div>
  852. </div>
  853. <!-- Toast Container -->
  854. <div class="fixed top-4 right-4 z-50">
  855. <div
  856. v-if="toast.show"
  857. :class="['alert', toast.type, 'glass-effect', 'shadow-2xl', 'min-w-80', 'max-w-md', 'animate-slide-in']"
  858. >
  859. <div class="flex items-center justify-between w-full">
  860. <div class="flex items-center">
  861. <i
  862. v-if="toast.type === 'alert-success'"
  863. class="fas fa-check-circle text-green-400 mr-3"
  864. ></i>
  865. <i
  866. v-else-if="toast.type === 'alert-error'"
  867. class="fas fa-exclamation-circle text-red-400 mr-3"
  868. ></i>
  869. <i v-else class="fas fa-info-circle text-blue-400 mr-3"></i>
  870. <span class="text-white font-medium">{{ toast.message }}</span>
  871. </div>
  872. <button
  873. @click="toast.show = false"
  874. class="btn btn-circle btn-sm bg-white/20 border-0 hover:bg-white/30 ml-3"
  875. >
  876. <i class="fas fa-times text-white"></i>
  877. </button>
  878. </div>
  879. </div>
  880. </div>
  881. </div>
  882. <script>
  883. const { createApp } = Vue;
  884. const API_BASE_URL = "https://zombies.telgather.com/platform";
  885. const Toast = {
  886. template: `
  887. <div class="fixed top-4 right-4 z-50">
  888. <div v-if="show" :class="['alert', type, 'glass-effect', 'shadow-2xl', 'min-w-80', 'max-w-md', 'animate-slide-in']">
  889. <div class="flex items-center justify-between w-full">
  890. <div class="flex items-center">
  891. <i v-if="type === 'alert-success'" class="fas fa-check-circle text-green-400 mr-3"></i>
  892. <i v-else-if="type === 'alert-error'" class="fas fa-exclamation-circle text-red-400 mr-3"></i>
  893. <i v-else class="fas fa-info-circle text-blue-400 mr-3"></i>
  894. <span class="text-white font-medium">{{ message }}</span>
  895. </div>
  896. <button
  897. @click="$emit('close')"
  898. class="btn btn-circle btn-sm bg-white/20 border-0 hover:bg-white/30 ml-3"
  899. >
  900. <i class="fas fa-times text-white"></i>
  901. </button>
  902. </div>
  903. </div>
  904. </div>
  905. `,
  906. props: ["show", "type", "message"],
  907. emits: ["close"],
  908. };
  909. createApp({
  910. data() {
  911. return {
  912. reason: "",
  913. waitInputAddresses: "",
  914. addressList: [],
  915. waitAddRewardList: [],
  916. showAddRewardDialog: false,
  917. showPasswordDialog: false,
  918. selectedReward: "",
  919. rewardQuantity: "",
  920. password: "",
  921. isVerifying: false,
  922. searchAddress: "",
  923. staticGoods: [],
  924. toast: {
  925. show: false,
  926. type: "alert-info",
  927. message: "",
  928. },
  929. showHistoryDialog: false,
  930. historyData: [],
  931. currentPage: 1,
  932. totalPages: 1,
  933. isLoadingHistory: false,
  934. };
  935. },
  936. computed: {
  937. totalRewardCount() {
  938. return this.waitAddRewardList.reduce(
  939. (total, reward) => total + reward.count,
  940. 0
  941. );
  942. },
  943. filteredAddresses() {
  944. if (!this.searchAddress) return this.addressList;
  945. return this.addressList.filter((addr) =>
  946. addr.toLowerCase().includes(this.searchAddress.toLowerCase())
  947. );
  948. },
  949. },
  950. components: {
  951. Toast,
  952. },
  953. watch: {
  954. showHistoryDialog(newVal) {
  955. if (newVal) {
  956. this.loadHistory();
  957. }
  958. },
  959. },
  960. methods: {
  961. // Parse addresses
  962. parseAddresses() {
  963. if (this.waitInputAddresses.trim()) {
  964. const addresses = this.waitInputAddresses
  965. .split("\n")
  966. .map((addr) => addr.trim())
  967. .filter((addr) => addr && addr.length > 0);
  968. this.addressList = addresses;
  969. } else {
  970. this.addressList = [];
  971. }
  972. },
  973. // Remove address
  974. removeAddress(index) {
  975. this.addressList.splice(index, 1);
  976. this.updateAddressInput();
  977. this.showToast(`Address removed`, "alert-info");
  978. },
  979. // Update address input
  980. updateAddressInput() {
  981. this.waitInputAddresses = this.addressList.join("\n");
  982. },
  983. // Clear addresses
  984. clearAddresses() {
  985. this.addressList = [];
  986. this.waitInputAddresses = "";
  987. this.showToast("All addresses cleared", "alert-info");
  988. },
  989. // Export addresses
  990. exportAddresses() {
  991. if (this.addressList.length === 0) {
  992. this.showToast("No addresses to export", "alert-error");
  993. return;
  994. }
  995. const text = this.addressList.join("\n");
  996. const blob = new Blob([text], { type: "text/plain" });
  997. const url = URL.createObjectURL(blob);
  998. const a = document.createElement("a");
  999. a.href = url;
  1000. a.download = `addresses_${
  1001. new Date().toISOString().split("T")[0]
  1002. }.txt`;
  1003. a.click();
  1004. URL.revokeObjectURL(url);
  1005. this.showToast(
  1006. `Exported ${this.addressList.length} addresses`,
  1007. "alert-success"
  1008. );
  1009. },
  1010. // Import addresses
  1011. importAddresses() {
  1012. const input = document.createElement("input");
  1013. input.type = "file";
  1014. input.accept = ".txt,.csv";
  1015. input.onchange = (e) => {
  1016. const file = e.target.files[0];
  1017. if (file) {
  1018. const reader = new FileReader();
  1019. reader.onload = (e) => {
  1020. const content = e.target.result;
  1021. this.waitInputAddresses = content;
  1022. this.parseAddresses();
  1023. this.showToast(
  1024. `Imported ${this.addressList.length} addresses`,
  1025. "alert-success"
  1026. );
  1027. };
  1028. reader.readAsText(file);
  1029. }
  1030. };
  1031. input.click();
  1032. },
  1033. // Add reward confirmation
  1034. addRewardConfirm() {
  1035. if (
  1036. this.selectedReward &&
  1037. this.rewardQuantity &&
  1038. this.rewardQuantity > 0
  1039. ) {
  1040. const itemId = parseInt(this.selectedReward);
  1041. const count = parseInt(this.rewardQuantity);
  1042. // Check if item already exists
  1043. const existingIndex = this.waitAddRewardList.findIndex(
  1044. (item) => item.id === itemId
  1045. );
  1046. if (existingIndex > -1) {
  1047. // If exists, increase quantity
  1048. this.waitAddRewardList[existingIndex].count += count;
  1049. this.showToast(
  1050. `Added ${count} more ${this.getItemName(itemId)}`,
  1051. "alert-success"
  1052. );
  1053. } else {
  1054. // If not exists, add new item
  1055. this.waitAddRewardList.push({
  1056. id: itemId,
  1057. count: count,
  1058. });
  1059. this.showToast(
  1060. `Added ${count} ${this.getItemName(itemId)}`,
  1061. "alert-success"
  1062. );
  1063. }
  1064. this.closeModal();
  1065. }
  1066. },
  1067. // Update reward count
  1068. updateRewardCount(id, count) {
  1069. const reward = this.waitAddRewardList.find(
  1070. (item) => item.id === id
  1071. );
  1072. if (reward && count > 0) {
  1073. reward.count = parseInt(count);
  1074. this.showToast(
  1075. `Updated ${this.getItemName(id)} quantity to ${count}`,
  1076. "alert-info"
  1077. );
  1078. }
  1079. },
  1080. // Remove reward
  1081. removeWaitReward(id) {
  1082. const index = this.waitAddRewardList.findIndex(
  1083. (item) => item.id === id
  1084. );
  1085. if (index > -1) {
  1086. const removedReward = this.waitAddRewardList.splice(index, 1)[0];
  1087. this.showToast(
  1088. `Removed ${removedReward.count} ${this.getItemName(id)}`,
  1089. "alert-info"
  1090. );
  1091. }
  1092. },
  1093. // Get item name
  1094. getItemName(id) {
  1095. const item = this.staticGoods.find((item) => item.id == id);
  1096. return item ? item.name : `Unknown Item (${id})`;
  1097. },
  1098. // Get item icon
  1099. getItemIcon(id) {
  1100. const item = this.staticGoods.find((item) => item.id == id);
  1101. if (item && item.icon) {
  1102. const iconPath = `imgs/${item.icon}.png`;
  1103. console.log(`Loading icon for item ${id}: ${iconPath}`);
  1104. return iconPath;
  1105. }
  1106. console.log(`No icon found for item ${id}, using default`);
  1107. return "imgs/default_icon.png";
  1108. },
  1109. // Handle image error
  1110. handleImageError(event) {
  1111. console.log("Image load error:", event.target.src);
  1112. // Replace with fallback icon
  1113. const parent = event.target.parentElement;
  1114. if (parent) {
  1115. parent.innerHTML =
  1116. '<i class="fas fa-question text-white/50 text-xl"></i>';
  1117. }
  1118. },
  1119. // Send all rewards
  1120. sendAllRewards() {
  1121. if (this.addressList.length === 0) {
  1122. this.showToast(
  1123. "Please add wallet addresses first",
  1124. "alert-error"
  1125. );
  1126. return;
  1127. }
  1128. if (this.waitAddRewardList.length === 0) {
  1129. this.showToast("Please add rewards first", "alert-error");
  1130. return;
  1131. }
  1132. // Show password verification dialog
  1133. this.showPasswordDialog = true;
  1134. this.password = "";
  1135. this.reason = "";
  1136. },
  1137. // Verify password and proceed with airdrop
  1138. async verifyPassword() {
  1139. if (!this.password.trim()) {
  1140. this.showToast("Please enter password", "alert-error");
  1141. return;
  1142. }
  1143. if (!this.reason.trim()) {
  1144. this.showToast("Please enter reason", "alert-error");
  1145. return;
  1146. }
  1147. this.isVerifying = true;
  1148. let goodList = [];
  1149. for (const item of this.waitAddRewardList) {
  1150. goodList.push({
  1151. id: item.id,
  1152. count: item.count,
  1153. });
  1154. }
  1155. try {
  1156. // Build send data
  1157. const sendData = {
  1158. reason: this.reason,
  1159. walletList: this.addressList,
  1160. goodList: goodList,
  1161. password: this.password,
  1162. };
  1163. console.log("Sending data to server:", sendData);
  1164. this.showToast(
  1165. `Verifying password and preparing airdrop...`,
  1166. "alert-info"
  1167. );
  1168. // Call server API for password verification and airdrop
  1169. const response = await fetch(
  1170. `${API_BASE_URL}/pog-service/callback/add/good`,
  1171. {
  1172. method: "POST",
  1173. headers: {
  1174. "Content-Type": "application/json",
  1175. },
  1176. body: JSON.stringify(sendData),
  1177. }
  1178. );
  1179. const result = await response.json();
  1180. if (response.ok) {
  1181. this.closePasswordModal();
  1182. this.showToast(
  1183. "Airdrop completed successfully!",
  1184. "alert-success"
  1185. );
  1186. console.log("Airdrop result:", result);
  1187. } else {
  1188. this.showToast(
  1189. result.message ||
  1190. "Airdrop failed. Please check your password and try again.",
  1191. "alert-error"
  1192. );
  1193. this.password = "";
  1194. }
  1195. } catch (error) {
  1196. console.error("Airdrop error:", error);
  1197. this.showToast(
  1198. "Network error. Please check your connection and try again.",
  1199. "alert-error"
  1200. );
  1201. this.password = "";
  1202. } finally {
  1203. this.isVerifying = false;
  1204. }
  1205. },
  1206. // Close password modal
  1207. closePasswordModal() {
  1208. this.showPasswordDialog = false;
  1209. this.password = "";
  1210. this.reason = "";
  1211. this.isVerifying = false;
  1212. },
  1213. closeModal() {
  1214. this.showAddRewardDialog = false;
  1215. this.selectedReward = "";
  1216. this.rewardQuantity = "";
  1217. },
  1218. showToast(message, type = "alert-info") {
  1219. // Hide existing toast first
  1220. this.toast.show = false;
  1221. // Show new toast after a brief delay
  1222. setTimeout(() => {
  1223. this.toast = {
  1224. show: true,
  1225. type: type,
  1226. message: message,
  1227. };
  1228. // Auto hide after 4 seconds
  1229. setTimeout(() => {
  1230. this.toast.show = false;
  1231. }, 4000);
  1232. }, 100);
  1233. },
  1234. // Load history data
  1235. async loadHistory() {
  1236. this.isLoadingHistory = true;
  1237. try {
  1238. const response = await fetch(
  1239. `${API_BASE_URL}/pog-service/callback/add/good/page?page=${this.currentPage}`,
  1240. {
  1241. method: "GET",
  1242. headers: {
  1243. "Content-Type": "application/json",
  1244. },
  1245. }
  1246. );
  1247. const result = await response.json();
  1248. if (response.ok && result.success) {
  1249. this.historyData = result.data.content || [];
  1250. // Assuming the API returns total pages or we can calculate it
  1251. // For now, we'll set a default total pages
  1252. this.totalPages = Math.max(
  1253. 1,
  1254. Math.ceil(this.historyData.length / 10)
  1255. );
  1256. this.showToast("History loaded successfully", "alert-success");
  1257. } else {
  1258. this.showToast(
  1259. result.message || "Failed to load history",
  1260. "alert-error"
  1261. );
  1262. }
  1263. } catch (error) {
  1264. console.error("History loading error:", error);
  1265. this.showToast(
  1266. "Network error. Please check your connection and try again.",
  1267. "alert-error"
  1268. );
  1269. } finally {
  1270. this.isLoadingHistory = false;
  1271. }
  1272. },
  1273. // Change page
  1274. changePage(newPage) {
  1275. if (newPage >= 1 && newPage <= this.totalPages) {
  1276. this.currentPage = newPage;
  1277. this.loadHistory();
  1278. }
  1279. },
  1280. // Parse good list from JSON string
  1281. parseGoodList(goodListString) {
  1282. try {
  1283. return JSON.parse(goodListString);
  1284. } catch (error) {
  1285. console.error("Error parsing good list:", error);
  1286. return [];
  1287. }
  1288. },
  1289. // Format date - Display UTC time directly
  1290. formatDate(dateString) {
  1291. try {
  1292. const date = new Date(dateString);
  1293. // Format as UTC time: YYYY-MM-DD HH:mm:ss
  1294. const year = date.getUTCFullYear();
  1295. const month = String(date.getUTCMonth() + 1).padStart(2, "0");
  1296. const day = String(date.getUTCDate()).padStart(2, "0");
  1297. const hours = String(date.getUTCHours()).padStart(2, "0");
  1298. const minutes = String(date.getUTCMinutes()).padStart(2, "0");
  1299. const seconds = String(date.getUTCSeconds()).padStart(2, "0");
  1300. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} UTC`;
  1301. } catch (error) {
  1302. return dateString;
  1303. }
  1304. },
  1305. // Close history modal
  1306. closeHistoryModal() {
  1307. this.showHistoryDialog = false;
  1308. this.historyData = [];
  1309. this.currentPage = 1;
  1310. this.totalPages = 1;
  1311. },
  1312. },
  1313. mounted() {
  1314. const m = {
  1315. 1: {
  1316. name: "POG",
  1317. count: 100,
  1318. icon: "icon_pog",
  1319. },
  1320. 2: {
  1321. name: "Diamond",
  1322. count: 100,
  1323. icon: "icon_gem",
  1324. },
  1325. 3: {
  1326. name: "GAME_SHARD",
  1327. count: 100,
  1328. icon: "icon_puzzle",
  1329. },
  1330. 5: {
  1331. name: "CRIT_CARD",
  1332. count: 100,
  1333. icon: "icon_crit",
  1334. },
  1335. 6: {
  1336. name: "FREE_ITEM_BOX",
  1337. count: 100,
  1338. icon: "icon_item_box",
  1339. },
  1340. 7: {
  1341. name: "ITEM_BOX",
  1342. count: 100,
  1343. icon: "icon_item_box",
  1344. },
  1345. 8: {
  1346. name: "FREE_POG_BOX",
  1347. count: 100,
  1348. icon: "icon_pog_box",
  1349. },
  1350. 9: {
  1351. name: "POG_BOX",
  1352. count: 100,
  1353. icon: "icon_pog_box",
  1354. },
  1355. };
  1356. this.staticGoods = [];
  1357. for (const key in m) {
  1358. this.staticGoods.push({
  1359. id: key,
  1360. name: m[key].name,
  1361. count: m[key].count,
  1362. icon: m[key].icon,
  1363. });
  1364. }
  1365. console.log(this.staticGoods);
  1366. },
  1367. }).mount("#app");
  1368. </script>
  1369. </body>
  1370. </html>