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

464 lines
18 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) {
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.GenealogyEntryPages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.GenealogyEntryPages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
function normalizeTargetPage(value) {
var target = String(value || '');
var hashIndex = target.indexOf('#');
var hash = hashIndex < 0 ? '' : target.slice(hashIndex);
var parts;
var params;
if (hashIndex >= 0) target = target.slice(0, hashIndex);
parts = target.split('?');
if (!/^profile-[a-z-]+\.html$/.test(parts[0])) return 'profile-family-home.html';
params = new URLSearchParams(parts[1] || '');
params.delete('genealogyId');
return parts[0] + (params.toString() ? '?' + params.toString() : '') + hash;
}
function getTargetPage(search) {
var params = new URLSearchParams(String(search === undefined && root.location ? root.location.search : search || '').replace(/^\?/, ''));
return normalizeTargetPage(params.get('next'));
}
function buildStatus(quota) {
var source = quota || {};
var details = [];
if (Number(source.createRemaining) === -1) details.push('创建不限');
else if (Number(source.createRemaining) >= 0) details.push('还可创建 ' + source.createRemaining + ' 部');
if (Number(source.joinRemaining) === -1) details.push('加入不限');
else if (Number(source.joinRemaining) >= 0) details.push('还可加入 ' + source.joinRemaining + ' 部');
return details.length ? '家谱额度:' + details.join('') + '。' : '';
}
function escapeHtml(value) {
return String(value === undefined || value === null ? '' : value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function trimOrUndefined(value) {
var text;
if (value === undefined || value === null) return undefined;
text = String(value).trim();
return text || undefined;
}
function normalizeId(value) {
var text;
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
if (value === undefined || value === null) return '';
text = String(value).trim();
return /^[1-9][0-9]*$/.test(text) ? text : '';
}
function buildGenealogyCreateBody(values) {
var source = values || {};
var body = {
genealogyName: String(source.genealogyName || '').trim(),
surname: String(source.surname || '').trim(),
regionCode: String(source.regionCode || '').trim()
};
var optionalNames = ['ancestralHall', 'originPlace', 'addressDetail', 'intro'];
var coverOssId = normalizeId(source.coverOssId);
var visibility = trimOrUndefined(source.visibility);
var joinMode = trimOrUndefined(source.joinMode);
optionalNames.forEach(function (name) {
var value = trimOrUndefined(source[name]);
if (value !== undefined) body[name] = value;
});
if (source.coverOssId !== undefined && source.coverOssId !== null && source.coverOssId !== '') {
if (coverOssId) body.coverOssId = coverOssId;
else body.invalidCoverOssId = true;
}
if (visibility !== undefined) body.visibility = visibility;
if (joinMode !== undefined) body.joinMode = joinMode;
return body;
}
function validateGenealogyCreateBody(body) {
if (!body || !body.genealogyName) return '请填写谱名';
if (!body.surname) return '请填写姓氏';
if (!body.regionCode) return '请选择家谱所在地区';
if (body.visibility !== undefined && ['0', '1', '2'].indexOf(body.visibility) < 0) return '可见范围选项无效';
if (body.joinMode !== undefined && ['0', '1', '2'].indexOf(body.joinMode) < 0) return '加入方式选项无效';
if (body.invalidCoverOssId) return '封面文件无效,请重新选择';
return '';
}
function canCreateGenealogy(quota) {
var remaining = Number(quota && quota.createRemaining);
if (quota && typeof quota.canCreate === 'boolean') return quota.canCreate;
return Number.isInteger(remaining) && (remaining === -1 || remaining > 0);
}
function normalizeCreatedGenealogy(item) {
var genealogyId = normalizeId(item && item.genealogyId);
var genealogyName = String(item && item.genealogyName || '').trim();
var surname = String(item && item.surname || '').trim();
if (!genealogyId || !genealogyName || !surname) return null;
return {
genealogyId: genealogyId,
genealogyName: genealogyName,
surname: surname,
regionFullName: String(item && item.regionFullName || '')
};
}
function buildCreatedGenealogyUrl(item) {
var genealogy = normalizeCreatedGenealogy(item);
return genealogy ? buildEntryUrl(genealogy.genealogyId, 'profile-family-home.html') : '';
}
function shouldRedirectToLogin(api, error) {
var status = error && (error.status || error.code);
return !api || !api.getToken || !api.getToken() || Number(status) === 401;
}
function redirectToLogin(api) {
if (api && api.clearToken) api.clearToken();
if (!root.location) return;
if (typeof root.location.replace === 'function') root.location.replace('login.html');
else root.location.href = 'login.html';
}
function getFormValues(form) {
var values = {};
Array.prototype.forEach.call(form.querySelectorAll('[name]'), function (field) {
values[field.name] = field.value;
});
return values;
}
function getApiStateType(error) {
return Number(error && (error.status || error.code)) === 403 ? 'forbidden' : 'error';
}
function setCreateStatus(message, type) {
var status = root.document && root.document.querySelector('[data-genealogy-create-status]');
if (!status) return;
if (!message) {
status.innerHTML = '';
return;
}
if (root.ProfileUI && root.ProfileUI.setApiState) {
root.ProfileUI.setApiState(status, type || 'error', message);
return;
}
status.textContent = message;
}
function setEntryStatus(message, type) {
var status = root.document && root.document.querySelector('[data-genealogy-entry-status]');
if (!status) return;
if (!message) {
status.innerHTML = '';
return;
}
if (type && root.ProfileUI && root.ProfileUI.setApiState) {
root.ProfileUI.setApiState(status, type, message);
return;
}
status.textContent = message;
}
function setGenealogyListState(container, type, message) {
if (!container) return;
if (root.ProfileUI && root.ProfileUI.setApiState) {
root.ProfileUI.setApiState(container, type, message);
return;
}
container.textContent = message;
}
function setCreatePending(pending) {
var button = root.document && root.document.querySelector('[data-genealogy-create-submit]');
if (!button) return;
if (!button.dataset.defaultLabel) button.dataset.defaultLabel = button.textContent;
button.disabled = Boolean(pending);
button.textContent = pending ? '正在创建…' : button.dataset.defaultLabel;
button.setAttribute('aria-busy', pending ? 'true' : 'false');
}
async function initGenealogyCreatePage() {
var form = root.document && root.document.querySelector('[data-genealogy-create-form]');
var quotaElement = root.document && root.document.querySelector('[data-genealogy-create-quota]');
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
var quota;
var writePending = false;
if (!form) return;
if (shouldRedirectToLogin(api)) {
redirectToLogin(api);
return;
}
setCreateStatus('正在读取创建额度…', 'loading');
try {
quota = await api.genealogyQuota();
if (quotaElement) quotaElement.textContent = buildStatus(quota) || '当前额度信息不可用';
if (!canCreateGenealogy(quota)) {
setCreateStatus('当前没有可用的家谱创建额度', 'empty');
form.querySelector('[data-genealogy-create-submit]').disabled = true;
return;
}
setCreateStatus('');
} catch (error) {
if (shouldRedirectToLogin(api, error)) {
redirectToLogin(api);
return;
}
if (quotaElement) quotaElement.textContent = '家谱额度读取失败';
setCreateStatus(error.message || '无法读取家谱创建额度', getApiStateType(error));
form.querySelector('[data-genealogy-create-submit]').disabled = true;
return;
}
if (form.__genealogyCreateBound) return;
form.__genealogyCreateBound = true;
form.addEventListener('submit', async function (event) {
var body;
var validation;
var created;
var verified;
event.preventDefault();
if (writePending) return;
body = buildGenealogyCreateBody(getFormValues(form));
validation = validateGenealogyCreateBody(body);
if (validation) {
setCreateStatus(validation, 'error');
return;
}
delete body.invalidCoverOssId;
writePending = true;
setCreatePending(true);
setCreateStatus('正在创建家谱…', 'loading');
try {
created = normalizeCreatedGenealogy(await api.createGenealogy(body));
if (!created) throw new Error('创建响应缺少有效家谱信息');
verified = normalizeCreatedGenealogy(await api.genealogyDetail(created.genealogyId));
if (!verified || verified.genealogyId !== created.genealogyId) throw new Error('创建后无法读取同一家谱');
if (root.ProfileUI && root.ProfileUI.setCurrentGenealogyContext) {
root.ProfileUI.setCurrentGenealogyContext(verified.genealogyId, verified.genealogyName);
}
if (root.location) root.location.href = buildCreatedGenealogyUrl(verified);
} catch (error) {
if (shouldRedirectToLogin(api, error)) {
redirectToLogin(api);
return;
}
setCreateStatus(error.message || '创建家谱失败,请稍后重试', getApiStateType(error));
} finally {
writePending = false;
setCreatePending(false);
}
});
}
function normalizeGenealogy(item) {
var source = item || {};
var id = source.genealogyId === undefined || source.genealogyId === null
? ''
: String(source.genealogyId);
if (!/^[1-9][0-9]*$/.test(id) || !source.genealogyName) return null;
return {
genealogyId: id,
genealogyName: String(source.genealogyName),
surname: source.surname === undefined || source.surname === null ? '' : String(source.surname),
regionFullName: source.regionFullName === undefined || source.regionFullName === null ? '' : String(source.regionFullName),
memberCount: source.memberCount,
personCount: source.personCount,
roleType: source.roleType === undefined || source.roleType === null ? '' : String(source.roleType),
canManage: source.canManage === true
};
}
function buildEntryUrl(genealogyId, target) {
var normalizedTarget = normalizeTargetPage(target);
var parts = normalizedTarget.split('#');
var pathAndQuery = parts[0];
var hash = parts.length > 1 ? '#' + parts.slice(1).join('#') : '';
var pathParts = pathAndQuery.split('?');
var params = new URLSearchParams(pathParts[1] || '');
params.set('genealogyId', String(genealogyId));
return pathParts[0] + '?' + params.toString() + hash;
}
function renderGenealogies(container, data, target) {
var items = Array.isArray(data) ? data.map(normalizeGenealogy).filter(Boolean) : [];
if (!container) return items;
if (!Array.isArray(data) || (data.length && !items.length)) {
setGenealogyListState(container, 'error', '家谱响应字段不完整,请联系后端核对。');
return [];
}
if (!items.length) {
setGenealogyListState(container, 'empty', '当前账号还没有可进入的家谱');
return [];
}
container.innerHTML = items.map(function (item) {
var meta = [
item.surname ? item.surname + '氏' : '',
item.regionFullName,
item.memberCount === undefined || item.memberCount === null ? '' : item.memberCount + ' 位成员',
item.personCount === undefined || item.personCount === null ? '' : item.personCount + ' 位世系人物'
].filter(Boolean).join(' · ');
return '<a class="module-row" data-genealogy-id="' + escapeHtml(item.genealogyId) +
'" data-genealogy-name="' + escapeHtml(item.genealogyName) +
'" href="' + escapeHtml(buildEntryUrl(item.genealogyId, target)) + '">' +
'<div><h3>' + escapeHtml(item.genealogyName) + '</h3><p>' + escapeHtml(meta) +
'</p></div><span class="pill">' + (item.canManage ? '管理并进入' : '进入家谱') + '</span></a>';
}).join('');
return items;
}
async function init() {
if (root.document && root.document.querySelector('[data-genealogy-create-page]')) {
await initGenealogyCreatePage();
return;
}
var status = root.document && root.document.querySelector('[data-genealogy-entry-status]');
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
var target = getTargetPage();
var container = root.document && root.document.querySelector('[data-genealogy-list="mine"]');
var sortSelect = root.document && root.document.querySelector('[data-genealogy-sort-select]');
var sortUp = root.document && root.document.querySelector('[data-genealogy-sort-up]');
var sortDown = root.document && root.document.querySelector('[data-genealogy-sort-down]');
var sortSave = root.document && root.document.querySelector('[data-genealogy-sort-save]');
var result;
var sortPending = false;
if (!status) return;
if (shouldRedirectToLogin(api)) {
redirectToLogin(api);
return;
}
if (!api || !api.genealogyQuota || !api.genealogiesMine || !api.saveGenealogyOrder) {
setEntryStatus('家谱入口接口初始化失败,请刷新后重试。', 'error');
setGenealogyListState(container, 'error', '家谱列表接口初始化失败,请刷新后重试。');
return;
}
setGenealogyListState(container, 'loading', '正在读取我的家谱…');
setEntryStatus('正在读取家谱额度…', 'loading');
try {
result = await api.genealogiesMine();
renderGenealogies(container, result, target);
if (sortSelect) {
var sortItems = Array.isArray(result) ? result.map(normalizeGenealogy).filter(Boolean) : [];
sortSelect.innerHTML = sortItems.map(function (item) {
return '<option value="' + escapeHtml(item.genealogyId) + '">' + escapeHtml(item.genealogyName) + '</option>';
}).join('');
[sortSelect, sortUp, sortDown, sortSave].forEach(function (control) {
if (control) control.disabled = sortItems.length < 2;
});
}
} catch (error) {
if (shouldRedirectToLogin(api, error)) {
redirectToLogin(api);
return;
}
setGenealogyListState(
container,
getApiStateType(error),
getApiStateType(error) === 'forbidden' ? '当前账号无权查看我的家谱。' : '无法读取我的家谱,请稍后重试。'
);
}
function moveSelected(offset) {
var index;
var targetIndex;
var option;
if (!sortSelect || sortPending) return;
index = sortSelect.selectedIndex;
targetIndex = index + offset;
if (index < 0 || targetIndex < 0 || targetIndex >= sortSelect.options.length) return;
option = sortSelect.options[index];
if (offset < 0) sortSelect.insertBefore(option, sortSelect.options[targetIndex]);
else sortSelect.insertBefore(sortSelect.options[targetIndex], option);
sortSelect.selectedIndex = targetIndex;
}
if (sortUp) sortUp.addEventListener('click', function () { moveSelected(-1); });
if (sortDown) sortDown.addEventListener('click', function () { moveSelected(1); });
if (sortSave) sortSave.addEventListener('click', async function () {
if (sortPending || !sortSelect) return;
sortPending = true;
[sortSelect, sortUp, sortDown, sortSave].forEach(function (control) { if (control) control.disabled = true; });
setEntryStatus('正在保存家谱顺序…', 'loading');
try {
await api.saveGenealogyOrder(Array.prototype.map.call(sortSelect.options, function (option) { return option.value; }));
setEntryStatus('家谱顺序已保存');
} catch (error) {
setEntryStatus(error.message || '家谱顺序保存失败,请稍后重试。', getApiStateType(error));
} finally {
sortPending = false;
[sortSelect, sortUp, sortDown, sortSave].forEach(function (control) {
if (control) control.disabled = sortSelect.options.length < 2;
});
}
});
try {
result = await api.genealogyQuota();
setEntryStatus(buildStatus(result));
} catch (error) {
if (shouldRedirectToLogin(api, error)) {
redirectToLogin(api);
return;
}
setEntryStatus(
getApiStateType(error) === 'forbidden' ? '当前账号无权读取家谱额度。' : '家谱额度读取失败,请稍后重试。',
getApiStateType(error)
);
}
}
return {
normalizeTargetPage: normalizeTargetPage,
getTargetPage: getTargetPage,
buildStatus: buildStatus,
buildGenealogyCreateBody: buildGenealogyCreateBody,
validateGenealogyCreateBody: validateGenealogyCreateBody,
canCreateGenealogy: canCreateGenealogy,
normalizeCreatedGenealogy: normalizeCreatedGenealogy,
buildCreatedGenealogyUrl: buildCreatedGenealogyUrl,
shouldRedirectToLogin: shouldRedirectToLogin,
initGenealogyCreatePage: initGenealogyCreatePage,
normalizeGenealogy: normalizeGenealogy,
buildEntryUrl: buildEntryUrl,
renderGenealogies: renderGenealogies,
init: init
};
});