348 lines
13 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.

document.addEventListener('DOMContentLoaded', () => {
/* ========== 通用工具 ========== */
const getCookie = k =>
document.cookie
.split(';')
.map(c => c.trim())
.find(c => c.startsWith(k + '='))?.split('=')[1] || null;
const $ = id => document.getElementById(id);
/* ----------- 0. DOM 快捷引用 ----------- */
const currentPrice = $('currentPrice');
const originalPrice = $('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 CFG_API = 'http://127.0.0.1:8091/api/v1/gbm/index/query_group_buy_market_config';
// 登录检查
const loginToken = getCookie('loginToken');
if (!loginToken) { location.href = 'login.html'; return; }
const username = loginToken;
// 保存活动 id开团 / 参团时要用)
let activityId = null;
const POST_BODY = {
userId : username,
source : 's01',
channel: 'c01',
goodsId: '9890001'
};
fetch(CFG_API, {
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; }
activityId = data.activityId;
renderGoods(data.goods);
renderStatistic(data.teamStatistic);
renderTeams(data.teamList, data.goods?.payPrice);
})
.catch(console.error);
/* ------------- 渲染商品信息 ------------- */
function renderGoods(g = {}) {
const { originalPrice: op = 0, payPrice = 0, deductionPrice = 0 } = g;
currentPrice.textContent = payPrice;
originalPrice.textContent = op;
dropPrice.textContent = `直降 ¥${deductionPrice}`;
singlePriceSpan.textContent = `${op}`;
groupPriceSpan.textContent = `${payPrice}`;
btnSingle.dataset.price = op;
btnGroup.dataset.price = payPrice;
}
/* ------------- 渲染统计信息 ------------- */
function renderStatistic(stat = {}) {
const { allTeamUserCount = 0 } = stat;
groupTitle.textContent = `${allTeamUserCount}人在抢,参与可立即拼成`;
soldBox.textContent = `${allTeamUserCount}人再抢`;
}
/* ------------- 渲染拼团列表 ------------- */
function renderTeams(list = [], groupPrice = 0) {
if (!list?.length) {
groupTitle.textContent = '小伙伴,赶紧去开团吧,做村里最靓的仔。';
return;
}
userList.innerHTML = '';
list.forEach(t => userList.appendChild(makeItem(t, groupPrice)));
initUserMarquee();
initCountdown();
}
// 把 teamId / activityId 写到 user-info 的 dataset 上,便于点击时读取
function makeItem(team, price) {
const { userId, targetCount, lockCount,
validTimeCountdown, teamId, activityId: tActId } = 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"
data-teamid="${teamId}"
data-activityid="${tActId}">
<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) 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 PAY_MALL_URL = 'http://127.0.0.1:8092';
const CREATE_PAY_API = `${PAY_MALL_URL}/api/v1/alipay/create_pay_order`;
// 4.1 支付确认弹窗
function showPaymentConfirm(price){
if(document.querySelector('.payment-overlay')) return;
const tpl = document.getElementById('tpl-payment');
const overlay = tpl.content.firstElementChild.cloneNode(true);
overlay.querySelector('#priceText').textContent = `${price}`;
overlay.querySelector('.copyable').onclick = function(){
navigator.clipboard.writeText(this.dataset.copy)
.then(()=>alert('买家账号已复制到剪贴板'));
};
overlay.querySelector('.cancel-btn').onclick = ()=>{
document.querySelectorAll('form').forEach(f=>f.remove());
overlay.remove();
};
overlay.querySelector('.confirm-btn').onclick = ()=>{
document.querySelector('form')?.submit();
overlay.remove();
};
overlay.addEventListener('click',e=>{
if(e.target===overlay) overlay.querySelector('.cancel-btn').click();
});
document.body.appendChild(overlay);
}
/* ---------- 4.2 单独购买 ---------- */
btnSingle.addEventListener('click', () => {
if (!getCookie('loginToken')) { location.href = 'login.html'; return; }
fetch(CREATE_PAY_API, {
method : 'POST',
headers: { 'Content-Type': 'application/json' },
body : JSON.stringify({
userId : username,
productId : POST_BODY.goodsId,
marketType: 0
})
})
.then(r=>r.json())
.then(json=>{
if (json.code!=='0000') return alert(json.info||'下单失败');
document.querySelectorAll('form').forEach(f=>f.remove());
document.body.insertAdjacentHTML('beforeend', json.data);
showPaymentConfirm(btnSingle.dataset.price);
})
.catch(console.error);
});
/* ---------- 4.3 开团购买 ---------- */
btnGroup.addEventListener('click', () => {
if (!getCookie('loginToken')) { location.href = 'login.html'; return; }
fetch(CREATE_PAY_API, {
method : 'POST',
headers: { 'Content-Type': 'application/json' },
body : JSON.stringify({
userId : username,
productId : POST_BODY.goodsId,
marketType: 1,
activityId: activityId
})
})
.then(r=>r.json())
.then(json=>{
if (json.code!=='0000') return alert(json.info||'下单失败');
document.querySelectorAll('form').forEach(f=>f.remove());
document.body.insertAdjacentHTML('beforeend', json.data);
showPaymentConfirm(btnGroup.dataset.price);
})
.catch(console.error);
});
/* ---------- 4.4 参与拼团( buy-btn ---------- */
document.body.addEventListener('click', e => {
const joinBtn = e.target.closest('.buy-btn');
if (!joinBtn) return;
if (!getCookie('loginToken')) { location.href = 'login.html'; return; }
// 获取 teamId、activityId
const userInfo = joinBtn.closest('.user-item')?.querySelector('.user-info');
const teamId = userInfo?.dataset.teamid;
const actId = userInfo?.dataset.activityid || activityId;
if (!teamId) { return alert('拼团信息已失效,请刷新页面'); }
fetch(CREATE_PAY_API, {
method : 'POST',
headers: { 'Content-Type': 'application/json' },
body : JSON.stringify({
userId : username,
productId : POST_BODY.goodsId,
teamId : teamId,
activityId: actId,
marketType: 1 // 参团
})
})
.then(r=>r.json())
.then(json=>{
if (json.code!=='0000') return alert(json.info||'参团失败');
document.querySelectorAll('form').forEach(f=>f.remove());
document.body.insertAdjacentHTML('beforeend', json.data);
showPaymentConfirm(joinBtn.dataset.price);
})
.catch(console.error);
});
/* =====================================================
* 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();
}
});