Forráskód Böngészése

feat(video): 添加视频封面图功能并优化表格显示

- 在视频内容列表表格中新增封面列,支持图片预览功能
- 添加视频封面图上传组件,支持图片选择、预览和上传操作
- 新增封面图相关的表单字段和文件列表管理
- 修改订阅时效单位从小时改为天,统一显示单位
- 更新视频内容类型定义,添加videoCoverUrl字段支持
- 实现封面图上传、删除和预览的完整业务逻辑
fugui001 1 hete
szülő
commit
0283e0bc9e

+ 4 - 1
src/api/system/physical/videoContent/types.ts

@@ -36,6 +36,8 @@ export interface VideoContentVO {
 
   videoTempUrl: string | number;
 
+  videoCoverUrl: string;
+
   /**
    * 视频状态:up=已上架(可被用户查看/购买),down=已下架(不可见)
    */
@@ -67,7 +69,6 @@ export interface VideoContentForm extends BaseEntity {
    * 视频标题
    */
   title?: string;
-
   /**
    * 观看完整视频所需消耗的视频点数(积分)
    */
@@ -116,6 +117,8 @@ export interface VideoContentForm extends BaseEntity {
   ossId?: string | number;
 
   categoryTagId?: string | number;
+
+  videoCoverUrl: string;
 }
 
 export interface VideoContentQuery extends PageQuery {

+ 124 - 5
src/views/system/physical/videoContent/index.vue

@@ -49,13 +49,26 @@
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="编号" align="center" prop="id" v-if="true" />
         <el-table-column label="视频标题" align="center" prop="title" />
+        <el-table-column label="封面" align="center" width="90">
+          <template #default="scope">
+            <el-image
+              v-if="scope.row.videoCoverUrl"
+              :src="scope.row.videoCoverUrl"
+              style="width: 40px; height: 40px; border-radius: 4px; cursor: zoom-in"
+              :preview-src-list="[scope.row.videoCoverUrl]"
+              :preview-teleported="true"
+              fit="cover"
+            />
+            <span v-else></span>
+          </template>
+        </el-table-column>
         <el-table-column label="观看人数" align="center" prop="videoSeeCount" />
         <el-table-column label="实际观看数" align="center" prop="videoTrueSeeCount" />
         <el-table-column label="所需视频点" align="center" prop="requiredPoints" />
         <el-table-column label="所属标签" align="center" prop="title" />
         <el-table-column label="视频时长(秒)" align="center" prop="durationSeconds" />
         <el-table-column label="试看时长(秒)" align="center" prop="previewDurationSeconds" />
-        <el-table-column label="订阅时效(时)" align="center" prop="subscriptionValidHours" />
+        <el-table-column label="订阅时效()" align="center" prop="subscriptionValidHours" />
         <!--        <el-table-column label="视频文件在阿里云OSS上的完整访问URL" align="center" prop="ossVideoUrl" />-->
         <el-table-column label="视频状态" align="center">
           <template #default="{ row }">
@@ -89,6 +102,41 @@
         <el-form-item label="视频标题" prop="title">
           <el-input v-model="form.title" placeholder="请输入视频标题" />
         </el-form-item>
+        <el-form-item label="视频封面图" prop="videoCoverUrl">
+          <div class="upload-container">
+            <el-upload
+              class="upload-icon"
+              action="#"
+              :on-change="handleVideoCoverUrlChange"
+              :on-remove="handleVideoCoverUrlRemove"
+              :file-list="videoCoverUrlFileList"
+              :auto-upload="false"
+              :limit="1"
+              accept="image/*"
+            >
+              <template #trigger>
+                <el-button type="primary">点击选择封面图</el-button>
+              </template>
+
+              <template #default>
+                <div class="preview-area" @click="handleVideoCoverUrlPreviewClick">
+                  <img
+                    v-if="videoCoverUrlPreviewUrl || form.videoCoverUrl"
+                    :src="videoCoverUrlPreviewUrl || form.videoCoverUrl"
+                    alt="预览图"
+                    style="max-width: 100px; max-height: 100px; margin-top: 10px; cursor: pointer"
+                  />
+                  <div v-else style="margin-top: 10px; color: #999">无图片</div>
+                </div>
+              </template>
+              <template #tip>
+                <div class="el-upload__tip">
+                  <span v-if="videoCoverUrlFileList.length > 0">当前已选文件:{{ videoCoverUrlFileList[0].name }}</span>
+                </div>
+              </template>
+            </el-upload>
+          </div>
+        </el-form-item>
         <el-form-item label="视频文件" prop="ossVideoUrl">
           <div class="upload-container">
             <el-upload
@@ -147,8 +195,8 @@
         <el-form-item label="试看时长(秒)" prop="previewDurationSeconds">
           <el-input v-model="form.previewDurationSeconds" placeholder="请输入试看时长(秒)" />
         </el-form-item>
-        <el-form-item label="订阅时效()" prop="subscriptionValidHours">
-          <el-input v-model="form.subscriptionValidHours" placeholder="请输入订阅时效()" />
+        <el-form-item label="订阅时效()" prop="subscriptionValidHours">
+          <el-input v-model="form.subscriptionValidHours" placeholder="请输入订阅时效()" />
         </el-form-item>
         <el-form-item label="所属标签" prop="categoryTagId">
           <el-select v-model="form.categoryTagId" placeholder="请选择所属标签" filterable clearable style="width: 100%">
@@ -184,7 +232,7 @@ import {
 import { VideoContentVO, VideoContentQuery, VideoContentForm } from '@/api/system/physical/videoContent/types';
 import { ServiceTabVO } from '@/api/system/physical/serviceTab/types';
 import { selectEnabledTabsByCategoryList } from '@/api/system/physical/serviceTab';
-
+import { uploadTournament } from '@/api/system/business/tournaments';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 const videoContentList = ref<VideoContentVO[]>([]);
@@ -227,7 +275,6 @@ const data = reactive<PageData<VideoContentForm, VideoContentQuery>>({
     durationSeconds: undefined,
     previewDurationSeconds: undefined,
     subscriptionValidHours: undefined,
-    ossVideoUrl: undefined,
     status: undefined,
     createdAt: undefined,
     updatedAt: undefined,
@@ -270,6 +317,8 @@ const reset = () => {
   videoContentFormRef.value?.resetFields();
   ossVideoUrlFileList.value = [];
   ossVideoUrlPreviewUrl.value = '';
+  videoCoverUrlFileList.value = [];
+  videoCoverUrlPreviewUrl.value = '';
 };
 
 /** 搜索按钮操作 */
@@ -306,6 +355,18 @@ const handleUpdate = async (row?: VideoContentVO) => {
   Object.assign(form.value, res.data);
   const videoTempUrl2 = res.data?.videoTempUrl || res.data?.ossVideoUrl || '';
   ossVideoUrlPreviewUrl.value = String(videoTempUrl2);
+
+  if (res.data.videoCoverUrl) {
+    videoCoverUrlFileList.value = [
+      {
+        name: '已上传封面图',
+        url: res.data.videoCoverUrl,
+        status: 'success'
+      }
+    ];
+    videoCoverUrlPreviewUrl.value = res.data.videoCoverUrl;
+  }
+
   dialog.visible = true;
   dialog.title = '修改';
 };
@@ -370,6 +431,10 @@ const loadServiceTabOptions = async () => {
 const ossVideoUrlFileList = ref<any[]>([]);
 const ossVideoUrlPreviewUrl = ref('');
 
+// 视频封面图上传相关
+const videoCoverUrlFileList = ref<any[]>([]);
+const videoCoverUrlPreviewUrl = ref('');
+
 // ... existing code ...
 // 视频文件改变事件
 async function handleOssVideoUrlChange(file: any) {
@@ -404,4 +469,58 @@ function handleOssVideoUrlRemove() {
   ossVideoUrlPreviewUrl.value = '';
   form.value.ossVideoUrl = '';
 }
+
+// 视频封面图上传处理
+const handleVideoCoverUrlChange = async (file: any) => {
+  const index = videoCoverUrlFileList.value.findIndex((f) => f.uid === file.uid);
+  videoCoverUrlFileList.value = [];
+
+  if (file.raw) {
+    videoCoverUrlPreviewUrl.value = URL.createObjectURL(file.raw);
+  }
+
+  if (index === -1) {
+    videoCoverUrlFileList.value.push(file);
+  }
+
+  try {
+    const rawFile = file.raw;
+    const res = await uploadTournament(rawFile);
+    if (res.code === 200) {
+      videoCoverUrlFileList.value[index] = {
+        ...file,
+        status: 'success',
+        response: res.data.url
+      };
+      form.value.videoCoverUrl = videoCoverUrlFileList.value[index].response;
+      videoCoverUrlPreviewUrl.value = videoCoverUrlFileList.value[index].response;
+      proxy?.$modal.msgSuccess('封面图上传成功');
+    } else {
+      throw new Error(res.msg);
+    }
+  } catch (error) {
+    videoCoverUrlFileList.value[index] = {
+      ...file,
+      status: 'fail',
+      error: '上传失败'
+    };
+    proxy?.$modal.msgError('封面图上传失败,请重试');
+  }
+};
+// 视频封面图移除处理
+const handleVideoCoverUrlRemove = () => {
+  videoCoverUrlFileList.value = [];
+  videoCoverUrlPreviewUrl.value = '';
+  form.value.videoCoverUrl = '';
+};
+
+// 视频封面图预览点击
+const handleVideoCoverUrlPreviewClick = () => {
+  if (videoCoverUrlPreviewUrl.value || form.value.videoCoverUrl) {
+    ElMessageBox.alert(`<img src="${videoCoverUrlPreviewUrl.value || form.value.videoCoverUrl}" style="max-width: 100%;" />`, '视频封面图预览', {
+      dangerouslyUseHTMLString: true,
+      confirmButtonText: '关闭'
+    });
+  }
+};
 </script>