|
@@ -311,7 +311,7 @@ import { ref, reactive, onMounted, getCurrentInstance, type ComponentInternalIns
|
|
|
import { ElMessageBox } from 'element-plus';
|
|
import { ElMessageBox } from 'element-plus';
|
|
|
import request from '@/utils/request';
|
|
import request from '@/utils/request';
|
|
|
|
|
|
|
|
-import { listVideoContent, getVideoContent, delVideoContent, updateVideoContent } from '@/api/system/physical/videoContent';
|
|
|
|
|
|
|
+import { listVideoContent, getVideoContent, delVideoContent, updateVideoContent, addVideoContent } from '@/api/system/physical/videoContent';
|
|
|
import type { VideoContentVO, VideoContentForm, VideoContentQuery } from '@/api/system/physical/videoContent/types';
|
|
import type { VideoContentVO, VideoContentForm, VideoContentQuery } from '@/api/system/physical/videoContent/types';
|
|
|
import { selectEnabledTabsByCategoryList } from '@/api/system/physical/serviceTab';
|
|
import { selectEnabledTabsByCategoryList } from '@/api/system/physical/serviceTab';
|
|
|
import type { ServiceTabVO } from '@/api/system/physical/serviceTab/types';
|
|
import type { ServiceTabVO } from '@/api/system/physical/serviceTab/types';
|
|
@@ -425,7 +425,7 @@ const startPolling = (videoId: number | string) => {
|
|
|
|
|
|
|
|
pollTimer.value = window.setInterval(async () => {
|
|
pollTimer.value = window.setInterval(async () => {
|
|
|
await pollVideoStatus(videoId);
|
|
await pollVideoStatus(videoId);
|
|
|
- }, 1000); // 每秒查询一次
|
|
|
|
|
|
|
+ }, 3000); // 3秒查询一次(大文件时长提取耗时长,窗口约5分钟)
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// ✅ 停止轮询
|
|
// ✅ 停止轮询
|
|
@@ -446,9 +446,9 @@ const pollVideoStatus = async (videoId: number | string) => {
|
|
|
method: 'GET'
|
|
method: 'GET'
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- if (res.data && res.data.data) {
|
|
|
|
|
- const statusData = res.data.data;
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // 拦截器已解包一层:res 即 R 体,res.data 就是状态 VO(原来多取一层 .data 导致轮询从不生效)
|
|
|
|
|
+ const statusData: any = res.data;
|
|
|
|
|
+ if (statusData) {
|
|
|
// ✅ 更新时长
|
|
// ✅ 更新时长
|
|
|
if (statusData.durationSeconds !== undefined && statusData.durationSeconds !== null) {
|
|
if (statusData.durationSeconds !== undefined && statusData.durationSeconds !== null) {
|
|
|
form.value.durationSeconds = statusData.durationSeconds;
|
|
form.value.durationSeconds = statusData.durationSeconds;
|
|
@@ -461,16 +461,16 @@ const pollVideoStatus = async (videoId: number | string) => {
|
|
|
|
|
|
|
|
// ⚠️ 不要更新视频URL!只有异步获取时才更新
|
|
// ⚠️ 不要更新视频URL!只有异步获取时才更新
|
|
|
|
|
|
|
|
- // 数据正常或超过30秒停止轮询
|
|
|
|
|
- if (statusData.dataStatus === '正常' || pollCount.value >= 30) {
|
|
|
|
|
|
|
+ // 数据正常或超过100次(约5分钟)停止轮询;还没跑完的靠刷新列表或点「异步获取」兑底
|
|
|
|
|
+ if (statusData.dataStatus === '正常' || pollCount.value >= 100) {
|
|
|
stopPolling();
|
|
stopPolling();
|
|
|
await getList(); // 刷新列表
|
|
await getList(); // 刷新列表
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('轮询查询状态失败:', error);
|
|
console.error('轮询查询状态失败:', error);
|
|
|
- // 如果轮询失败超过30次也停止
|
|
|
|
|
- if (pollCount.value >= 30) {
|
|
|
|
|
|
|
+ // 如果轮询失败超过100次也停止
|
|
|
|
|
+ if (pollCount.value >= 100) {
|
|
|
stopPolling();
|
|
stopPolling();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -536,6 +536,9 @@ const handleUpdate = async (row?: VideoContentVO) => {
|
|
|
Object.assign(form.value, res.data);
|
|
Object.assign(form.value, res.data);
|
|
|
ossVideoUrlPreviewUrl.value = String(res.data?.ossVideoUrl || '');
|
|
ossVideoUrlPreviewUrl.value = String(res.data?.ossVideoUrl || '');
|
|
|
|
|
|
|
|
|
|
+ // 初始化数据状态:异常行打开编辑时直接显示「异步获取」修复按钮
|
|
|
|
|
+ dataStatus.value = Number(form.value.durationSeconds) > 0 && form.value.ossVideoUrl ? '正常' : '异常';
|
|
|
|
|
+
|
|
|
if (res.data.videoCoverUrl) {
|
|
if (res.data.videoCoverUrl) {
|
|
|
videoCoverUrlFileList.value = [{ name: '已上传封面图', url: res.data.videoCoverUrl, status: 'success' }];
|
|
videoCoverUrlFileList.value = [{ name: '已上传封面图', url: res.data.videoCoverUrl, status: 'success' }];
|
|
|
videoCoverUrlPreviewUrl.value = res.data.videoCoverUrl;
|
|
videoCoverUrlPreviewUrl.value = res.data.videoCoverUrl;
|
|
@@ -561,6 +564,17 @@ const submitForm = () => {
|
|
|
try {
|
|
try {
|
|
|
if (form.value.id) {
|
|
if (form.value.id) {
|
|
|
await updateVideoContent(form.value);
|
|
await updateVideoContent(form.value);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 新增模式:按确定时才创建记录并开始后台处理(上传阶段只传OSS不建记录)
|
|
|
|
|
+ const addRes = await addVideoContent(form.value);
|
|
|
|
|
+ const newId = addRes.data;
|
|
|
|
|
+ if (newId) {
|
|
|
|
|
+ form.value.id = Number(newId);
|
|
|
|
|
+ // 时长还没就绪(大文件异步提取)时启动轮询自动补
|
|
|
|
|
+ if (!(Number(form.value.durationSeconds) > 0)) {
|
|
|
|
|
+ startPolling(form.value.id);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
proxy?.$modal.msgSuccess('操作成功');
|
|
proxy?.$modal.msgSuccess('操作成功');
|
|
|
dialog.visible = false;
|
|
dialog.visible = false;
|
|
@@ -733,7 +747,8 @@ const handleOssVideoUrlChange = async (file: any) => {
|
|
|
if (form.value.videoSeeCount) formData.append('videoSeeCount', String(form.value.videoSeeCount));
|
|
if (form.value.videoSeeCount) formData.append('videoSeeCount', String(form.value.videoSeeCount));
|
|
|
if (form.value.id) formData.append('videoId', String(form.value.id));
|
|
if (form.value.id) formData.append('videoId', String(form.value.id));
|
|
|
|
|
|
|
|
- const timeout = Math.max(300000, Math.floor(fileSizeMB * 3000));
|
|
|
|
|
|
|
+ // 超时:底10分钟 + 每MB 2秒(按上行0.5MB/s估并留4倍余量),2GB≈67分钟
|
|
|
|
|
+ const timeout = Math.max(600000, Math.floor(fileSizeMB * 2000));
|
|
|
|
|
|
|
|
const uploadRes = await request({
|
|
const uploadRes = await request({
|
|
|
url: '/physical/videoContent/uploadVideo',
|
|
url: '/physical/videoContent/uploadVideo',
|
|
@@ -753,61 +768,41 @@ const handleOssVideoUrlChange = async (file: any) => {
|
|
|
|
|
|
|
|
uploading.value = false;
|
|
uploading.value = false;
|
|
|
|
|
|
|
|
- // ⚠️ 根据核心原则:上传成功后不设置视频URL!
|
|
|
|
|
- // 视频URL只在两个地方设置:1. 修改页面打开时 2. 异步获取成功时
|
|
|
|
|
- // 拦截器已解包一层:uploadRes 即 R 体,uploadRes.data 就是 SysOssVo(原来多取一层 .data 全为 undefined)
|
|
|
|
|
|
|
+ // 拦截器已解包一层:uploadRes 即 R 体,uploadRes.data 就是 SysOssVo
|
|
|
const ossData: any = uploadRes.data || {};
|
|
const ossData: any = uploadRes.data || {};
|
|
|
- const videoUrl = ossData.url || ossData.ossVideoUrl;
|
|
|
|
|
const ossId = ossData.ossId;
|
|
const ossId = ossData.ossId;
|
|
|
- const duration = ossData.duration || ossData.videoDuration;
|
|
|
|
|
- const videoFileName = ossData.videoFileName;
|
|
|
|
|
- const serviceTagName = ossData.serviceTagName;
|
|
|
|
|
-
|
|
|
|
|
- // ❌ 删除:不设置视频URL
|
|
|
|
|
- // form.value.ossVideoUrl = videoUrl || localPreviewUrl;
|
|
|
|
|
- // ossVideoUrlPreviewUrl.value = videoTempUrl;
|
|
|
|
|
|
|
+ const permanentUrl = ossData.url;
|
|
|
|
|
+ const duration = ossData.duration;
|
|
|
|
|
|
|
|
if (ossId) form.value.ossId = ossId;
|
|
if (ossId) form.value.ossId = ossId;
|
|
|
- if (videoFileName) {
|
|
|
|
|
- form.value.videoFileName = videoFileName;
|
|
|
|
|
|
|
+ // 永久URL:新增模式确定时入库用;编辑模式 updateByBo 会以同值覆盖
|
|
|
|
|
+ if (permanentUrl) form.value.ossVideoUrl = permanentUrl;
|
|
|
|
|
+ if (ossData.originalName) {
|
|
|
|
|
+ form.value.videoFileName = ossData.originalName;
|
|
|
} else if (fileObj?.name) {
|
|
} else if (fileObj?.name) {
|
|
|
form.value.videoFileName = fileObj.name;
|
|
form.value.videoFileName = fileObj.name;
|
|
|
}
|
|
}
|
|
|
- if (serviceTagName) {
|
|
|
|
|
- form.value.serviceTagName = serviceTagName;
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- // 后端把新建视频主键放在 ext1 返回(SysOssVo 无 id 字段),取不到会导致确定时不走整体更新、选视频后改的字段(如所属标签)丢失
|
|
|
|
|
- const newVideoId = ossData.id || ossData.ext1;
|
|
|
|
|
- if (newVideoId) {
|
|
|
|
|
- form.value.id = Number(newVideoId);
|
|
|
|
|
|
|
+ const finalDuration = duration && !isNaN(duration) && Number(duration) > 0 ? Math.floor(Number(duration)) : 0;
|
|
|
|
|
+ if (finalDuration > 0) {
|
|
|
|
|
+ form.value.durationSeconds = finalDuration;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ // 只有编辑模式才回传主键 ext1(记录已存在);新增模式上传不建记录,需按确定才入库
|
|
|
|
|
+ const returnedVideoId = ossData.ext1;
|
|
|
|
|
+ if (returnedVideoId) {
|
|
|
|
|
+ form.value.id = Number(returnedVideoId);
|
|
|
// ✅ 上传成功后启动轮询(轮询只更新时长和状态,不更新视频URL)
|
|
// ✅ 上传成功后启动轮询(轮询只更新时长和状态,不更新视频URL)
|
|
|
startPolling(form.value.id);
|
|
startPolling(form.value.id);
|
|
|
|
|
+ await getList();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- let finalDuration = duration && !isNaN(duration) && Number(duration) > 0 ? Math.floor(Number(duration)) : 0;
|
|
|
|
|
-
|
|
|
|
|
- if (!isDurationValid(finalDuration) && form.value.id) {
|
|
|
|
|
- try {
|
|
|
|
|
- const durationRes = await request({
|
|
|
|
|
- url: `/physical/videoContent/getVideoDuration/${form.value.id}`,
|
|
|
|
|
- method: 'GET'
|
|
|
|
|
- });
|
|
|
|
|
- if (durationRes.data && durationRes.data.success && durationRes.data.duration > 0) {
|
|
|
|
|
- finalDuration = durationRes.data.duration;
|
|
|
|
|
- }
|
|
|
|
|
- } catch (durationError) {
|
|
|
|
|
- console.error('调用获取时长接口失败:', durationError);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (finalDuration > 0) {
|
|
|
|
|
- form.value.durationSeconds = finalDuration;
|
|
|
|
|
|
|
+ // 提示语与实际链路对齐:≤1GB 时长同步提取;>1GB 才是后台异步提取;同步提取失败时只能靠确定后点「异步获取」从备份表修复
|
|
|
|
|
+ let uploadMsg = '视频上传成功!时长已自动获取';
|
|
|
|
|
+ if (finalDuration <= 0) {
|
|
|
|
|
+ uploadMsg = fileSizeMB > 1024 ? '视频上传成功!时长后台提取中,按确定后自动补齐' : '视频上传成功!但未获取到时长,按确定后如数据状态异常可点「异步获取」从备份表修复';
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- proxy?.$modal.msgSuccess('视频上传成功!时长已自动获取');
|
|
|
|
|
- await getList();
|
|
|
|
|
|
|
+ proxy?.$modal.msgSuccess(uploadMsg);
|
|
|
} catch (error: any) {
|
|
} catch (error: any) {
|
|
|
console.error('视频上传失败:', error);
|
|
console.error('视频上传失败:', error);
|
|
|
uploading.value = false;
|
|
uploading.value = false;
|