2025-07-08 15:44:31 +08:00
|
|
|
|
/* -------------------------------------------------------
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
const POST_BODY = {
|
|
|
|
|
userId : username, // 不再写死
|
|
|
|
|
source : 's01',
|
|
|
|
|
channel: 'c01',
|
|
|
|
|
goodsId: '9890001'
|
|
|
|
|
};
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
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;
|
2025-07-06 17:35:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
/* ------------- 渲染统计信息 ------------- */
|
|
|
|
|
function renderStatistic(stat = {}) {
|
|
|
|
|
const { allTeamUserCount = 0 } = stat;
|
|
|
|
|
groupTitle.textContent = `${allTeamUserCount}人在抢,参与可立即拼成`;
|
|
|
|
|
soldBox.textContent = `${allTeamUserCount}人再抢`;
|
2025-07-06 17:35:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
/* ------------- 渲染拼团列表 ------------- */
|
|
|
|
|
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();
|
2025-07-06 17:35:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
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;
|
|
|
|
|
}
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
/* =====================================================
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
setInterval(() => {
|
|
|
|
|
idx++;
|
|
|
|
|
userList.style.transition = 'transform .5s ease';
|
|
|
|
|
userList.style.transform = `translateY(${-idx * itemH}px)`;
|
|
|
|
|
}, 3000);
|
2025-07-06 17:35:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
/* =====================================================
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
const toSec = (t) => {
|
|
|
|
|
if (!t.includes(':')) return 0;
|
2025-07-06 17:35:38 +08:00
|
|
|
|
const [h = '00', m = '00', s = '00'] = t.split(':');
|
2025-07-08 15:44:31 +08:00
|
|
|
|
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)}`;
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
function tick() {
|
2025-07-06 17:35:38 +08:00
|
|
|
|
countdownData.forEach((c) => {
|
|
|
|
|
if (c.remain > 0) {
|
2025-07-08 15:44:31 +08:00
|
|
|
|
c.remain--;
|
|
|
|
|
c.el.textContent = format(c.remain);
|
|
|
|
|
if (c.remain === 0) expire(c.el);
|
2025-07-06 17:35:38 +08:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-07-08 15:44:31 +08:00
|
|
|
|
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');
|
2025-07-06 18:11:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
/* =====================================================
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
amountText.textContent = `支付金额 ¥${btn.dataset.price || 0}`;
|
|
|
|
|
modal.style.display = 'flex';
|
2025-07-06 17:35:38 +08:00
|
|
|
|
});
|
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
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'; });
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
/* =====================================================
|
|
|
|
|
* 5. 顶部横向轮播(原逻辑保留)
|
|
|
|
|
* =================================================== */
|
|
|
|
|
const wrapper = document.querySelector('.swiper-wrapper');
|
|
|
|
|
const slides = [...wrapper.children];
|
|
|
|
|
const pagination = document.querySelector('.swiper-pagination');
|
|
|
|
|
const count = slides.length;
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
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;
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
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();
|
|
|
|
|
}
|
2025-07-06 17:35:38 +08:00
|
|
|
|
});
|