【修复】表单初始化,申请节点随机时间

This commit is contained in:
cai
2025-09-04 15:21:26 +08:00
parent 8e0c6ca125
commit e9515bb6ae
70 changed files with 3083 additions and 1488 deletions

View File

@@ -10,21 +10,21 @@
<body>
<div class="font-sans bg-white text-secondary">
<!-- 首屏Banner -->
<section class="pt-28 pb-20 md:pt-36 md:pb-28 bg-gradient-to-b">
<section class="pt-28 pb-20 md:pt-24 md:pb-28 bg-gradient-to-b">
<div class="container mx-auto px-4">
<div class="max-w-4xl mx-auto text-center">
<div class=" mx-auto text-center">
<h1 class="text-[clamp(2rem,5vw,3.5rem)] font-bold leading-tight text-dark mb-6 animate-fade-in">
堡塔<span class="text-primary">域名注册</span>重磅上线!<br />一站式搞定建站访问
</h1>
<p class="text-[clamp(1rem,2vw,1.25rem)] text-secondary-80 mb-10 max-w-2xl mx-auto">
.com低至54元,.cn低至20部分后缀新人9.9元注册
.com低至53.9元,.cn低至19.9部分后缀新人9.9元注册
</p>
<!-- 搜索框和按钮 -->
<div id="search-section" class="flex flex-col sm:flex-row max-w-3xl mx-auto mb-8 gap-4">
<div id="search-section" class="flex flex-col sm:flex-row max-w-3xl mx-auto gap-4 pb-20">
<div class="input-container">
<input type="text" placeholder="输入您想注册的域名yourbrand" class="search-input" id="domain-query-input" />
<i class="fa fa-times-circle clear-input-button" id="clear-input-button" title="清空输入"></i>
<div class="mt-2 text-left"><a class="text-primary hover:text-primary" style="text-decoration: none;" href="/domain/domain/transfer">域名转入 .cn地址31</a></div>
<div class="mt-2 text-left"><a class="text-primary hover:text-primary" style="text-decoration: none;" href="/domain/domain/transfer">域名转入 .cn地址29.9</a></div>
</div>
<div class="flex gap-4">
<a id="domain-query-button"
@@ -37,53 +37,160 @@
</a>
</div>
</div>
</div>
<!-- 价格表格 -->
<div class="mt-24 md:mt-36 mx-auto max-w-[900px]">
<div class="text-center mb-8">
<h2 class="text-clamp font-bold text-dark mb-4" style="margin-top: 60px">域名价格一览表</h2>
<div class="text-secondary-80 max-w-2xl mx-auto px-4">
透明的价格体系,无隐藏费用,让您明明白白消费,更多服务<span
class="contact-service-trigger border-b border-dashed border-primary cursor-pointer relative">请联系客服咨询
<!-- 二维码悬浮层 -->
<div
class="qr-code-popup absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 opacity-0 invisible transition-all duration-300 z-50">
<div class="bg-white rounded-lg shadow-xl p-4 border border-gray-200">
<div class="text-center mb-2">
<p class="text-sm font-medium text-gray-700">扫码联系客服</p>
<!-- 0.01秒杀活动区域 -->
<div class="seckill-activity-section py-20" id="seckill-activity" style="display: none;">
<div class="container">
<!-- 活动标题与规则说明 -->
<div class="activity-header text-center mb-6">
<h2 class="activity-title text-2xl md:text-3xl font-bold text-primary mb-3">
开学季专属福利 · 0.01元限量秒杀
</h2>
<div class="activity-rules flex flex-col sm:flex-row items-center justify-center gap-4 text-sm text-secondary-70">
<div class="rule-item flex items-center gap-2">
<i class="fa fa-star text-primary"></i>
<span>每日限量100个名额</span>
</div>
<div class="rule-item flex items-center gap-2">
<i class="fa fa-ban text-primary"></i>
<span>每个账号仅限参与1次</span>
</div>
<div class="rule-item flex items-center gap-2">
<i class="fa fa-ticket text-primary"></i>
<span>领取资格后即可使用</span>
</div>
</div>
</div>
<!-- 扁平化商品卡片 -->
<div class="seckill-flat-card">
<div class="flat-card-container">
<!-- 左侧:产品信息区 -->
<div class="product-info-section">
<div class="product-badge">
<i class="fa fa-fire"></i>
<span>10:00准时开抢</span>
</div>
<!-- 二维码SVG -->
<img src="https://www.bt.cn/Public/new/images/wechat-qrcode.png" alt="二维码"
class="w-24 h-24 mx-auto" />
<div class="text-center mt-2">
<p class="text-xs text-gray-500">微信客服</p>
<div class="product-content">
<h3 class="product-name">域名注册 0.01元秒杀</h3>
<p class="product-desc">.top/.icu/.xyz/.cyou后缀每日限量100个名额每日上午10:00准时开枪</p>
</div>
<!-- 小箭头 -->
<div
class="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-white">
</div>
<!-- 右侧购买信息区水平4列 -->
<div class="purchase-info-section">
<!-- 第1列购买时长 -->
<div class="info-column">
<div class="column-label">购买时长</div>
<div class="column-value">1年</div>
</div>
<!-- 第2列购买数量 -->
<div class="info-column">
<div class="column-label">购买数量</div>
<div class="column-value">1</div>
</div>
<!-- 第3列价格信息 -->
<div class="info-column price-column">
<div class="current-price-info">
<span class="price-symbol">¥</span>
<span class="price-number">0.01</span>
<span class="price-unit">元/1年</span>
</div>
<div class="original-price-info">
<span>官网价格:</span>
<span class="strikethrough">¥97.9元/1年</span>
</div>
</div>
<!-- 第4列倒计时与操作 -->
<div class="info-column action-column">
<!-- 进度信息(活动开始后显示,替代倒计时) -->
<div class="progress-info" id="progress-info-section" style="display: none;">
<span class="progress-text" id="progress-text-flat">已售0%</span>
<div class="progress-bar-mini">
<div class="progress-fill-mini" id="progress-fill-flat" style="width: 0%"></div>
</div>
</div>
<!-- 倒计时区域(活动开始前显示) -->
<div class="countdown-section" id="countdown-section">
<div class="countdown-label-mini">距离下次开抢</div>
<div class="countdown-display-mini">
<span class="time-digit-mini" id="countdown-hours-flat">00</span>
<span class="time-sep">:</span>
<span class="time-digit-mini" id="countdown-minutes-flat">00</span>
<span class="time-sep">:</span>
<span class="time-digit-mini" id="countdown-seconds-flat">00</span>
</div>
</div>
<button class="seckill-btn-flat" id="seckill-action-btn-flat">
<span class="btn-text">即将开始</span>
<div class="btn-loading" style="display: none;">
<i class="fa fa-spinner fa-spin"></i>
</div>
</button>
<div class="action-tips-mini" id="action-tips-flat">
活动即将开始,请耐心等待
</div>
</div>
<div class="action-tips-mini action-tips-footer-info">
* 活动截止时间是2025年9月15日抢到秒杀资格后请尽快使用
</div>
</div>
</div>
</span>
</div>
</div>
</div>
<div class="overflow-x-auto px-4 md:px-0">
<!-- 现代化价格表格 -->
<div class="modern-price-table bg-white overflow-hidden shadow-lg">
<!-- 表头 -->
<div class="price-table-header bg-gradient-to-r from-primary to-green-600 text-white py-4 px-4">
<div class="grid grid-cols-4 gap-4 text-center">
<div class="text-sm md:text-base font-semibold">域名后缀</div>
<div class="text-sm md:text-base font-semibold">首年价格</div>
<div class="text-sm md:text-base font-semibold">续费价格</div>
<div class="text-sm md:text-base font-semibold">转入续费</div>
</div>
<!-- 价格表格 -->
<div class="mx-auto max-w-[900px]">
<div class="text-center mb-8">
<h2 class="text-clamp font-bold text-dark mb-4" style="margin-top: 80px">域名价格一览表</h2>
<div class="text-secondary-80 max-w-4xl mx-auto px-4">
透明的价格体系,无隐藏费用,让您明明白白消费,更多服务请<a class="border-b border-dashed border-primary text-secondary-80" href="https://qm.qq.com/q/fxbto4wZkk" target="_blank" rel="noopener" style="text-decoration: none;">加入QQ群</a>或者<span
class="contact-service-trigger cursor-pointer border-b border-dashed border-primary relative">请联系客服咨询
<!-- 二维码悬浮层 -->
<div
class="qr-code-popup absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 opacity-0 invisible transition-all duration-300 z-50">
<div class="bg-white rounded-lg shadow-xl p-4 border border-gray-200">
<div class="text-center mb-2">
<p class="text-sm font-medium text-gray-700">扫码联系客服</p>
</div>
<!-- 二维码SVG -->
<img src="https://www.bt.cn/Public/new/images/wechat-qrcode.png" alt="二维码"
class="w-24 h-24 mx-auto" />
<div class="text-center mt-2">
<p class="text-xs text-gray-500">微信客服</p>
</div>
<!-- 小箭头 -->
<div
class="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-white">
</div>
</div>
</div>
</span>
</div>
<!-- 表格内容 -->
<div class="price-table-body" id="price-table-body">
<!-- 动态生成的价格表格内容 -->
</div>
<div class="overflow-x-auto px-4 md:px-0">
<!-- 现代化价格表格 -->
<div class="modern-price-table bg-white overflow-hidden shadow-lg">
<!-- 表头 -->
<div class="price-table-header bg-gradient-to-r from-primary to-green-600 text-white py-4 px-4">
<div class="grid grid-cols-4 gap-4 text-center">
<div class="text-sm md:text-base font-semibold">域名后缀</div>
<div class="text-sm md:text-base font-semibold">首年价格</div>
<div class="text-sm md:text-base font-semibold">续费价格</div>
<div class="text-sm md:text-base font-semibold">转入续费</div>
</div>
</div>
<!-- 表格内容 -->
<div class="price-table-body" id="price-table-body">
<!-- 动态生成的价格表格内容 -->
</div>
</div>
</div>
</div>

View File

@@ -41,6 +41,9 @@ import type {
OrderDetailRequest,
OrderDetailResponseData,
} from "../types/api-types/order-detail";
import type {
SeckillActivityInfoResponseData,
} from "../types/api-types/flashsale";
// 落地页-域名查询
export function domainQueryCheck(
@@ -154,6 +157,15 @@ export function getOrderDetail(
return api.post<OrderDetailResponseData>("/v1/order/detail", data, headers);
}
// 获取今日秒杀活动信息
export function getSeckillActivityInfo(): Promise<ApiResponse<SeckillActivityInfoResponseData>> {
return api.post<SeckillActivityInfoResponseData>("v1/user/flashsale/get_today_info",{});
}
// 领取秒杀
export function grabSeckill(): Promise<ApiResponse> {
return api.post("v1/user/flashsale/grab_coupon", {});
}
/**
* WHOIS查询API
* @param domain 域名

View File

@@ -4,66 +4,615 @@ import { renderTemplateList } from "@utils/core";
import type { DomainPrice } from "@types";
import { NotificationManager } from "@utils";
import { bindContactServicePopupClick } from "@utils";
import { getSeckillActivityInfo, grabSeckill } from "../api/landing";
window.isLoggedIn = localStorage.getItem("isLogin") === "true";
// window.isLoggedIn = localStorage.getItem("isLogin") === "true";
window.isLoggedIn = true;
const isDev = (): boolean => process.env.NODE_ENV === "development";
/**
* 秒杀活动状态枚举
*/
enum SeckillStatus {
NOT_STARTED = 'not_started', // 未开始
CAN_QUALIFY = 'can_qualify', // 可领资格
CAN_SECKILL = 'can_seckill', // 可秒杀
PARTICIPATED = 'participated', // 已参与
SOLD_OUT = 'sold_out' // 已抢完
}
/**
* 秒杀活动数据接口
*/
interface SeckillActivityData {
startTime: string; // 开始时间 (HH:mm 格式)
totalQuota: number; // 总配额
grabbedCount: number; // 已抢数量
userStatus: SeckillStatus; // 用户状态
isActive: boolean; // 活动是否激活
}
/**
* 秒杀活动数据适配器
*/
class SeckillDataAdapter {
/**
* 映射API状态到前端状态
*/
static mapGrabStatusToSeckillStatus(grabStatus: number, isLoggedIn: boolean): SeckillStatus {
switch(grabStatus) {
case 0: // 可抢
return isLoggedIn ? SeckillStatus.CAN_SECKILL : SeckillStatus.CAN_QUALIFY;
case 1: // 已抢到未使用
case 2: // 已使用
return SeckillStatus.PARTICIPATED;
case 3: // 活动未开始
return SeckillStatus.NOT_STARTED;
case 4: // 活动已结束
case 5: // 已抢完
return SeckillStatus.SOLD_OUT;
default:
return SeckillStatus.NOT_STARTED;
}
}
}
/**
* 倒计时管理器
*/
class SeckillTimer {
private targetTime: Date;
private timer: number | null = null;
private onUpdate: ((timeLeft: { hours: number; minutes: number; seconds: number }) => void) | null = null;
private onComplete: (() => void) | null = null;
constructor(targetHour: number = 10, targetMinute: number = 0) {
this.targetTime = this.calculateNextTarget(targetHour, targetMinute);
}
/**
* 计算下一个目标时间
*/
private calculateNextTarget(hour: number, minute: number): Date {
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hour, minute, 0);
// 如果今天的时间已过,计算明天的时间
if (today <= now) {
today.setDate(today.getDate() + 1);
}
return today;
}
/**
* 开始倒计时
*/
start(onUpdate?: (timeLeft: { hours: number; minutes: number; seconds: number }) => void, onComplete?: () => void): void {
this.onUpdate = onUpdate || null;
this.onComplete = onComplete || null;
this.tick();
}
/**
* 停止倒计时
*/
stop(): void {
if (this.timer) {
window.clearTimeout(this.timer);
this.timer = null;
}
}
/**
* 倒计时逻辑
*/
private tick(): void {
const now = new Date();
const timeLeft = this.targetTime.getTime() - now.getTime();
if (timeLeft <= 0) {
// 倒计时结束
if (this.onComplete) {
this.onComplete();
}
return;
}
const hours = Math.floor(timeLeft / (1000 * 60 * 60));
const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);
if (this.onUpdate) {
this.onUpdate({ hours, minutes, seconds });
}
this.timer = window.setTimeout(() => this.tick(), 1000);
}
}
/**
* 秒杀活动状态管理器
*/
class SeckillStateManager {
private currentStatus: SeckillStatus = SeckillStatus.NOT_STARTED;
private activityData!: SeckillActivityData;
private clickDebounceTimer: number | null = null;
constructor() {
// 初始化默认状态等待API数据
this.setupDefaultData();
}
/**
* 异步初始化API数据
*/
async initialize(): Promise<void> {
await this.initWithApiData();
}
/**
* 通过API初始化活动数据
*/
async initWithApiData(): Promise<void> {
try {
const response = await getSeckillActivityInfo();
if (response.status === true && response.data) {
const { grab_status, remaining_coupons, total_coupons } = response.data;
// 设置初始状态
const isLoggedIn = (window as any).isLoggedIn;
this.currentStatus = SeckillDataAdapter.mapGrabStatusToSeckillStatus(
grab_status,
isLoggedIn,
);
// 设置活动数据(保持现有结构)
this.activityData = {
startTime: "10:00", // 保持现有逻辑
totalQuota: total_coupons,
grabbedCount: total_coupons - remaining_coupons,
userStatus: this.currentStatus,
isActive: true // 保持现有逻辑
};
} else {
// API调用失败使用默认数据
this.setupDefaultData();
}
} catch (error) {
console.error('获取活动数据失败:', error);
// 错误时使用默认数据
this.setupDefaultData();
}
}
/**
* 设置默认数据
*/
private setupDefaultData(): void {
// 使用默认数据,保持现有逻辑
const randomGrabbedCount = Math.floor(Math.random() * 30) + 5;
this.activityData = {
startTime: "10:00",
totalQuota: 100,
grabbedCount: randomGrabbedCount,
userStatus: SeckillStatus.NOT_STARTED,
isActive: true
};
this.currentStatus = SeckillStatus.NOT_STARTED;
}
/**
* 更新状态
*/
updateStatus(newStatus: SeckillStatus): void {
this.currentStatus = newStatus;
}
/**
* 获取当前状态
*/
getCurrentStatus(): SeckillStatus {
return this.currentStatus;
}
/**
* 获取活动数据
*/
getActivityData(): SeckillActivityData {
return { ...this.activityData };
}
/**
* 更新进度
*/
updateProgress(grabbedCount: number): void {
this.activityData.grabbedCount = Math.min(grabbedCount, this.activityData.totalQuota);
}
/**
* 防重复点击
*/
debounceClick(callback: () => void, delay: number = 3000): void {
if (this.clickDebounceTimer) {
return;
}
callback();
this.clickDebounceTimer = window.setTimeout(() => {
this.clickDebounceTimer = null;
}, delay);
}
}
/**
* 秒杀活动主类
*/
class SeckillActivity {
private timer: SeckillTimer;
private stateManager: SeckillStateManager;
private $container: any;
private $btn: any;
private $btnText: any;
private $btnLoading: any;
private $tips: any;
private $progressBar: any;
private $progressText: any;
private $countdownSection: any;
private $progressSection: any;
constructor() {
// 倒计时时间10:00
this.timer = new SeckillTimer(10, 0);
this.stateManager = new SeckillStateManager();
this.$container = $("#seckill-activity");
this.$btn = $("#seckill-action-btn-flat");
this.$btnText = this.$btn.find(".btn-text");
this.$btnLoading = this.$btn.find(".btn-loading");
this.$tips = $("#action-tips-flat");
this.$progressBar = $("#progress-fill-flat");
this.$progressText = $("#progress-text-flat");
this.$countdownSection = $("#countdown-section");
this.$progressSection = $("#progress-info-section");
}
/**
* 初始化活动
*/
async init(): Promise<void> {
// 等待状态管理器数据加载完成
await this.stateManager.initialize();
this.renderInitialState();
this.bindEvents();
this.startTimer();
this.checkActivityVisibility();
}
/**
* 刷新活动状态和数据用于API数据更新后重新渲染
*/
refreshState(): void {
this.renderInitialState();
}
/**
* 检查活动显示状态
*/
private checkActivityVisibility(): void {
// 模拟检查活动是否应该显示的逻辑
const shouldShow = true; // 可以根据实际需求调整逻辑
if (shouldShow) {
this.$container.show().addClass('animate-fade-in');
}
}
/**
* 渲染初始状态
*/
private renderInitialState(): void {
const status = this.stateManager.getCurrentStatus();
const data = this.stateManager.getActivityData();
this.updateButtonState(status);
// 根据状态决定显示倒计时还是进度条
if (status === SeckillStatus.NOT_STARTED) {
// 活动未开始,显示倒计时,隐藏进度条
this.$countdownSection.show();
this.$progressSection.hide();
} else {
// 活动已开始,隐藏倒计时,显示进度条
this.$countdownSection.hide();
this.$progressSection.show();
this.updateProgress(data.grabbedCount, data.totalQuota);
}
}
/**
* 绑定事件
*/
private bindEvents(): void {
this.$btn.on('click', () => {
this.stateManager.debounceClick(() => {
this.handleButtonClick();
});
});
}
/**
* 开始倒计时
*/
private startTimer(): void {
this.timer.start(
(timeLeft) => this.updateCountdown(timeLeft),
() => this.onTimerComplete()
);
}
/**
* 更新倒计时显示 - 适配扁平化布局
*/
private updateCountdown(timeLeft: { hours: number; minutes: number; seconds: number }): void {
const $hours = $("#countdown-hours-flat");
const $minutes = $("#countdown-minutes-flat");
const $seconds = $("#countdown-seconds-flat");
// 更新数字(无动画效果)
const updateDigit = ($element: any, value: number) => {
const newValue = value.toString().padStart(2, '0');
$element.text(newValue);
};
updateDigit($hours, timeLeft.hours);
updateDigit($minutes, timeLeft.minutes);
updateDigit($seconds, timeLeft.seconds);
// 时间紧迫时的特殊样式
const totalMinutes = timeLeft.hours * 60 + timeLeft.minutes;
if (totalMinutes < 10) {
$(".time-digit-mini").addClass('urgent');
} else {
$(".time-digit-mini").removeClass('urgent');
}
}
/**
* 倒计时完成处理
*/
private onTimerComplete(): void {
const isLoggedIn = (window as any).isLoggedIn;
const newStatus = isLoggedIn ? SeckillStatus.CAN_SECKILL : SeckillStatus.CAN_QUALIFY;
this.stateManager.updateStatus(newStatus);
this.updateButtonState(newStatus);
// 隐藏倒计时,显示进度条
this.$countdownSection.hide();
this.$progressSection.show();
// 更新进度条显示
const data = this.stateManager.getActivityData();
this.updateProgress(data.grabbedCount, data.totalQuota);
// 按钮闪烁提示
this.$btn.addClass('blink');
setTimeout(() => this.$btn.removeClass('blink'), 1500);
}
/**
* 更新按钮状态
*/
private updateButtonState(status: SeckillStatus): void {
// 清除所有状态类
this.$btn.removeClass('not-started can-qualify can-seckill participated sold-out loading');
const config = this.getButtonConfig(status);
this.$btn.addClass(config.className);
this.$btnText.text(config.text);
this.$tips.html(config.tips);
}
/**
* 获取按钮配置
*/
private getButtonConfig(status: SeckillStatus): { className: string; text: string; tips: string } {
const configs = {
[SeckillStatus.NOT_STARTED]: {
className: "not-started",
text: "即将开始",
tips: "活动即将开始,请耐心等待",
},
[SeckillStatus.CAN_QUALIFY]: {
className: "can-qualify",
text: "领取资格",
tips: "点击领取秒杀资格",
},
[SeckillStatus.CAN_SECKILL]: {
className: "can-seckill",
text: "立即领取",
tips: "限时秒杀进行中,立即抢购",
},
[SeckillStatus.PARTICIPATED]: {
className: "participated",
text: "已领取",
tips: "您已成功领取秒杀名额</br>快去<a href='/new/domain-query-register.html' class='text-primary hover:text-primary' target='_blank' rel='noopener'>注册域名</a>吧",
},
[SeckillStatus.SOLD_OUT]: {
className: "sold-out",
text: "已抢完",
tips: "今日名额已抢完,明日再来",
},
};
return configs[status];
}
/**
* 处理按钮点击
*/
private handleButtonClick(): void {
const status = this.stateManager.getCurrentStatus();
this.$btn.addClass('btn-click');
setTimeout(() => this.$btn.removeClass('btn-click'), 200);
switch (status) {
case SeckillStatus.CAN_QUALIFY:
this.handleQualifyAction();
break;
case SeckillStatus.CAN_SECKILL:
this.handleSeckillAction();
break;
default:
break;
}
}
/**
* 处理资格领取
*/
private handleQualifyAction(): void {
NotificationManager.show({
type: "warning",
message: "请先登录后再领取资格",
});
setTimeout(() => {
location.href = `/login.html?ReturnUrl=${location.href}`;
}, 2000);
}
/**
* 处理秒杀操作
*/
private async handleSeckillAction(): Promise<void> {
try {
this.setButtonLoading(true);
// 这里请求领取接口
const response = await grabSeckill() as any;
if (response.status === true) {
// 成功后更新状态
this.stateManager.updateStatus(SeckillStatus.PARTICIPATED);
// 更新进度(模拟增加一个已抢数量)
const data = this.stateManager.getActivityData();
const newGrabbedCount = data.grabbedCount + 1;
this.stateManager.updateProgress(newGrabbedCount);
this.updateProgress(newGrabbedCount, data.totalQuota);
// 显示成功消息
NotificationManager.show({
type: "success",
message: response.msg || "恭喜您!成功领取秒杀名额",
});
} else {
// 失败时显示错误消息
NotificationManager.show({
type: "error",
message: response.msg || "领取失败,请稍后重试",
});
}
} catch (error: any) {
NotificationManager.show({
type: "error",
message: error.message || "网络错误,请稍后重试",
});
} finally {
this.setButtonLoading(false);
}
}
/**
* 设置按钮加载状态
*/
private setButtonLoading(loading: boolean): void {
if (loading) {
this.$btn.addClass('loading');
this.$btnLoading.show();
} else {
this.$btn.removeClass('loading');
this.$btnLoading.hide();
const status = this.stateManager.getCurrentStatus();
this.updateButtonState(status);
}
}
/**
* 更新进度条 - 支持百分比显示
*/
private updateProgress(current: number, total: number): void {
const percentage = Math.min((current / total) * 100, 100);
this.$progressBar.css('width', `${percentage}%`);
// 显示百分比
if (percentage >= 100) {
this.$progressText.text('已抢100%');
} else {
this.$progressText.text(`已抢${Math.floor(percentage)}%`);
}
}
}
/**
* 域名价格数据 - 纯数据对象f
*/
const domainPriceData: DomainPrice[] = [
{
suffix: ".com",
originalPrice: 89,
firstYearPrice: 54,
renewPrice: 79,
transferPrice: 79,
},
{
suffix: ".net",
originalPrice: 99,
firstYearPrice: 86,
renewPrice: 89,
transferPrice: 89,
},
{
suffix: ".cn",
originalPrice: 39,
firstYearPrice: 20,
renewPrice: 34,
transferPrice: 31,
firstYearPrice: 19.9,
renewPrice: 33.9,
transferPrice: 29.9,
},
{
suffix: ".com",
originalPrice: 89,
firstYearPrice: 53.9,
renewPrice: 79,
transferPrice: 79,
},
{
suffix: ".top",
originalPrice: 49,
firstYearPrice: 9.9,
renewPrice: 31,
transferPrice: 31,
// isWan: true,
},
{
suffix: ".cyou",
originalPrice: 109,
firstYearPrice: 9.9,
renewPrice: 98,
transferPrice: 98,
// isWan: true,
},
{
suffix: ".icu",
originalPrice: 109,
firstYearPrice: 9.9,
renewPrice: 98,
transferPrice: 98,
renewPrice: 29.9,
transferPrice: 29.9,
// isWan: true,
},
{
suffix: ".xyz",
originalPrice: 109,
firstYearPrice: 9.9,
renewPrice: 92,
transferPrice: 92,
renewPrice: 91.9,
transferPrice: 91.9,
// isWan: true,
},
{
suffix: ".cyou",
originalPrice: 109,
firstYearPrice: 9.9,
renewPrice: 97.9,
transferPrice: 97.9,
// isWan: true,
},
{
suffix: ".icu",
originalPrice: 109,
firstYearPrice: 9.9,
renewPrice: 97.9,
transferPrice: 97.9,
// isWan: true,
},
{
suffix: ".net",
originalPrice: 99,
firstYearPrice: 85.9,
renewPrice: 85.9,
transferPrice: 85.9,
},
];
/**
@@ -178,8 +727,8 @@ const handleDomainQuery = (): void => {
}
if (query) {
window.location.href = `/new/domain-query-register.html?search=${encodeURIComponent(
query
window.location.href = `${isDev() ? "" : "/new"}/domain-query-register.html?search=${encodeURIComponent(
query,
)}`; // 跳转到注册页并携带查询词
} else {
$input.focus().addClass("shake"); // 空值时聚焦并触发轻微抖动提示
@@ -453,10 +1002,18 @@ const initCartButton = (): void => {
}
};
/**
* 初始化秒杀活动
*/
const initSeckillActivity = async (): Promise<void> => {
const seckillActivity = new SeckillActivity();
await seckillActivity.init();
};
/**
* 初始化所有 UI 事件与页面效果
*/
const initUIEvents = (): void => {
const initUIEvents = async (): Promise<void> => {
initFaqToggles();
initPageLoadAnimations();
initDomainQueryEvents();
@@ -466,17 +1023,19 @@ const initUIEvents = (): void => {
initScrollToSearchButton();
initServiceQRCode();
initCartButton();
// 异步初始化秒杀活动等待API数据加载
await initSeckillActivity();
};
/**
* 应用初始化(等待 jQuery 可用后初始化 UI
*/
const initApp = (): void => {
const initApp = async (): Promise<void> => {
if (typeof (window as any).jQuery === "undefined") {
window.setTimeout(initApp, 100); // 依赖 jQuery未加载则轮询等待
return;
}
initUIEvents();
await initUIEvents();
(window as any).scrollToSearchBox = scrollToSearchBox; // 暴露给内联事件或其他脚本调用
};

View File

@@ -742,6 +742,8 @@ button{
color: #374151;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@@ -1404,6 +1406,9 @@ th.text-center {
border-radius: 4px 4px 0 0;
cursor: pointer;
outline: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
position: relative;
top: 2px;
@@ -2223,12 +2228,12 @@ th.text-center {
line-height: 1.4;
margin: 0;
}
/* 隐藏注册域名说明文案 */
/* 隐藏"注册域名"说明文案 */
.cart-payment-modal .domain-suffix { display: none; }
/* 下层左:年限选择 */
.cart-payment-modal .cart-item-options { grid-area: year; }
/* 隐藏购买年限说明文案 */
/* 隐藏"购买年限"说明文案 */
.cart-payment-modal .option-label { display: none; }
/* 年限选择器尺寸与行高优化 */
.cart-payment-modal .year-selector .select-display {
@@ -2336,7 +2341,7 @@ th.text-center {
padding: 0 1rem;
}
/* 把原价格+按钮的父容器扁平化,不占位 */
/* 把原"价格+按钮"的父容器扁平化,不占位 */
.cart-payment-modal .payment-section > .price-pos {
display: contents;
}
@@ -2363,5 +2368,559 @@ th.text-center {
}
}
/* =========================== */
/* 0.01秒杀活动样式 */
/* =========================== */
/* 活动标题 */
.activity-title {
background: linear-gradient(135deg, var(--primary) 0%, #16a34a 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* 活动规则说明 */
.activity-rules .rule-item {
background: rgba(32, 165, 58, 0.05);
padding: 8px 12px;
border-radius: 20px;
border: 1px solid rgba(32, 165, 58, 0.15);
}
/* 扁平化商品卡片 */
.seckill-flat-card {
max-width: 1200px;
margin: 0 auto;
background: white;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
overflow: hidden;
border: 1px solid #e2e8f0;
}
.flat-card-container {
display: grid;
grid-template-columns: 2fr 3fr;
height: 180px;
align-items: center;
}
/* 左侧产品信息区 */
.product-info-section {
background: linear-gradient(135deg, var(--primary) 0%, #16a34a 100%);
color: white;
padding: 24px 28px;
height: 100%;
position: relative;
display: flex;
align-items: center;
gap: 16px;
text-align: left;
}
.product-badge {
position: absolute;
top: 12px;
right: 16px;
background: rgba(255, 255, 255, 0.2);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
padding: 6px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
display: flex;
align-items: center;
gap: 4px;
}
.product-content {
flex: 1;
}
.product-name {
font-size: 24px;
font-weight: bold;
margin-bottom: 8px;
line-height: 1.2;
}
.product-desc {
font-size: 14px;
opacity: 0.9;
line-height: 1.4;
margin: 0;
}
/* 右侧购买信息区4列网格 */
.purchase-info-section {
padding: 20px 24px;
display: grid;
grid-template-columns: 1fr 1fr 1.5fr 1.2fr;
gap: 20px;
align-items: center;
height: 100%;
position: relative;
}
.info-column {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
}
.column-label {
font-size: 14px;
color: var(--secondary-70);
margin-bottom: 6px;
font-weight: 500;
}
.column-value {
font-size: 16px;
font-weight: 600;
color: var(--dark);
margin-bottom: 50px;
}
/* 价格列样式 */
.price-column {
gap: 2px;
}
.current-price-info {
display: flex;
align-items: baseline;
gap: 2px;
margin-bottom: 4px;
}
.price-symbol {
font-size: 16px;
color: #f97316;
font-weight: 600;
}
.price-number {
font-size: 24px;
color: #f97316;
font-weight: bold;
line-height: 1;
}
.price-unit {
font-size: 12px;
color: #f97316;
font-weight: 500;
}
.original-price-info {
font-size: 12px;
color: var(--secondary-70);
margin-bottom: 50px;
text-align: left;
}
.strikethrough {
text-decoration: line-through;
}
.progress-info {
display: flex;
flex-direction: column;
gap: 4px;
}
.progress-text {
font-size: 12px;
color: var(--primary);
font-weight: 600;
}
.progress-bar-mini {
width: 100%;
height: 5px;
background: #e5e7eb;
border-radius: 2px;
overflow: hidden;
}
.progress-fill-mini {
height: 100%;
background: linear-gradient(90deg, var(--primary) 0%, #16a34a 100%);
border-radius: 2px;
transition: width 0.5s ease;
position: relative;
}
.progress-fill-mini::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
animation: progress-shine 2s infinite;
}
@keyframes progress-shine {
0% { left: -100%; }
100% { left: 100%; }
}
/* 操作列样式 */
.action-column {
text-align: center;
gap: 4px;
}
/* 倒计时区域 */
.countdown-section {
margin-bottom: 8px;
}
.countdown-label-mini {
font-size: 12px;
color: var(--secondary-70);
margin-bottom: 4px;
font-weight: 500;
}
.countdown-display-mini {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
margin-bottom: 4px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}
/* 进度信息区域(替代倒计时位置) */
#progress-info-section {
margin-bottom: 8px;
}
.time-digit-mini {
background: var(--primary);
color: white;
padding: 6px 8px;
border-radius: 4px;
font-size: 16px;
font-weight: bold;
min-width: 32px;
text-align: center;
transition: all 0.3s ease;
}
.time-digit-mini.urgent {
background: #e53e3e;
animation: urgent-pulse 1s infinite;
}
.time-digit-mini.animate {
transform: scale(1.05);
}
@keyframes urgent-pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.02); }
}
.time-sep {
font-size: 16px;
font-weight: bold;
color: var(--primary);
}
.seckill-btn-flat {
width: 100%;
height: 40px;
border: none;
border-radius: 2px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
background: linear-gradient(135deg, var(--primary) 0%, #16a34a 100%);
color: white;
box-shadow: 0 2px 8px rgba(32, 165, 58, 0.3);
margin-bottom: 4px;
}
.seckill-btn-flat:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(32, 165, 58, 0.4);
}
.seckill-btn-flat.not-started {
background: #9ca3af;
cursor: not-allowed;
box-shadow: none;
}
.seckill-btn-flat.not-started:hover {
transform: none;
}
.seckill-btn-flat.can-seckill {
animation: seckill-pulse 1.5s infinite;
}
@keyframes seckill-pulse {
0% { box-shadow: 0 2px 8px rgba(32, 165, 58, 0.3); }
50% { box-shadow: 0 2px 12px rgba(32, 165, 58, 0.5), 0 0 0 2px rgba(32, 165, 58, 0.2); }
100% { box-shadow: 0 2px 8px rgba(32, 165, 58, 0.3); }
}
.seckill-btn-flat.sold-out,
.seckill-btn-flat.participated {
background: none;
cursor: not-allowed;
box-shadow: none;
border: 1px solid #999999;
color:#999999
}
.seckill-btn-flat.participated:hover {
transform: none;
}
.seckill-btn-flat.sold-out:hover {
transform: none;
}
.seckill-btn-flat.loading .btn-text {
opacity: 0;
}
.seckill-btn-flat .btn-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
gap: 4px;
color: white;
font-size: 11px;
}
.action-tips-mini {
font-size: 12px;
color: var(--secondary-70);
line-height: 1.3;
}
.action-tips-footer-info{
position: absolute;
bottom: 38px;
left:60px;
}
/* 按钮点击动画 */
.seckill-btn.btn-click {
animation: btn-click 0.2s ease;
}
@keyframes btn-click {
0% { transform: scale(1); }
50% { transform: scale(0.95); }
100% { transform: scale(1); }
}
/* 按钮闪烁提示 */
.seckill-btn.blink {
animation: btn-blink 0.5s ease 3;
}
@keyframes btn-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
/* 响应式适配 */
@media (max-width: 920px) {
.seckill-activity-section {
margin: 1rem 0;
padding: 1.5rem 0;
}
.seckill-flat-card {
margin: 0 1rem;
}
.flat-card-container {
grid-template-columns: 1fr;
height: auto;
min-height: 160px;
}
.product-info-section {
padding: 16px 20px;
min-height: 80px;
}
.product-name {
font-size: 20px;
margin-bottom: 6px;
}
.product-desc {
font-size: 13px;
}
.purchase-info-section {
padding: 16px 20px;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 12px;
}
.action-column {
grid-column: 1 / -1;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.countdown-section,
#progress-info-section {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 4px;
}
.countdown-display-mini {
justify-self: center;
margin-bottom: 0;
}
.seckill-btn-flat {
height: 36px;
font-size: 13px;
margin-bottom: 0;
}
.action-tips-mini {
width: 100%;
text-align: center;
font-size: 11px;
}
.activity-rules {
gap: 8px;
}
.activity-rules .rule-item {
padding: 6px 10px;
font-size: 12px;
}
.action-tips-footer-info{
position: static;
margin-top: 20px;
grid-column: 1 / -1;
}
.original-price-info,
.column-value{
margin-bottom: 10px;
}
}
@media (max-width: 740px) {
.activity-rules{
flex-direction:row;
}
.activity-rules .rule-item{
font-size: 10px;
}
}
/* 小屏设备优化 */
@media (max-width: 480px) {
.activity-title {
font-size: 1.5rem;
}
.seckill-flat-card {
margin: 0 0.5rem;
}
.flat-card-container {
height: auto;
min-height: 140px;
}
.product-info-section {
padding: 14px 16px;
min-height: 70px;
}
.product-name {
font-size: 16px;
}
.product-desc {
font-size: 12px;
}
.product-badge{
top: 10px;
right: 8px;
padding: 2px 8px;
}
.purchase-info-section {
padding: 14px 16px;
gap: 10px;
}
.column-label {
font-size: 11px;
}
.column-value {
font-size: 14px;
}
.price-number {
font-size: 20px;
}
.time-digit-mini {
font-size: 14px;
padding: 4px 6px;
min-width: 26px;
}
.seckill-btn-flat {
height: 32px;
font-size: 12px;
}
.action-tips-mini {
font-size: 10px;
}
.countdown-section,
#progress-info-section {
margin-bottom: 6px;
}
.activity-rules .rule-item{
padding: 2px;
border-radius:2px;
}
.activity-rules .rule-item .fa{
display: none;
}
.original-price-info, .column-value{
margin-bottom: 0;
}
}

View File

@@ -0,0 +1,29 @@
export type SeckillActivityInfoResponseData = {
// 是否可以抢券false表示不可抢
can_grab: boolean;
// 优惠券价格,单位:元
coupon_price: string;
// 当前系统时间
current_time: string;
// 活动描述
description: string;
// 活动结束时间
end_time: string;
// 抢券状态0=可抢1=已抢到未使用2=已使用3=活动未开始4=活动已结束5=已抢完
grab_status: number;
// 是否有活动true表示有活动
has_activity: boolean;
// 剩余优惠券数量
remaining_coupons: number;
// 活动开始时间
start_time: string;
// 状态文本描述
status_text: string;
// 总优惠券数量
total_coupons: number;
// 用户优惠券状态
user_coupon: {
// 0: 未领取1: 已领取2: 已使用
status: number;
};
};