329 lines
12 KiB
JavaScript
329 lines
12 KiB
JavaScript
(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 getTargetPage(search) {
|
||
var params = new URLSearchParams(String(search === undefined && root.location ? root.location.search : search || '').replace(/^\?/, ''));
|
||
var target = params.get('next') || '';
|
||
|
||
return /^profile-[a-z-]+\.html$/.test(target) ? target : 'profile-family-home.html';
|
||
}
|
||
|
||
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, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
}
|
||
|
||
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 setCreateStatus(message) {
|
||
var status = root.document && root.document.querySelector('[data-genealogy-create-status]');
|
||
|
||
if (status) status.textContent = message || '';
|
||
}
|
||
|
||
function setCreatePending(pending) {
|
||
var button = root.document && root.document.querySelector('[data-genealogy-create-submit]');
|
||
|
||
if (button) button.disabled = Boolean(pending);
|
||
}
|
||
|
||
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;
|
||
}
|
||
try {
|
||
quota = await api.genealogyQuota();
|
||
if (quotaElement) quotaElement.textContent = buildStatus(quota) || '当前额度信息不可用';
|
||
if (!canCreateGenealogy(quota)) {
|
||
setCreateStatus('当前没有可用的家谱创建额度');
|
||
setCreatePending(true);
|
||
return;
|
||
}
|
||
} catch (error) {
|
||
if (shouldRedirectToLogin(api, error)) {
|
||
redirectToLogin(api);
|
||
return;
|
||
}
|
||
if (quotaElement) quotaElement.textContent = '家谱额度读取失败';
|
||
setCreateStatus(error.message || '无法读取家谱创建额度');
|
||
setCreatePending(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);
|
||
return;
|
||
}
|
||
delete body.invalidCoverOssId;
|
||
writePending = true;
|
||
setCreatePending(true);
|
||
setCreateStatus('正在创建家谱…');
|
||
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 || '创建家谱失败,请稍后重试');
|
||
} 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 params = new URLSearchParams();
|
||
|
||
params.set('genealogyId', String(genealogyId));
|
||
return (target || 'profile-family-home.html') + '?' + params.toString();
|
||
}
|
||
|
||
function renderGenealogies(container, data, target) {
|
||
var items = Array.isArray(data) ? data.map(normalizeGenealogy).filter(Boolean) : [];
|
||
|
||
if (!container) return;
|
||
if (!items.length) {
|
||
container.innerHTML = '<div class="api-empty">当前账号还没有可进入的家谱</div>';
|
||
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('');
|
||
}
|
||
|
||
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 result;
|
||
|
||
if (!status) return;
|
||
if (shouldRedirectToLogin(api)) {
|
||
redirectToLogin(api);
|
||
return;
|
||
}
|
||
if (!api || !api.genealogyQuota || !api.genealogiesMine) {
|
||
status.textContent = '家谱入口接口初始化失败,请刷新后重试。';
|
||
return;
|
||
}
|
||
|
||
try {
|
||
result = await Promise.all([api.genealogiesMine(), api.genealogyQuota()]);
|
||
renderGenealogies(container, result[0], target);
|
||
status.textContent = buildStatus(result[1]);
|
||
} catch (error) {
|
||
if (shouldRedirectToLogin(api, error)) {
|
||
redirectToLogin(api);
|
||
return;
|
||
}
|
||
status.textContent = '无法读取我的家谱,请稍后重试。';
|
||
}
|
||
}
|
||
|
||
return {
|
||
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
|
||
};
|
||
});
|