(function (root, factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.MemoPages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.MemoPages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var documentRef = root.document;
var memosById = Object.create(null);
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 getCurrentMemoId(search) {
var source = search === undefined && root.location ? root.location.search : search;
return normalizeId(queryParam(source, 'memoId'));
}
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 toSafeInteger(value) {
var text = trimOrUndefined(value);
var number;
if (!text) return undefined;
number = Number(text);
return Number.isSafeInteger(number) ? number : undefined;
}
function buildMemoBody(values) {
var source = values || {};
var body = { memoTitle: String(source.memoTitle || '').trim() };
var memoContent = trimOrUndefined(source.memoContent);
var remindTime = toBackendDateTime(source.remindTime);
var completed = trimOrUndefined(source.completed);
var mediaOssIds = trimOrUndefined(source.mediaOssIds);
var sortOrderText = trimOrUndefined(source.sortOrder);
var status = trimOrUndefined(source.status);
if (memoContent !== undefined) body.memoContent = memoContent;
if (remindTime !== undefined) body.remindTime = remindTime;
if (completed !== undefined) body.completed = completed;
if (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 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 validateMemoBody(body) {
if (!body || !body.memoTitle) return '请填写备忘录标题';
if (body.remindTime !== undefined && !isValidBackendDateTime(body.remindTime)) return '提醒时间格式无效';
if (body.completed !== undefined && body.completed !== '0' && body.completed !== '1') {
return '完成状态只能是未完成或已完成';
}
if (body.mediaOssIds !== undefined && !/^[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 stringValue(value) {
return value === undefined || value === null ? '' : String(value);
}
function optionalSafeInteger(value) {
var number;
if (value === undefined || value === null || value === '') return undefined;
number = Number(value);
return Number.isSafeInteger(number) ? number : undefined;
}
function normalizeMemo(item) {
var memoId = normalizeId(item && item.memoId);
var genealogyId = normalizeId(item && item.genealogyId);
var appUserId = normalizeId(item && item.appUserId);
var memoTitle = String(item && item.memoTitle || '').trim();
var completed = stringValue(item && item.completed);
var mediaOssIds = stringValue(item && item.mediaOssIds);
var status = stringValue(item && item.status);
var sortOrder = optionalSafeInteger(item && item.sortOrder);
var memo;
if (!memoId || !genealogyId || !memoTitle || (completed !== '0' && completed !== '1') || (status !== '0' && status !== '1')) {
return null;
}
if (item.appUserId !== undefined && item.appUserId !== null && item.appUserId !== '' && !appUserId) return null;
if (mediaOssIds && !/^[1-9][0-9]*(,[1-9][0-9]*)*$/.test(mediaOssIds)) return null;
if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null;
if (item.remindTime && !isValidBackendDateTime(String(item.remindTime))) return null;
memo = {
memoId: memoId,
genealogyId: genealogyId,
genealogyNo: stringValue(item.genealogyNo),
genealogyName: stringValue(item.genealogyName),
surname: stringValue(item.surname),
appUserNickName: stringValue(item.appUserNickName),
appUserPhone: stringValue(item.appUserPhone),
memoTitle: memoTitle,
memoContent: stringValue(item.memoContent),
remindTime: stringValue(item.remindTime),
completed: completed,
mediaOssIds: mediaOssIds,
status: status,
remark: stringValue(item.remark)
};
if (appUserId) memo.appUserId = appUserId;
if (sortOrder !== undefined) memo.sortOrder = sortOrder;
return memo;
}
function matchesSavedMemo(item, expectedMemoId) {
var memo = normalizeMemo(item);
return Boolean(memo && memo.memoId === normalizeId(expectedMemoId));
}
function isEditableMemo(memo) {
return Boolean(memo && memo.status === '0');
}
function escapeHtml(value) {
return stringValue(value)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function attachmentCount(mediaOssIds) {
return mediaOssIds ? mediaOssIds.split(',').filter(Boolean).length : 0;
}
function buildListUrl(genealogyId, memoId) {
var url = 'profile-memo.html?genealogyId=' + encodeURIComponent(genealogyId);
return memoId ? url + '&memoId=' + encodeURIComponent(memoId) : url;
}
function buildEditUrl(genealogyId, memoId) {
var url = 'profile-memo-edit.html?genealogyId=' + encodeURIComponent(genealogyId);
return memoId ? url + '&memoId=' + encodeURIComponent(memoId) : url;
}
function renderMemoRow(memo) {
var summary = [];
summary.push(memo.completed === '1' ? '已完成' : '未完成');
if (memo.remindTime) summary.push('提醒:' + memo.remindTime);
if (memo.memoContent) summary.push(memo.memoContent);
return '' + escapeHtml(memo.memoTitle) + '
' +
escapeHtml(summary.join(' · ')) + '
' +
'
' +
'
编辑' +
'
' +
'
';
}
function detailValue(label, value) {
return '
' + escapeHtml(label) + '' + escapeHtml(value || '未提供') + '
';
}
function renderMemoDetail(memo) {
var count;
if (!memo) return '请选择一条备忘录查看详情
';
count = attachmentCount(memo.mediaOssIds);
return '' +
detailValue('备忘标题', memo.memoTitle) +
detailValue('备忘内容', memo.memoContent) +
detailValue('提醒时间', memo.remindTime) +
detailValue('完成状态', memo.completed === '1' ? '已完成' : '未完成') +
detailValue('附件', count ? count + ' 个附件' : '未上传') +
detailValue('记录人', memo.appUserNickName) +
detailValue('备注', memo.remark) +
'
';
}
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-memo-form-status]');
if (target) target.textContent = message || '';
}
function setEditorEnabled(enabled, message) {
var placeholder = query('[data-memo-editor-placeholder]');
queryAll('[data-memo-editor], [data-memo-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-memo-form] button, [data-memo-form] input, [data-memo-form] textarea, [data-memo-form] select, [data-memo-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-memo-list]');
var detail = query('[data-memo-detail]');
var message = '请先选择家谱,再查看或维护备忘录。';
if (list) list.innerHTML = '' + message + '
';
if (detail) detail.innerHTML = '当前没有家谱上下文。
';
setEditorEnabled(false, message);
setFormStatus(message);
}
function requireGenealogyId() {
var genealogyId = getCurrentGenealogyId();
if (!genealogyId) renderNoContext();
return genealogyId;
}
function renderMemos(data) {
var container = query('[data-memo-list]');
var normalized = Array.isArray(data) ? data.map(normalizeMemo) : [];
var invalidCount = normalized.filter(function (memo) { return !memo; }).length;
var memos = normalized.filter(Boolean);
memosById = Object.create(null);
memos.forEach(function (memo) {
memosById[memo.memoId] = memo;
});
if (!container) return memos;
if (!Array.isArray(data) || invalidCount) {
container.innerHTML = '备忘录响应缺少稳定 MemoVo 字段,请联系后端核对。
';
return [];
}
if (!memos.length) {
container.innerHTML = '暂无备忘录
';
return [];
}
container.innerHTML = memos.map(renderMemoRow).join('');
return memos;
}
function renderDetail(memo) {
var container = query('[data-memo-detail]');
if (container) container.innerHTML = renderMemoDetail(memo);
}
async function loadMemoDetail(memoId) {
var api = getApi();
var genealogyId = requireGenealogyId();
var memo;
if (!genealogyId || !normalizeId(memoId) || redirectUnauthorized(api)) return null;
try {
memo = normalizeMemo(await api.memoDetail(genealogyId, memoId));
if (!memo) throw new Error('备忘录详情响应缺少稳定 MemoVo 字段');
memosById[memo.memoId] = memo;
renderDetail(memo);
return memo;
} catch (error) {
if (redirectUnauthorized(api, error)) return null;
renderDetail(null);
showMessage(isForbidden(error) ? '当前账号无权查看该备忘录。' : (error.message || '备忘录详情加载失败'));
return null;
}
}
async function loadMemos() {
var api = getApi();
var genealogyId;
var memos;
var requestedMemoId;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
syncLinks();
try {
memos = renderMemos(await api.memos(genealogyId));
requestedMemoId = getCurrentMemoId();
if (requestedMemoId && memosById[requestedMemoId]) {
await loadMemoDetail(requestedMemoId);
} else if (memos.length) {
await loadMemoDetail(memos[0].memoId);
} else {
renderDetail(null);
}
} catch (error) {
if (redirectUnauthorized(api, error)) return;
if (query('[data-memo-list]')) {
query('[data-memo-list]').innerHTML = '' +
(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-memo-form] [name="' + name + '"]');
if (field) field.value = value === undefined || value === null ? '' : String(value);
}
function fillMemoForm(memo) {
if (!isEditableMemo(memo)) throw new Error('停用备忘录无法通过当前 PC 接口重新读取或编辑');
setFieldValue('memoTitle', memo.memoTitle);
setFieldValue('memoContent', memo.memoContent);
setFieldValue('remindTime', toDateTimeInputValue(memo.remindTime));
setFieldValue('completed', memo.completed);
setFieldValue('mediaOssIds', memo.mediaOssIds);
setFieldValue('sortOrder', memo.sortOrder);
setFieldValue('status', '0');
if (query('[data-memo-media-status]')) {
query('[data-memo-media-status]').textContent = memo.mediaOssIds
? '已保留 ' + attachmentCount(memo.mediaOssIds) + ' 个附件,可继续选择追加'
: '未选择文件';
}
}
async function loadMemoEditor() {
var api = getApi();
var genealogyId;
var memoId;
var memo;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
memoId = getCurrentMemoId();
syncLinks();
setEditorEnabled(false, '正在加载备忘录编辑信息…');
try {
if (memoId) {
memo = normalizeMemo(await api.memoDetail(genealogyId, memoId));
if (!memo) throw new Error('备忘录详情响应缺少稳定 MemoVo 字段');
fillMemoForm(memo);
if (query('[data-memo-editor-title]')) query('[data-memo-editor-title]').textContent = '编辑备忘录';
}
setEditorEnabled(true);
} catch (error) {
if (redirectUnauthorized(api, error)) return;
setEditorEnabled(false, isForbidden(error)
? '当前账号无权维护该家谱的备忘录。'
: (error.message || '备忘录编辑信息加载失败'));
setFormStatus(error.message || '备忘录编辑信息加载失败');
}
}
async function submitMemo(form) {
var api = getApi();
var genealogyId = requireGenealogyId();
var memoId = getCurrentMemoId();
var body;
var validation;
var result;
var saved;
var savedId;
var verifiedDetail;
if (writePending || !genealogyId || redirectUnauthorized(api)) return;
body = buildMemoBody(getFormValues(form));
validation = validateMemoBody(body);
if (validation) {
setFormStatus(validation);
return;
}
delete body.invalidSortOrder;
setWritePending(true);
setFormStatus('正在保存备忘录…');
try {
result = memoId
? await api.updateMemo(genealogyId, memoId, body)
: await api.createMemo(genealogyId, body);
saved = normalizeMemo(result);
savedId = memoId || (saved && saved.memoId);
if (!savedId) throw new Error('保存响应缺少 memoId');
verifiedDetail = await api.memoDetail(genealogyId, savedId);
if (!matchesSavedMemo(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 deleteMemo(memoId) {
var api = getApi();
var genealogyId = requireGenealogyId();
var memo = memosById[memoId];
if (writePending || !genealogyId || !normalizeId(memoId) || redirectUnauthorized(api)) return;
if (root.confirm && !root.confirm('确认删除“' + (memo ? memo.memoTitle : '该备忘录') + '”吗?删除后不可恢复,并会释放附件引用。')) return;
setWritePending(true);
try {
await api.deleteMemo(genealogyId, memoId);
await loadMemos();
showMessage('备忘录已删除');
} catch (error) {
if (redirectUnauthorized(api, error)) return;
showMessage(isForbidden(error) ? '当前账号无权删除该备忘录。' : (error.message || '备忘录删除失败'));
} finally {
setWritePending(false);
}
}
function clearMediaSelection() {
var target = query('[data-memo-form] [name="mediaOssIds"]');
var status = query('[data-memo-media-status]');
var fileInput = query('[data-memo-media-input]');
if (target) target.value = '';
if (fileInput) fileInput.value = '';
if (status) status.textContent = '未选择文件';
}
function bindActions() {
if (!documentRef) return;
documentRef.addEventListener('click', function (event) {
var detailButton = event.target.closest('[data-memo-detail-id]');
var deleteButton = event.target.closest('[data-memo-delete-id]');
var clearMediaButton = event.target.closest('[data-memo-clear-media]');
if (detailButton) {
event.preventDefault();
loadMemoDetail(detailButton.getAttribute('data-memo-detail-id'));
return;
}
if (deleteButton) {
event.preventDefault();
deleteMemo(deleteButton.getAttribute('data-memo-delete-id'));
return;
}
if (clearMediaButton) {
event.preventDefault();
clearMediaSelection();
}
});
documentRef.addEventListener('submit', function (event) {
var form = event.target.closest('[data-memo-form]');
if (!form) return;
event.preventDefault();
submitMemo(form);
});
}
function init() {
if (!documentRef) return;
bindActions();
if (query('[data-memo-page]')) loadMemos();
if (query('[data-memo-edit-page]')) loadMemoEditor();
}
return {
getCurrentGenealogyId: getCurrentGenealogyId,
getCurrentMemoId: getCurrentMemoId,
buildMemoBody: buildMemoBody,
validateMemoBody: validateMemoBody,
normalizeMemo: normalizeMemo,
matchesSavedMemo: matchesSavedMemo,
isEditableMemo: isEditableMemo,
renderMemoDetail: renderMemoDetail,
shouldRedirectToLogin: shouldRedirectToLogin,
isForbidden: isForbidden,
init: init
};
});