2025-07-08 15:44:31 +08:00
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
|
2025-08-04 22:19:35 +08:00
|
|
|
|
/* ========== 工具 & 登录检查(统一用 common) ========== */
|
2025-07-17 18:30:48 +08:00
|
|
|
|
const $ = id => document.getElementById(id);
|
2025-07-08 15:44:31 +08:00
|
|
|
|
|
2025-08-04 22:19:35 +08:00
|
|
|
|
// 统一解析当前用户,拿不到就跳登录
|
|
|
|
|
const username = AppUtils.resolveUserId();
|
|
|
|
|
if (!username) { location.href = 'login.html'; return; }
|
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
/* ----------- 0. DOM 快捷引用 ----------- */
|
2025-07-17 18:30:48 +08:00
|
|
|
|
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');
|
2025-07-08 15:44:31 +08:00
|
|
|
|
|
|
|
|
|
/* =====================================================
|
|
|
|
|
* 1. 取接口数据并渲染
|
|
|
|
|
* =================================================== */
|
2025-08-04 22:19:35 +08:00
|
|
|
|
const CFG_API = `${AppConfig.groupBuyMarketUrl}/api/v1/gbm/index/query_group_buy_market_config`;
|
2025-07-08 15:44:31 +08:00
|
|
|
|
|
2025-07-17 18:30:48 +08:00
|
|
|
|
// 保存活动 id(开团 / 参团时要用)
|
|
|
|
|
let activityId = null;
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
const POST_BODY = {
|
2025-07-17 18:30:48 +08:00
|
|
|
|
userId : username,
|
2025-07-08 15:44:31 +08:00
|
|
|
|
source : 's01',
|
|
|
|
|
channel: 'c01',
|
2025-08-04 22:19:35 +08:00
|
|
|
|
goodsId: AppConfig.goodsId
|
2025-07-08 15:44:31 +08:00
|
|
|
|
};
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-17 18:30:48 +08:00
|
|
|
|
fetch(CFG_API, {
|
2025-07-08 15:44:31 +08:00
|
|
|
|
method : 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
2025-07-17 18:30:48 +08:00
|
|
|
|
body : JSON.stringify(POST_BODY)
|
2025-07-08 15:44:31 +08:00
|
|
|
|
})
|
2025-07-17 18:30:48 +08:00
|
|
|
|
.then(r => r.json())
|
2025-07-08 15:44:31 +08:00
|
|
|
|
.then(({ code, info, data }) => {
|
2025-07-17 18:30:48 +08:00
|
|
|
|
if (code !== '0000' || !data) { console.error(info); return; }
|
|
|
|
|
activityId = data.activityId;
|
2025-07-08 15:44:31 +08:00
|
|
|
|
renderGoods(data.goods);
|
|
|
|
|
renderStatistic(data.teamStatistic);
|
|
|
|
|
renderTeams(data.teamList, data.goods?.payPrice);
|
|
|
|
|
})
|
2025-07-17 18:30:48 +08:00
|
|
|
|
.catch(console.error);
|
2025-07-08 15:44:31 +08:00
|
|
|
|
|
|
|
|
|
/* ------------- 渲染商品信息 ------------- */
|
|
|
|
|
function renderGoods(g = {}) {
|
2025-07-17 18:30:48 +08:00
|
|
|
|
const { originalPrice: op = 0, payPrice = 0, deductionPrice = 0 } = g;
|
|
|
|
|
currentPrice.textContent = payPrice;
|
|
|
|
|
originalPrice.textContent = op;
|
|
|
|
|
dropPrice.textContent = `直降 ¥${deductionPrice}`;
|
2025-07-08 15:44:31 +08:00
|
|
|
|
|
2025-07-17 18:30:48 +08:00
|
|
|
|
singlePriceSpan.textContent = `¥${op}`;
|
2025-07-08 15:44:31 +08:00
|
|
|
|
groupPriceSpan.textContent = `¥${payPrice}`;
|
2025-07-17 18:30:48 +08:00
|
|
|
|
btnSingle.dataset.price = op;
|
2025-07-08 15:44:31 +08:00
|
|
|
|
btnGroup.dataset.price = payPrice;
|
2025-07-06 17:35:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
/* ------------- 渲染统计信息 ------------- */
|
|
|
|
|
function renderStatistic(stat = {}) {
|
2025-08-04 22:19:35 +08:00
|
|
|
|
const { allTeamUserCount = 0, inTeamUserCount = 0 } = stat;
|
2025-07-21 11:24:41 +08:00
|
|
|
|
groupTitle.textContent = `${inTeamUserCount}人在抢,参与可立即拼成`;
|
|
|
|
|
soldBox.textContent = `${allTeamUserCount}人已抢`;
|
2025-07-06 17:35:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-07-30 18:25:15 +08:00
|
|
|
|
/* ------------- 渲染拼团列表 (按 teamId 去重) ------------- */
|
2025-07-08 15:44:31 +08:00
|
|
|
|
function renderTeams(list = [], groupPrice = 0) {
|
2025-07-30 18:25:15 +08:00
|
|
|
|
const uniq = [];
|
|
|
|
|
const seen = new Set();
|
|
|
|
|
list.forEach(t => {
|
|
|
|
|
if (!seen.has(t.teamId)) {
|
|
|
|
|
uniq.push(t);
|
|
|
|
|
seen.add(t.teamId);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!uniq.length) {
|
2025-07-08 15:44:31 +08:00
|
|
|
|
groupTitle.textContent = '小伙伴,赶紧去开团吧,做村里最靓的仔。';
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-07-30 18:25:15 +08:00
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
userList.innerHTML = '';
|
2025-07-30 18:25:15 +08:00
|
|
|
|
uniq.forEach(t => userList.appendChild(makeItem(t, groupPrice)));
|
|
|
|
|
|
2025-07-21 11:24:41 +08:00
|
|
|
|
initUserMarquee(3);
|
2025-07-08 15:44:31 +08:00
|
|
|
|
initCountdown();
|
2025-07-06 17:35:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-07-17 18:30:48 +08:00
|
|
|
|
// 把 teamId / activityId 写到 user-info 的 dataset 上,便于点击时读取
|
2025-07-08 15:44:31 +08:00
|
|
|
|
function makeItem(team, price) {
|
2025-08-04 22:19:35 +08:00
|
|
|
|
const { userId = '', targetCount, lockCount, validTimeCountdown } = team;
|
2025-07-17 18:30:48 +08:00
|
|
|
|
|
2025-08-04 22:19:35 +08:00
|
|
|
|
// ① 脱敏统一用 common
|
|
|
|
|
const maskedId = AppUtils.obfuscateUserId(userId);
|
2025-07-17 18:30:48 +08:00
|
|
|
|
|
2025-08-04 22:19:35 +08:00
|
|
|
|
// ② 原逻辑
|
2025-07-08 15:44:31 +08:00
|
|
|
|
const leftNum = Math.max(targetCount - lockCount, 0);
|
|
|
|
|
const timeText = validTimeCountdown || '00:00:00';
|
|
|
|
|
|
|
|
|
|
const div = document.createElement('div');
|
|
|
|
|
div.className = 'user-item';
|
|
|
|
|
div.innerHTML = `
|
2025-08-04 22:19:35 +08:00
|
|
|
|
<div class="user-avatar"><i class="fas fa-user"></i></div>
|
|
|
|
|
<div class="user-info"
|
|
|
|
|
data-teamid="${team.teamId}"
|
|
|
|
|
data-activityid="${team.activityId}">
|
|
|
|
|
<div class="user-name">${maskedId}</div>
|
|
|
|
|
<div class="user-status">
|
|
|
|
|
仅剩 ${leftNum} 人成团
|
|
|
|
|
<span class="countdown">${timeText}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="buy-btn" data-price="${price}">参与拼团</button>
|
|
|
|
|
`;
|
2025-07-08 15:44:31 +08:00
|
|
|
|
return div;
|
|
|
|
|
}
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
/* =====================================================
|
|
|
|
|
* 2. 拼单列表纵向轮播
|
|
|
|
|
* =================================================== */
|
2025-07-21 11:24:41 +08:00
|
|
|
|
function initUserMarquee(visibleCount = 3, interval = 3000, duration = 500) {
|
|
|
|
|
const box = document.querySelector('.group-users');
|
|
|
|
|
const listEl = userList;
|
|
|
|
|
let originals = Array.from(listEl.children);
|
|
|
|
|
const total = originals.length;
|
|
|
|
|
|
|
|
|
|
if (total === 0) return;
|
|
|
|
|
if (total <= visibleCount) {
|
|
|
|
|
const h0 = originals[0].offsetHeight;
|
|
|
|
|
box.style.height = (h0 * visibleCount) + 'px';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
originals.forEach(item => listEl.appendChild(item.cloneNode(true)));
|
|
|
|
|
const itemH = listEl.children[0].offsetHeight;
|
|
|
|
|
box.style.height = (itemH * visibleCount) + 'px';
|
|
|
|
|
|
|
|
|
|
let index = 0;
|
|
|
|
|
let ticking = false;
|
|
|
|
|
|
|
|
|
|
function step() {
|
|
|
|
|
index++;
|
|
|
|
|
listEl.style.transition = `transform ${duration}ms ease`;
|
|
|
|
|
listEl.style.transform = `translateY(-${index * itemH}px)`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
listEl.addEventListener('transitionend', () => {
|
|
|
|
|
ticking = false;
|
|
|
|
|
if (index === total) {
|
|
|
|
|
listEl.style.transition = 'none';
|
|
|
|
|
listEl.style.transform = 'translateY(0)';
|
|
|
|
|
index = 0;
|
|
|
|
|
void listEl.offsetHeight;
|
|
|
|
|
listEl.style.transition = `transform ${duration}ms ease`;
|
2025-07-08 15:44:31 +08:00
|
|
|
|
}
|
|
|
|
|
});
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-21 11:24:41 +08:00
|
|
|
|
const timer = setInterval(() => {
|
|
|
|
|
if (!ticking) {
|
|
|
|
|
ticking = true;
|
|
|
|
|
step();
|
|
|
|
|
}
|
|
|
|
|
}, interval);
|
|
|
|
|
|
|
|
|
|
listEl.addEventListener('mouseenter', () => clearInterval(timer));
|
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');
|
2025-07-17 18:30:48 +08:00
|
|
|
|
countdownData = Array.from(els).map(el => ({
|
2025-07-08 15:44:31 +08:00
|
|
|
|
el,
|
2025-07-17 18:30:48 +08:00
|
|
|
|
remain: toSec(el.textContent.trim())
|
2025-07-08 15:44:31 +08:00
|
|
|
|
}));
|
|
|
|
|
setInterval(tick, 1000);
|
|
|
|
|
}
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-17 18:30:48 +08:00
|
|
|
|
const toSec = t => {
|
2025-07-08 15:44:31 +08:00
|
|
|
|
if (!t.includes(':')) return 0;
|
2025-07-17 18:30:48 +08:00
|
|
|
|
const [h='00', m='00', s='00'] = t.split(':');
|
|
|
|
|
return +h*3600 + +m*60 + +s;
|
2025-07-08 15:44:31 +08:00
|
|
|
|
};
|
2025-07-17 18:30:48 +08:00
|
|
|
|
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-17 18:30:48 +08:00
|
|
|
|
countdownData.forEach(c=>{
|
2025-07-06 17:35:38 +08:00
|
|
|
|
if (c.remain > 0) {
|
2025-07-08 15:44:31 +08:00
|
|
|
|
c.remain--;
|
|
|
|
|
c.el.textContent = format(c.remain);
|
2025-07-17 18:30:48 +08:00
|
|
|
|
if(!c.remain) expire(c.el);
|
2025-07-06 17:35:38 +08:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-07-17 18:30:48 +08:00
|
|
|
|
function expire(el){
|
2025-07-08 15:44:31 +08:00
|
|
|
|
el.textContent = '00:00:00';
|
|
|
|
|
const item = el.closest('.user-item');
|
|
|
|
|
item?.classList.add('expired');
|
2025-07-17 18:30:48 +08:00
|
|
|
|
item?.querySelector('.buy-btn')?.setAttribute('disabled','disabled');
|
2025-07-06 18:11:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-07-08 15:44:31 +08:00
|
|
|
|
/* =====================================================
|
2025-08-04 22:19:35 +08:00
|
|
|
|
* 4. 支付相关(统一从 AppConfig 取 IP)
|
2025-07-08 15:44:31 +08:00
|
|
|
|
* =================================================== */
|
2025-08-04 22:19:35 +08:00
|
|
|
|
const CREATE_PAY_API = `${AppConfig.sPayMallUrl}/api/v1/alipay/create_pay_order`;
|
2025-07-17 18:30:48 +08:00
|
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
});
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-17 18:30:48 +08:00
|
|
|
|
document.body.appendChild(overlay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------- 4.2 单独购买 ---------- */
|
|
|
|
|
btnSingle.addEventListener('click', () => {
|
2025-08-04 22:19:35 +08:00
|
|
|
|
if (!AppUtils.getCookie('loginToken')) { location.href = 'login.html'; return; }
|
2025-07-17 18:30:48 +08:00
|
|
|
|
|
|
|
|
|
fetch(CREATE_PAY_API, {
|
|
|
|
|
method : 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body : JSON.stringify({
|
|
|
|
|
userId : username,
|
2025-08-04 22:19:35 +08:00
|
|
|
|
productId : AppConfig.goodsId,
|
2025-07-17 18:30:48 +08:00
|
|
|
|
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);
|
2025-07-06 17:35:38 +08:00
|
|
|
|
});
|
|
|
|
|
|
2025-07-17 18:30:48 +08:00
|
|
|
|
/* ---------- 4.3 开团购买 ---------- */
|
|
|
|
|
btnGroup.addEventListener('click', () => {
|
2025-08-04 22:19:35 +08:00
|
|
|
|
if (!AppUtils.getCookie('loginToken')) { location.href = 'login.html'; return; }
|
2025-07-17 18:30:48 +08:00
|
|
|
|
|
|
|
|
|
fetch(CREATE_PAY_API, {
|
|
|
|
|
method : 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body : JSON.stringify({
|
|
|
|
|
userId : username,
|
2025-08-04 22:19:35 +08:00
|
|
|
|
productId : AppConfig.goodsId,
|
2025-07-17 18:30:48 +08:00
|
|
|
|
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;
|
|
|
|
|
|
2025-08-04 22:19:35 +08:00
|
|
|
|
if (!AppUtils.getCookie('loginToken')) { location.href = 'login.html'; return; }
|
2025-07-17 18:30:48 +08:00
|
|
|
|
|
|
|
|
|
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,
|
2025-08-04 22:19:35 +08:00
|
|
|
|
productId : AppConfig.goodsId,
|
2025-07-17 18:30:48 +08:00
|
|
|
|
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);
|
|
|
|
|
});
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-08-04 22:19:35 +08:00
|
|
|
|
/* ---------- 4.5 我的订单 ---------- */
|
|
|
|
|
const ordersBtn = document.getElementById('btnOrders');
|
|
|
|
|
if (ordersBtn) {
|
|
|
|
|
ordersBtn.addEventListener('click', () => {
|
|
|
|
|
const userId = username; // 已统一解析
|
|
|
|
|
const target = `order-list.html${userId ? `?userId=${encodeURIComponent(userId)}` : ''}`;
|
|
|
|
|
window.location.href = target;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
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');
|
2025-07-17 18:30:48 +08:00
|
|
|
|
dot.className = 'swiper-dot' + (i===0?' active':'');
|
2025-07-08 15:44:31 +08:00
|
|
|
|
dot.onclick = () => goTo(i);
|
|
|
|
|
pagination.appendChild(dot);
|
|
|
|
|
}
|
|
|
|
|
const dots = pagination.children;
|
2025-07-06 17:35:38 +08:00
|
|
|
|
|
2025-07-17 18:30:48 +08:00
|
|
|
|
const goTo = i => {
|
|
|
|
|
current = (i+count)%count;
|
2025-07-08 15:44:31 +08:00
|
|
|
|
wrapper.style.transition = 'transform .3s ease';
|
2025-07-17 18:30:48 +08:00
|
|
|
|
wrapper.style.transform = `translateX(-${current*100}%)`;
|
|
|
|
|
[...dots].forEach((d,j)=>d.classList.toggle('active',j===current));
|
2025-07-08 15:44:31 +08:00
|
|
|
|
};
|
2025-07-17 18:30:48 +08:00
|
|
|
|
const auto = () => { timer=setInterval(()=>goTo(current+1),3000); };
|
2025-07-08 15:44:31 +08:00
|
|
|
|
const stop = () => clearInterval(timer);
|
|
|
|
|
auto();
|
|
|
|
|
|
2025-07-17 18:30:48 +08:00
|
|
|
|
const getX = e => e.touches?e.touches[0].clientX:e.clientX;
|
2025-08-04 22:19:35 +08:00
|
|
|
|
const 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-17 18:30:48 +08:00
|
|
|
|
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);
|
2025-07-08 15:44:31 +08:00
|
|
|
|
wrapper.addEventListener('pointercancel',endSwipe);
|
2025-07-17 18:30:48 +08:00
|
|
|
|
wrapper.addEventListener('pointerleave',endSwipe);
|
2025-07-06 17:35:38 +08:00
|
|
|
|
});
|