feat(api): 完善家谱系统API客户端契约
- 实现家谱管理相关方法,包括创建、详情、概览、我的家谱和选项查询 - 添加家谱加入申请功能,支持申请、审核、取消和待审核列表操作 - 集成通知详情获取方法和通知ID安全验证机制 - 完善功德记录、谱文、相册、视频、祭祀活动的完整CRUD操作契约 - 实现家谱成员管理功能,包含成员列表、更新、移除和转让所有者操作 - 优化路径ID验证逻辑,拒绝不安全的数值ID并提供明确错误提示 - 更新测试用例以验证所有新增API方法的路径和请求体白名单机制
This commit is contained in:
@@ -0,0 +1,644 @@
|
||||
(function (root, factory) {
|
||||
if (typeof module === 'object' && module.exports) {
|
||||
module.exports = factory(root);
|
||||
return;
|
||||
}
|
||||
|
||||
root.MeritPages = factory(root);
|
||||
if (root.document) {
|
||||
root.document.addEventListener('DOMContentLoaded', function () {
|
||||
root.MeritPages.init();
|
||||
});
|
||||
}
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||
'use strict';
|
||||
|
||||
var documentRef = root.document;
|
||||
var recordsById = Object.create(null);
|
||||
var writePending = false;
|
||||
var meritTypes = {
|
||||
donation: '捐赠',
|
||||
repair: '修缮',
|
||||
public: '公益',
|
||||
other: '其他'
|
||||
};
|
||||
|
||||
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 getCurrentMeritId(search) {
|
||||
var source = search === undefined && root.location ? root.location.search : search;
|
||||
|
||||
return normalizeId(queryParam(source, 'meritId'));
|
||||
}
|
||||
|
||||
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 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 buildMeritRecordBody(values) {
|
||||
var source = values || {};
|
||||
var body = {
|
||||
donorName: String(source.donorName || '').trim(),
|
||||
meritTitle: String(source.meritTitle || '').trim()
|
||||
};
|
||||
var meritType = trimOrUndefined(source.meritType);
|
||||
var meritContent = trimOrUndefined(source.meritContent);
|
||||
var amountText = trimOrUndefined(source.amount);
|
||||
var meritTime = toBackendDateTime(source.meritTime);
|
||||
var sortOrderText = trimOrUndefined(source.sortOrder);
|
||||
var status = trimOrUndefined(source.status);
|
||||
|
||||
if (meritType !== undefined) body.meritType = meritType;
|
||||
if (meritContent !== undefined) body.meritContent = meritContent;
|
||||
if (amountText !== undefined) {
|
||||
body.amount = toFiniteNumber(amountText);
|
||||
if (body.amount === undefined) body.invalidAmount = true;
|
||||
else if (!isExactJsonNumber(amountText, body.amount)) body.invalidAmountPrecision = true;
|
||||
}
|
||||
if (meritTime !== undefined) body.meritTime = meritTime;
|
||||
if (sortOrderText !== undefined) {
|
||||
body.sortOrder = toSafeInteger(sortOrderText);
|
||||
if (body.sortOrder === undefined) body.invalidSortOrder = true;
|
||||
}
|
||||
if (status !== undefined) body.status = status;
|
||||
return body;
|
||||
}
|
||||
|
||||
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 validateMeritRecordBody(body) {
|
||||
if (!body || !body.donorName) return '请填写功德人姓名';
|
||||
if (!body.meritTitle) return '请填写功德标题';
|
||||
if (body.meritType !== undefined && !Object.prototype.hasOwnProperty.call(meritTypes, body.meritType)) {
|
||||
return '功德类型无效';
|
||||
}
|
||||
if (body.invalidAmount || (Object.prototype.hasOwnProperty.call(body, 'amount') && !Number.isFinite(body.amount))) {
|
||||
return '功德金额必须是有限数字';
|
||||
}
|
||||
if (body.invalidAmountPrecision) return '功德金额超出浏览器可安全提交的精度';
|
||||
if (body.meritTime !== undefined && !isValidBackendDateTime(body.meritTime)) 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 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 normalizeMeritRecord(item) {
|
||||
var meritId = normalizeId(item && item.meritId);
|
||||
var genealogyId = normalizeId(item && item.genealogyId);
|
||||
var appUserId = normalizeId(item && item.appUserId);
|
||||
var donorName = String(item && item.donorName || '').trim();
|
||||
var meritType = stringValue(item && item.meritType);
|
||||
var meritTitle = String(item && item.meritTitle || '').trim();
|
||||
var amount = normalizeAmount(item && item.amount);
|
||||
var meritTime = stringValue(item && item.meritTime);
|
||||
var sortOrder = optionalSafeInteger(item && item.sortOrder);
|
||||
var status = stringValue(item && item.status);
|
||||
var record;
|
||||
|
||||
if (!meritId || !genealogyId || !donorName || !meritTitle || !Object.prototype.hasOwnProperty.call(meritTypes, meritType)) {
|
||||
return null;
|
||||
}
|
||||
if (item.appUserId !== undefined && item.appUserId !== null && item.appUserId !== '' && !appUserId) return null;
|
||||
if (item.amount !== undefined && item.amount !== null && item.amount !== '' && amount === '') return null;
|
||||
if (meritTime && !isValidBackendDateTime(meritTime)) return null;
|
||||
if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null;
|
||||
if (status !== '0' && status !== '1') return null;
|
||||
record = {
|
||||
meritId: meritId,
|
||||
genealogyId: genealogyId,
|
||||
genealogyNo: stringValue(item.genealogyNo),
|
||||
genealogyName: stringValue(item.genealogyName),
|
||||
surname: stringValue(item.surname),
|
||||
appUserNickName: stringValue(item.appUserNickName),
|
||||
appUserPhone: stringValue(item.appUserPhone),
|
||||
donorName: donorName,
|
||||
meritType: meritType,
|
||||
meritTitle: meritTitle,
|
||||
meritContent: stringValue(item.meritContent),
|
||||
amount: amount,
|
||||
meritTime: meritTime,
|
||||
status: status,
|
||||
remark: stringValue(item.remark)
|
||||
};
|
||||
if (appUserId) record.appUserId = appUserId;
|
||||
if (sortOrder !== undefined) record.sortOrder = sortOrder;
|
||||
return record;
|
||||
}
|
||||
|
||||
function matchesSavedMerit(item, expectedMeritId) {
|
||||
var record = normalizeMeritRecord(item);
|
||||
|
||||
return Boolean(record && record.meritId === normalizeId(expectedMeritId));
|
||||
}
|
||||
|
||||
function isEditableMerit(record) {
|
||||
return Boolean(record && record.status === '0');
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return stringValue(value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function buildListUrl(genealogyId, meritId) {
|
||||
var url = 'profile-merit.html?genealogyId=' + encodeURIComponent(genealogyId);
|
||||
|
||||
return meritId ? url + '&meritId=' + encodeURIComponent(meritId) : url;
|
||||
}
|
||||
|
||||
function buildEditUrl(genealogyId, meritId) {
|
||||
var url = 'profile-merit-edit.html?genealogyId=' + encodeURIComponent(genealogyId);
|
||||
|
||||
return meritId ? url + '&meritId=' + encodeURIComponent(meritId) : url;
|
||||
}
|
||||
|
||||
function renderMeritRow(record) {
|
||||
var summary = [meritTypes[record.meritType]];
|
||||
|
||||
if (record.meritTime) summary.push(record.meritTime);
|
||||
if (record.amount) summary.push('金额:' + record.amount);
|
||||
return '<article class="module-row merit-row"><div><h3>' + escapeHtml(record.meritTitle) + '</h3><p>' +
|
||||
escapeHtml(record.donorName + ' · ' + summary.join(' · ')) + '</p></div><div class="row-actions">' +
|
||||
'<button class="pill" type="button" data-merit-detail-id="' + escapeHtml(record.meritId) + '">查看详情</button>' +
|
||||
'<a class="pill" href="' + escapeHtml(buildEditUrl(record.genealogyId, record.meritId)) + '">编辑</a>' +
|
||||
'<button class="pill is-danger" type="button" data-merit-delete-id="' + escapeHtml(record.meritId) + '">删除</button>' +
|
||||
'</div></article>';
|
||||
}
|
||||
|
||||
function detailValue(label, value) {
|
||||
return '<p><span>' + escapeHtml(label) + '</span><b>' + escapeHtml(value || '未提供') + '</b></p>';
|
||||
}
|
||||
|
||||
function renderMeritDetail(record) {
|
||||
if (!record) return '<div class="api-empty">请选择一条功德记录查看详情</div>';
|
||||
return '<div class="form-like merit-detail">' +
|
||||
detailValue('功德人', record.donorName) +
|
||||
detailValue('功德类型', meritTypes[record.meritType]) +
|
||||
detailValue('功德标题', record.meritTitle) +
|
||||
detailValue('事迹内容', record.meritContent) +
|
||||
detailValue('金额', record.amount) +
|
||||
detailValue('功德时间', record.meritTime) +
|
||||
detailValue('记录人', record.appUserNickName) +
|
||||
detailValue('备注', record.remark) +
|
||||
'</div><div class="bottom-actions"><a class="btn ghost" href="' +
|
||||
escapeHtml(buildEditUrl(record.genealogyId, record.meritId)) + '">编辑功德记录</a>' +
|
||||
'<button class="btn ghost" type="button" data-merit-delete-id="' + escapeHtml(record.meritId) + '">删除功德记录</button></div>';
|
||||
}
|
||||
|
||||
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 redirectUnauthorized(api, error) {
|
||||
if (!shouldRedirectToLogin(api, error)) return false;
|
||||
if (api && api.clearToken) api.clearToken();
|
||||
if (!root.location) return true;
|
||||
if (typeof root.location.replace === 'function') root.location.replace('login.html');
|
||||
else root.location.href = 'login.html';
|
||||
return true;
|
||||
}
|
||||
|
||||
function setFormStatus(message) {
|
||||
var target = query('[data-merit-form-status]');
|
||||
|
||||
if (target) target.textContent = message || '';
|
||||
}
|
||||
|
||||
function setEditorEnabled(enabled, message) {
|
||||
var placeholder = query('[data-merit-editor-placeholder]');
|
||||
|
||||
queryAll('[data-merit-editor], [data-merit-editor-action]').forEach(function (element) {
|
||||
element.hidden = !enabled;
|
||||
});
|
||||
if (placeholder) {
|
||||
placeholder.hidden = Boolean(enabled);
|
||||
if (message) placeholder.textContent = message;
|
||||
}
|
||||
}
|
||||
|
||||
function setWritePending(value) {
|
||||
writePending = Boolean(value);
|
||||
queryAll('[data-merit-form] button, [data-merit-form] input, [data-merit-form] textarea, [data-merit-form] select, [data-merit-delete-id]').forEach(function (control) {
|
||||
control.disabled = writePending;
|
||||
});
|
||||
}
|
||||
|
||||
function syncLinks() {
|
||||
if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) root.ProfileUI.syncGenealogyContextLinks();
|
||||
}
|
||||
|
||||
function renderNoContext() {
|
||||
var list = query('[data-merit-list]');
|
||||
var detail = query('[data-merit-detail]');
|
||||
var message = '请先选择家谱,再查看或维护功德记录。';
|
||||
|
||||
if (list) list.innerHTML = '<div class="api-empty">' + message + '</div>';
|
||||
if (detail) detail.innerHTML = '<div class="api-empty">当前没有家谱上下文。</div>';
|
||||
setEditorEnabled(false, message);
|
||||
setFormStatus(message);
|
||||
}
|
||||
|
||||
function requireGenealogyId() {
|
||||
var genealogyId = getCurrentGenealogyId();
|
||||
|
||||
if (!genealogyId) renderNoContext();
|
||||
return genealogyId;
|
||||
}
|
||||
|
||||
function renderMeritRecords(data) {
|
||||
var container = query('[data-merit-list]');
|
||||
var normalized = Array.isArray(data) ? data.map(normalizeMeritRecord) : [];
|
||||
var invalidCount = normalized.filter(function (record) { return !record; }).length;
|
||||
var records = normalized.filter(Boolean);
|
||||
|
||||
recordsById = Object.create(null);
|
||||
records.forEach(function (record) {
|
||||
recordsById[record.meritId] = record;
|
||||
});
|
||||
if (!container) return records;
|
||||
if (!Array.isArray(data) || invalidCount) {
|
||||
container.innerHTML = '<div class="api-empty">功德记录响应缺少稳定 MeritRecordVo 字段,请联系后端核对。</div>';
|
||||
return [];
|
||||
}
|
||||
if (!records.length) {
|
||||
container.innerHTML = '<div class="api-empty">暂无功德记录</div>';
|
||||
return [];
|
||||
}
|
||||
container.innerHTML = records.map(renderMeritRow).join('');
|
||||
return records;
|
||||
}
|
||||
|
||||
function renderDetail(record) {
|
||||
var container = query('[data-merit-detail]');
|
||||
|
||||
if (container) container.innerHTML = renderMeritDetail(record);
|
||||
}
|
||||
|
||||
async function loadMeritDetail(meritId) {
|
||||
var api = getApi();
|
||||
var genealogyId = requireGenealogyId();
|
||||
var record;
|
||||
|
||||
if (!genealogyId || !normalizeId(meritId) || redirectUnauthorized(api)) return null;
|
||||
try {
|
||||
record = normalizeMeritRecord(await api.meritRecordDetail(genealogyId, meritId));
|
||||
if (!record) throw new Error('功德记录详情响应缺少稳定 MeritRecordVo 字段');
|
||||
recordsById[record.meritId] = record;
|
||||
renderDetail(record);
|
||||
return record;
|
||||
} catch (error) {
|
||||
if (redirectUnauthorized(api, error)) return null;
|
||||
renderDetail(null);
|
||||
showMessage(isForbidden(error) ? '当前账号无权查看该功德记录。' : (error.message || '功德记录详情加载失败'));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMeritRecords() {
|
||||
var api = getApi();
|
||||
var genealogyId;
|
||||
var records;
|
||||
var requestedMeritId;
|
||||
|
||||
if (redirectUnauthorized(api)) return;
|
||||
genealogyId = requireGenealogyId();
|
||||
if (!genealogyId) return;
|
||||
syncLinks();
|
||||
try {
|
||||
records = renderMeritRecords(await api.meritRecords(genealogyId));
|
||||
requestedMeritId = getCurrentMeritId();
|
||||
if (requestedMeritId && recordsById[requestedMeritId]) {
|
||||
await loadMeritDetail(requestedMeritId);
|
||||
} else if (records.length) {
|
||||
await loadMeritDetail(records[0].meritId);
|
||||
} else {
|
||||
renderDetail(null);
|
||||
}
|
||||
} catch (error) {
|
||||
if (redirectUnauthorized(api, error)) return;
|
||||
if (query('[data-merit-list]')) {
|
||||
query('[data-merit-list]').innerHTML = '<div class="api-empty">' +
|
||||
(isForbidden(error) ? '当前账号无权查看该家谱的功德记录。' : '功德记录加载失败,请稍后重试。') +
|
||||
'</div>';
|
||||
}
|
||||
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-merit-form] [name="' + name + '"]');
|
||||
|
||||
if (field) field.value = value === undefined || value === null ? '' : String(value);
|
||||
}
|
||||
|
||||
function fillMeritForm(record) {
|
||||
var contentField;
|
||||
var contentEditor;
|
||||
|
||||
if (!isEditableMerit(record)) throw new Error('停用功德记录无法通过当前 PC 接口重新读取或编辑');
|
||||
setFieldValue('donorName', record.donorName);
|
||||
setFieldValue('meritType', record.meritType);
|
||||
setFieldValue('meritTitle', record.meritTitle);
|
||||
setFieldValue('meritContent', record.meritContent);
|
||||
setFieldValue('amount', record.amount);
|
||||
setFieldValue('meritTime', toDateTimeInputValue(record.meritTime));
|
||||
setFieldValue('sortOrder', record.sortOrder);
|
||||
setFieldValue('status', '0');
|
||||
contentField = query('[name="meritContent"]');
|
||||
if (root.AppRichEditor && root.AppRichEditor.init && contentField) {
|
||||
contentEditor = root.AppRichEditor.init(contentField);
|
||||
if (contentEditor && contentEditor.editor && contentEditor.editor.setHtml) {
|
||||
contentEditor.editor.setHtml(record.meritContent);
|
||||
contentEditor.sync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMeritEditor() {
|
||||
var api = getApi();
|
||||
var genealogyId;
|
||||
var meritId;
|
||||
var record;
|
||||
|
||||
if (redirectUnauthorized(api)) return;
|
||||
genealogyId = requireGenealogyId();
|
||||
if (!genealogyId) return;
|
||||
meritId = getCurrentMeritId();
|
||||
syncLinks();
|
||||
setEditorEnabled(false, '正在加载功德记录编辑信息…');
|
||||
try {
|
||||
if (meritId) {
|
||||
record = normalizeMeritRecord(await api.meritRecordDetail(genealogyId, meritId));
|
||||
if (!record) throw new Error('功德记录详情响应缺少稳定 MeritRecordVo 字段');
|
||||
fillMeritForm(record);
|
||||
if (query('[data-merit-editor-title]')) query('[data-merit-editor-title]').textContent = '编辑功德记录';
|
||||
}
|
||||
setEditorEnabled(true);
|
||||
} catch (error) {
|
||||
if (redirectUnauthorized(api, error)) return;
|
||||
setEditorEnabled(false, isForbidden(error)
|
||||
? '当前账号无权维护该家谱的功德记录。'
|
||||
: (error.message || '功德记录编辑信息加载失败'));
|
||||
setFormStatus(error.message || '功德记录编辑信息加载失败');
|
||||
}
|
||||
}
|
||||
|
||||
async function submitMeritRecord(form) {
|
||||
var api = getApi();
|
||||
var genealogyId = requireGenealogyId();
|
||||
var meritId = getCurrentMeritId();
|
||||
var body;
|
||||
var validation;
|
||||
var result;
|
||||
var saved;
|
||||
var savedId;
|
||||
var verifiedDetail;
|
||||
|
||||
if (writePending || !genealogyId || redirectUnauthorized(api)) return;
|
||||
body = buildMeritRecordBody(getFormValues(form));
|
||||
validation = validateMeritRecordBody(body);
|
||||
if (validation) {
|
||||
setFormStatus(validation);
|
||||
return;
|
||||
}
|
||||
delete body.invalidAmount;
|
||||
delete body.invalidAmountPrecision;
|
||||
delete body.invalidSortOrder;
|
||||
setWritePending(true);
|
||||
setFormStatus('正在保存功德记录…');
|
||||
try {
|
||||
result = meritId
|
||||
? await api.updateMeritRecord(genealogyId, meritId, body)
|
||||
: await api.createMeritRecord(genealogyId, body);
|
||||
saved = normalizeMeritRecord(result);
|
||||
savedId = meritId || (saved && saved.meritId);
|
||||
if (!savedId) throw new Error('保存响应缺少 meritId');
|
||||
verifiedDetail = await api.meritRecordDetail(genealogyId, savedId);
|
||||
if (!matchesSavedMerit(verifiedDetail, savedId)) throw new Error('保存后重读未返回同一条功德记录');
|
||||
if (root.location) root.location.href = buildListUrl(genealogyId, savedId);
|
||||
} catch (error) {
|
||||
if (redirectUnauthorized(api, error)) return;
|
||||
setFormStatus(isForbidden(error) ? '当前账号无权保存该功德记录。' : (error.message || '功德记录保存失败'));
|
||||
} finally {
|
||||
setWritePending(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteMeritRecord(meritId) {
|
||||
var api = getApi();
|
||||
var genealogyId = requireGenealogyId();
|
||||
var record = recordsById[meritId];
|
||||
|
||||
if (writePending || !genealogyId || !normalizeId(meritId) || redirectUnauthorized(api)) return;
|
||||
if (root.confirm && !root.confirm('确认删除“' + (record ? record.meritTitle : '该功德记录') + '”吗?删除后不可恢复。')) return;
|
||||
setWritePending(true);
|
||||
try {
|
||||
await api.deleteMeritRecord(genealogyId, meritId);
|
||||
await loadMeritRecords();
|
||||
showMessage('功德记录已删除');
|
||||
} catch (error) {
|
||||
if (redirectUnauthorized(api, error)) return;
|
||||
showMessage(isForbidden(error) ? '当前账号无权删除该功德记录。' : (error.message || '功德记录删除失败'));
|
||||
} finally {
|
||||
setWritePending(false);
|
||||
}
|
||||
}
|
||||
|
||||
function bindActions() {
|
||||
if (!documentRef) return;
|
||||
documentRef.addEventListener('click', function (event) {
|
||||
var detailButton = event.target.closest('[data-merit-detail-id]');
|
||||
var deleteButton = event.target.closest('[data-merit-delete-id]');
|
||||
|
||||
if (detailButton) {
|
||||
event.preventDefault();
|
||||
loadMeritDetail(detailButton.getAttribute('data-merit-detail-id'));
|
||||
return;
|
||||
}
|
||||
if (deleteButton) {
|
||||
event.preventDefault();
|
||||
deleteMeritRecord(deleteButton.getAttribute('data-merit-delete-id'));
|
||||
}
|
||||
});
|
||||
documentRef.addEventListener('submit', function (event) {
|
||||
var form = event.target.closest('[data-merit-form]');
|
||||
|
||||
if (!form) return;
|
||||
event.preventDefault();
|
||||
submitMeritRecord(form);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (!documentRef) return;
|
||||
bindActions();
|
||||
if (query('[data-merit-page]')) loadMeritRecords();
|
||||
if (query('[data-merit-edit-page]')) loadMeritEditor();
|
||||
}
|
||||
|
||||
return {
|
||||
getCurrentGenealogyId: getCurrentGenealogyId,
|
||||
getCurrentMeritId: getCurrentMeritId,
|
||||
buildMeritRecordBody: buildMeritRecordBody,
|
||||
validateMeritRecordBody: validateMeritRecordBody,
|
||||
normalizeMeritRecord: normalizeMeritRecord,
|
||||
matchesSavedMerit: matchesSavedMerit,
|
||||
isEditableMerit: isEditableMerit,
|
||||
renderMeritDetail: renderMeritDetail,
|
||||
shouldRedirectToLogin: shouldRedirectToLogin,
|
||||
isForbidden: isForbidden,
|
||||
init: init
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user