693 lines
25 KiB
JavaScript
693 lines
25 KiB
JavaScript
(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';
|
|
|
|
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 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 (source.coverOssId === null) body.coverOssId = null;
|
|
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 && body.coverOssId !== null && !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 videoFile = MediaDisplay && MediaDisplay.normalizeFileAccess(item && item.videoFile);
|
|
var coverFile = MediaDisplay && MediaDisplay.normalizeFileAccess(item && item.coverFile);
|
|
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 || !videoFile || !title || (status !== '0' && status !== '1')) return null;
|
|
if (item.coverFile !== undefined && item.coverFile !== null && !coverFile) 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),
|
|
videoFile: videoFile,
|
|
publisherNickName: stringValue(item.publisherNickName),
|
|
publisherPhone: stringValue(item.publisherPhone),
|
|
publishTime: stringValue(item.publishTime),
|
|
status: status,
|
|
remark: stringValue(item.remark)
|
|
};
|
|
if (coverFile) video.coverFile = coverFile;
|
|
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, '"')
|
|
.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 = '<button class="pill" type="button" data-video-detail-id="' + escapeHtml(video.videoId) + '">查看详情</button>';
|
|
|
|
if (managementAccess) {
|
|
actions += '<a class="pill" href="' + escapeHtml(buildEditUrl(video.genealogyId, video.videoId)) + '">编辑</a>' +
|
|
'<button class="pill is-danger" type="button" data-video-delete-id="' + escapeHtml(video.videoId) + '">删除</button>';
|
|
}
|
|
return '<article class="module-row video-row"><div><h3>' + escapeHtml(video.videoTitle) + '</h3><p>' +
|
|
escapeHtml(video.publisherNickName || '未知发布人') +
|
|
(video.publishTime ? ' · ' + escapeHtml(video.publishTime) : '') +
|
|
' · 时长 ' + escapeHtml(formatDuration(video.durationSeconds)) +
|
|
'</p></div><div class="row-actions">' + actions + '</div></article>';
|
|
}
|
|
|
|
function detailValue(label, value) {
|
|
return '<p><span>' + escapeHtml(label) + '</span><b>' + escapeHtml(value || '未提供') + '</b></p>';
|
|
}
|
|
|
|
function renderVideoDetail(video, managementAccess) {
|
|
var actions = '';
|
|
|
|
if (!video) return '';
|
|
if (managementAccess) {
|
|
actions = '<div class="bottom-actions"><a class="btn ghost" href="' +
|
|
escapeHtml(buildEditUrl(video.genealogyId, video.videoId)) + '">编辑视频</a>' +
|
|
'<button class="btn danger" type="button" data-video-delete-id="' + escapeHtml(video.videoId) + '">删除视频</button></div>';
|
|
}
|
|
return (MediaDisplay ? MediaDisplay.renderVideo(video.videoFile, {
|
|
posterFile: video.coverFile, title: video.videoTitle, className: 'video-detail-media'
|
|
}) : '') + '<div class="form-like video-detail">' +
|
|
detailValue('视频标题', video.videoTitle) +
|
|
detailValue('视频说明', video.videoDesc) +
|
|
detailValue('所属家谱', video.genealogyName || video.genealogyNo) +
|
|
detailValue('发布人', video.publisherNickName) +
|
|
detailValue('发布时间', video.publishTime) +
|
|
detailValue('视频文件', '已上传') +
|
|
detailValue('封面文件', video.coverFile ? '已上传' : '未上传') +
|
|
detailValue('视频时长', formatDuration(video.durationSeconds)) +
|
|
detailValue('浏览量', video.viewCount === undefined ? '' : String(video.viewCount)) +
|
|
detailValue('状态', video.status === '0' ? '正常' : '停用') +
|
|
detailValue('备注', video.remark) +
|
|
'</div>' + 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();
|
|
root.NavigationUtil.open('login.html');
|
|
return true;
|
|
}
|
|
|
|
function syncGenealogyLinks() {
|
|
if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) root.ProfileUI.syncGenealogyContextLinks();
|
|
}
|
|
|
|
function canEditVideos(genealogy) {
|
|
return Boolean(genealogy && (genealogy.canEditContent === true || genealogy.canManage === true));
|
|
}
|
|
|
|
function renderNoContext() {
|
|
setVideoListState('empty', '请先选择家谱,再查看家族视频。');
|
|
setVideoDetailState('empty', '当前没有家谱上下文。');
|
|
setPermissionPlaceholder('empty', '请先选择家谱,再发布视频。');
|
|
setFormStatus('请先选择家谱,再发布视频。', 'forbidden');
|
|
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 getApiStateType(error) {
|
|
return isForbidden(error) ? 'forbidden' : 'error';
|
|
}
|
|
|
|
function setVideoState(selector, type, message) {
|
|
var container = query(selector);
|
|
|
|
if (!container) return;
|
|
if (root.ProfileUI && root.ProfileUI.setApiState) {
|
|
root.ProfileUI.setApiState(container, type, message);
|
|
return;
|
|
}
|
|
container.textContent = message;
|
|
}
|
|
|
|
function setVideoListState(type, message) {
|
|
setVideoState('[data-video-list]', type, message);
|
|
}
|
|
|
|
function setVideoDetailState(type, message) {
|
|
setVideoState('[data-video-detail]', type, message);
|
|
}
|
|
|
|
function setPermissionPlaceholder(type, message) {
|
|
var placeholder = query('[data-video-permission-placeholder]');
|
|
|
|
if (!placeholder) return;
|
|
if (root.ProfileUI && root.ProfileUI.setApiState) {
|
|
root.ProfileUI.setApiState(placeholder, type, message);
|
|
return;
|
|
}
|
|
placeholder.textContent = message;
|
|
}
|
|
|
|
function setFormStatus(message, type) {
|
|
var status = query('[data-video-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 setWritePending(pending, label) {
|
|
var submit = query('[data-video-form] [type="submit"]');
|
|
|
|
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;
|
|
});
|
|
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 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) {
|
|
setVideoListState('error', '视频响应缺少稳定 VideoVo 字段,请联系后端核对。');
|
|
return [];
|
|
}
|
|
if (!videos.length) {
|
|
setVideoListState('empty', '暂无家族视频');
|
|
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) return;
|
|
if (!video) {
|
|
setVideoDetailState('empty', '请选择一个视频查看详情');
|
|
return;
|
|
}
|
|
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;
|
|
setVideoDetailState('loading', '正在加载视频详情…');
|
|
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;
|
|
setVideoDetailState(
|
|
getApiStateType(error),
|
|
isForbidden(error) ? '当前账号无权查看该视频。' : (error.message || '视频详情加载失败')
|
|
);
|
|
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();
|
|
setVideoListState('loading', '正在加载视频…');
|
|
setVideoDetailState('loading', '正在加载视频详情…');
|
|
try {
|
|
result = await Promise.all([api.genealogyDetail(genealogyId), api.videos(genealogyId)]);
|
|
setManagementAccess(canEditVideos(result[0]));
|
|
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);
|
|
setVideoListState('forbidden', '当前账号无权查看该家谱视频。');
|
|
setVideoDetailState('forbidden', '当前账号无权查看该家谱视频。');
|
|
return;
|
|
}
|
|
setVideoListState('error', '视频加载失败,请稍后重试。');
|
|
setVideoDetailState('error', '视频详情加载失败,请稍后重试。');
|
|
showMessage(error.message || '视频加载失败');
|
|
}
|
|
}
|
|
|
|
function getFormValues(form) {
|
|
var values = {};
|
|
|
|
queryAll('[name]', form).forEach(function (field) {
|
|
values[field.name] = root.AttachmentEditor ? root.AttachmentEditor.readField(field) : 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('durationSeconds', video.durationSeconds);
|
|
setFieldValue('sortOrder', video.sortOrder);
|
|
setFieldValue('status', '0');
|
|
}
|
|
|
|
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();
|
|
setFormStatus('正在读取家谱权限…', 'loading');
|
|
setPermissionPlaceholder('loading', '正在核对视频维护权限…');
|
|
try {
|
|
result = await Promise.all([
|
|
api.genealogyDetail(genealogyId),
|
|
videoId ? api.videoDetail(genealogyId, videoId) : Promise.resolve(null)
|
|
]);
|
|
setManagementAccess(canEditVideos(result[0]));
|
|
if (!canEditContent) {
|
|
setPermissionPlaceholder('forbidden', '当前账号没有视频维护权限。');
|
|
setFormStatus('当前账号没有视频维护权限。', 'forbidden');
|
|
return;
|
|
}
|
|
if (videoId) {
|
|
video = normalizeVideo(result[1]);
|
|
if (!video) throw new Error('视频详情响应缺少稳定 VideoVo 字段');
|
|
fillVideoForm(video);
|
|
root.AttachmentEditor.setFiles('#video-oss-id', result[1].videoFile ? [result[1].videoFile] : []);
|
|
root.AttachmentEditor.setFiles('#video-cover-oss-id', result[1].coverFile ? [result[1].coverFile] : []);
|
|
if (query('[data-video-editor-title]')) query('[data-video-editor-title]').textContent = '编辑视频';
|
|
}
|
|
setFormStatus('');
|
|
} catch (error) {
|
|
if (redirectUnauthorized(api, error)) return;
|
|
setManagementAccess(false);
|
|
setPermissionPlaceholder(
|
|
getApiStateType(error),
|
|
isForbidden(error) ? '当前账号没有视频维护权限。' : (error.message || '视频编辑信息加载失败')
|
|
);
|
|
setFormStatus(
|
|
isForbidden(error) ? '当前账号没有视频维护权限。' : (error.message || '视频编辑信息加载失败'),
|
|
getApiStateType(error)
|
|
);
|
|
}
|
|
}
|
|
|
|
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, 'error');
|
|
return;
|
|
}
|
|
delete body.invalidDuration;
|
|
delete body.invalidSortOrder;
|
|
setWritePending(true, '正在保存…');
|
|
setFormStatus('正在保存视频…', 'loading');
|
|
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);
|
|
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 deleteVideo(videoId) {
|
|
var api = getApi();
|
|
var genealogyId = requireGenealogyId();
|
|
var video = videosById[videoId];
|
|
|
|
if (writePending || !canEditContent || !genealogyId || !normalizeId(videoId) || redirectUnauthorized(api)) return;
|
|
if (!await confirmAction('确认删除“' + (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,
|
|
canEditVideos: canEditVideos,
|
|
renderVideoRow: renderVideoRow,
|
|
renderVideoDetail: renderVideoDetail,
|
|
setPermissionPlaceholder: setPermissionPlaceholder,
|
|
shouldRedirectToLogin: shouldRedirectToLogin,
|
|
isForbidden: isForbidden,
|
|
init: init
|
|
};
|
|
});
|