180 lines
5.1 KiB
Vue
180 lines
5.1 KiB
Vue
<script setup>
|
||
import { computed, ref, watch } from "vue";
|
||
import { useRoute, useRouter } from "vue-router";
|
||
import { useAuthStore } from "../stores/auth.js";
|
||
import { activateCardKeyApi } from "../api/cardKeys.js";
|
||
import AuthLayout from "../components/AuthLayout.vue";
|
||
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
const auth = useAuthStore();
|
||
|
||
const username = ref("");
|
||
const serialNumber = ref("");
|
||
const loading = ref(false);
|
||
const feedback = ref({ severity: "", message: "" });
|
||
const resultSnapshot = ref(null);
|
||
|
||
const vipEndLabel = computed(() => {
|
||
const raw = resultSnapshot.value?.vip_end_time ?? 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 = resultSnapshot.value?.points ?? auth.currentUser?.points;
|
||
if (p == null || Number.isNaN(Number(p))) return "0";
|
||
return String(p);
|
||
});
|
||
|
||
const showStatus = computed(() => auth.isAuthenticated || resultSnapshot.value != null);
|
||
|
||
watch(
|
||
() => route.query.username,
|
||
(value) => {
|
||
if (typeof value === "string" && value.trim()) {
|
||
username.value = value.trim();
|
||
} else if (auth.currentUser?.username) {
|
||
username.value = auth.currentUser.username;
|
||
}
|
||
},
|
||
{ immediate: true },
|
||
);
|
||
|
||
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 name = username.value.trim();
|
||
const serial = serialNumber.value.trim().toUpperCase();
|
||
if (!name) {
|
||
showMessage("warn", "请输入用户名");
|
||
return;
|
||
}
|
||
if (!serial) {
|
||
showMessage("warn", "请输入卡密");
|
||
return;
|
||
}
|
||
|
||
loading.value = true;
|
||
feedback.value = { severity: "", message: "" };
|
||
|
||
const res = await activateCardKeyApi({ username: name, serialNumber: serial });
|
||
loading.value = false;
|
||
|
||
if (!res.ok) {
|
||
showMessage("error", res.message || "激活失败");
|
||
return;
|
||
}
|
||
|
||
serialNumber.value = "";
|
||
resultSnapshot.value = res.data ?? null;
|
||
|
||
if (auth.isAuthenticated && auth.currentUser?.username === name) {
|
||
await auth.refreshProfile();
|
||
}
|
||
|
||
const detail = formatActivateDetail(res.data);
|
||
showMessage("success", detail ? `${res.message}(${detail})` : res.message || "激活成功");
|
||
|
||
// if (auth.isAuthenticated && auth.currentUser?.username === name && !auth.isVipExpired) {
|
||
// router.replace({ name: "home" });
|
||
// }
|
||
}
|
||
|
||
function goLogin() {
|
||
router.push({ name: "login" });
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<AuthLayout title="卡密激活" subtitle="输入用户名与卡密序列号,兑换会员时长或点数">
|
||
<Message
|
||
v-if="feedback.message"
|
||
:severity="feedback.severity"
|
||
:closable="true"
|
||
class="mb-4 w-full"
|
||
@close="feedback.message = ''"
|
||
>
|
||
{{ feedback.message }}
|
||
</Message>
|
||
|
||
<div v-if="showStatus" class="card-activate-page__status mb-4">
|
||
<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>
|
||
|
||
<form class="flex flex-col gap-4" @submit.prevent="onSubmit">
|
||
<div class="flex flex-col gap-2">
|
||
<label for="card-username" class="font-mono text-xs text-text-muted uppercase">用户名</label>
|
||
<InputText
|
||
id="card-username"
|
||
v-model="username"
|
||
placeholder="请输入要激活的用户名"
|
||
autocomplete="username"
|
||
class="w-full"
|
||
:disabled="loading"
|
||
/>
|
||
</div>
|
||
<div class="flex flex-col gap-2">
|
||
<label for="card-serial" class="font-mono text-xs text-text-muted uppercase">卡密序列号</label>
|
||
<InputText
|
||
id="card-serial"
|
||
v-model="serialNumber"
|
||
placeholder="请输入卡密"
|
||
class="w-full font-mono uppercase tracking-wider"
|
||
autocomplete="off"
|
||
:disabled="loading"
|
||
/>
|
||
</div>
|
||
<Button type="submit" label="立即激活" :loading="loading" class="w-full" />
|
||
<Button
|
||
v-if="!auth.isAuthenticated"
|
||
type="button"
|
||
label="已有账号,去登录"
|
||
severity="secondary"
|
||
text
|
||
class="w-full"
|
||
@click="goLogin"
|
||
/>
|
||
</form>
|
||
</AuthLayout>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.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>
|