|
|
<!doctype html><html lang="zh-CN"><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <title>落地页详情</title> <style> body, html { margin:0; padding:0; height:100%; font-family: Arial, "Helvetica Neue", "PingFang SC", "Microsoft Yahei"; background:#f4f6f8; color:#333; } .page-container { width:100%; height:100vh; display:flex; flex-direction:column; } .top-bar { height:60px; background:#fff; color:#333; padding:0 20px; display:flex; align-items:center; font-size:18px; font-weight:600; border-bottom:1px solid #eee; } .main-container { display:flex; flex:1; overflow:hidden; } .sidebar { width:220px; background:#f5f7fa; border-right:1px solid #e8e8e8; padding:16px; box-sizing:border-box; } .sidebar h3 { margin:0 0 12px 0; font-size:14px; color:#333; } .content-area { flex:1; padding:20px; overflow:auto; background:#fff; } .content-header { display:flex; justify-content:flex-end; margin-bottom:15px; padding-bottom:10px; border-bottom:1px solid #e8e8e8; } .filter-section { margin-bottom:20px; } .filter-form { display:flex; align-items:center; flex-wrap:wrap; gap:12px; } .filter-form label { font-size:14px; color:#333; margin-right:6px; } .time-separator { margin:0 10px; } .btn { display:inline-block; padding:8px 12px; border-radius:6px; border:0; cursor:pointer; } .btn-primary { background:#1890ff; color:#fff; } .btn-plain { background:#fff; border:1px solid #dcdcdc; color:#333; } table { width:100%; border-collapse:collapse; background:#fff; } th, td { padding:12px 8px; border-bottom:1px solid #f0f0f0; text-align:center; font-size:14px; } th { background:#fafafa; font-weight:600; } .pagination-container { display:flex; justify-content:flex-end; align-items:center; margin-top:10px; gap:8px; } select, input[type="number"] { padding:6px; border-radius:4px; border:1px solid #dcdcdc; } .loading { display:inline-block; margin-left:8px; color:#1890ff; font-size:13px; } .msg-box { position:fixed; right:20px; bottom:20px; z-index:9999; padding:8px 12px; border-radius:6px; color:#fff; box-shadow:0 4px 12px rgba(0,0,0,0.1); display:none; } .msg-success { background:#67c23a; } .msg-error { background:#f56c6c; } @media (max-width:700px) { .sidebar { display:none; } .content-area { padding:12px; } } #msgBoxTop { position: fixed; top: 16px; /* 距离顶部距离,可调整 */ left: 50%; transform: translateX(-50%) translateY(-8px); z-index: 2147483647; /* 尽可能置顶 */ min-width: 220px; max-width: 92%; padding: 10px 16px; border-radius: 8px; color: #fff; box-shadow: 0 8px 28px rgba(0,0,0,0.18); display: none; align-items: center; justify-content: center; font-size: 14px; text-align: center; white-space: pre-wrap; pointer-events: none; /* 不阻塞下方交互;如果需要可改为 auto */}
/* 显示时的动画 */#msgBoxTop.show { display: flex; animation: msg-slide-down 220ms cubic-bezier(.2,.9,.2,1); transform: translateX(-50%) translateY(0);}
/* 轻微淡出(移除 show 时也会自然消失)*/@keyframes msg-slide-down { from { opacity: 0; transform: translateX(-50%) translateY(-8px); } to { opacity: 1; transform: translateX(-50%) translateY(0); }}
/* 颜色主题 */#msgBoxTop.msg-success { background: #67c23a; }#msgBoxTop.msg-error { background: #f56c6c; } </style></head><body> <div class="page-container"> <div class="top-bar"><span>详情</span></div> <div class="main-container"> <div class="sidebar"> <h3>管理菜单</h3> <div>• 落地页管理</div> </div>
<div class="content-area"> <div class="content-header"> <button id="btnBack" class="btn btn-plain">返回上一级页面</button> </div>
<div class="filter-section"> <div class="filter-form" role="form" aria-label="筛选"> <div> <label>打开网页时间:</label> <input id="filterStart" type="datetime-local" style="width:180px" /> <span class="time-separator">至</span> <input id="filterEnd" type="datetime-local" style="width:180px" /> </div>
<div> <label>收下状态:</label> <select id="filterStatus" style="width:150px"> <option value="">全部</option> <option value="1">是</option> <option value="0">否</option> </select> </div>
<div> <button id="btnQuery" class="btn btn-primary">查询</button> </div>
<div> <button id="btnExport" class="btn btn-primary btn-plain">导出</button> </div> </div> </div>
<div id="tableWrap" style="min-height:200px"> <table aria-describedby="详情列表"> <thead> <tr> <th style="width:80px">序号</th> <th style="min-width:180px">用户信息</th> <th style="min-width:180px">打开网页时间</th> <th style="min-width:120px">收下状态</th> </tr> </thead> <tbody id="tableBody"> <!-- JS 渲染 --> </tbody> </table>
<div id="loadingIndicator" style="padding:10px 0; display:none;"><span class="loading">加载中...</span></div> <div id="noData" style="padding:20px 0; display:none; color:#666; text-align:center;">暂无数据</div> </div>
<div class="pagination-container" style="margin-top:14px;"> <div>共 <span id="totalCount">0</span> 条</div> <div style="margin-left:12px;"> 每页 <select id="pageSizeSelect"> <option value="20">20</option> <option value="50">50</option> <option value="100">100</option> </select> 条 </div> <div style="margin-left:12px;"> <button id="prevBtn" class="btn">上一页</button> <span style="margin:0 8px">第 <span id="currentPage">1</span> 页</span> <button id="nextBtn" class="btn">下一页</button> </div> </div> </div> </div> </div>
<!-- 消息提示 --> <div id="msgBox" class="msg-box"></div>
<script type="module"> import { getLandingDetailApi, exportLandingDetailApi } from './src/api/member.js';;(function () { // ====== 状态 ====== let tableData = []; let currentPage = 1; let pageSize = 20; let totalCount = 0;
// ====== DOM ====== const btnBack = document.getElementById('btnBack'); const btnQuery = document.getElementById('btnQuery'); const btnExport = document.getElementById('btnExport'); const filterStart = document.getElementById('filterStart'); const filterEnd = document.getElementById('filterEnd'); const filterStatus = document.getElementById('filterStatus'); const tableBody = document.getElementById('tableBody'); const totalCountEl = document.getElementById('totalCount'); const currentPageEl = document.getElementById('currentPage'); const pageSizeSelect = document.getElementById('pageSizeSelect'); const prevBtn = document.getElementById('prevBtn'); const nextBtn = document.getElementById('nextBtn'); const loadingIndicator = document.getElementById('loadingIndicator'); const noData = document.getElementById('noData'); const msgBox = document.getElementById('msgBox');
function showMessage(text, type = 'success', duration = 3000) { let el = document.getElementById('msgBoxTop'); if (!el) { el = document.createElement('div'); el.id = 'msgBoxTop'; el.setAttribute('role', 'status'); el.setAttribute('aria-live', 'polite'); document.body.appendChild(el); }
// 更新内容与样式 el.textContent = text; el.classList.remove('msg-success', 'msg-error', 'show'); el.classList.add(type === 'error' ? 'msg-error' : 'msg-success');
// 触发显示(重新触发动画) // pointer-events: none 允许消息不阻塞下层交互;若想阻塞改为 auto requestAnimationFrame(() => { el.classList.add('show'); });
// 清除旧定时器并设置新定时器隐藏 if (el._timer) { clearTimeout(el._timer); } el._timer = setTimeout(() => { el.classList.remove('show'); // 可选:在动画结束后彻底隐藏(防止 display:flex 仍在) setTimeout(() => { if (!el.classList.contains('show')) { el.style.display = 'none'; // 恢复 display 控制以便再次显示时通过 .show 控制显示 el.style.display = ''; } }, 240); }, Math.max(800, duration)); // 最少保留 800ms,避免闪烁}
function formatDateTimeInput(v) { // 输入 v: "2025-10-23T14:00" -> 输出 "YYYY-MM-DD HH:mm:ss" if (!v) return ''; // v may be "2025-10-23T14:00" or "2025-10-23T14:00:00" let s = v.replace('T', ' '); if (s.length === 16) s += ':00'; return s; }
function parseQueryParam(name) { const sp = new URLSearchParams(location.search); return sp.get(name); } function syncEndMinWithStart() { if (!filterStart || !filterEnd) return; // set min attribute on end input so browser picker enforces it if (filterStart.value) { filterEnd.min = filterStart.value; } else { filterEnd.removeAttribute('min'); } } function isTimeRangeValid() { if (!filterStart.value || !filterEnd.value) return true; // 空值不做强制(按需求可改) // Compare strings directly is OK for datetime-local "YYYY-MM-DDTHH:MM" format return filterEnd.value >= filterStart.value; }// 绑定事件:开始时间变化时更新 end.min,并修正 end 小于 start 的情况 filterStart && filterStart.addEventListener('change', () => { syncEndMinWithStart(); if (filterEnd.value && filterEnd.value < filterStart.value) { // 把结束时间自动设为开始时间,提示用户 filterEnd.value = filterStart.value; showMessage('结束时间已被调整为不早于开始时间', 'error'); } });
// 绑定事件:结束时间变化时校验,如果不合法则阻止并提示(并回退到开始时间或清空) filterEnd && filterEnd.addEventListener('change', () => { if (!isTimeRangeValid()) { showMessage('结束时间不能早于开始时间,请调整', 'error'); // 方案:把结束时间重置为开始时间(更直观),也可选择清空 if (filterStart.value) { filterEnd.value = filterStart.value; } else { filterEnd.value = ''; } } }); // 构建列表查询参数对象 function buildQueryParams() { const id = parseQueryParam('id'); const startTime = formatDateTimeInput(filterStart.value); const endTime = formatDateTimeInput(filterEnd.value); const state = (filterStatus.value === '' || filterStatus.value === null) ? -1 : filterStatus.value; return { id, page: currentPage, page_size: pageSize, start_time: startTime, end_time: endTime, state }; }
// 将 params 对象转为查询字符串 function toQueryString(params) { return Object.keys(params) .filter(k => params[k] !== undefined && params[k] !== null && params[k] !== '') .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k])) .join('&'); }
// ====== 数据请求 ====== async function getLandingDetail() { const id = parseQueryParam('id'); if (!id) { showMessage('未获取到活动ID,无法加载详情', 'error'); return; } loadingIndicator.style.display = 'inline-block'; tableBody.innerHTML = ''; noData.style.display = 'none'; try { const res = await getLandingDetailApi({ id: id, page: currentPage, page_size: pageSize, start_time: formatDateTimeInput(filterStart.value), end_time: formatDateTimeInput(filterEnd.value), state: (filterStatus.value === '' || filterStatus.value === null) ? -1 : filterStatus.value }); if(res.code == 200){ tableData = res.data.list; totalCount = res.data.total ; renderTable(); } else { showMessage('获取活动详情失败', 'error'); } } catch (err) { console.error(err); showMessage('网络异常,无法加载数据', 'error'); } finally { loadingIndicator.style.display = 'none'; } }
function renderTable() { tableBody.innerHTML = ''; if (!tableData || tableData.length === 0) { noData.style.display = 'block'; return } else { noData.style.display = 'none'; }
tableData.forEach((row, idx) => { const tr = document.createElement('tr');
const tdIndex = document.createElement('td'); tdIndex.textContent = (currentPage - 1) * pageSize + idx + 1; tr.appendChild(tdIndex);
const tdUser = document.createElement('td'); tdUser.textContent = row.user_info || ''; tr.appendChild(tdUser);
const tdCreated = document.createElement('td'); tdCreated.textContent = row.created_at || ''; tr.appendChild(tdCreated);
const tdState = document.createElement('td'); tdState.textContent = row.state === 1 ? '是' : '否'; tr.appendChild(tdState);
tableBody.appendChild(tr); });
totalCountEl.textContent = totalCount; currentPageEl.textContent = currentPage; }
// ====== 导出功能 ====== async function handleExport() { const id = parseQueryParam('id'); if (!id) { showMessage('未获取到活动ID,无法导出', 'error'); return; } loadingIndicator.style.display = 'inline-block'; try { const res = await exportLandingDetailApi({ id: id, start_time: formatDateTimeInput(filterStart.value), end_time: formatDateTimeInput(filterEnd.value), state: (filterStatus.value === '' || filterStatus.value === null) ? -1 : filterStatus.value }); const data = res.data !== undefined ? res.data : res; const blob = new Blob([data], { type: res.headers?.['content-type'] || 'application/octet-stream' }); let filename = `活动数据.xlsx`; const blobUrl = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = blobUrl; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(blobUrl); showMessage('导出成功'); } catch (err) { console.error(err); showMessage('导出失败,请重试', 'error'); } finally { loadingIndicator.style.display = 'none'; } }
// ====== 分页事件 ====== prevBtn.addEventListener('click', () => { if (currentPage > 1) { currentPage--; getLandingDetail(); } }); nextBtn.addEventListener('click', () => { const maxPage = Math.max(1, Math.ceil((totalCount || 1) / pageSize)); if (currentPage < maxPage) { currentPage++; getLandingDetail(); } }); pageSizeSelect.addEventListener('change', (e) => { pageSize = Number(e.target.value); currentPage = 1; getLandingDetail(); });
// ====== 交互事件 ====== btnBack.addEventListener('click', () => { history.back(); }); btnQuery.addEventListener('click', () => { currentPage = 1; getLandingDetail(); }); btnExport.addEventListener('click', () => { handleExport(); });
// 页面加载时读取 id 并请求数据 (function init() { const id = parseQueryParam('id'); if (!id) { showMessage('缺少活动ID,请在 URL 里添加 ?id=xxx', 'error'); } // use defaults from HTML (pageSizeSelect initial value) pageSize = Number(pageSizeSelect.value || 20); getLandingDetail(); })();
})();</script></body></html>
|