购买 CDK 入口更醒目 + 跳转前弹流程说明
ci/woodpecker/tag/build Pipeline was successful

入口视觉强化:
- 从 <a> 改为 <button>(点击先进说明弹窗,由弹窗确认后才跳转)
- 内边距 p-4→py-5、图标 11→12、标题 15px→base/extrabold、箭头 8→9
- 增加呼吸光环动画 cdk-pulse(prefers-reduced-motion 下自动关闭)

跳转前说明弹窗(新增 showCdkBuyConfirm):
按用户要求提示完整流程——
  1. 在购买平台购买卡密,需填写并牢记「邮箱」与「查询密码」
  2. 支付成功后,用同样的邮箱与查询密码在平台查询 CDK
  3. 回到本站,在「兑换次数 / VIP」处粘贴 CDK 兑换
并提示购买平台为独立站点、兑换码勿泄露。

跳转方式:确认后在 DOM 中创建临时 <a target=_blank> 并 click(),
而非 window.open —— 手机浏览器会把程序化 window.open 当弹窗强制
当前页打开,原生链接行为更稳定。
This commit is contained in:
becomer
2026-10-21 13:10:53 +08:00
parent b503f44a2d
commit fab756a1ef
+123 -14
View File
@@ -86,6 +86,24 @@ const vipExpireAt = ref('')
// 「购买 CDK」入口(后台系统设置控制:跳转链接 + 是否展示;visible 且 url 非空才显示)
const cdkBuyUrl = ref('')
const cdkBuyVisible = ref(false)
// 跳转购买平台前的说明弹窗:告知购买流程(需邮箱+查询密码,支付后回本站兑换)
const showCdkBuyConfirm = ref(false)
function onCdkBuyClick() {
if (!cdkBuyUrl.value) return
showCdkBuyConfirm.value = true
}
// 确认后在新标签打开购买平台。用真实 <a> 动态点击而非 window.open,
// 避免手机浏览器把它当弹窗强制当前页打开。
function confirmCdkBuy() {
showCdkBuyConfirm.value = false
const a = document.createElement('a')
a.href = cdkBuyUrl.value
a.target = '_blank'
a.rel = 'noopener noreferrer'
document.body.appendChild(a)
a.click()
a.remove()
}
// 验证码
const captchaId = ref('')
const captchaImage = ref('')
@@ -444,28 +462,27 @@ async function handleRedeemInvite() {
<!-- ======== 购买 CDK(显眼入口:后台系统设置控制显隐与链接) ========
用真实 <a target=_blank> 而非 window.open:手机浏览器会把程序化 window.open
当弹窗强制当前页打开,原生链接才能稳定新 tab(微信 WebView 等无 tab 环境除外) -->
<a
<button
v-if="cdkBuyVisible"
:href="cdkBuyUrl"
target="_blank"
rel="noopener noreferrer"
class="cdk-buy-btn press relative w-full overflow-hidden rounded-2xl p-4 flex items-center gap-3 rise-in"
type="button"
class="cdk-buy-btn press relative w-full overflow-hidden rounded-2xl px-4 py-5 flex items-center gap-3 rise-in"
style="animation-delay: 80ms"
@click="onCdkBuyClick"
>
<div class="relative z-10 w-11 h-11 rounded-2xl bg-white/20 ring-1 ring-white/30 flex items-center justify-center flex-shrink-0">
<ShoppingCart class="w-5 h-5 text-white" :stroke-width="2.4" />
<div class="relative z-10 w-12 h-12 rounded-2xl bg-white/20 ring-1 ring-white/30 flex items-center justify-center flex-shrink-0">
<ShoppingCart class="w-6 h-6 text-white" :stroke-width="2.4" />
</div>
<div class="relative z-10 min-w-0 flex-1">
<p class="text-[15px] font-bold text-white leading-tight tracking-tight">购买 CDK 兑换码</p>
<p class="text-xs text-white/85 mt-1">次数卡 · VIP 卡 · 即买即兑</p>
<div class="relative z-10 min-w-0 flex-1 text-left">
<p class="text-base font-extrabold text-white leading-tight tracking-tight">购买 CDK 兑换码</p>
<p class="text-xs text-white/90 mt-1">次数卡 · VIP 卡 · 即买即兑</p>
</div>
<span class="cdk-buy-hot relative z-10 flex-shrink-0 flex items-center gap-0.5 px-2 py-1 rounded-full text-[10px] font-extrabold tracking-wide">
<Flame class="w-3 h-3" :stroke-width="2.5" />HOT
</span>
<span class="relative z-10 flex-shrink-0 w-8 h-8 rounded-full bg-white/20 flex items-center justify-center">
<ChevronRight class="w-[18px] h-[18px] text-white" :stroke-width="2.5" />
<span class="relative z-10 flex-shrink-0 w-9 h-9 rounded-full bg-white/20 flex items-center justify-center">
<ChevronRight class="w-5 h-5 text-white" :stroke-width="2.5" />
</span>
</a>
</button>
<!-- ======== 数据三宫格:可执行次数 / 邀请积分 / 已邀请 ======== -->
<div class="grid grid-cols-3 gap-2.5 rise-in" style="animation-delay: 120ms">
@@ -750,6 +767,67 @@ async function handleRedeemInvite() {
</div>
</AppDialog>
<!-- ======== 购买 CDK 前说明弹窗 ========
用户点「购买 CDK 兑换码」后先看到购买/兑换全流程说明,
确认后才跳转外部购买平台,避免用户不清楚要准备邮箱与查询密码 -->
<AppDialog
:open="showCdkBuyConfirm"
variant="center"
panel-class="w-[88%] sm:max-w-md"
title="购买 CDK 兑换码"
@update:open="(v: boolean) => { if (!v) showCdkBuyConfirm = false }"
>
<div class="p-5">
<div class="flex items-center gap-3 mb-4">
<div class="w-11 h-11 rounded-2xl cdk-buy-icon flex items-center justify-center shrink-0">
<ShoppingCart class="w-5 h-5 text-white" :stroke-width="2.4" />
</div>
<div>
<h3 class="text-base font-semibold text-gray-900">即将跳转 CDK 购买平台</h3>
<p class="text-xs text-gray-400 mt-0.5">购买完成后请回到本站兑换</p>
</div>
</div>
<div class="rounded-xl bg-gray-50 p-4 space-y-3">
<div class="flex gap-2.5">
<span class="cdk-step-no">1</span>
<div class="min-w-0">
<p class="text-sm font-medium text-gray-800">在购买平台购买卡密</p>
<p class="text-xs text-gray-500 mt-0.5">购买平台需要填写<strong class="text-gray-700">邮箱</strong>和<strong class="text-gray-700">查询密码</strong>,请牢记这两项</p>
</div>
</div>
<div class="flex gap-2.5">
<span class="cdk-step-no">2</span>
<div class="min-w-0">
<p class="text-sm font-medium text-gray-800">支付成功后查询卡密</p>
<p class="text-xs text-gray-500 mt-0.5">在购买平台输入<strong class="text-gray-700">邮箱</strong>和<strong class="text-gray-700">查询密码</strong>查询你拿到的 CDK</p>
</div>
</div>
<div class="flex gap-2.5">
<span class="cdk-step-no">3</span>
<div class="min-w-0">
<p class="text-sm font-medium text-gray-800">回到本站兑换</p>
<p class="text-xs text-gray-500 mt-0.5">在下方「兑换次数 / VIP」处粘贴 CDK 完成兑换</p>
</div>
</div>
</div>
<div class="mt-4 flex items-start gap-1.5 text-xs text-amber-600 bg-amber-50 rounded-lg p-2.5">
<TriangleAlert class="w-3.5 h-3.5 mt-0.5 shrink-0" />
<span>购买平台为独立站点,兑换码请勿泄露给他人</span>
</div>
<div class="flex gap-2 mt-5">
<Button variant="outline" class="flex-1 h-10 text-sm" @click="showCdkBuyConfirm = false">
取消
</Button>
<Button class="flex-1 h-10 text-sm font-medium" @click="confirmCdkBuy">
前往购买平台
</Button>
</div>
</div>
</AppDialog>
<!-- 随机绑定设备码:连续两次风险确认弹窗 -->
<AppDialog
:open="randBindStep > 0"
@@ -904,7 +982,16 @@ async function handleRedeemInvite() {
.cdk-buy-btn {
color: #fff;
background: linear-gradient(135deg, #ff9a3d 0%, #ff5d5d 55%, #f43f5e 100%);
box-shadow: 0 8px 20px -6px rgb(244 63 94 / 0.45);
box-shadow: 0 10px 26px -6px rgb(244 63 94 / 0.55), 0 0 0 1px rgb(255 255 255 / 0.18) inset;
animation: cdk-pulse 2.4s ease-in-out infinite;
}
/* 呼吸光环:让入口在列表中更醒目 */
@keyframes cdk-pulse {
0%, 100% { box-shadow: 0 10px 26px -6px rgb(244 63 94 / 0.55), 0 0 0 0 rgb(244 63 94 / 0.45); }
50% { box-shadow: 0 10px 26px -6px rgb(244 63 94 / 0.55), 0 0 0 6px rgb(244 63 94 / 0); }
}
@media (prefers-reduced-motion: reduce) {
.cdk-buy-btn { animation: none; }
}
.cdk-buy-btn::after {
content: '';
@@ -924,6 +1011,28 @@ async function handleRedeemInvite() {
pointer-events: none;
}
/* 弹窗内的购买图标底色(与入口按钮同色系) */
.cdk-buy-icon {
background: linear-gradient(135deg, #ff9a3d 0%, #ff5d5d 55%, #f43f5e 100%);
box-shadow: 0 6px 16px -6px rgb(244 63 94 / 0.5);
}
/* 弹窗内的步骤序号 */
.cdk-step-no {
flex-shrink: 0;
width: 20px;
height: 20px;
border-radius: 9999px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
color: #fff;
background: linear-gradient(135deg, #ff9a3d 0%, #ff5d5d 100%);
margin-top: 1px;
}
/* HOT 角标:白底珊瑚红字(固定色,在渐变底上高对比) */
.cdk-buy-hot {
color: #e11d48;