Files
hdy/public/index.html

1064 lines
38 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>狐蒂云活动监控 - 性价比专区</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
min-height: 100vh;
color: #fff;
}
.header {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
padding: 20px;
text-align: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.header h1 {
font-size: 24px;
margin-bottom: 10px;
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.status-bar {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
flex-wrap: wrap;
}
.status-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: rgba(255, 255, 255, 0.8);
}
.status-dot {
width: 10px !important;
height: 10px !important;
min-width: 10px !important;
min-height: 10px !important;
max-width: 10px !important;
max-height: 10px !important;
border-radius: 50% !important;
animation: pulse 2s infinite;
box-shadow: none !important;
display: inline-block !important;
flex-shrink: 0 !important;
}
.status-dot.online {
background: #00ff88 !important;
}
.status-dot.offline {
background: #ff4444 !important;
}
.status-dot.loading {
background: #ffaa00 !important;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.section-title {
font-size: 20px;
margin: 30px 0 20px;
padding-bottom: 10px;
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
gap: 10px;
}
.section-title .badge {
background: #00d4ff;
color: #000;
padding: 2px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
}
.section-title.sold-out .badge {
background: #ff6b6b;
}
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 20px;
}
.product-card {
background: rgba(255, 255, 255, 0.05);
border-radius: 16px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: transform 0.3s, box-shadow 0.3s;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 40px rgba(0, 212, 255, 0.2);
}
.product-card.sold-out {
opacity: 0.6;
border-color: rgba(255, 107, 107, 0.3);
}
.product-card.sold-out:hover {
box-shadow: 0 10px 40px rgba(255, 107, 107, 0.1);
}
.product-header {
background: linear-gradient(135deg, rgba(0, 212, 255, 0.2), rgba(123, 44, 191, 0.2));
padding: 20px;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.product-card.sold-out .product-header {
background: linear-gradient(135deg, rgba(255, 107, 107, 0.2), rgba(100, 50, 50, 0.2));
}
.product-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 5px;
}
.product-tag {
background: rgba(255, 255, 255, 0.2);
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.product-desc {
font-size: 13px;
color: rgba(255, 255, 255, 0.7);
margin-top: 5px;
}
.product-badge {
background: #00ff88;
color: #000;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: bold;
}
.product-card.sold-out .product-badge {
background: #ff6b6b;
color: #fff;
}
.product-specs {
padding: 20px;
}
.spec-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
font-size: 14px;
}
.spec-row:last-child {
border-bottom: none;
}
.spec-label {
color: rgba(255, 255, 255, 0.6);
}
.spec-value {
color: #fff;
text-align: right;
max-width: 60%;
}
.product-footer {
padding: 20px;
background: rgba(0, 0, 0, 0.2);
display: flex;
justify-content: space-between;
align-items: center;
}
.price-info {
display: flex;
flex-direction: column;
}
.price-current {
font-size: 28px;
font-weight: bold;
color: #00ff88;
}
.product-card.sold-out .price-current {
color: #ff6b6b;
}
.price-current span {
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
}
.price-old {
font-size: 14px;
color: rgba(255, 255, 255, 0.4);
text-decoration: line-through;
}
.discount-tag {
background: linear-gradient(90deg, #ff6b6b, #ff8e53);
color: #fff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
margin-left: 10px;
}
.buy-btn {
background: linear-gradient(135deg, #00d4ff, #7b2cbf);
color: #fff;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
text-decoration: none;
transition: transform 0.2s, box-shadow 0.2s;
}
.buy-btn:hover {
transform: scale(1.05);
box-shadow: 0 5px 20px rgba(0, 212, 255, 0.4);
}
.buy-btn.sold-out {
background: rgba(255, 107, 107, 0.3);
cursor: not-allowed;
pointer-events: none;
}
.remaining-tag {
background: rgba(255, 193, 7, 0.2);
color: #ffc107;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
margin-left: 10px;
}
.error-message {
background: rgba(255, 68, 68, 0.2);
border: 1px solid rgba(255, 68, 68, 0.5);
border-radius: 12px;
padding: 20px;
text-align: center;
color: #ff6b6b;
}
.loading {
text-align: center;
padding: 40px;
color: rgba(255, 255, 255, 0.6);
}
.loading::after {
content: '';
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: #00d4ff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 10px;
vertical-align: middle;
}
.status-dot.loading::after {
display: none !important;
}
.status-item .loading {
padding: 0;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.countdown {
background: rgba(255, 255, 255, 0.1);
padding: 5px 15px;
border-radius: 20px;
font-size: 13px;
}
.manual-refresh {
background: rgba(0, 212, 255, 0.2);
border: 1px solid rgba(0, 212, 255, 0.5);
color: #00d4ff;
padding: 5px 15px;
border-radius: 20px;
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
}
.manual-refresh:hover {
background: rgba(0, 212, 255, 0.3);
}
.interval-select {
background: rgba(0, 212, 255, 0.2);
border: 1px solid rgba(0, 212, 255, 0.5);
color: #00d4ff;
padding: 5px 10px;
border-radius: 20px;
font-size: 13px;
cursor: pointer;
outline: none;
}
.alert-config {
background: rgba(255, 255, 255, 0.05);
padding: 15px;
border-radius: 12px;
margin: 20px 0;
}
.alert-config-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 12px;
color: #00d4ff;
}
.alert-config-section {
margin-bottom: 15px;
}
.alert-config-label {
font-size: 14px;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 8px;
display: block;
}
.alert-config-checkboxes {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.checkbox-item {
display: flex;
align-items: center;
gap: 5px;
background: rgba(0, 212, 255, 0.1);
padding: 5px 12px;
border-radius: 15px;
font-size: 13px;
cursor: pointer;
}
.checkbox-item input {
cursor: pointer;
}
.toggle-config-btn {
background: linear-gradient(135deg, #00d4ff, #7b2cbf);
color: #fff;
border: none;
padding: 10px 20px;
border-radius: 20px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
margin-bottom: 10px;
transition: transform 0.2s, box-shadow 0.2s;
}
.toggle-config-btn:hover {
transform: scale(1.05);
box-shadow: 0 5px 20px rgba(0, 212, 255, 0.4);
}
.alert-config-content {
overflow: hidden;
transition: max-height 0.3s ease-out, opacity 0.3s ease-out;
}
.alert-config-content.collapsed {
max-height: 0;
opacity: 0;
}
.alert-config-content.expanded {
max-height: 500px;
opacity: 1;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: rgba(255, 255, 255, 0.5);
}
.empty-state h3 {
margin-bottom: 10px;
color: rgba(255, 255, 255, 0.7);
}
.promo-code {
background: rgba(0, 255, 136, 0.1);
border: 1px dashed rgba(0, 255, 136, 0.3);
padding: 8px 12px;
border-radius: 8px;
margin-top: 10px;
font-size: 12px;
}
.promo-code code {
color: #00ff88;
font-weight: bold;
background: rgba(0, 255, 136, 0.2);
padding: 2px 6px;
border-radius: 4px;
}
.product-monitor-checkbox {
display: flex;
align-items: center;
gap: 8px;
margin-top: 10px;
padding: 8px;
background: rgba(0, 212, 255, 0.1);
border-radius: 8px;
font-size: 13px;
}
.product-monitor-checkbox input {
width: 18px;
height: 18px;
cursor: pointer;
}
.alert-banner {
background: linear-gradient(90deg, #ff6b6b, #ff8e53);
padding: 15px;
border-radius: 12px;
margin: 20px 0;
text-align: center;
animation: pulse 1.5s infinite;
}
.alert-banner h3 {
font-size: 18px;
margin-bottom: 8px;
}
.alert-banner p {
font-size: 14px;
opacity: 0.9;
}
</style>
</head>
<body>
<div class="header">
<h1>🦊 狐蒂云活动监控 - 性价比专区</h1>
<div class="status-bar">
<div class="status-item">
<span class="status-dot loading" id="statusDot" style="display:inline-block;width:10px;height:10px;min-width:10px;min-height:10px;max-width:10px;max-height:10px;border-radius:50%;"></span>
<span id="statusText">正在加载...</span>
</div>
<div class="status-item">
<span class="countdown">下次刷新: <span id="countdown">60</span></span>
</div>
<div class="status-item">
<span>刷新间隔:</span>
<select class="interval-select" id="intervalSelect" onchange="changeInterval()">
<option value="300">5分钟</option>
<option value="180">3分钟</option>
<option value="60" selected>1分钟</option>
<option value="30">30秒</option>
<option value="20">20秒</option>
<option value="5">5秒</option>
<option value="3">3秒</option>
<option value="1">1秒</option>
</select>
</div>
<div class="status-item">
<button class="manual-refresh" onclick="manualRefresh()">🔄 立即刷新</button>
</div>
<div class="status-item">
<span>上次更新: <span id="lastUpdate">-</span></span>
</div>
</div>
</div>
<audio id="alertAudio" src="1671896015.mp3" preload="auto" style="display: none;"></audio>
<div class="container">
<div id="alertBanner"></div>
<div class="alert-config">
<button class="toggle-config-btn" onclick="toggleConfig()">
<span id="toggleBtnText">🔔 展开多条件提醒配置</span>
</button>
<div class="alert-config-content collapsed" id="configContent">
<div class="alert-config-section">
<label class="alert-config-label">选择产品地区(可多选):</label>
<div class="alert-config-checkboxes" id="regionCheckboxes">
<label class="checkbox-item">
<input type="checkbox" value="香港" onchange="toggleRegion(this)"> 香港
</label>
<label class="checkbox-item">
<input type="checkbox" value="美国" onchange="toggleRegion(this)"> 美国
</label>
<label class="checkbox-item">
<input type="checkbox" value="日本" onchange="toggleRegion(this)"> 日本
</label>
<label class="checkbox-item">
<input type="checkbox" value="韩国" onchange="toggleRegion(this)"> 韩国
</label>
<label class="checkbox-item">
<input type="checkbox" value="台湾" onchange="toggleRegion(this)"> 台湾
</label>
<label class="checkbox-item">
<input type="checkbox" value="上海" onchange="toggleRegion(this)"> 上海
</label>
</div>
</div>
<div class="alert-config-section">
<label class="alert-config-label">选择时长(可多选):</label>
<div class="alert-config-checkboxes" id="durationCheckboxes">
<label class="checkbox-item">
<input type="checkbox" value="1年" onchange="toggleDuration(this)"> 1年
</label>
<label class="checkbox-item">
<input type="checkbox" value="2年" onchange="toggleDuration(this)"> 2年
</label>
<label class="checkbox-item">
<input type="checkbox" value="3年" onchange="toggleDuration(this)"> 3年
</label>
<label class="checkbox-item">
<input type="checkbox" value="4年" onchange="toggleDuration(this)"> 4年
</label>
<label class="checkbox-item">
<input type="checkbox" value="5年" onchange="toggleDuration(this)"> 5年
</label>
</div>
</div>
</div>
</div>
<div class="section-title">
🟢 可购买商品
<span class="badge" id="availableCount">0</span>
</div>
<div class="products-grid" id="availableProducts">
<div class="loading">正在获取活动数据...</div>
</div>
<div class="section-title sold-out">
🔴 已售罄商品
<span class="badge" id="soldOutCount">0</span>
</div>
<div class="products-grid" id="soldOutProducts">
<div class="loading">正在获取活动数据...</div>
</div>
</div>
<script>
const API_URL = '/api/activity';
const ACTIVITY_URL = 'https://www.szhdy.com/activities/default.html?method=activity&id=9';
let countdownTimer;
let countdown = 60;
let monitoredProducts = new Set();
let previousProductStates = new Map();
let audioPlayed = new Set();
let selectedRegions = new Set();
let selectedDurations = new Set();
let conditionalAlertPlayed = new Set();
function getInterval() {
return parseInt(document.getElementById('intervalSelect').value);
}
function toggleRegion(checkbox) {
if (checkbox.checked) {
selectedRegions.add(checkbox.value);
} else {
selectedRegions.delete(checkbox.value);
}
}
function toggleDuration(checkbox) {
if (checkbox.checked) {
selectedDurations.add(checkbox.value);
} else {
selectedDurations.delete(checkbox.value);
}
}
let isConfigExpanded = false;
function toggleConfig() {
const configContent = document.getElementById('configContent');
const toggleBtnText = document.getElementById('toggleBtnText');
isConfigExpanded = !isConfigExpanded;
if (isConfigExpanded) {
configContent.classList.remove('collapsed');
configContent.classList.add('expanded');
toggleBtnText.textContent = '🔔 收起多条件提醒配置';
} else {
configContent.classList.remove('expanded');
configContent.classList.add('collapsed');
toggleBtnText.textContent = '🔔 展开多条件提醒配置';
}
}
function toggleMonitor(productId) {
if (monitoredProducts.has(productId)) {
monitoredProducts.delete(productId);
audioPlayed.delete(productId);
} else {
monitoredProducts.add(productId);
}
}
function playAlert() {
const audio = document.getElementById('alertAudio');
audio.currentTime = 0;
audio.play().catch(e => console.log('音频播放需要用户交互'));
}
function showAlertBanner(products) {
const banner = document.getElementById('alertBanner');
if (products.length > 0) {
banner.innerHTML = `
<div class="alert-banner">
<h3>🎉 有产品可以购买啦!</h3>
<p>${products.map(p => escapeHtml(p.title)).join('、')}</p>
</div>
`;
} else {
banner.innerHTML = '';
}
}
function checkConditionalAlert(product) {
if (selectedRegions.size === 0 && selectedDurations.size === 0) {
return false;
}
const productId = product.id;
if (conditionalAlertPlayed.has(productId)) {
return false;
}
let regionMatch = selectedRegions.size === 0;
let durationMatch = selectedDurations.size === 0;
if (selectedRegions.size > 0) {
for (const region of selectedRegions) {
if (product.title.includes(region)) {
regionMatch = true;
break;
}
}
}
if (selectedDurations.size > 0 && product.duration) {
for (const duration of selectedDurations) {
if (product.duration.includes(duration)) {
durationMatch = true;
break;
}
}
}
if (regionMatch && durationMatch && !product.isSoldOut) {
conditionalAlertPlayed.add(productId);
return true;
}
return false;
}
function changeInterval() {
countdown = getInterval();
updateCountdownDisplay();
}
async function fetchActivity() {
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('statusText');
statusDot.className = 'status-dot loading';
statusText.textContent = '正在获取数据...';
try {
const response = await fetch(API_URL);
const data = await response.json();
if (data.success) {
parseAndRender(data.html);
statusDot.className = 'status-dot online';
statusText.textContent = '数据获取成功';
document.getElementById('lastUpdate').textContent = new Date().toLocaleTimeString();
} else {
throw new Error(data.error);
}
} catch (error) {
console.error('获取数据失败:', error);
showError(error.message);
statusDot.className = 'status-dot offline';
statusText.textContent = '获取失败: ' + error.message;
}
}
function parseAndRender(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const availableProducts = [];
const soldOutProducts = [];
const allProducts = [];
const section = doc.querySelector('#navigation_379');
if (!section) {
showError('未找到性价比专区');
return;
}
const cards = section.querySelectorAll('.activity-promotion-card, .cat-styleB-promotion-card, .cat-styleC-promotion-card');
cards.forEach(card => {
const product = parseProductCard(card);
allProducts.push(product);
if (product.isSoldOut) {
soldOutProducts.push(product);
} else {
availableProducts.push(product);
}
});
availableProducts.sort((a, b) => {
if (a.title.includes('韩国')) return -1;
if (b.title.includes('韩国')) return 1;
return 0;
});
checkProductStatusChanges(allProducts);
renderProducts(availableProducts, 'availableProducts', false);
renderProducts(soldOutProducts, 'soldOutProducts', true);
document.getElementById('availableCount').textContent = availableProducts.length;
document.getElementById('soldOutCount').textContent = soldOutProducts.length;
}
function checkProductStatusChanges(products) {
products.forEach(product => {
const productId = product.id;
const wasSoldOut = previousProductStates.get(productId);
const isNowSoldOut = product.isSoldOut;
if (wasSoldOut !== undefined) {
if (wasSoldOut && !isNowSoldOut) {
if (monitoredProducts.has(productId) && !audioPlayed.has(productId)) {
playAlert();
audioPlayed.add(productId);
}
if (checkConditionalAlert(product)) {
playAlert();
conditionalAlertPlayed.add(productId);
}
}
}
previousProductStates.set(productId, isNowSoldOut);
});
}
function parseProductCard(card) {
const product = {
id: card.getAttribute('data-id'),
title: '',
tag: '',
desc: '',
specs: [],
price: '',
oldPrice: '',
discount: '',
remaining: '',
promoCode: '',
duration: '',
buyUrl: '',
isSoldOut: false
};
let titleEl = card.querySelector('.card-title h1');
if (!titleEl) titleEl = card.querySelector('.cat-styleBtitle h1');
if (!titleEl) titleEl = card.querySelector('.cat-styleCtitle h1');
if (titleEl) product.title = titleEl.textContent.trim();
const tagEl = card.querySelector('.titleTags');
if (tagEl) product.tag = tagEl.textContent.trim();
let descEl = card.querySelector('.card-desc p');
if (!descEl) descEl = card.querySelector('.cat-styleBdesc p');
if (!descEl) descEl = card.querySelector('.cat-styleCdesc p');
if (descEl) product.desc = descEl.textContent.trim();
const formContainers = card.querySelectorAll('.form-container');
formContainers.forEach(container => {
const label = container.querySelector('.form-title h5');
const value = container.querySelector('.form-text');
if (label && value) {
const labelText = label.textContent.trim();
const valueText = value.textContent.trim();
if (labelText.includes('温馨提醒') && valueText.includes('优惠券')) {
const codeMatch = valueText.match(/\s*(\S+)\s*/);
if (codeMatch && codeMatch[1]) {
product.promoCode = codeMatch[1];
}
} else if (!labelText.includes('温馨提醒')) {
product.specs.push({ label: labelText, value: valueText });
if (!product.duration) {
if (labelText.includes('时长') || labelText.includes('周期') ||
labelText.includes('有效期') || labelText.includes('月') ||
labelText.includes('年') || labelText.includes('天')) {
product.duration = valueText;
}
}
}
}
});
const currentPriceEl = card.querySelector('.main-price-current');
if (currentPriceEl) {
product.price = currentPriceEl.textContent.trim();
if (!product.duration) {
const parent = currentPriceEl.parentElement;
if (parent) {
const text = parent.textContent;
const durationMatch = text.match(/(?:\d+)\s*(?:月|年|天|个月|个年)/);
if (durationMatch) {
product.duration = durationMatch[0];
}
}
}
}
const oldPriceEl = card.querySelector('.main-price-old');
if (oldPriceEl) product.oldPrice = oldPriceEl.textContent.trim();
const discountEl = card.querySelector('.main-price-discount');
if (discountEl) product.discount = discountEl.textContent.trim();
const remainingEl = card.querySelector('.head-tag-item:not(.active)');
if (remainingEl) {
const text = remainingEl.textContent.trim();
if (text.includes('剩余')) {
product.remaining = text;
}
}
const allText = card.textContent;
if (!product.duration) {
const durationMatch = allText.match(/(?:\d+)\s*(?:月|年|天|个月|个年)/);
if (durationMatch) {
product.duration = durationMatch[0];
}
}
const durationEls = card.querySelectorAll('[class*="duration"], [class*="period"], [class*="cycle"], [class*="time"], [class*="month"], [class*="year"]');
durationEls.forEach(el => {
if (!product.duration) {
product.duration = el.textContent.trim();
}
});
let buyBtn = card.querySelector('.form-footer-butt');
if (!buyBtn) buyBtn = card.querySelector('.cat-styleBform-footer-butt');
if (!buyBtn) buyBtn = card.querySelector('.cat-styleCform-footer a');
if (buyBtn) {
if (buyBtn.classList.contains('disableButton')) {
product.isSoldOut = true;
}
const href = buyBtn.getAttribute('href');
if (href && href.includes('pid')) {
product.buyUrl = 'https://www.szhdy.com' + href;
}
}
return product;
}
function renderProducts(products, containerId, isSoldOut) {
const container = document.getElementById(containerId);
if (products.length === 0) {
container.innerHTML = `
<div class="empty-state">
<h3>${isSoldOut ? '暂无售罄商品' : '暂无可购买商品'}</h3>
<p>${isSoldOut ? '所有商品都在售!' : '请等待下次刷新或手动刷新查看最新状态'}</p>
</div>
`;
return;
}
container.innerHTML = products.map(product => `
<div class="product-card ${isSoldOut ? 'sold-out' : ''}">
<div class="product-header">
<div>
<div class="product-title">${escapeHtml(product.title)}</div>
${product.tag ? `<span class="product-tag">${escapeHtml(product.tag)}</span>` : ''}
${product.desc ? `<div class="product-desc">${escapeHtml(product.desc)}</div>` : ''}
</div>
<span class="product-badge">${isSoldOut ? '已售罄' : '可购买'}</span>
</div>
<div class="product-specs">
${product.specs.map(spec => `
<div class="spec-row">
<span class="spec-label">${escapeHtml(spec.label)}</span>
<span class="spec-value">${escapeHtml(spec.value)}</span>
</div>
`).join('')}
${product.duration ? `
<div class="spec-row">
<span class="spec-label">时长</span>
<span class="spec-value">${escapeHtml(product.duration)}</span>
</div>
` : ''}
${product.promoCode ? `
<div class="promo-code">
🎫 优惠券: <code>${escapeHtml(product.promoCode)}</code>
</div>
` : ''}
</div>
<div class="product-footer">
<div class="price-info">
<div class="price-current">
¥${escapeHtml(product.price)}
<span>元/续费同价</span>
${product.discount ? `<span class="discount-tag">${escapeHtml(product.discount)}</span>` : ''}
${product.remaining ? `<span class="remaining-tag">${escapeHtml(product.remaining)}</span>` : ''}
</div>
${product.oldPrice ? `<div class="price-old">原价: ¥${escapeHtml(product.oldPrice)}</div>` : ''}
</div>
${isSoldOut
? '<span class="buy-btn sold-out">已售罄</span>'
: `<a href="${product.buyUrl || ACTIVITY_URL}" target="_blank" class="buy-btn">立即购买</a>`
}
</div>
<div class="product-monitor-checkbox">
<input type="checkbox" id="monitor-${product.id}"
onchange="toggleMonitor('${product.id}')"
${monitoredProducts.has(product.id) ? 'checked' : ''}>
<label for="monitor-${product.id}">监控此产品,上架时提醒</label>
</div>
</div>
`).join('');
}
function showError(message) {
const containers = ['availableProducts', 'soldOutProducts'];
containers.forEach(id => {
document.getElementById(id).innerHTML = `
<div class="error-message">
<h3>获取数据失败</h3>
<p>${escapeHtml(message)}</p>
<p style="margin-top: 10px; font-size: 12px; opacity: 0.7;">请检查网络连接或稍后重试</p>
</div>
`;
});
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function startCountdown() {
countdown = getInterval();
updateCountdownDisplay();
if (countdownTimer) clearInterval(countdownTimer);
countdownTimer = setInterval(() => {
countdown--;
updateCountdownDisplay();
if (countdown <= 0) {
fetchActivity();
countdown = getInterval();
}
}, 1000);
}
function updateCountdownDisplay() {
document.getElementById('countdown').textContent = countdown;
}
function manualRefresh() {
fetchActivity();
countdown = getInterval();
}
fetchActivity();
startCountdown();
</script>
</body>
</html>