Files
jiapu/public/js/profile-pages.js
T
2026-08-29 19:06:07 +08:00

328 lines
9.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(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() {
if (!root.location) return;
if (typeof root.location.replace === 'function') {
root.location.replace('login.html');
return;
}
root.location.href = '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;
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
};
});