新增授权码数据的管理功能,改名原本的授权码管理为订单管理

This commit is contained in:
wangli 2025-12-30 02:28:52 +08:00
parent 49fcf172ce
commit 9c518bf352
4 changed files with 266 additions and 4 deletions

View File

@ -116,7 +116,7 @@ public class AdminController {
}
/**
* 获取所有有效的授权码
* 获取所有有效的授权码 (用于订单管理)
* 仅管理员可访问
*
* @return 授权码列表
@ -128,6 +128,46 @@ public class AdminController {
return jdbcTemplate.queryForList("SELECT code, order_no, login_limit, login_count, expires_at FROM auth_codes WHERE status = 1 ORDER BY created_at DESC");
}
/**
* 获取所有授权码 (用于授权码管理)
* 支持分页查询
*/
@GetMapping("/auth-codes/list")
@ResponseBody
@SaCheckRole("admin")
public Map<String, Object> getAuthCodesList(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) {
int offset = (page - 1) * size;
String countSql = "SELECT COUNT(*) FROM auth_codes";
int total = jdbcTemplate.queryForObject(countSql, Integer.class);
String sql = "SELECT * FROM auth_codes ORDER BY created_at DESC LIMIT ? OFFSET ?";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, size, offset);
Map<String, Object> result = new HashMap<>();
result.put("total", total);
result.put("list", list);
return result;
}
/**
* 更新授权码信息
*/
@PostMapping("/auth-codes/update")
@ResponseBody
@SaCheckRole("admin")
public String updateAuthCode(@RequestBody Map<String, Object> payload) {
String code = (String) payload.get("code");
String orderNo = (String) payload.get("order_no");
String expiresAt = (String) payload.get("expires_at");
Integer loginLimit = Integer.parseInt(payload.get("login_limit").toString());
Integer status = Integer.parseInt(payload.get("status").toString());
String sql = "UPDATE auth_codes SET order_no = ?, expires_at = ?, login_limit = ?, status = ? WHERE code = ?";
int rows = jdbcTemplate.update(sql, orderNo, expiresAt, loginLimit, status, code);
return rows > 0 ? "ok" : "error";
}
/**
* 获取指定授权码的购物车内容
* 仅管理员可访问

View File

@ -56,7 +56,7 @@ public class PageController {
}
/**
* 授权码管理页面
* 订单管理页面 (原授权码管理)
*/
@GetMapping("/admin/codes")
@SaCheckRole("admin")
@ -64,6 +64,15 @@ public class PageController {
return "admin_codes";
}
/**
* 授权码管理页面 (新增)
*/
@GetMapping("/admin/auth-codes")
@SaCheckRole("admin")
public String adminAuthCodesPage() {
return "admin_auth_codes";
}
/**
* 文章管理页面
*/

View File

@ -39,7 +39,7 @@
border-radius: 20px;
box-shadow: var(--card-shadow);
text-align: center;
width: 400px;
width: 600px;
}
h1 {
@ -49,7 +49,7 @@
.menu-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
}
@ -102,6 +102,10 @@
<h1>后台管理系统</h1>
<div class="menu-grid">
<a href="/admin/codes" class="menu-item">
<div class="menu-icon">📦</div>
<div class="menu-title">订单管理</div>
</a>
<a href="/admin/auth-codes" class="menu-item">
<div class="menu-icon">🔑</div>
<div class="menu-title">授权码管理</div>
</a>

View File

@ -0,0 +1,209 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>授权码管理</title>
<script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
<style>
:root {
--primary-color: #007bff;
--bg-color: #e9f5ff;
--glass-bg: rgba(255, 255, 255, 0.6);
--card-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07);
}
body {
font-family: sans-serif;
background-color: var(--bg-color);
margin: 0;
padding: 20px;
min-height: 100vh;
box-sizing: border-box;
}
#particles-js {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 0;
}
.container {
position: relative;
z-index: 1;
max-width: 1200px;
margin: 0 auto;
background: var(--glass-bg);
backdrop-filter: blur(10px);
padding: 20px;
border-radius: 8px;
box-shadow: var(--card-shadow);
}
h1 { margin-top: 0; color: #333; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; background: #fff; border-radius: 8px; overflow: hidden; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
th { background-color: #f8f9fa; font-weight: 600; }
input[type="text"], input[type="number"], input[type="datetime-local"], select {
width: 100%; padding: 6px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px;
}
.btn { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; color: #fff; font-size: 14px; }
.btn-save { background-color: #28a745; }
.btn-save:hover { background-color: #218838; }
.pagination { margin-top: 20px; display: flex; justify-content: center; gap: 10px; }
.page-btn { padding: 8px 12px; border: 1px solid #ddd; background: #fff; cursor: pointer; border-radius: 4px; }
.page-btn.active { background-color: #007bff; color: #fff; border-color: #007bff; }
.back-link { display: inline-block; margin-bottom: 20px; color: #007bff; text-decoration: none; font-weight: bold; }
</style>
</head>
<body>
<div id="particles-js"></div>
<div class="container">
<a href="/admin" class="back-link">&larr; 返回后台首页</a>
<h1>授权码管理</h1>
<table>
<thead>
<tr>
<th>授权码</th>
<th>订单号</th>
<th>过期时间</th>
<th>登录限制</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="table-body">
<!-- Data will be populated here -->
</tbody>
</table>
<div class="pagination" id="pagination"></div>
</div>
<script>
let currentPage = 1;
const pageSize = 10;
async function loadData(page) {
currentPage = page;
const response = await fetch(`/api/admin/auth-codes/list?page=${page}&size=${pageSize}`);
const data = await response.json();
renderTable(data.list);
renderPagination(data.total);
}
function renderTable(list) {
const tbody = document.getElementById('table-body');
tbody.innerHTML = '';
list.forEach(item => {
const tr = document.createElement('tr');
// 格式化日期时间,适配 datetime-local 输入框 (yyyy-MM-ddTHH:mm)
let formattedDate = '';
if (item.expires_at) {
// 尝试解析 ISO 格式
try {
const date = new Date(item.expires_at);
// 调整时区偏移,转为本地时间字符串
const offset = date.getTimezoneOffset() * 60000;
const localISOTime = (new Date(date - offset)).toISOString().slice(0, 16);
formattedDate = localISOTime;
} catch (e) {
// 如果解析失败,尝试直接使用(假设已经是正确格式)
formattedDate = item.expires_at;
}
}
tr.innerHTML = `
<td>${item.code}</td>
<td><input type="text" value="${item.order_no || ''}" id="order-${item.code}"></td>
<td><input type="datetime-local" value="${formattedDate}" id="expire-${item.code}"></td>
<td><input type="number" value="${item.login_limit}" id="limit-${item.code}"></td>
<td>
<select id="status-${item.code}">
<option value="1" ${item.status === 1 ? 'selected' : ''}>有效</option>
<option value="0" ${item.status === 0 ? 'selected' : ''}>无效</option>
<option value="2" ${item.status === 2 ? 'selected' : ''}>已交付</option>
</select>
</td>
<td>
<button class="btn btn-save" onclick="saveChanges('${item.code}')">保存</button>
</td>
`;
tbody.appendChild(tr);
});
}
function renderPagination(total) {
const totalPages = Math.ceil(total / pageSize);
const pagination = document.getElementById('pagination');
pagination.innerHTML = '';
for (let i = 1; i <= totalPages; i++) {
const btn = document.createElement('button');
btn.className = `page-btn ${i === currentPage ? 'active' : ''}`;
btn.innerText = i;
btn.onclick = () => loadData(i);
pagination.appendChild(btn);
}
}
async function saveChanges(code) {
const orderNo = document.getElementById(`order-${code}`).value;
const expiresAt = document.getElementById(`expire-${code}`).value;
const loginLimit = document.getElementById(`limit-${code}`).value;
const status = document.getElementById(`status-${code}`).value;
try {
const response = await fetch('/api/admin/auth-codes/update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
code: code,
order_no: orderNo,
expires_at: expiresAt,
login_limit: loginLimit,
status: status
})
});
if (await response.text() === 'ok') {
alert('保存成功');
loadData(currentPage);
} else {
alert('保存失败');
}
} catch (e) {
alert('网络错误');
}
}
document.addEventListener('DOMContentLoaded', () => {
loadData(1);
particlesJS('particles-js', {
"particles": {
"number": { "value": 80, "density": { "enable": true, "value_area": 800 } },
"color": { "value": "#007bff" },
"shape": { "type": "circle", "stroke": { "width": 0, "color": "#000000" }, "polygon": { "nb_sides": 5 } },
"opacity": { "value": 0.5, "random": false, "anim": { "enable": false, "speed": 1, "opacity_min": 0.1, "sync": false } },
"size": { "value": 3, "random": true, "anim": { "enable": false, "speed": 40, "size_min": 0.1, "sync": false } },
"line_linked": { "enable": true, "distance": 150, "color": "#007bff", "opacity": 0.4, "width": 1 },
"move": { "enable": true, "speed": 6, "direction": "none", "random": false, "straight": false, "out_mode": "out", "bounce": false, "attract": { "enable": false, "rotateX": 600, "rotateY": 1200 } }
},
"interactivity": {
"detect_on": "canvas",
"events": { "onhover": { "enable": true, "mode": "repulse" }, "onclick": { "enable": true, "mode": "push" }, "resize": true },
"modes": { "grab": { "distance": 400, "line_linked": { "opacity": 1 } }, "bubble": { "distance": 400, "size": 40, "duration": 2, "opacity": 8, "speed": 3 }, "repulse": { "distance": 200, "duration": 0.4 }, "push": { "particles_nb": 4 }, "remove": { "particles_nb": 2 } }
},
"retina_detect": true
});
});
</script>
</body>
</html>