(function (root, factory) { // 个人资料模块同时支持浏览器页面和 Node 测试。 if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } root.ProfilePages = factory(root); if (root.document) { root.document.addEventListener('DOMContentLoaded', function () { root.ProfilePages.init(); }); } })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; var documentRef = root.document; var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null); function trimOrUndefined(value) { var text = value === undefined || value === null ? '' : String(value).trim(); return text || undefined; } function profileValue(profile, key, fallback) { var value = profile && profile[key]; return value === undefined || value === null || value === '' ? fallback : value; } function getAvatarText(profile) { // 头像字只取展示名第一个字符,避免接口没有头像时页面空白。 var displayName = profileValue(profile, 'nickName', '家'); return String(displayName).charAt(0) || '家'; } function getSexText(value) { var labels = { '0': '男', '1': '女', '2': '未知' }; return labels[String(value)] || '待填写'; } function buildProfileView(profile) { var avatarFile = MediaDisplay && MediaDisplay.normalizeFileAccess(profile && profile.avatarFile); var view; // 页面展示模型和接口返回分开,后续改文案不影响接口契约。 view = { displayName: profileValue(profile, 'nickName', '未设置昵称'), realName: profileValue(profile, 'realName', '待填写'), avatarText: getAvatarText(profile), avatarStatus: avatarFile ? '已设置' : '未设置', phone: profileValue(profile, 'phone', '未绑定'), sex: getSexText(profile && profile.sex), birthday: profileValue(profile, 'birthday', '待填写'), email: profileValue(profile, 'email', '待填写'), regionText: '当前资料接口未提供' }; if (avatarFile) view.avatarFile = avatarFile; return view; } function buildProfileUpdateBody(values) { // 字段名称严格对应 PC YAML 里的 ProfileUpdateBody。 var source = values || {}; var body = {}; var avatar = trimOrUndefined(source.avatar); [ 'nickName', 'realName', 'sex', 'birthday', 'email' ].forEach(function (field) { var value = trimOrUndefined(source[field]); if (value !== undefined) body[field] = value; }); if (avatar !== undefined) { if (!/^\d+$/.test(avatar)) throw new Error('头像 OSS ID 必须是整数'); // YAML 将 avatar 标成 int64,但其示例已超过 JS 安全整数;浏览器按十进制字符串保留精度。 body.avatar = avatar; } return body; } function validateProfileUpdateBody(body) { if (!body || Object.keys(body).length === 0) return '请至少填写一项资料'; if (body.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(body.email)) return '请输入正确的邮箱地址'; return ''; } function getApi() { return root.GenealogyApi && root.GenealogyApi.defaultClient; } function shouldRedirectToLogin(api, error) { var status = error && (error.status || error.code); return !api || !api.getToken || !api.getToken() || Number(status) === 401; } function redirectToLogin() { root.NavigationUtil.open('login.html'); } function redirectUnauthorized(api, error) { if (!shouldRedirectToLogin(api, error)) return false; if (api && api.clearToken) api.clearToken(); redirectToLogin(); return true; } function query(selector, rootNode) { if (!documentRef) return null; return (rootNode || documentRef).querySelector(selector); } function queryAll(selector, rootNode) { if (!documentRef) return []; return Array.prototype.slice.call((rootNode || documentRef).querySelectorAll(selector)); } function showMessage(message) { if (root.layui && root.layui.layer) { root.layui.layer.msg(message); return; } if (root.alert) { root.alert(message); } } function setStatus(form, type, message) { var statusNode = query('[data-profile-status]', form); if (!statusNode) return; if (root.ProfileUI && root.ProfileUI.setApiState && type) { root.ProfileUI.setApiState(statusNode, type, message); return; } statusNode.textContent = message || ''; } function setSubmitting(form, isSubmitting) { queryAll('button[type="submit"]', form).forEach(function (button) { if (!button.dataset.defaultLabel) button.dataset.defaultLabel = button.textContent; button.disabled = isSubmitting; button.textContent = isSubmitting ? '保存中…' : button.dataset.defaultLabel; }); } function renderProfile(view) { // 所有资料展示点用 data-profile-text 标记,避免按中文文案查找节点。 queryAll('[data-profile-text]').forEach(function (node) { var key = node.getAttribute('data-profile-text'); var value = view[key]; if (key === 'avatarText' && view.avatarFile) return; if (value !== undefined && value !== null && value !== '') { node.textContent = value; } }); queryAll('[data-profile-avatar]').forEach(function (node) { if (!MediaDisplay) { node.textContent = view.avatarText; return; } node.innerHTML = MediaDisplay.renderAvatar(view.avatarFile, { name: view.displayName, className: 'profile-avatar-media' }); }); } function setProfileLoadStatus(type, message) { var statusNode = query('[data-profile-load-status]'); if (!statusNode) return; if (root.ProfileUI && root.ProfileUI.setApiState && type) { root.ProfileUI.setApiState(statusNode, type, message); return; } statusNode.textContent = message || ''; } function buildUnavailableProfileView() { return { displayName: '账户资料暂时无法读取', realName: '暂无数据', avatarText: '!', avatarStatus: '暂无数据', phone: '暂无数据', birthday: '暂无数据', email: '暂无数据', sex: '暂无数据', regionText: '暂无数据' }; } function getFormValues(form) { var values = {}; queryAll('[name]', form).forEach(function (field) { values[field.name] = field.value; }); return values; } function fillProfileForm(profile) { var form = query('[data-profile-form]'); var source = profile || {}; if (!form) return; queryAll('[name]', form).forEach(function (field) { if (source[field.name] !== undefined && source[field.name] !== null) { field.value = source[field.name]; } }); } var latestProfileRequestId = 0; async function loadProfile() { var api = getApi(); var profile; var requestId = latestProfileRequestId + 1; latestProfileRequestId = requestId; if (!api || !documentRef || !documentRef.querySelector('[data-profile-page]')) return; if (redirectUnauthorized(api)) return; setProfileLoadStatus('loading', '正在读取账户资料…'); try { profile = await api.currentProfile(); if (requestId !== latestProfileRequestId) return; if (root.PublicNavigation) root.PublicNavigation.storeUser(profile); renderProfile(buildProfileView(profile)); fillProfileForm(profile); setProfileLoadStatus('', ''); } catch (error) { if (redirectUnauthorized(api, error)) return; renderProfile(buildUnavailableProfileView()); setProfileLoadStatus('error', '资料读取失败,请刷新页面后重试。'); showMessage(error.message || '个人资料加载失败'); } } async function submitProfileForm(form) { var api = getApi(); var body; var validation; if (!api || !form) return; if (redirectUnauthorized(api)) return; try { body = buildProfileUpdateBody(getFormValues(form)); } catch (error) { setStatus(form, 'error', error.message || '资料校验失败'); return; } validation = validateProfileUpdateBody(body); if (validation) { setStatus(form, 'error', validation); return; } try { setSubmitting(form, true); setStatus(form, 'loading', '正在保存资料…'); await api.updateProfile(body); setStatus(form, '', '已保存'); showMessage('个人资料已保存'); await loadProfile(); } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus(form, 'error', '保存失败,请稍后重试。'); showMessage(error.message || '个人资料保存失败'); } finally { setSubmitting(form, false); } } function bindProfileForm() { var form = query('[data-profile-form]'); if (!form) return; form.addEventListener('submit', function (event) { event.preventDefault(); if (!form.checkValidity()) { setStatus(form, 'error', '请检查资料填写是否正确'); form.reportValidity(); return; } submitProfileForm(form); }); } function init() { bindProfileForm(); loadProfile(); } return { getAvatarText: getAvatarText, buildProfileView: buildProfileView, buildUnavailableProfileView: buildUnavailableProfileView, buildProfileUpdateBody: buildProfileUpdateBody, validateProfileUpdateBody: validateProfileUpdateBody, shouldRedirectToLogin: shouldRedirectToLogin, reloadProfile: loadProfile, init: init }; });