254 lines
9.5 KiB
JavaScript
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.

/* -------------------------------------------------------
* Author : 你
* Desc : 改进版拼团页面脚本userId 从 cookie 里读)
* ----------------------------------------------------- */
document.addEventListener('DOMContentLoaded', () => {
/* ========== 通用工具 ========== */
const getCookie = (k) =>
document.cookie
.split(';')
.map((c) => c.trim())
.find((c) => c.startsWith(k + '='))?.split('=')[1] || null;
/* ----------- 0. DOM 快捷引用 ----------- */
const $ = (id) => document.getElementById(id);
const currentPrice = $('currentPrice');
const originalPriceElem = $('originalPrice');
const dropPrice = $('dropPrice');
const soldBox = $('soldBox');
const groupTitle = $('groupTitle');
const userList = $('userList');
const singlePriceSpan = $('singlePrice');
const groupPriceSpan = $('groupPrice');
const btnSingle = $('btnSingle');
const btnGroup = $('btnGroup');
/* =====================================================
* 1. 取接口数据并渲染
* =================================================== */
const API_URL = 'http://127.0.0.1:8091/api/v1/gbm/index/query_group_buy_market_config';
// 读取 cookie 中的 username 当作 userId
const username = getCookie('username');
// 如果没登录,直接跳去登录页,免得后面接口 401/判空
if (!username) {
location.href = 'login.html';
return;
}
const POST_BODY = {
userId : username, // 不再写死
source : 's01',
channel: 'c01',
goodsId: '9890001'
};
fetch(API_URL, {
method : 'POST',
headers: { 'Content-Type': 'application/json' },
body : JSON.stringify(POST_BODY),
})
.then((r) => r.json())
.then(({ code, info, data }) => {
if (code !== '0000' || !data) {
console.error('接口异常:', info);
return;
}
renderGoods(data.goods);
renderStatistic(data.teamStatistic);
renderTeams(data.teamList, data.goods?.payPrice);
})
.catch((e) => console.error('接口请求失败:', e));
/* ------------- 渲染商品信息 ------------- */
function renderGoods(g = {}) {
const { originalPrice = 0, payPrice = 0, deductionPrice = 0 } = g;
currentPrice.textContent = payPrice;
originalPriceElem.textContent = originalPrice;
dropPrice.textContent = `直降 ¥${deductionPrice}`;
singlePriceSpan.textContent = `${originalPrice}`;
groupPriceSpan.textContent = `${payPrice}`;
btnSingle.dataset.price = originalPrice;
btnGroup.dataset.price = payPrice;
}
/* ------------- 渲染统计信息 ------------- */
function renderStatistic(stat = {}) {
const { allTeamUserCount = 0 } = stat;
groupTitle.textContent = `${allTeamUserCount}人在抢,参与可立即拼成`;
soldBox.textContent = `${allTeamUserCount}人再抢`;
}
/* ------------- 渲染拼团列表 ------------- */
function renderTeams(list = [], groupPrice = 0) {
if (!list || list.length === 0) {
groupTitle.textContent = '小伙伴,赶紧去开团吧,做村里最靓的仔。';
return;
}
userList.innerHTML = '';
list.forEach((t) => userList.appendChild(makeItem(t, groupPrice)));
initUserMarquee();
initCountdown();
}
function makeItem(team, price) {
const { userId, targetCount, lockCount, validTimeCountdown } = team;
const leftNum = Math.max(targetCount - lockCount, 0);
const timeText = validTimeCountdown || '00:00:00';
const div = document.createElement('div');
div.className = 'user-item';
div.innerHTML = `
<div class="user-avatar"><i class="fas fa-user"></i></div>
<div class="user-info">
<div class="user-name">${userId}</div>
<div class="user-status">
仅剩${leftNum}人成团
<span class="countdown">${timeText}</span>
</div>
</div>
<button class="buy-btn" data-price="${price}">参与拼团</button>
`;
return div;
}
/* =====================================================
* 2. 拼单列表纵向轮播
* =================================================== */
function initUserMarquee() {
const items = userList.querySelectorAll('.user-item');
if (items.length <= 1) return;
const itemH = items[0].offsetHeight;
userList.appendChild(items[0].cloneNode(true)); // 无缝衔接
let idx = 0;
userList.addEventListener('transitionend', () => {
if (idx >= items.length) {
userList.style.transition = 'none';
userList.style.transform = 'translateY(0)';
idx = 0;
void userList.offsetWidth;
}
});
setInterval(() => {
idx++;
userList.style.transition = 'transform .5s ease';
userList.style.transform = `translateY(${-idx * itemH}px)`;
}, 3000);
}
/* =====================================================
* 3. 倒计时
* =================================================== */
let countdownData = [];
function initCountdown() {
const els = document.querySelectorAll('.countdown');
countdownData = Array.from(els).map((el) => ({
el,
remain: toSec(el.textContent.trim()),
}));
setInterval(tick, 1000);
}
const toSec = (t) => {
if (!t.includes(':')) return 0;
const [h = '00', m = '00', s = '00'] = t.split(':');
return +h * 3600 + +m * 60 + +s;
};
const fmt = (n) => String(n).padStart(2, '0');
const format = (s) => `${fmt(s / 3600 | 0)}:${fmt((s % 3600) / 60 | 0)}:${fmt(s % 60)}`;
function tick() {
countdownData.forEach((c) => {
if (c.remain > 0) {
c.remain--;
c.el.textContent = format(c.remain);
if (c.remain === 0) expire(c.el);
}
});
}
function expire(el) {
el.textContent = '00:00:00';
const item = el.closest('.user-item');
item?.classList.add('expired');
item?.querySelector('.buy-btn')?.setAttribute('disabled', 'disabled');
}
/* =====================================================
* 4. 支付弹窗(事件委托)
* =================================================== */
const modal = $('paymentModal');
const amountText = $('paymentAmount');
const cancelPayment = $('cancelPayment');
const completePayment= $('completePayment');
document.body.addEventListener('click', (e) => {
const btn = e.target.closest('.buy-btn, .btn-single, .btn-group');
if (!btn) return;
// 再次确认 cookie防止手动删 cookie
if (!getCookie('username')) {
location.href = 'login.html';
return;
}
amountText.textContent = `支付金额 ¥${btn.dataset.price || 0}`;
modal.style.display = 'flex';
});
cancelPayment.onclick = () => modal.style.display = 'none';
completePayment.onclick= () => { alert('支付成功!'); modal.style.display = 'none'; };
modal.addEventListener('click', (e) => { if (e.target === modal) modal.style.display = 'none'; });
/* =====================================================
* 5. 顶部横向轮播(原逻辑保留)
* =================================================== */
const wrapper = document.querySelector('.swiper-wrapper');
const slides = [...wrapper.children];
const pagination = document.querySelector('.swiper-pagination');
const count = slides.length;
let current = 0, startX = 0, dragging = false, timer;
for (let i = 0; i < count; i++) {
const dot = document.createElement('div');
dot.className = 'swiper-dot' + (i === 0 ? ' active' : '');
dot.onclick = () => goTo(i);
pagination.appendChild(dot);
}
const dots = pagination.children;
const goTo = (i) => {
current = (i + count) % count;
wrapper.style.transition = 'transform .3s ease';
wrapper.style.transform = `translateX(-${current * 100}%)`;
[...dots].forEach((d, j) => d.classList.toggle('active', j === current));
};
const auto = () => { timer = setInterval(() => goTo(current + 1), 3000); };
const stop = () => clearInterval(timer);
auto();
const getX = (e) => (e.touches ? e.touches[0].clientX : e.clientX);
wrapper.addEventListener('pointerdown', (e) => { stop(); dragging = true; startX = getX(e); wrapper.style.transition = 'none'; });
wrapper.addEventListener('pointermove', (e) => { if (!dragging) return; const diff = getX(e) - startX; wrapper.style.transform = `translateX(calc(${-current * 100}% + ${diff}px))`; });
wrapper.addEventListener('pointerup', endSwipe);
wrapper.addEventListener('pointercancel',endSwipe);
wrapper.addEventListener('pointerleave', endSwipe);
function endSwipe(e) {
if (!dragging) return;
dragging = false;
const diff = getX(e) - startX;
const limit = wrapper.offsetWidth * 0.15;
if (diff > limit) goTo(current - 1);
else if (diff < -limit) goTo(current + 1);
else goTo(current);
auto();
}
});