/* ------------------------------------------------------- * 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 = `
${userId}
仅剩${leftNum}人成团 ${timeText}
`; 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(); } });