Spaces:
Paused
Paused
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>{% block title %}GPT Team 管理系统{% endblock %}</title> | |
| <link rel="icon" type="image/png" href="/static/favicon.png"> | |
| <link rel="stylesheet" href="/static/css/style.css"> | |
| <!-- Lucide Icons --> | |
| <script src="https://unpkg.com/lucide@latest"></script> | |
| {% block extra_css %}{% endblock %} | |
| </head> | |
| <body class="bg-gray-50 text-slate-900{% if user %} admin-theme{% endif %}" data-pool-type="{% if active_page == 'welfare' %}welfare{% else %}normal{% endif %}"> | |
| {% if user %} | |
| <!-- 导航栏 --> | |
| <nav class="navbar"> | |
| <div class="navbar-container"> | |
| <div class="navbar-left"> | |
| <button class="mobile-menu-toggle" onclick="toggleSidebar()"> | |
| <i data-lucide="menu"></i> | |
| </button> | |
| <div class="navbar-brand"> | |
| <h1>GPT Team 管理系统</h1> | |
| </div> | |
| </div> | |
| <div class="navbar-menu"> | |
| <span class="navbar-user">管理员</span> | |
| <button onclick="logout()" class="btn btn-secondary">登出</button> | |
| </div> | |
| </div> | |
| </nav> | |
| <!-- 移动端侧边栏遮罩 --> | |
| <div id="sidebarOverlay" class="sidebar-overlay" onclick="toggleSidebar()"></div> | |
| <!-- 主容器 --> | |
| <div class="main-container"> | |
| <!-- 侧边栏 --> | |
| <aside class="sidebar" id="adminSidebar"> | |
| <ul class="sidebar-menu"> | |
| <li class="menu-item {% if active_page == 'dashboard' %}active{% endif %}"> | |
| <a href="/admin"> | |
| <span class="menu-icon" data-lucide="layout-dashboard"></span> | |
| <span class="menu-text">控制台</span> | |
| </a> | |
| </li> | |
| <li class="menu-item {% if active_page == 'codes' %}active{% endif %}"> | |
| <a href="/admin/codes"> | |
| <span class="menu-icon" data-lucide="ticket"></span> | |
| <span class="menu-text">兑换码管理</span> | |
| </a> | |
| </li> | |
| <li class="menu-item {% if active_page == 'records' %}active{% endif %}"> | |
| <a href="/admin/records"> | |
| <span class="menu-icon" data-lucide="file-text"></span> | |
| <span class="menu-text">使用记录</span> | |
| </a> | |
| </li> | |
| <li class="menu-item {% if active_page == 'welfare' %}active{% endif %}"> | |
| <a href="/admin/welfare"> | |
| <span class="menu-icon" data-lucide="heart-handshake"></span> | |
| <span class="menu-text">福利车位</span> | |
| </a> | |
| </li> | |
| <li class="menu-item {% if active_page == 'announcement' %}active{% endif %}"> | |
| <a href="/admin/announcement"> | |
| <span class="menu-icon" data-lucide="megaphone"></span> | |
| <span class="menu-text">公告通知</span> | |
| </a> | |
| </li> | |
| <li class="menu-item {% if active_page == 'settings' %}active{% endif %}"> | |
| <a href="/admin/settings"> | |
| <span class="menu-icon" data-lucide="settings"></span> | |
| <span class="menu-text">系统设置</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </aside> | |
| <!-- 主内容区 --> | |
| <main class="main-content"> | |
| {% block content %}{% endblock %} | |
| </main> | |
| </div> | |
| {% else %} | |
| <!-- 未登录时的内容 --> | |
| <div class="auth-container"> | |
| {% block auth_content %}{% endblock %} | |
| </div> | |
| {% endif %} | |
| <!-- 页脚 --> | |
| <footer class="footer"> | |
| <p>© 2026 <a href="https://github.com/loLollipop/team-manage-refresh" target="_blank" class="footer-link">GPT Team | |
| 管理系统</a> v0.1.0</p> | |
| </footer> | |
| <script> | |
| function toggleSidebar() { | |
| const sidebar = document.getElementById('adminSidebar'); | |
| const overlay = document.getElementById('sidebarOverlay'); | |
| if (sidebar) sidebar.classList.toggle('open'); | |
| if (overlay) overlay.classList.toggle('show'); | |
| } | |
| </script> | |
| <!-- 导入 Team 模态框 --> | |
| <div id="importTeamModal" class="modal-overlay"> | |
| <div class="modal"> | |
| <div class="modal-header"> | |
| <h3>导入 Team</h3> | |
| <button class="modal-close" onclick="hideModal('importTeamModal')">×</button> | |
| </div> | |
| <div class="modal-body"> | |
| <div class="modal-tabs"> | |
| <button class="modal-tab-btn active" | |
| onclick="switchModalTab('importTeamModal', 'singleImport')">单个导入</button> | |
| <button class="modal-tab-btn" | |
| onclick="switchModalTab('importTeamModal', 'batchImport')">批量导入</button> | |
| </div> | |
| <div id="singleImport" class="import-panel"> | |
| <form id="singleImportForm" onsubmit="handleSingleImport(event)"> | |
| <input type="hidden" name="poolType" value="{% if active_page == 'welfare' %}welfare{% else %}normal{% endif %}"> | |
| <div id="oauthQuickSection" class="import-mode-card oauth-quick-card"> | |
| <div class="oauth-quick-head"> | |
| <label>一键获取 Token</label> | |
| <p class="oauth-quick-subtitle">三步完成:获取授权链接 → 粘贴回调 → 自动填充。</p> | |
| </div> | |
| <div class="oauth-quick-action-row"> | |
| <button type="button" id="btnOneClickToken" class="btn btn-primary oauth-main-btn">获取授权链接</button> | |
| </div> | |
| <div class="oauth-quick-fields"> | |
| <input type="text" id="oauthAuthorizeUrlOutput" class="form-control" placeholder="授权链接会自动复制,这里仅作备份显示" readonly> | |
| <textarea id="oauthCallbackInput" class="form-control" rows="3" placeholder="登录后粘贴完整回调 URL"></textarea> | |
| </div> | |
| <div class="oauth-quick-btn-row"> | |
| <button type="button" id="btnParseOAuthCallback" class="btn btn-primary">解析并自动填充</button> | |
| <button type="button" id="btnExportOAuthJson" class="btn btn-primary">导出 JSON</button> | |
| <button type="submit" id="btnQuickImportTeam" class="btn btn-primary">导入 Team</button> | |
| </div> | |
| <div class="import-mode-toggle-wrap"> | |
| <button type="button" id="switchToManualFill" class="import-mode-toggle-btn">已有 Token?直接填写</button> | |
| </div> | |
| </div> | |
| <div id="manualTokenSection" class="import-mode-card" style="display: none;"> | |
| <div class="manual-import-action-top" style="margin-bottom: 0.85rem;"> | |
| <button type="submit" class="btn btn-primary">导入 Team</button> | |
| </div> | |
| <div class="form-group"> | |
| <label>Access Token (AT) <span class="required">*</span></label> | |
| <input type="text" name="accessToken" class="form-control" | |
| placeholder="eyJhbGciOiJSUzI1Ni..." required> | |
| <small class="form-text">必填项,以 eyJ 开头的 JWT Token</small> | |
| </div> | |
| <div class="form-group"> | |
| <label>Refresh Token (RT)</label> | |
| <input type="text" name="refreshToken" class="form-control" placeholder="rt-..."> | |
| <small class="form-text">可选,用于自动刷新 AT</small> | |
| </div> | |
| <div class="form-group"> | |
| <label>Session Token</label> | |
| <input type="text" name="sessionToken" class="form-control" placeholder="eyJ..."> | |
| <small class="form-text">可选,作为备选刷新方式</small> | |
| </div> | |
| <div class="form-group"> | |
| <label>Client ID</label> | |
| <input type="text" name="clientId" class="form-control" placeholder="Client ID"> | |
| <small class="form-text">使用 Refresh Token 时必填</small> | |
| </div> | |
| <div class="form-group"> | |
| <label>邮箱</label> | |
| <input type="email" name="email" class="form-control" placeholder="admin@example.com"> | |
| <small class="form-text">可选,如果不填写将从 Token 中自动提取</small> | |
| </div> | |
| <div class="form-group"> | |
| <label>Account ID</label> | |
| <input type="text" name="accountId" class="form-control" | |
| placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"> | |
| <small class="form-text">可选,如果不填写将从 API 自动获取</small> | |
| </div> | |
| <div class="import-mode-toggle-wrap"> | |
| <button type="button" id="switchToQuickToken" class="import-mode-toggle-btn">返回一键获取 Token</button> | |
| </div> | |
| </div> | |
| </form> | |
| </div> | |
| <div id="batchImport" class="import-panel" style="display: none;"> | |
| <form id="batchImportForm" onsubmit="handleBatchImport(event)"> | |
| <input type="hidden" name="poolType" value="{% if active_page == 'welfare' %}welfare{% else %}normal{% endif %}"> | |
| <div class="json-import-box" id="jsonImportBox"> | |
| <input type="file" id="jsonImportFile" accept=".json,application/json" style="display: none;"> | |
| <div class="json-import-box-title">JSON 文件导入</div> | |
| <button type="button" id="chooseJsonFileBtn" class="btn btn-primary">选择 JSON 文件</button> | |
| <div class="json-import-file-hint" id="jsonImportFileName">支持单对象、对象数组,或 {"teams": [...]} 格式</div> | |
| </div> | |
| <textarea name="batchContent" style="display:none"></textarea> | |
| <div id="batchProgressContainer" style="display: none; margin-bottom: 1.5rem;"> | |
| <div class="progress-info" | |
| style="display: flex; justify-content: space-between; margin-bottom: 0.5rem; font-size: 0.875rem;"> | |
| <span id="batchProgressStage">正在准备...</span> | |
| <span id="batchProgressPercent">0%</span> | |
| </div> | |
| <div class="progress-bar-bg" | |
| style="width: 100%; height: 8px; background: rgba(255,255,255,0.05); border-radius: 4px; overflow: hidden;"> | |
| <div id="batchProgressBar" | |
| style="width: 0%; height: 100%; background: linear-gradient(90deg, var(--primary), var(--accent)); transition: width 0.3s ease;"> | |
| </div> | |
| </div> | |
| <div id="batchProgressStats" | |
| style="margin-top: 0.5rem; font-size: 0.75rem; color: var(--text-dim); display: flex; gap: 1rem;"> | |
| <span>成功: <span id="batchSuccessCount" class="text-success">0</span></span> | |
| <span>失败: <span id="batchFailedCount" class="text-danger">0</span></span> | |
| </div> | |
| </div> | |
| <div id="batchResultsContainer" style="display: none; margin-bottom: 1rem;"> | |
| <div | |
| style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;"> | |
| <h4 style="margin: 0;">导入详情</h4> | |
| <small id="batchFinalSummary" class="text-muted"></small> | |
| </div> | |
| <div id="batchResults"></div> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 生成兑换码 模态框 --> | |
| <div id="generateCodeModal" class="modal-overlay"> | |
| <div class="modal"> | |
| <div class="modal-header"> | |
| <h3>生成兑换码</h3> | |
| <button class="modal-close" onclick="hideModal('generateCodeModal')">×</button> | |
| </div> | |
| <div class="modal-body"> | |
| <div class="modal-tabs"> | |
| <button class="modal-tab-btn active" | |
| onclick="switchModalTab('generateCodeModal', 'singleGenerate')">单个生成</button> | |
| <button class="modal-tab-btn" | |
| onclick="switchModalTab('generateCodeModal', 'batchGenerate')">批量生成</button> | |
| </div> | |
| <div id="singleGenerate" class="card-body"> | |
| <form onsubmit="generateSingle(event)"> | |
| <div class="form-group"> | |
| <label>自定义兑换码 (可选)</label> | |
| <input type="text" name="customCode" class="form-control" placeholder="留空则自动生成" | |
| maxlength="32"> | |
| </div> | |
| <div class="form-group"> | |
| <label>有效期 (天数, 可选)</label> | |
| <input type="number" name="expiresDays" class="form-control" placeholder="留空则永久有效" min="1" | |
| max="3650"> | |
| </div> | |
| <div class="form-group"> | |
| <label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;"> | |
| <input type="checkbox" name="hasWarranty" value="true" style="width: auto; margin: 0;" | |
| onchange="toggleWarrantyDays(this, 'single-warranty-days-group')"> | |
| <span>质保兑换码 (可重复使用)</span> | |
| </label> | |
| <small class="form-text">如果是质保兑换码,在质保期内,如果加入的 Team 被封号,可以重复使用</small> | |
| </div> | |
| <div class="form-group" id="single-warranty-days-group" style="display: none;"> | |
| <label>质保时长 (天) *</label> | |
| <input type="number" name="warrantyDays" class="form-control" value="30" min="1" max="3650"> | |
| </div> | |
| <button type="submit" class="btn btn-primary">生成兑换码</button> | |
| </form> | |
| <div id="singleResult" class="result-box" style="display: none;"> | |
| <h4>生成成功</h4> | |
| <div class="code-display"> | |
| <code id="generatedCode"></code> | |
| <button onclick="copyCode()" class="btn btn-sm btn-secondary">复制</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="batchGenerate" class="card-body" style="display: none;"> | |
| <form onsubmit="generateBatch(event)"> | |
| <div class="form-group"> | |
| <label>生成数量 *</label> | |
| <input type="number" name="count" class="form-control" placeholder="请输入生成数量" min="1" | |
| max="1000" required> | |
| </div> | |
| <div class="form-group"> | |
| <label>有效期 (天数, 可选)</label> | |
| <input type="number" name="expiresDays" class="form-control" placeholder="留空则永久有效" min="1" | |
| max="3650"> | |
| </div> | |
| <div class="form-group"> | |
| <label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;"> | |
| <input type="checkbox" name="hasWarranty" value="true" style="width: auto; margin: 0;" | |
| onchange="toggleWarrantyDays(this, 'batch-warranty-days-group')"> | |
| <span>质保兑换码 (可重复使用)</span> | |
| </label> | |
| <small class="form-text">如果是质保兑换码,在质保期内,如果加入的 Team 被封号,可以重复使用</small> | |
| </div> | |
| <div class="form-group" id="batch-warranty-days-group" style="display: none;"> | |
| <label>质保时长 (天) *</label> | |
| <input type="number" name="warrantyDays" class="form-control" value="30" min="1" max="3650"> | |
| </div> | |
| <button type="submit" class="btn btn-primary">批量生成</button> | |
| </form> | |
| <div id="batchResult" class="result-box" style="display: none;"> | |
| <h4>批量生成成功</h4> | |
| <p>成功生成 <strong id="batchTotal">0</strong> 个兑换码</p> | |
| <textarea id="batchCodes" readonly rows="5" class="form-control" | |
| style="margin: 10px 0;"></textarea> | |
| <button onclick="copyBatchCodes()" class="btn btn-sm btn-secondary">复制全部</button> | |
| <button onclick="downloadCodes()" class="btn btn-sm btn-secondary">下载</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 成员管理 模态框 --> | |
| <div id="manageMembersModal" class="modal-overlay"> | |
| <div class="modal" style="max-width: 800px;"> | |
| <div class="modal-header"> | |
| <div> | |
| <h3 id="modalTeamName">Team 成员管理</h3> | |
| <small id="modalTeamEmail" class="text-muted" style="display: block; margin-top: 4px;"></small> | |
| </div> | |
| <button class="modal-close" onclick="hideModal('manageMembersModal')">×</button> | |
| </div> | |
| <div class="modal-body"> | |
| <!-- 添加成员表单 --> | |
| <div class="content-section" | |
| style="padding: 1.25rem; margin-bottom: 1.5rem; background: rgba(255,255,255,0.03);"> | |
| <form id="addMemberForm" onsubmit="handleAddMember(event)" | |
| style="display: flex; gap: 1rem; align-items: flex-end;"> | |
| <div class="form-group" style="flex: 1; margin-bottom: 0;"> | |
| <label for="memberEmail">新增成员邮箱</label> | |
| <input type="email" id="memberEmail" name="email" class="form-control" | |
| placeholder="user@example.com" required> | |
| </div> | |
| <button type="submit" class="btn btn-primary" id="addMemberSubmitBtn"> | |
| <i data-lucide="user-plus"></i> 添加 | |
| </button> | |
| </form> | |
| </div> | |
| <!-- 已加入成员 --> | |
| <div class="content-section" style="margin-bottom: 1.5rem;"> | |
| <h4 style="margin-bottom: 1rem; color: var(--success);">已加入成员</h4> | |
| <div class="table-container" style="max-height: 250px; overflow-y: auto; margin: 0;"> | |
| <table class="data-table"> | |
| <thead> | |
| <tr> | |
| <th>邮箱</th> | |
| <th style="width: 100px;">角色</th> | |
| <th style="width: 160px;">加入时间</th> | |
| <th style="text-align: right; width: 100px;">操作</th> | |
| </tr> | |
| </thead> | |
| <tbody id="modalJoinedMembersTableBody"> | |
| <!-- 动态加载内容 --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- 待加入成员 --> | |
| <div class="content-section" style="margin: 0;"> | |
| <h4 style="margin-bottom: 1rem; color: var(--warning);">待加入成员 (邀请中)</h4> | |
| <div class="table-container" style="max-height: 250px; overflow-y: auto; margin: 0;"> | |
| <table class="data-table"> | |
| <thead> | |
| <tr> | |
| <th>邮箱</th> | |
| <th style="width: 100px;">角色</th> | |
| <th style="width: 160px;">邀请时间</th> | |
| <th style="text-align: right; width: 100px;">操作</th> | |
| </tr> | |
| </thead> | |
| <tbody id="modalInvitedMembersTableBody"> | |
| <!-- 动态加载内容 --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="toast" class="toast"></div> | |
| <script src="/static/js/main.js?v=20260314-settings-anchor-4"></script> | |
| <script> | |
| lucide.createIcons(); | |
| </script> | |
| {% block extra_js %}{% endblock %} | |
| </body> | |
| </html> | |