<?php
require_once __DIR__ . '/../auth/bootstrap.php';
requireLogin('index.php?msg=relogin');
$username = $_SESSION['admin_username'] ?? 'admin';
$isAgent = (isset($_SESSION['admin_role']) && $_SESSION['admin_role'] === 'agent');
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>卡密与用户管理 - 验证系统</title>
    <style>
        * { box-sizing: border-box; margin: 0; padding: 0; }
        body { font-family: "Microsoft YaHei", sans-serif; background: linear-gradient(135deg, #1a1f3a 0%, #0a0e27 100%); min-height: 100vh; color: #e0e0e0; padding: 20px; }
        .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 12px; }
        .header h1 { font-size: 22px; color: #4a9eff; }
        .header .user { font-size: 14px; color: #888; }
        .header .user a { color: #4a9eff; text-decoration: none; margin-left: 12px; }
        .section { margin-bottom: 40px; }
        .section h2 { font-size: 18px; color: #4a9eff; margin-bottom: 16px; }
        .toolbar { display: flex; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; }
        .toolbar input { padding: 8px 12px; border: 1px solid rgba(74,158,255,0.3); border-radius: 6px; background: rgba(26,31,36,0.8); color: #fff; font-size: 14px; }
        .btn { padding: 8px 16px; border: none; border-radius: 6px; font-size: 14px; cursor: pointer; }
        .btn-primary { background: linear-gradient(135deg, #4a9eff 0%, #258DF2 100%); color: #fff; }
        .btn-danger { background: #e03131; color: #fff; }
        .btn-small { padding: 4px 10px; font-size: 12px; }
        .btn-link { background: rgba(74,158,255,0.2); color: #74c0fc; border: 1px solid rgba(74,158,255,0.4); text-decoration: none; display: inline-block; }
        .btn-link:hover { background: rgba(74,158,255,0.3); color: #fff; }
        table { width: 100%; border-collapse: collapse; background: rgba(47,54,60,0.6); border-radius: 12px; overflow: hidden; }
        th, td { padding: 12px 14px; text-align: left; border-bottom: 1px solid rgba(255,255,255,0.06); }
        th { background: rgba(74,158,255,0.15); color: #4a9eff; font-weight: 600; }
        tr:hover td { background: rgba(255,255,255,0.03); }
        .status-0 { color: #ffd43b; }
        .status-1 { color: #51cf66; }
        .expired { color: #ff6b6b; }
        .paused { color: #ff922b; }
        .btn-warning { background: #f59f00; color: #fff; }
        .btn-success { background: #51cf66; color: #1a1a1a; }
        .btn-type { padding: 6px 12px; font-size: 13px; background: rgba(74,158,255,0.2); color: #74c0fc; border: 1px solid rgba(74,158,255,0.4); border-radius: 6px; cursor: pointer; margin-right: 4px; }
        .btn-type:hover { background: rgba(74,158,255,0.3); color: #fff; }
        .btn-type.active { background: linear-gradient(135deg, #4a9eff, #258DF2); color: #fff; border-color: transparent; }
        .batch-toolbar { display: flex; }
        .card-checkbox { cursor: pointer; }
        .ip { font-family: monospace; font-size: 13px; color: #74c0fc; }
        .msg { margin-bottom: 12px; font-size: 14px; min-height: 22px; }
        .msg.err { color: #ff6b6b; }
        .msg.ok { color: #51cf66; }
        .empty { text-align: center; padding: 40px; color: #888; }
        .invalid { color: #ff6b6b; }
        .user-status.status-ok { color: #51cf66; }
        .user-status.status-pause { color: #f59f00; }
        .user-status.status-invalid { color: #ff6b6b; }
        .status-online { color: #51cf66; }
        .status-offline { color: #888; }
        .dashboard { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 28px; }
        @media (max-width: 1200px) { .dashboard { grid-template-columns: repeat(2, 1fr); } }
        @media (max-width: 600px) { .dashboard { grid-template-columns: 1fr; } }
        .dash-card { background: rgba(47,54,60,0.7); border: 1px solid rgba(255,255,255,0.08); border-radius: 14px; padding: 22px 24px; position: relative; overflow: hidden; }
        .dash-card::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; }
        .dash-card.card-users::before { background: linear-gradient(180deg, #4a9eff, #258DF2); }
        .dash-card.card-cards::before { background: linear-gradient(180deg, #51cf66, #37b24d); }
        .dash-card.card-unused::before { background: linear-gradient(180deg, #ffd43b, #f59f00); }
        .dash-card.card-used::before { background: linear-gradient(180deg, #74c0fc, #4a9eff); }
        .dash-card.card-expired::before { background: linear-gradient(180deg, #ff6b6b, #e03131); }
        .dash-card.card-today::before { background: linear-gradient(180deg, #69db7c, #51cf66); }
        .dash-card.card-week::before { background: linear-gradient(180deg, #a78bfa, #8b5cf6); }
        .dash-card .dash-label { font-size: 13px; color: #94a3b8; margin-bottom: 8px; letter-spacing: 0.02em; }
        .dash-card .dash-num { font-size: 18px; font-weight: 700; color: #f1f5f9; letter-spacing: -0.02em; line-height: 1.2; }
        .dash-card .dash-sub { font-size: 12px; color: #64748b; margin-top: 10px; }
        .dash-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-bottom: 20px; }
        @media (max-width: 768px) { .dash-row { grid-template-columns: 1fr; } }
        .dash-group { background: rgba(47,54,60,0.5); border: 1px solid rgba(74,158,255,0.2); border-radius: 14px; padding: 20px 24px; }
        .dash-group h3 { font-size: 14px; color: #94a3b8; font-weight: 600; margin-bottom: 16px; text-transform: uppercase; letter-spacing: 0.05em; }
        .dash-group .dash-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
        .dash-group .dash-item { display: flex; flex-direction: column; gap: 4px; }
        .dash-group .dash-item .val { font-size: 24px; font-weight: 700; color: #e2e8f0; }
        .dash-group .dash-item .txt { font-size: 12px; color: #64748b; }
        /* 单一多区域圆盘样式 */
        .summary-pie-layout {
            display: flex;
            gap: 24px;
            align-items: center;
            flex-wrap: wrap;
        }
        .summary-pie-circle-wrap {
            flex: 0 0 auto;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .summary-pie-circle {
            --pie-unused: #facc15;
            --pie-used: #22c55e;
            --pie-expired: #f97373;
            width: 160px;
            height: 160px;
            border-radius: 50%;
            position: relative;
            background:
                radial-gradient(circle at center, #020617 55%, transparent 57%),
                conic-gradient(#1e293b 0 100%);
            box-shadow: 0 0 0 1px rgba(15,23,42,0.6), 0 18px 35px rgba(15,23,42,0.9);
        }
        .summary-pie-center {
            position: absolute;
            inset: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            pointer-events: none;
        }
        .summary-pie-total {
            font-size: 22px;
            font-weight: 700;
            color: #e5e7eb;
        }
        .summary-pie-sub {
            margin-top: 4px;
            font-size: 12px;
            color: #9ca3af;
        }
        .summary-pie-legend {
            flex: 1 1 220px;
            display: flex;
            flex-direction: column;
            gap: 6px;
            font-size: 13px;
            color: #cbd5f5;
        }
        .legend-row {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .legend-dot {
            width: 10px;
            height: 10px;
            border-radius: 999px;
            display: inline-block;
        }
        .legend-unused { background: #facc15; }
        .legend-used { background: #22c55e; }
        .legend-expired { background: #f97373; }
        .legend-label {
            flex: 0 0 52px;
            color: #e5e7eb;
        }
        .legend-value {
            min-width: 40px;
            color: #e5e7eb;
        }
        .legend-percent {
            min-width: 40px;
            color: #9ca3af;
        }
        .legend-divider {
            margin: 4px 0;
            height: 1px;
            background: rgba(148,163,206,0.28);
        }
        .legend-extra {
            display: grid;
            grid-template-columns: repeat(2, minmax(0, 1fr));
            gap: 4px 16px;
            font-size: 12px;
            color: #9ca3af;
        }
        @media (max-width: 768px) {
            .summary-pie-layout { flex-direction: column; align-items: flex-start; }
            .summary-pie-circle-wrap { justify-content: flex-start; }
            .legend-extra { grid-template-columns: 1fr; }
        }
        .stats { display: flex; flex-wrap: wrap; gap: 16px; margin-bottom: 24px; }
        .stat-box { background: rgba(47,54,60,0.6); border: 1px solid rgba(74,158,255,0.3); border-radius: 10px; padding: 16px 20px; min-width: 120px; }
        .stat-box .num { font-size: 24px; font-weight: 600; color: #4a9eff; }
        .stat-box .label { font-size: 13px; color: #888; margin-top: 4px; }
        .filter-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 12px; }
        .filter-row select { padding: 6px 10px; border: 1px solid rgba(74,158,255,0.3); border-radius: 6px; background: rgba(26,31,36,0.8); color: #fff; font-size: 13px; }
        .pagination { display: flex; align-items: center; gap: 12px; margin-top: 12px; font-size: 14px; color: #888; }
        .pagination button { padding: 6px 12px; cursor: pointer; background: rgba(74,158,255,0.2); border: 1px solid rgba(74,158,255,0.4); color: #74c0fc; border-radius: 6px; }
        .pagination button:hover { background: rgba(74,158,255,0.3); }
        .pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
        @media (max-width: 768px) { table, .toolbar { font-size: 12px; } th, td { padding: 8px; } }
    </style>
</head>
<body>
    <div class="header">
        <h1><?php echo $isAgent ? '代理后台 - 卡密与用户管理' : '卡密与用户管理'; ?></h1>
        <div class="user">
            <span><?php echo htmlspecialchars($username); ?><?php echo $isAgent ? ' <span style="color:#94a3b8;">(代理)</span>' : ''; ?></span>
            <a href="index.php?action=change_password">修改密码</a>
            <a href="index.php?action=logout">退出</a>
        </div>
    </div>
    <div id="msg" class="msg"></div>

    <!-- 数据统计 - 圆盘总览（代理仅显示自己卡密统计） -->
    <div class="section">
        <h2 style="margin-bottom:20px;"><?php echo $isAgent ? '我的数据概览' : '数据概览'; ?></h2>
        <div id="statsBox" class="dashboard">
            <div class="dash-card card-cards" style="grid-column: 1 / -1;">
                <div class="dash-label">卡密分布概览</div>
                <div class="summary-pie-layout">
                    <div class="summary-pie-circle-wrap">
                        <div class="summary-pie-circle" id="cardSummaryPie">
                            <div class="summary-pie-center">
                                <div class="summary-pie-total" id="statTotalCards">0</div>
                                <div class="summary-pie-sub">总卡密</div>
                            </div>
                        </div>
                    </div>
                    <div class="summary-pie-legend">
                        <div class="legend-row">
                            <span class="legend-dot legend-unused"></span>
                            <span class="legend-label">未使用</span>
                            <span class="legend-value" id="statCardsUnused">0</span>
                            <span class="legend-percent" id="statCardsUnusedPct">0%</span>
                        </div>
                        <div class="legend-row">
                            <span class="legend-dot legend-used"></span>
                            <span class="legend-label">已使用</span>
                            <span class="legend-value" id="statCardsUsed">0</span>
                            <span class="legend-percent" id="statCardsUsedPct">0%</span>
                        </div>
                        <div class="legend-row">
                            <span class="legend-dot legend-expired"></span>
                            <span class="legend-label">已过期</span>
                            <span class="legend-value" id="statCardsExpired">0</span>
                            <span class="legend-percent" id="statCardsExpiredPct">0%</span>
                        </div>
                        <div class="legend-divider"></div>
                        <div class="legend-extra">
                            <div>总用户：<span id="statTotalUsers">0</span></div>
                            <div>今日新增：<span id="statTodayUsers">0</span></div>
                            <div>本周新增：<span id="statWeekUsers">0</span></div>
                            <div>今日激活：<span id="statTodayActivations">0</span></div>
                            <div>本周激活：<span id="statWeekActivations">0</span></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 用户管理 -->
    <div class="section">
        <h2>用户管理</h2>
        <div class="filter-row">
            <input type="text" id="userKeyword" placeholder="用户名" style="width:140px;">
            <button type="button" class="btn btn-primary" id="btnUserSearch">查询</button>
            <a href="#" id="btnUserExport" class="btn btn-link" target="_blank">导出用户 CSV</a>
        </div>
        <table id="userTable">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>用户名</th>
                    <th>卡密</th>
                    <th>用户状态</th>
                    <th>卡密状态</th>
                    <th>到期时间</th>
                    <th>登录IP</th>
                    <th>注册时间</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody id="userListBody">
                <tr><td colspan="9" class="empty">加载中...</td></tr>
            </tbody>
        </table>
        <div id="userPagination" class="pagination" style="display:none;"></div>
    </div>

    <!-- 卡密管理（代理仅显示自己生成的） -->
    <div class="section">
        <h2><?php echo $isAgent ? '我生成的卡密' : '卡密管理'; ?></h2>
        <div class="toolbar">
            <label>生成数量：</label>
            <input type="number" id="genCount" value="5" min="1" max="100" style="width:70px">
            <label>长度：</label>
            <input type="number" id="genLen" value="16" min="8" max="32" style="width:60px">
            <label>卡类型：</label>
            <button type="button" class="btn btn-type active" data-type="day" id="btnTypeDay">天卡(24h)</button>
            <button type="button" class="btn btn-type" data-type="week" id="btnTypeWeek">周卡(7天)</button>
            <button type="button" class="btn btn-type" data-type="month" id="btnTypeMonth">月卡(30天)</button>
            <button type="button" class="btn btn-primary" id="btnGenerate">生成卡密</button>
        </div>
        <div class="filter-row">
            <input type="text" id="cardKeyword" placeholder="卡密/备注" style="width:140px;">
            <select id="cardStatus"><option value="all">状态：全部</option><option value="0">未使用</option><option value="1">已使用</option></select>
            <select id="cardPaused"><option value="all">暂停：全部</option><option value="0">启用</option><option value="1">已暂停</option></select>
            <select id="cardType"><option value="all">类型：全部</option><option value="day">天卡</option><option value="week">周卡</option><option value="month">月卡</option></select>
            <button type="button" class="btn btn-primary" id="btnCardSearch">查询</button>
            <a href="#" id="btnCardExport" class="btn btn-link" target="_blank">导出卡密 CSV</a>
        </div>
        <div class="batch-toolbar" id="cardBatchToolbar" style="display:none; margin-bottom:10px; align-items:center; gap:10px; flex-wrap:wrap;">
            <span id="cardSelectedCount" style="color:#888;">已选 0 条</span>
            <button type="button" class="btn btn-small btn-warning" id="btnBatchPause">批量暂停</button>
            <button type="button" class="btn btn-small btn-success" id="btnBatchEnable">批量启用</button>
            <button type="button" class="btn btn-small btn-danger" id="btnBatchDelete">批量删除</button>
        </div>
        <table>
            <thead>
                <tr>
                    <th style="width:40px;"><input type="checkbox" id="cardSelectAll" title="全选"></th>
                    <th>ID</th>
                    <th>卡密</th>
                    <?php if (!$isAgent): ?><th>生成者</th><?php endif; ?>
                    <th>类型</th>
                    <th>状态</th>
                    <th>暂停</th>
                    <th>到期时间</th>
                    <th>备注</th>
                    <th>注册IP</th>
                    <th>登录IP</th>
                    <th>使用时间</th>
                    <th>创建时间</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody id="cardListBody">
                <tr><td colspan="<?php echo $isAgent ? 13 : 14; ?>" class="empty">加载中...</td></tr>
            </tbody>
        </table>
        <div id="cardPagination" class="pagination" style="display:none;"></div>
    </div>

    <?php if (!$isAgent): ?>
    <!-- 代理生成的卡密（仅总管理可见，显示每条卡密由哪个代理生成） -->
    <div class="section" id="sectionAgentCards">
        <h2>代理生成的卡密</h2>
        <div class="filter-row">
            <button type="button" class="btn btn-primary" id="btnAgentCardsRefresh">刷新</button>
        </div>
        <table>
            <thead>
                <tr>
                    <th>ID</th>
                    <th>卡密</th>
                    <th>类型</th>
                    <th>状态</th>
                    <th>生成代理</th>
                    <th>到期时间</th>
                    <th>备注</th>
                    <th>使用时间</th>
                    <th>创建时间</th>
                </tr>
            </thead>
            <tbody id="agentCardListBody">
                <tr><td colspan="9" class="empty">加载中...</td></tr>
            </tbody>
        </table>
        <div id="agentCardPagination" class="pagination" style="display:none;"></div>
    </div>

    <!-- 代理管理 -->
    <div class="section" id="sectionAgents">
        <h2>代理管理</h2>
        <div class="toolbar" style="margin-bottom:12px;">
            <input type="text" id="agentUsername" placeholder="代理用户名" style="width:140px;">
            <input type="password" id="agentPassword" placeholder="密码（至少4位）" style="width:140px;">
            <button type="button" class="btn btn-primary" id="btnAgentAdd">添加代理</button>
        </div>
        <table>
            <thead>
                <tr><th>ID</th><th>用户名</th><th>创建时间</th><th>操作</th></tr>
            </thead>
            <tbody id="agentListBody">
                <tr><td colspan="4" class="empty">加载中...</td></tr>
            </tbody>
        </table>
    </div>
    <?php endif; ?>

    <?php if (!$isAgent): ?>
    <!-- 黑名单 -->
    <div class="section">
        <h2>黑名单</h2>
        <div class="filter-row">
            <select id="blacklistType"><option value="ip">IP</option><option value="user">用户</option></select>
            <input type="text" id="blacklistValue" placeholder="IP 或 用户名" style="width:180px;">
            <button type="button" class="btn btn-primary" id="btnBlacklistAdd">添加</button>
        </div>
        <table>
            <thead>
                <tr><th>类型</th><th>值</th><th>添加时间</th><th>操作</th></tr>
            </thead>
            <tbody id="blacklistBody">
                <tr><td colspan="4" class="empty">加载中...</td></tr>
            </tbody>
        </table>
    </div>

    <!-- 操作日志 -->
    <div class="section">
        <h2>操作日志</h2>
        <table>
            <thead>
                <tr><th>ID</th><th>管理员</th><th>操作</th><th>详情</th><th>时间</th></tr>
            </thead>
            <tbody id="logListBody">
                <tr><td colspan="5" class="empty">加载中...</td></tr>
            </tbody>
        </table>
        <div id="logPagination" class="pagination" style="display:none;"></div>
    </div>

    <?php endif; ?>

    <script>
        var API_BASE = (function() {
            var p = window.location.pathname || '';
            var i = p.indexOf('/admin');
            return i >= 0 ? p.substring(0, i) : '';
        })();
        function showMsg(text, isErr) {
            var el = document.getElementById('msg');
            el.textContent = text;
            el.className = 'msg ' + (isErr ? 'err' : 'ok');
        }
        function escapeHtml(s) {
            if (!s) return '';
            var d = document.createElement('div');
            d.textContent = s;
            return d.innerHTML;
        }
        // 检测为未登录/账号失效时跳转登录页并提示请重新登录
        function redirectToLoginIfAuthFailed(e) {
            var msg = (e && e.message) ? String(e.message) : '';
            if (/401|请先登录|请重新登录|账号已失效|未登录|已过期/.test(msg)) {
                var base = (typeof API_BASE !== 'undefined' && API_BASE) ? API_BASE : '';
                window.location.href = base + '/admin/index.php?msg=relogin';
                return true;
            }
            return false;
        }
        // 带超时的 fetch（不依赖 AbortController），超时后拒绝，避免一直显示「加载中」
        function fetchWithTimeout(url, options, timeoutMs) {
            timeoutMs = timeoutMs || 15000;
            var timeoutPromise = new Promise(function(_, reject) {
                setTimeout(function() {
                    reject(new Error('请求超时（' + (timeoutMs / 1000) + ' 秒），请检查网络或 API 地址'));
                }, timeoutMs);
            });
            var fetchPromise = fetch(url, options || {}).then(function(r) { return r; });
            return Promise.race([fetchPromise, timeoutPromise]);
        }

        // 数据统计
        function loadStats() {
            fetch(API_BASE + '/api/index.php?module=stats', { credentials: 'include' })
                .then(function(r) {
                    if (r.status === 401) return r.json().then(function(res) { res._authFailed = true; return res; });
                    return r.json();
                })
                .then(function(res) {
                    if (res && res._authFailed) { redirectToLoginIfAuthFailed(new Error(res.msg || '请重新登录')); return; }
                    if (res.code !== 0 || !res.data) return;
                    var d = res.data;
                    function setStat(id, val) { var el = document.getElementById(id); if (el) el.textContent = val; }
                    setStat('statTotalUsers', d.total_users || 0);
                    setStat('statTotalCards', d.total_cards || 0);
                    setStat('statCardsUnused', d.cards_unused || 0);
                    setStat('statCardsUsed', d.cards_used || 0);
                    setStat('statCardsExpired', d.cards_expired || 0);
                    setStat('statTodayUsers', d.today_users || 0);
                    setStat('statWeekUsers', d.week_users || 0);
                    setStat('statTodayActivations', d.today_activations || 0);
                    setStat('statWeekActivations', d.week_activations || 0);
                    // 根据统计数据更新单一圆盘的多区域百分比
                    try {
                        var totalCards = d.total_cards || 0;
                        var unused = d.cards_unused || 0;
                        var used = d.cards_used || 0;
                        var expired = d.cards_expired || 0;
                        var pie = document.getElementById('cardSummaryPie');

                        var pUnused = 0, pUsed = 0, pExpired = 0;
                        if (totalCards > 0) {
                            pUnused = Math.max(0, unused) / totalCards;
                            pUsed = Math.max(0, used) / totalCards;
                            pExpired = Math.max(0, expired) / totalCards;
                            var sum = pUnused + pUsed + pExpired;
                            if (sum > 0) {
                                pUnused /= sum;
                                pUsed /= sum;
                                pExpired /= sum;
                            }
                        }
                        var stop1 = (pUnused * 100);
                        var stop2 = (pUnused + pUsed) * 100;

                        // 更新圆盘背景为多色区域
                        if (pie) {
                            var bg = 'radial-gradient(circle at center, #020617 55%, transparent 57%),' +
                                'conic-gradient(' +
                                'var(--pie-unused) 0 ' + stop1.toFixed(2) + '%, ' +
                                'var(--pie-used) ' + stop1.toFixed(2) + '% ' + stop2.toFixed(2) + '%, ' +
                                'var(--pie-expired) ' + stop2.toFixed(2) + '% 100%)';
                            pie.style.background = bg;
                        }

                        function setPercentText(id, value) {
                            var el = document.getElementById(id);
                            if (!el) return;
                            el.textContent = value.toFixed(0) + '%';
                        }

                        // 百分比文本
                        if (totalCards > 0) {
                            setPercentText('statCardsUnusedPct', pUnused * 100);
                            setPercentText('statCardsUsedPct', pUsed * 100);
                            setPercentText('statCardsExpiredPct', pExpired * 100);
                        } else {
                            setPercentText('statCardsUnusedPct', 0);
                            setPercentText('statCardsUsedPct', 0);
                            setPercentText('statCardsExpiredPct', 0);
                        }
                    } catch (e) {
                        console.warn('更新统计圆盘失败:', e);
                    }
                }).catch(function() {});
        }

        var cardPage = 1;
        var cardPageSize = 10;
        var cardTotal = 0;
        var userPage = 1;
        var userPageSize = 20;
        var userTotal = 0;

        // 卡密列表
        function loadCardList(page) {
            var cardKw = document.getElementById('cardKeyword');
            var cardBody = document.getElementById('cardListBody');
            if (!cardBody) return;
            if (page !== undefined) cardPage = page;
            var keyword = (cardKw && cardKw.value) ? String(cardKw.value).trim() : '';
            var statusEl = document.getElementById('cardStatus');
            var pausedEl = document.getElementById('cardPaused');
            var typeEl = document.getElementById('cardType');
            var status = statusEl ? statusEl.value : 'all';
            var paused = pausedEl ? pausedEl.value : 'all';
            var cardType = typeEl ? typeEl.value : 'all';
            var url = API_BASE + '/api/index.php?module=card&action=list&page=' + cardPage + '&page_size=' + cardPageSize;
            if (keyword) url += '&keyword=' + encodeURIComponent(keyword);
            if (status && status !== 'all') url += '&status=' + encodeURIComponent(status);
            if (paused && paused !== 'all') url += '&paused=' + encodeURIComponent(paused);
            if (cardType && cardType !== 'all') url += '&card_type=' + encodeURIComponent(cardType);
            fetchWithTimeout(url, { credentials: 'include' }, 15000)
                .then(function(r) {
                    if (!r.ok) {
                        var statusMsg = r.status + ' ' + (r.statusText || '');
                        if (r.status === 401) throw new Error('未登录或已过期，请重新登录');
                        throw new Error(statusMsg);
                    }
                    return r.text();
                })
                .then(function(text) {
                    var res;
                    try { res = JSON.parse(text); } catch (e) {
                        var preview = (text && text.length > 500) ? text.slice(0, 500) + '...' : (text || '');
                        throw new Error('返回非 JSON。响应预览: ' + preview);
                    }
                    return res;
                })
                .then(function(res) {
                    var tbody = document.getElementById('cardListBody');
                    if (!tbody) return;
                    try {
                        var cardCols = IS_AGENT ? 13 : 14;
                        if (res.code !== 0) {
                            tbody.innerHTML = '<tr><td colspan="' + cardCols + '" class="empty">' + (res.msg || '加载失败') + '</td></tr>';
                            return;
                        }
                        var list = (res.data && Array.isArray(res.data.list)) ? res.data.list : [];
                        cardTotal = (res.data && res.data.total !== undefined) ? (parseInt(res.data.total, 10) || 0) : 0;
                        var total = cardTotal;
                        if (!list.length) {
                            tbody.innerHTML = '<tr><td colspan="' + cardCols + '" class="empty">暂无卡密，请先生成</td></tr>';
                            var tb = document.getElementById('cardBatchToolbar');
                            var tp = document.getElementById('cardPagination');
                            if (tb) tb.style.display = 'none';
                            if (tp) tp.style.display = 'none';
                            return;
                        }
                        var tb = document.getElementById('cardBatchToolbar');
                        var tp = document.getElementById('cardPagination');
                        if (tb) tb.style.display = 'flex';
                        if (tp) tp.style.display = 'flex';
                        tbody.innerHTML = list.map(function(row) {
                        var boundAt = row.bound_at ? row.bound_at.replace(' ', '<br>') : '-';
                        var boundIp = row.bound_ip ? '<span class="ip">' + escapeHtml(row.bound_ip) + '</span>' : '-';
                        var loginIps = (row.login_ips && row.login_ips.length) ? row.login_ips.map(function(ip) { return '<span class="ip">' + escapeHtml(ip) + '</span>'; }).join('<br>') : '-';
                        var expiresAt = row.expires_at ? row.expires_at.replace(' ', '<br>') : (row.card_type === 'day' ? '激活后1天' : (row.card_type === 'week' ? '激活后7天' : (row.card_type === 'month' ? '激活后30天' : '永久')));
                        var typeText = row.card_type_text ? escapeHtml(row.card_type_text) : '-';
                        var statusCls = (row.status_text === '已过期') ? 'expired' : ('status-' + row.status);
                        var paused = row.paused ? 1 : 0;
                        var pauseText = paused ? '已暂停' : '启用';
                        var pauseCls = paused ? 'paused' : '';
                        var btnPause = paused ? '' : '<button type="button" class="btn btn-small btn-warning btn-pause" data-id="' + row.id + '">暂停</button> ';
                        var btnEnable = paused ? '<button type="button" class="btn btn-small btn-success btn-enable" data-id="' + row.id + '">启用</button> ' : '';
                        var remarkText = row.remark ? escapeHtml(row.remark) : '-';
                        var creatorCell = IS_AGENT ? '' : ('<td>' + escapeHtml(row.creator_username || '总管理') + '</td>');
                        return '<tr>' +
                            '<td><input type="checkbox" class="card-checkbox" value="' + row.id + '"></td>' +
                            '<td>' + row.id + '</td>' +
                            '<td><code>' + escapeHtml(row.card_code) + '</code></td>' +
                            creatorCell +
                            '<td>' + typeText + '</td>' +
                            '<td class="' + statusCls + '">' + escapeHtml(row.status_text) + '</td>' +
                            '<td class="' + pauseCls + '">' + pauseText + '</td>' +
                            '<td>' + expiresAt + '</td>' +
                            '<td><span class="card-remark" data-id="' + row.id + '" data-remark="' + escapeHtml((row.remark || '').replace(/"/g, '&quot;')) + '">' + remarkText + '</span> <button type="button" class="btn btn-small btn-link btn-remark-edit" data-id="' + row.id + '" data-remark="' + escapeHtml((row.remark || '').replace(/"/g, '&quot;')) + '" style="padding:2px 6px;font-size:12px;">编辑</button></td>' +
                            '<td>' + boundIp + '</td>' +
                            '<td>' + loginIps + '</td>' +
                            '<td>' + boundAt + '</td>' +
                            '<td>' + (row.created_at || '').replace(' ', '<br>') + '</td>' +
                            '<td>' + btnPause + btnEnable + '<button type="button" class="btn btn-danger btn-small btn-card-del" data-id="' + row.id + '">删除</button></td>' +
                            '</tr>';
                    }).join('');
                    document.getElementById('cardSelectAll').checked = false;
                    tbody.querySelectorAll('.card-checkbox').forEach(function(cb) {
                        cb.onchange = updateCardSelectedCount;
                    });
                    document.getElementById('cardSelectAll').onchange = function() {
                        tbody.querySelectorAll('.card-checkbox').forEach(function(cb) { cb.checked = document.getElementById('cardSelectAll').checked; });
                        updateCardSelectedCount();
                    };
                    updateCardSelectedCount();
                    tbody.querySelectorAll('.btn-card-del').forEach(function(btn) {
                        btn.onclick = function() {
                            if (!confirm('确定删除卡密？')) return;
                            deleteCard(btn.getAttribute('data-id'));
                        };
                    });
                    tbody.querySelectorAll('.btn-pause').forEach(function(btn) {
                        btn.onclick = function() { pauseCard(btn.getAttribute('data-id')); };
                    });
                    tbody.querySelectorAll('.btn-enable').forEach(function(btn) {
                        btn.onclick = function() { enableCard(btn.getAttribute('data-id')); };
                    });
                    tbody.querySelectorAll('.btn-remark-edit').forEach(function(btn) {
                        btn.onclick = function() {
                            var id = btn.getAttribute('data-id');
                            var oldRemark = (btn.getAttribute('data-remark') || '').replace(/&quot;/g, '"');
                            var newRemark = prompt('编辑备注', oldRemark);
                            if (newRemark === null) return;
                            fetch(API_BASE + '/api/index.php?module=card', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ action: 'update_remark', id: parseInt(id, 10), remark: newRemark }) })
                                .then(function(r) { return r.json(); }).then(function(res) {
                                if (res.code === 0) { showMsg('备注已更新'); loadCardList(); } else { showMsg(res.msg || '更新失败', true); }
                            }).catch(function() { showMsg('网络错误', true); });
                        };
                    });
                    var totalPages = Math.max(1, Math.ceil(total / cardPageSize));
                    var pagEl = document.getElementById('cardPagination');
                    pagEl.innerHTML = '<span>共 ' + total + ' 条</span><button type="button" id="cardPrev">上一页</button><span>第 ' + cardPage + ' / ' + totalPages + ' 页</span><button type="button" id="cardNext">下一页</button>';
                    document.getElementById('cardPrev').disabled = cardPage <= 1;
                    document.getElementById('cardNext').disabled = cardPage >= totalPages;
                    document.getElementById('cardPrev').onclick = function() { if (cardPage > 1) loadCardList(cardPage - 1); };
                    document.getElementById('cardNext').onclick = function() { if (cardPage < totalPages) loadCardList(cardPage + 1); };
                    } catch (err) {
                        tbody.innerHTML = '<tr><td colspan="' + (IS_AGENT ? 13 : 14) + '" class="empty">加载失败<br><small>' + escapeHtml(String(err && err.message)) + '</small></td></tr>';
                        var pag = document.getElementById('cardPagination');
                        if (pag) pag.style.display = 'none';
                    }
                }).catch(function(e) {
                    if (redirectToLoginIfAuthFailed(e)) return;
                    console.error('[卡密列表]', e);
                    var tbody = document.getElementById('cardListBody');
                    if (tbody) tbody.innerHTML = '<tr><td colspan="' + (IS_AGENT ? 13 : 14) + '" class="empty">请求失败<br><small>' + escapeHtml(String(e && e.message)) + '</small></td></tr>';
                    var pag = document.getElementById('cardPagination');
                    if (pag) pag.style.display = 'none';
                });
        }
        var btnCardSearch = document.getElementById('btnCardSearch');
        if (btnCardSearch) btnCardSearch.onclick = function() { loadCardList(1); };
        var btnCardExport = document.getElementById('btnCardExport');
        if (btnCardExport) btnCardExport.onclick = function(e) {
            e.preventDefault();
            var cardKw = document.getElementById('cardKeyword');
            var keyword = (cardKw && cardKw.value) ? String(cardKw.value).trim() : '';
            var statusEl = document.getElementById('cardStatus');
            var pausedEl = document.getElementById('cardPaused');
            var typeEl = document.getElementById('cardType');
            var status = statusEl ? statusEl.value : 'all';
            var paused = pausedEl ? pausedEl.value : 'all';
            var cardType = typeEl ? typeEl.value : 'all';
            var url = API_BASE + '/api/index.php?module=export&type=cards';
            if (keyword) url += '&keyword=' + encodeURIComponent(keyword);
            if (status && status !== 'all') url += '&status=' + encodeURIComponent(status);
            if (paused && paused !== 'all') url += '&paused=' + encodeURIComponent(paused);
            if (cardType && cardType !== 'all') url += '&card_type=' + encodeURIComponent(cardType);
            window.open(url, '_blank');
        };
        function getSelectedCardIds() {
            var ids = [];
            document.querySelectorAll('.card-checkbox:checked').forEach(function(cb) { ids.push(parseInt(cb.value, 10)); });
            return ids;
        }
        function updateCardSelectedCount() {
            var total = document.querySelectorAll('.card-checkbox').length;
            var n = getSelectedCardIds().length;
            var el = document.getElementById('cardSelectedCount');
            if (el) el.textContent = '已选 ' + n + ' 条';
            var allCb = document.getElementById('cardSelectAll');
            if (allCb) { allCb.checked = total > 0 && n === total; allCb.indeterminate = n > 0 && n < total; }
        }
        var btnBatchPause = document.getElementById('btnBatchPause');
        if (btnBatchPause) btnBatchPause.onclick = function() {
            var ids = getSelectedCardIds();
            if (ids.length === 0) { showMsg('请先勾选要操作的卡密', true); return; }
            if (!confirm('确定暂停选中的 ' + ids.length + ' 条卡密？')) return;
            fetch(API_BASE + '/api/index.php?module=card', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ action: 'pause', ids: ids }) })
                .then(function(r) { return r.json(); }).then(function(res) {
                if (res.code === 0) { showMsg('已暂停 ' + (res.data && res.data.affected) + ' 条'); loadStats(); loadCardList(); loadUserList(); } else { showMsg(res.msg || '操作失败', true); }
            }).catch(function() { showMsg('网络错误', true); });
        };
        var btnBatchEnable = document.getElementById('btnBatchEnable');
        if (btnBatchEnable) btnBatchEnable.onclick = function() {
            var ids = getSelectedCardIds();
            if (ids.length === 0) { showMsg('请先勾选要操作的卡密', true); return; }
            if (!confirm('确定启用选中的 ' + ids.length + ' 条卡密？')) return;
            fetch(API_BASE + '/api/index.php?module=card', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ action: 'enable', ids: ids }) })
                .then(function(r) { return r.json(); }).then(function(res) {
                if (res.code === 0) { showMsg('已启用 ' + (res.data && res.data.affected) + ' 条'); loadStats(); loadCardList(); loadUserList(); } else { showMsg(res.msg || '操作失败', true); }
            }).catch(function() { showMsg('网络错误', true); });
        };
        var btnBatchDelete = document.getElementById('btnBatchDelete');
        if (btnBatchDelete) btnBatchDelete.onclick = function() {
            var ids = getSelectedCardIds();
            if (ids.length === 0) { showMsg('请先勾选要删除的卡密', true); return; }
            if (!confirm('确定删除选中的 ' + ids.length + ' 条卡密？此操作不可恢复。')) return;
            fetch(API_BASE + '/api/index.php?module=card', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ action: 'delete', ids: ids }) })
                .then(function(r) { return r.json(); }).then(function(res) {
                if (res.code === 0) { showMsg('已删除 ' + (res.data && res.data.affected) + ' 条'); loadStats(); loadCardList(); loadUserList(); } else { showMsg(res.msg || '删除失败', true); }
            }).catch(function() { showMsg('网络错误', true); });
        };
        var selectedCardType = 'day';
        document.querySelectorAll('.btn-type').forEach(function(btn) {
            btn.onclick = function() {
                document.querySelectorAll('.btn-type').forEach(function(b) { b.classList.remove('active'); });
                btn.classList.add('active');
                selectedCardType = btn.getAttribute('data-type');
            };
        });
        function deleteCard(id) {
            fetch(API_BASE + '/api/index.php?module=card', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                credentials: 'include',
                body: JSON.stringify({ action: 'delete', id: parseInt(id, 10) })
            }).then(function(r) { return r.json(); }).then(function(res) {
                if (res.code === 0) { showMsg('卡密删除成功'); loadCardList(); loadUserList(); } else { showMsg(res.msg || '删除失败', true); }
            }).catch(function() { showMsg('网络错误', true); });
        }
        function pauseCard(id) {
            var cid = parseInt(id, 10);
            if (!cid) { showMsg('无法获取卡密ID', true); return; }
            var url = API_BASE + '/api/index.php?module=card&action=pause&id=' + cid;
            fetch(url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                credentials: 'include',
                body: JSON.stringify({ action: 'pause', id: cid })
            }).then(function(r) { return r.json(); }).then(function(res) {
                if (res.code === 0) { showMsg('已暂停'); loadCardList(); loadUserList(); } else { showMsg(res.msg || '操作失败', true); if (res.code === 404) loadUserList(); }
            }).catch(function() { showMsg('网络错误', true); });
        }
        function enableCard(id) {
            var cid = parseInt(id, 10);
            if (!cid) { showMsg('无法获取卡密ID', true); return; }
            var url = API_BASE + '/api/index.php?module=card&action=enable&id=' + cid;
            fetch(url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                credentials: 'include',
                body: JSON.stringify({ action: 'enable', id: cid })
            }).then(function(r) { return r.json(); }).then(function(res) {
                if (res.code === 0) { showMsg('已启用'); loadCardList(); loadUserList(); } else { showMsg(res.msg || '操作失败', true); if (res.code === 404) loadUserList(); }
            }).catch(function() { showMsg('网络错误', true); });
        }
        window.userPauseCard = pauseCard;
        window.userEnableCard = enableCard;
        var btnGenerate = document.getElementById('btnGenerate');
        if (btnGenerate) btnGenerate.onclick = function() {
            var genCount = document.getElementById('genCount');
            var genLen = document.getElementById('genLen');
            var count = (genCount && genCount.value) ? (parseInt(genCount.value, 10) || 5) : 5;
            var len = (genLen && genLen.value) ? (parseInt(genLen.value, 10) || 16) : 16;
            fetch(API_BASE + '/api/index.php?module=card', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                credentials: 'include',
                body: JSON.stringify({ action: 'generate', count: count, length: len, type: selectedCardType })
            }).then(function(r) { return r.json(); }).then(function(res) {
                if (res.code === 0) {
                    showMsg('成功生成 ' + (res.data && res.data.count) + ' 个' + (res.data && res.data.card_type_text ? res.data.card_type_text : '') + '卡密');
                    loadStats();
                    loadCardList(1);
                } else {
                    showMsg(res.msg || '生成失败', true);
                }
            }).catch(function() { showMsg('网络错误', true); });
        };

        // 用户列表
        function loadUserList(page) {
            var userKw = document.getElementById('userKeyword');
            var userBody = document.getElementById('userListBody');
            if (!userBody) return;
            if (page !== undefined) userPage = page;
            var keyword = (userKw && userKw.value) ? String(userKw.value).trim() : '';
            var url = API_BASE + '/api/index.php?module=admin_users&action=list&page=' + userPage + '&page_size=' + userPageSize;
            if (keyword) url += '&keyword=' + encodeURIComponent(keyword);
            fetchWithTimeout(url, { credentials: 'include' }, 15000)
                .then(function(r) {
                    if (!r.ok) {
                        if (r.status === 401) throw new Error('未登录或已过期，请重新登录');
                        throw new Error(r.status + ' ' + (r.statusText || ''));
                    }
                    return r.text();
                })
                .then(function(text) {
                    var res;
                    try { res = JSON.parse(text); } catch (e) {
                        var preview = (text && text.length > 500) ? text.slice(0, 500) + '...' : (text || '');
                        throw new Error('返回非 JSON。响应预览: ' + preview);
                    }
                    return res;
                })
                .then(function(res) {
                    var tbody = document.getElementById('userListBody');
                    if (!tbody) return;
                    try {
                        if (res.code !== 0) {
                            tbody.innerHTML = '<tr><td colspan="9" class="empty">' + (res.msg || '加载失败') + '</td></tr>';
                            return;
                        }
                        var list = (res.data && Array.isArray(res.data.list)) ? res.data.list : [];
                        userTotal = (res.data && res.data.total !== undefined) ? (parseInt(res.data.total, 10) || 0) : 0;
                        var total = userTotal;
                        if (!list.length) {
                            tbody.innerHTML = '<tr><td colspan="9" class="empty">暂无注册用户</td></tr>';
                            var up = document.getElementById('userPagination');
                            if (up) up.style.display = 'none';
                            return;
                        }
                        var up = document.getElementById('userPagination');
                        if (up) up.style.display = 'flex';
                        tbody.innerHTML = list.map(function(row) {
                        var loginIps = (row.login_ips && row.login_ips.length) ? row.login_ips.map(function(ip) { return '<span class="ip">' + escapeHtml(ip) + '</span>'; }).join('<br>') : '-';
                        var cardStatus = row.card_valid ? '<span style="color:#51cf66">有效</span>' : '<span class="invalid">已删除/已过期</span>';
                        var expiresAt = row.expires_at ? row.expires_at.replace(' ', '<br>') : (row.card_key_id ? '永久' : '-');
                        var cardId = row.card_key_id ? row.card_key_id : '';
                        var statusText = row.user_status || '-';
                        var statusClass = statusText === '正常' ? 'status-ok' : (statusText === '已暂停' ? 'status-pause' : (statusText === '已过期' ? 'status-invalid' : ''));
                        return '<tr>' +
                            '<td>' + row.id + '</td>' +
                            '<td>' + escapeHtml(row.username) + '</td>' +
                            '<td><code>' + escapeHtml(row.card_code || '-') + '</code></td>' +
                            '<td><span class="user-status ' + statusClass + '">' + escapeHtml(statusText) + '</span></td>' +
                            '<td>' + cardStatus + '</td>' +
                            '<td>' + expiresAt + '</td>' +
                            '<td>' + loginIps + '</td>' +
                            '<td>' + (row.created_at || '').replace(' ', '<br>') + '</td>' +
                            '<td><button type="button" class="btn btn-small btn-primary btn-user-extend" data-id="' + row.id + '">加时</button> ' +
                                '<button type="button" class="btn btn-danger btn-small btn-user-del" data-id="' + row.id + '" data-name="' + escapeHtml(row.username) + '">删除</button></td>' +
                            '</tr>';
                    }).join('');
                    var totalPages = Math.max(1, Math.ceil(total / userPageSize));
                    var pagEl = document.getElementById('userPagination');
                    pagEl.innerHTML = '<span>共 ' + total + ' 条</span><button type="button" id="userPrev">上一页</button><span>第 ' + userPage + ' / ' + totalPages + ' 页</span><button type="button" id="userNext">下一页</button>';
                    document.getElementById('userPrev').disabled = userPage <= 1;
                    document.getElementById('userNext').disabled = userPage >= totalPages;
                    document.getElementById('userPrev').onclick = function() { if (userPage > 1) loadUserList(userPage - 1); };
                    document.getElementById('userNext').onclick = function() { if (userPage < totalPages) loadUserList(userPage + 1); };
                    } catch (err) {
                        tbody.innerHTML = '<tr><td colspan="9" class="empty">加载失败<br><small>' + escapeHtml(String(err && err.message)) + '</small></td></tr>';
                        var up = document.getElementById('userPagination');
                        if (up) up.style.display = 'none';
                    }
                }).catch(function(e) {
                    if (redirectToLoginIfAuthFailed(e)) return;
                    console.error('[用户列表]', e);
                    var tbody = document.getElementById('userListBody');
                    if (tbody) tbody.innerHTML = '<tr><td colspan="9" class="empty">请求失败<br><small>' + escapeHtml(String(e && e.message)) + '</small></td></tr>';
                    var pag = document.getElementById('userPagination');
                    if (pag) pag.style.display = 'none';
                });
        }
        // 用户列表：事件委托仅处理删除（暂停/启用已用内联 onclick）
        document.getElementById('userTable').addEventListener('click', function(e) {
            var btn = e.target;
            var tableEl = document.getElementById('userTable');
            while (btn && btn !== tableEl) {
                if (btn.classList) {
                    // 删除用户
                    if (btn.classList.contains('btn-user-del')) {
                        e.preventDefault();
                        if (!confirm('确定删除用户「' + (btn.getAttribute('data-name') || '') + '」？')) return;
                        var delId = btn.getAttribute('data-id');
                        if (!delId) return;
                        fetch(API_BASE + '/api/index.php?module=admin_users', {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            credentials: 'include',
                            body: JSON.stringify({ action: 'delete', id: parseInt(delId, 10) })
                        }).then(function(r) { return r.json(); }).then(function(res) {
                            if (res.code === 0) { showMsg('用户删除成功'); loadStats(); loadUserList(); } else { showMsg(res.msg || '删除失败', true); }
                        }).catch(function() { showMsg('网络错误', true); });
                        return;
                    }
                    // 用户加时（以天为单位）
                    if (btn.classList.contains('btn-user-extend')) {
                        e.preventDefault();
                        var uid = btn.getAttribute('data-id');
                        if (!uid) return;
                        var input = prompt('请输入要为该用户增加的天数（正整数，例如 1、7、30）', '1');
                        if (input === null) return;
                        input = input.trim();
                        if (!input) {
                            showMsg('请输入加时天数', true);
                            return;
                        }
                        var days = parseInt(input, 10);
                        if (!days || days <= 0) {
                            showMsg('加时天数必须为正整数', true);
                            return;
                        }
                        fetch(API_BASE + '/api/index.php?module=admin_users', {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            credentials: 'include',
                            body: JSON.stringify({ action: 'extend_expire', user_id: parseInt(uid, 10), days: days })
                        }).then(function(r) { return r.json(); }).then(function(res) {
                            if (res.code === 0) {
                                var newExp = res.data && res.data.new_expires_at ? res.data.new_expires_at : '';
                                showMsg('加时成功，新的到期时间：' + newExp);
                                alert('加时成功！新的到期时间：\n' + (newExp || '未知'));
                                loadStats();
                                loadUserList();
                                loadCardList();
                            } else {
                                showMsg(res.msg || '加时失败', true);
                                alert('加时失败：' + (res.msg || '未知错误'));
                            }
                        }).catch(function() {
                            showMsg('网络错误', true);
                            alert('加时失败：网络错误或服务器无响应');
                        });
                        return;
                    }
                }
                btn = btn.parentNode;
            }
        });
        var btnUserSearch = document.getElementById('btnUserSearch');
        if (btnUserSearch) btnUserSearch.onclick = function() { loadUserList(1); };
        var btnUserExport = document.getElementById('btnUserExport');
        if (btnUserExport) btnUserExport.onclick = function(e) {
            e.preventDefault();
            var userKw = document.getElementById('userKeyword');
            var keyword = (userKw && userKw.value) ? String(userKw.value).trim() : '';
            var url = API_BASE + '/api/index.php?module=export&type=users';
            if (keyword) url += '&keyword=' + encodeURIComponent(keyword);
            window.open(url, '_blank');
        };

        // 黑名单
        function loadBlacklist() {
            fetch(API_BASE + '/api/index.php?module=blacklist&action=list', { credentials: 'include' })
                .then(function(r) { return r.json(); })
                .then(function(res) {
                    var tbody = document.getElementById('blacklistBody');
                    if (res.code !== 0) {
                        tbody.innerHTML = '<tr><td colspan="4" class="empty">' + (res.msg || '加载失败') + '</td></tr>';
                        return;
                    }
                    var list = res.data && res.data.list ? res.data.list : [];
                    if (list.length === 0) {
                        tbody.innerHTML = '<tr><td colspan="4" class="empty">暂无黑名单</td></tr>';
                        return;
                    }
                    tbody.innerHTML = list.map(function(row) {
                        var typeText = row.type === 'ip' ? 'IP' : '用户';
                        return '<tr><td>' + escapeHtml(typeText) + '</td><td><code>' + escapeHtml(row.value) + '</code></td><td>' + (row.created_at || '').replace(' ', '<br>') + '</td><td><button type="button" class="btn btn-danger btn-small btn-blacklist-del" data-id="' + row.id + '">删除</button></td></tr>';
                    }).join('');
                    tbody.querySelectorAll('.btn-blacklist-del').forEach(function(btn) {
                        btn.onclick = function() {
                            if (!confirm('确定移出黑名单？')) return;
                            var id = btn.getAttribute('data-id');
                            fetch(API_BASE + '/api/index.php?module=blacklist', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ action: 'delete', id: parseInt(id, 10) }) })
                                .then(function(r) { return r.json(); }).then(function(res) {
                                if (res.code === 0) { showMsg('已移除'); loadBlacklist(); } else { showMsg(res.msg || '操作失败', true); }
                            }).catch(function() { showMsg('网络错误', true); });
                        };
                    });
                }).catch(function() {
                    document.getElementById('blacklistBody').innerHTML = '<tr><td colspan="4" class="empty">请求失败</td></tr>';
                });
        }
        var btnBlacklistAdd = document.getElementById('btnBlacklistAdd');
        if (btnBlacklistAdd) btnBlacklistAdd.onclick = function() {
            var typeEl = document.getElementById('blacklistType');
            var valueEl = document.getElementById('blacklistValue');
            var type = typeEl ? typeEl.value : 'ip';
            var value = (valueEl && valueEl.value) ? String(valueEl.value).trim() : '';
            if (!value) { showMsg(type === 'ip' ? '请输入IP' : '请输入用户名', true); return; }
            fetch(API_BASE + '/api/index.php?module=blacklist', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ action: 'add', type: type, value: value }) })
                .then(function(r) { return r.json(); }).then(function(res) {
                if (res.code === 0) { showMsg('添加成功'); if (valueEl) valueEl.value = ''; loadBlacklist(); } else { showMsg(res.msg || '添加失败', true); }
            }).catch(function() { showMsg('网络错误', true); });
        };

        // 操作日志
        var logPage = 1;
        var logPageSize = 30;
        function loadOperationLog(page) {
            if (page !== undefined) logPage = page;
            var url = API_BASE + '/api/index.php?module=operation_log&action=list&page=' + logPage + '&page_size=' + logPageSize;
            fetch(url, { credentials: 'include' })
                .then(function(r) { return r.json(); }).then(function(res) {
                    var tbody = document.getElementById('logListBody');
                    if (res.code !== 0) {
                        tbody.innerHTML = '<tr><td colspan="5" class="empty">' + (res.msg || '加载失败') + '</td></tr>';
                        return;
                    }
                    var list = res.data && res.data.list ? res.data.list : [];
                    var total = res.data && res.data.total !== undefined ? res.data.total : 0;
                    if (list.length === 0) {
                        tbody.innerHTML = '<tr><td colspan="5" class="empty">暂无操作记录</td></tr>';
                        document.getElementById('logPagination').style.display = 'none';
                        return;
                    }
                    document.getElementById('logPagination').style.display = 'flex';
                    tbody.innerHTML = list.map(function(row) {
                        return '<tr><td>' + row.id + '</td><td>' + escapeHtml(row.admin_username) + '</td><td>' + escapeHtml(row.action) + '</td><td>' + escapeHtml(row.detail || '-') + '</td><td>' + (row.created_at || '').replace(' ', '<br>') + '</td></tr>';
                    }).join('');
                    var totalPages = Math.max(1, Math.ceil(total / logPageSize));
                    var pagEl = document.getElementById('logPagination');
                    if (!pagEl) return;
                    pagEl.innerHTML = '<span>共 ' + total + ' 条</span><button type="button" id="logPrev">上一页</button><span>第 ' + logPage + ' / ' + totalPages + ' 页</span><button type="button" id="logNext">下一页</button>';
                    var logPrev = document.getElementById('logPrev');
                    var logNext = document.getElementById('logNext');
                    if (logPrev) { logPrev.disabled = logPage <= 1; logPrev.onclick = function() { if (logPage > 1) loadOperationLog(logPage - 1); }; }
                    if (logNext) { logNext.disabled = logPage >= totalPages; logNext.onclick = function() { if (logPage < totalPages) loadOperationLog(logPage + 1); }; }
                }).catch(function() {
                    document.getElementById('logListBody').innerHTML = '<tr><td colspan="5" class="empty">请求失败</td></tr>';
                });
        }

        var agentCardPage = 1;
        var agentCardPageSize = 10;
        function loadAgentCardsList(page) {
            if (page !== undefined) agentCardPage = page;
            var url = API_BASE + '/api/index.php?module=card&action=list_agent_cards&page=' + agentCardPage + '&page_size=' + agentCardPageSize;
            fetch(url, { credentials: 'include' })
                .then(function(r) { return r.json(); })
                .then(function(res) {
                    var tbody = document.getElementById('agentCardListBody');
                    if (!tbody) return;
                    if (res.code !== 0) {
                        tbody.innerHTML = '<tr><td colspan="9" class="empty">' + (res.msg || '加载失败') + '</td></tr>';
                        return;
                    }
                    var list = res.data && res.data.list ? res.data.list : [];
                    var total = res.data && res.data.total !== undefined ? res.data.total : 0;
                    if (list.length === 0) {
                        tbody.innerHTML = '<tr><td colspan="9" class="empty">暂无代理生成的卡密</td></tr>';
                        document.getElementById('agentCardPagination').style.display = 'none';
                        return;
                    }
                    document.getElementById('agentCardPagination').style.display = 'flex';
                    tbody.innerHTML = list.map(function(row) {
                        var boundAt = row.bound_at ? row.bound_at.replace(' ', '<br>') : '-';
                        var expiresAt = row.expires_at ? row.expires_at.replace(' ', '<br>') : (row.card_type === 'day' ? '激活后1天' : (row.card_type === 'week' ? '激活后7天' : (row.card_type === 'month' ? '激活后30天' : '永久')));
                        var typeText = row.card_type_text ? escapeHtml(row.card_type_text) : '-';
                        var statusCls = (row.status_text === '已过期') ? 'expired' : ('status-' + row.status);
                        var creatorName = escapeHtml(row.creator_username || '-');
                        var remarkText = row.remark ? escapeHtml(row.remark) : '-';
                        return '<tr><td>' + row.id + '</td><td><code>' + escapeHtml(row.card_code) + '</code></td><td>' + typeText + '</td><td class="' + statusCls + '">' + escapeHtml(row.status_text) + '</td><td>' + creatorName + '</td><td>' + expiresAt + '</td><td>' + remarkText + '</td><td>' + boundAt + '</td><td>' + (row.created_at || '').replace(' ', '<br>') + '</td></tr>';
                    }).join('');
                    var totalPages = Math.max(1, Math.ceil(total / agentCardPageSize));
                    var pagEl = document.getElementById('agentCardPagination');
                    pagEl.innerHTML = '<span>共 ' + total + ' 条</span><button type="button" id="agentCardPrev">上一页</button><span>第 ' + agentCardPage + ' / ' + totalPages + ' 页</span><button type="button" id="agentCardNext">下一页</button>';
                    document.getElementById('agentCardPrev').disabled = agentCardPage <= 1;
                    document.getElementById('agentCardNext').disabled = agentCardPage >= totalPages;
                    document.getElementById('agentCardPrev').onclick = function() { if (agentCardPage > 1) loadAgentCardsList(agentCardPage - 1); };
                    document.getElementById('agentCardNext').onclick = function() { if (agentCardPage < totalPages) loadAgentCardsList(agentCardPage + 1); };
                }).catch(function() {
                    var tbody = document.getElementById('agentCardListBody');
                    if (tbody) tbody.innerHTML = '<tr><td colspan="9" class="empty">请求失败</td></tr>';
                });
        }
        function loadAgentsList() {
            fetch(API_BASE + '/api/index.php?module=agents&action=list', { credentials: 'include' })
                .then(function(r) { return r.json(); })
                .then(function(res) {
                    var tbody = document.getElementById('agentListBody');
                    if (!tbody) return;
                    if (res.code !== 0) {
                        tbody.innerHTML = '<tr><td colspan="4" class="empty">' + (res.msg || '加载失败') + '</td></tr>';
                        return;
                    }
                    var list = res.data && res.data.list ? res.data.list : [];
                    if (list.length === 0) {
                        tbody.innerHTML = '<tr><td colspan="4" class="empty">暂无代理，请添加</td></tr>';
                        return;
                    }
                    tbody.innerHTML = list.map(function(row) {
                        return '<tr><td>' + row.id + '</td><td>' + escapeHtml(row.username) + '</td><td>' + (row.created_at || '').replace(' ', '<br>') + '</td><td><button type="button" class="btn btn-danger btn-small btn-agent-del" data-id="' + row.id + '" data-name="' + escapeHtml(row.username) + '">删除</button></td></tr>';
                    }).join('');
                    tbody.querySelectorAll('.btn-agent-del').forEach(function(btn) {
                        btn.onclick = function() {
                            var name = btn.getAttribute('data-name') || '';
                            if (!confirm('确定删除代理「' + name + '」？\n\n该代理已生成的卡密及对应用户将被全部删除，此操作不可恢复。')) return;
                            var id = btn.getAttribute('data-id');
                            fetch(API_BASE + '/api/index.php?module=agents', {
                                method: 'POST',
                                headers: { 'Content-Type': 'application/json' },
                                credentials: 'include',
                                body: JSON.stringify({ action: 'delete', id: parseInt(id, 10) })
                            }).then(function(r) { return r.json(); }).then(function(res) {
                                if (res.code === 0) { showMsg('已删除'); loadAgentsList(); loadAgentCardsList(1); } else { showMsg(res.msg || '删除失败', true); }
                            }).catch(function() { showMsg('网络错误', true); });
                        };
                    });
                }).catch(function() {
                    var tbody = document.getElementById('agentListBody');
                    if (tbody) tbody.innerHTML = '<tr><td colspan="4" class="empty">请求失败</td></tr>';
                });
        }
        if (document.getElementById('btnAgentCardsRefresh')) {
            document.getElementById('btnAgentCardsRefresh').onclick = function() { loadAgentCardsList(1); };
        }
        if (document.getElementById('btnAgentAdd')) {
            document.getElementById('btnAgentAdd').onclick = function() {
                var username = (document.getElementById('agentUsername') && document.getElementById('agentUsername').value) ? document.getElementById('agentUsername').value.trim() : '';
                var password = (document.getElementById('agentPassword') && document.getElementById('agentPassword').value) ? document.getElementById('agentPassword').value : '';
                if (!username || username.length < 2) { showMsg('用户名至少2位', true); return; }
                if (!password || password.length < 4) { showMsg('密码至少4位', true); return; }
                fetch(API_BASE + '/api/index.php?module=agents', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify({ action: 'add', username: username, password: password })
                }).then(function(r) { return r.json(); }).then(function(res) {
                    if (res.code === 0) {
                        showMsg('代理添加成功');
                        if (document.getElementById('agentUsername')) document.getElementById('agentUsername').value = '';
                        if (document.getElementById('agentPassword')) document.getElementById('agentPassword').value = '';
                        loadAgentsList();
                    } else {
                        showMsg(res.msg || '添加失败', true);
                    }
                }).catch(function() { showMsg('网络错误', true); });
            };
        }

        var IS_AGENT = <?php echo $isAgent ? 'true' : 'false'; ?>;


        loadStats();
        // 卡密/用户列表：DOM 就绪后再请求，并做两次延迟重试（避免登录后 session 未带上或首请求被取消）
        function runInitialLists() {
            loadCardList();
            loadUserList();
        }
        function scheduleInitialLists() {
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', function once() {
                    document.removeEventListener('DOMContentLoaded', once);
                    runInitialLists();
                    setTimeout(runInitialLists, 350);
                    setTimeout(runInitialLists, 900);
                });
            } else {
                runInitialLists();
                setTimeout(runInitialLists, 350);
                setTimeout(runInitialLists, 900);
            }
        }
        scheduleInitialLists();
        if (!IS_AGENT) {
            loadBlacklist();
            loadOperationLog();
            loadAgentCardsList();
            loadAgentsList();
        }

    </script>
</body>
</html>
