|
@@ -29,13 +29,19 @@
|
|
|
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['physical:videoContent:add']">新增</el-button>
|
|
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['physical:videoContent:add']">新增</el-button>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="1.5">
|
|
<el-col :span="1.5">
|
|
|
- <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['physical:videoContent:edit']">修改</el-button>
|
|
|
|
|
|
|
+ <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['physical:videoContent:edit']"
|
|
|
|
|
+ >修改</el-button
|
|
|
|
|
+ >
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="1.5">
|
|
<el-col :span="1.5">
|
|
|
- <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['physical:videoContent:remove']">删除</el-button>
|
|
|
|
|
|
|
+ <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['physical:videoContent:remove']"
|
|
|
|
|
+ >删除</el-button
|
|
|
|
|
+ >
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="3">
|
|
<el-col :span="3">
|
|
|
- <el-button type="warning" plain icon="Wrench" @click="handleFixAll" v-hasPermi="['physical:videoContent:edit']" :loading="isFixing">修复所有数据</el-button>
|
|
|
|
|
|
|
+ <el-button type="warning" plain icon="Wrench" @click="handleFixAll" v-hasPermi="['physical:videoContent:edit']" :loading="isFixing"
|
|
|
|
|
+ >修复所有数据</el-button
|
|
|
|
|
+ >
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
|
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
|
|
</el-row>
|
|
</el-row>
|
|
@@ -68,10 +74,18 @@
|
|
|
<el-table-column label="所属标签" align="center" prop="serviceTagName" />
|
|
<el-table-column label="所属标签" align="center" prop="serviceTagName" />
|
|
|
<el-table-column label="视频时长(秒)" align="center">
|
|
<el-table-column label="视频时长(秒)" align="center">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
- <span :class="{ 'text-danger': !isDurationValid(row.durationSeconds) && row.durationSeconds !== null && row.durationSeconds !== undefined }">
|
|
|
|
|
|
|
+ <span
|
|
|
|
|
+ :class="{ 'text-danger': !isDurationValid(row.durationSeconds) && row.durationSeconds !== null && row.durationSeconds !== undefined }"
|
|
|
|
|
+ >
|
|
|
{{ row.durationSeconds ?? '待更新' }}
|
|
{{ row.durationSeconds ?? '待更新' }}
|
|
|
- <span v-if="!isDurationValid(row.durationSeconds) && row.durationSeconds !== null && row.durationSeconds !== undefined" style="font-size: 12px; color: #f56c6c;">(异常)</span>
|
|
|
|
|
- <span v-if="row.durationSeconds === null || row.durationSeconds === undefined" style="font-size: 12px; color: #909399;">(待异步更新)</span>
|
|
|
|
|
|
|
+ <span
|
|
|
|
|
+ v-if="!isDurationValid(row.durationSeconds) && row.durationSeconds !== null && row.durationSeconds !== undefined"
|
|
|
|
|
+ style="font-size: 12px; color: #f56c6c"
|
|
|
|
|
+ >(异常)</span
|
|
|
|
|
+ >
|
|
|
|
|
+ <span v-if="row.durationSeconds === null || row.durationSeconds === undefined" style="font-size: 12px; color: #909399"
|
|
|
|
|
+ >(待异步更新)</span
|
|
|
|
|
+ >
|
|
|
</span>
|
|
</span>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
@@ -99,11 +113,11 @@
|
|
|
<el-table-column label="操作" align="center" width="80">
|
|
<el-table-column label="操作" align="center" width="80">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
<el-dropdown trigger="click" placement="bottom">
|
|
<el-dropdown trigger="click" placement="bottom">
|
|
|
- <span style="color: #303133; cursor: pointer; font-size: 14px; font-weight: 500;">
|
|
|
|
|
- 操作 <span style="font-size: 14px; margin-left: 2px;">▼</span>
|
|
|
|
|
|
|
+ <span style="color: #303133; cursor: pointer; font-size: 14px; font-weight: 500">
|
|
|
|
|
+ 操作 <span style="font-size: 14px; margin-left: 2px">▼</span>
|
|
|
</span>
|
|
</span>
|
|
|
<template #dropdown>
|
|
<template #dropdown>
|
|
|
- <el-dropdown-menu style="min-width: 100px; padding: 4px 0;">
|
|
|
|
|
|
|
+ <el-dropdown-menu style="min-width: 100px; padding: 4px 0">
|
|
|
<el-dropdown-item @click="handleUpdate(scope.row)" v-hasPermi="['physical:videoContent:edit']">修改</el-dropdown-item>
|
|
<el-dropdown-item @click="handleUpdate(scope.row)" v-hasPermi="['physical:videoContent:edit']">修改</el-dropdown-item>
|
|
|
<el-dropdown-item @click="handleDelete(scope.row)" v-hasPermi="['physical:videoContent:remove']">删除</el-dropdown-item>
|
|
<el-dropdown-item @click="handleDelete(scope.row)" v-hasPermi="['physical:videoContent:remove']">删除</el-dropdown-item>
|
|
|
<el-dropdown-item @click="matchFromBackup(scope.row)" v-hasPermi="['physical:videoContent:edit']">异步获取</el-dropdown-item>
|
|
<el-dropdown-item @click="matchFromBackup(scope.row)" v-hasPermi="['physical:videoContent:edit']">异步获取</el-dropdown-item>
|
|
@@ -162,8 +176,9 @@
|
|
|
size="small"
|
|
size="small"
|
|
|
icon="Delete"
|
|
icon="Delete"
|
|
|
@click.stop="handleVideoCoverUrlRemove"
|
|
@click.stop="handleVideoCoverUrlRemove"
|
|
|
- style="position: absolute; top: 5px; right: -50px;"
|
|
|
|
|
- >删除</el-button>
|
|
|
|
|
|
|
+ style="position: absolute; top: 5px; right: -50px"
|
|
|
|
|
+ >删除</el-button
|
|
|
|
|
+ >
|
|
|
</div>
|
|
</div>
|
|
|
<div v-else style="margin-top: 10px; color: #999">无图片</div>
|
|
<div v-else style="margin-top: 10px; color: #999">无图片</div>
|
|
|
</template>
|
|
</template>
|
|
@@ -206,27 +221,42 @@
|
|
|
</div>
|
|
</div>
|
|
|
<el-progress :percentage="100" :stroke-width="20" status="success" />
|
|
<el-progress :percentage="100" :stroke-width="20" status="success" />
|
|
|
</div>
|
|
</div>
|
|
|
- <video v-if="ossVideoUrlPreviewUrl" :src="ossVideoUrlPreviewUrl" controls style="max-width: 300px; max-height: 200px; margin-top: 10px" />
|
|
|
|
|
|
|
+ <video
|
|
|
|
|
+ v-if="ossVideoUrlPreviewUrl"
|
|
|
|
|
+ :src="ossVideoUrlPreviewUrl"
|
|
|
|
|
+ controls
|
|
|
|
|
+ style="max-width: 300px; max-height: 200px; margin-top: 10px"
|
|
|
|
|
+ />
|
|
|
</template>
|
|
</template>
|
|
|
<template v-else>
|
|
<template v-else>
|
|
|
<!-- ✅ 使用 videoTempUrl 作为视频源 -->
|
|
<!-- ✅ 使用 videoTempUrl 作为视频源 -->
|
|
|
- <video v-if="ossVideoUrlPreviewUrl" :src="ossVideoUrlPreviewUrl" controls style="max-width: 300px; max-height: 200px; margin-top: 10px" @error="handleVideoError" />
|
|
|
|
|
|
|
+ <video
|
|
|
|
|
+ v-if="ossVideoUrlPreviewUrl"
|
|
|
|
|
+ :src="ossVideoUrlPreviewUrl"
|
|
|
|
|
+ controls
|
|
|
|
|
+ style="max-width: 300px; max-height: 200px; margin-top: 10px"
|
|
|
|
|
+ @error="handleVideoError"
|
|
|
|
|
+ />
|
|
|
<div v-else style="margin-top: 10px; color: #999">暂无视频</div>
|
|
<div v-else style="margin-top: 10px; color: #999">暂无视频</div>
|
|
|
- <el-button v-if="ossVideoUrlPreviewUrl || form.ossVideoUrl" type="danger" size="small" icon="Delete" @click="handleOssVideoUrlRemove" style="margin-top: 10px">删除视频</el-button>
|
|
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="ossVideoUrlPreviewUrl || form.ossVideoUrl"
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ icon="Delete"
|
|
|
|
|
+ @click="handleOssVideoUrlRemove"
|
|
|
|
|
+ style="margin-top: 10px"
|
|
|
|
|
+ >删除视频</el-button
|
|
|
|
|
+ >
|
|
|
</template>
|
|
</template>
|
|
|
</div>
|
|
</div>
|
|
|
<!-- ✅ 数据状态显示 -->
|
|
<!-- ✅ 数据状态显示 -->
|
|
|
- <div v-if="dataStatus" style="margin-top: 10px;">
|
|
|
|
|
|
|
+ <div v-if="dataStatus" style="margin-top: 10px">
|
|
|
<span :class="['status-badge', dataStatus === '正常' ? 'status-normal' : 'status-abnormal']">
|
|
<span :class="['status-badge', dataStatus === '正常' ? 'status-normal' : 'status-abnormal']">
|
|
|
{{ dataStatus || '异常' }}
|
|
{{ dataStatus || '异常' }}
|
|
|
</span>
|
|
</span>
|
|
|
- <el-button
|
|
|
|
|
- v-if="dataStatus === '异常'"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- size="small"
|
|
|
|
|
- @click="handleMatchFromBackup"
|
|
|
|
|
- style="margin-left: 10px;"
|
|
|
|
|
- >异步获取</el-button>
|
|
|
|
|
|
|
+ <el-button v-if="dataStatus === '异常'" type="primary" size="small" @click="handleMatchFromBackup" style="margin-left: 10px"
|
|
|
|
|
+ >异步获取</el-button
|
|
|
|
|
+ >
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
</el-upload>
|
|
</el-upload>
|
|
@@ -241,9 +271,7 @@
|
|
|
<el-form-item label="试看时长 (秒)" prop="previewDurationSeconds">
|
|
<el-form-item label="试看时长 (秒)" prop="previewDurationSeconds">
|
|
|
<div>
|
|
<div>
|
|
|
<el-input v-model="form.previewDurationSeconds" placeholder="请输入试看时长 (秒)" style="width: 100%" />
|
|
<el-input v-model="form.previewDurationSeconds" placeholder="请输入试看时长 (秒)" style="width: 100%" />
|
|
|
- <div style="margin-top: 4px; font-size: 12px; color: #999">
|
|
|
|
|
- 温馨提示:输入 -1 表示免费观看
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div style="margin-top: 4px; font-size: 12px; color: #999">温馨提示:输入 -1 表示免费观看</div>
|
|
|
</div>
|
|
</div>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item label="订阅时效(天)" prop="subscriptionValidHours">
|
|
<el-form-item label="订阅时效(天)" prop="subscriptionValidHours">
|
|
@@ -262,9 +290,7 @@
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item label="视频时长(秒)" prop="durationSeconds">
|
|
<el-form-item label="视频时长(秒)" prop="durationSeconds">
|
|
|
<el-input v-model="form.durationSeconds" placeholder="视频时长(秒)- 由系统自动获取" readonly />
|
|
<el-input v-model="form.durationSeconds" placeholder="视频时长(秒)- 由系统自动获取" readonly />
|
|
|
- <div style="margin-top: 4px; font-size: 12px; color: #909399;">
|
|
|
|
|
- 视频时长由系统自动获取
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div style="margin-top: 4px; font-size: 12px; color: #909399">视频时长由系统自动获取</div>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-form>
|
|
</el-form>
|
|
|
<template #footer>
|
|
<template #footer>
|
|
@@ -278,9 +304,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup name="VideoContent" lang="ts">
|
|
<script setup name="VideoContent" lang="ts">
|
|
|
-import { ref, reactive, onMounted, getCurrentInstance, type ComponentInternalInstance, onUnmounted } from 'vue';
|
|
|
|
|
-import { ElFormInstance } from 'element-plus';
|
|
|
|
|
-import { InfoFilled, Wrench, Refresh, ChevronDown } from '@element-plus/icons-vue';
|
|
|
|
|
|
|
+import { ref, reactive, onMounted, getCurrentInstance, type ComponentInternalInstance, onUnmounted, toRefs } from 'vue';import { ElFormInstance } from 'element-plus';
|
|
|
import { ElMessageBox } from 'element-plus';
|
|
import { ElMessageBox } from 'element-plus';
|
|
|
import request from '@/utils/request';
|
|
import request from '@/utils/request';
|
|
|
|
|
|
|
@@ -385,12 +409,11 @@ const isDurationValid = (duration?: number): boolean => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const isVideoDataValid = (row: VideoContentVO): boolean => {
|
|
const isVideoDataValid = (row: VideoContentVO): boolean => {
|
|
|
- const hasValidOssId = row.ossId && row.ossId > 0;
|
|
|
|
|
|
|
+ const hasValidOssId = row.ossId && Number(row.ossId) > 0;
|
|
|
const hasValidUrl = row.ossVideoUrl && String(row.ossVideoUrl).startsWith('http');
|
|
const hasValidUrl = row.ossVideoUrl && String(row.ossVideoUrl).startsWith('http');
|
|
|
const hasValidDuration = isDurationValid(row.durationSeconds);
|
|
const hasValidDuration = isDurationValid(row.durationSeconds);
|
|
|
return hasValidOssId && hasValidUrl && hasValidDuration;
|
|
return hasValidOssId && hasValidUrl && hasValidDuration;
|
|
|
};
|
|
};
|
|
|
-
|
|
|
|
|
// ============ 轮询功能 ============
|
|
// ============ 轮询功能 ============
|
|
|
// ✅ 启动轮询
|
|
// ✅ 启动轮询
|
|
|
const startPolling = (videoId: number | string) => {
|
|
const startPolling = (videoId: number | string) => {
|
|
@@ -471,7 +494,7 @@ const resetQuery = () => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const handleSelectionChange = (selection: VideoContentVO[]) => {
|
|
const handleSelectionChange = (selection: VideoContentVO[]) => {
|
|
|
- ids.value = selection.map(item => item.id);
|
|
|
|
|
|
|
+ ids.value = selection.map((item) => item.id);
|
|
|
single.value = selection.length !== 1;
|
|
single.value = selection.length !== 1;
|
|
|
multiple.value = !selection.length;
|
|
multiple.value = !selection.length;
|
|
|
};
|
|
};
|
|
@@ -642,15 +665,7 @@ const matchFromBackup = async (row: VideoContentVO) => {
|
|
|
|
|
|
|
|
const handleFixAll = async () => {
|
|
const handleFixAll = async () => {
|
|
|
try {
|
|
try {
|
|
|
- await proxy?.$modal.confirm(
|
|
|
|
|
- '确定要批量修复所有缺失URL、时长或oss_id的视频数据吗?',
|
|
|
|
|
- '批量修复',
|
|
|
|
|
- {
|
|
|
|
|
- confirmButtonText: '确定',
|
|
|
|
|
- cancelButtonText: '取消',
|
|
|
|
|
- type: 'warning'
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ await proxy?.$modal.confirm('确定要批量修复所有缺失URL、时长或oss_id的视频数据吗?');
|
|
|
|
|
|
|
|
isFixing.value = true;
|
|
isFixing.value = true;
|
|
|
|
|
|
|
@@ -681,7 +696,6 @@ const handleFixAll = async () => {
|
|
|
isFixing.value = false;
|
|
isFixing.value = false;
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
-
|
|
|
|
|
// ============ 文件上传 ============
|
|
// ============ 文件上传 ============
|
|
|
const handleOssVideoUrlChange = async (file: any) => {
|
|
const handleOssVideoUrlChange = async (file: any) => {
|
|
|
ossVideoUrlPreviewUrl.value = '';
|
|
ossVideoUrlPreviewUrl.value = '';
|
|
@@ -795,9 +809,9 @@ const handleOssVideoUrlChange = async (file: any) => {
|
|
|
ossVideoUrlPreviewUrl.value = localPreviewUrl;
|
|
ossVideoUrlPreviewUrl.value = localPreviewUrl;
|
|
|
URL.revokeObjectURL(localPreviewUrl);
|
|
URL.revokeObjectURL(localPreviewUrl);
|
|
|
|
|
|
|
|
- proxy?.$modal.msgError(error.code === 'ECONNABORTED'
|
|
|
|
|
- ? '视频上传超时,请尝试上传更小的文件或联系管理员'
|
|
|
|
|
- : '视频上传失败:' + (error.message || '未知错误'));
|
|
|
|
|
|
|
+ proxy?.$modal.msgError(
|
|
|
|
|
+ error.code === 'ECONNABORTED' ? '视频上传超时,请尝试上传更小的文件或联系管理员' : '视频上传失败:' + (error.message || '未知错误')
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
form.value.ossVideoUrl = '';
|
|
form.value.ossVideoUrl = '';
|
|
|
form.value.ossId = undefined;
|
|
form.value.ossId = undefined;
|
|
@@ -908,7 +922,7 @@ onUnmounted(() => {
|
|
|
const loadServiceTabOptions = async () => {
|
|
const loadServiceTabOptions = async () => {
|
|
|
try {
|
|
try {
|
|
|
const res = await selectEnabledTabsByCategoryList('video');
|
|
const res = await selectEnabledTabsByCategoryList('video');
|
|
|
- serviceTabOptions.value = Array.isArray(res.data) ? res.data : (Array.isArray(res) ? res : []);
|
|
|
|
|
|
|
+ serviceTabOptions.value = Array.isArray(res.data) ? res.data : Array.isArray(res) ? res : [];
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('加载页签列表失败:', error);
|
|
console.error('加载页签列表失败:', error);
|
|
|
proxy?.$modal.msgError('加载页签列表失败');
|
|
proxy?.$modal.msgError('加载页签列表失败');
|