(function (root, factory) { if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } root.CeremonyAdminPages = factory(root); if (root.document) { root.document.addEventListener('DOMContentLoaded', function () { root.CeremonyAdminPages.init(); }); } })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; var documentRef = root.document; var ceremoniesById = Object.create(null); var giftsById = Object.create(null); var inviteeOptionsByUserId = Object.create(null); var currentCeremony = null; var canEditContent = false; var writePending = false; var invitationStatuses = ['PENDING', 'ACCEPTED', 'DECLINED', 'CANCELED']; var invitationStatusLabels = { PENDING: '待响应', ACCEPTED: '已接受', DECLINED: '已拒绝', CANCELED: '已取消' }; 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 stringValue(value) { return value === undefined || value === null ? '' : String(value); } function trimOrUndefined(value) { var text = stringValue(value).trim(); return text || undefined; } 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 normalizeNullableId(value) { if (value === undefined || value === null || value === '') return ''; return normalizeId(value); } function queryParam(search, name) { return new URLSearchParams(stringValue(search).replace(/^\?/, '')).get(name) || ''; } function getCurrentGenealogyId(search) { var source = search === undefined && root.location ? root.location.search : search; var contextId; if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) { contextId = root.ProfileUI.getGenealogyId(); if (contextId) return normalizeId(contextId); } return normalizeId(queryParam(source, 'genealogyId')); } function getCurrentCeremonyId(search) { var source = search === undefined && root.location ? root.location.search : search; return normalizeId(queryParam(source, 'ceremonyId')); } function optionalSafeInteger(value) { var number; if (value === undefined || value === null || value === '') return undefined; number = Number(value); return Number.isSafeInteger(number) ? number : undefined; } function optionalFiniteNumber(value) { var number; if (value === undefined || value === null || value === '') return undefined; number = Number(value); return Number.isFinite(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 toDateTimeInputValue(value) { var text = stringValue(value); if (!/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/.test(text)) return ''; return text.slice(0, 16).replace(' ', 'T'); } function isValidBackendDateTime(value) { var match = stringValue(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 buildCeremonyBody(values) { var source = values || {}; var body = { ceremonyType: stringValue(source.ceremonyType).trim(), ceremonyTitle: stringValue(source.ceremonyTitle).trim(), status: '0' }; var optionalNames = ['ceremonyDesc', 'location', 'locationAddress']; var ceremonyTime = toBackendDateTime(source.ceremonyTime); var longitude = optionalFiniteNumber(source.longitude); var latitude = optionalFiniteNumber(source.latitude); var coverOssId = normalizeNullableId(source.coverOssId); var sortOrder = optionalSafeInteger(source.sortOrder); optionalNames.forEach(function (name) { var value = trimOrUndefined(source[name]); if (value !== undefined) body[name] = value; }); if (ceremonyTime !== undefined) body.ceremonyTime = ceremonyTime; if (source.longitude !== undefined && source.longitude !== null && source.longitude !== '') { if (longitude !== undefined) body.longitude = longitude; else body.invalidLongitude = true; } if (source.latitude !== undefined && source.latitude !== null && source.latitude !== '') { if (latitude !== undefined) body.latitude = latitude; else body.invalidLatitude = true; } if (source.coverOssId !== undefined && source.coverOssId !== null && source.coverOssId !== '') { if (coverOssId) body.coverOssId = coverOssId; else body.invalidCoverOssId = true; } if (source.sortOrder !== undefined && source.sortOrder !== null && source.sortOrder !== '') { if (sortOrder !== undefined) body.sortOrder = sortOrder; else body.invalidSortOrder = true; } return body; } function validateCeremonyBody(body) { if (!body || !body.ceremonyType) return '请填写活动类型'; if (!body.ceremonyTitle) return '请填写活动标题'; if (body.ceremonyTime !== undefined && !isValidBackendDateTime(body.ceremonyTime)) return '活动时间格式无效'; if ((body.longitude === undefined) !== (body.latitude === undefined)) return '经度和纬度必须同时提供'; if (body.invalidLongitude) return '经度必须是有效数字'; if (body.invalidLatitude) return '纬度必须是有效数字'; if (body.longitude !== undefined && (body.longitude < -180 || body.longitude > 180)) return '经度范围必须是 -180 到 180'; if (body.latitude !== undefined && (body.latitude < -90 || body.latitude > 90)) return '纬度范围必须是 -90 到 90'; if (stringValue(body.locationAddress).length > 300) return '详细地址不能超过 300 个字符'; if (body.invalidCoverOssId) return '封面文件编号无效,请重新选择文件'; if (body.invalidSortOrder) return '排序值必须是安全整数'; if (body.status === '1') return '当前 PC 无法重新读取停用活动,暂不开放停用'; if (body.status !== '0') return '活动状态只能是 0'; return ''; } function buildCeremonyGiftBody(values) { var source = values || {}; var body = {}; var giverName = trimOrUndefined(source.giverName); var giftMessage = trimOrUndefined(source.giftMessage); var giftAmount = optionalFiniteNumber(source.giftAmount); if (giverName !== undefined) body.giverName = giverName; if (source.giftAmount !== undefined && source.giftAmount !== null && source.giftAmount !== '') { if (giftAmount !== undefined) body.giftAmount = giftAmount; else body.invalidGiftAmount = true; } if (giftMessage !== undefined) body.giftMessage = giftMessage; return body; } function validateCeremonyGiftBody(body) { if (!body || (body.giftAmount === undefined && !body.invalidGiftAmount)) return '请填写礼金金额'; if (body.invalidGiftAmount) return '礼金金额必须是有效数字'; if (body.giftAmount < 0) return '礼金金额不能小于 0'; return ''; } function normalizeCeremony(item) { var ceremonyId = normalizeId(item && item.ceremonyId); var genealogyId = normalizeId(item && item.genealogyId); var sponsorUserId = normalizeNullableId(item && item.sponsorUserId); var coverOssId = normalizeNullableId(item && item.coverOssId); var ceremonyType = stringValue(item && item.ceremonyType).trim(); var ceremonyTitle = stringValue(item && item.ceremonyTitle).trim(); var longitude = optionalFiniteNumber(item && item.longitude); var latitude = optionalFiniteNumber(item && item.latitude); var giftCount = optionalSafeInteger(item && item.giftCount); var giftAmount = optionalFiniteNumber(item && item.giftAmount); var sortOrder = optionalSafeInteger(item && item.sortOrder); var status = stringValue(item && item.status); var ceremony; if (!ceremonyId || !genealogyId || !ceremonyType || !ceremonyTitle) return null; if (item.sponsorUserId !== undefined && item.sponsorUserId !== null && item.sponsorUserId !== '' && !sponsorUserId) return null; if (item.coverOssId !== undefined && item.coverOssId !== null && item.coverOssId !== '' && !coverOssId) return null; if ((item.longitude !== undefined && item.longitude !== null && item.longitude !== '' && longitude === undefined) || (item.latitude !== undefined && item.latitude !== null && item.latitude !== '' && latitude === undefined)) return null; if ((longitude === undefined) !== (latitude === undefined)) return null; if (item.giftCount !== undefined && item.giftCount !== null && item.giftCount !== '' && giftCount === undefined) return null; if (item.giftAmount !== undefined && item.giftAmount !== null && item.giftAmount !== '' && giftAmount === undefined) return null; if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null; if (status !== '0' && status !== '1') return null; ceremony = { ceremonyId: ceremonyId, genealogyId: genealogyId, genealogyNo: stringValue(item.genealogyNo), genealogyName: stringValue(item.genealogyName), surname: stringValue(item.surname), sponsorNickName: stringValue(item.sponsorNickName), sponsorPhone: stringValue(item.sponsorPhone), ceremonyType: ceremonyType, ceremonyTitle: ceremonyTitle, ceremonyDesc: stringValue(item.ceremonyDesc), ceremonyTime: stringValue(item.ceremonyTime), location: stringValue(item.location), locationAddress: stringValue(item.locationAddress), status: status, remark: stringValue(item.remark) }; if (sponsorUserId) ceremony.sponsorUserId = sponsorUserId; if (coverOssId) ceremony.coverOssId = coverOssId; if (longitude !== undefined) ceremony.longitude = longitude; if (latitude !== undefined) ceremony.latitude = latitude; if (giftCount !== undefined) ceremony.giftCount = giftCount; if (giftAmount !== undefined) ceremony.giftAmount = giftAmount; if (sortOrder !== undefined) ceremony.sortOrder = sortOrder; return ceremony; } function normalizeCeremonyGift(item) { var giftId = normalizeId(item && item.giftId); var genealogyId = normalizeId(item && item.genealogyId); var ceremonyId = normalizeId(item && item.ceremonyId); var giverUserId = normalizeNullableId(item && item.giverUserId); var giftAmount = optionalFiniteNumber(item && item.giftAmount); var status = stringValue(item && item.status); var gift; if (!giftId || !genealogyId || !ceremonyId || giftAmount === undefined || giftAmount < 0) return null; if (item.giverUserId !== undefined && item.giverUserId !== null && item.giverUserId !== '' && !giverUserId) return null; if (status !== '0' && status !== '1') return null; gift = { giftId: giftId, genealogyId: genealogyId, genealogyNo: stringValue(item.genealogyNo), genealogyName: stringValue(item.genealogyName), surname: stringValue(item.surname), ceremonyId: ceremonyId, ceremonyTitle: stringValue(item.ceremonyTitle), giverNickName: stringValue(item.giverNickName), giverPhone: stringValue(item.giverPhone), giverName: stringValue(item.giverName), giftAmount: giftAmount, giftMessage: stringValue(item.giftMessage), giftTime: stringValue(item.giftTime), status: status, remark: stringValue(item.remark) }; if (giverUserId) gift.giverUserId = giverUserId; return gift; } function normalizeInvitation(item) { var invitationId = normalizeId(item && item.invitationId); var genealogyId = normalizeId(item && item.genealogyId); var ceremonyId = normalizeId(item && item.ceremonyId); var inviteeUserId = normalizeId(item && item.inviteeUserId); var inviteStatus = stringValue(item && item.inviteStatus); var inviteVersion = optionalSafeInteger(item && item.inviteVersion); var invitation; if (!invitationId || !genealogyId || !ceremonyId || !inviteeUserId || invitationStatuses.indexOf(inviteStatus) < 0) return null; if (item.inviteVersion !== undefined && item.inviteVersion !== null && item.inviteVersion !== '' && inviteVersion === undefined) return null; invitation = { invitationId: invitationId, genealogyId: genealogyId, ceremonyId: ceremonyId, inviteeUserId: inviteeUserId, inviteStatus: inviteStatus, deliveredTime: stringValue(item.deliveredTime), readTime: stringValue(item.readTime), responseTime: stringValue(item.responseTime) }; if (inviteVersion !== undefined) invitation.inviteVersion = inviteVersion; return invitation; } function normalizeInviteeOption(item) { var memberId = normalizeId(item && item.memberId); var genealogyId = normalizeId(item && item.genealogyId); var appUserId = normalizeNullableId(item && item.appUserId); var status = stringValue(item && item.status); if (!memberId || !genealogyId || !appUserId || status !== '0') return null; return { memberId: memberId, genealogyId: genealogyId, appUserId: appUserId, appUserNickName: stringValue(item.appUserNickName), memberName: stringValue(item.memberName), roleType: stringValue(item.roleType), status: status }; } function normalizeList(data, normalizer) { var normalized; if (!Array.isArray(data)) return []; normalized = data.map(normalizer); return normalized.some(function (item) { return !item; }) ? [] : normalized; } function buildInviteesBody(values) { var seen = Object.create(null); var ids = []; (Array.isArray(values) ? values : []).forEach(function (value) { var id = normalizeId(value); if (id && !seen[id]) { seen[id] = true; ids.push(id); } }); return { inviteeUserIds: ids }; } function escapeHtml(value) { return stringValue(value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function detailValue(label, value) { return '

' + escapeHtml(label) + '' + escapeHtml(value === '' || value === undefined ? '未提供' : value) + '

'; } function buildListUrl(genealogyId) { return 'profile-ceremony.html?genealogyId=' + encodeURIComponent(genealogyId); } function buildEditUrl(genealogyId, ceremonyId) { var url = 'profile-gift-edit.html?genealogyId=' + encodeURIComponent(genealogyId); return ceremonyId ? url + '&ceremonyId=' + encodeURIComponent(ceremonyId) : url; } function buildDetailUrl(genealogyId, ceremonyId) { return 'profile-ceremony-detail.html?genealogyId=' + encodeURIComponent(genealogyId) + '&ceremonyId=' + encodeURIComponent(ceremonyId); } function renderCeremonyDetail(ceremony) { var mapLink = ''; var actions = ''; if (!ceremony) return ''; if (ceremony.longitude !== undefined && ceremony.latitude !== undefined) { mapLink = ' 打开地图'; } if (canEditContent) { actions = '
编辑活动' + '
'; } return '
' + detailValue('活动标题', ceremony.ceremonyTitle) + detailValue('活动类型', ceremony.ceremonyType) + detailValue('活动说明', ceremony.ceremonyDesc) + detailValue('活动时间', ceremony.ceremonyTime) + detailValue('地点', ceremony.location) + '

详细地址' + escapeHtml(ceremony.locationAddress || '未提供') + mapLink + '

' + detailValue('发起人', ceremony.sponsorNickName) + detailValue('祭品数量', ceremony.giftCount === undefined ? '' : ceremony.giftCount) + detailValue('礼金合计', ceremony.giftAmount === undefined ? '' : ceremony.giftAmount) + detailValue('备注', ceremony.remark) + '
' + actions; } function renderCeremonyRow(ceremony) { var actions = '查看详情'; if (canEditContent) { actions += '编辑'; } return '

' + escapeHtml(ceremony.ceremonyTitle) + '

' + escapeHtml([ceremony.ceremonyType, ceremony.ceremonyTime, ceremony.location].filter(Boolean).join(' · ')) + '

' + actions + '
'; } function renderGiftList(data) { var gifts = Array.isArray(data) ? data.map(normalizeCeremonyGift) : []; if (gifts.some(function (gift) { return !gift; })) return ''; if (!gifts.length) return ''; return gifts.map(function (gift) { var action = canEditContent ? '' : ''; return '

' + escapeHtml(gift.giverName || gift.giverNickName || '匿名宗亲') + ' · ' + escapeHtml(gift.giftAmount) + '

' + escapeHtml([gift.giftMessage, gift.giftTime].filter(Boolean).join(' · ') || '未提供留言') + '

' + action + '
'; }).join(''); } function renderInvitationList(data) { var invitations = normalizeList(data, normalizeInvitation); if (!Array.isArray(data) || (data.length && !invitations.length)) return ''; if (!invitations.length) return ''; return invitations.map(function (invitation) { var option = inviteeOptionsByUserId[invitation.inviteeUserId]; var label = option && (option.memberName || option.appUserNickName) || '已邀请成员'; return '

' + escapeHtml(label) + '

' + escapeHtml(invitationStatusLabels[invitation.inviteStatus]) + (invitation.deliveredTime ? ' · ' + escapeHtml(invitation.deliveredTime) : '') + '

'; }).join(''); } 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 showMessage(message) { if (root.layui && root.layui.layer) root.layui.layer.msg(message); else if (root.alert) root.alert(message); } function getApiStateType(error) { return isForbidden(error) ? 'forbidden' : 'error'; } function setStatus(selector, message, type) { var target = query(selector); 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 syncLinks() { if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) root.ProfileUI.syncGenealogyContextLinks(); } function setWritePending(value) { writePending = Boolean(value); queryAll('[data-ceremony-form] input, [data-ceremony-form] textarea, [data-ceremony-form] button, [data-ceremony-gift-form] input, [data-ceremony-gift-form] textarea, [data-ceremony-gift-form] button, [data-ceremony-invitee-form] input, [data-ceremony-invitee-form] button, [data-ceremony-delete-id], [data-ceremony-gift-delete-id]').forEach(function (control) { control.disabled = writePending; }); } function setEditorEnabled(enabled, message) { var placeholder = query('[data-ceremony-editor-placeholder]'); queryAll('[data-ceremony-editor], [data-ceremony-editor-action], [data-ceremony-admin-editor]').forEach(function (element) { element.hidden = !enabled; }); if (placeholder) { placeholder.hidden = Boolean(enabled); if (message) placeholder.textContent = message; } } function renderNoContext() { var message = '请先选择家谱,再查看或维护祭祀活动。'; setStatus('[data-ceremony-list]', message, 'empty'); setStatus('[data-ceremony-detail]', '当前没有家谱上下文。', 'empty'); setStatus('[data-ceremony-gift-list]', '当前没有家谱上下文。', 'empty'); setStatus('[data-ceremony-invitation-list]', '当前没有家谱上下文。', 'empty'); setStatus('[data-ceremony-invitee-options]', '当前没有家谱上下文。', 'empty'); setEditorEnabled(false, message); setStatus('[data-ceremony-form-status]', message, 'forbidden'); } function requireGenealogyId() { var genealogyId = getCurrentGenealogyId(); if (!genealogyId) renderNoContext(); return genealogyId; } function canEditCeremonies(genealogy) { return Boolean(genealogy && (genealogy.canEditContent === true || genealogy.canManage === true)); } async function loadCapability(api, genealogyId) { var detail = await api.genealogyDetail(genealogyId); canEditContent = canEditCeremonies(detail); queryAll('[data-ceremony-create-link], [data-ceremony-editor-action], [data-ceremony-admin-editor]').forEach(function (element) { element.hidden = !canEditContent; }); return detail; } function renderCeremonies(data) { var container = query('[data-ceremony-list]'); var ceremonies = normalizeList(data, normalizeCeremony); ceremoniesById = Object.create(null); ceremonies.forEach(function (ceremony) { ceremoniesById[ceremony.ceremonyId] = ceremony; }); if (!container) return ceremonies; if (!Array.isArray(data) || (data.length && !ceremonies.length)) { setStatus('[data-ceremony-list]', '活动响应缺少稳定 CeremonyVo 字段,请联系后端核对。', 'error'); return []; } if (!ceremonies.length) { setStatus('[data-ceremony-list]', '暂无祭祀活动', 'empty'); return []; } container.innerHTML = ceremonies.map(renderCeremonyRow).join(''); return ceremonies; } async function loadCeremonies() { var api = getApi(); var genealogyId; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; syncLinks(); setStatus('[data-ceremony-list]', '正在加载祭祀活动…', 'loading'); try { await loadCapability(api, genealogyId); renderCeremonies(await api.ceremonies(genealogyId)); } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus( '[data-ceremony-list]', isForbidden(error) ? '当前账号无权查看该家谱的祭祀活动。' : '活动加载失败,请稍后重试。', getApiStateType(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(formSelector, name, value) { var field = query(formSelector + ' [name="' + name + '"]'); if (field) field.value = value === undefined || value === null ? '' : String(value); } function fillCeremonyForm(ceremony) { var contentField; var contentEditor; if (!ceremony || ceremony.status !== '0') throw new Error('停用活动无法通过当前 PC 接口重新读取或编辑'); ['ceremonyType', 'ceremonyTitle', 'ceremonyDesc', 'location', 'locationAddress', 'longitude', 'latitude', 'coverOssId', 'sortOrder'].forEach(function (name) { setFieldValue('[data-ceremony-form]', name, ceremony[name]); }); setFieldValue('[data-ceremony-form]', 'ceremonyTime', toDateTimeInputValue(ceremony.ceremonyTime)); setFieldValue('[data-ceremony-form]', 'status', '0'); contentField = query('[data-ceremony-form] [name="ceremonyDesc"]'); if (contentField && root.AppRichEditor && root.AppRichEditor.init) { contentEditor = root.AppRichEditor.init(contentField); if (contentEditor && contentEditor.editor && contentEditor.editor.setHtml) { contentEditor.editor.setHtml(ceremony.ceremonyDesc || ''); contentEditor.sync(); } } } async function loadCeremonyEditor() { var api = getApi(); var genealogyId; var ceremonyId; var ceremony; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; ceremonyId = getCurrentCeremonyId(); syncLinks(); setEditorEnabled(false, '正在加载活动编辑信息…'); setStatus('[data-ceremony-form-status]', '正在读取家谱权限…', 'loading'); try { await loadCapability(api, genealogyId); if (!canEditContent) throw { status: 403, message: '当前账号无权维护该家谱的祭祀活动。' }; if (ceremonyId) { ceremony = normalizeCeremony(await api.ceremonyDetail(genealogyId, ceremonyId)); if (!ceremony || ceremony.ceremonyId !== ceremonyId) throw new Error('活动详情响应缺少稳定 ceremonyId'); fillCeremonyForm(ceremony); if (query('[data-ceremony-editor-title]')) query('[data-ceremony-editor-title]').textContent = '编辑祭祀活动'; } setEditorEnabled(true); setStatus('[data-ceremony-form-status]', ''); } catch (error) { if (redirectUnauthorized(api, error)) return; setEditorEnabled(false, isForbidden(error) ? '当前账号无权维护该家谱的祭祀活动。' : (error.message || '活动编辑信息加载失败')); setStatus( '[data-ceremony-form-status]', isForbidden(error) ? '当前账号无权维护该家谱的祭祀活动。' : (error.message || '活动编辑信息加载失败'), getApiStateType(error) ); } } async function loadGifts(api, genealogyId, ceremonyId) { var data; var gifts; var container = query('[data-ceremony-gift-list]'); setStatus('[data-ceremony-gift-list]', '正在加载祭品…', 'loading'); data = await api.ceremonyGifts(genealogyId, ceremonyId); gifts = normalizeList(data, normalizeCeremonyGift); giftsById = Object.create(null); gifts.forEach(function (gift) { giftsById[gift.giftId] = gift; }); if (!container) return gifts; if (!Array.isArray(data) || (data.length && !gifts.length)) { setStatus('[data-ceremony-gift-list]', '祭品响应缺少稳定 CeremonyGiftVo 字段,请联系后端核对。', 'error'); return []; } if (!gifts.length) { setStatus('[data-ceremony-gift-list]', '暂无祭品记录', 'empty'); return []; } container.innerHTML = renderGiftList(gifts); return gifts; } function renderInviteeOptions(options, invitations) { var container = query('[data-ceremony-invitee-options]'); var selected = Object.create(null); invitations.forEach(function (invitation) { if (invitation.inviteStatus !== 'CANCELED') selected[invitation.inviteeUserId] = true; }); if (!container) return; if (!options.length) { setStatus('[data-ceremony-invitee-options]', '暂无已绑定账号的正常成员', 'empty'); return; } container.innerHTML = options.map(function (option) { var label = option.memberName || option.appUserNickName || '家谱成员'; return ''; }).join(''); } async function loadInvitees(api, genealogyId, ceremonyId) { var results; var options; var invitations; var list = query('[data-ceremony-invitation-list]'); setStatus('[data-ceremony-invitation-list]', '正在加载邀请记录…', 'loading'); setStatus('[data-ceremony-invitee-options]', '正在加载成员选项…', 'loading'); results = await Promise.all([ api.genealogyMemberOptions(genealogyId), api.ceremonyInvitations(genealogyId, ceremonyId) ]); options = normalizeList(results[0], normalizeInviteeOption); invitations = normalizeList(results[1], normalizeInvitation); inviteeOptionsByUserId = Object.create(null); options.forEach(function (option) { inviteeOptionsByUserId[option.appUserId] = option; }); if (!Array.isArray(results[0]) || (results[0].length && !options.length)) { setStatus('[data-ceremony-invitee-options]', '成员选项响应字段不完整', 'error'); } else { renderInviteeOptions(options, invitations); } if (!list) return { options: options, invitations: invitations }; if (!Array.isArray(results[1]) || (results[1].length && !invitations.length)) { setStatus('[data-ceremony-invitation-list]', '邀请响应字段不完整', 'error'); } else if (!invitations.length) { setStatus('[data-ceremony-invitation-list]', '暂无邀请记录', 'empty'); } else { list.innerHTML = renderInvitationList(results[1]); } return { options: options, invitations: invitations }; } async function loadCeremonyDetailPage() { var api = getApi(); var genealogyId; var ceremonyId; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); ceremonyId = getCurrentCeremonyId(); if (!genealogyId || !ceremonyId) { if (genealogyId) { setStatus('[data-ceremony-detail]', '缺少有效活动编号。', 'error'); setStatus('[data-ceremony-gift-list]', '缺少有效活动编号。', 'empty'); setStatus('[data-ceremony-invitation-list]', '缺少有效活动编号。', 'empty'); setStatus('[data-ceremony-invitee-options]', '缺少有效活动编号。', 'empty'); } return; } syncLinks(); setStatus('[data-ceremony-detail]', '正在加载活动详情…', 'loading'); setStatus('[data-ceremony-gift-list]', '正在加载祭品…', 'loading'); try { await loadCapability(api, genealogyId); currentCeremony = normalizeCeremony(await api.ceremonyDetail(genealogyId, ceremonyId)); if (!currentCeremony || currentCeremony.ceremonyId !== ceremonyId) throw new Error('活动详情响应缺少稳定 ceremonyId'); if (query('[data-ceremony-detail]')) query('[data-ceremony-detail]').innerHTML = renderCeremonyDetail(currentCeremony); if (query('[data-ceremony-detail-title]')) query('[data-ceremony-detail-title]').textContent = currentCeremony.ceremonyTitle; if (query('[data-ceremony-editor-action]')) query('[data-ceremony-editor-action]').href = buildEditUrl(genealogyId, ceremonyId); } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus( '[data-ceremony-detail]', isForbidden(error) ? '当前账号无权查看该活动。' : (error.message || '活动详情加载失败'), getApiStateType(error) ); setStatus( '[data-ceremony-gift-list]', isForbidden(error) ? '当前账号无权查看该活动。' : '祭品加载失败,请稍后重试。', getApiStateType(error) ); setStatus( '[data-ceremony-invitation-list]', isForbidden(error) ? '当前账号无权查看该活动。' : '邀请记录加载失败,请稍后重试。', getApiStateType(error) ); setStatus( '[data-ceremony-invitee-options]', isForbidden(error) ? '当前账号无权查看该活动。' : '成员选项加载失败,请稍后重试。', getApiStateType(error) ); showMessage(isForbidden(error) ? '当前账号无权查看该活动。' : (error.message || '活动详情加载失败')); return; } try { await loadGifts(api, genealogyId, ceremonyId); } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus( '[data-ceremony-gift-list]', isForbidden(error) ? '当前账号无权查看该活动祭品。' : (error.message || '祭品加载失败'), getApiStateType(error) ); showMessage(isForbidden(error) ? '当前账号无权查看该活动祭品。' : (error.message || '祭品加载失败')); } if (!canEditContent) { setStatus('[data-ceremony-invitation-list]', '当前账号无权查看或维护受邀名单。', 'forbidden'); setStatus('[data-ceremony-invitee-options]', '当前账号无权查看或维护受邀名单。', 'forbidden'); return; } try { await loadInvitees(api, genealogyId, ceremonyId); } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus( '[data-ceremony-invitation-list]', isForbidden(error) ? '当前账号无权查看或维护受邀名单。' : (error.message || '邀请记录加载失败'), getApiStateType(error) ); setStatus( '[data-ceremony-invitee-options]', isForbidden(error) ? '当前账号无权查看或维护受邀名单。' : '成员选项加载失败,请稍后重试。', getApiStateType(error) ); showMessage(isForbidden(error) ? '当前账号无权查看或维护受邀名单。' : (error.message || '邀请记录加载失败')); } } async function submitCeremony(form) { var api = getApi(); var genealogyId = requireGenealogyId(); var ceremonyId = getCurrentCeremonyId(); var body; var validation; var saved; var savedId; var verified; if (writePending || !canEditContent || !genealogyId || redirectUnauthorized(api)) return; if (root.AppRichEditor && root.AppRichEditor.syncAll) root.AppRichEditor.syncAll(); body = buildCeremonyBody(getFormValues(form)); validation = validateCeremonyBody(body); if (validation) { setStatus('[data-ceremony-form-status]', validation, 'error'); return; } delete body.invalidLongitude; delete body.invalidLatitude; delete body.invalidCoverOssId; delete body.invalidSortOrder; setWritePending(true); setStatus('[data-ceremony-form-status]', '正在保存活动…', 'loading'); try { saved = normalizeCeremony(ceremonyId ? await api.updateCeremony(genealogyId, ceremonyId, body) : await api.createCeremony(genealogyId, body)); savedId = ceremonyId || (saved && saved.ceremonyId); if (!savedId) throw new Error('保存响应缺少 ceremonyId'); verified = normalizeCeremony(await api.ceremonyDetail(genealogyId, savedId)); if (!verified || verified.ceremonyId !== savedId) throw new Error('保存后未能重读同一活动'); if (root.location) root.location.href = buildDetailUrl(genealogyId, savedId); } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus( '[data-ceremony-form-status]', isForbidden(error) ? '当前账号无权保存该活动。' : (error.message || '活动保存失败'), getApiStateType(error) ); } finally { setWritePending(false); } } async function submitGift(form) { var api = getApi(); var genealogyId = requireGenealogyId(); var ceremonyId = getCurrentCeremonyId(); var body; var validation; var saved; if (writePending || !canEditContent || !genealogyId || !ceremonyId || redirectUnauthorized(api)) return; body = buildCeremonyGiftBody(getFormValues(form)); validation = validateCeremonyGiftBody(body); if (validation) { setStatus('[data-ceremony-gift-status]', validation, 'error'); return; } delete body.invalidGiftAmount; setWritePending(true); setStatus('[data-ceremony-gift-status]', '正在添加祭品…', 'loading'); try { saved = normalizeCeremonyGift(await api.createCeremonyGift(genealogyId, ceremonyId, body)); if (!saved || saved.ceremonyId !== ceremonyId) throw new Error('添加祭品响应缺少稳定 giftId'); if (!(await loadGifts(api, genealogyId, ceremonyId)).some(function (gift) { return gift.giftId === saved.giftId; })) { throw new Error('添加后祭品列表未返回同一记录'); } form.reset(); setStatus('[data-ceremony-gift-status]', '祭品已添加'); } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus( '[data-ceremony-gift-status]', isForbidden(error) ? '当前账号无权添加祭品。' : (error.message || '祭品添加失败'), getApiStateType(error) ); } finally { setWritePending(false); } } async function submitInvitees() { var api = getApi(); var genealogyId = requireGenealogyId(); var ceremonyId = getCurrentCeremonyId(); var body; if (writePending || !canEditContent || !genealogyId || !ceremonyId || redirectUnauthorized(api)) return; body = buildInviteesBody(queryAll('[data-invitee-user-id]:checked').map(function (input) { return input.getAttribute('data-invitee-user-id'); })); setWritePending(true); setStatus('[data-ceremony-invitee-status]', '正在更新受邀人…', 'loading'); try { await api.replaceCeremonyInvitees(genealogyId, ceremonyId, body.inviteeUserIds); await loadInvitees(api, genealogyId, ceremonyId); setStatus('[data-ceremony-invitee-status]', '受邀名单已更新'); } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus( '[data-ceremony-invitee-status]', isForbidden(error) ? '当前账号无权维护受邀名单。' : (error.message || '受邀名单更新失败'), getApiStateType(error) ); } finally { setWritePending(false); } } async function deleteCeremony(ceremonyId) { var api = getApi(); var genealogyId = requireGenealogyId(); var ceremony = ceremoniesById[ceremonyId] || currentCeremony; if (writePending || !canEditContent || !genealogyId || !normalizeId(ceremonyId) || redirectUnauthorized(api)) return; if (root.confirm && !root.confirm('确认删除“' + (ceremony ? ceremony.ceremonyTitle : '该活动') + '”吗?删除后不可恢复。')) return; setWritePending(true); try { await api.deleteCeremony(genealogyId, ceremonyId); if (query('[data-ceremony-list-page]')) await loadCeremonies(); else if (root.location) root.location.href = buildListUrl(genealogyId); } catch (error) { if (redirectUnauthorized(api, error)) return; showMessage(isForbidden(error) ? '当前账号无权删除该活动。' : (error.message || '活动删除失败')); } finally { setWritePending(false); } } async function deleteGift(giftId) { var api = getApi(); var genealogyId = requireGenealogyId(); var ceremonyId = getCurrentCeremonyId(); var gift = giftsById[giftId]; if (writePending || !canEditContent || !genealogyId || !ceremonyId || !normalizeId(giftId) || redirectUnauthorized(api)) return; if (root.confirm && !root.confirm('确认删除“' + (gift && (gift.giverName || gift.giverNickName) || '该祭品') + '”的记录吗?')) return; setWritePending(true); try { await api.deleteCeremonyGift(genealogyId, ceremonyId, giftId); await loadGifts(api, genealogyId, ceremonyId); 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 ceremonyDelete = event.target.closest('[data-ceremony-delete-id]'); var giftDelete = event.target.closest('[data-ceremony-gift-delete-id]'); if (ceremonyDelete) { event.preventDefault(); deleteCeremony(ceremonyDelete.getAttribute('data-ceremony-delete-id')); } else if (giftDelete) { event.preventDefault(); deleteGift(giftDelete.getAttribute('data-ceremony-gift-delete-id')); } }); documentRef.addEventListener('submit', function (event) { var ceremonyForm = event.target.closest('[data-ceremony-form]'); var giftForm = event.target.closest('[data-ceremony-gift-form]'); var inviteeForm = event.target.closest('[data-ceremony-invitee-form]'); if (ceremonyForm) { event.preventDefault(); submitCeremony(ceremonyForm); } else if (giftForm) { event.preventDefault(); submitGift(giftForm); } else if (inviteeForm) { event.preventDefault(); submitInvitees(); } }); } function init() { if (!documentRef) return; bindActions(); if (query('[data-ceremony-list-page]')) loadCeremonies(); if (query('[data-ceremony-edit-page]')) loadCeremonyEditor(); if (query('[data-ceremony-detail-page]')) loadCeremonyDetailPage(); } return { getCurrentGenealogyId: getCurrentGenealogyId, getCurrentCeremonyId: getCurrentCeremonyId, buildCeremonyBody: buildCeremonyBody, validateCeremonyBody: validateCeremonyBody, buildCeremonyGiftBody: buildCeremonyGiftBody, validateCeremonyGiftBody: validateCeremonyGiftBody, normalizeCeremony: normalizeCeremony, normalizeCeremonyGift: normalizeCeremonyGift, normalizeInvitation: normalizeInvitation, normalizeInviteeOption: normalizeInviteeOption, buildInviteesBody: buildInviteesBody, canEditCeremonies: canEditCeremonies, renderCeremonyDetail: renderCeremonyDetail, renderGiftList: renderGiftList, shouldRedirectToLogin: shouldRedirectToLogin, isForbidden: isForbidden, init: init }; });