Browse Source

管理页

master
maziyang 3 months ago
parent
commit
6a63d17993
  1. 1085
      adminConfig.html
  2. 887
      adminDetail.html
  3. 23
      src/api/member.js
  4. 4
      vite.config.js

1085
adminConfig.html
File diff suppressed because it is too large
View File

887
adminDetail.html

@ -1,549 +1,400 @@
<!DOCTYPE html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理后台</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>落地页详情</title>
<style>
/* 基础样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", sans-serif;
}
body {
background-color: #f5f7fa;
color: #333;
}
.container {
display: flex;
min-height: 100vh;
}
/* 侧边栏样式 */
.sidebar {
width: 220px;
background-color: #2c3e50;
color: #fff;
padding: 20px 0;
}
.sidebar-logo {
text-align: center;
padding: 0 20px 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
margin-bottom: 20px;
}
.sidebar-logo h2 {
font-size: 18px;
margin-top: 10px;
}
.sidebar-menu {
list-style: none;
}
.sidebar-menu li a {
display: flex;
align-items: center;
padding: 12px 20px;
color: rgba(255,255,255,0.8);
text-decoration: none;
transition: all 0.3s;
}
.sidebar-menu li a:hover,
.sidebar-menu li a.active {
background-color: #34495e;
color: #fff;
}
.sidebar-menu li a i {
margin-right: 10px;
font-size: 16px;
}
/* 主内容区样式 */
.main-content {
flex: 1;
padding: 20px;
overflow-y: auto;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.page-header h2 {
font-size: 20px;
font-weight: 600;
color: #333;
}
.btn {
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #fff;
cursor: pointer;
color: #333;
text-decoration: none;
}
.btn-primary {
background-color: #3498db;
color: #fff;
border-color: #3498db;
}
.btn-danger {
background-color: #e74c3c;
color: #fff;
border-color: #e74c3c;
}
/* 查询区域样式 */
.search-area {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap; /* 适配小屏幕 */
}
.search-area input,
.search-area select {
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 4px;
outline: none;
}
.search-area .date-picker {
display: flex;
align-items: center;
gap: 5px;
}
/* 表格样式 */
.table-container {
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
overflow: hidden;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th,
.data-table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #f0f0f0;
}
.data-table th {
background-color: #f9fafb;
font-weight: 600;
color: #666;
}
.data-table tbody tr:hover {
background-color: #f5f7fa;
}
/* 分页样式 */
.pagination {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-top: 1px solid #f0f0f0;
}
.pagination-info {
color: #666;
font-size: 14px;
}
.pagination-list {
display: flex;
list-style: none;
padding: 0;
margin: 0;
}
.pagination-list li {
margin: 0 2px;
}
.pagination-list a,
.pagination-list span {
display: inline-block;
width: 45px;
height: 45px;
line-height: 45px;
text-align: center;
border: 1px solid #ddd;
border-radius: 4px;
text-decoration: none;
color: #333;
font-size: 14px;
box-sizing: border-box;
}
.pagination-list a {
cursor: pointer;
}
.pagination-list a:hover,
.pagination-list a.active {
background-color: #3498db;
color: #fff;
border-color: #3498db;
}
.pagination-list span {
background-color: #f5f5f5;
color: #999;
cursor: not-allowed;
}
.pagination-jump {
display: flex;
align-items: center;
font-size: 14px;
gap: 5px;
}
.pagination-jump input {
width: 50px;
height: 32px;
margin: 0 5px;
padding: 0 5px;
border: 1px solid #ddd;
border-radius: 4px;
text-align: center;
box-sizing: border-box;
}
.pagination-jump button {
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #fff;
cursor: pointer;
height: 32px;
box-sizing: border-box;
}
/* 加载中样式 */
.loading {
text-align: center;
padding: 50px 0;
color: #666;
}
.loading i {
font-size: 24px;
margin-bottom: 10px;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
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>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
<div class="container">
<div class="sidebar">
<div class="sidebar-logo">
<i class="fa fa-line-chart" style="font-size: 24px;"></i>
<h2>后台管理系统</h2>
<div class="page-container">
<div class="top-bar"><span>详情</span></div>
<div class="main-container">
<div class="sidebar">
<h3>管理菜单</h3>
<div>• 落地页管理</div>
</div>
<ul class="sidebar-menu">
<li><a href="#" class="active"><i class="fa fa-file-text-o"></i> 落地页管理</a></li>
</ul>
</div>
<!-- 主内容区 -->
<div class="main-content">
<div class="page-header">
<div class="page-header h2">详情</div>
<a href="adminConfig.html" class="btn">返回上一级页面</a>
</div>
<div class="content-area">
<div class="content-header">
<button id="btnBack" class="btn btn-plain">返回上一级页面</button>
</div>
<!-- 查询区域 -->
<div class="search-area">
<label class="date-picker">
打开网页时间
<input type="date" id="startTime" placeholder="请选择开始时间" />
<input type="date" id="endTime" placeholder="请选择结束时间" />
</label>
<div>
<label for="statusSelect">收下状态</label>
<select id="statusSelect">
<option value="">请选择状态</option>
<option value="1"></option>
<option value="0"></option>
</select>
<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>
<button id="searchBtn" class="btn btn-primary">查询</button>
<button class="btn btn-danger">导出</button>
</div>
<!-- 数据表格 -->
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>序号</th>
<th>打开网页时间</th>
<th>收下状态</th>
</tr>
</thead>
<tbody id="tableBody">
<tr>
<td colspan="3" class="loading"> <!-- 注意:表格列数是3,colspan改为3 -->
<i class="fa fa-spinner"></i>
<p>加载中...</p>
</td>
</tr>
</tbody>
</table>
<!-- 分页控件 -->
<div class="pagination" id="paginationContainer">
<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>
<script type="module">
// 导入API函数(假设接口定义)
import { getLandingDetailApi } from './src/api/member.js';
// DOM元素
const tableBody = document.getElementById('tableBody');
const paginationContainer = document.getElementById('paginationContainer');
const searchBtn = document.getElementById('searchBtn');
const startTimeInput = document.getElementById('startTime');
const endTimeInput = document.getElementById('endTime');
const statusSelect = document.getElementById('statusSelect');
// 分页参数
let currentPage = 1;
const pageSize = 20;
let totalCount = 0;
let totalPages = 0;
// URL获取的id参数
let landingId = '';
// 初始化页面:从URL获取页码,请求后端数据
async function initPage() {
const urlParams = new URLSearchParams(window.location.search);
landingId = urlParams.get('id');
if (!landingId) {
renderEmptyState('缺少必要的参数');
return;
<!-- 消息提示 -->
<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 = '';
}
// 从后端请求数据
await fetchDataFromBackend();
}
// 核心:从后端API请求数据
async function fetchDataFromBackend() {
// 显示加载状态
tableBody.innerHTML = `
<tr>
<td colspan="3" class="loading">
<i class="fa fa-spinner"></i>
<p>加载中...</p>
</td>
</tr>
`;
paginationContainer.innerHTML = '';
try {
// 1. 构造请求参数(分页+查询条件)
const requestParams = {
id: landingId,
page: currentPage, // 当前页码
size: pageSize, // 每页条数
startTime: startTimeInput.value || '', // 开始时间(查询条件)
endTime: endTimeInput.value || '', // 结束时间(查询条件)
receiveStatus: statusSelect.value || '' // 收下状态(查询条件)
};
// 2. 调用导入的API函数
const result = await getLandingDetailApi(requestParams);
// 3. 处理API返回结果
if (result.code === 200) {
const { list, total } = result.data;
totalCount = total;
totalPages = Math.ceil(totalCount / pageSize);
renderTable(list);
renderPagination();
}, 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 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 {
renderEmptyState('获取数据失败');
}
} catch (error) {
// 捕获异常
console.error('请求数据异常:', error);
renderEmptyState('网络错误,请稍后重试');
showMessage('获取活动详情失败', 'error');
}
} catch (err) {
console.error(err);
showMessage('网络异常,无法加载数据', 'error');
} finally {
loadingIndicator.style.display = 'none';
}
}
// 渲染表格数据
function renderTable(dataList) {
if (!dataList || dataList.length === 0) {
renderEmptyState('暂无符合条件的数据');
return;
}
let html = '';
dataList.forEach((item, index) => {
// 计算序号:(当前页-1)*每页条数 + 索引+1
const serialNumber = (currentPage - 1) * pageSize + index + 1;
// 注意:item的字段名需与后端返回的字段一致
html += `
<tr>
<td>${serialNumber}</td>
<td>${formatDate(item.openTime || '')}</td>
<td>${item.receiveStatus === '1' ? '是' : item.receiveStatus === '0' ? '否' : ''}</td>
</tr>
`;
});
tableBody.innerHTML = html;
function renderTable() {
tableBody.innerHTML = '';
if (!tableData || tableData.length === 0) {
noData.style.display = 'block';
} else {
noData.style.display = 'none';
}
// 渲染空状态
function renderEmptyState(message) {
tableBody.innerHTML = `
<tr>
<td colspan="3" style="text-align: center; padding: 30px;">
<i class="fa fa-inbox" style="font-size: 24px; color: #ddd; margin-bottom: 10px;"></i>
<p>${message}</p>
</td>
</tr>
`;
paginationContainer.innerHTML = '';
}
tableData.forEach((row, idx) => {
const tr = document.createElement('tr');
// 渲染分页控件
function renderPagination() {
if (totalCount === 0) return;
// 生成页码列表
const pageNumbers = [];
let startPage = Math.max(1, currentPage - 2);
let endPage = Math.min(totalPages, currentPage + 2);
if (endPage - startPage < 4 && totalPages >= 5) {
if (startPage === 1) endPage = 5;
else if (endPage === totalPages) startPage = totalPages - 4;
}
for (let i = startPage; i <= endPage; i++) {
pageNumbers.push(i);
}
const tdIndex = document.createElement('td');
tdIndex.textContent = (currentPage - 1) * pageSize + idx + 1;
tr.appendChild(tdIndex);
// 分页HTML
const html = `
<div class="pagination-info">
共 ${totalCount} 条记录,当前第 ${currentPage} / ${totalPages} 页
</div>
<ul class="pagination-list">
<li>
${currentPage === 1
? `<span>首页</span>`
: `<a href="?page=1">首页</a>`
}
</li>
<li>
${currentPage > 1
? `<a href="?page=${currentPage - 1}">上一页</a>`
: `<span>上一页</span>`
}
</li>
${pageNumbers.map(num => `
<li>
<a href="?page=${num}" class="${num === currentPage ? 'active' : ''}">
${num}
</a>
</li>
`).join('')}
<li>
${currentPage < totalPages
? `<a href="?page=${currentPage + 1}">下一页</a>`
: `<span>下一页</span>`
}
</li>
<li>
${currentPage === totalPages
? `<span>尾页</span>`
: `<a href="?page=${totalPages}">尾页</a>`
}
</li>
</ul>
<div class="pagination-jump">
<span>跳至</span>
<input type="number" min="1" max="${totalPages}" value="${currentPage}" id="jumpPageInput">
<span></span>
<button id="jumpBtn">确定</button>
</div>
`;
const tdUser = document.createElement('td');
tdUser.textContent = row.user_info || '';
tr.appendChild(tdUser);
paginationContainer.innerHTML = html;
const tdCreated = document.createElement('td');
tdCreated.textContent = row.created_at || '';
tr.appendChild(tdCreated);
// 绑定跳转事件
document.getElementById('jumpBtn').addEventListener('click', handlePageJump);
document.getElementById('jumpPageInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') handlePageJump();
});
}
// 处理页码跳转
function handlePageJump() {
const pageInput = document.getElementById('jumpPageInput');
const page = parseInt(pageInput.value);
if (page && !isNaN(page) && page >= 1 && page <= totalPages && page !== currentPage) {
window.location.href = `?page=${page}`;
} else {
alert('请输入有效的页码');
}
}
const tdState = document.createElement('td');
tdState.textContent = row.state === 1 ? '是' : '否';
tr.appendChild(tdState);
// 查询按钮事件:点击后重新请求数据
searchBtn.addEventListener('click', async () => {
currentPage = 1; // 重置为第一页
await fetchDataFromBackend(); // 重新请求后端数据
tableBody.appendChild(tr);
});
// 工具函数:格式化日期(适配后端返回的时间格式)
function formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
// 处理无效日期
if (isNaN(date.getTime())) return dateString;
// 格式化显示:年-月-日 时:分:秒
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).replace(/\//g, '-');
}
// 工具函数:HTML转义(防止XSS攻击)
function escapeHtml(str) {
if (!str) return '';
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
// 页面加载时初始化
window.addEventListener('DOMContentLoaded', initPage);
</script>
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,
page: currentPage,
page_size: pageSize,
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>
</html>

23
src/api/member.js

@ -32,7 +32,16 @@ export function acceptCardApi(data) {
// 获取落地页活动列表
export function getLandingListApi(data) {
return request({
url: `${API_BASE_URL}/api/getLanding`,
url: `${API_BASE_URL}/api/activity/get`,
method: "post",
data: data,
});
}
// 添加落地页活动
export function addLandingApi(data) {
return request({
url: `${API_BASE_URL}/admin/activity/add`,
method: "post",
data: data,
});
@ -41,8 +50,18 @@ export function getLandingListApi(data) {
// 获取落地页活动列表
export function getLandingDetailApi(data) {
return request({
url: `${API_BASE_URL}/api/getLandingDetail`,
url: `${API_BASE_URL}/admin/activity/detail`,
method: "post",
data: data,
});
}
// 导出落地页活动列表
export function exportLandingDetailApi(data) {
return request({
url: `${API_BASE_URL}/admin/activity/export`,
method: "post",
data: data,
responseType: 'arraybuffer'
});
}

4
vite.config.js

@ -29,7 +29,9 @@ export default defineConfig(({ mode, command }) => {
outDir: outDirMap[mode] || 'dist',
rollupOptions: {
input: {
main: './index.html'
main: './index.html',
list:'./adminConfig.html',
detail:'./adminDetail.html'
},
output: {
entryFileNames: '[name].js', // 入口文件命名

Loading…
Cancel
Save