(function (root, factory) { if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } root.GrowthPages = factory(root); if (root.document) { root.document.addEventListener('DOMContentLoaded', function () { root.GrowthPages.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, rootNode) { return documentRef ? (rootNode || documentRef).querySelector(selector) : null; } function queryAll(selector, rootNode) { return documentRef ? Array.prototype.slice.call((rootNode || documentRef).querySelectorAll(selector)) : []; } function getQueryParam(search, name) { var params = new URLSearchParams(String(search || '').replace(/^\?/, '')); return params.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 fromProfile; if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) { fromProfile = root.ProfileUI.getGenealogyId(); if (fromProfile) return normalizeId(fromProfile); } return normalizeId(getQueryParam(source, 'genealogyId')); } function getCurrentRecordId(search) { var source = search === undefined && root.location ? root.location.search : search; return normalizeId(getQueryParam(source, 'recordId')); } function trimOrUndefined(value) { var text = String(value === undefined || value === null ? '' : value).trim(); return text || undefined; } function toSafeInteger(value) { var text = trimOrUndefined(value); var number; if (text === undefined) return undefined; number = Number(text); return Number.isSafeInteger(number) ? number : undefined; } 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 toDateInputValue(value) { var text = String(value || ''); return /^\d{4}-\d{2}-\d{2}/.test(text) ? text.slice(0, 10) : ''; } 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 buildGrowthRecordBody(values) { var source = values || {}; var body = { recordTitle: String(source.recordTitle || '').trim() }; var lineagePersonId = trimOrUndefined(source.lineagePersonId); var recordType = trimOrUndefined(source.recordType); var recordContent = trimOrUndefined(source.recordContent); var recordDate = trimOrUndefined(source.recordDate); var remindTime = toBackendDateTime(source.remindTime); var mediaOssIds = trimOrUndefined(source.mediaOssIds); var sortOrderText = trimOrUndefined(source.sortOrder); var status = trimOrUndefined(source.status); if (lineagePersonId !== undefined) body.lineagePersonId = lineagePersonId; if (recordType !== undefined) body.recordType = recordType; if (recordContent !== undefined) body.recordContent = recordContent; if (recordDate !== undefined) body.recordDate = recordDate; if (remindTime !== undefined) body.remindTime = remindTime; 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 validateGrowthRecordBody(body) { if (!body || !body.recordTitle) return '请填写记录标题'; if (body.lineagePersonId !== undefined && !normalizeId(body.lineagePersonId)) 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.recordDate !== undefined && !/^\d{4}-\d{2}-\d{2}$/.test(body.recordDate)) return '记录日期格式无效'; if (body.remindTime !== undefined && !/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(body.remindTime)) { 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 normalizeGrowthRecord(item) { var recordId = normalizeId(item && item.recordId); var genealogyId = normalizeId(item && item.genealogyId); var appUserId = normalizeId(item && item.appUserId); var lineagePersonId = normalizeId(item && item.lineagePersonId); var title = String(item && item.recordTitle || '').trim(); var status = stringValue(item && item.status); var mediaFiles = MediaDisplay ? MediaDisplay.normalizeFileList(item && item.mediaFiles) : []; var sortOrder = optionalSafeInteger(item && item.sortOrder); var record; if (!recordId || !genealogyId || !title || (status !== '0' && status !== '1')) return null; if (item.appUserId !== undefined && item.appUserId !== null && item.appUserId !== '' && !appUserId) return null; if (item.lineagePersonId !== undefined && item.lineagePersonId !== null && item.lineagePersonId !== '' && !lineagePersonId) return null; if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null; record = { recordId: recordId, genealogyId: genealogyId, genealogyNo: stringValue(item.genealogyNo), genealogyName: stringValue(item.genealogyName), surname: stringValue(item.surname), appUserNickName: stringValue(item.appUserNickName), appUserPhone: stringValue(item.appUserPhone), lineagePersonNo: stringValue(item.lineagePersonNo), lineagePersonName: stringValue(item.lineagePersonName), recordType: stringValue(item.recordType), recordTitle: title, recordContent: stringValue(item.recordContent), recordDate: stringValue(item.recordDate), remindTime: stringValue(item.remindTime), status: status, remark: stringValue(item.remark) }; if (mediaFiles.length) record.mediaFiles = mediaFiles; if (appUserId) record.appUserId = appUserId; if (lineagePersonId) record.lineagePersonId = lineagePersonId; if (sortOrder !== undefined) record.sortOrder = sortOrder; return record; } function matchesSavedRecord(item, expectedRecordId) { var record = normalizeGrowthRecord(item); return Boolean(record && record.recordId === normalizeId(expectedRecordId)); } function normalizeLineageOption(item) { var personId = normalizeId(item && (item.personId || item.lineagePersonId)); var name = String(item && (item.name || item.lineagePersonName) || '').trim(); if (!personId || !name) return null; return { personId: personId, name: name, generationName: stringValue(item.generationName) }; } function escapeHtml(value) { return stringValue(value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function renderLineageOptions(data, selectedId, fallbackName) { var selected = normalizeId(selectedId); var options = Array.isArray(data) ? data.map(normalizeLineageOption).filter(Boolean) : []; var selectedFound = options.some(function (option) { return option.personId === selected; }); if (selected && !selectedFound && fallbackName) { options.push({ personId: selected, name: String(fallbackName), generationName: '' }); } return '' + options.map(function (option) { return ''; }).join(''); } function attachmentCount(mediaFiles) { return Array.isArray(mediaFiles) ? mediaFiles.length : 0; } function buildListUrl(genealogyId, recordId) { var url = 'profile-growth.html?genealogyId=' + encodeURIComponent(genealogyId); return recordId ? url + '&recordId=' + encodeURIComponent(recordId) : url; } function buildEditUrl(genealogyId, recordId) { var url = 'profile-growth-edit.html?genealogyId=' + encodeURIComponent(genealogyId); return recordId ? url + '&recordId=' + encodeURIComponent(recordId) : url; } function canEditRecords(genealogy) { return Boolean(genealogy && (genealogy.canEditContent === true || genealogy.canManage === true)); } function renderGrowthRow(record) { var summary = []; var actions = ''; if (record.lineagePersonName) summary.push(record.lineagePersonName); if (record.recordType) summary.push(record.recordType); if (record.recordDate) summary.push(record.recordDate); if (!summary.length) summary.push('未填写日期和关联人物'); if (canEditContent) { actions += '编辑'; } return '

' + escapeHtml(record.recordTitle) + '

' + escapeHtml(summary.join(' · ')) + '

' + actions + '
'; } function detailValue(label, value) { return '

' + escapeHtml(label) + '' + escapeHtml(value || '未提供') + '

'; } function renderGrowthDetail(record) { var count; var actions = ''; if (!record) return stateHtml('empty', '请选择一条成长记录查看详情'); count = attachmentCount(record.mediaFiles); if (canEditContent) { actions = '
编辑记录' + '
'; } return (MediaDisplay ? MediaDisplay.renderGallery(record.mediaFiles, { label: '成长记录附件' }) : '') + '
' + detailValue('标题', record.recordTitle) + detailValue('关联人物', record.lineagePersonName || record.lineagePersonNo) + detailValue('记录类型', record.recordType) + detailValue('记录日期', record.recordDate) + detailValue('提醒时间', record.remindTime) + detailValue('记录内容', record.recordContent) + detailValue('附件', count ? count + ' 个附件' : '未上传') + detailValue('记录人', record.appUserNickName) + detailValue('状态', record.status === '0' ? '正常' : '停用') + detailValue('备注', record.remark) + '
' + actions; } function showMessage(message) { if (root.layui && root.layui.layer) { root.layui.layer.msg(message); return; } 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 syncGenealogyLinks() { if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) root.ProfileUI.syncGenealogyContextLinks(); } function stateHtml(type, message) { if (root.ProfileUI && root.ProfileUI.renderApiState) return root.ProfileUI.renderApiState(type, message); return '
' + escapeHtml(message) + '
'; } 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 status = query('[data-growth-form-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 setEditorEnabled(enabled, message, type) { var placeholder = query('[data-growth-editor-placeholder]'); queryAll('[data-growth-editor], [data-growth-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-growth-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 renderNoContext() { var list = query('[data-growth-list]'); var detail = query('[data-growth-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 setWritePending(pending, label) { var submit = query('[data-growth-form] [type="submit"]'); writePending = Boolean(pending); queryAll('[data-growth-form] input, [data-growth-form] textarea, [data-growth-form] select, [data-growth-form] button, [data-growth-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 renderGrowthRecords(data) { var container = query('[data-growth-list]'); var normalized = Array.isArray(data) ? data.map(normalizeGrowthRecord) : []; var invalidCount = normalized.filter(function (record) { return !record; }).length; var records = normalized.filter(Boolean); recordsById = Object.create(null); records.forEach(function (record) { recordsById[record.recordId] = record; }); if (!container) return records; if (!Array.isArray(data) || invalidCount) { setState(container, 'error', '成长记录响应缺少稳定 GrowthRecordVo 字段,请联系后端核对。'); return []; } if (!records.length) { setState(container, 'empty', '暂无成长记录'); return []; } container.innerHTML = records.map(renderGrowthRow).join(''); return records; } function renderDetail(record) { var container = query('[data-growth-detail]'); if (container) container.innerHTML = renderGrowthDetail(record); } async function loadGrowthDetail(recordId) { var api = getApi(); var genealogyId = requireGenealogyId(); var record; if (!genealogyId || !normalizeId(recordId) || redirectUnauthorized(api)) return null; setState(query('[data-growth-detail]'), 'loading', '正在加载成长记录详情…'); try { record = normalizeGrowthRecord(await api.growthRecordDetail(genealogyId, recordId)); if (!record) throw new Error('成长记录详情响应缺少稳定 GrowthRecordVo 字段'); recordsById[record.recordId] = record; renderDetail(record); return record; } catch (error) { if (redirectUnauthorized(api, error)) return null; setState( query('[data-growth-detail]'), getApiStateType(error), isForbidden(error) ? '当前账号无权查看该成长记录。' : '成长记录详情加载失败,请稍后重试。' ); showMessage(isForbidden(error) ? '当前账号无权查看该成长记录。' : (error.message || '成长记录详情加载失败')); return null; } } async function loadGrowthRecords() { var api = getApi(); var genealogyId; var records; var requestedRecordId; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; syncGenealogyLinks(); setState(query('[data-growth-list]'), 'loading', '正在加载成长记录…'); try { await loadCapability(api, genealogyId); records = renderGrowthRecords(await api.growthRecords(genealogyId)); requestedRecordId = getCurrentRecordId(); if (requestedRecordId && recordsById[requestedRecordId]) { await loadGrowthDetail(requestedRecordId); } else if (records.length) { await loadGrowthDetail(records[0].recordId); } else { renderDetail(null); } } catch (error) { if (redirectUnauthorized(api, error)) return; setEditAccess(false); setState( query('[data-growth-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-growth-form] [name="' + name + '"]'); if (field) field.value = value === undefined || value === null ? '' : String(value); } function fillGrowthForm(record) { var contentField; var editorInstance; setFieldValue('lineagePersonId', record.lineagePersonId); setFieldValue('recordType', record.recordType); setFieldValue('recordTitle', record.recordTitle); setFieldValue('recordContent', record.recordContent); setFieldValue('recordDate', toDateInputValue(record.recordDate)); setFieldValue('remindTime', toDateTimeInputValue(record.remindTime)); setFieldValue('mediaOssIds', record.mediaOssIds); setFieldValue('sortOrder', record.sortOrder); setFieldValue('status', '0'); contentField = query('[data-growth-form] [name="recordContent"]'); if (contentField && root.AppRichEditor && root.AppRichEditor.init) { editorInstance = root.AppRichEditor.init(contentField); if (editorInstance && editorInstance.editor && editorInstance.editor.setHtml) { editorInstance.editor.setHtml(record.recordContent || ''); } } if (query('[data-growth-media-status]')) { query('[data-growth-media-status]').textContent = record.mediaOssIds ? '已保留 ' + attachmentCount(record.mediaOssIds) + ' 个附件,可继续选择追加' : '未选择文件'; } } function applyLineageOptions(data, selectedId, fallbackName) { var select = query('[data-growth-lineage-person]'); if (select) select.innerHTML = renderLineageOptions(data, selectedId, fallbackName); } async function loadGrowthEditor() { var api = getApi(); var genealogyId; var recordId; var result; var record; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; recordId = getCurrentRecordId(); syncGenealogyLinks(); setEditorEnabled(false, '正在加载成长记录编辑信息…', 'loading'); setFormStatus('正在读取家谱权限…', 'loading'); try { await loadCapability(api, genealogyId); if (!canEditContent) throw { status: 403, message: '当前账号无权维护该家谱的成长记录。' }; result = await Promise.all([ api.lineagePersonOptions(genealogyId), recordId ? api.growthRecordDetail(genealogyId, recordId) : Promise.resolve(null) ]); record = recordId ? normalizeGrowthRecord(result[1]) : null; if (recordId && !record) throw new Error('成长记录详情响应缺少稳定 GrowthRecordVo 字段'); applyLineageOptions(result[0], record && record.lineagePersonId, record && record.lineagePersonName); if (record) { fillGrowthForm(record); if (query('[data-growth-editor-title]')) query('[data-growth-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 submitGrowthRecord(form) { var api = getApi(); var genealogyId = requireGenealogyId(); var recordId = getCurrentRecordId(); var body; var validation; var result; var saved; var savedId; var verifiedDetail; if (writePending || !canEditContent || !genealogyId || redirectUnauthorized(api)) return; body = buildGrowthRecordBody(getFormValues(form)); validation = validateGrowthRecordBody(body); if (validation) { setFormStatus(validation, 'error'); return; } delete body.invalidSortOrder; setWritePending(true, '正在保存…'); setFormStatus('正在保存成长记录…', 'loading'); try { result = recordId ? await api.updateGrowthRecord(genealogyId, recordId, body) : await api.createGrowthRecord(genealogyId, body); saved = normalizeGrowthRecord(result); savedId = recordId || (saved && saved.recordId); if (!savedId) throw new Error('保存响应缺少 recordId'); verifiedDetail = await api.growthRecordDetail(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 deleteGrowthRecord(recordId) { var api = getApi(); var genealogyId = requireGenealogyId(); var record = recordsById[recordId]; if (writePending || !canEditContent || !genealogyId || !normalizeId(recordId) || redirectUnauthorized(api)) return; if (!await confirmAction('确认删除“' + (record ? record.recordTitle : '该成长记录') + '”吗?删除后不可恢复,并会释放附件引用。')) return; setWritePending(true); try { await api.deleteGrowthRecord(genealogyId, recordId); await loadGrowthRecords(); showMessage('成长记录已删除'); } catch (error) { if (redirectUnauthorized(api, error)) return; showMessage(isForbidden(error) ? '当前账号无权删除该成长记录。' : (error.message || '成长记录删除失败')); } finally { setWritePending(false); } } function clearMediaSelection() { var target = query('[data-growth-form] [name="mediaOssIds"]'); var status = query('[data-growth-media-status]'); var fileInput = query('[data-growth-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-growth-detail-id]'); var deleteButton = event.target.closest('[data-growth-delete-id]'); var clearMediaButton = event.target.closest('[data-growth-clear-media]'); var recordId; var genealogyId; if (detailButton) { event.preventDefault(); recordId = detailButton.getAttribute('data-growth-detail-id'); genealogyId = requireGenealogyId(); if (root.history && genealogyId) root.history.replaceState(null, '', buildListUrl(genealogyId, recordId)); loadGrowthDetail(recordId).then(function (record) { if (record && root.ProfileUI && root.ProfileUI.revealDetail) { root.ProfileUI.revealDetail(query('[data-growth-detail-heading]')); } }); return; } if (deleteButton) { event.preventDefault(); deleteGrowthRecord(deleteButton.getAttribute('data-growth-delete-id')); return; } if (clearMediaButton) { event.preventDefault(); clearMediaSelection(); } }); documentRef.addEventListener('submit', function (event) { var form = event.target.closest('[data-growth-form]'); if (!form) return; event.preventDefault(); submitGrowthRecord(form); }); } function init() { if (!documentRef) return; bindActions(); if (query('[data-growth-page]')) loadGrowthRecords(); if (query('[data-growth-edit-page]')) loadGrowthEditor(); } return { getCurrentGenealogyId: getCurrentGenealogyId, getCurrentRecordId: getCurrentRecordId, buildGrowthRecordBody: buildGrowthRecordBody, validateGrowthRecordBody: validateGrowthRecordBody, normalizeGrowthRecord: normalizeGrowthRecord, matchesSavedRecord: matchesSavedRecord, canEditRecords: canEditRecords, renderLineageOptions: renderLineageOptions, renderGrowthDetail: renderGrowthDetail, shouldRedirectToLogin: shouldRedirectToLogin, isForbidden: isForbidden, init: init }; });