chore: 清理前端与容器脚本中指向已删台账的过时编号

模板/样式/脚本的注释里散落着「第 N 条」「F59」「P3-43」这类编号,它们指向的
修复验收台账与交接文档已在本轮删除——留着既检索不到、也对不上号,还让后来读
注释的人以为存在一份可查的编号清单。本次只动注释,正文零改动。

覆盖范围(全仓一致):
- templates/index.html 163 处、static/site_auth.js 55 处,共 218 处编号整段摘除
  (含「第 N 条(Px):」前缀与「(Fxx 短标签)」括号)。
- 容器/配置 16 处:docker-compose.yml、entrypoint.sh(F59 / 第 93 条 / 第 97 条)、
  Dockerfile(T7 / 第 97 条)、.dockerignore(T1 / 第 97 条 / 第 55 条 / P2-9)、
  .env.example(F59 / 第 100 条 / 「详见交接文档 F11」这条失效指引)、.gitignore(P3-41)。
- Python 4 处:core/config.py(D-1)、services/tasks.py(O-5×2)、services/_shared.py(O-5)
  ——上一轮 Python 侧清理的漏网。

为什么规则要收窄(两类误伤,实测踩到):
1. 标签首字母不能用泛化的 [A-Z]:X5 是 QQ 浏览器内核、ES5 是 JS 版本,都不是编号。
   泛化后会把「微信 XWeb/X5、QQ 内置」改成「微信 XWeb/、QQ 内置」,把
   「Chromium <87 的老 X5 不识别 inset」改成「老 不识别」。现按本仓真实用过的
   任务族白名单收窄(A B C D F O P R S T U),X5/ES5 原样保留。
2. 括号内「编号 + 长说明」不能整段删:「(F51 只防了切歌,这里补同歌翻页)」
   整段删掉会连说明一起丢;现只摘编号、保留说明文字。

为什么同步改黄金契约快照:题头注释属于模板内容,改了注释首页字节数必然变
(466261 → 464333),tests/golden/responses.json 的 index 条目按既定流程刷新
html_len/html_sha256;并在 tests/test_golden.py 补一段说明,写清该条目记的是
「当前模板」而非 Flask 期数值、以及有意改模板后如何重算
(html_len = 文件字节数 - 1,sha256 = 文件去掉末尾换行后的哈希)。

验证(全部通过):
- 与 HEAD 比对:剥离全部注释后的正文逐行一致(唯一差异是删掉了一处行尾空注释),
  行数不变、每行半角括号/引号数量不变 → 代码与字符串零改动;
- node --check 通过(静态 JS + index.html 内联脚本),<style> 块花括号平衡;
- 真实 uvicorn 冒烟:/health /user/status /api/topen → 200、/admin/site/backup/list → 403,
  /static/site_auth.js 响应体与本地文件 sha1 相同,/ 无「第 N 条」残留;
- ruff All checks passed;pyright 0 errors / 0 warnings;pytest 144 passed。
This commit is contained in:
snowy-yang committed 2026-09-26 13:18:44 +08:00
1 parent 6288a98ecc
commit da3d013e9c
13 files changed
+246 -242

No files matched your search

+56 -56
View File
@@ -5,7 +5,7 @@
(function () {
'use strict';
/* P2-11:Promise.finally 老内核 polyfill(与 index.html 同一份标准实现——
/* Promise.finally 老内核 polyfill(与 index.html 同一份标准实现——
index.html 的脚本先加载,这里再兜底一次确保独立引用时也安全) */
if (!Promise.prototype.finally) {
Promise.prototype.finally = function (cb) {
@@ -71,7 +71,7 @@
return r.json().then(function (d) { if (timer) clearTimeout(timer); return d; },
function (e) { if (timer) clearTimeout(timer); throw e; });
}).catch(function () {
// F9:默认失败兜底——网络断/超时/非 JSON 响应不再产生 unhandledrejection
// 默认失败兜底——网络断/超时/非 JSON 响应不再产生 unhandledrejection
// 噪音(喂 fe_error 上报通道),统一转为标准失败对象走调用方既有的 toast
if (timer) clearTimeout(timer);
return { status: 0, success: false, message: '网络异常,请检查连接后重试' };
@@ -92,9 +92,9 @@
/* ---------------- 设备标识 ---------------- */
function getDeviceId() {
/* F-7(第 79 条):与 index.html 的 getDeviceId 统一为同一键/同一值——
/* 与 index.html 的 getDeviceId 统一为同一键/同一值——
注册指纹与下载任务/心跳的设备号不再各走一套;
内存缓存兜底 localStorage 不可写时同一页面内保持稳定(F108 同款)。 */
内存缓存兜底 localStorage 不可写时同一页面内保持稳定。 */
if (window.__siteDeviceIdCache) return window.__siteDeviceIdCache;
var KEY = 'netease_device_id';
var d = '';
@@ -111,8 +111,8 @@ function getDeviceId() {
var user = null;
var durationText = '';
var ann = null;
var smtpOk = false; // F-8.2:fail-closed——/auth/me 返回前不显示注册入口(防止未配置 SMTP 的窗口期误开)
var registerCredits = 3; // F-6.1:注册赠送次数由 /auth/me 下发,文案不再硬编码
var smtpOk = false; // fail-closed——/auth/me 返回前不显示注册入口(防止未配置 SMTP 的窗口期误开)
var registerCredits = 3; // 注册赠送次数由 /auth/me 下发,文案不再硬编码
/* ---------------- 弹窗 ---------------- */
function openModal(id) { var m = $('#' + id); if (m) m.classList.add('show'); }
@@ -129,9 +129,9 @@ function getDeviceId() {
['goto-register', 'goto-register2'].forEach(function (id) {
var a = $('#' + id); if (a) a.hidden = !showRegister;
});
// F-8.1:两个弹窗(登录/注册)里的试用提示都要受管——原 querySelector 只命中第一个
// 两个弹窗(登录/注册)里的试用提示都要受管——原 querySelector 只命中第一个
$$('.auth-trial-box').forEach(function (box) { box.hidden = !showRegister; });
$$('.reg-gift-num').forEach(function (el) { el.textContent = registerCredits; }); // F-6.1
$$('.reg-gift-num').forEach(function (el) { el.textContent = registerCredits; });
var guest = $('#auth-guest-area'), uarea = $('#auth-user-area');
if (guest && uarea) {
if (user) {
@@ -163,12 +163,12 @@ function getDeviceId() {
durationText = d.duration_text || '';
ann = d.announcement || null;
smtpOk = !!d.smtp_configured;
// F-6.1:注册赠送次数动态下发(/auth/me),文案不再硬编码
// 注册赠送次数动态下发(/auth/me),文案不再硬编码
if (typeof d.register_credits === 'number') registerCredits = d.register_credits;
renderAuthArea();
renderUserCenter();
maybeShowAnnouncement();
// A1:确认登录后再恢复下载任务(游客调任务接口会 401 误弹登录框)
// 确认登录后再恢复下载任务(游客调任务接口会 401 误弹登录框)
if (user && typeof window.SITE_TASKS_BOOTSTRAP === 'function') {
try { window.SITE_TASKS_BOOTSTRAP(); } catch (e) {}
}
@@ -190,14 +190,14 @@ function getDeviceId() {
showExpiredModal();
return;
}
// F53:会话失效时清掉内存里的旧用户状态再引导登录
// 会话失效时清掉内存里的旧用户状态再引导登录
//(原实现保留旧 user,页面继续显示"已登录"却处处 401)
user = null;
durationText = '';
renderAuthArea();
renderUserCenter();
authToastOnce(msg || '登录已失效,请重新登录');
// 第 87 条:登录弹窗已打开且正处于找回/两步验证视图时,不复位视图——
// 登录弹窗已打开且正处于找回/两步验证视图时,不复位视图——
// 未登录期间后台任务轮询/心跳会持续吃 401 反复进入本函数,若每次都把
// 弹窗拽回登录视图,管理员正在输入的验证码会被打断、票据被清空
var _lm = document.getElementById('login-modal');
@@ -209,7 +209,7 @@ function getDeviceId() {
hideForgotView();
hideAdmin2faView();
}
// 第 98 条:会话失效弹登录窗前,先关掉可能叠开的注册弹窗(双遮罩错乱),
// 会话失效弹登录窗前,先关掉可能叠开的注册弹窗(双遮罩错乱),
// 已 show 则短路——401 风暴(1.5s 轮询)期间不反复重开
var _reg = document.getElementById('register-modal');
if (_reg && _reg.classList.contains('show')) closeModal('register-modal');
@@ -265,14 +265,14 @@ function getDeviceId() {
durationText = d.duration_text || '';
renderAuthArea();
renderUserCenter();
// A1:登录成功后立即恢复本设备的进行中任务
// 登录成功后立即恢复本设备的进行中任务
if (user && typeof window.SITE_TASKS_BOOTSTRAP === 'function') {
try { window.SITE_TASKS_BOOTSTRAP(); } catch (e) {}
}
}
/* ---------------- 登录 / 注册弹窗 ---------------- */
// 第 81 条:注册唯一入口——导航按钮与登录弹窗两处链接(全站仅有的 3 条打开
// 注册唯一入口——导航按钮与登录弹窗两处链接(全站仅有的 3 条打开
// 路径)统一经此:未同意协议 → cookieGate('register') 弹「隐私与协议设置」
// 注册模式(16 秒强制阅读计时)并中止;已同意 → 直接进注册弹窗。
// 提交侧 doRegister 另有 fail-closed 兜底闸,程序化打开也绕不过。
@@ -294,7 +294,7 @@ function getDeviceId() {
['login-close', 'register-close', 'announce-close'].forEach(function (id) {
var b = $('#' + id);
if (b) b.addEventListener('click', function () {
if (id.indexOf('login') === 0 && window.resetAuthViews) window.resetAuthViews(); // 第 87 条:关闭侧复位
if (id.indexOf('login') === 0 && window.resetAuthViews) window.resetAuthViews(); // 关闭侧复位
closeModal(id.indexOf('login') === 0 ? 'login-modal' : id.indexOf('register') === 0 ? 'register-modal' : 'announce-modal');
});
});
@@ -302,7 +302,7 @@ function getDeviceId() {
var m = $('#' + p[0]);
if (m) m.addEventListener('click', function (e) {
if (e.target === m) {
if (p[0] === 'login-modal' && window.resetAuthViews) window.resetAuthViews(); // 第 87 条:关闭侧复位
if (p[0] === 'login-modal' && window.resetAuthViews) window.resetAuthViews(); // 关闭侧复位
closeModal(p[0]);
}
});
@@ -312,12 +312,12 @@ function getDeviceId() {
if (g2) g2.addEventListener('click', function () { closeModal('login-modal'); openRegisterFlow(); });
var gl1 = $('#goto-login');
if (gl1) gl1.addEventListener('click', function () { closeModal('register-modal'); hideForgotView(); hideAdmin2faView(); openModal('login-modal'); });
// 第 86 条:找回密码入口/返回(F5.1 只交付 DOM 漏了绑定)
// 找回密码入口/返回(只交付 DOM 漏了绑定)
var gf = $('#goto-forgot');
if (gf) gf.addEventListener('click', showForgotView);
var fbl = $('#forgot-back-login');
if (fbl) fbl.addEventListener('click', hideForgotView);
// 第 86 条:管理员两步验证视图的返回链接
// 管理员两步验证视图的返回链接
var a2b = $('#a2fa-back');
if (a2b) a2b.addEventListener('click', hideAdmin2faView);
@@ -334,7 +334,7 @@ function getDeviceId() {
api('/auth/login', { username: username, password: password })
.then(function (data) {
if (data.status !== 200) throw new Error(data.message || '登录失败');
// 第 86 条:管理员密码验证通过——转两步验证视图(服务端未建会话)
// 管理员密码验证通过——转两步验证视图(服务端未建会话)
if (data.data && data.data.admin_2fa) {
showAdmin2faView(data.data.ticket, data.data.email_masked);
toast('密码验证通过,请完成邮箱验证', 'success');
@@ -358,7 +358,7 @@ function getDeviceId() {
/* ---------------- 注册 ---------------- */
var _REG_EMAIL_RE = /^[\w.%+-]+@[\w.-]+\.[A-Za-z]{2,}$/;
var _REG_NAME_RE = /^[A-Za-z0-9_\u4e00-\u9fa5]{3,20}$/;
// F5.1:注册弹窗内常驻错误行(长文案 2.6s toast 看不完)
// 注册弹窗内常驻错误行(长文案 2.6s toast 看不完)
function regShowError(msg) {
toast(msg, 'error');
var line = $('#reg-error-line');
@@ -391,8 +391,8 @@ function getDeviceId() {
}
/* ---------------- 找回密码 ---------------- */
// 第 84 条:F5.1 只交付了找回视图 DOM 未实现 JS——此处补齐全链路。
// 后端 /auth/forgot_password(第 79 条)已就绪:验证码原子消费、防枚举
// 只交付了找回视图 DOM 未实现 JS——此处补齐全链路。
// 后端 /auth/forgot_password 已就绪:验证码原子消费、防枚举
// 统一文案、login_seq 踢全会话、5 次/分/IP 限流;发码复用 /auth/send_code。
function forgotShowError(msg) {
toast(msg, 'error');
@@ -470,7 +470,7 @@ function getDeviceId() {
.finally(function () { btn.disabled = false; btn.textContent = '重置密码'; });
}
/* ---------------- 管理员两步验证(第 86 条) ----------------
/* ---------------- 管理员两步验证 ----------------
* Step1 用户名+密码 → 服务端签发票据(不建会话);本视图完成
* Step2 绑定邮箱比对(触发发码)与 Step3 验证码登录。普通用户不经过此流程。 */
var _a2faTicket = '';
@@ -495,7 +495,7 @@ function getDeviceId() {
_a2faTicket = ticket || '';
var mask = $('#a2fa-email-mask');
if (mask) mask.textContent = emailMask || '-';
// 第 87 条:清掉上次会话残留的输入(错码/旧邮箱不再被原样重复提交)
// 清掉上次会话残留的输入(错码/旧邮箱不再被原样重复提交)
var ae = $('#a2fa-email'), ac = $('#a2fa-code');
if (ae) ae.value = '';
if (ac) ac.value = '';
@@ -515,7 +515,7 @@ function getDeviceId() {
a2faClearError();
a2faStopTimer();
}
// 第 87 条:关闭侧统一复位——登录弹窗的 X/遮罩/Escape/doLogout 关闭路径都调用,
// 关闭侧统一复位——登录弹窗的 X/遮罩/Escape/doLogout 关闭路径都调用,
// 不再只依赖"打开侧纪律";同时供 index.html 的 Escape 通用分支使用
window.resetAuthViews = function () { hideForgotView(); hideAdmin2faView(); };
function doA2faSendCode() {
@@ -527,7 +527,7 @@ function getDeviceId() {
api('/auth/admin_login/email', { ticket: _a2faTicket, email: email })
.then(function (data) {
if (data.status === 401) {
// 第 87 条:先复位视图再提示——错误行随视图隐藏,此处 toast 是唯一可达提示
// 先复位视图再提示——错误行随视图隐藏,此处 toast 是唯一可达提示
hideAdmin2faView();
a2faShowError(data.message || '请重新登录');
return;
@@ -558,7 +558,7 @@ function getDeviceId() {
api('/auth/admin_login/verify', { ticket: _a2faTicket, code: code })
.then(function (data) {
if (data.status === 401) {
// 第 87 条:先复位视图再提示(同上,错误行随视图隐藏)
// 先复位视图再提示(同上,错误行随视图隐藏)
hideAdmin2faView();
a2faShowError(data.message || '请重新登录');
return;
@@ -582,7 +582,7 @@ function getDeviceId() {
var email = ($('#reg-email') || {}).value || '';
var code = ($('#reg-code') || {}).value || '';
regClearError();
// 第 81 条:提交侧兜底闸(入口已统一走 openRegisterFlow,此处防任何程序化
// 提交侧兜底闸(入口已统一走 openRegisterFlow,此处防任何程序化
// 打开路径绕过)。fail-closed:协议闸缺失视为未同意。注册语境只查协议同意
// (功能性 Cookie 不拦注册,拦解析/下载时另有提示);未同意时闸内已自动
// 弹「隐私与协议设置」注册模式并 toast,此处不再重复提示
@@ -591,7 +591,7 @@ function getDeviceId() {
return;
}
if (!window.SITE_COOKIE_GATE('register')) return;
// F1.1:前端即时校验(口径与后端一致,省一次网络往返)
// 前端即时校验(口径与后端一致,省一次网络往返)
if (!username || !password || !email || !code) { regShowError('请填写完整注册信息'); return; }
if (!_REG_NAME_RE.test(username)) { regShowError('用户名需 3-20 位,仅支持中文、字母、数字、下划线,且不能纯下划线'); return; }
if (!_REG_EMAIL_RE.test(email.trim())) { regShowError('邮箱格式不正确'); return; }
@@ -619,10 +619,10 @@ function getDeviceId() {
api('/auth/logout', {}).then(function () {
user = null; durationText = '';
renderAuthArea();
renderUserCenter(); // 第 83 条:同步个人中心 gate/content 显隐,与其他登出路径一致
if (window.resetAuthViews) window.resetAuthViews(); // 第 87 条:登出复位登录弹窗视图
renderUserCenter(); // 同步个人中心 gate/content 显隐,与其他登出路径一致
if (window.resetAuthViews) window.resetAuthViews(); // 登出复位登录弹窗视图
closeModal('register-modal'); closeModal('login-modal');
// 第 102 条(U15 拍板):退出连带清除个人网易云登录(index.html 注册的钩子:
// 退出连带清除个人网易云登录(index.html 注册的钩子:
// 清 localStorage 的个人 Cookie/解析账号选择并复位账号栏)
if (typeof window.SITE_ON_LOGOUT === 'function') {
try { window.SITE_ON_LOGOUT(); } catch (e) {}
@@ -680,7 +680,7 @@ function getDeviceId() {
.then(function (data) {
if (data.status !== 200) throw new Error(data.message || '兑换失败');
durationText = (data.data && data.data.duration_text) || durationText;
// F69:同步更新内存余额(批量下载预检读的是 user.duration_seconds,
// 同步更新内存余额(批量下载预检读的是 user.duration_seconds,
// 原实现只改显示文字,预检仍用充值前的旧值)
if (user && data.data && typeof data.data.duration_seconds === 'number') {
user.duration_seconds = data.data.duration_seconds;
@@ -700,7 +700,7 @@ function getDeviceId() {
/* ---------------- 公告 ---------------- */
function maybeShowAnnouncement(force) {
// F81:手动"查看公告"(force=true)无条件展示;
// 手动"查看公告"(force=true)无条件展示;
// 自动弹出仍按 popup_mode 与 once 已读版本判断
if (!force && (!ann || !ann.content || ann.popup_mode === 'off')) return;
if (!force && ann && ann.popup_mode === 'once') {
@@ -717,7 +717,7 @@ function getDeviceId() {
var t = $('#announce-title'), c = $('#announce-content');
if (t) t.textContent = ann.title || '系统公告';
if (c) c.innerHTML = String(ann.content).split('\n').map(function (l) { return escapeHtml(l); }).join('<br>');
// 第 98 条:首访 Cookie 协议窗显示中不叠加公告弹窗(双遮罩错乱);
// 首访 Cookie 协议窗显示中不叠加公告弹窗(双遮罩错乱);
// 跳过时不记已读,下次进入页面再弹
var _cm = document.getElementById('cookie-modal');
if (_cm && _cm.classList.contains('show')) return;
@@ -747,7 +747,7 @@ function getDeviceId() {
function uaBindSide() {
$$('#admin-side .admin-side-item[data-view]').forEach(function (item) {
// 第 97 条(P3):侧栏项补键盘语义(F109 同型——tabindex/role + Enter/空格激活)
// 侧栏项补键盘语义(同型——tabindex/role + Enter/空格激活)
item.setAttribute('tabindex', '0');
item.setAttribute('role', 'button');
item.addEventListener('click', function () {
@@ -767,7 +767,7 @@ function getDeviceId() {
});
var lo = $('#ua-logout');
if (lo) lo.addEventListener('click', doLogout);
// 第 97 条(P3):返回/退出两个无 data-view 的侧栏项同样补键盘语义
// 返回/退出两个无 data-view 的侧栏项同样补键盘语义
[back, lo].forEach(function (el) {
if (!el) return;
el.setAttribute('tabindex', '0');
@@ -776,7 +776,7 @@ function getDeviceId() {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); el.click(); }
});
});
/* ---- 播放 API 密钥(第 78 条) ---- */
/* ---- 播放 API 密钥 ---- */
var pkShow = $('#ua-playkey-show');
var pkInput = $('#ua-playkey-value');
function pkCopyToClipboard(text) {
@@ -891,12 +891,12 @@ function getDeviceId() {
var isAdmin = u.role === 'admin';
var disabled = (u.status === 0);
var banned = (u.status === 2);
// 第 97 条(P3):数值字段同过转义(纵深防御——后端口径若变为字符串
// 数值字段同过转义(纵深防御——后端口径若变为字符串
// 不再形成管理后台存储型 XSS 面,与同表 username/email 口径对齐)
var uid = escapeHtml(String(u.id));
var dur = escapeHtml(String(u.duration_seconds));
var cred = u.duration_seconds < 0 ? '不限' : (dur + ' 次');
var dev = [u.register_ip, u.trial_device].filter(Boolean).join(' · ') || '-'; // R5-79:IP 优先显示
var dev = [u.register_ip, u.trial_device].filter(Boolean).join(' · ') || '-'; // IP 优先显示
if (u.last_province) dev += ' · ' + u.last_province;
return '<tr' + ((disabled || banned) ? ' style="opacity:.55;"' : '') + '>' +
'<td>' + uid + '</td>' +
@@ -925,7 +925,7 @@ function getDeviceId() {
.then(function (d) { toast(d.message || (d.status === 200 ? '已修改' : '修改失败'), d.status === 200 ? 'success' : 'error'); if (d.status === 200) uaLoadUsers(); });
});
});
// T3:管理员自助改密(需验证旧密码;改完其他设备全部下线)
// 管理员自助改密(需验证旧密码;改完其他设备全部下线)
$$('[data-uself]', body).forEach(function (b) {
b.addEventListener('click', function () {
var oldpwd = prompt('请输入当前密码:', '');
@@ -990,7 +990,7 @@ function getDeviceId() {
}
/* ---- 全局 Cookie ---- */
var uaQrTimer = null, uaUnikey = '', uaQrGen = 0; // T2:轮询代次令牌(删除 Cookie 后作废旧轮询)
var uaQrTimer = null, uaUnikey = '', uaQrGen = 0; // 轮询代次令牌(删除 Cookie 后作废旧轮询)
function uaLoadCookieStatus() {
api('/admin/cookie/status', {}).then(function (data) {
var el = $('#ua-cookie-status');
@@ -1013,13 +1013,13 @@ function getDeviceId() {
}
if (tip) tip.textContent = '请使用网易云音乐手机 APP 扫码';
if (uaQrTimer) clearInterval(uaQrTimer);
uaQrGen++; // T2:新轮询代次
uaQrGen++; // 新轮询代次
uaQrTimer = setInterval(uaQrPoll, 3000);
});
}
function uaQrPoll() {
if (!uaUnikey) { if (uaQrTimer) clearInterval(uaQrTimer); return; }
var myGen = uaQrGen; // T2:本次轮询所属代次
var myGen = uaQrGen; // 本次轮询所属代次
api('/admin/qr/check', { unikey: uaUnikey }).then(function (data) {
if (myGen !== uaQrGen) return; // 期间已删除 Cookie/重开扫码,丢弃迟到响应
var tip = $('#ua-qr-tip');
@@ -1045,7 +1045,7 @@ function getDeviceId() {
function uaCookieDelete() {
if (!window.confirm('确定删除全局 Cookie?删除后需重新扫码登录。')) return;
api('/admin/cookie/delete', {}).then(function (data) {
// T2:作废在途扫码轮询——迟到的 803 成功响应不得把刚删除的 Cookie 写回
// 作废在途扫码轮询——迟到的 803 成功响应不得把刚删除的 Cookie 写回
if (uaQrTimer) clearInterval(uaQrTimer);
uaUnikey = '';
uaQrGen++;
@@ -1099,7 +1099,7 @@ function getDeviceId() {
var total = (data.data && data.data.total) || 0;
var pages = (data.data && data.data.pages) || 1;
uaLogPage = (data.data && data.data.page) || 1;
window._uaLogPages = pages; // P3-43:next 按钮按真实页数封顶
window._uaLogPages = pages; // next 按钮按真实页数封顶
var el;
if ((el = $('#ua-log-page'))) el.textContent = uaLogPage;
if ((el = $('#ua-log-pages'))) el.textContent = pages;
@@ -1147,7 +1147,7 @@ function getDeviceId() {
uaLoadCards(0);
});
}
// 第 81 条:卡密管理操作列(禁用/启用/删除)+ 分页(F85 后端已支持,
// 卡密管理操作列(禁用/启用/删除)+ 分页(后端已支持,
// 不分页则超出最新 100 张的旧卡密在界面上永远管理不到)
var _uaCardPage = 0;
var _uaCardTotal = 0;
@@ -1312,7 +1312,7 @@ function getDeviceId() {
uaLoadDb();
})
.catch(function (err) {
// 第 97 条(P2):全站唯一绕过统一请求封装的调用此前无失败兜底——
// 全站唯一绕过统一请求封装的调用此前无失败兜底——
// 网络失败/非 JSON 响应时管理员毫无感知还制造 unhandledrejection 噪音
toast((err && err.message) || '备份上传失败,请检查网络后重试', 'error');
});
@@ -1323,7 +1323,7 @@ function getDeviceId() {
bindAuthModal();
var s1 = $('#login-submit'); if (s1) s1.addEventListener('click', doLogin);
var s2 = $('#login-password'); if (s2) s2.addEventListener('keydown', function (e) { if (e.key === 'Enter') doLogin(); });
// F-3.1:回车提交覆盖全部输入框(登录用户名框 / 注册四框)
// 回车提交覆盖全部输入框(登录用户名框 / 注册四框)
var s2u = $('#login-username'); if (s2u) s2u.addEventListener('keydown', function (e) { if (e.key === 'Enter') doLogin(); });
var s3 = $('#reg-submit'); if (s3) s3.addEventListener('click', doRegister);
var s4 = $('#reg-send-code'); if (s4) s4.addEventListener('click', sendCode);
@@ -1331,7 +1331,7 @@ function getDeviceId() {
var el = $(sel);
if (el) el.addEventListener('keydown', function (e) { if (e.key === 'Enter') doRegister(); });
});
// F-4.1:密码明文切换(登录/注册/找回)
// 密码明文切换(登录/注册/找回)
[['#login-pwd-eye', '#login-password'], ['#reg-pwd-eye', '#reg-password'], ['#forgot-pwd-eye', '#forgot-password']].forEach(function (pair) {
var eye = $(pair[0]), input = $(pair[1]);
if (!eye || !input) return;
@@ -1341,7 +1341,7 @@ function getDeviceId() {
eye.textContent = show ? '隐藏' : '显示';
});
});
// F-2.1:发码后修改邮箱 → 提前终止倒计时并恢复按钮,提示需重发
// 发码后修改邮箱 → 提前终止倒计时并恢复按钮,提示需重发
var regEmail = $('#reg-email');
if (regEmail) regEmail.addEventListener('input', function () {
var btn = $('#reg-send-code');
@@ -1351,7 +1351,7 @@ function getDeviceId() {
toast('邮箱已修改,请重新发送验证码', 'info');
}
});
// 第 84 条:找回视图——发码/提交/回车/改邮箱终止倒计时(对齐注册交互)
// 找回视图——发码/提交/回车/改邮箱终止倒计时(对齐注册交互)
var fsc = $('#forgot-send-code'); if (fsc) fsc.addEventListener('click', doForgotSendCode);
var fsub = $('#forgot-submit'); if (fsub) fsub.addEventListener('click', doForgotReset);
['#forgot-email', '#forgot-code', '#forgot-password'].forEach(function (sel) {
@@ -1367,7 +1367,7 @@ function getDeviceId() {
toast('邮箱已修改,请重新发送验证码', 'info');
}
});
// 第 86 条:管理员两步验证——发码/验证提交/回车提交
// 管理员两步验证——发码/验证提交/回车提交
var asc = $('#a2fa-send'); if (asc) asc.addEventListener('click', doA2faSendCode);
var asb = $('#a2fa-submit'); if (asb) asb.addEventListener('click', doA2faVerify);
['#a2fa-email', '#a2fa-code'].forEach(function (sel) {
@@ -1386,7 +1386,7 @@ function getDeviceId() {
else { var t = $('#tab-usercenter'); if (t) t.click(); }
});
var ucRedeem = $('#uc-card-redeem'); if (ucRedeem) ucRedeem.addEventListener('click', doRedeem);
var ucAnn = $('#uc-ann-view'); if (ucAnn) ucAnn.addEventListener('click', function () { maybeShowAnnouncement(true); }); // F81:手动查看强制展示
var ucAnn = $('#uc-ann-view'); if (ucAnn) ucAnn.addEventListener('click', function () { maybeShowAnnouncement(true); }); // 手动查看强制展示
var ucLogout = $('#uc-logout'); if (ucLogout) ucLogout.addEventListener('click', doLogout);
uaBindSide();
@@ -1400,7 +1400,7 @@ function getDeviceId() {
var b8 = $('#ua-log-tab-ok'); if (b8) b8.addEventListener('click', function () { uaLogsType = 'success'; uaLogPage = 1; uaLoadLogs(); });
var b9 = $('#ua-log-tab-fail'); if (b9) b9.addEventListener('click', function () { uaLogsType = 'fail'; uaLogPage = 1; uaLoadLogs(); });
var blogPrev = $('#ua-log-prev'); if (blogPrev) blogPrev.addEventListener('click', function () { if (uaLogPage > 1) { uaLogPage--; uaLoadLogs(); } });
// P3-43:next 补边界(原实现可翻到空页——前端无 pages 时放宽为 999 防呆)
// next 补边界(原实现可翻到空页——前端无 pages 时放宽为 999 防呆)
var blogNext = $('#ua-log-next'); if (blogNext) blogNext.addEventListener('click', function () { var maxP = window._uaLogPages || 999; if (uaLogPage < maxP) { uaLogPage++; uaLoadLogs(); } });
var blogClear = $('#ua-log-clear'); if (blogClear) blogClear.addEventListener('click', uaClearLogs);
var b10 = $('#ua-card-gen'); if (b10) b10.addEventListener('click', uaGenCards);