752 lines
28 KiB
JavaScript
752 lines
28 KiB
JavaScript
(function (root, factory) {
|
|
if (typeof module === 'object' && module.exports) {
|
|
module.exports = factory(root);
|
|
return;
|
|
}
|
|
|
|
root.RelativePages = factory(root);
|
|
if (root.document) {
|
|
root.document.addEventListener('DOMContentLoaded', function () {
|
|
root.RelativePages.init();
|
|
});
|
|
}
|
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
|
'use strict';
|
|
|
|
function confirmAction(message) {
|
|
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
|
return root.ProfileUI.confirmAction(message);
|
|
}
|
|
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
|
}
|
|
|
|
var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null);
|
|
|
|
var documentRef = root.document;
|
|
var recordsById = Object.create(null);
|
|
var canEditContent = false;
|
|
var writePending = false;
|
|
|
|
function getApi() {
|
|
return root.GenealogyApi && root.GenealogyApi.defaultClient;
|
|
}
|
|
|
|
function query(selector, node) {
|
|
return documentRef ? (node || documentRef).querySelector(selector) : null;
|
|
}
|
|
|
|
function queryAll(selector, node) {
|
|
return documentRef ? Array.prototype.slice.call((node || documentRef).querySelectorAll(selector)) : [];
|
|
}
|
|
|
|
function trimOrUndefined(value) {
|
|
var text = String(value === undefined || value === null ? '' : value).trim();
|
|
|
|
return text || undefined;
|
|
}
|
|
|
|
function queryParam(search, name) {
|
|
return new URLSearchParams(String(search || '').replace(/^\?/, '')).get(name) || '';
|
|
}
|
|
|
|
function normalizeId(value) {
|
|
if (value === undefined || value === null || value === '') return '';
|
|
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
|
|
return /^[1-9][0-9]*$/.test(String(value)) ? String(value) : '';
|
|
}
|
|
|
|
function getCurrentGenealogyId(search) {
|
|
var source = search === undefined && root.location ? root.location.search : search;
|
|
var profileGenealogyId;
|
|
|
|
if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) {
|
|
profileGenealogyId = root.ProfileUI.getGenealogyId();
|
|
if (profileGenealogyId) return normalizeId(profileGenealogyId);
|
|
}
|
|
return normalizeId(queryParam(source, 'genealogyId'));
|
|
}
|
|
|
|
function getCurrentRelativeId(search) {
|
|
var source = search === undefined && root.location ? root.location.search : search;
|
|
|
|
return normalizeId(queryParam(source, 'relativeId'));
|
|
}
|
|
|
|
function toFiniteNumber(value) {
|
|
var text = trimOrUndefined(value);
|
|
var number;
|
|
|
|
if (!text) return undefined;
|
|
number = Number(text);
|
|
return Number.isFinite(number) ? number : undefined;
|
|
}
|
|
|
|
function toSafeInteger(value) {
|
|
var number = toFiniteNumber(value);
|
|
|
|
return Number.isSafeInteger(number) ? number : undefined;
|
|
}
|
|
|
|
function decimalKey(value) {
|
|
var text = String(value).trim().replace(/^([+-]?)\./, '$10.');
|
|
var match = text.match(/^([+-]?)(\d+)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/);
|
|
var coefficient;
|
|
var scale;
|
|
|
|
if (!match) return '';
|
|
coefficient = BigInt((match[2] + (match[3] || '')).replace(/^0+(?=\d)/, ''));
|
|
scale = (match[3] || '').length - Number(match[4] || 0);
|
|
if (coefficient === 0n) return '0:0';
|
|
if (scale < 0) {
|
|
coefficient *= 10n ** BigInt(-scale);
|
|
scale = 0;
|
|
}
|
|
while (scale > 0 && coefficient % 10n === 0n) {
|
|
coefficient /= 10n;
|
|
scale -= 1;
|
|
}
|
|
if (match[1] === '-') coefficient = -coefficient;
|
|
return String(coefficient) + ':' + String(scale);
|
|
}
|
|
|
|
function isExactJsonNumber(sourceText, number) {
|
|
return Number.isFinite(number) && decimalKey(sourceText) === decimalKey(JSON.stringify(number));
|
|
}
|
|
|
|
function toBackendDateTime(value) {
|
|
var text = trimOrUndefined(value);
|
|
|
|
if (!text) return undefined;
|
|
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(text)) return text.replace('T', ' ') + ':00';
|
|
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(text)) return text.replace('T', ' ');
|
|
return text;
|
|
}
|
|
|
|
function toDateTimeInputValue(value) {
|
|
var text = String(value || '');
|
|
|
|
if (!/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/.test(text)) return '';
|
|
return text.slice(0, 16).replace(' ', 'T');
|
|
}
|
|
|
|
function buildRelativeRecordBody(values) {
|
|
var source = values || {};
|
|
var body = { relativeName: String(source.relativeName || '').trim() };
|
|
var relationName = trimOrUndefined(source.relationName);
|
|
var eventName = trimOrUndefined(source.eventName);
|
|
var eventTime = toBackendDateTime(source.eventTime);
|
|
var amountText = trimOrUndefined(source.giftAmount);
|
|
var recordContent = trimOrUndefined(source.recordContent);
|
|
var mediaOssIds = trimOrUndefined(source.mediaOssIds);
|
|
var sortOrderText = trimOrUndefined(source.sortOrder);
|
|
var status = trimOrUndefined(source.status);
|
|
|
|
if (relationName !== undefined) body.relationName = relationName;
|
|
if (eventName !== undefined) body.eventName = eventName;
|
|
if (eventTime !== undefined) body.eventTime = eventTime;
|
|
if (amountText !== undefined) {
|
|
body.giftAmount = toFiniteNumber(amountText);
|
|
if (body.giftAmount === undefined) body.invalidGiftAmount = true;
|
|
else if (!isExactJsonNumber(amountText, body.giftAmount)) body.invalidGiftPrecision = true;
|
|
}
|
|
if (recordContent !== undefined) body.recordContent = recordContent;
|
|
if (source.mediaOssIds !== undefined) body.mediaOssIds = mediaOssIds || '';
|
|
if (sortOrderText !== undefined) {
|
|
body.sortOrder = toSafeInteger(sortOrderText);
|
|
if (body.sortOrder === undefined) body.invalidSortOrder = true;
|
|
}
|
|
if (status !== undefined) body.status = status;
|
|
return body;
|
|
}
|
|
|
|
function validateRelativeRecordBody(body) {
|
|
if (!body || !body.relativeName) return '请填写亲友姓名';
|
|
if (body.invalidGiftAmount || (Object.prototype.hasOwnProperty.call(body, 'giftAmount') && !Number.isFinite(body.giftAmount))) {
|
|
return '礼金金额必须是有限数字';
|
|
}
|
|
if (body.invalidGiftPrecision) return '礼金金额超出浏览器可安全提交的精度';
|
|
if (body.eventTime !== undefined && !isValidBackendDateTime(body.eventTime)) {
|
|
return '事件时间格式无效';
|
|
}
|
|
if (body.mediaOssIds !== undefined && body.mediaOssIds !== '' && !/^[1-9][0-9]*(,[1-9][0-9]*)*$/.test(body.mediaOssIds)) {
|
|
return '附件上传结果无效';
|
|
}
|
|
if (body.invalidSortOrder || (Object.prototype.hasOwnProperty.call(body, 'sortOrder') && !Number.isSafeInteger(body.sortOrder))) {
|
|
return '排序值必须是安全整数';
|
|
}
|
|
if (body.status === '1') return '当前 PC 无法重新读取停用记录,暂不开放停用';
|
|
if (body.status !== undefined && body.status !== '0') return '记录状态只能是 0';
|
|
return '';
|
|
}
|
|
|
|
function isValidBackendDateTime(value) {
|
|
var match = String(value || '').match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
|
|
var year;
|
|
var month;
|
|
var day;
|
|
var days;
|
|
|
|
if (!match) return false;
|
|
year = Number(match[1]);
|
|
month = Number(match[2]);
|
|
day = Number(match[3]);
|
|
if (month < 1 || month > 12 || Number(match[4]) > 23 || Number(match[5]) > 59 || Number(match[6]) > 59) return false;
|
|
days = [31, year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
return day >= 1 && day <= days[month - 1];
|
|
}
|
|
|
|
function stringValue(value) {
|
|
return value === undefined || value === null ? '' : String(value);
|
|
}
|
|
|
|
function normalizeAmount(value) {
|
|
var text;
|
|
|
|
if (value === undefined || value === null || value === '') return '';
|
|
if (typeof value === 'number') return Number.isFinite(value) ? String(value) : '';
|
|
text = String(value).trim();
|
|
return /^-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(text) ? text : '';
|
|
}
|
|
|
|
function optionalSafeInteger(value) {
|
|
var number;
|
|
|
|
if (value === undefined || value === null || value === '') return undefined;
|
|
number = Number(value);
|
|
return Number.isSafeInteger(number) ? number : undefined;
|
|
}
|
|
|
|
function normalizeRelativeRecord(item) {
|
|
var relativeId = normalizeId(item && item.relativeId);
|
|
var genealogyId = normalizeId(item && item.genealogyId);
|
|
var appUserId = normalizeId(item && item.appUserId);
|
|
var relativeName = String(item && item.relativeName || '').trim();
|
|
var status = stringValue(item && item.status);
|
|
var mediaFiles = MediaDisplay ? MediaDisplay.normalizeFileList(item && item.mediaFiles) : [];
|
|
var giftAmount = normalizeAmount(item && item.giftAmount);
|
|
var sortOrder = optionalSafeInteger(item && item.sortOrder);
|
|
var record;
|
|
|
|
if (!relativeId || !genealogyId || !relativeName || (status !== '0' && status !== '1')) return null;
|
|
if (item.appUserId !== undefined && item.appUserId !== null && item.appUserId !== '' && !appUserId) return null;
|
|
if (item.giftAmount !== undefined && item.giftAmount !== null && item.giftAmount !== '' && giftAmount === '') return null;
|
|
if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null;
|
|
record = {
|
|
relativeId: relativeId,
|
|
genealogyId: genealogyId,
|
|
genealogyNo: stringValue(item.genealogyNo),
|
|
genealogyName: stringValue(item.genealogyName),
|
|
surname: stringValue(item.surname),
|
|
appUserNickName: stringValue(item.appUserNickName),
|
|
appUserPhone: stringValue(item.appUserPhone),
|
|
relativeName: relativeName,
|
|
relationName: stringValue(item.relationName),
|
|
eventName: stringValue(item.eventName),
|
|
eventTime: stringValue(item.eventTime),
|
|
giftAmount: giftAmount,
|
|
recordContent: stringValue(item.recordContent),
|
|
status: status,
|
|
remark: stringValue(item.remark)
|
|
};
|
|
if (mediaFiles.length) record.mediaFiles = mediaFiles;
|
|
if (appUserId) record.appUserId = appUserId;
|
|
if (sortOrder !== undefined) record.sortOrder = sortOrder;
|
|
return record;
|
|
}
|
|
|
|
function matchesSavedRecord(item, expectedRelativeId) {
|
|
var record = normalizeRelativeRecord(item);
|
|
|
|
return Boolean(record && record.relativeId === normalizeId(expectedRelativeId));
|
|
}
|
|
|
|
function isEditableRecord(record) {
|
|
return Boolean(record && record.status === '0');
|
|
}
|
|
|
|
function escapeHtml(value) {
|
|
return stringValue(value)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
function attachmentCount(mediaFiles) {
|
|
return Array.isArray(mediaFiles) ? mediaFiles.length : 0;
|
|
}
|
|
|
|
function buildListUrl(genealogyId, relativeId) {
|
|
var url = 'profile-relative.html?genealogyId=' + encodeURIComponent(genealogyId);
|
|
|
|
return relativeId ? url + '&relativeId=' + encodeURIComponent(relativeId) : url;
|
|
}
|
|
|
|
function buildEditUrl(genealogyId, relativeId) {
|
|
var url = 'profile-relative-edit.html?genealogyId=' + encodeURIComponent(genealogyId);
|
|
|
|
return relativeId ? url + '&relativeId=' + encodeURIComponent(relativeId) : url;
|
|
}
|
|
|
|
function canEditRecords(genealogy) {
|
|
return Boolean(genealogy && (genealogy.canEditContent === true || genealogy.canManage === true));
|
|
}
|
|
|
|
function renderRelativeRow(record) {
|
|
var summary = [];
|
|
var actions = '<button class="pill" type="button" data-relative-detail-id="' +
|
|
escapeHtml(record.relativeId) + '">查看详情</button>';
|
|
|
|
if (record.relationName) summary.push(record.relationName);
|
|
if (record.eventName) summary.push(record.eventName);
|
|
if (record.eventTime) summary.push(record.eventTime);
|
|
if (!summary.length) summary.push('未填写关系和事件');
|
|
if (canEditContent) {
|
|
actions += '<a class="pill" href="' + escapeHtml(buildEditUrl(record.genealogyId, record.relativeId)) +
|
|
'">编辑</a><button class="pill is-danger" type="button" data-relative-delete-id="' +
|
|
escapeHtml(record.relativeId) + '">删除</button>';
|
|
}
|
|
return '<article class="module-row relative-row"><div><h3>' + escapeHtml(record.relativeName) + '</h3><p>' +
|
|
escapeHtml(summary.join(' · ')) + '</p></div><div class="row-actions">' + actions + '</div></article>';
|
|
}
|
|
|
|
function detailValue(label, value) {
|
|
return '<p><span>' + escapeHtml(label) + '</span><b>' + escapeHtml(value || '未提供') + '</b></p>';
|
|
}
|
|
|
|
function renderRelativeDetail(record) {
|
|
var count;
|
|
var actions = '';
|
|
|
|
if (!record) return stateHtml('empty', '请选择一条亲友记录查看详情');
|
|
count = attachmentCount(record.mediaFiles);
|
|
if (canEditContent) {
|
|
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
|
escapeHtml(buildEditUrl(record.genealogyId, record.relativeId)) + '">编辑记录</a>' +
|
|
'<button class="btn danger" type="button" data-relative-delete-id="' +
|
|
escapeHtml(record.relativeId) + '">删除记录</button></div>';
|
|
}
|
|
return (MediaDisplay ? MediaDisplay.renderGallery(record.mediaFiles, { label: '亲友记录附件' }) : '') +
|
|
'<div class="form-like relative-detail">' +
|
|
detailValue('亲友姓名', record.relativeName) +
|
|
detailValue('关系名称', record.relationName) +
|
|
detailValue('事件名称', record.eventName) +
|
|
detailValue('事件时间', record.eventTime) +
|
|
detailValue('礼金金额', record.giftAmount) +
|
|
detailValue('记录内容', record.recordContent) +
|
|
detailValue('附件', count ? count + ' 个附件' : '未上传') +
|
|
detailValue('记录人', record.appUserNickName) +
|
|
detailValue('状态', record.status === '0' ? '正常' : '停用') +
|
|
detailValue('备注', record.remark) +
|
|
'</div>' + actions;
|
|
}
|
|
|
|
function showMessage(message) {
|
|
if (root.layui && root.layui.layer) root.layui.layer.msg(message);
|
|
else if (root.alert) root.alert(message);
|
|
}
|
|
|
|
function shouldRedirectToLogin(api, error) {
|
|
var status = error && (error.status || error.code);
|
|
|
|
return !api || !api.getToken || !api.getToken() || Number(status) === 401;
|
|
}
|
|
|
|
function isForbidden(error) {
|
|
return Number(error && (error.status || error.code)) === 403;
|
|
}
|
|
|
|
function getApiStateType(error) {
|
|
return isForbidden(error) ? 'forbidden' : 'error';
|
|
}
|
|
|
|
function redirectUnauthorized(api, error) {
|
|
if (!shouldRedirectToLogin(api, error)) return false;
|
|
if (api && api.clearToken) api.clearToken();
|
|
root.NavigationUtil.open('login.html');
|
|
return true;
|
|
}
|
|
|
|
function stateHtml(type, message) {
|
|
if (root.ProfileUI && root.ProfileUI.renderApiState) return root.ProfileUI.renderApiState(type, message);
|
|
return '<div class="api-state api-state--' + type + '" role="' +
|
|
(type === 'error' || type === 'forbidden' ? 'alert' : 'status') +
|
|
'" aria-live="polite">' + escapeHtml(message) + '</div>';
|
|
}
|
|
|
|
function setState(container, type, message) {
|
|
if (!container) return;
|
|
if (root.ProfileUI && root.ProfileUI.setApiState) {
|
|
root.ProfileUI.setApiState(container, type, message);
|
|
return;
|
|
}
|
|
container.innerHTML = stateHtml(type, message);
|
|
}
|
|
|
|
function setFormStatus(message, type) {
|
|
var target = query('[data-relative-form-status]');
|
|
|
|
if (!target) return;
|
|
if (!message) {
|
|
target.innerHTML = '';
|
|
return;
|
|
}
|
|
if (type && root.ProfileUI && root.ProfileUI.setApiState) {
|
|
root.ProfileUI.setApiState(target, type, message);
|
|
return;
|
|
}
|
|
target.textContent = message;
|
|
}
|
|
|
|
function setEditorEnabled(enabled, message, type) {
|
|
var placeholder = query('[data-relative-editor-placeholder]');
|
|
|
|
queryAll('[data-relative-editor], [data-relative-editor-action]').forEach(function (element) {
|
|
element.hidden = !enabled;
|
|
});
|
|
if (placeholder) {
|
|
placeholder.hidden = Boolean(enabled);
|
|
if (!enabled && message) {
|
|
if (type) setState(placeholder, type, message);
|
|
else placeholder.textContent = message;
|
|
}
|
|
}
|
|
}
|
|
|
|
function setEditAccess(enabled) {
|
|
canEditContent = Boolean(enabled);
|
|
queryAll('[data-relative-create-link]').forEach(function (element) {
|
|
element.hidden = !canEditContent;
|
|
});
|
|
}
|
|
|
|
async function loadCapability(api, genealogyId) {
|
|
var genealogy = await api.genealogyDetail(genealogyId);
|
|
|
|
setEditAccess(canEditRecords(genealogy));
|
|
return genealogy;
|
|
}
|
|
|
|
function setWritePending(value, label) {
|
|
var submit = query('[data-relative-form] [type="submit"]');
|
|
|
|
writePending = Boolean(value);
|
|
queryAll('[data-relative-form] button, [data-relative-form] input, [data-relative-form] textarea, [data-relative-delete-id]').forEach(function (control) {
|
|
control.disabled = writePending;
|
|
});
|
|
if (submit) {
|
|
if (!submit.dataset.defaultLabel) submit.dataset.defaultLabel = submit.textContent;
|
|
submit.textContent = writePending && label ? label : submit.dataset.defaultLabel;
|
|
submit.setAttribute('aria-busy', writePending ? 'true' : 'false');
|
|
}
|
|
}
|
|
|
|
function syncLinks() {
|
|
if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) root.ProfileUI.syncGenealogyContextLinks();
|
|
}
|
|
|
|
function renderNoContext() {
|
|
var list = query('[data-relative-list]');
|
|
var detail = query('[data-relative-detail]');
|
|
var message = '请先选择家谱,再查看或维护亲友记录。';
|
|
|
|
setState(list, 'empty', message);
|
|
setState(detail, 'empty', '当前没有家谱上下文。');
|
|
setEditAccess(false);
|
|
setEditorEnabled(false, message, 'empty');
|
|
setFormStatus(message, 'empty');
|
|
}
|
|
|
|
function requireGenealogyId() {
|
|
var genealogyId = getCurrentGenealogyId();
|
|
|
|
if (!genealogyId) renderNoContext();
|
|
return genealogyId;
|
|
}
|
|
|
|
function renderRelativeRecords(data) {
|
|
var container = query('[data-relative-list]');
|
|
var normalized = Array.isArray(data) ? data.map(normalizeRelativeRecord) : [];
|
|
var invalidCount = normalized.filter(function (record) { return !record; }).length;
|
|
var records = normalized.filter(Boolean);
|
|
|
|
recordsById = Object.create(null);
|
|
records.forEach(function (record) {
|
|
recordsById[record.relativeId] = record;
|
|
});
|
|
if (!container) return records;
|
|
if (!Array.isArray(data) || invalidCount) {
|
|
setState(container, 'error', '亲友记录响应缺少稳定 RelativeRecordVo 字段,请联系后端核对。');
|
|
return [];
|
|
}
|
|
if (!records.length) {
|
|
setState(container, 'empty', '暂无亲友往来记录');
|
|
return [];
|
|
}
|
|
container.innerHTML = records.map(renderRelativeRow).join('');
|
|
return records;
|
|
}
|
|
|
|
function renderDetail(record) {
|
|
var container = query('[data-relative-detail]');
|
|
|
|
if (container) container.innerHTML = renderRelativeDetail(record);
|
|
}
|
|
|
|
async function loadRelativeDetail(relativeId) {
|
|
var api = getApi();
|
|
var genealogyId = requireGenealogyId();
|
|
var record;
|
|
|
|
if (!genealogyId || !normalizeId(relativeId) || redirectUnauthorized(api)) return null;
|
|
setState(query('[data-relative-detail]'), 'loading', '正在加载亲友记录详情…');
|
|
try {
|
|
record = normalizeRelativeRecord(await api.relativeRecordDetail(genealogyId, relativeId));
|
|
if (!record) throw new Error('亲友记录详情响应缺少稳定 RelativeRecordVo 字段');
|
|
recordsById[record.relativeId] = record;
|
|
renderDetail(record);
|
|
return record;
|
|
} catch (error) {
|
|
if (redirectUnauthorized(api, error)) return null;
|
|
setState(
|
|
query('[data-relative-detail]'),
|
|
getApiStateType(error),
|
|
isForbidden(error) ? '当前账号无权查看该亲友记录。' : '亲友记录详情加载失败,请稍后重试。'
|
|
);
|
|
showMessage(isForbidden(error) ? '当前账号无权查看该亲友记录。' : (error.message || '亲友记录详情加载失败'));
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function loadRelativeRecords() {
|
|
var api = getApi();
|
|
var genealogyId;
|
|
var records;
|
|
var requestedRelativeId;
|
|
|
|
if (redirectUnauthorized(api)) return;
|
|
genealogyId = requireGenealogyId();
|
|
if (!genealogyId) return;
|
|
syncLinks();
|
|
setState(query('[data-relative-list]'), 'loading', '正在加载亲友记录…');
|
|
try {
|
|
await loadCapability(api, genealogyId);
|
|
records = renderRelativeRecords(await api.relativeRecords(genealogyId));
|
|
requestedRelativeId = getCurrentRelativeId();
|
|
if (requestedRelativeId && recordsById[requestedRelativeId]) {
|
|
await loadRelativeDetail(requestedRelativeId);
|
|
} else if (records.length) {
|
|
await loadRelativeDetail(records[0].relativeId);
|
|
} else {
|
|
renderDetail(null);
|
|
}
|
|
} catch (error) {
|
|
if (redirectUnauthorized(api, error)) return;
|
|
setEditAccess(false);
|
|
setState(
|
|
query('[data-relative-list]'),
|
|
getApiStateType(error),
|
|
isForbidden(error) ? '当前账号无权查看该家谱的亲友记录。' : '亲友记录加载失败,请稍后重试。'
|
|
);
|
|
showMessage(isForbidden(error) ? '当前账号无权查看该家谱的亲友记录。' : (error.message || '亲友记录加载失败'));
|
|
}
|
|
}
|
|
|
|
function getFormValues(form) {
|
|
var values = {};
|
|
|
|
queryAll('[name]', form).forEach(function (field) {
|
|
values[field.name] = field.value;
|
|
});
|
|
return values;
|
|
}
|
|
|
|
function setFieldValue(name, value) {
|
|
var field = query('[data-relative-form] [name="' + name + '"]');
|
|
|
|
if (field) field.value = value === undefined || value === null ? '' : String(value);
|
|
}
|
|
|
|
function fillRelativeForm(record) {
|
|
if (!isEditableRecord(record)) throw new Error('停用亲友记录无法通过当前 PC 接口重新读取或编辑');
|
|
setFieldValue('relativeName', record.relativeName);
|
|
setFieldValue('relationName', record.relationName);
|
|
setFieldValue('eventName', record.eventName);
|
|
setFieldValue('eventTime', toDateTimeInputValue(record.eventTime));
|
|
setFieldValue('giftAmount', record.giftAmount);
|
|
setFieldValue('recordContent', record.recordContent);
|
|
setFieldValue('sortOrder', record.sortOrder);
|
|
setFieldValue('status', '0');
|
|
}
|
|
|
|
async function loadRelativeEditor() {
|
|
var api = getApi();
|
|
var genealogyId;
|
|
var relativeId;
|
|
var record;
|
|
|
|
if (redirectUnauthorized(api)) return;
|
|
genealogyId = requireGenealogyId();
|
|
if (!genealogyId) return;
|
|
relativeId = getCurrentRelativeId();
|
|
syncLinks();
|
|
setEditorEnabled(false, '正在加载亲友记录编辑信息…', 'loading');
|
|
setFormStatus('正在读取家谱权限…', 'loading');
|
|
try {
|
|
await loadCapability(api, genealogyId);
|
|
if (!canEditContent) throw { status: 403, message: '当前账号无权维护该家谱的亲友记录。' };
|
|
if (relativeId) {
|
|
var relativeResponse = await api.relativeRecordDetail(genealogyId, relativeId);
|
|
record = normalizeRelativeRecord(relativeResponse);
|
|
if (!record) throw new Error('亲友记录详情响应缺少稳定 RelativeRecordVo 字段');
|
|
fillRelativeForm(record);
|
|
root.AttachmentEditor.setFiles('#relativeMediaOssIds', relativeResponse.mediaFiles || []);
|
|
if (query('[data-relative-editor-title]')) query('[data-relative-editor-title]').textContent = '编辑亲友记录';
|
|
}
|
|
setEditorEnabled(true);
|
|
setFormStatus('');
|
|
} catch (error) {
|
|
if (redirectUnauthorized(api, error)) return;
|
|
setEditAccess(false);
|
|
setEditorEnabled(false, isForbidden(error)
|
|
? '当前账号无权维护该家谱的亲友记录。'
|
|
: (error.message || '亲友记录编辑信息加载失败'), getApiStateType(error));
|
|
setFormStatus(
|
|
isForbidden(error) ? '当前账号无权维护该家谱的亲友记录。' : (error.message || '亲友记录编辑信息加载失败'),
|
|
getApiStateType(error)
|
|
);
|
|
}
|
|
}
|
|
|
|
async function submitRelativeRecord(form) {
|
|
var api = getApi();
|
|
var genealogyId = requireGenealogyId();
|
|
var relativeId = getCurrentRelativeId();
|
|
var body;
|
|
var validation;
|
|
var result;
|
|
var saved;
|
|
var savedId;
|
|
var verifiedDetail;
|
|
|
|
if (writePending || !canEditContent || !genealogyId || redirectUnauthorized(api)) return;
|
|
body = buildRelativeRecordBody(getFormValues(form));
|
|
validation = validateRelativeRecordBody(body);
|
|
if (validation) {
|
|
setFormStatus(validation, 'error');
|
|
return;
|
|
}
|
|
delete body.invalidGiftAmount;
|
|
delete body.invalidGiftPrecision;
|
|
delete body.invalidSortOrder;
|
|
setWritePending(true, '正在保存…');
|
|
setFormStatus('正在保存亲友记录…', 'loading');
|
|
try {
|
|
result = relativeId
|
|
? await api.updateRelativeRecord(genealogyId, relativeId, body)
|
|
: await api.createRelativeRecord(genealogyId, body);
|
|
saved = normalizeRelativeRecord(result);
|
|
savedId = relativeId || (saved && saved.relativeId);
|
|
if (!savedId) throw new Error('保存响应缺少 relativeId');
|
|
verifiedDetail = await api.relativeRecordDetail(genealogyId, savedId);
|
|
if (!matchesSavedRecord(verifiedDetail, savedId)) throw new Error('保存后重读未返回同一条亲友记录');
|
|
root.NavigationUtil.open(buildListUrl(genealogyId, savedId));
|
|
} catch (error) {
|
|
if (redirectUnauthorized(api, error)) return;
|
|
setFormStatus(
|
|
isForbidden(error) ? '当前账号无权保存该亲友记录。' : (error.message || '亲友记录保存失败'),
|
|
getApiStateType(error)
|
|
);
|
|
} finally {
|
|
setWritePending(false);
|
|
}
|
|
}
|
|
|
|
async function deleteRelativeRecord(relativeId) {
|
|
var api = getApi();
|
|
var genealogyId = requireGenealogyId();
|
|
var record = recordsById[relativeId];
|
|
|
|
if (writePending || !canEditContent || !genealogyId || !normalizeId(relativeId) || redirectUnauthorized(api)) return;
|
|
if (!await confirmAction('确认删除“' + (record ? record.relativeName : '该亲友记录') + '”吗?删除后不可恢复,并会释放附件引用。')) return;
|
|
setWritePending(true);
|
|
try {
|
|
await api.deleteRelativeRecord(genealogyId, relativeId);
|
|
await loadRelativeRecords();
|
|
showMessage('亲友记录已删除');
|
|
} catch (error) {
|
|
if (redirectUnauthorized(api, error)) return;
|
|
showMessage(isForbidden(error) ? '当前账号无权删除该亲友记录。' : (error.message || '亲友记录删除失败'));
|
|
} finally {
|
|
setWritePending(false);
|
|
}
|
|
}
|
|
|
|
function clearMediaSelection() {
|
|
root.AttachmentEditor.clear('#relativeMediaOssIds');
|
|
}
|
|
|
|
function bindActions() {
|
|
if (!documentRef) return;
|
|
documentRef.addEventListener('click', function (event) {
|
|
var detailButton = event.target.closest('[data-relative-detail-id]');
|
|
var deleteButton = event.target.closest('[data-relative-delete-id]');
|
|
var clearMediaButton = event.target.closest('[data-relative-clear-media]');
|
|
var relativeId;
|
|
var genealogyId;
|
|
|
|
if (detailButton) {
|
|
event.preventDefault();
|
|
relativeId = detailButton.getAttribute('data-relative-detail-id');
|
|
genealogyId = requireGenealogyId();
|
|
if (root.history && genealogyId) root.history.replaceState(null, '', buildListUrl(genealogyId, relativeId));
|
|
loadRelativeDetail(relativeId).then(function (record) {
|
|
if (record && root.ProfileUI && root.ProfileUI.revealDetail) {
|
|
root.ProfileUI.revealDetail(query('[data-relative-detail-heading]'));
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
if (deleteButton) {
|
|
event.preventDefault();
|
|
deleteRelativeRecord(deleteButton.getAttribute('data-relative-delete-id'));
|
|
return;
|
|
}
|
|
if (clearMediaButton) {
|
|
event.preventDefault();
|
|
clearMediaSelection();
|
|
}
|
|
});
|
|
documentRef.addEventListener('submit', function (event) {
|
|
var form = event.target.closest('[data-relative-form]');
|
|
|
|
if (!form) return;
|
|
event.preventDefault();
|
|
submitRelativeRecord(form);
|
|
});
|
|
}
|
|
|
|
function init() {
|
|
if (!documentRef) return;
|
|
bindActions();
|
|
if (query('[data-relative-page]')) loadRelativeRecords();
|
|
if (query('[data-relative-edit-page]')) loadRelativeEditor();
|
|
}
|
|
|
|
return {
|
|
getCurrentGenealogyId: getCurrentGenealogyId,
|
|
getCurrentRelativeId: getCurrentRelativeId,
|
|
buildRelativeRecordBody: buildRelativeRecordBody,
|
|
validateRelativeRecordBody: validateRelativeRecordBody,
|
|
normalizeRelativeRecord: normalizeRelativeRecord,
|
|
matchesSavedRecord: matchesSavedRecord,
|
|
canEditRecords: canEditRecords,
|
|
isEditableRecord: isEditableRecord,
|
|
renderRelativeDetail: renderRelativeDetail,
|
|
shouldRedirectToLogin: shouldRedirectToLogin,
|
|
isForbidden: isForbidden,
|
|
init: init
|
|
};
|
|
});
|