mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-11 09:11:10 +08:00
【修复】表单初始化,申请节点随机时间
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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 域名
|
||||
|
||||
@@ -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; // 暴露给内联事件或其他脚本调用
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
29
frontend/apps/domain-official/src/types/api-types/flashsale.d.ts
vendored
Normal file
29
frontend/apps/domain-official/src/types/api-types/flashsale.d.ts
vendored
Normal 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;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user