464 lines
18 KiB
JavaScript
464 lines
18 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 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, '&')
|
||
.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 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
|
||
};
|
||
});
|