(function (root, factory) { if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } root.VideoPages = factory(root); if (root.document) { root.document.addEventListener('DOMContentLoaded', function () { root.VideoPages.init(); }); } })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; var documentRef = root.document; var videosById = 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 getCurrentVideoId(search) { var source = search === undefined && root.location ? root.location.search : search; return normalizeId(getQueryParam(source, 'videoId')); } 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 buildVideoBody(values) { var source = values || {}; var body = { videoTitle: String(source.videoTitle || '').trim() }; var videoDesc = trimOrUndefined(source.videoDesc); var coverOssId = trimOrUndefined(source.coverOssId); var videoOssId = trimOrUndefined(source.videoOssId); var durationText = trimOrUndefined(source.durationSeconds); var sortOrderText = trimOrUndefined(source.sortOrder); var status = trimOrUndefined(source.status); if (videoDesc !== undefined) body.videoDesc = videoDesc; if (coverOssId !== undefined) body.coverOssId = coverOssId; if (videoOssId !== undefined) body.videoOssId = videoOssId; if (durationText !== undefined) { body.durationSeconds = toSafeInteger(durationText); if (body.durationSeconds === undefined) body.invalidDuration = true; } if (sortOrderText !== undefined) { body.sortOrder = toSafeInteger(sortOrderText); if (body.sortOrder === undefined) body.invalidSortOrder = true; } if (status !== undefined) body.status = status; return body; } function validateVideoBody(body) { if (!body || !body.videoTitle) return '请填写视频标题'; if (!body.videoOssId) return '请先选择并上传视频文件'; if (!normalizeId(body.videoOssId)) return '视频文件上传结果无效'; if (body.coverOssId !== undefined && !normalizeId(body.coverOssId)) return '封面上传结果无效'; if (body.invalidDuration || (body.durationSeconds !== undefined && !Number.isSafeInteger(body.durationSeconds))) { return '视频时长必须是安全整数'; } if (body.durationSeconds !== undefined && body.durationSeconds < 0) return '视频时长不能小于 0'; 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 optionalInteger(value, minimum) { var number; if (value === undefined || value === null || value === '') return undefined; number = Number(value); if (!Number.isSafeInteger(number) || (minimum !== undefined && number < minimum)) return undefined; return number; } function stringValue(value) { return value === undefined || value === null ? '' : String(value); } function normalizeVideo(item) { var videoId = normalizeId(item && item.videoId); var genealogyId = normalizeId(item && item.genealogyId); var videoOssId = normalizeId(item && item.videoOssId); var coverOssId = normalizeId(item && item.coverOssId); var publisherUserId = normalizeId(item && item.publisherUserId); var title = String(item && item.videoTitle || '').trim(); var status = stringValue(item && item.status); var video; var duration; var viewCount; var sortOrder; if (!videoId || !genealogyId || !videoOssId || !title || (status !== '0' && status !== '1')) return null; if (item.coverOssId !== undefined && item.coverOssId !== null && item.coverOssId !== '' && !coverOssId) return null; if (item.publisherUserId !== undefined && item.publisherUserId !== null && item.publisherUserId !== '' && !publisherUserId) return null; video = { videoId: videoId, genealogyId: genealogyId, genealogyNo: stringValue(item.genealogyNo), genealogyName: stringValue(item.genealogyName), surname: stringValue(item.surname), videoTitle: title, videoDesc: stringValue(item.videoDesc), videoOssId: videoOssId, publisherNickName: stringValue(item.publisherNickName), publisherPhone: stringValue(item.publisherPhone), publishTime: stringValue(item.publishTime), status: status, remark: stringValue(item.remark) }; if (coverOssId) video.coverOssId = coverOssId; if (publisherUserId) video.publisherUserId = publisherUserId; duration = optionalInteger(item.durationSeconds, 0); viewCount = optionalInteger(item.viewCount, 0); sortOrder = optionalInteger(item.sortOrder); if (duration !== undefined) video.durationSeconds = duration; if (viewCount !== undefined) video.viewCount = viewCount; if (sortOrder !== undefined) video.sortOrder = sortOrder; return video; } function normalizeDurationSeconds(value) { var number = Number(value); return Number.isFinite(number) && number >= 0 ? Math.ceil(number) : undefined; } function escapeHtml(value) { return stringValue(value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function formatDuration(seconds) { var value = optionalInteger(seconds, 0); var minutes; var remaining; if (value === undefined) return '未提供'; minutes = Math.floor(value / 60); remaining = String(value % 60).padStart(2, '0'); return minutes + ':' + remaining; } function buildListUrl(genealogyId, videoId) { var url = 'profile-video.html?genealogyId=' + encodeURIComponent(genealogyId); return videoId ? url + '&videoId=' + encodeURIComponent(videoId) : url; } function buildEditUrl(genealogyId, videoId) { var url = 'profile-video-edit.html?genealogyId=' + encodeURIComponent(genealogyId); return videoId ? url + '&videoId=' + encodeURIComponent(videoId) : url; } function renderVideoRow(video, managementAccess) { var actions = ''; if (managementAccess) { actions += '编辑' + ''; } return '

' + escapeHtml(video.videoTitle) + '

' + escapeHtml(video.publisherNickName || '未知发布人') + (video.publishTime ? ' · ' + escapeHtml(video.publishTime) : '') + ' · 时长 ' + escapeHtml(formatDuration(video.durationSeconds)) + '

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

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

'; } function renderVideoDetail(video, managementAccess) { var actions = ''; if (!video) return '
请选择一个视频查看详情
'; if (managementAccess) { actions = '
编辑视频' + '
'; } return '
' + detailValue('视频标题', video.videoTitle) + detailValue('视频说明', video.videoDesc) + detailValue('所属家谱', video.genealogyName || video.genealogyNo) + detailValue('发布人', video.publisherNickName) + detailValue('发布时间', video.publishTime) + detailValue('视频文件', '已上传') + detailValue('封面文件', video.coverOssId ? '已上传' : '未上传') + detailValue('视频时长', formatDuration(video.durationSeconds)) + detailValue('浏览量', video.viewCount === undefined ? '' : String(video.viewCount)) + detailValue('状态', video.status === '0' ? '正常' : '停用') + detailValue('备注', video.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 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'); return true; } root.location.href = 'login.html'; return true; } function syncGenealogyLinks() { if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) root.ProfileUI.syncGenealogyContextLinks(); } function renderNoContext() { var list = query('[data-video-list]'); var detail = query('[data-video-detail]'); var placeholder = query('[data-video-permission-placeholder]'); if (list) list.innerHTML = '
请先选择家谱,再查看家族视频。
'; if (detail) detail.innerHTML = '
当前没有家谱上下文。
'; if (placeholder) placeholder.textContent = '请先选择家谱,再发布视频。'; setFormStatus('请先选择家谱,再发布视频。'); setManagementAccess(false); } function requireGenealogyId() { var genealogyId = getCurrentGenealogyId(); if (!genealogyId) renderNoContext(); return genealogyId; } function setManagementAccess(enabled) { var placeholder = query('[data-video-permission-placeholder]'); canEditContent = Boolean(enabled); queryAll('[data-video-management]').forEach(function (element) { element.hidden = !canEditContent; }); if (placeholder) placeholder.hidden = canEditContent; } function setFormStatus(message) { var status = query('[data-video-form-status]'); if (status) status.textContent = message || ''; } function setWritePending(pending) { writePending = Boolean(pending); queryAll('[data-video-form] input, [data-video-form] textarea, [data-video-form] button, [data-video-delete-id]').forEach(function (control) { control.disabled = writePending; }); } function renderVideos(data) { var container = query('[data-video-list]'); var normalized = Array.isArray(data) ? data.map(normalizeVideo) : []; var invalidCount = normalized.filter(function (item) { return !item; }).length; var videos = normalized.filter(Boolean); videosById = Object.create(null); videos.forEach(function (video) { videosById[video.videoId] = video; }); if (!container) return videos; if (!Array.isArray(data) || invalidCount) { container.innerHTML = '
视频响应缺少稳定 VideoVo 字段,请联系后端核对。
'; return []; } if (!videos.length) { container.innerHTML = '
暂无家族视频
'; return []; } container.innerHTML = videos.map(function (video) { return renderVideoRow(video, canEditContent); }).join(''); return videos; } function renderDetail(video) { var container = query('[data-video-detail]'); if (container) container.innerHTML = renderVideoDetail(video, canEditContent); } async function loadVideoDetail(videoId) { var api = getApi(); var genealogyId = requireGenealogyId(); var video; if (!genealogyId || !normalizeId(videoId) || redirectUnauthorized(api)) return null; try { video = normalizeVideo(await api.videoDetail(genealogyId, videoId)); if (!video) throw new Error('视频详情响应缺少稳定 VideoVo 字段'); videosById[video.videoId] = video; renderDetail(video); return video; } catch (error) { if (redirectUnauthorized(api, error)) return null; renderDetail(null); showMessage(error.message || '视频详情加载失败'); return null; } } async function loadVideos() { var api = getApi(); var genealogyId; var result; var videos; var requestedVideoId; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; syncGenealogyLinks(); try { result = await Promise.all([api.genealogyDetail(genealogyId), api.videos(genealogyId)]); setManagementAccess(Boolean(result[0] && result[0].canEditContent === true)); videos = renderVideos(result[1]); requestedVideoId = getCurrentVideoId(); if (requestedVideoId && videosById[requestedVideoId]) { await loadVideoDetail(requestedVideoId); } else if (videos.length) { await loadVideoDetail(videos[0].videoId); } else { renderDetail(null); } } catch (error) { if (redirectUnauthorized(api, error)) return; if (isForbidden(error)) { setManagementAccess(false); if (query('[data-video-list]')) query('[data-video-list]').innerHTML = '
当前账号无权查看该家谱视频。
'; return; } if (query('[data-video-list]')) query('[data-video-list]').innerHTML = '
视频加载失败,请稍后重试。
'; showMessage(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-video-form] [name="' + name + '"]'); if (field) field.value = value === undefined || value === null ? '' : String(value); } function fillVideoForm(video) { setFieldValue('videoTitle', video.videoTitle); setFieldValue('videoDesc', video.videoDesc); setFieldValue('coverOssId', video.coverOssId); setFieldValue('videoOssId', video.videoOssId); setFieldValue('durationSeconds', video.durationSeconds); setFieldValue('sortOrder', video.sortOrder); setFieldValue('status', '0'); if (query('[data-video-file-status]')) query('[data-video-file-status]').textContent = '已保留原视频文件,可重新选择替换'; if (query('[data-video-cover-status]')) query('[data-video-cover-status]').textContent = video.coverOssId ? '已保留原封面,可重新选择替换' : '未选择封面'; } async function loadVideoEditor() { var api = getApi(); var genealogyId; var videoId; var result; var video; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; videoId = getCurrentVideoId(); syncGenealogyLinks(); try { result = await Promise.all([ api.genealogyDetail(genealogyId), videoId ? api.videoDetail(genealogyId, videoId) : Promise.resolve(null) ]); setManagementAccess(Boolean(result[0] && result[0].canEditContent === true)); if (!canEditContent) { if (query('[data-video-permission-placeholder]')) { query('[data-video-permission-placeholder]').textContent = '当前账号没有视频维护权限。'; } setFormStatus('当前账号没有视频维护权限。'); return; } if (videoId) { video = normalizeVideo(result[1]); if (!video) throw new Error('视频详情响应缺少稳定 VideoVo 字段'); fillVideoForm(video); if (query('[data-video-editor-title]')) query('[data-video-editor-title]').textContent = '编辑视频'; } } catch (error) { if (redirectUnauthorized(api, error)) return; setManagementAccess(false); if (query('[data-video-permission-placeholder]')) { query('[data-video-permission-placeholder]').textContent = isForbidden(error) ? '当前账号没有视频维护权限。' : (error.message || '视频编辑信息加载失败'); } setFormStatus(isForbidden(error) ? '当前账号没有视频维护权限。' : (error.message || '视频编辑信息加载失败')); } } async function submitVideo(form) { var api = getApi(); var genealogyId = requireGenealogyId(); var videoId = getCurrentVideoId(); var body = buildVideoBody(getFormValues(form)); var validation = validateVideoBody(body); var result; var saved; var savedId; if (writePending || !canEditContent || !genealogyId || redirectUnauthorized(api)) return; if (validation) { setFormStatus(validation); return; } delete body.invalidDuration; delete body.invalidSortOrder; setWritePending(true); setFormStatus('正在保存视频…'); try { result = videoId ? await api.updateVideo(genealogyId, videoId, body) : await api.createVideo(genealogyId, body); saved = normalizeVideo(result); savedId = videoId || (saved && saved.videoId); if (!savedId) throw new Error('保存响应缺少 videoId'); await api.videoDetail(genealogyId, savedId); 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 deleteVideo(videoId) { var api = getApi(); var genealogyId = requireGenealogyId(); var video = videosById[videoId]; if (writePending || !canEditContent || !genealogyId || !normalizeId(videoId) || redirectUnauthorized(api)) return; if (root.confirm && !root.confirm('确认删除“' + (video ? video.videoTitle : '该视频') + '”吗?删除会释放视频和封面文件引用。')) return; setWritePending(true); try { await api.deleteVideo(genealogyId, videoId); await loadVideos(); showMessage('视频已删除'); } catch (error) { if (redirectUnauthorized(api, error)) return; showMessage(isForbidden(error) ? '当前账号没有视频维护权限。' : (error.message || '视频删除失败')); } finally { setWritePending(false); } } function readVideoDuration(input) { var file = input && input.files && input.files[0]; var targetSelector = input && input.getAttribute('data-video-duration-target'); var target = targetSelector ? query(targetSelector) : null; var media; var objectUrl; if (!file || !target || !documentRef || !root.URL || !root.URL.createObjectURL) return; media = documentRef.createElement('video'); objectUrl = root.URL.createObjectURL(file); media.preload = 'metadata'; media.onloadedmetadata = function () { var seconds = normalizeDurationSeconds(media.duration); target.value = seconds === undefined ? '' : String(seconds); root.URL.revokeObjectURL(objectUrl); }; media.onerror = function () { target.value = ''; root.URL.revokeObjectURL(objectUrl); }; media.src = objectUrl; } function bindActions() { if (!documentRef) return; documentRef.addEventListener('click', function (event) { var detailButton = event.target.closest('[data-video-detail-id]'); var deleteButton = event.target.closest('[data-video-delete-id]'); if (detailButton) { event.preventDefault(); loadVideoDetail(detailButton.getAttribute('data-video-detail-id')); return; } if (deleteButton) { event.preventDefault(); deleteVideo(deleteButton.getAttribute('data-video-delete-id')); } }); documentRef.addEventListener('submit', function (event) { var form = event.target.closest('[data-video-form]'); if (!form) return; event.preventDefault(); submitVideo(form); }); documentRef.addEventListener('change', function (event) { var input = event.target.closest('[data-video-duration-target]'); if (input && input.type === 'file') readVideoDuration(input); }); } function init() { if (!documentRef) return; bindActions(); if (query('[data-video-page]')) loadVideos(); if (query('[data-video-edit-page]')) loadVideoEditor(); } return { getCurrentGenealogyId: getCurrentGenealogyId, getCurrentVideoId: getCurrentVideoId, buildVideoBody: buildVideoBody, validateVideoBody: validateVideoBody, normalizeVideo: normalizeVideo, normalizeDurationSeconds: normalizeDurationSeconds, renderVideoRow: renderVideoRow, renderVideoDetail: renderVideoDetail, shouldRedirectToLogin: shouldRedirectToLogin, isForbidden: isForbidden, init: init }; });