diff --git a/docs/dev-ops/nginx/html/css/index.css b/docs/dev-ops/nginx/html/css/index.css new file mode 100644 index 0000000..789c918 --- /dev/null +++ b/docs/dev-ops/nginx/html/css/index.css @@ -0,0 +1,327 @@ +/* index.css */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif; +} + +body { + background-color: #f5f5f5; + color: #333; + max-width: 500px; + margin: 0 auto; + position: relative; + padding-bottom: 30px; +} + +/* 轮播图样式 */ +.swiper-container { + width: 100%; + height: 375px; + position: relative; + overflow: hidden; +} +.swiper-wrapper { + display: flex; + transition: transform 0.3s; +} +.swiper-slide { + flex: 0 0 100%; + height: 375px; +} +.swiper-slide img { + width:100%; + height:100%; + object-fit:contain; /* cover → contain 可看到整幅图 */ + background:#fff; /* 若比例不一致留白更自然 */ +} +.swiper-pagination { + position: absolute; + bottom: 10px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 6px; +} +.swiper-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: rgba(255,255,255,0.5); + transition: all 0.3s; +} +.swiper-dot.active { + background: #ff5000; + width: 16px; + border-radius: 4px; +} + + +/* 商品信息区域 */ +.group-left{ + margin-right:4px; + color:#ff5000; + font-weight:bold; +} +.product-info { + background: #fff; + padding: 15px; + margin-bottom: 10px; +} + +.price-row { + display: flex; + align-items: center; + margin-bottom: 12px; +} + +.current-price { + color: #ff5000; + font-size: 28px; + font-weight: bold; +} + +.current-price::before { + content: "¥"; + font-size: 18px; +} + +.original-price { + color: #999; + font-size: 16px; + text-decoration: line-through; + margin-left: 8px; +} + +.original-price::before { + content: "¥"; +} + +.title { + font-size: 18px; + font-weight: bold; + line-height: 1.4; + margin-bottom: 10px; +} + +.promo-tag { + display: inline-block; + background: linear-gradient(90deg, #ff2c2c, #ff6b22); + color: white; + font-size: 12px; + padding: 2px 6px; + border-radius: 2px; + margin-right: 5px; +} + +.promo-info { + color: #ff5000; + font-size: 14px; + margin: 8px 0; + display: flex; + align-items: center; +} + +.promo-info i { + margin-right: 5px; +} + +/* 拼单区域 - 修改了高度 */ +/* “仅剩 x 人成团” 前缀样式 */ +.left-num{ + color: #666; + margin-right:4px; + font-weight:bold; +} + +.group-buying { + background: #fff; + padding: 15px; + margin-bottom: 10px; + position: relative; + overflow: hidden; +} + +.section-title { + font-size: 16px; + font-weight: bold; + margin-bottom: 12px; + position: relative; + padding-left: 10px; +} + +.section-title::before { + content: ""; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 3px; + height: 16px; + background: #ff5000; + border-radius: 2px; +} + +.group-users { + height: 120px; /* 修改为120px以容纳两条完整信息 */ + position: relative; + overflow: hidden; +} + +.user-list { + position: absolute; + top: 0; + left: 0; + width: 100%; + transition: transform 0.5s ease; +} + +.user-item { + display: flex; + align-items: center; + padding: 8px 0; /* 减小上下内边距 */ + border-bottom: 1px solid #f5f5f5; +} + +.user-item:last-child { + border-bottom: none; +} + +.user-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + background: #f5f5f5; + display: flex; + align-items: center; + justify-content: center; + margin-right: 10px; + color: #999; + font-size: 20px; +} + +.user-info { + flex: 1; +} + +.user-name { + font-size: 15px; + font-weight: bold; + margin-bottom: 4px; +} + +.user-status { + font-size: 13px; + color: #666; +} + +.countdown { + display: inline-block; + background: #ff5000; + color: white; + padding: 1px 4px; + border-radius: 2px; + margin-left: 5px; +} + +.buy-btn { + background: linear-gradient(90deg, #ff2c2c, #ff6b22); + color: white; + border: none; + border-radius: 4px; + padding: 6px 15px; + font-size: 14px; + font-weight: bold; + cursor: pointer; +} + +/* 底部操作栏 */ +.action-bar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + max-width: 500px; + margin: 0 auto; + background: #fff; + display: flex; + height: 60px; + box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); + z-index: 100; +} + +.action-btn { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: 12px; + color: #666; +} + +.action-btn i { + font-size: 20px; + margin-bottom: 4px; +} + +.purchase-btn { + flex: 2; + display: flex; +} + +.btn-single { + flex: 1; + background: #ff9500; + color: white; + border: none; + font-size: 16px; + font-weight: bold; +} + +.btn-group { + flex: 1; + background: #ff5000; + color: white; + border: none; + font-size: 16px; + font-weight: bold; +} + +/* ===== 支付弹窗 ===== */ +.pay-mask{ + position:fixed;inset:0;z-index:999; + background:rgba(0,0,0,.45); + display:none;align-items:center;justify-content:center; +} + +/* ① 适当放宽弹窗,保证二维码和按钮都有留白 */ +.pay-box{ + width:92%; /* → 手机端留 8% 边距 */ + max-width:420px; /* → PC 也能放下 260px 二维码 */ + background:#fff; + border-radius:10px; + padding:28px 24px; + text-align:center; +} + +/* ② 用 .qr-code 选中 ,限制尺寸并居中 */ +.qr-code{ + width:260px; /* 你想要的显示大小 */ + max-width:100%; /* 小屏自动缩小 */ + height:auto; + display:block; + margin:0 auto 32px; /* 居中 + 与按钮留间距 */ + border-radius:12px; + object-fit:contain; /* 防止被拉伸/裁切 */ +} + +/* 其余按钮样式保持不变 */ +.pay-btns{display:flex;gap:16px;justify-content:center;} +.btn-primary,.btn-secondary{ + flex:1;padding:12px 0;border:none;border-radius:10px; + font-size:16px;font-weight:600;cursor:pointer; +} +.btn-primary{background:#12a400;color:#fff;} +.btn-secondary{background:#f0f0f0;} \ No newline at end of file diff --git a/docs/dev-ops/nginx/html/css/login.css b/docs/dev-ops/nginx/html/css/login.css new file mode 100644 index 0000000..525a8db --- /dev/null +++ b/docs/dev-ops/nginx/html/css/login.css @@ -0,0 +1,91 @@ +/* 整体布局 */ +*{margin:0;padding:0;box-sizing:border-box;font-family:'PingFang SC','Helvetica Neue',Arial,sans-serif;} + +body{ + height:100vh; + display:flex; + align-items:center; + justify-content:center; + background:#d5d1e8; /* 柔和紫色背景 */ +} + +.container{ + width:90%;max-width:420px; +} + +.login-form{ + background:#fff; + padding:36px 28px; + border-radius:14px; + box-shadow:0 4px 12px rgba(0,0,0,.08); +} + +.login-form h2{ + font-size:24px; + font-weight:600; + text-align:center; + margin-bottom:32px; +} + +/* 输入框组 */ +.input-group{ + position:relative; + margin-bottom:26px; +} + +.input-group input{ + width:100%; + padding:14px 16px; + font-size:16px; + border:2px solid #aaa3; + border-radius:10px; + outline:none; + transition:border .25s; +} + +.input-group input:focus{ + border-color:#1296ff; +} + +.input-group label{ + position:absolute; + left:18px; + top:50%; + transform:translateY(-50%); + color:#999; + pointer-events:none; + transition:all .25s; +} + +/* 上浮效果 */ +.input-group input:focus + label, +.input-group input:not(:placeholder-shown) + label{ + top:0; + transform:translateY(-50%) scale(.86); + background:#fff; + padding:0 4px; + color:#1296ff; +} + +/* 登录按钮 */ +button[type="submit"]{ + width:100%; + padding:14px 0; + font-size:18px; + font-weight:600; + color:#fff; + background:#1296ff; + border:none; + border-radius:10px; + cursor:pointer; + transition:opacity .25s; +} +button[type="submit"]:hover{opacity:.9;} + +.error-message{ + margin-top:18px; + font-size:14px; + color:#e02424; + text-align:center; + display:none; +} diff --git a/docs/dev-ops/nginx/html/images/goods_info1.png b/docs/dev-ops/nginx/html/images/goods_info1.png new file mode 100644 index 0000000..4e80f6a Binary files /dev/null and b/docs/dev-ops/nginx/html/images/goods_info1.png differ diff --git a/docs/dev-ops/nginx/html/images/goods_info2.png b/docs/dev-ops/nginx/html/images/goods_info2.png new file mode 100644 index 0000000..56fe32d Binary files /dev/null and b/docs/dev-ops/nginx/html/images/goods_info2.png differ diff --git a/docs/dev-ops/nginx/html/images/goods_info3.png b/docs/dev-ops/nginx/html/images/goods_info3.png new file mode 100644 index 0000000..57429a9 Binary files /dev/null and b/docs/dev-ops/nginx/html/images/goods_info3.png differ diff --git a/docs/dev-ops/nginx/html/images/qrcode.png b/docs/dev-ops/nginx/html/images/qrcode.png new file mode 100644 index 0000000..79f2351 Binary files /dev/null and b/docs/dev-ops/nginx/html/images/qrcode.png differ diff --git a/docs/dev-ops/nginx/html/index.html b/docs/dev-ops/nginx/html/index.html new file mode 100644 index 0000000..6c53f0e --- /dev/null +++ b/docs/dev-ops/nginx/html/index.html @@ -0,0 +1,143 @@ + + + + + + 手写MyBatis:渐进式源码实践 - 拼多多 + + + + + +
+
+
+
+
+
+
+
+ + +
+
+
80
+
100
+
+
手写MyBatis:渐进式源码实践(全彩)
+
+ 大促优惠 + 直降¥60,76人再抢,参与马上抢到 +
+
+ + + +
+
+ + +
+
正在拼单
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+ +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ + +
+
+ + 首页 +
+
+ + 收藏 +
+
+ + 购物车 +
+
+ + +
+
+ +
+
+

请扫码支付

+

+ 支付二维码 +
+ + +
+
+
+ + + + \ No newline at end of file diff --git a/docs/dev-ops/nginx/html/js/index.js b/docs/dev-ops/nginx/html/js/index.js new file mode 100644 index 0000000..a04facb --- /dev/null +++ b/docs/dev-ops/nginx/html/js/index.js @@ -0,0 +1,198 @@ +// index.js (改进版) +// 功能: +// 1. 解决用户列表竖向轮播在无缝跳转时出现的卡顿/闪动问题 +// 2. 为每条拼单信息增加实时倒计时,秒级更新 + +document.addEventListener('DOMContentLoaded', function () { + /* ---------- 顶部横向轮播 ---------- */ + const wrapper = document.querySelector('.swiper-wrapper'); + const slides = [...wrapper.children]; + const pagination = document.querySelector('.swiper-pagination'); + const count = slides.length; + + let current = 0; // 当前索引 + let startX = 0; // 手势起点 + let dragging = false; // 拖动状态 + let timer = null; // 自动轮播计时器 + + /* --- 1. 生成分页小圆点 --- */ + for(let i=0;igoTo(i)); + pagination.appendChild(dot); + } + const dots = pagination.children; + + /* --- 2. 切换核心 --- */ + function goTo(index){ + current = (index + count) % count; // 防越界 + wrapper.style.transition = 'transform .3s ease'; + wrapper.style.transform = `translateX(-${current*100}%)`; + [...dots].forEach((d,i)=>d.classList.toggle('active',i===current)); + } + + /* --- 3. 自动轮播 --- */ + function startAuto(){ + timer = setInterval(()=>goTo(current+1),3000); + } + function stopAuto(){ + clearInterval(timer); + } + startAuto(); + + /* --- 4. 手势/鼠标拖动 --- */ + const getX = e => e.touches ? e.touches[0].clientX : e.clientX; + + wrapper.addEventListener('pointerdown',e=>{ + stopAuto(); + 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))`; + }); + + const endSwipe = e=>{ + if(!dragging) return; + dragging = false; + const diff = getX(e) - startX; + const limit = wrapper.offsetWidth * 0.15; // 15% 宽度阈值 + if(diff > limit) goTo(current-1); + else if(diff < -limit) goTo(current+1); + else goTo(current); // 回弹 + startAuto(); + }; + wrapper.addEventListener('pointerup', endSwipe); + wrapper.addEventListener('pointercancel', endSwipe); + wrapper.addEventListener('pointerleave', endSwipe); + + + /* --------- 动态生成“xx人在抢,参与可立即拼成” --------- */ + const leftNum = Math.floor(Math.random() * 101) + 100; // 100 ~ 200 之间的随机整数 + const leftSpan = document.querySelector('.group-left'); + if (leftSpan) leftSpan.textContent = `${leftNum}人在抢,参与可立即拼成`; + + /* ---------- 拼单用户纵向轮播 ---------- */ + const userList = document.getElementById('userList'); + const userItems = userList.querySelectorAll('.user-item'); + const itemHeight = userItems[0].offsetHeight; + let userIndex = 0; + const originalCount = userItems.length; + + // 克隆第一条放到末尾,实现无缝衔接 + userList.appendChild(userItems[0].cloneNode(true)); + + userList.addEventListener('transitionend', () => { + if (userIndex >= originalCount) { + // 闪电跳回首条,关闭过渡以避免闪屏 + userList.style.transition = 'none'; + userList.style.transform = 'translateY(0)'; + userIndex = 0; + // 强制回流,保证下次 transition 生效 + void userList.offsetWidth; + } + }); + + function rotateUsers() { + userIndex++; + userList.style.transition = 'transform 0.5s ease'; + userList.style.transform = `translateY(${-userIndex * itemHeight}px)`; + } + + // 每 3 秒滚动一次 + setInterval(rotateUsers, 3000); + // 页面加载后立即滚动一次,保证视觉一致 + rotateUsers(); + + /* ---------- 拼单倒计时 ---------- */ + const countdownEls = document.querySelectorAll('.countdown'); + // 预处理:把初始文本转为秒数 + const countdownData = Array.from(countdownEls).map((el) => ({ + el, + remain: parseTime(el.textContent.trim()), + })); + + function parseTime(t) { + const [h = '00', m = '00', s = '00'] = t.split(':'); + return Number(h) * 3600 + Number(m) * 60 + Number(s); + } + function formatTime(sec) { + const h = String(Math.floor(sec / 3600)).padStart(2, '0'); + const m = String(Math.floor((sec % 3600) / 60)).padStart(2, '0'); + const s = String(sec % 60).padStart(2, '0'); + return `${h}:${m}:${s}`; + } + + function updateCountdown() { + countdownData.forEach((c) => { + if (c.remain > 0) { + c.remain -= 1; + c.el.textContent = formatTime(c.remain); + } else { + c.el.textContent = '00:00:00'; + // 可选:到点后给整条加灰色样式,并禁用按钮 + const item = c.el.closest('.user-item'); + item?.classList.add('expired'); + item?.querySelector('.buy-btn')?.setAttribute('disabled', 'disabled'); + } + }); + } + + /* --------- 给每条拼单状态前加 “仅剩 x 人成团” --------- */ + document.querySelectorAll('.user-status').forEach(statusEl=>{ + const x = Math.floor(Math.random()*3)+1; // 1 ~ 3 随机整数 + const span = document.createElement('span'); + span.className = 'left-num'; + span.textContent = `仅剩${x}人成团,`; // 注意带逗号或空格 + statusEl.prepend(span); + }); + + /* ============= 支付 & 登录判断 ============= */ + const modal = document.getElementById('paymentModal'); + const paymentAmount = document.getElementById('paymentAmount'); + const cancelPayment = document.getElementById('cancelPayment'); + const completePayment= document.getElementById('completePayment'); + + /* 把 3 类按钮统一选出来 */ + [...document.querySelectorAll('.buy-btn, .btn-single, .btn-group')].forEach(btn=>{ + btn.addEventListener('click',()=>{ + /* 简单读取 cookie 判断是否登录 */ + if(!getCookie('username')){ + window.location.href='login.html'; // 跳转到登录页 + return; + } + /* 已登录:弹出支付弹窗 */ + const price = btn.dataset.price || '0'; + paymentAmount.textContent = `支付金额 ¥${price}`; + modal.style.display='flex'; + }); + }); + + /* 取消/完成支付 */ + cancelPayment.addEventListener('click', ()=>modal.style.display='none'); + completePayment.addEventListener('click', ()=>{ + alert('支付成功!'); + modal.style.display='none'; + }); + + /* 读取 cookie 工具函数 */ + function getCookie(name){ + return document.cookie.split(';').map(c=>c.trim()) + .find(c=>c.startsWith(name+'='))?.split('=')[1] || null; + } + + /* 点击遮罩空白关闭弹窗 */ + modal.addEventListener('click', e=>{ + if(e.target===modal) modal.style.display='none'; + }); + + + // 每秒刷新一次倒计时 + setInterval(updateCountdown, 1000); +}); diff --git a/docs/dev-ops/nginx/html/js/login.js b/docs/dev-ops/nginx/html/js/login.js new file mode 100644 index 0000000..d6909af --- /dev/null +++ b/docs/dev-ops/nginx/html/js/login.js @@ -0,0 +1,28 @@ +document.addEventListener('DOMContentLoaded',()=>{ + const loginForm = document.getElementById('loginForm'); + const errorMessage = document.getElementById('errorMessage'); + + loginForm.addEventListener('submit',e=>{ + e.preventDefault(); + + const username = document.getElementById('username').value.trim(); + const password = document.getElementById('password').value.trim(); + + if(!username || !password){ + errorMessage.textContent = '用户名和密码不能为空'; + errorMessage.style.display = 'block'; + return; + } + + /* 这里可替换为真实校验逻辑 —— 目前直接视为成功 */ + errorMessage.style.display = 'none'; + + /* 写入 cookie,1 天有效 */ + const expire = new Date(); + expire.setDate(expire.getDate() + 1); + document.cookie = `username=${encodeURIComponent(username)}; expires=${expire.toUTCString()}; path=/`; + + /* 登录后跳回首页(商品详情页) */ + window.location.href = 'index.html'; + }); +}); diff --git a/docs/dev-ops/nginx/html/login.html b/docs/dev-ops/nginx/html/login.html new file mode 100644 index 0000000..221de38 --- /dev/null +++ b/docs/dev-ops/nginx/html/login.html @@ -0,0 +1,31 @@ + + + + + + 欢迎登录 - 小傅哥拼团 + + + +
+ +
+ + + +