admin 页面增加统计功能
This commit is contained in:
parent
707b8c2551
commit
9025507bf3
@ -6,23 +6,25 @@ import cn.hutool.crypto.symmetric.AES;
|
|||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import eu.org.biwin.screen.model.GenerateCodeRequest;
|
import eu.org.biwin.screen.model.GenerateCodeRequest;
|
||||||
import eu.org.biwin.screen.model.ImageGroup;
|
import eu.org.biwin.screen.model.ImageGroup;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -35,12 +37,14 @@ import java.util.zip.ZipOutputStream;
|
|||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/api/admin")
|
@RequestMapping("/api/admin")
|
||||||
// **[REMOVED]** Removed @SaCheckRole("admin") from the class level
|
|
||||||
public class AdminController {
|
public class AdminController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private JdbcTemplate jdbcTemplate;
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DataSource dataSource;
|
||||||
|
|
||||||
@Value("${file.path}")
|
@Value("${file.path}")
|
||||||
private String rootPath;
|
private String rootPath;
|
||||||
@Value("${app.secure-key}")
|
@Value("${app.secure-key}")
|
||||||
@ -50,27 +54,42 @@ public class AdminController {
|
|||||||
|
|
||||||
private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||||
|
|
||||||
// This method is now PUBLIC and requires NO LOGIN
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
try (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement()) {
|
||||||
|
// Create delivered auth codes table
|
||||||
|
stmt.execute("CREATE TABLE IF NOT EXISTS delivered_auth_codes (" +
|
||||||
|
"id INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||||
|
"code TEXT NOT NULL UNIQUE," +
|
||||||
|
"order_no TEXT," +
|
||||||
|
"login_limit INTEGER DEFAULT 1," +
|
||||||
|
"login_count INTEGER DEFAULT 0," +
|
||||||
|
"created_at TEXT," +
|
||||||
|
"expires_at TEXT," +
|
||||||
|
"delivered_at TEXT," +
|
||||||
|
"status INTEGER DEFAULT 2)"); // 2 for delivered
|
||||||
|
|
||||||
|
// Create delivered cart items table
|
||||||
|
stmt.execute("CREATE TABLE IF NOT EXISTS delivered_cart_items (" +
|
||||||
|
"id INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||||
|
"auth_code TEXT NOT NULL," +
|
||||||
|
"item_id TEXT NOT NULL)");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException("Failed to create delivered tables", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/generate-code")
|
@PostMapping("/generate-code")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public String generateCode(@RequestBody GenerateCodeRequest request) {
|
public String generateCode(@RequestBody GenerateCodeRequest request) {
|
||||||
if (StringUtils.hasText(request.getAuthCode()) && Objects.equals(request.getAuthCode(), secureAuth)) {
|
if (StringUtils.hasText(request.getAuthCode()) && Objects.equals(request.getAuthCode(), secureAuth)) {
|
||||||
|
|
||||||
|
|
||||||
String orderNo = request.getOrderNo();
|
String orderNo = request.getOrderNo();
|
||||||
Integer loginLimit = request.getLoginLimit();
|
Integer loginLimit = request.getLoginLimit();
|
||||||
Long expireSeconds = request.getExpireSeconds();
|
Long expireSeconds = request.getExpireSeconds();
|
||||||
|
|
||||||
// 1. 设置 12 小时后的过期时间戳
|
|
||||||
long expireTime = System.currentTimeMillis() + (Objects.isNull(expireSeconds) ? 12 * 60 * 60 * 1000 : expireSeconds);
|
long expireTime = System.currentTimeMillis() + (Objects.isNull(expireSeconds) ? 12 * 60 * 60 * 1000 : expireSeconds);
|
||||||
|
|
||||||
// 2. 创建 AES 实例(Hutool 会自动根据 16 字节 key 选择 AES-128)
|
|
||||||
AES aes = SecureUtil.aes(secureKey.getBytes(StandardCharsets.UTF_8));
|
AES aes = SecureUtil.aes(secureKey.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
// 3. 加密时间戳并返回
|
|
||||||
String loginCode = aes.encryptHex(String.valueOf(expireTime));
|
String loginCode = aes.encryptHex(String.valueOf(expireTime));
|
||||||
|
|
||||||
|
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
LocalDateTime expiresAt = now.plusSeconds(expireSeconds);
|
LocalDateTime expiresAt = now.plusSeconds(expireSeconds);
|
||||||
String sql = "INSERT INTO auth_codes (code, order_no, login_limit, created_at, expires_at, status) VALUES (?, ?, ?, ?, ?, ?)";
|
String sql = "INSERT INTO auth_codes (code, order_no, login_limit, created_at, expires_at, status) VALUES (?, ?, ?, ?, ?, ?)";
|
||||||
@ -85,31 +104,64 @@ public class AdminController {
|
|||||||
|
|
||||||
@GetMapping("/codes")
|
@GetMapping("/codes")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
@SaCheckRole("admin") // Add annotation to each protected method
|
@SaCheckRole("admin")
|
||||||
public List<Map<String, Object>> getAuthCodes() {
|
public List<Map<String, Object>> getAuthCodes() {
|
||||||
return jdbcTemplate.queryForList("SELECT code, order_no, login_limit, login_count, expires_at FROM auth_codes ORDER BY created_at DESC");
|
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("/cart/{code}")
|
@GetMapping("/cart/{code}")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
@SaCheckRole("admin") // Add annotation to each protected method
|
@SaCheckRole("admin")
|
||||||
public List<ImageGroup> getCartForCode(@PathVariable String code) {
|
public List<ImageGroup> getCartForCode(@PathVariable String code) {
|
||||||
List<String> itemIds = jdbcTemplate.queryForList("SELECT item_id FROM cart_items WHERE auth_code = ?", String.class, code);
|
List<String> itemIds = jdbcTemplate.queryForList("SELECT item_id FROM cart_items WHERE auth_code = ?", String.class, code);
|
||||||
return itemIds.stream().map(this::createImageGroupFromId).filter(Objects::nonNull).collect(Collectors.toList());
|
return itemIds.stream().map(this::createImageGroupFromId).filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/download-cart/{code}")
|
@PostMapping("/deliver/{code}")
|
||||||
@SaCheckRole("admin") // Add annotation to each protected method
|
@ResponseBody
|
||||||
public void downloadCartAsZip(@PathVariable String code, HttpServletResponse response) throws IOException {
|
@SaCheckRole("admin")
|
||||||
|
@Transactional
|
||||||
|
public void deliverCode(@PathVariable String code) {
|
||||||
|
// 1. Get the auth_code details
|
||||||
|
Map<String, Object> authCode = jdbcTemplate.queryForMap("SELECT * FROM auth_codes WHERE code = ?", code);
|
||||||
|
|
||||||
|
// 2. Insert into delivered_auth_codes
|
||||||
|
jdbcTemplate.update("INSERT INTO delivered_auth_codes (code, order_no, login_limit, login_count, created_at, expires_at, delivered_at, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
authCode.get("code"), authCode.get("order_no"), authCode.get("login_limit"), authCode.get("login_count"),
|
||||||
|
authCode.get("created_at"), authCode.get("expires_at"), formatter.format(LocalDateTime.now()), 2);
|
||||||
|
|
||||||
|
// 3. Get cart items
|
||||||
List<String> itemIds = jdbcTemplate.queryForList("SELECT item_id FROM cart_items WHERE auth_code = ?", String.class, code);
|
List<String> itemIds = jdbcTemplate.queryForList("SELECT item_id FROM cart_items WHERE auth_code = ?", String.class, code);
|
||||||
|
|
||||||
|
// 4. Insert into delivered_cart_items
|
||||||
|
for (String itemId : itemIds) {
|
||||||
|
jdbcTemplate.update("INSERT INTO delivered_cart_items (auth_code, item_id) VALUES (?, ?)", code, itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Delete from original tables
|
||||||
|
jdbcTemplate.update("DELETE FROM cart_items WHERE auth_code = ?", code);
|
||||||
|
jdbcTemplate.update("DELETE FROM auth_codes WHERE code = ?", code);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/cancel/{code}")
|
||||||
|
@ResponseBody
|
||||||
|
@SaCheckRole("admin")
|
||||||
|
@Transactional
|
||||||
|
public void cancelCode(@PathVariable String code) {
|
||||||
|
jdbcTemplate.update("DELETE FROM cart_items WHERE auth_code = ?", code);
|
||||||
|
jdbcTemplate.update("DELETE FROM auth_codes WHERE code = ?", code);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/download-cart/{code}")
|
||||||
|
@SaCheckRole("admin")
|
||||||
|
public void downloadCartAsZip(@PathVariable String code, HttpServletResponse response) throws IOException {
|
||||||
|
List<String> itemIds = jdbcTemplate.queryForList("SELECT item_id FROM cart_items WHERE auth_code = ?", String.class, code);
|
||||||
Map<String, Object> codeDetails = jdbcTemplate.queryForMap("SELECT order_no FROM auth_codes WHERE code = ?", code);
|
Map<String, Object> codeDetails = jdbcTemplate.queryForMap("SELECT order_no FROM auth_codes WHERE code = ?", code);
|
||||||
String orderId = (String) codeDetails.get("order_no");
|
String orderId = (String) codeDetails.get("order_no");
|
||||||
String zipFileName = (orderId != null ? orderId.replaceAll("[^a-zA-Z0-9]", "_") : code) + "_templates.zip";
|
String zipFileName = (orderId != null ? orderId.replaceAll("[^a-zA-Z0-9]", "_") : code) + "_templates.zip";
|
||||||
|
|
||||||
response.setContentType("application/zip");
|
response.setContentType("application/zip");
|
||||||
response.setHeader("Content-Disposition", "attachment; filename=\"" + zipFileName + "\"");
|
response.setHeader("Content-Disposition", "attachment; filename=\"" + zipFileName + "\"");
|
||||||
|
|
||||||
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
|
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
|
||||||
for (String itemId : itemIds) {
|
for (String itemId : itemIds) {
|
||||||
File templateFile = findTemplateFile(itemId);
|
File templateFile = findTemplateFile(itemId);
|
||||||
@ -129,20 +181,17 @@ public class AdminController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... (Helper methods remain the same)
|
|
||||||
private ImageGroup createImageGroupFromId(String itemId) {
|
private ImageGroup createImageGroupFromId(String itemId) {
|
||||||
String displayName = new File(itemId).getName();
|
String displayName = new File(itemId).getName();
|
||||||
ImageGroup group = new ImageGroup(itemId, displayName);
|
ImageGroup group = new ImageGroup(itemId, displayName);
|
||||||
File effectFile = findFile(itemId, "effect");
|
File effectFile = findFile(itemId, "effect");
|
||||||
File templateFile = findFile(itemId, "template");
|
File templateFile = findFile(itemId, "template");
|
||||||
if (effectFile == null) return null;
|
if (effectFile == null) return null;
|
||||||
|
|
||||||
String rootAbsolutePath = new File(rootPath).getAbsolutePath();
|
String rootAbsolutePath = new File(rootPath).getAbsolutePath();
|
||||||
java.util.function.Function<File, String> getUrl = (file) -> {
|
java.util.function.Function<File, String> getUrl = (file) -> {
|
||||||
if (file == null) return null;
|
if (file == null) return null;
|
||||||
return file.getAbsolutePath().substring(rootAbsolutePath.length()).replace("\\", "/");
|
return file.getAbsolutePath().substring(rootAbsolutePath.length()).replace("\\", "/");
|
||||||
};
|
};
|
||||||
|
|
||||||
group.setEffectImageUrl(getUrl.apply(effectFile));
|
group.setEffectImageUrl(getUrl.apply(effectFile));
|
||||||
group.setEffectThumbnailUrl(getUrl.apply(effectFile));
|
group.setEffectThumbnailUrl(getUrl.apply(effectFile));
|
||||||
group.setTemplateImageUrl(getUrl.apply(templateFile));
|
group.setTemplateImageUrl(getUrl.apply(templateFile));
|
||||||
|
|||||||
@ -53,12 +53,58 @@
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-list-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-list-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-list-header #code-count {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-header #cart-item-count {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-list-item { padding: 12px; border-bottom: 1px solid #eee; cursor: pointer; border-radius: 6px; }
|
.code-list-item { padding: 12px; border-bottom: 1px solid #eee; cursor: pointer; border-radius: 6px; }
|
||||||
.code-list-item:hover, .code-list-item.active { background-color: #d1e7fd; }
|
.code-list-item:hover, .code-list-item.active { background-color: #d1e7fd; }
|
||||||
.code-list-item strong { font-size: 14px; color: #333; }
|
.code-list-item strong { font-size: 14px; color: #333; }
|
||||||
.code-list-item span { font-size: 12px; color: #777; display: block; margin-top: 4px; }
|
.code-list-item span { font-size: 12px; color: #777; display: block; margin-top: 4px; }
|
||||||
|
.code-list-item .actions { margin-top: 8px; display: flex; gap: 8px; }
|
||||||
|
.code-list-item .actions button {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 10px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.code-list-item .actions .deliver-btn { background-color: #28a745; color: white; }
|
||||||
|
.code-list-item .actions .cancel-btn { background-color: #dc3545; color: white; }
|
||||||
|
|
||||||
|
|
||||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; margin-top: 20px; }
|
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; margin-top: 20px; }
|
||||||
.grid-item { background: #fff; border-radius: 10px; overflow: hidden; box-shadow: var(--card-shadow); }
|
.grid-item { background: #fff; border-radius: 10px; overflow: hidden; box-shadow: var(--card-shadow); }
|
||||||
.grid-item img { width: 100%; height: auto; display: block; }
|
.grid-item img { width: 100%; height: auto; display: block; }
|
||||||
@ -73,13 +119,19 @@
|
|||||||
<div id="particles-js"></div>
|
<div id="particles-js"></div>
|
||||||
|
|
||||||
<div class="sidebar" id="sidebar">
|
<div class="sidebar" id="sidebar">
|
||||||
<h3>授权码列表</h3>
|
<div class="code-list-header">
|
||||||
|
<h3>授权码列表</h3>
|
||||||
|
<div id="code-count"></div>
|
||||||
|
</div>
|
||||||
<input type="text" id="search-input" placeholder="搜索授权码...">
|
<input type="text" id="search-input" placeholder="搜索授权码...">
|
||||||
<div id="code-list"></div>
|
<div id="code-list"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
<h2>购物车详情</h2>
|
<div class="cart-header">
|
||||||
|
<h2>购物车详情</h2>
|
||||||
|
<div id="cart-item-count"></div>
|
||||||
|
</div>
|
||||||
<button id="download-btn">打包下载模板原图</button>
|
<button id="download-btn">打包下载模板原图</button>
|
||||||
<div class="grid" id="cart-grid"></div>
|
<div class="grid" id="cart-grid"></div>
|
||||||
<div id="loader">请从左侧选择一个授权码查看</div>
|
<div id="loader">请从左侧选择一个授权码查看</div>
|
||||||
@ -91,6 +143,8 @@
|
|||||||
const loaderEl = document.getElementById('loader');
|
const loaderEl = document.getElementById('loader');
|
||||||
const downloadBtn = document.getElementById('download-btn');
|
const downloadBtn = document.getElementById('download-btn');
|
||||||
const searchInput = document.getElementById('search-input');
|
const searchInput = document.getElementById('search-input');
|
||||||
|
const codeCountEl = document.getElementById('code-count');
|
||||||
|
const cartItemCountEl = document.getElementById('cart-item-count');
|
||||||
let activeCode = null;
|
let activeCode = null;
|
||||||
let allCodes = [];
|
let allCodes = [];
|
||||||
|
|
||||||
@ -108,17 +162,26 @@
|
|||||||
function renderCodeList() {
|
function renderCodeList() {
|
||||||
const searchTerm = searchInput.value.toLowerCase();
|
const searchTerm = searchInput.value.toLowerCase();
|
||||||
const filteredCodes = allCodes.filter(code =>
|
const filteredCodes = allCodes.filter(code =>
|
||||||
(code.order_id && code.order_id.toLowerCase().includes(searchTerm)) ||
|
(code.order_no && code.order_no.toLowerCase().includes(searchTerm)) ||
|
||||||
code.code.toLowerCase().includes(searchTerm)
|
code.code.toLowerCase().includes(searchTerm)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
codeCountEl.innerText = `待处理: ${allCodes.length}`;
|
||||||
codeListEl.innerHTML = '';
|
codeListEl.innerHTML = '';
|
||||||
filteredCodes.forEach(code => {
|
filteredCodes.forEach(code => {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'code-list-item';
|
item.className = 'code-list-item';
|
||||||
item.dataset.code = code.code;
|
item.dataset.code = code.code;
|
||||||
item.innerHTML = `<strong>${code.order_id || code.code}</strong><span>登录/上限:${code.login_count}/${code.login_limit} | 失效: ${new Date(code.expires_at).toLocaleString()}</span>`;
|
item.innerHTML = `
|
||||||
item.onclick = () => loadCartForCode(code.code);
|
<div onclick="loadCartForCode('${code.code}')">
|
||||||
|
<strong>${code.order_no || code.code}</strong>
|
||||||
|
<span>登录/上限:${code.login_count}/${code.login_limit} | 失效: ${new Date(code.expires_at).toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="deliver-btn" onclick="deliverCodeAction('${code.code}', event)">已交付</button>
|
||||||
|
<button class="cancel-btn" onclick="cancelCodeAction('${code.code}', event)">已取消</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
codeListEl.appendChild(item);
|
codeListEl.appendChild(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -130,11 +193,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deliverCodeAction(code, event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
if (!confirm(`确定要将授权码 ${code} 标记为已交付吗?`)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/admin/deliver/${code}`, { method: 'POST' });
|
||||||
|
if (!response.ok) throw new Error('操作失败');
|
||||||
|
fetchAuthCodes(); // Refresh the list
|
||||||
|
} catch (e) {
|
||||||
|
alert(e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cancelCodeAction(code, event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
if (!confirm(`确定要取消授权码 ${code} 吗?此操作不可逆!`)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/admin/cancel/${code}`, { method: 'POST' });
|
||||||
|
if (!response.ok) throw new Error('操作失败');
|
||||||
|
fetchAuthCodes(); // Refresh the list
|
||||||
|
} catch (e) {
|
||||||
|
alert(e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadCartForCode(code) {
|
async function loadCartForCode(code) {
|
||||||
activeCode = code;
|
activeCode = code;
|
||||||
renderCodeList(); // Re-render to highlight the active item
|
renderCodeList(); // Re-render to highlight the active item
|
||||||
|
|
||||||
cartGridEl.innerHTML = '';
|
cartGridEl.innerHTML = '';
|
||||||
|
cartItemCountEl.innerText = '';
|
||||||
loaderEl.innerText = '正在加载...';
|
loaderEl.innerText = '正在加载...';
|
||||||
loaderEl.style.display = 'block';
|
loaderEl.style.display = 'block';
|
||||||
downloadBtn.style.display = 'none';
|
downloadBtn.style.display = 'none';
|
||||||
@ -144,6 +234,8 @@
|
|||||||
if (!response.ok) throw new Error('Failed to load cart');
|
if (!response.ok) throw new Error('Failed to load cart');
|
||||||
const items = await response.json();
|
const items = await response.json();
|
||||||
|
|
||||||
|
cartItemCountEl.innerText = `总数: ${items.length}`;
|
||||||
|
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
loaderEl.innerText = '这个购物车是空的';
|
loaderEl.innerText = '这个购物车是空的';
|
||||||
return;
|
return;
|
||||||
@ -177,9 +269,8 @@
|
|||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.style.display = 'none';
|
a.style.display = 'none';
|
||||||
a.href = url;
|
a.href = url;
|
||||||
// Use order_id for filename if available
|
|
||||||
const activeItem = document.querySelector(`[data-code="${activeCode}"] strong`);
|
const activeItem = document.querySelector(`[data-code="${activeCode}"] strong`);
|
||||||
const filename = `${activeItem.innerText.replace(/\\s+/g, '_')}_templates.zip`;
|
const filename = `${activeItem.innerText.replace(/\s+/g, '_')}_templates.zip`;
|
||||||
a.download = filename;
|
a.download = filename;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user