(function (root, factory) { // 认证页脚本同时给浏览器页面和 Node 测试使用。 if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } root.AuthPages = factory(root); if (root.document) { root.document.addEventListener('DOMContentLoaded', function () { root.AuthPages.init(); }); } })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; var documentRef = root.document; function hashPassword(value) { // 接口文档要求密码传 32 位 MD5;没有加载 md5.js 时保留原值,便于测试定位。 if (root.hex_md5) return root.hex_md5(value); if (root.md5) return root.md5(value); return value; } function buildLoginBody(values, hashFn) { // 这里只组装页面字段,grantType、tenantId、clientId 由 api-client 统一补齐。 var hash = hashFn || hashPassword; var body = { phone: values.phone, password: hash(values.password) }; if (values.validToken) body.validToken = values.validToken; return body; } function buildSmsLoginBody(values) { // 短信登录接口只需要页面字段,grantType、tenantId、clientId 由 api-client 统一补齐。 var body = { phone: values.phone, smsCode: values.smsCode }; if (values.validToken) body.validToken = values.validToken; return body; } function buildRegisterBody(values, hashFn) { // 注册验证码当前对应 validToken,可选字段只在有值时提交。 var hash = hashFn || hashPassword; var body = { phone: values.phone, password: hash(values.password), nickName: values.nickName || values.phone }; if (values.validToken) body.validToken = values.validToken; return body; } function buildPasswordResetBody(values, hashFn) { // 找回密码接口使用 newPassword 字段,页面确认密码只在提交前校验,不传给后端。 var hash = hashFn || hashPassword; return { phone: values.phone, smsCode: values.smsCode, newPassword: hash(values.newPassword), validToken: values.validToken || '' }; } function getApi() { return root.GenealogyApi && root.GenealogyApi.defaultClient; } function getCaptchaScene(formType) { // PC/H5 认证场景来自后台验证码配置,客户端 Key 为 web_pc。 var map = { login: 'WEB_H5_LOGIN', register: 'WEB_H5_REGISTER', 'password-reset': 'WEB_H5_FORGOT_PASSWORD' }; return map[formType] || ''; } function getFormCaptchaScene(form, formType) { // 页面可通过 data-captcha-scene 显式声明后台配置的验证码场景。 var sceneCode = form && form.getAttribute && form.getAttribute('data-captcha-scene'); return sceneCode || getCaptchaScene(formType); } async function ensureCaptcha(form, sceneCode, subject) { // 未配置 PC/H5 验证场景时不调用验证中心,也不伪造 validToken。 if (!sceneCode) return true; // 验证适配层不存在时不阻塞测试环境;真实页面会加载 captcha-pages.js。 if (!root.CaptchaPages || !root.CaptchaPages.ensureToken) return true; return Boolean(await root.CaptchaPages.ensureToken(form, { sceneCode: sceneCode, subject: subject })); } function query(selector, rootNode) { return (rootNode || documentRef).querySelector(selector); } function queryAll(selector, rootNode) { return Array.prototype.slice.call((rootNode || documentRef).querySelectorAll(selector)); } function readForm(form) { // 页面通过 name 属性和接口字段对齐,避免按 placeholder 文案取值。 var values = {}; queryAll('input[name], textarea[name], select[name]', form).forEach(function (field) { values[field.name] = String(field.value || '').trim(); }); return values; } function setBusy(button, busy) { // 加载状态只切 class 和 aria,不在 JS 中写样式。 if (!button) return; button.classList.toggle('is-loading', busy); if ('disabled' in button) button.disabled = busy; button.setAttribute('aria-busy', busy ? 'true' : 'false'); } function showMessage(message) { if (root.MessageUtil && root.MessageUtil.show) { root.MessageUtil.show(message); return; } if (root.console && root.console.warn) root.console.warn(message); } function requireValue(value, message) { if (value) return true; showMessage(message); return false; } function bindSubmit(selector, handler) { // 所有认证表单统一拦截 submit,避免链接跳转绕过接口请求。 var form = query(selector); if (!form) return; form.addEventListener('submit', function (event) { event.preventDefault(); handler(form, query('[type="submit"]', form)); }); } function setLoginMode(activeMode) { // 登录方式切换只更新状态,具体样式由 login.css 控制。 queryAll('[data-login-mode-tab]').forEach(function (button) { var isActive = button.getAttribute('data-login-mode-tab') === activeMode; button.classList.toggle('is-active', isActive); button.setAttribute('aria-selected', isActive ? 'true' : 'false'); }); queryAll('[data-login-mode-panel]').forEach(function (panel) { var isActive = panel.getAttribute('data-login-mode-panel') === activeMode; panel.hidden = !isActive; }); } async function submitLogin(form, button) { // 登录成功后跳转地址由 HTML 的 data-success-url 控制,页面可单独调整。 var api = getApi(); var values = readForm(form); if (!api) return; if (!requireValue(values.phone, '请填写手机号')) return; if (!requireValue(values.password, '请填写密码')) return; if (!await ensureCaptcha(form, getFormCaptchaScene(form, 'login'), values.phone)) return; values = readForm(form); setBusy(button, true); try { await api.login(buildLoginBody(values)); root.location.href = form.getAttribute('data-success-url') || 'profile.html'; } catch (error) { showMessage(error.message || '登录失败'); } finally { setBusy(button, false); } } async function submitSmsLogin(form, button) { // PC 短信登录与密码登录共用 WEB_H5_LOGIN 场景,短信码由同页按钮发送。 var api = getApi(); var values = readForm(form); if (!api) return; if (!requireValue(values.phone, '请填写手机号')) return; if (!requireValue(values.smsCode, '请填写短信验证码')) return; if (!await ensureCaptcha(form, getFormCaptchaScene(form, 'login'), values.phone)) return; values = readForm(form); setBusy(button, true); try { await api.loginBySms(buildSmsLoginBody(values)); root.location.href = form.getAttribute('data-success-url') || 'profile.html'; } catch (error) { showMessage(error.message || '短信登录失败'); } finally { setBusy(button, false); } } async function submitRegister(form, button) { // 注册成功后默认进入创建家谱流程。 var api = getApi(); var values = readForm(form); if (!api) return; if (!requireValue(values.phone, '请填写手机号')) return; if (!requireValue(values.password, '请填写密码')) return; if (!await ensureCaptcha(form, getFormCaptchaScene(form, 'register'), values.phone)) return; values = readForm(form); setBusy(button, true); try { await api.register(buildRegisterBody(values)); root.location.href = form.getAttribute('data-success-url') || 'create-genealogy.html'; } catch (error) { showMessage(error.message || '注册失败'); } finally { setBusy(button, false); } } async function sendCode(button) { // 当前只接找回密码验证码;后续注册短信可复用 data-scene-code 扩展。 var api = getApi(); var form = button.closest('form'); var values = readForm(form); var sceneCode = button.getAttribute('data-scene-code') || getFormCaptchaScene(form, 'password-reset'); if (!api) return; if (!requireValue(values.phone, '请填写手机号')) return; if (!await ensureCaptcha(form, sceneCode, values.phone)) return; values = readForm(form); setBusy(button, true); try { await api.sendSmsCode({ sceneCode: sceneCode, phone: values.phone, validToken: values.validToken || '' }); showMessage('验证码已发送'); } catch (error) { showMessage(error.message || '验证码发送失败'); } finally { setBusy(button, false); } } async function submitPasswordReset(form, button) { // 前端先校验两次密码一致,再按接口字段提交重置请求。 var api = getApi(); var values = readForm(form); if (!api) return; if (!requireValue(values.phone, '请填写手机号')) return; if (!requireValue(values.smsCode, '请填写验证码')) return; if (!requireValue(values.newPassword, '请填写新密码')) return; if (values.newPassword !== values.confirmPassword) { showMessage('两次输入的新密码不一致'); return; } if (!await ensureCaptcha(form, getFormCaptchaScene(form, 'password-reset'), values.phone)) return; values = readForm(form); setBusy(button, true); try { await api.resetPassword(buildPasswordResetBody(values)); showMessage('密码已重设,请重新登录'); root.location.href = form.getAttribute('data-success-url') || 'login.html'; } catch (error) { showMessage(error.message || '重设密码失败'); } finally { setBusy(button, false); } } function bindCodeButtons() { queryAll('[data-api-send-code]').forEach(function (button) { button.addEventListener('click', function (event) { event.preventDefault(); sendCode(button); }); }); } function bindLoginModeTabs() { queryAll('[data-login-mode-tab]').forEach(function (button) { button.addEventListener('click', function () { setLoginMode(button.getAttribute('data-login-mode-tab')); }); }); } function init() { // 每个页面只会命中自己的 data-auth-form,不需要拆多个入口文件。 if (!documentRef) return; bindSubmit('[data-auth-form="login"]', submitLogin); bindSubmit('[data-auth-form="sms-login"]', submitSmsLogin); bindSubmit('[data-auth-form="register"]', submitRegister); bindSubmit('[data-auth-form="password-reset"]', submitPasswordReset); bindLoginModeTabs(); bindCodeButtons(); } return { buildLoginBody: buildLoginBody, buildSmsLoginBody: buildSmsLoginBody, buildRegisterBody: buildRegisterBody, buildPasswordResetBody: buildPasswordResetBody, getCaptchaScene: getCaptchaScene, getFormCaptchaScene: getFormCaptchaScene, init: init }; });