wengan01 hace 2 semanas
padre
commit
176acc535d

+ 4 - 392
src/api/system/physical/tournaments/types.ts

@@ -1,342 +1,81 @@
 export interface TournamentsVO {
-  /**
-   *
-   */
   id: string | number;
-
-  /**
-   * 赛事名称
-   */
   name: string;
-
-  /**
-   * 比赛开始时间
-   */
   startTime: string;
-
-  /**
-   * 比赛结束时间
-   */
   endTime: string;
-
-  /**
-   * 比赛暂停时间
-   */
   pauseTime: string;
-
-  /**
-   * 游戏类型
-   */
   gameType: number;
-
-  /**
-   * 游戏玩法类型:0=德州扑克, 1=奥马哈, 2=短牌
-   */
   gameVariant: number;
-
-  /**
-   * 起始记分牌数量
-   */
   startingChips: number;
-
-  /**
-   * 级别持续时间(分钟)
-   */
   levelDuration: number;
-
-  /**
-   * 起始级别
-   */
   startBlindLevel?: number;
-
-  /**
-   * 截止报名级别
-   */
   lateRegistrationLevel: number;
-
-  /**
-   * 最大参赛人数
-   */
   maxPlayers: number;
-
-  /**
-   *
-   */
   minPlayers: number;
-
-  /**
-   * 奖励人数
-   */
   rewardPlayers: number;
-
-  /**
-   * 总手数,包括rebuy
-   */
   totalSignup: number;
-
-  /**
-   * 机器人报名默认数量,0-为不开启机器人报名
-   */
   robotCount: number;
-
-  /**
-   * 赛事状态 0 未开始 1 进行中 2 同步发牌 3 普通暂停 4 完成 5 异常
-   */
   status: number;
-
-  /**
-   * 报名时间
-   */
   signTime: number;
-
-  /**
-   * 比赛图标
-   */
   competitionIcon: string;
   itemsPrizeList?: ItemsPrize[];
   itemsConditionList?: ItemsPrize[];
-  /**
-   * 赛事背景图
-   */
   competitionBg: string;
-
-  /**
-   * 比赛ID
-   */
   tournamentsBiId: string | number;
-
-  /**
-   * 修改人
-   */
   updateUserId: string | number;
-
-  /**
-   *
-   */
   updatedAt: string;
-
-  /**
-   * 创建人
-   */
   createUserId: string | number;
-
-  /**
-   *
-   */
   createdAt: string;
-
-  /**
-   * 是否删除
-   */
   isDelete: number;
-
-  /**
-   * 延迟卡时间
-   */
   delayCardTime: number;
-
-  /**
-   * 延迟卡数量
-   */
   delayCardNum: number;
-
-  /**
-   * 行动时间(使用卡延时)
-   */
   actionTime: number;
-
-  /**
-   * 目标锦标赛ID(预赛晋级到的决赛ID)
-   */
   targetTournamentId: string | number;
-
-  /**
-   * 晋级条件类型:0=无晋级,1=按级别晋级,2=按人数比例晋级
-   */
   qualifierType: number;
-
-  /**
-   * 晋级条件值:级别数或人数比例(1-100)
-   */
   qualifierValue: number;
-
   tournamentType?: number;
-
   isShow: number;
+  isOffline?: number; // 👈 新增字段
 }
 
 export interface TournamentsForm extends BaseEntity {
-  /**
-   *
-   */
   id?: string | number;
-
-  /**
-   * 赛事名称
-   */
   name?: string;
-
-  /**
-   * 赛事位置
-   */
   competitionLocation?: string;
-
-  /**
-   * 比赛开始时间
-   */
   startTime?: string;
-
-  /**
-   * 比赛结束时间
-   */
   endTime?: string;
-
-  /**
-   * 比赛暂停时间
-   */
   pauseTime?: string;
-
-  /**
-   * 游戏类型
-   */
   gameType?: string;
-
-  /**
-   * 游戏玩法类型:0=德州扑克, 1=奥马哈, 2=短牌
-   */
   gameVariant?: string;
-
-  /**
-   * 起始记分牌数量
-   */
   startingChips?: number;
-
-  /**
-   * 级别持续时间(分钟)
-   */
   levelDuration?: number;
-
-  /**
-   * 截止报名级别
-   */
   lateRegistrationLevel?: number;
-
   startBlindLevel?: number;
-
-  /**
-   * 最大参赛人数
-   */
   maxPlayers?: number;
-
-  /**
-   *
-   */
   minPlayers?: number;
-
   conditionNote?: string;
-
-  /**
-   * 奖励人数
-   */
   rewardPlayers?: number;
-
-  /**
-   * 总手数,包括rebuy
-   */
   totalSignup?: number;
-
-  /**
-   * 机器人报名默认数量,0-为不开启机器人报名
-   */
   robotCount?: number;
-
-  /**
-   * 赛事状态 0 未开始 1 进行中 2 同步发牌 3 普通暂停 4 完成 5 异常
-   */
   status?: number;
-
-  /**
-   * 报名时间
-   */
   signTime?: string;
-
-  /**
-   * 道具ID
-   */
   itemsId?: number;
-
   itemsNum?: number;
-
   blindStructureId?: number;
-
-  /**
-   * 比赛图标
-   */
   competitionIcon?: string;
-
-  /**
-   * 赛事背景图
-   */
   competitionBg?: string;
-
-  /**
-   * 比赛ID
-   */
   tournamentsBiId?: string | number;
-
-  /**
-   * 修改人
-   */
   updateUserId?: string | number;
-
-  /**
-   *
-   */
   updatedAt?: string;
-
-  /**
-   * 创建人
-   */
   createUserId?: string | number;
-
-  /**
-   *
-   */
   createdAt?: string;
-
-  /**
-   * 是否删除
-   */
   isDelete?: number;
-
-  /**
-   * 延迟卡时间
-   */
   delayCardTime?: number;
-
-  /**
-   * 延迟卡数量
-   */
   delayCardNum?: number;
-
-  /**
-   * 行动时间(使用卡延时)
-   */
   actionTime?: number;
-
-  /**
-   * 目标锦标赛ID(预赛晋级到的决赛ID)
-   */
   targetTournamentId?: string | number;
-
-  /**
-   * 晋级条件类型:0=无晋级,1=按级别晋级,2=按人数比例晋级
-   */
   qualifierType?: string;
   tournamentType?: number;
-  /**
-   * 晋级条件值:级别数或人数比例(1-100)
-   */
   qualifierValue?: number;
   itemsPrizeList?: ItemsPrize[];
   itemsConditionList?: ItemsPrize[];
@@ -346,173 +85,46 @@ export interface TournamentsForm extends BaseEntity {
   leagueTournamentRegion?: string;
   judgeId?: number[];
   isShow: number;
+  isOffline?: number; // 👈 新增字段
 }
-export interface ItemsPrize {
-  ranking: number;
-  itemId: number;
-  quantity: number;
-}
+
 export interface TournamentsQuery extends PageQuery {
-  /**
-   * 赛事名称
-   */
   id?: string;
-  /**
-   * 赛事名称
-   */
   name?: string;
-
-  /**
-   * 比赛开始时间
-   */
   startTime?: string;
-
   startTimeOne?: string;
-  /**
-   * 比赛结束时间
-   */
   endTime?: string;
-
-  /**
-   * 比赛暂停时间
-   */
   pauseTime?: string;
-
   startBlindLevel?: number;
-  /**
-   * 游戏类型
-   */
   gameType?: number;
-
-  /**
-   * 游戏玩法类型:0=德州扑克, 1=奥马哈, 2=短牌
-   */
   gameVariant?: number;
-
-  /**
-   * 起始记分牌数量
-   */
   startingChips?: number;
-
-  /**
-   * 级别持续时间(分钟)
-   */
   levelDuration?: number;
-
-  /**
-   * 截止报名级别
-   */
   lateRegistrationLevel?: number;
-
-  /**
-   * 最大参赛人数
-   */
   maxPlayers?: number;
-
-  /**
-   *
-   */
   minPlayers?: number;
-
-  /**
-   * 奖励人数
-   */
   rewardPlayers?: number;
-
-  /**
-   * 总手数,包括rebuy
-   */
   totalSignup?: number;
-
-  /**
-   * 机器人报名默认数量,0-为不开启机器人报名
-   */
   robotCount?: number;
-
-  /**
-   * 赛事状态 0 未开始 1 进行中 2 同步发牌 3 普通暂停 4 完成 5 异常
-   */
   status?: number;
-
-  /**
-   * 报名时间
-   */
   signTime?: number;
-
-  /**
-   * 比赛图标
-   */
   competitionIcon?: string;
-
-  /**
-   * 赛事背景图
-   */
   competitionBg?: string;
-
-  /**
-   * 比赛ID
-   */
   tournamentsBiId?: string | number;
-
-  /**
-   * 修改人
-   */
   updateUserId?: string | number;
-
-  /**
-   *
-   */
   updatedAt?: string;
-
-  /**
-   * 创建人
-   */
   createUserId?: string | number;
-
-  /**
-   *
-   */
   createdAt?: string;
-
-  /**
-   * 是否删除
-   */
   isDelete?: number;
-
-  /**
-   * 延迟卡时间
-   */
   delayCardTime?: number;
-
-  /**
-   * 延迟卡数量
-   */
   delayCardNum?: number;
-
-  /**
-   * 行动时间(使用卡延时)
-   */
   actionTime?: number;
-
-  /**
-   * 目标锦标赛ID(预赛晋级到的决赛ID)
-   */
   targetTournamentId?: string | number;
-
-  /**
-   * 晋级条件类型:0=无晋级,1=按级别晋级,2=按人数比例晋级
-   */
   qualifierType?: number;
-
-  /**
-   * 晋级条件值:级别数或人数比例(1-100)
-   */
   qualifierValue?: number;
   tournamentType?: number;
   isShow: number;
-  /**
-   * 日期范围参数
-   */
+  isOffline?: number; // 👈 新增字段
   params?: any;
   leagueTournamentId?: string | number;
 }

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

@@ -107,7 +107,10 @@ export interface TournamentsRegistrationQuery extends PageQuery {
    * 用户ID(对应玩家表)
    */
   userId?: string | number;
-
+  /**
+   * 赛事ID
+   */
+  tournamentId?: string | number;
   /**
    * 锦标联赛-赛事ID(外键,关联physical_league_tournament表)
    */

+ 1 - 1
src/utils/request.ts

@@ -28,7 +28,7 @@ axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID;
 // 创建 axios 实例
 const service = axios.create({
   baseURL: import.meta.env.VITE_APP_BASE_API,
-  timeout: 50000
+  timeout: 300000 // 5分钟超时,支持大文件上传
 });
 
 // 请求拦截器

+ 23 - 4
src/views/system/physical/participants/index.vue

@@ -144,9 +144,11 @@
 </template>
 
 <script setup name="Participants" lang="ts">
-import { ref, reactive, onMounted, toRefs } from 'vue';
+import { ref, reactive, onMounted, toRefs, watch } from 'vue';
+import { useRoute } from 'vue-router';  // 添加这一行
 import { ElFormInstance } from 'element-plus';
 import { ComponentInternalInstance, getCurrentInstance } from 'vue';
+const route = useRoute();  // 添加这一行
 import {
   listParticipants,
   getParticipants,
@@ -350,7 +352,24 @@ const handleExport = () => {
   );
 };
 
-onMounted(() => {
-  getList();
-});
+// 监听路由变化
+watch(
+  () => route.query,
+  (newQuery) => {
+    console.log('路由参数变化:', newQuery);
+    if (newQuery.tournamentId) {
+      // 手动重置所有查询参数(不调用 resetQuery,因为它会自动 getList)
+      queryParams.value.tournamentId = '';
+      queryParams.value.name = '';
+      queryParams.value.mobile = '';
+      queryParams.value.registrationTime = '';
+      queryParams.value.pageNum = 1;
+      // 然后设置 tournamentId
+      queryParams.value.tournamentId = newQuery.tournamentId;
+      console.log('设置 tournamentId:', queryParams.value.tournamentId);
+      getList();
+    }
+  },
+  { immediate: true }  // 立即执行一次
+);
 </script>

+ 57 - 42
src/views/system/physical/tournaments/index.vue

@@ -141,32 +141,26 @@
             </span>
           </template>
         </el-table-column>
-        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-          <template #default="scope">
-            <el-tooltip content="修改" placement="top">
-              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['physical:tournaments:edit']">修改</el-button>
-            </el-tooltip>
-            <el-tooltip content="删除" placement="top">
-              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['physical:tournaments:remove']"
-                >删除</el-button
-              >
-            </el-tooltip>
-            <el-tooltip content="复制" placement="top" v-hasPermi="['business:tournaments:query']">
-              <el-button link type="primary" icon="Files" @click="handleCopy(scope.row)"> 复制 </el-button>
-            </el-tooltip>
-            <el-tooltip :content="scope.row.isShow === 1 ? '隐藏' : '展示'" placement="top">
-              <el-button
-                link
-                :type="scope.row.isShow === 1 ? 'warning' : 'success'"
-                :icon="scope.row.isShow === 1 ? 'Hide' : 'View'"
-                @click="handleToggleShow(scope.row)"
-                v-hasPermi="['physical:tournaments:edit']"
-              >
-                {{ scope.row.isShow === 1 ? '隐藏' : '展示' }}
-              </el-button>
-            </el-tooltip>
-          </template>
-        </el-table-column>
+               <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                 <template #default="scope">
+                   <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>
+                     <template #dropdown>
+                       <el-dropdown-menu style="min-width: 100px; padding: 4px 0;">
+                         <el-dropdown-item @click="handleUpdate(scope.row)" v-hasPermi="['physical:tournaments:edit']">修改</el-dropdown-item>
+                         <el-dropdown-item @click="handleDelete(scope.row)" v-hasPermi="['physical:tournaments:remove']">删除</el-dropdown-item>
+                         <el-dropdown-item @click="handleCopy(scope.row)" v-hasPermi="['business:tournaments:query']">复制</el-dropdown-item>
+                         <el-dropdown-item @click="handleToggleShow(scope.row)" v-hasPermi="['physical:tournaments:edit']">
+                           {{ scope.row.isShow === 1 ? '隐藏' : '展示' }}
+                         </el-dropdown-item>
+                         <el-dropdown-item @click="handleQueryItem(scope.row)">查询线下报名记录</el-dropdown-item>
+                       </el-dropdown-menu>
+                     </template>
+                   </el-dropdown>
+                 </template>
+               </el-table-column>
       </el-table>
 
       <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
@@ -473,6 +467,12 @@
             <el-option v-for="item in itemOptionsStructuresLevel2" :key="item.id" :label="item.label" :value="item.id" />
           </el-select>
         </el-form-item>
+        <el-form-item label="是否可以报名" prop="isOffline">
+          <el-select v-model="form.isOffline" placeholder="请选择" style="width: 200px" :disabled="dialog.mode === 'view'">
+            <el-option label="是" :value="1" />
+            <el-option label="否" :value="0" />
+          </el-select>
+        </el-form-item>
       </el-form>
       <template #footer>
         <div class="dialog-footer">
@@ -519,7 +519,9 @@ import { LeagueTournamentVO } from '@/api/system/physical/leagueTournament/types
 import { JudgeVO } from '@/api/system/physical/judge/types';
 import { ElSelect } from 'element-plus';
 import { ref, nextTick } from 'vue';
+import { useRouter } from 'vue-router';
 import { CompetitionZoneVO } from '@/api/system/physical/competitionZone/types';
+const router = useRouter();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { game_variant_type, tournaments_time, physical_tournaments_type, qualifier_type } = toRefs<any>(
   proxy?.useDict('game_variant_type', 'tournaments_time', 'physical_tournaments_type', 'qualifier_type')
@@ -579,7 +581,8 @@ const initFormData: TournamentsForm = {
   targetTournamentId: undefined,
   qualifierType: undefined,
   qualifierValue: undefined,
-  tournamentType: 0 // 👈 默认普通赛
+  tournamentType: 0,
+  isOffline: 1,
 };
 const data = reactive<PageData<TournamentsForm, TournamentsQuery>>({
   form: { ...initFormData },
@@ -733,22 +736,23 @@ const data = reactive<PageData<TournamentsForm, TournamentsQuery>>({
         trigger: 'blur'
       }
     ],
-    minPlayers: [
-      { required: false, message: '参赛人数不能为空', trigger: 'blur' },
-      {
-        validator: (rule: any, value: any, callback: any) => {
-          const num = Number(value);
-          if (!/^\d+$/.test(value)) {
-            callback(new Error('只能输入正整数'));
-          } else if (num < 0 || num > 999) {
-            callback(new Error('参赛人数必须在0-999之间'));
-          } else {
-            callback();
+     minPlayers: [
+          { required: false, message: '参赛人数不能为空', trigger: 'blur' },
+          {
+            validator: (rule: any, value: any, callback: any) => {
+              const num = Number(value);
+              if (!/^\d+$/.test(value)) {
+                callback(new Error('只能输入正整数'));
+              } else if (num < 0 || num > 999) {
+                callback(new Error('参赛人数必须在0-999之间'));
+              } else {
+                callback();
+              }
+            },
+            trigger: 'blur'
           }
-        },
-        trigger: 'blur'
-      }
-    ]
+        ],
+        isOffline: [{ required: true, message: '是否可以报名不能为空', trigger: 'change' }]
   }
 });
 // 控制 Dialog 是否显示
@@ -1524,4 +1528,15 @@ const handleToggleShow = async (row: TournamentsVO) => {
     }
   }
 };
+/** 查询按钮操作 */
+const handleQueryItem = (row: TournamentsVO) => {
+  // 跳转到线下报名记录页面
+  router.push({
+    path: '/physical/participants',  // 改对路径!
+    query: {
+      tournamentId: row.id
+    }
+  });
+};
+
 </script>

+ 15 - 0
src/views/system/physical/tournamentsRegistration/index.vue

@@ -7,6 +7,9 @@
             <el-form-item label="用户ID" prop="userId">
               <el-input v-model="queryParams.userId" placeholder="请输入用户ID" clearable @keyup.enter="handleQuery" />
             </el-form-item>
+            <el-form-item label="赛事ID" prop="tournamentId">
+              <el-input v-model="queryParams.tournamentId" placeholder="请输入赛事ID" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
             <el-form-item label="所属联赛" prop="leagueTournamentId">
               <el-select v-model="queryParams.leagueTournamentId" placeholder="请选择联赛">
                 <el-option v-for="item in leagueTournamentOptions" :key="item.id" :label="item.title" :value="item.id" />
@@ -206,8 +209,11 @@ import {
 import { ElSelect } from 'element-plus';
 import { LeagueTournamentVO } from '@/api/system/physical/leagueTournament/types';
 import { parseTime } from '@/utils/dateUtils';
+import { useRoute, onMounted } from 'vue-router';
+const route = useRoute();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
+
 const tournamentsRegistrationList = ref<TournamentsRegistrationVO[]>([]);
 const buttonLoading = ref(false);
 const loading = ref(true);
@@ -243,6 +249,7 @@ const data = reactive<PageData<TournamentsRegistrationForm, TournamentsRegistrat
     pageNum: 1,
     pageSize: 10,
     userId: undefined,
+    tournamentId: undefined,
     leagueTournamentId: undefined,
     imageUrl: undefined,
     status: undefined,
@@ -271,6 +278,14 @@ const getList = async () => {
   loading.value = false;
 };
 
+// 页面加载时检查路由参数
+onMounted(() => {
+  if (route.query.tournamentId) {
+    queryParams.value.tournamentId = route.query.tournamentId;
+    getList();
+  }
+});
+
 /** 取消按钮 */
 const cancel = () => {
   reset();

+ 632 - 242
src/views/system/physical/videoContent/index.vue

@@ -8,7 +8,7 @@
               <el-input v-model="queryParams.title" placeholder="请输入视频标题" clearable @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item label="状态" prop="status">
-              <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
+              <el-select v-model="queryParams.status" placeholder="请选择状态" clearable @change="handleQuery">
                 <el-option label="上架" value="up" />
                 <el-option label="下架" value="down" />
               </el-select>
@@ -34,29 +34,45 @@
           <el-col :span="1.5">
             <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['physical:videoContent:remove']">删除</el-button>
           </el-col>
-          <el-col :span="2">
-            <el-button type="warning" plain icon="Wrench" @click="handleFixAll" v-hasPermi="['physical:videoContent:edit']">修复所有数据</el-button>
+          <el-col :span="3">
+            <el-button type="warning" plain icon="Wrench" @click="handleFixAll" v-hasPermi="['physical:videoContent:edit']" :loading="isFixing">修复所有数据</el-button>
           </el-col>
           <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
         </el-row>
       </template>
 
-      <el-table v-loading="loading" border :data="videoContentList" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" border :data="videoContentList" @selection-change="handleSelectionChange" :row-height="60">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="编号" align="center" prop="id" />
+        <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" prop="videoFileName" />
+        <el-table-column label="文件名" align="center" prop="videoFileName" width="180">
+          <template #default="scope">
+            <span class="file-name-ellipsis" :title="scope.row.videoFileName">{{ scope.row.videoFileName }}</span>
+          </template>
+        </el-table-column>
         <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" fit="cover" />
+            <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="requiredPoints" />
         <el-table-column label="所属标签" align="center" prop="serviceTagName" />
-        <el-table-column label="视频时长" align="center">
+        <el-table-column label="视频时长(秒)" align="center">
           <template #default="{ row }">
-            {{ row.durationSeconds ? formatDuration(row.durationSeconds) : '待更新' }}
+            <span :class="{ 'text-danger': !isDurationValid(row.durationSeconds) && row.durationSeconds !== null && row.durationSeconds !== undefined }">
+              {{ 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>
           </template>
         </el-table-column>
         <el-table-column label="试看时长(秒)" align="center" prop="previewDurationSeconds" />
@@ -76,17 +92,21 @@
           </template>
         </el-table-column>
         <el-table-column label="创建时间" align="center" prop="createdAt" width="180">
-          <template #default="scope">{{ parseTime(scope.row.createdAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</template>
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.createdAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+          </template>
         </el-table-column>
         <el-table-column label="操作" align="center" width="80">
           <template #default="scope">
-            <el-dropdown trigger="click" @command="(cmd) => handleOperation(cmd, scope.row)">
-              <el-button type="text" size="small" style="color: #409eff;">操作</el-button>
+            <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>
               <template #dropdown>
-                <el-dropdown-menu>
-                  <el-dropdown-item command="edit" v-hasPermi="['physical:videoContent:edit']">修改</el-dropdown-item>
-                  <el-dropdown-item command="delete" v-hasPermi="['physical:videoContent:remove']">删除</el-dropdown-item>
-                  <el-dropdown-item command="match" v-hasPermi="['physical:videoContent:edit']">异步获取</el-dropdown-item>
+                <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="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-menu>
               </template>
             </el-dropdown>
@@ -98,67 +118,133 @@
     </el-card>
 
     <!-- 添加或修改视频内容信息对话框 -->
-    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body>
       <el-form ref="videoContentFormRef" :model="form" :rules="rules" label-width="120px">
         <el-form-item label="视频标题" prop="title">
           <el-input v-model="form.title" placeholder="请输入视频标题" />
         </el-form-item>
         <el-form-item label="视频封面图" prop="videoCoverUrl">
-          <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 v-if="videoCoverUrlPreviewUrl || form.videoCoverUrl" style="margin-top: 10px">
-                <img :src="videoCoverUrlPreviewUrl || form.videoCoverUrl" style="max-width: 100px; max-height: 100px" />
-                <el-button type="danger" size="small" icon="Delete" @click="handleVideoCoverUrlRemove" style="margin-left: 10px;">删除</el-button>
-              </div>
-              <div v-else style="margin-top: 10px; color: #999">无图片</div>
-            </template>
-          </el-upload>
+          <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">
+                  <template v-if="coverUploading">
+                    <div style="margin-top: 10px; width: 200px">
+                      <div style="display: flex; justify-content: space-between; margin-bottom: 5px; font-size: 14px">
+                        <span>上传中...</span>
+                        <span>{{ coverUploadProgress }}%</span>
+                      </div>
+                      <el-progress :percentage="coverUploadProgress" :stroke-width="15" />
+                    </div>
+                  </template>
+                  <template v-else>
+                    <div v-if="videoCoverUrlPreviewUrl || form.videoCoverUrl" style="position: relative; display: inline-block">
+                      <img
+                        :src="videoCoverUrlPreviewUrl || form.videoCoverUrl"
+                        alt="预览图"
+                        style="max-width: 100px; max-height: 100px; margin-top: 10px; cursor: pointer"
+                        @click="handleVideoCoverUrlPreviewClick"
+                      />
+                      <el-button
+                        type="danger"
+                        size="small"
+                        icon="Delete"
+                        @click.stop="handleVideoCoverUrlRemove"
+                        style="position: absolute; top: 5px; right: -50px;"
+                      >删除</el-button>
+                    </div>
+                    <div v-else style="margin-top: 10px; color: #999">无图片</div>
+                  </template>
+                </div>
+              </template>
+            </el-upload>
+          </div>
         </el-form-item>
         <el-form-item label="视频文件" prop="ossVideoUrl">
-          <el-upload
-            class="upload-icon"
-            action="#"
-            :on-change="handleOssVideoSelect"
-            :on-remove="handleOssVideoUrlRemove"
-            :file-list="ossVideoUrlFileList"
-            :auto-upload="false"
-            :limit="1"
-            accept="video/*"
-          >
-            <template #trigger><el-button type="primary">点击上传视频</el-button></template>
-            <template #default>
-              <template v-if="uploading && uploadProgress < 100">
-                <el-progress :percentage="uploadProgress" :stroke-width="20" style="margin-top: 10px; width: 300px" />
-                <div style="font-size: 12px; color: #666; margin-top: 5px">视频上传中,请稍候...</div>
-              </template>
-              <template v-else-if="uploading">
-                <el-progress :percentage="100" :stroke-width="20" status="success" style="margin-top: 10px; width: 300px" />
-                <div style="font-size: 12px; color: #67c23a; margin-top: 5px">视频上传完成</div>
-                <el-button v-if="form.videoFileName" type="danger" size="small" icon="Delete" @click="handleOssVideoUrlRemove" style="margin-top: 10px">删除视频</el-button>
+          <div class="upload-container">
+            <el-upload
+              class="upload-icon"
+              action="#"
+              :on-change="handleOssVideoUrlChange"
+              :on-remove="handleOssVideoUrlRemove"
+              :file-list="ossVideoUrlFileList"
+              :auto-upload="false"
+              :limit="1"
+              accept="video/*"
+            >
+              <template #trigger>
+                <el-button type="primary">点击选择视频</el-button>
               </template>
-              <template v-else>
-                <div v-if="form.videoFileName" style="margin-top: 10px; color: #666">已选择: {{ form.videoFileName }}</div>
-                <div v-if="form.durationSeconds" style="font-size: 12px; color: #999">时长: {{ formatDuration(form.durationSeconds) }}</div>
-                <el-button v-if="form.videoFileName" type="danger" size="small" icon="Delete" @click="handleOssVideoUrlRemove" style="margin-top: 10px">删除视频</el-button>
+              <template #default>
+                <div class="preview-area">
+                  <template v-if="uploading && uploadProgress < 100">
+                    <div style="margin-top: 10px; width: 300px">
+                      <div style="display: flex; justify-content: space-between; margin-bottom: 5px; font-size: 14px">
+                        <span>上传中...</span>
+                        <span>{{ uploadProgress }}%</span>
+                      </div>
+                      <el-progress :percentage="uploadProgress" :stroke-width="20" />
+                    </div>
+                  </template>
+                  <template v-else-if="uploading && uploadProgress >= 100">
+                    <div style="margin-top: 10px; width: 300px">
+                      <div style="display: flex; justify-content: space-between; margin-bottom: 5px; font-size: 14px">
+                        <span>处理中...</span>
+                        <span>100%</span>
+                      </div>
+                      <el-progress :percentage="100" :stroke-width="20" status="success" />
+                    </div>
+                    <video v-if="ossVideoUrlPreviewUrl" :src="ossVideoUrlPreviewUrl" controls style="max-width: 300px; max-height: 200px; margin-top: 10px" />
+                  </template>
+                  <template v-else>
+                    <!-- ✅ 使用 videoTempUrl 作为视频源 -->
+                    <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>
+                    <el-button v-if="ossVideoUrlPreviewUrl || form.ossVideoUrl" type="danger" size="small" icon="Delete" @click="handleOssVideoUrlRemove" style="margin-top: 10px">删除视频</el-button>
+                  </template>
+                </div>
+                <!-- ✅ 数据状态显示 -->
+                <div v-if="dataStatus" style="margin-top: 10px;">
+                  <span :class="['status-badge', dataStatus === '正常' ? 'status-normal' : 'status-abnormal']">
+                    {{ dataStatus || '异常' }}
+                  </span>
+                  <el-button
+                    v-if="dataStatus === '异常'"
+                    type="primary"
+                    size="small"
+                    @click="handleMatchFromBackup"
+                    style="margin-left: 10px;"
+                  >异步获取</el-button>
+                </div>
               </template>
-            </template>
-          </el-upload>
+            </el-upload>
+          </div>
+        </el-form-item>
+        <el-form-item label="观看数">
+          <el-input v-model="form.videoSeeCount" placeholder="请输入观看数" />
         </el-form-item>
         <el-form-item label="所需视频点" prop="requiredPoints">
           <el-input v-model="form.requiredPoints" placeholder="请输入所需视频点" />
         </el-form-item>
         <el-form-item label="试看时长 (秒)" prop="previewDurationSeconds">
-          <el-input v-model="form.previewDurationSeconds" placeholder="请输入试看时长 (秒)" />
+          <div>
+            <el-input v-model="form.previewDurationSeconds" placeholder="请输入试看时长 (秒)" style="width: 100%" />
+            <div style="margin-top: 4px; font-size: 12px; color: #999">
+              温馨提示:输入 -1 表示免费观看
+            </div>
+          </div>
         </el-form-item>
         <el-form-item label="订阅时效(天)" prop="subscriptionValidHours">
           <el-input v-model="form.subscriptionValidHours" placeholder="请输入订阅时效(天)" />
@@ -174,23 +260,31 @@
             <el-option label="下架" value="down" />
           </el-select>
         </el-form-item>
+        <el-form-item label="视频时长(秒)" prop="durationSeconds">
+          <el-input v-model="form.durationSeconds" placeholder="视频时长(秒)- 由系统自动获取" readonly />
+          <div style="margin-top: 4px; font-size: 12px; color: #909399;">
+            视频时长由系统自动获取
+          </div>
+        </el-form-item>
       </el-form>
       <template #footer>
-        <el-button :loading="buttonLoading" :disabled="uploading || !isVideoReady" type="primary" @click="submitForm">确 定</el-button>
-        <el-button @click="cancel">取 消</el-button>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
       </template>
     </el-dialog>
   </div>
 </template>
 
 <script setup name="VideoContent" lang="ts">
-import { ref, reactive, onMounted, toRefs, computed } from 'vue';
-import { ElFormInstance, ElMessageBox } from 'element-plus';
+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 { ElMessageBox } from 'element-plus';
 import request from '@/utils/request';
-import { getCurrentInstance } from 'vue';
-import type { ComponentInternalInstance } from 'vue';
 
-import { listVideoContent, getVideoContent, delVideoContent, addVideoContent, updateVideoContent, matchFromBackup } from '@/api/system/physical/videoContent';
+import { listVideoContent, getVideoContent, delVideoContent, updateVideoContent } from '@/api/system/physical/videoContent';
 import type { VideoContentVO, VideoContentForm, VideoContentQuery } from '@/api/system/physical/videoContent/types';
 import { selectEnabledTabsByCategoryList } from '@/api/system/physical/serviceTab';
 import type { ServiceTabVO } from '@/api/system/physical/serviceTab/types';
@@ -198,7 +292,7 @@ import { uploadTournament } from '@/api/system/business/tournaments';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
-// 状态管理
+// ============ 状态管理 ============
 const videoContentList = ref<VideoContentVO[]>([]);
 const buttonLoading = ref(false);
 const loading = ref(true);
@@ -207,6 +301,12 @@ const ids = ref<Array<string | number>>([]);
 const single = ref(true);
 const multiple = ref(true);
 const total = ref(0);
+const isFixing = ref(false);
+
+// ✅ 轮询相关状态
+const pollTimer = ref<number | null>(null);
+const pollCount = ref(0);
+const dataStatus = ref(''); // 数据状态:正常/异常
 
 const queryFormRef = ref<ElFormInstance>();
 const videoContentFormRef = ref<ElFormInstance>();
@@ -214,29 +314,51 @@ const videoContentFormRef = ref<ElFormInstance>();
 const dialog = reactive<DialogOption>({ visible: false, title: '' });
 
 const initFormData: VideoContentForm = {
-  id: undefined, videoFileName: undefined, title: undefined, requiredPoints: undefined,
-  durationSeconds: undefined, previewDurationSeconds: undefined, subscriptionValidHours: undefined,
-  ossVideoUrl: undefined, videoUrl: undefined, videoTempUrl: undefined, tempUrl: undefined,
-  status: 'up', createdAt: undefined, updatedAt: undefined, ossId: undefined,
-  videoCoverUrl: undefined, videoSeeCount: undefined, categoryTagId: undefined
+  id: undefined,
+  videoFileName: undefined,
+  title: undefined,
+  requiredPoints: undefined,
+  durationSeconds: undefined,
+  previewDurationSeconds: undefined,
+  subscriptionValidHours: undefined,
+  ossVideoUrl: undefined,
+  status: 'up',
+  createdAt: undefined,
+  updatedAt: undefined,
+  ossId: undefined,
+  videoCoverUrl: undefined,
+  videoSeeCount: undefined,
+  categoryTagId: undefined,
+  serviceTagName: undefined
 };
 
 const data = reactive<PageData<VideoContentForm, VideoContentQuery>>({
   form: { ...initFormData },
   queryParams: {
-    pageNum: 1, pageSize: 10, title: undefined, requiredPoints: undefined,
-    durationSeconds: undefined, previewDurationSeconds: undefined, subscriptionValidHours: undefined,
-    status: undefined, createdAt: undefined, updatedAt: undefined, ossId: undefined,
-    orderBy: 'createdAt', orderDirection: 'desc', params: {}
+    pageNum: 1,
+    pageSize: 10,
+    title: undefined,
+    requiredPoints: undefined,
+    durationSeconds: undefined,
+    previewDurationSeconds: undefined,
+    subscriptionValidHours: undefined,
+    status: undefined,
+    createdAt: undefined,
+    updatedAt: undefined,
+    ossId: undefined,
+    orderBy: 'createdAt',
+    orderDirection: 'desc',
+    params: {}
   },
   rules: {
     title: [{ required: true, message: '视频标题不能为空', trigger: 'blur' }],
-    requiredPoints: [{ required: true, message: '所需视频点数不能为空', trigger: 'blur' }],
-    previewDurationSeconds: [{ required: true, message: '试看时长不能为空', trigger: 'blur' }],
-    subscriptionValidHours: [{ required: true, message: '订阅时效不能为空', trigger: 'blur' }],
+    requiredPoints: [{ required: true, message: '观看完整视频所需消耗的视频点数不能为空', trigger: 'blur' }],
+    previewDurationSeconds: [{ required: true, message: '免费试看时长,单位:秒不能为空', trigger: 'blur' }],
+    subscriptionValidHours: [{ required: true, message: '订阅后可观看的有效期,单位:小时不能为空', trigger: 'blur' }],
     videoCoverUrl: [{ required: true, message: '视频封面不能为空', trigger: 'blur' }],
-    status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
-    categoryTagId: [{ required: true, message: '所属标签不能为空', trigger: 'blur' }]
+    status: [{ required: true, message: '视频状态:up=已上架不能为空', trigger: 'change' }],
+    categoryTagId: [{ required: true, message: '不能为空', trigger: 'blur' }],
+    durationSeconds: []
   }
 });
 
@@ -245,93 +367,138 @@ const { queryParams, form, rules } = toRefs(data);
 // 上传状态
 const uploading = ref(false);
 const uploadProgress = ref(0);
-const videoUploaded = ref(false);
-
-// 防止重复提交
-const isSubmitting = ref(false);
+const coverUploading = ref(false);
+const coverUploadProgress = ref(0);
 
 // 文件列表
 const ossVideoUrlFileList = ref<any[]>([]);
+const ossVideoUrlPreviewUrl = ref('');
 const videoCoverUrlFileList = ref<any[]>([]);
 const videoCoverUrlPreviewUrl = ref('');
 
 // 页签选项
 const serviceTabOptions = ref<ServiceTabVO[]>([]);
 
-// 视频是否准备好
-const isVideoReady = computed(() => {
-  // 无论新增还是编辑模式,只要有视频文件名或视频已上传完成即可
-  // 如果上传进度已完成(100%),即使还在处理中也允许保存
-  if (uploading.value && uploadProgress.value < 100) return false;
-  // 如果已经有视频文件名或视频已上传完成,则可以保存
-  return !!form.value.videoFileName || videoUploaded.value;
-});
+// ============ 工具函数 ============
+const isDurationValid = (duration?: number): boolean => {
+  return duration !== undefined && duration !== null && duration >= 0;
+};
+
+const isVideoDataValid = (row: VideoContentVO): boolean => {
+  const hasValidOssId = row.ossId && row.ossId > 0;
+  const hasValidUrl = row.ossVideoUrl && String(row.ossVideoUrl).startsWith('http');
+  const hasValidDuration = isDurationValid(row.durationSeconds);
+  return hasValidOssId && hasValidUrl && hasValidDuration;
+};
+
+// ============ 轮询功能 ============
+// ✅ 启动轮询
+const startPolling = (videoId: number | string) => {
+  stopPolling(); // 先停止之前的轮询
+  pollCount.value = 0;
 
-// 格式化时长
-const formatDuration = (seconds: number): string => {
-  if (!seconds || seconds < 0) return '00:00';
-  const hrs = Math.floor(seconds / 3600);
-  const mins = Math.floor((seconds % 3600) / 60);
-  const secs = Math.floor(seconds % 60);
-  if (hrs > 0) {
-    return `${hrs.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
+  pollTimer.value = window.setInterval(async () => {
+    await pollVideoStatus(videoId);
+  }, 1000); // 每秒查询一次
+};
+
+// ✅ 停止轮询
+const stopPolling = () => {
+  if (pollTimer.value) {
+    clearInterval(pollTimer.value);
+    pollTimer.value = null;
   }
-  return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
 };
 
-// 检查视频数据是否有效
-const isVideoDataValid = (row: VideoContentVO): boolean => {
-  return row.ossId && row.ossId > 0 && row.ossVideoUrl && String(row.ossVideoUrl).startsWith('http') && row.durationSeconds && row.durationSeconds > 0;
+// ✅ 轮询查询状态 - 只更新时长和状态,不更新视频URL!
+const pollVideoStatus = async (videoId: number | string) => {
+  pollCount.value++;
+
+  try {
+    const res = await request({
+      url: `/physical/videoContent/status/${videoId}`,
+      method: 'GET'
+    });
+
+    if (res.data && res.data.data) {
+      const statusData = res.data.data;
+
+      // ✅ 更新时长
+      if (statusData.durationSeconds !== undefined && statusData.durationSeconds !== null) {
+        form.value.durationSeconds = statusData.durationSeconds;
+      }
+
+      // ✅ 更新数据状态
+      if (statusData.dataStatus !== undefined) {
+        dataStatus.value = statusData.dataStatus;
+      }
+
+      // ⚠️ 不要更新视频URL!只有异步获取时才更新
+
+      // 数据正常或超过30秒停止轮询
+      if (statusData.dataStatus === '正常' || pollCount.value >= 30) {
+        stopPolling();
+        await getList(); // 刷新列表
+      }
+    }
+  } catch (error) {
+    console.error('轮询查询状态失败:', error);
+    // 如果轮询失败超过30次也停止
+    if (pollCount.value >= 30) {
+      stopPolling();
+    }
+  }
 };
 
-// 数据查询
+// ============ 数据查询 ============
 const getList = async () => {
   loading.value = true;
-  try {
-    const res = await listVideoContent(queryParams.value);
-    videoContentList.value = res.rows;
-    total.value = res.total;
-  } catch (error: any) {
-    proxy?.$modal.msgError('数据加载失败:' + (error.message || '未知错误'));
-  } finally {
-    loading.value = false;
-  }
+  const res = await listVideoContent(queryParams.value);
+  videoContentList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.status = undefined;
+  handleQuery();
 };
 
-const handleQuery = () => { queryParams.value.pageNum = 1; getList(); };
-const resetQuery = () => { queryFormRef.value?.resetFields(); handleQuery(); };
 const handleSelectionChange = (selection: VideoContentVO[]) => {
   ids.value = selection.map(item => item.id);
   single.value = selection.length !== 1;
   multiple.value = !selection.length;
 };
 
-// 表单操作
+// ============ 表单操作 ============
 const reset = () => {
-  // 重置表单数据
+  stopPolling(); // ✅ 重置时停止轮询
+  dataStatus.value = ''; // 重置数据状态
   form.value = { ...initFormData };
   videoContentFormRef.value?.resetFields();
-
-  // 清空文件列表
   ossVideoUrlFileList.value = [];
+  ossVideoUrlPreviewUrl.value = '';
   videoCoverUrlFileList.value = [];
   videoCoverUrlPreviewUrl.value = '';
-
-  // 重置上传状态
   uploading.value = false;
   uploadProgress.value = 0;
-  videoUploaded.value = false;
-
-  // 确保清除任何可能的提交状态
-  isSubmitting.value = false;
+  coverUploading.value = false;
+  coverUploadProgress.value = 0;
 };
 
-const cancel = () => { reset(); dialog.visible = false; };
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
 
 const handleAdd = () => {
   reset();
-  // 确保在新增模式下 id 为 undefined
-  form.value.id = undefined;
   dialog.visible = true;
   dialog.title = '添加';
 };
@@ -341,141 +508,217 @@ const handleUpdate = async (row?: VideoContentVO) => {
   const _id = row?.id || ids.value[0];
   const res = await getVideoContent(_id);
   Object.assign(form.value, res.data);
+  // ✅ 核心原则1:修改页面打开时设置视频URL
+  ossVideoUrlPreviewUrl.value = String(res.data?.videoTempUrl || res.data?.ossVideoUrl || '');
+
   if (res.data.videoCoverUrl) {
     videoCoverUrlFileList.value = [{ name: '已上传封面图', url: res.data.videoCoverUrl, status: 'success' }];
     videoCoverUrlPreviewUrl.value = res.data.videoCoverUrl;
   }
-  // 编辑模式下,如果有视频文件则标记为已上传
-  if (res.data.videoFileName) {
-    videoUploaded.value = true;
-  }
+
   dialog.visible = true;
   dialog.title = '修改';
 };
 
-const submitForm = async () => {
-  if (isSubmitting.value) return;
+const submitForm = () => {
+  if (!form.value.ossVideoUrl && ossVideoUrlPreviewUrl.value) {
+    form.value.ossVideoUrl = ossVideoUrlPreviewUrl.value;
+  }
+
+  if (!form.value.ossVideoUrl) {
+    proxy?.$modal.msgError('请先上传视频文件');
+    return;
+  }
 
   videoContentFormRef.value?.validate(async (valid) => {
     if (valid) {
-      isSubmitting.value = true;
       buttonLoading.value = true;
-
       try {
-        // 如果视频上传进度已完成(100%),但仍在处理中,等待处理完成
-        if (uploading.value && uploadProgress.value === 100) {
-          proxy?.$modal.msgInfo('视频上传处理中,请稍候...');
-          // 等待上传完成,最多等待一定时间
-          let attempts = 0;
-          const maxAttempts = 30; // 最多等待15秒(每0.5秒检查一次)
-          while (uploading.value && attempts < maxAttempts) {
-            await new Promise(resolve => setTimeout(resolve, 500));
-            attempts++;
-          }
-
-          if (uploading.value) {
-            proxy?.$modal.msgError('视频上传处理超时,请稍后重试');
-            return;
-          }
-        }
-
         if (form.value.id) {
-          // 修改操作
           await updateVideoContent(form.value);
-          proxy?.$modal.msgSuccess(`视频「${form.value.title}」修改成功!`);
-        } else {
-          // 新增操作
-          await addVideoContent(form.value);
-          proxy?.$modal.msgSuccess(`视频「${form.value.title}」新增成功!`);
         }
-
-        reset();
+        proxy?.$modal.msgSuccess('操作成功');
         dialog.visible = false;
         await getList();
       } finally {
         buttonLoading.value = false;
-        isSubmitting.value = false;
       }
+    } else {
+      proxy?.$modal.msgWarning('请检查并填写所有必填字段');
+      setTimeout(() => {
+        const errorField = document.querySelector('.el-form-item__error');
+        if (errorField) {
+          errorField.scrollIntoView({ behavior: 'smooth', block: 'center' });
+        }
+      }, 100);
     }
   });
 };
 
 const handleDelete = async (row?: VideoContentVO) => {
   const _ids = row?.id || ids.value;
+
   if (!_ids || (Array.isArray(_ids) && _ids.length === 0)) {
     proxy?.$modal.msgError('请先选择要删除的数据');
     return;
   }
-  await proxy?.$modal.confirm(`确认删除编号为"${_ids}"的数据?`);
+
+  await proxy?.$modal.confirm(`是否确认删除视频内容信息编号为"${_ids}"的数据项?`).finally(() => (loading.value = false));
+
   try {
     await delVideoContent(_ids);
     proxy?.$modal.msgSuccess('删除成功');
     await getList();
   } catch (error: any) {
+    console.error('删除失败:', error);
     proxy?.$modal.msgError('删除失败:' + (error.message || '未知错误'));
+    await getList();
   }
 };
 
-const handleOperation = async (command: string, row: VideoContentVO) => {
-  switch (command) {
-    case 'edit': await handleUpdate(row); break;
-    case 'delete': await handleDelete(row); break;
-    case 'match': await handleMatchFromBackup(row); break;
+// ✅ 编辑页面内的异步获取按钮 - 核心原则2:异步获取成功时设置视频URL
+const handleMatchFromBackup = async () => {
+  if (!form.value.id) {
+    proxy?.$modal.msgError('请先选择视频');
+    return;
+  }
+
+  try {
+    await proxy?.$modal.confirm(`确认对视频「${form.value.title}」执行异步获取?`);
+
+    const res = await request({
+      url: `/physical/videoContent/matchFromBackup/${form.value.id}`,
+      method: 'POST'
+    });
+
+    if (res.data && res.data.success) {
+      // ✅ 核心原则2:只有异步获取成功时才能更新视频URL
+      if (res.data.tempUrl) {
+        ossVideoUrlPreviewUrl.value = res.data.tempUrl;
+      }
+      if (res.data.duration) {
+        form.value.durationSeconds = res.data.duration;
+      }
+      dataStatus.value = '正常';
+
+      proxy?.$modal.msgSuccess('异步获取成功!已更新视频URL和时长');
+      await getList();
+    } else {
+      proxy?.$modal.msgWarning(res.data?.message || '未找到可异步获取的数据');
+    }
+  } catch (error: any) {
+    if (error !== 'cancel') {
+      console.error('异步获取失败:', error);
+      proxy?.$modal.msgError('异步获取失败:' + (error.message || '未知错误'));
+    }
   }
 };
 
-const handleMatchFromBackup = async (row: VideoContentVO) => {
-  await proxy?.$modal.confirm(`确认对视频「${row.title}」执行异步获取?`);
-  const res = await matchFromBackup(row.id);
-  proxy?.$modal.msgSuccess(res.data?.message || '异步获取成功!');
-  await getList();
+// 表格操作栏的异步获取
+const matchFromBackup = async (row: VideoContentVO) => {
+  try {
+    await proxy?.$modal.confirm(`确认对视频「${row.title}」执行异步获取?`);
+
+    proxy?.$modal.msgSuccess(`正在执行异步获取...`);
+
+    const res = await request({
+      url: `/physical/videoContent/matchFromBackup/${row.id}`,
+      method: 'POST'
+    });
+
+    if (res.data && res.data.success) {
+      proxy?.$modal.msgSuccess('异步获取成功!已更新视频URL和时长');
+      await getList();
+    } else {
+      proxy?.$modal.msgWarning(res.data?.message || '未找到可异步获取的数据');
+    }
+  } catch (error: any) {
+    if (error !== 'cancel') {
+      console.error('异步获取失败:', error);
+      proxy?.$modal.msgError('异步获取失败:' + (error.message || '未知错误'));
+    }
+  }
 };
 
 const handleFixAll = async () => {
-  await proxy?.$modal.confirm('确认修复所有视频数据?');
-  const res = await request({ url: '/physical/videoContent/fixAllVideoData', method: 'POST' });
-  proxy?.$modal.msgSuccess(res.data?.message || '修复完成!');
-  await getList();
-};
+  try {
+    await proxy?.$modal.confirm(
+      '确定要批量修复所有缺失URL、时长或oss_id的视频数据吗?',
+      '批量修复',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    );
 
-// 文件选择 - 选择后立即自动上传(仅新增模式)
-const handleOssVideoSelect = async (file: any) => {
-  const fileObj = file.raw || file;
-  if (!fileObj) return;
+    isFixing.value = true;
 
-  form.value.videoFileName = fileObj.name;
-  ossVideoUrlFileList.value = [file];
+    const res = await request({
+      url: '/physical/videoContent/fixAllVideoData',
+      method: 'POST'
+    });
 
-  // 只有新增模式下才自动上传
-  // 编辑模式下用户选择新视频时才上传,但保留原ID
-  if (!form.value.id) {
-    await uploadVideoAsync(fileObj);
-  } else {
-    // 编辑模式:用户选择了新视频,需要上传但要保留原ID
-    await uploadVideoAsync(fileObj, true);
+    if (res.data && res.data.success) {
+      const data = res.data;
+      proxy?.$modal.msgSuccess({
+        message: data.message || `批量修复任务已启动!共处理 ${data.count || 0} 条数据`,
+        duration: 5000
+      });
+
+      setTimeout(() => {
+        getList();
+      }, 1500);
+    } else {
+      proxy?.$modal.msgWarning(res.data?.message || '没有需要修复的数据');
+    }
+  } catch (error: any) {
+    if (error !== 'cancel') {
+      console.error('批量修复失败:', error);
+      proxy?.$modal.msgError('批量修复失败:' + (error.message || '未知错误'));
+    }
+  } finally {
+    isFixing.value = false;
   }
 };
 
-// 视频异步上传
-// isEditMode: 是否为编辑模式(编辑模式下不覆盖原ID)
-const uploadVideoAsync = async (fileObj: File, isEditMode = false) => {
+// ============ 文件上传 ============
+const handleOssVideoUrlChange = async (file: any) => {
+  ossVideoUrlPreviewUrl.value = '';
+  dataStatus.value = ''; // 重置数据状态
+  const fileObj = file.raw || file;
+
+  if (!fileObj) return;
+
   const fileSizeMB = fileObj.size / (1024 * 1024);
-  if (fileSizeMB > 10240) {
-    proxy?.$modal.msgError('视频文件大小不能超过10GB');
-    ossVideoUrlFileList.value = [];
-    form.value.videoFileName = undefined;
+  if (fileSizeMB > 2048) {
+    proxy?.$modal.msgError('视频文件大小不能超过2GB');
     return;
   }
 
+  form.value.ossId = undefined;
+  form.value.durationSeconds = undefined;
+
   uploading.value = true;
   uploadProgress.value = 0;
-  videoUploaded.value = false;
+  const localPreviewUrl = URL.createObjectURL(fileObj);
 
   try {
     const formData = new FormData();
     formData.append('file', fileObj);
 
+    if (form.value.title) formData.append('title', form.value.title);
+    if (form.value.requiredPoints) formData.append('requiredPoints', String(form.value.requiredPoints));
+    if (form.value.previewDurationSeconds) formData.append('previewDurationSeconds', String(form.value.previewDurationSeconds));
+    if (form.value.subscriptionValidHours) formData.append('subscriptionValidHours', String(form.value.subscriptionValidHours));
+    if (form.value.videoCoverUrl) formData.append('videoCoverUrl', form.value.videoCoverUrl);
+    if (form.value.status) formData.append('status', form.value.status);
+    if (form.value.categoryTagId) formData.append('categoryTagId', String(form.value.categoryTagId));
+    if (form.value.videoSeeCount) formData.append('videoSeeCount', String(form.value.videoSeeCount));
+    if (form.value.id) formData.append('videoId', String(form.value.id));
+
     const timeout = Math.max(300000, Math.floor(fileSizeMB * 3000));
+
     const uploadRes = await request({
       url: '/physical/videoContent/uploadVideo',
       method: 'POST',
@@ -485,72 +728,219 @@ const uploadVideoAsync = async (fileObj: File, isEditMode = false) => {
       onUploadProgress: (progressEvent: any) => {
         if (progressEvent.total > 0) {
           uploadProgress.value = Math.round((progressEvent.loaded / progressEvent.total) * 100);
+          if (uploadProgress.value >= 100) {
+            ossVideoUrlPreviewUrl.value = localPreviewUrl;
+          }
         }
       }
     });
 
     uploading.value = false;
-    const video = uploadRes.data || uploadRes;
 
-    // 关键修复:编辑模式下不覆盖原ID!
-    form.value.ossVideoUrl = video.ossVideoUrl || '';
-    if (video.ossId) form.value.ossId = video.ossId;
-    if (video.videoFileName || video.fileName) form.value.videoFileName = video.videoFileName || video.fileName;
-    if (video.durationSeconds) form.value.durationSeconds = video.durationSeconds;
+    // ⚠️ 根据核心原则:上传成功后不设置视频URL!
+    // 视频URL只在两个地方设置:1. 修改页面打开时 2. 异步获取成功时
+    const videoUrl = uploadRes.data?.data?.url || uploadRes.data?.data?.ossVideoUrl;
+    const ossId = uploadRes.data?.data?.ossId;
+    const duration = uploadRes.data?.data?.duration || uploadRes.data?.data?.videoDuration;
+    const videoFileName = uploadRes.data?.data?.videoFileName;
+    const serviceTagName = uploadRes.data?.data?.serviceTagName;
+
+    // ❌ 删除:不设置视频URL
+    // form.value.ossVideoUrl = videoUrl || localPreviewUrl;
+    // ossVideoUrlPreviewUrl.value = videoTempUrl;
+
+    if (ossId) form.value.ossId = ossId;
+    if (videoFileName) {
+      form.value.videoFileName = videoFileName;
+    } else if (fileObj?.name) {
+      form.value.videoFileName = fileObj.name;
+    }
+    if (serviceTagName) {
+      form.value.serviceTagName = serviceTagName;
+    }
+
+    if (uploadRes.data?.data?.id) {
+      form.value.id = uploadRes.data.data.id;
 
-    // 只有新增模式下才设置ID
-    if (!isEditMode && video.id) {
-      form.value.id = video.id;
+      // ✅ 上传成功后启动轮询(轮询只更新时长和状态,不更新视频URL)
+      startPolling(uploadRes.data.data.id);
     }
 
-    // 无论新增还是编辑模式,上传完成后都标记为已上传
-    uploading.value = false;
-    videoUploaded.value = true;
+    let finalDuration = duration && !isNaN(duration) && Number(duration) > 0 ? Math.floor(Number(duration)) : 0;
+
+    if (!isDurationValid(finalDuration) && uploadRes.data?.data?.id) {
+      try {
+        const durationRes = await request({
+          url: `/physical/videoContent/getVideoDuration/${uploadRes.data.data.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;
+    }
+
+    proxy?.$modal.msgSuccess('视频上传成功!时长已自动获取');
+    await getList();
   } catch (error: any) {
+    console.error('视频上传失败:', error);
     uploading.value = false;
     uploadProgress.value = 0;
-    videoUploaded.value = false;
+    ossVideoUrlPreviewUrl.value = localPreviewUrl;
+    URL.revokeObjectURL(localPreviewUrl);
+
+    proxy?.$modal.msgError(error.code === 'ECONNABORTED'
+      ? '视频上传超时,请尝试上传更小的文件或联系管理员'
+      : '视频上传失败:' + (error.message || '未知错误'));
+
+    form.value.ossVideoUrl = '';
+    form.value.ossId = undefined;
+    form.value.durationSeconds = undefined;
+    form.value.id = undefined;
+  }
+};
+
+const handleOssVideoUrlRemove = async () => {
+  if (!form.value.ossVideoUrl && !ossVideoUrlPreviewUrl.value) {
+    ossVideoUrlFileList.value = [];
+    ossVideoUrlPreviewUrl.value = '';
+    form.value.ossVideoUrl = '';
+    form.value.ossId = undefined;
+    form.value.durationSeconds = undefined;
+    return;
+  }
+
+  try {
+    await proxy?.$modal.confirm('确定要删除当前视频吗?删除后需要重新上传。');
+
     ossVideoUrlFileList.value = [];
+    ossVideoUrlPreviewUrl.value = '';
+    form.value.ossVideoUrl = '';
+    form.value.ossId = undefined;
+    form.value.durationSeconds = undefined;
     form.value.videoFileName = undefined;
-    proxy?.$modal.msgError('视频上传失败:' + (error.message || '未知错误'));
+    dataStatus.value = ''; // 重置数据状态
+
+    if (form.value.id) {
+      try {
+        await request({
+          url: `/physical/videoContent/clearVideo/${form.value.id}`,
+          method: 'POST'
+        });
+        proxy?.$modal.msgSuccess('视频删除成功');
+        await getList();
+      } catch (error) {
+        console.error('删除视频失败:', error);
+      }
+    } else {
+      proxy?.$modal.msgSuccess('视频已从表单中移除');
+    }
+  } catch (error) {
+    // 用户取消删除
   }
 };
 
-const handleOssVideoUrlRemove = () => {
-  ossVideoUrlFileList.value = [];
-  form.value.videoFileName = undefined;
-  form.value.ossVideoUrl = undefined;
-  form.value.durationSeconds = undefined;
-  videoUploaded.value = false;
-};
+const handleVideoCoverUrlChange = async (file: any) => {
+  videoCoverUrlFileList.value = [];
 
-const handleVideoCoverUrlChange = (file: any) => {
-  videoCoverUrlFileList.value = [file];
-  const reader = new FileReader();
-  reader.onload = (e) => {
-    videoCoverUrlPreviewUrl.value = e.target?.result as string;
-  };
-  reader.readAsDataURL(file.raw || file);
+  if (file.raw) {
+    videoCoverUrlPreviewUrl.value = URL.createObjectURL(file.raw);
+  }
+
+  videoCoverUrlFileList.value.push(file);
+  coverUploading.value = true;
+
+  try {
+    const res = await uploadTournament(file.raw);
+    if (res.code === 200) {
+      videoCoverUrlFileList.value[0] = { ...file, status: 'success', response: res.data.url };
+      form.value.videoCoverUrl = res.data.url;
+      videoCoverUrlPreviewUrl.value = res.data.url;
+      proxy?.$modal.msgSuccess('封面图上传成功');
+    } else {
+      throw new Error(res.msg);
+    }
+  } catch (error) {
+    videoCoverUrlFileList.value[0] = { ...file, status: 'fail', error: '上传失败' };
+    proxy?.$modal.msgError('封面图上传失败,请重试');
+  } finally {
+    coverUploading.value = false;
+  }
 };
 
 const handleVideoCoverUrlRemove = () => {
   videoCoverUrlFileList.value = [];
   videoCoverUrlPreviewUrl.value = '';
-  form.value.videoCoverUrl = undefined;
+  form.value.videoCoverUrl = '';
 };
 
-// 获取页签选项
-const getServiceTabs = async () => {
-  try {
-    const res = await selectEnabledTabsByCategoryList('video');
-    serviceTabOptions.value = res.data;
-  } catch (error: any) {
-    proxy?.$modal.msgError('获取标签列表失败:' + (error.message || '未知错误'));
+const handleVideoCoverUrlPreviewClick = () => {
+  const imageUrl = videoCoverUrlPreviewUrl.value || form.value.videoCoverUrl;
+  if (imageUrl) {
+    ElMessageBox.alert(`<img src="${imageUrl}" style="max-width: 100%;" />`, '视频封面图预览', {
+      dangerouslyUseHTMLString: true,
+      confirmButtonText: '关闭'
+    });
   }
 };
 
+// ✅ 视频错误处理
+const handleVideoError = () => {
+  console.error('视频加载失败');
+};
+
+// ============ 初始化和清理 ============
 onMounted(() => {
   getList();
-  getServiceTabs();
+  loadServiceTabOptions();
 });
+
+onUnmounted(() => {
+  stopPolling(); // ✅ 组件卸载时停止轮询
+});
+
+const loadServiceTabOptions = async () => {
+  try {
+    const res = await selectEnabledTabsByCategoryList('video');
+    serviceTabOptions.value = Array.isArray(res.data) ? res.data : (Array.isArray(res) ? res : []);
+  } catch (error) {
+    console.error('加载页签列表失败:', error);
+    proxy?.$modal.msgError('加载页签列表失败');
+  }
+};
 </script>
+
+<style scoped>
+.file-name-ellipsis {
+  display: block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  max-width: 100%;
+}
+
+/* ✅ 数据状态样式 */
+.status-badge {
+  display: inline-block;
+  padding: 4px 12px;
+  border-radius: 4px;
+  font-size: 12px;
+  font-weight: 500;
+}
+
+.status-normal {
+  background-color: #f0f9eb;
+  color: #67c23a;
+}
+
+.status-abnormal {
+  background-color: #fef0f0;
+  color: #f56c6c;
+}
+</style>