(function (root, factory) { if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } root.AlbumPages = factory(root); if (root.document) { root.document.addEventListener('DOMContentLoaded', function () { root.AlbumPages.init(); }); } })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; var documentRef = root.document; var albumsById = Object.create(null); var photosById = Object.create(null); var currentAlbum = null; var canEditContent = false; var writePending = false; function getApi() { return root.GenealogyApi && root.GenealogyApi.defaultClient; } function query(selector, node) { return documentRef ? (node || documentRef).querySelector(selector) : null; } function queryAll(selector, node) { return documentRef ? Array.prototype.slice.call((node || documentRef).querySelectorAll(selector)) : []; } function stringValue(value) { return value === undefined || value === null ? '' : String(value); } function trimOrUndefined(value) { var text = stringValue(value).trim(); return text || undefined; } function queryParam(search, name) { return new URLSearchParams(stringValue(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 normalizeNullableId(value) { if (value === undefined || value === null || value === '') return ''; return normalizeId(value); } function optionalSafeInteger(value) { var number; if (value === undefined || value === null || value === '') return undefined; number = Number(value); return Number.isSafeInteger(number) ? number : undefined; } 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 getCurrentAlbumId(search) { var source = search === undefined && root.location ? root.location.search : search; return normalizeId(queryParam(source, 'albumId')); } 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 buildAlbumBody(values) { var source = values || {}; var body = { albumName: stringValue(source.albumName).trim(), status: '0' }; var albumDesc = trimOrUndefined(source.albumDesc); var coverOssId = normalizeNullableId(source.coverOssId); var sortOrder = optionalSafeInteger(source.sortOrder); if (albumDesc !== undefined) body.albumDesc = albumDesc; 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 validateAlbumBody(body) { if (!body || !body.albumName) return '请填写相册名称'; if (body.invalidCoverOssId) return '封面文件编号无效,请重新选择文件'; if (body.invalidSortOrder) return '排序值必须是安全整数'; if (body.status === '1') return '当前 PC 无法重新读取停用相册,暂不开放停用'; if (body.status !== '0') return '相册状态只能是 0'; return ''; } function buildAlbumPhotoBody(values) { var source = values || {}; var body = { status: '0' }; var ossId = normalizeNullableId(source.ossId); var photoTitle = trimOrUndefined(source.photoTitle); var photoDesc = trimOrUndefined(source.photoDesc); var photographer = trimOrUndefined(source.photographer); var shootTime = toBackendDateTime(source.shootTime); var sortOrder = optionalSafeInteger(source.sortOrder); if (ossId) body.ossId = ossId; else if (source.ossId !== undefined && source.ossId !== null && source.ossId !== '') body.invalidOssId = true; if (photoTitle !== undefined) body.photoTitle = photoTitle; if (photoDesc !== undefined) body.photoDesc = photoDesc; if (photographer !== undefined) body.photographer = photographer; if (shootTime !== undefined) body.shootTime = shootTime; if (source.sortOrder !== undefined && source.sortOrder !== null && source.sortOrder !== '') { if (sortOrder !== undefined) body.sortOrder = sortOrder; else body.invalidSortOrder = true; } return body; } function validateAlbumPhotoBody(body) { if (!body || !body.ossId || body.invalidOssId) return '请选择并上传照片'; if (body.shootTime !== undefined && !isValidBackendDateTime(body.shootTime)) return '拍摄时间格式无效'; if (body.invalidSortOrder) return '排序值必须是安全整数'; if (body.status === '1') return '当前 PC 无法重新读取停用照片,暂不开放停用'; if (body.status !== '0') return '照片状态只能是 0'; return ''; } function normalizeAlbum(item) { var albumId = normalizeId(item && item.albumId); var genealogyId = normalizeId(item && item.genealogyId); var coverOssId = normalizeNullableId(item && item.coverOssId); var albumName = stringValue(item && item.albumName).trim(); var photoCount = optionalSafeInteger(item && item.photoCount); var sortOrder = optionalSafeInteger(item && item.sortOrder); var status = stringValue(item && item.status); var album; if (!albumId || !genealogyId || !albumName) return null; if (item.coverOssId !== undefined && item.coverOssId !== null && item.coverOssId !== '' && !coverOssId) return null; if (item.photoCount !== undefined && item.photoCount !== null && item.photoCount !== '' && photoCount === undefined) return null; if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null; if (status !== '0' && status !== '1') return null; album = { albumId: albumId, genealogyId: genealogyId, genealogyNo: stringValue(item.genealogyNo), genealogyName: stringValue(item.genealogyName), surname: stringValue(item.surname), albumName: albumName, albumDesc: stringValue(item.albumDesc), status: status, remark: stringValue(item.remark) }; if (coverOssId) album.coverOssId = coverOssId; if (photoCount !== undefined) album.photoCount = photoCount; if (sortOrder !== undefined) album.sortOrder = sortOrder; return album; } function normalizeAlbumPhoto(item) { var photoId = normalizeId(item && item.photoId); var genealogyId = normalizeId(item && item.genealogyId); var albumId = normalizeId(item && item.albumId); var ossId = normalizeId(item && item.ossId); var sortOrder = optionalSafeInteger(item && item.sortOrder); var status = stringValue(item && item.status); var photo; if (!photoId || !genealogyId || !albumId || !ossId) return null; if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null; if (status !== '0' && status !== '1') return null; photo = { photoId: photoId, genealogyId: genealogyId, genealogyNo: stringValue(item.genealogyNo), genealogyName: stringValue(item.genealogyName), surname: stringValue(item.surname), albumId: albumId, albumName: stringValue(item.albumName), ossId: ossId, photoTitle: stringValue(item.photoTitle), photoDesc: stringValue(item.photoDesc), photographer: stringValue(item.photographer), shootTime: stringValue(item.shootTime), status: status, remark: stringValue(item.remark) }; if (sortOrder !== undefined) photo.sortOrder = sortOrder; return photo; } function normalizeAlbums(data) { var normalized; if (!Array.isArray(data)) return []; normalized = data.map(normalizeAlbum); return normalized.some(function (album) { return !album; }) ? [] : normalized; } function normalizeAlbumPhotos(data) { var normalized; if (!Array.isArray(data)) return []; normalized = data.map(normalizeAlbumPhoto); return normalized.some(function (photo) { return !photo; }) ? [] : normalized; } function findAlbumById(data, albumId) { var albums = normalizeAlbums(data); var expectedId = normalizeId(albumId); return albums.find(function (album) { return album.albumId === expectedId; }) || null; } function escapeHtml(value) { return stringValue(value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function buildListUrl(genealogyId) { return 'profile-album.html?genealogyId=' + encodeURIComponent(genealogyId); } function buildEditUrl(genealogyId, albumId) { var url = 'profile-album-edit.html?genealogyId=' + encodeURIComponent(genealogyId); return albumId ? url + '&albumId=' + encodeURIComponent(albumId) : url; } function buildDetailUrl(genealogyId, albumId) { return 'profile-album-detail.html?genealogyId=' + encodeURIComponent(genealogyId) + '&albumId=' + encodeURIComponent(albumId); } function detailValue(label, value) { return '

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

'; } function renderAlbumDetail(album) { var actions = ''; if (!album) return '
未找到该相册
'; if (canEditContent) { actions = '
编辑相册' + '
'; } return '
' + detailValue('相册名称', album.albumName) + detailValue('相册说明', album.albumDesc) + detailValue('照片数量', album.photoCount === undefined ? '' : album.photoCount) + detailValue('备注', album.remark) + '
' + actions; } function renderAlbumRow(album) { var actions = '查看照片'; if (canEditContent) { actions += '编辑'; } return '

' + escapeHtml(album.albumName) + '

' + escapeHtml(album.albumDesc || ('照片数量:' + (album.photoCount || 0))) + '

' + actions + '
'; } function renderPhotoList(data) { var photos = Array.isArray(data) ? data.map(normalizeAlbumPhoto) : []; if (photos.some(function (photo) { return !photo; })) return '
照片响应字段不完整
'; if (!photos.length) return '
暂无照片
'; return photos.map(function (photo) { var action = canEditContent ? '' : ''; return '

' + escapeHtml(photo.photoTitle || '未命名照片') + '

' + escapeHtml([photo.photoDesc, photo.photographer, photo.shootTime].filter(Boolean).join(' · ') || '文件已上传') + '

' + action + '
'; }).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 syncLinks() { if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) root.ProfileUI.syncGenealogyContextLinks(); } function setStatus(selector, message) { var target = query(selector); if (target) target.textContent = message || ''; } function setEditorEnabled(enabled, message) { var placeholder = query('[data-album-editor-placeholder]'); queryAll('[data-album-editor], [data-album-editor-action], [data-album-photo-editor]').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-album-form] button, [data-album-form] input, [data-album-form] textarea, [data-album-photo-form] button, [data-album-photo-form] input, [data-album-photo-form] textarea, [data-album-delete-id], [data-album-photo-delete-id]').forEach(function (control) { control.disabled = writePending; }); } function renderNoContext() { var message = '请先选择家谱,再查看或维护相册。'; if (query('[data-album-list]')) query('[data-album-list]').innerHTML = '
' + message + '
'; if (query('[data-album-detail]')) query('[data-album-detail]').innerHTML = '
当前没有家谱上下文。
'; if (query('[data-album-photo-list]')) query('[data-album-photo-list]').innerHTML = '
当前没有家谱上下文。
'; setEditorEnabled(false, message); setStatus('[data-album-form-status]', message); setStatus('[data-album-photo-status]', message); } function requireGenealogyId() { var genealogyId = getCurrentGenealogyId(); if (!genealogyId) renderNoContext(); return genealogyId; } async function loadCapability(api, genealogyId) { var detail = await api.genealogyDetail(genealogyId); canEditContent = Boolean(detail && (detail.canEditContent || detail.canManage)); queryAll('[data-album-create-link], [data-album-editor-action], [data-album-photo-editor]').forEach(function (element) { element.hidden = !canEditContent; }); return detail; } function renderAlbums(data) { var container = query('[data-album-list]'); var albums = normalizeAlbums(data); albumsById = Object.create(null); albums.forEach(function (album) { albumsById[album.albumId] = album; }); if (!container) return albums; if (!Array.isArray(data) || (data.length && !albums.length)) { container.innerHTML = '
相册响应缺少稳定 AlbumVo 字段,请联系后端核对。
'; return []; } if (!albums.length) { container.innerHTML = '
暂无相册
'; return []; } container.innerHTML = albums.map(renderAlbumRow).join(''); return albums; } async function loadAlbums() { var api = getApi(); var genealogyId; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; syncLinks(); try { await loadCapability(api, genealogyId); renderAlbums(await api.albums(genealogyId)); } catch (error) { if (redirectUnauthorized(api, error)) return; if (query('[data-album-list]')) { query('[data-album-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(formSelector, name, value) { var field = query(formSelector + ' [name="' + name + '"]'); if (field) field.value = value === undefined || value === null ? '' : String(value); } function fillAlbumForm(album) { if (!album || album.status !== '0') throw new Error('停用相册无法通过当前 PC 接口重新读取或编辑'); setFieldValue('[data-album-form]', 'albumName', album.albumName); setFieldValue('[data-album-form]', 'albumDesc', album.albumDesc); setFieldValue('[data-album-form]', 'coverOssId', album.coverOssId); setFieldValue('[data-album-form]', 'sortOrder', album.sortOrder); setFieldValue('[data-album-form]', 'status', '0'); } async function loadAlbumEditor() { var api = getApi(); var genealogyId; var albumId; var albums; var album; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; albumId = getCurrentAlbumId(); syncLinks(); setEditorEnabled(false, '正在加载相册编辑信息…'); try { await loadCapability(api, genealogyId); if (!canEditContent) throw { status: 403, message: '当前账号无权维护该家谱的相册。' }; if (albumId) { albums = await api.albums(genealogyId); album = findAlbumById(albums, albumId); if (!album) throw new Error('相册列表未返回当前相册'); fillAlbumForm(album); if (query('[data-album-editor-title]')) query('[data-album-editor-title]').textContent = '编辑相册'; } setEditorEnabled(true); } catch (error) { if (redirectUnauthorized(api, error)) return; setEditorEnabled(false, isForbidden(error) ? '当前账号无权维护该家谱的相册。' : (error.message || '相册编辑信息加载失败')); setStatus('[data-album-form-status]', isForbidden(error) ? '当前账号无权维护该家谱的相册。' : (error.message || '相册编辑信息加载失败')); } } async function loadPhotos(api, genealogyId, albumId) { var data = await api.albumPhotos(genealogyId, albumId); var photos = normalizeAlbumPhotos(data); var container = query('[data-album-photo-list]'); photosById = Object.create(null); photos.forEach(function (photo) { photosById[photo.photoId] = photo; }); if (container) { container.innerHTML = !Array.isArray(data) || (data.length && !photos.length) ? '
照片响应缺少稳定 AlbumPhotoVo 字段,请联系后端核对。
' : renderPhotoList(photos); } return photos; } async function loadAlbumDetailPage() { var api = getApi(); var genealogyId; var albumId; var albums; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); albumId = getCurrentAlbumId(); if (!genealogyId || !albumId) { if (genealogyId && query('[data-album-detail]')) query('[data-album-detail]').innerHTML = '
缺少有效相册编号。
'; return; } syncLinks(); try { await loadCapability(api, genealogyId); albums = await api.albums(genealogyId); currentAlbum = findAlbumById(albums, albumId); if (!currentAlbum) throw new Error('相册列表未返回当前相册'); if (query('[data-album-detail]')) query('[data-album-detail]').innerHTML = renderAlbumDetail(currentAlbum); if (query('[data-album-detail-title]')) query('[data-album-detail-title]').textContent = currentAlbum.albumName; await loadPhotos(api, genealogyId, albumId); } catch (error) { if (redirectUnauthorized(api, error)) return; if (query('[data-album-detail]')) query('[data-album-detail]').innerHTML = '
' + (isForbidden(error) ? '当前账号无权查看该相册。' : escapeHtml(error.message || '相册详情加载失败')) + '
'; showMessage(isForbidden(error) ? '当前账号无权查看该相册。' : (error.message || '相册详情加载失败')); } } async function submitAlbum(form) { var api = getApi(); var genealogyId = requireGenealogyId(); var albumId = getCurrentAlbumId(); var body; var validation; var result; var saved; var savedId; var verified; if (writePending || !genealogyId || redirectUnauthorized(api)) return; body = buildAlbumBody(getFormValues(form)); validation = validateAlbumBody(body); if (validation) { setStatus('[data-album-form-status]', validation); return; } delete body.invalidCoverOssId; delete body.invalidSortOrder; setWritePending(true); setStatus('[data-album-form-status]', '正在保存相册…'); try { result = albumId ? await api.updateAlbum(genealogyId, albumId, body) : await api.createAlbum(genealogyId, body); saved = normalizeAlbum(result); savedId = albumId || (saved && saved.albumId); if (!savedId) throw new Error('保存响应缺少 albumId'); verified = findAlbumById(await api.albums(genealogyId), savedId); if (!verified) throw new Error('保存后相册列表未返回同一相册'); if (root.location) root.location.href = buildDetailUrl(genealogyId, savedId); } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus('[data-album-form-status]', isForbidden(error) ? '当前账号无权保存该相册。' : (error.message || '相册保存失败')); } finally { setWritePending(false); } } async function submitPhoto(form) { var api = getApi(); var genealogyId = requireGenealogyId(); var albumId = getCurrentAlbumId(); var body; var validation; var saved; if (writePending || !genealogyId || !albumId || redirectUnauthorized(api)) return; body = buildAlbumPhotoBody(getFormValues(form)); validation = validateAlbumPhotoBody(body); if (validation) { setStatus('[data-album-photo-status]', validation); return; } delete body.invalidOssId; delete body.invalidSortOrder; setWritePending(true); setStatus('[data-album-photo-status]', '正在添加照片…'); try { saved = normalizeAlbumPhoto(await api.createAlbumPhoto(genealogyId, albumId, body)); if (!saved || saved.albumId !== albumId) throw new Error('添加照片响应缺少稳定 photoId'); if (!(await loadPhotos(api, genealogyId, albumId)).some(function (photo) { return photo.photoId === saved.photoId; })) { throw new Error('添加后照片列表未返回同一照片'); } form.reset(); setStatus('[data-album-photo-status]', '照片已添加'); } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus('[data-album-photo-status]', isForbidden(error) ? '当前账号无权添加照片。' : (error.message || '照片添加失败')); } finally { setWritePending(false); } } async function deleteAlbum(albumId) { var api = getApi(); var genealogyId = requireGenealogyId(); var album = albumsById[albumId] || currentAlbum; if (writePending || !genealogyId || !normalizeId(albumId) || redirectUnauthorized(api)) return; if (root.confirm && !root.confirm('确认删除“' + (album ? album.albumName : '该相册') + '”及其中全部照片吗?删除后不可恢复。')) return; setWritePending(true); try { await api.deleteAlbum(genealogyId, albumId); if (query('[data-album-page]')) await loadAlbums(); 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 deletePhoto(photoId) { var api = getApi(); var genealogyId = requireGenealogyId(); var albumId = getCurrentAlbumId(); var photo = photosById[photoId]; if (writePending || !genealogyId || !albumId || !normalizeId(photoId) || redirectUnauthorized(api)) return; if (root.confirm && !root.confirm('确认删除“' + (photo && photo.photoTitle || '该照片') + '”吗?删除后不可恢复。')) return; setWritePending(true); try { await api.deleteAlbumPhoto(genealogyId, albumId, photoId); await loadPhotos(api, genealogyId, albumId); 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 albumDelete = event.target.closest('[data-album-delete-id]'); var photoDelete = event.target.closest('[data-album-photo-delete-id]'); if (albumDelete) { event.preventDefault(); deleteAlbum(albumDelete.getAttribute('data-album-delete-id')); return; } if (photoDelete) { event.preventDefault(); deletePhoto(photoDelete.getAttribute('data-album-photo-delete-id')); } }); documentRef.addEventListener('submit', function (event) { var albumForm = event.target.closest('[data-album-form]'); var photoForm = event.target.closest('[data-album-photo-form]'); if (albumForm) { event.preventDefault(); submitAlbum(albumForm); } else if (photoForm) { event.preventDefault(); submitPhoto(photoForm); } }); } function init() { if (!documentRef) return; bindActions(); if (query('[data-album-page]')) loadAlbums(); if (query('[data-album-edit-page]')) loadAlbumEditor(); if (query('[data-album-detail-page]')) loadAlbumDetailPage(); } return { getCurrentGenealogyId: getCurrentGenealogyId, getCurrentAlbumId: getCurrentAlbumId, buildAlbumBody: buildAlbumBody, validateAlbumBody: validateAlbumBody, buildAlbumPhotoBody: buildAlbumPhotoBody, validateAlbumPhotoBody: validateAlbumPhotoBody, normalizeAlbum: normalizeAlbum, normalizeAlbumPhoto: normalizeAlbumPhoto, normalizeAlbums: normalizeAlbums, normalizeAlbumPhotos: normalizeAlbumPhotos, findAlbumById: findAlbumById, renderAlbumDetail: renderAlbumDetail, renderPhotoList: renderPhotoList, shouldRedirectToLogin: shouldRedirectToLogin, isForbidden: isForbidden, init: init }; });