This commit is contained in:
fengchuanhn@gmail.com
2026-05-23 10:19:24 +08:00
parent a6335ba712
commit 6ad45ed60c
5 changed files with 176 additions and 5 deletions

View File

@@ -0,0 +1,150 @@
<script setup>
import { computed, ref } from "vue";
import { useAuthStore } from "../stores/auth.js";
import { activateCardKeyApi } from "../api/cardKeys.js";
const auth = useAuthStore();
const serialNumber = ref("");
const loading = ref(false);
const feedback = ref({ severity: "", message: "" });
const vipEndLabel = computed(() => {
const raw = auth.currentUser?.vip_end_time;
if (!raw) return "未开通";
const d = new Date(raw);
if (Number.isNaN(d.getTime())) return "—";
return d.toLocaleString("zh-CN", { hour12: false });
});
const pointsLabel = computed(() => {
const p = auth.currentUser?.points;
if (p == null || Number.isNaN(Number(p))) return "0";
return String(p);
});
function showMessage(severity, message) {
feedback.value = { severity, message };
}
function formatActivateDetail(data) {
if (!data) return "";
if (data.card_type === "时长" && data.added_days != null) {
return `会员已延长 ${data.added_days}`;
}
if (data.card_type === "点数" && data.added_points != null) {
return `已获得 ${data.added_points} 点数`;
}
return "";
}
async function onSubmit() {
const serial = serialNumber.value.trim().toUpperCase();
if (!serial) {
showMessage("warn", "请输入卡密");
return;
}
loading.value = true;
feedback.value = { severity: "", message: "" };
const res = await activateCardKeyApi(auth.token, serial);
loading.value = false;
if (!res.ok) {
showMessage("error", res.message || "激活失败");
return;
}
serialNumber.value = "";
await auth.refreshProfile();
const detail = formatActivateDetail(res.data);
showMessage("success", detail ? `${res.message}${detail}` : res.message || "激活成功");
}
</script>
<template>
<div class="card-activate-page">
<div class="card-activate-page__header">
<h1 class="gradient-text text-xl font-semibold">卡密激活</h1>
<p class="mt-1 text-sm text-slate-400">输入卡密序列号兑换会员时长或点数</p>
</div>
<div class="card-activate-page__panel">
<div class="card-activate-page__status">
<div class="card-activate-page__status-item">
<span class="text-xs uppercase text-slate-500">会员到期</span>
<span class="text-sm text-slate-200">{{ vipEndLabel }}</span>
</div>
<div class="card-activate-page__status-item">
<span class="text-xs uppercase text-slate-500">当前点数</span>
<span class="text-sm text-slate-200">{{ pointsLabel }}</span>
</div>
</div>
<Message
v-if="feedback.message"
:severity="feedback.severity"
:closable="true"
class="w-full"
@close="feedback.message = ''"
>
{{ feedback.message }}
</Message>
<form class="flex flex-col gap-4" @submit.prevent="onSubmit">
<div class="flex flex-col gap-2">
<label for="card-serial" class="text-xs uppercase text-slate-500">卡密序列号</label>
<InputText
id="card-serial"
v-model="serialNumber"
placeholder="请输入卡密"
class="w-full font-mono uppercase tracking-wider"
autocomplete="off"
:disabled="loading"
@keyup.enter="onSubmit"
/>
</div>
<Button type="submit" label="立即激活" :loading="loading" class="w-full" />
</form>
</div>
</div>
</template>
<style scoped>
.card-activate-page {
max-width: 28rem;
margin: 0 auto;
padding: 1.5rem 1rem 2rem;
}
.card-activate-page__header {
margin-bottom: 1.5rem;
}
.card-activate-page__panel {
display: flex;
flex-direction: column;
gap: 1.25rem;
padding: 1.25rem;
border-radius: 0.75rem;
border: 1px solid rgb(255 255 255 / 0.1);
background: rgb(255 255 255 / 0.05);
}
.card-activate-page__status {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
}
.card-activate-page__status-item {
display: flex;
flex-direction: column;
gap: 0.25rem;
padding: 0.75rem;
border-radius: 0.5rem;
background: rgb(0 0 0 / 0.2);
}
</style>