Pārlūkot izejas kodu

feat:增加走秀视频和视频换脸管理

chenjiaxin 2 dienas atpakaļ
vecāks
revīzija
ee3e975028

+ 106 - 0
src/components/ExcelImport/index.vue

@@ -0,0 +1,106 @@
+<template>
+  <div class="excel-import">
+    <el-upload
+      class="excel-uploader"
+      :action="uploadUrl"
+      :headers="headers"
+      :data="uploadData"
+      :on-progress="handleUploadProgress"
+      :on-success="handleUploadSuccess"
+      :on-error="handleUploadError"
+      :before-upload="beforeUpload"
+      :show-file-list="false"
+      accept=".xlsx,.xls"
+    >
+      <el-button type="primary">
+        <i class="el-icon-upload2" /><slot name="upload-text"> 上传Excel文件</slot>
+      </el-button>
+      <div class="el-upload__tip" slot="tip">只能上传 xlsx/xls 文件,且不超过10MB</div>
+    </el-upload>
+    
+    <el-progress 
+      v-if="uploadProgress > 0 && uploadProgress < 100"
+      :percentage="uploadProgress"
+      :status="uploadStatus"
+      style="margin-top: 20px"
+    />
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ExcelImport',
+  props: {
+    uploadUrl: {
+      type: String,
+      required: true
+    },
+    uploadData: {
+      type: Object,
+      default: () => ({})
+    },
+    headers: {
+      type: Object,
+      default: () => ({})
+    },
+    maxSize: {
+      type: Number,
+      default: 10
+    }
+  },
+  data() {
+    return {
+      uploadProgress: 0,
+      uploadStatus: null
+    }
+  },
+  methods: {
+    beforeUpload(file) {
+      const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls')
+      const isLt10M = file.size / 1024 / 1024 < this.maxSize
+
+      if (!isExcel) {
+        this.$message.error('只能上传 Excel 文件!')
+        return false
+      }
+      if (!isLt10M) {
+        this.$message.error(`文件大小不能超过 ${this.maxSize}MB!`)
+        return false
+      }
+      
+      this.uploadProgress = 0
+      this.uploadStatus = null
+      return true
+    },
+    
+    handleUploadProgress(event) {
+      this.uploadProgress = Math.floor((event.loaded / event.total) * 100)
+    },
+    
+    handleUploadSuccess(response) {
+      this.uploadStatus = 'success'
+      this.$emit('on-success', response)
+      setTimeout(() => {
+        this.uploadProgress = 0
+      }, 2000)
+    },
+    
+    handleUploadError() {
+      this.uploadStatus = 'exception'
+      this.$message.error('文件上传失败')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.excel-import {
+ text-align: right;
+}
+
+.el-upload__tip {
+  margin-top: 10px;
+  color: #909399;
+  font-size: 12px;
+}
+</style>

+ 11 - 6
src/permission.js

@@ -35,16 +35,21 @@ router.beforeEach(async (to, from, next) => {
             { routerPath: "/auth" },
             { routerPath: "/auth/user" },
             { routerPath: "/auth/role" },
-            { routerPath: "/auth/orgGroup" },
+            { routerPath: "/auth/orgGroup" }, 
             { routerPath: "/auth/position" },
             { routerPath: "/auth/changePassword" },
             { routerPath: "/auth/org" },
             { routerPath: "/auth/res" },
-            { routerPath: "/ai" },
-            { routerPath: "/ai/cutVideo" },
-            { routerPath: "/ai/faceSwapVideo" },
-            { routerPath: "/video" },
-            { routerPath: "/video/oralVideo" }]
+            { routerPath: "/product" },
+            { routerPath: "/catwalkVideo" },
+            { routerPath: "/catwalkVideo/catwalkVideoIndex" },
+            { routerPath: "/catwalkVideo/catwalkVideoTool" },
+            { routerPath: "/faceSwapVideo" },
+            { routerPath: "/faceSwapVideo/faceSwapVideoIndex" },
+            { routerPath: "/faceSwapVideo/faceSwapVideoTool" },
+            { routerPath: "/oralVideo" },
+            { routerPath: "/oralVideo/oralVideoIndex" }]
+         
           const accessRoutes = await store.dispatch('permission/generateRoutes', data);
           router.addRoutes(accessRoutes);
           next({ ...to, replace: true })

+ 8 - 7
src/router/index.js

@@ -28,19 +28,20 @@ export const constantRoutes = [
     children: [
       {
         path: "index",
-        component: () => import("@/views/video/oral-video.vue")
+        component: () => import("@/views/oral-video/index.vue")
       }
     ]
   },
 ];
 import userAuthRouter from "./modules/userAuth";
-import videoRouter from "./modules/video";
-import aiToolsRouter from "./modules/aiTools";
-
+import oralVideoRouter from "./modules/oralVideo";
+import catwalkVideoRouter from "./modules/catwalkVideo";
+import faceSwapVideoRouter from "./modules/faceSwapVideo";
 export const asyncRoutes = [
-  videoRouter,
-  aiToolsRouter,
-  // userAuthRouter,
+  oralVideoRouter,
+  catwalkVideoRouter,
+  faceSwapVideoRouter,
+  userAuthRouter,
 ];
 
 

+ 0 - 26
src/router/modules/aiTools.js

@@ -1,26 +0,0 @@
-/** When your routing table is too long, you can split it into small modules **/
-
-import Layout from "@/layout";
-
-const aiToolsRouter = {
-  path: "/ai",
-  component: Layout,
-  redirect: "/ai/cutVideo",
-  name: "AI工具",
-  meta: { title: "AI工具", icon: "el-icon-s-cooperation" },
-  children: [
-    {
-      path: "cutVideo",
-      component: () => import("@/views/ai-tools/cut-video"),
-      name: "走秀视频剪辑",
-      meta: { title: "走秀视频剪辑" }
-    },
-    // {
-    //     path: "faceSwapVideo",
-    //     component: () => import("@/views/ai-tools/video-face-swap"),
-    //     name: "视频换脸",
-    //     meta: { title: "视频换脸" }
-    //   }
-  ]
-};
-export default aiToolsRouter;

+ 26 - 0
src/router/modules/catwalkVideo.js

@@ -0,0 +1,26 @@
+/** When your routing table is too long, you can split it into small modules **/
+
+import Layout from "@/layout";
+
+const catwalkVideoRouter = {
+  path: "/catwalkVideo",
+  component: Layout,
+  redirect: "/catwalkVideo/catwalkVideoIndex",
+  name: "走秀视频管理",
+  meta: { title: "走秀视频管理", icon: "el-icon-s-cooperation" },
+  children: [
+    {
+      path: "catwalkVideoIndex",
+      component: () => import("@/views/catwalk-video/index"),
+      name: "走秀视频列表",
+      meta: { title: "走秀视频列表" }
+    },
+    {
+      path: "catwalkVideoTool",
+      component: () => import("@/views/catwalk-video/catwalk-video"),
+      name: "走秀视频AI剪辑",
+      meta: { title: "走秀视频AI剪辑" }
+    },
+  ]
+};
+export default catwalkVideoRouter;

+ 26 - 0
src/router/modules/faceSwapVideo.js

@@ -0,0 +1,26 @@
+/** When your routing table is too long, you can split it into small modules **/
+
+import Layout from "@/layout";
+
+const faceSwapVideoRouter = {
+  path: "/faceSwapVideo",
+  component: Layout,
+  redirect: "/face-swap-video/faceSwapVideoIndex",
+  name: "视频换脸管理",
+  meta: { title: "视频换脸管理", icon: "el-icon-camera-solid" },
+  children: [
+    {
+      path: "faceSwapVideoIndex",
+      component: () => import("@/views/face-swap-video/index"),
+      name: "视频换脸列表",
+      meta: { title: "视频换脸列表" }
+    },
+    {
+      path: "faceSwapVideoTool",
+      component: () => import("@/views/face-swap-video/face-swap-video"),
+      name: "视频换脸AI工具",
+      meta: { title: "视频换脸AI工具" }
+    },
+  ]
+};
+export default faceSwapVideoRouter;

+ 26 - 0
src/router/modules/oralVideo.js

@@ -0,0 +1,26 @@
+/** When your routing table is too long, you can split it into small modules **/
+
+import Layout from "@/layout";
+
+const oralVideoRouter = {
+  path: "/oralVideo",
+  component: Layout,
+  redirect: "/oralVideo/oralVideoIndex",
+  name: "内容管理",
+  meta: { title: "内容管理", icon: "el-icon-video-camera-solid" },
+  children: [
+    {
+      path: "oralVideoIndex",
+      component: () => import("@/views/oral-video/index.vue"),
+      name: "口播视频",
+      meta: { title: "口播视频" }
+    },
+    {
+      path: "product",
+      component: () => import("@/views/product/product.vue"),
+      name: "商品列表",
+      meta: { title: "商品列表" }
+    }
+  ]
+};
+export default oralVideoRouter;

+ 0 - 20
src/router/modules/video.js

@@ -1,20 +0,0 @@
-/** When your routing table is too long, you can split it into small modules **/
-
-import Layout from "@/layout";
-
-const videoRouter = {
-  path: "/video",
-  component: Layout,
-  redirect: "/video/oralVideo",
-  name: "内容管理",
-  meta: { title: "内容管理", icon: "el-icon-video-camera-solid" },
-  children: [
-    {
-      path: "oralVideo",
-      component: () => import("@/views/video/oral-video.vue"),
-      name: "口播视频",
-      meta: { title: "口播视频" }
-    }
-  ]
-};
-export default videoRouter;

+ 0 - 59
src/views/ai-tools/cut-video.vue

@@ -1,59 +0,0 @@
-<template>
-  <div class="cut-video-container">
-    <iframe
-      ref="iframeRef"
-      :src="iframeUrl"
-      frameborder="0"
-      width="100%"
-      height="100%"
-      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
-      allowfullscreen
-    ></iframe>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'CutVideo',
-  data() {
-    return {
-      iframeUrl: 'https://ai-app.gloria.com.cn/walk_show_cut/'
-    }
-  },
-  mounted() {
-    // 添加消息事件监听器,用于处理iframe的跨域通信
-    window.addEventListener('message', this.handleMessage)
-  },
-  beforeDestroy() {
-    // 组件销毁前移除事件监听器
-    window.removeEventListener('message', this.handleMessage)
-  },
-  methods: {
-    handleMessage(event) {
-      // 验证消息来源
-      if (event.origin !== 'https://ai-app.gloria.com.cn/walk_show_cut/') {
-        return
-      }
-      // 处理来自iframe的消息
-      console.log('Received message from iframe:', event.data)
-      // 根据需要处理接收到的消息
-      // this.processMessage(event.data)
-    },
-    // 向iframe发送消息的方法
-    sendMessageToIframe(message) {
-      const iframe = this.$refs.iframeRef
-      if (iframe) {
-        iframe.contentWindow.postMessage(message, 'https://ai-app.gloria.com.cn/walk_show_cut/')
-      }
-    }
-  }
-}
-</script>
-
-<style scoped>
-.cut-video-container {
-  width: 100%;
-  height: 100vh;
-  overflow: hidden;
-}
-</style>

+ 0 - 59
src/views/ai-tools/video-face-swap.vue

@@ -1,59 +0,0 @@
-<template>
-  <div class="cut-video-container">
-    <iframe
-      ref="iframeRef"
-      :src="iframeUrl"
-      frameborder="0"
-      width="100%"
-      height="100%"
-      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
-      allowfullscreen
-    ></iframe>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'CutVideo',
-  data() {
-    return {
-      iframeUrl: 'http://118.145.148.169:7777/'
-    }
-  },
-  mounted() {
-    // 添加消息事件监听器,用于处理iframe的跨域通信
-    window.addEventListener('message', this.handleMessage)
-  },
-  beforeDestroy() {
-    // 组件销毁前移除事件监听器
-    window.removeEventListener('message', this.handleMessage)
-  },
-  methods: {
-    handleMessage(event) {
-      // 验证消息来源
-      if (event.origin !== 'http://118.145.148.169:7777') {
-        return
-      }
-      // 处理来自iframe的消息
-      console.log('Received message from iframe:', event.data)
-      // 根据需要处理接收到的消息
-      // this.processMessage(event.data)
-    },
-    // 向iframe发送消息的方法
-    sendMessageToIframe(message) {
-      const iframe = this.$refs.iframeRef
-      if (iframe) {
-        iframe.contentWindow.postMessage(message, 'http://118.145.148.169:7777')
-      }
-    }
-  }
-}
-</script>
-
-<style scoped>
-.cut-video-container {
-  width: 100%;
-  height: 100vh;
-  overflow: hidden;
-}
-</style>

+ 1 - 1
src/views/auth/user/index.vue

@@ -377,7 +377,7 @@ export default {
         cancelButtonText: "取消",
         type: "warning"
       }).then(() => {
-        resetPwd([{ userId: row.id }]).then(() => {
+        resetPwd(row).then(() => {
           this.$notify({
             title: "成功",
             message: "重置成功",

+ 32 - 0
src/views/catwalk-video/catwalk-video.vue

@@ -0,0 +1,32 @@
+<template>
+  <div class="cut-video-container">
+    <iframe
+      ref="iframeRef"
+      :src="iframeUrl"
+      frameborder="0"
+      width="100%"
+      height="100%"
+      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
+      allowfullscreen
+    ></iframe>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'catwalkVideoIndex',
+  data() {
+    return {
+      iframeUrl: 'https://ai-app.gloria.com.cn/walk_show_cut/',
+    }
+  }
+};
+</script>
+
+<style scoped>
+.cut-video-container {
+  width: 100%;
+  height: 100vh;
+  overflow: hidden;
+}
+</style>

+ 377 - 0
src/views/catwalk-video/index.vue

@@ -0,0 +1,377 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <div class="filter-container-lt">
+        <el-input placeholder="视频标题" class="filter-item" style="width: 200px;" clearable
+          v-model="listQuery.videoTitle" />
+        <!-- <el-input placeholder="SKU" class="filter-item" style="width: 200px;" clearable v-model="listQuery.sku" /> -->
+        <el-button v-waves class="filter-btn" type="primary" icon="el-icon-search" @click="handleFilter">搜索</el-button>
+      </div>
+      <!-- <div class="filter-container-rt">
+        <excel-import :upload-url="'/api/upload-excel'" :headers="{ token: 'your-token' }"
+          :upload-data="{ type: 'import' }" @on-success="handleImportSuccess">
+          <template slot="upload-text">
+            导入话题
+          </template>
+        </excel-import>
+      </div> -->
+    </div>
+
+    <div class="table-container">
+      <el-table style="width: 100%" v-loading="listLoading" :key="tableKey" :data="list" row-key="id" stripe border fit
+        highlight-current-row>
+        <el-table-column label="序号" type="index" width="60" align="center" />
+        <el-table-column label="视频ID" min-width="100" align="center" prop="id" />
+
+        <el-table-column label="视频标题" min-width="150" align="center" prop="videoTitle" />
+        <el-table-column label="视频封面" min-width="110" align="center" prop="videoCoverImageUrl">
+          <template slot-scope="scope">
+            <div v-if="scope.row.videoCoverImageUrl" class="video" @click="handlePlay(scope.row)">
+              <img :src="scope.row.videoCoverImageUrl" class="video-img" />
+              <i class="el-icon-video-play" />
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="视频描述" min-width="150" align="center" prop="videoDesc">
+          <template slot-scope="scope">
+            <el-popover placement="top-start" width="200" trigger="hover" :content="scope.row.videoDesc">
+              <span class="desc"
+                slot="reference">{{ scope.row.videoDesc }}</span>
+            </el-popover>
+          </template>
+        </el-table-column>
+        <el-table-column label="SKU" min-width="100" align="center" prop="sku" />
+
+        <el-table-column label="SKUID" min-width="100" align="center" prop="skuId" />
+        <el-table-column label="审核状态" min-width="100" align="center" prop="reviewStatus">
+          <template slot-scope="scope">
+            <span class="status-tag" :class="{
+              'status-pending': scope.row.reviewStatus === 0,
+              'status-success': scope.row.reviewStatus === 1,
+              'status-error': scope.row.reviewStatus === 2
+            }"></span>
+            {{ getValueByKey(scope.row.reviewStatus, auditStatus) }}
+          </template>
+        </el-table-column>
+
+        <el-table-column label="审核时间" min-width="100" align="center" prop="reviewTime" />
+        <el-table-column label="审核人" min-width="100" align="center" prop="reviewer" />
+        <el-table-column label="发布状态" min-width="100" align="center" prop="publishStatus">
+          <template slot-scope="scope">
+            <span class="status-tag" :class="{
+              'status-pending': scope.row.publishStatus === 0,
+              'status-success': scope.row.publishStatus === 1,
+              'status-error': scope.row.publishStatus === 2
+            }"></span>
+            {{ getValueByKey(scope.row.publishStatus, auditStatus) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="发布时间" min-width="100" align="center" prop="publishTime" />
+        <el-table-column label="发布人" min-width="100" align="center" prop="publisher" />
+        <el-table-column label="关联话题" min-width="100" align="center" prop="hotWords" />
+
+        <el-table-column label="直播时间" min-width="100" align="center" prop="liveTime" />
+        <!-- <el-table-column
+          label="直播场次"
+          min-width="100"
+          align="center"
+          prop="liveSession"
+        /> -->
+        <el-table-column label="直播中控" min-width="120" align="center" prop="liveController">
+        </el-table-column>
+
+        <el-table-column label="直播间号" min-width="100" align="center" prop="liveRoomId" />
+        <el-table-column label="操作人" min-width="100" align="center" prop="creator" />
+
+        <el-table-column label="创建时间" min-width="100" align="center" prop="createTime" />
+        <el-table-column label="操作" align="center" min-width="160"
+          v-bind="device !== 'mobile' ? { fixed: 'right' } : {}">
+          <template slot-scope="scope">
+            <el-tooltip class="item" effect="dark" content="下载" placement="top">
+              <el-button type="primary" :loading="scope.row && scope.row.id
+                ? downloadingRows[scope.row.id]
+                : false
+                " size="mini" icon="el-icon-download" circle @click="handleDownload(scope.row)" />
+            </el-tooltip>
+            <el-tooltip class="item" effect="dark" content="播放" placement="top">
+              <el-button type="success" size="mini" icon="el-icon-caret-right" circle @click="handlePlay(scope.row)" />
+            </el-tooltip>
+            <el-tooltip
+              class="more"
+              effect="dark"
+              content="删除"
+              placement="top"
+            >
+              <el-button
+                type="danger"
+                size="mini"
+                icon="el-icon-delete-solid"
+                circle
+                @click="handleDelete(scope.row)"
+              />
+            </el-tooltip>
+            <!-- <el-tooltip class="more" effect="dark" content="更多" placement="top">
+              <el-dropdown trigger="click" @command="command => handleMore(command, scope.row)">
+                <el-button size="mini" type="primary" icon="el-icon-more" circle title="更多" />
+                <el-dropdown-menu class="dropdown-menu" slot="dropdown">
+                  <el-dropdown-item command="changeTopic">修改话题</el-dropdown-item>
+                  <el-dropdown-item command="delete">删除</el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
+            </el-tooltip> -->
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- 分页 -->
+    <swPage v-if="total > 0" key="2" :listQuery="listQuery" :total="total" pos="btmRight" @retPage="retPage" />
+    <!-- 视频播放 -->
+    <video-player ref="videoPlayer" :video-url="currentVideoUrl" :video-title="currentVideoTitle"
+      @close="handlePlayerClose" />
+    <!-- 修改话题-->
+     <change-topic ref="changeTopicRef" :rows="currentVideo" @confirm="handleTopicChange" />
+  </div>
+</template>
+
+<script>
+import waves from "@/directive/waves";
+import swPage from "@/views/common/swPage";
+import { auditStatus } from "@/constants/index";
+import { getValueByKey } from "@/utils/index";
+import ExcelImport from "@/components/ExcelImport";
+import changeTopic from "@/views/oral-video/component/changeTopic";
+
+import { fetchOralVideoList, deleteOralVideoList } from "@/api/video";
+import downloadUtil from "@/utils/downloadUtil";
+import VideoPlayer from "@/components/VideoPlayer";
+import { mapGetters } from "vuex";
+
+
+export default {
+  name: "oralVideoIndex",
+  directives: {
+    waves
+  },
+  components: {
+    swPage,
+    VideoPlayer,
+    ExcelImport,
+    changeTopic
+  },
+  computed: {
+    ...mapGetters(["device"]) // 从 vuex 中获取设备类型
+  },
+  data() {
+    return {
+      auditStatus,
+      tableKey: 0,
+      list: [],
+      total: 0,
+      listLoading: false,
+      downloadingRows: {},
+      listQuery: {
+        currentPage: 1,
+        pageSize: 10,
+        videoTitle: null,
+        // sku: null,
+      },
+      currentVideoUrl: null,
+      currentVideoTitle: null,
+      currentVideo:{},
+    };
+  },
+  //页面创建的时候执行
+  created() {
+    this.getList();
+  },
+  methods: {
+    getValueByKey,
+    getList() {
+      this.listLoading = true;
+      fetchOralVideoList(this.listQuery).then(res => {
+        if (200 == res.code) {
+          this.total = res.data.total;
+          this.list = res.data.rows;
+        }
+        this.listLoading = false;
+      });
+    },
+    retPage() {
+      //分页
+      this.getList();
+    },
+    handleFilter() {
+      this.listQuery.page = 1;
+      this.getList();
+    },
+    // 视频下载
+    async handleDownload(row) {
+      const { videoUrl, videoTitle, id, sku } = row;
+      this.$set(this.downloadingRows, id, true);
+      try {
+        const title = sku ? videoTitle + "_" + sku : videoTitle;
+        const res = await downloadUtil.videoDownload(videoUrl, title);
+        if (res) {
+          this.$message.success("下载成功");
+        }
+      } catch (error) {
+        this.$message.error(error.message);
+      } finally {
+        this.$set(this.downloadingRows, id, false);
+      }
+    },
+    // 视频播放
+    handlePlay(row) {
+      const { videoUrl, videoTitle } = row;
+      ``;
+      if (!videoUrl) {
+        this.$message.error("视频地址不存在");
+        return;
+      }
+      this.currentVideoUrl = videoUrl;
+      this.currentVideoTitle = videoTitle;
+      this.$refs.videoPlayer.show();
+    },
+    handlePlayerClose() {
+      this.currentVideoUrl = "";
+      this.currentVideoTitle = "";
+    },
+    handleDelete(row) {
+      //删除
+      this.$confirm("是否删除该视频?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        const { id } = row;
+        deleteOralVideoList([id]).then(() => {
+          this.$notify({
+            title: "成功",
+            message: "删除成功",
+            type: "success",
+            duration: 3000
+          });
+          const index = this.list.indexOf(row);
+          this.list.splice(index, 1);
+        });
+      });
+    },
+    // 导入话题回调
+    handleImportSuccess({ data, header, file }) {
+      console.log("导入的数据:", data);
+      console.log("表头:", header);
+      console.log("原始文件:", file);
+      // 处理导入的数据
+    },
+    //更多
+    handleMore(command, row) {
+      if (command === "delete") {
+        this.handleDelete(row); // 删除
+      } else if (command === "changeTopic") {
+        this.changeTopic(row); // 修改话题
+      }
+    },
+    // 修改话题
+    changeTopic(row) {
+      this.currentVideo = row;
+      this.$refs.changeTopicRef.show();
+    },
+    handleTopicChange() {
+      this.$refs.changeTopicRef.show();
+      this.retPage()
+    }
+  }
+};
+</script>
+
+<style rel="stylesheet/scss" lang="scss" scoped>
+@import "@/styles/layout.scss";
+
+.button {
+  .el-button {
+    margin-bottom: 10px;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+}
+
+.video {
+  position: relative;
+  display: flex;
+  align-content: center;
+  justify-content: center;
+
+  &:hover {
+    i {
+      opacity: 1;
+    }
+
+    .video-img {
+      opacity: 0.6;
+    }
+  }
+
+  &-img {
+    width: 60px;
+    height: 60px;
+    border: 1px solid #eee;
+    object-fit: cover;
+    cursor: pointer;
+  }
+
+  i {
+    width: 60px;
+    height: 60px;
+    position: absolute;
+    top: 85%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    font-size: 20px;
+    font-weight: bold;
+    color: #fff;
+    opacity: 0;
+    cursor: pointer;
+  }
+}
+
+.status-tag {
+  display: inline-block;
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  margin-right: 6px;
+}
+
+.status-pending {
+  background-color: #e6a23c;
+}
+
+.status-success {
+  background-color: #67c23a;
+}
+
+.status-error {
+  background-color: #f56c6c;
+}
+
+.filter-btn {
+  height: 40px;
+}
+
+.desc {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  line-height: 1.5;
+  max-height: 3em;
+}
+
+.more {
+  margin-left: 10px;
+}
+</style>

+ 32 - 0
src/views/face-swap-video/face-swap-video.vue

@@ -0,0 +1,32 @@
+<template>
+  <div class="cut-video-container">
+    <iframe
+      ref="iframeRef"
+      :src="iframeUrl"
+      frameborder="0"
+      width="100%"
+      height="100%"
+      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
+      allowfullscreen
+    ></iframe>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'catwalkVideoIndex',
+  data() {
+    return {
+      iframeUrl: 'https://ai-app.gloria.com.cn/change-face/',
+    }
+  }
+};
+</script>
+
+<style scoped>
+.cut-video-container {
+  width: 100%;
+  height: 100vh;
+  overflow: hidden;
+}
+</style>

+ 377 - 0
src/views/face-swap-video/index.vue

@@ -0,0 +1,377 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <div class="filter-container-lt">
+        <el-input placeholder="视频标题" class="filter-item" style="width: 200px;" clearable
+          v-model="listQuery.videoTitle" />
+        <!-- <el-input placeholder="SKU" class="filter-item" style="width: 200px;" clearable v-model="listQuery.sku" /> -->
+        <el-button v-waves class="filter-btn" type="primary" icon="el-icon-search" @click="handleFilter">搜索</el-button>
+      </div>
+      <!-- <div class="filter-container-rt">
+        <excel-import :upload-url="'/api/upload-excel'" :headers="{ token: 'your-token' }"
+          :upload-data="{ type: 'import' }" @on-success="handleImportSuccess">
+          <template slot="upload-text">
+            导入话题
+          </template>
+        </excel-import>
+      </div> -->
+    </div>
+
+    <div class="table-container">
+      <el-table style="width: 100%" v-loading="listLoading" :key="tableKey" :data="list" row-key="id" stripe border fit
+        highlight-current-row>
+        <el-table-column label="序号" type="index" width="60" align="center" />
+        <el-table-column label="视频ID" min-width="100" align="center" prop="id" />
+
+        <el-table-column label="视频标题" min-width="150" align="center" prop="videoTitle" />
+        <el-table-column label="视频封面" min-width="110" align="center" prop="videoCoverImageUrl">
+          <template slot-scope="scope">
+            <div v-if="scope.row.videoCoverImageUrl" class="video" @click="handlePlay(scope.row)">
+              <img :src="scope.row.videoCoverImageUrl" class="video-img" />
+              <i class="el-icon-video-play" />
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="视频描述" min-width="150" align="center" prop="videoDesc">
+          <template slot-scope="scope">
+            <el-popover placement="top-start" width="200" trigger="hover" :content="scope.row.videoDesc">
+              <span class="desc"
+                slot="reference">{{ scope.row.videoDesc }}</span>
+            </el-popover>
+          </template>
+        </el-table-column>
+        <el-table-column label="SKU" min-width="100" align="center" prop="sku" />
+
+        <el-table-column label="SKUID" min-width="100" align="center" prop="skuId" />
+        <el-table-column label="审核状态" min-width="100" align="center" prop="reviewStatus">
+          <template slot-scope="scope">
+            <span class="status-tag" :class="{
+              'status-pending': scope.row.reviewStatus === 0,
+              'status-success': scope.row.reviewStatus === 1,
+              'status-error': scope.row.reviewStatus === 2
+            }"></span>
+            {{ getValueByKey(scope.row.reviewStatus, auditStatus) }}
+          </template>
+        </el-table-column>
+
+        <el-table-column label="审核时间" min-width="100" align="center" prop="reviewTime" />
+        <el-table-column label="审核人" min-width="100" align="center" prop="reviewer" />
+        <el-table-column label="发布状态" min-width="100" align="center" prop="publishStatus">
+          <template slot-scope="scope">
+            <span class="status-tag" :class="{
+              'status-pending': scope.row.publishStatus === 0,
+              'status-success': scope.row.publishStatus === 1,
+              'status-error': scope.row.publishStatus === 2
+            }"></span>
+            {{ getValueByKey(scope.row.publishStatus, auditStatus) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="发布时间" min-width="100" align="center" prop="publishTime" />
+        <el-table-column label="发布人" min-width="100" align="center" prop="publisher" />
+        <el-table-column label="关联话题" min-width="100" align="center" prop="hotWords" />
+
+        <el-table-column label="直播时间" min-width="100" align="center" prop="liveTime" />
+        <!-- <el-table-column
+          label="直播场次"
+          min-width="100"
+          align="center"
+          prop="liveSession"
+        /> -->
+        <el-table-column label="直播中控" min-width="120" align="center" prop="liveController">
+        </el-table-column>
+
+        <el-table-column label="直播间号" min-width="100" align="center" prop="liveRoomId" />
+        <el-table-column label="操作人" min-width="100" align="center" prop="creator" />
+
+        <el-table-column label="创建时间" min-width="100" align="center" prop="createTime" />
+        <el-table-column label="操作" align="center" min-width="160"
+          v-bind="device !== 'mobile' ? { fixed: 'right' } : {}">
+          <template slot-scope="scope">
+            <el-tooltip class="item" effect="dark" content="下载" placement="top">
+              <el-button type="primary" :loading="scope.row && scope.row.id
+                ? downloadingRows[scope.row.id]
+                : false
+                " size="mini" icon="el-icon-download" circle @click="handleDownload(scope.row)" />
+            </el-tooltip>
+            <el-tooltip class="item" effect="dark" content="播放" placement="top">
+              <el-button type="success" size="mini" icon="el-icon-caret-right" circle @click="handlePlay(scope.row)" />
+            </el-tooltip>
+            <el-tooltip
+              class="more"
+              effect="dark"
+              content="删除"
+              placement="top"
+            >
+              <el-button
+                type="danger"
+                size="mini"
+                icon="el-icon-delete-solid"
+                circle
+                @click="handleDelete(scope.row)"
+              />
+            </el-tooltip>
+            <!-- <el-tooltip class="more" effect="dark" content="更多" placement="top">
+              <el-dropdown trigger="click" @command="command => handleMore(command, scope.row)">
+                <el-button size="mini" type="primary" icon="el-icon-more" circle title="更多" />
+                <el-dropdown-menu class="dropdown-menu" slot="dropdown">
+                  <el-dropdown-item command="changeTopic">修改话题</el-dropdown-item>
+                  <el-dropdown-item command="delete">删除</el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
+            </el-tooltip> -->
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- 分页 -->
+    <swPage v-if="total > 0" key="2" :listQuery="listQuery" :total="total" pos="btmRight" @retPage="retPage" />
+    <!-- 视频播放 -->
+    <video-player ref="videoPlayer" :video-url="currentVideoUrl" :video-title="currentVideoTitle"
+      @close="handlePlayerClose" />
+    <!-- 修改话题-->
+     <change-topic ref="changeTopicRef" :rows="currentVideo" @confirm="handleTopicChange" />
+  </div>
+</template>
+
+<script>
+import waves from "@/directive/waves";
+import swPage from "@/views/common/swPage";
+import { auditStatus } from "@/constants/index";
+import { getValueByKey } from "@/utils/index";
+import ExcelImport from "@/components/ExcelImport";
+import changeTopic from "@/views/oral-video/component/changeTopic";
+
+import { fetchOralVideoList, deleteOralVideoList } from "@/api/video";
+import downloadUtil from "@/utils/downloadUtil";
+import VideoPlayer from "@/components/VideoPlayer";
+import { mapGetters } from "vuex";
+
+
+export default {
+  name: "oralVideoIndex",
+  directives: {
+    waves
+  },
+  components: {
+    swPage,
+    VideoPlayer,
+    ExcelImport,
+    changeTopic
+  },
+  computed: {
+    ...mapGetters(["device"]) // 从 vuex 中获取设备类型
+  },
+  data() {
+    return {
+      auditStatus,
+      tableKey: 0,
+      list: [],
+      total: 0,
+      listLoading: false,
+      downloadingRows: {},
+      listQuery: {
+        currentPage: 1,
+        pageSize: 10,
+        videoTitle: null,
+        // sku: null,
+      },
+      currentVideoUrl: null,
+      currentVideoTitle: null,
+      currentVideo:{},
+    };
+  },
+  //页面创建的时候执行
+  created() {
+    this.getList();
+  },
+  methods: {
+    getValueByKey,
+    getList() {
+      this.listLoading = true;
+      fetchOralVideoList(this.listQuery).then(res => {
+        if (200 == res.code) {
+          this.total = res.data.total;
+          this.list = res.data.rows;
+        }
+        this.listLoading = false;
+      });
+    },
+    retPage() {
+      //分页
+      this.getList();
+    },
+    handleFilter() {
+      this.listQuery.page = 1;
+      this.getList();
+    },
+    // 视频下载
+    async handleDownload(row) {
+      const { videoUrl, videoTitle, id, sku } = row;
+      this.$set(this.downloadingRows, id, true);
+      try {
+        const title = sku ? videoTitle + "_" + sku : videoTitle;
+        const res = await downloadUtil.videoDownload(videoUrl, title);
+        if (res) {
+          this.$message.success("下载成功");
+        }
+      } catch (error) {
+        this.$message.error(error.message);
+      } finally {
+        this.$set(this.downloadingRows, id, false);
+      }
+    },
+    // 视频播放
+    handlePlay(row) {
+      const { videoUrl, videoTitle } = row;
+      ``;
+      if (!videoUrl) {
+        this.$message.error("视频地址不存在");
+        return;
+      }
+      this.currentVideoUrl = videoUrl;
+      this.currentVideoTitle = videoTitle;
+      this.$refs.videoPlayer.show();
+    },
+    handlePlayerClose() {
+      this.currentVideoUrl = "";
+      this.currentVideoTitle = "";
+    },
+    handleDelete(row) {
+      //删除
+      this.$confirm("是否删除该视频?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        const { id } = row;
+        deleteOralVideoList([id]).then(() => {
+          this.$notify({
+            title: "成功",
+            message: "删除成功",
+            type: "success",
+            duration: 3000
+          });
+          const index = this.list.indexOf(row);
+          this.list.splice(index, 1);
+        });
+      });
+    },
+    // 导入话题回调
+    handleImportSuccess({ data, header, file }) {
+      console.log("导入的数据:", data);
+      console.log("表头:", header);
+      console.log("原始文件:", file);
+      // 处理导入的数据
+    },
+    //更多
+    handleMore(command, row) {
+      if (command === "delete") {
+        this.handleDelete(row); // 删除
+      } else if (command === "changeTopic") {
+        this.changeTopic(row); // 修改话题
+      }
+    },
+    // 修改话题
+    changeTopic(row) {
+      this.currentVideo = row;
+      this.$refs.changeTopicRef.show();
+    },
+    handleTopicChange() {
+      this.$refs.changeTopicRef.show();
+      this.retPage()
+    }
+  }
+};
+</script>
+
+<style rel="stylesheet/scss" lang="scss" scoped>
+@import "@/styles/layout.scss";
+
+.button {
+  .el-button {
+    margin-bottom: 10px;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+}
+
+.video {
+  position: relative;
+  display: flex;
+  align-content: center;
+  justify-content: center;
+
+  &:hover {
+    i {
+      opacity: 1;
+    }
+
+    .video-img {
+      opacity: 0.6;
+    }
+  }
+
+  &-img {
+    width: 60px;
+    height: 60px;
+    border: 1px solid #eee;
+    object-fit: cover;
+    cursor: pointer;
+  }
+
+  i {
+    width: 60px;
+    height: 60px;
+    position: absolute;
+    top: 85%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    font-size: 20px;
+    font-weight: bold;
+    color: #fff;
+    opacity: 0;
+    cursor: pointer;
+  }
+}
+
+.status-tag {
+  display: inline-block;
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  margin-right: 6px;
+}
+
+.status-pending {
+  background-color: #e6a23c;
+}
+
+.status-success {
+  background-color: #67c23a;
+}
+
+.status-error {
+  background-color: #f56c6c;
+}
+
+.filter-btn {
+  height: 40px;
+}
+
+.desc {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  line-height: 1.5;
+  max-height: 3em;
+}
+
+.more {
+  margin-left: 10px;
+}
+</style>

+ 91 - 0
src/views/oral-video/component/changeTopic.vue

@@ -0,0 +1,91 @@
+<template>
+  <el-dialog
+    :title="'修改话题 - ' + rows.videoTitle"
+    width="500px"
+    :visible.sync="dialogVisible"
+  >
+    <el-form :model="form" :rules="rules" ref="topicForm" label-width="80px">
+      <el-form-item label="选择话题">
+        <el-select
+          v-model="form.topic"
+          filterable
+          allow-create
+          default-first-option
+          placeholder="请选择或输入话题"
+        >
+          <el-option
+            v-for="item in topicOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+    </el-form>
+
+    <span slot="footer" class="dialog-footer">
+      <el-button @click="dialogVisible = false">取消</el-button>
+      <el-button type="primary" @click="handleConfirm">确定</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  name: "ChangeTopicDialog",
+  props: {
+    rows: {
+      type: Object,
+      required: true
+    }
+  },
+  data() {
+    return {
+      form: {
+        topic: ""
+      },
+      dialogVisible: false,
+      topicOptions: [],
+      loading: false,
+      rules: {
+        topic: [
+          { required: true, message: "请选择或输入话题", trigger: "blur" }
+        ]
+      }
+    };
+  },
+  methods: {
+    show() {
+      this.dialogVisible = !this.dialogVisible;
+    },
+    async fetchTopics() {
+      try {
+        this.loading = true;
+        const res = await this.$api.video.getTopics(); // 假设接口路径为/video/topics
+        this.topicOptions = res.data.map(item => ({
+          value: item,
+          label: item
+        }));
+      } finally {
+        this.loading = false;
+      }
+    },
+    handleConfirm() {
+      this.$refs.topicForm.validate(valid => {
+        if (valid) {
+          this.$emit("confirm", this.form.topic);
+        } else {
+          return false;
+        }
+      });
+      this.dialogVisible = false;
+    }
+  }
+};
+</script>
+
+<style scoped>
+.el-select {
+  width: 100%;
+}
+</style>

+ 377 - 0
src/views/oral-video/index.vue

@@ -0,0 +1,377 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <div class="filter-container-lt">
+        <el-input placeholder="视频标题" class="filter-item" style="width: 200px;" clearable
+          v-model="listQuery.videoTitle" />
+        <!-- <el-input placeholder="SKU" class="filter-item" style="width: 200px;" clearable v-model="listQuery.sku" /> -->
+        <el-button v-waves class="filter-btn" type="primary" icon="el-icon-search" @click="handleFilter">搜索</el-button>
+      </div>
+      <!-- <div class="filter-container-rt">
+        <excel-import :upload-url="'/api/upload-excel'" :headers="{ token: 'your-token' }"
+          :upload-data="{ type: 'import' }" @on-success="handleImportSuccess">
+          <template slot="upload-text">
+            导入话题
+          </template>
+        </excel-import>
+      </div> -->
+    </div>
+
+    <div class="table-container">
+      <el-table style="width: 100%" v-loading="listLoading" :key="tableKey" :data="list" row-key="id" stripe border fit
+        highlight-current-row>
+        <el-table-column label="序号" type="index" width="60" align="center" />
+        <el-table-column label="视频ID" min-width="100" align="center" prop="id" />
+
+        <el-table-column label="视频标题" min-width="150" align="center" prop="videoTitle" />
+        <el-table-column label="视频封面" min-width="110" align="center" prop="videoCoverImageUrl">
+          <template slot-scope="scope">
+            <div v-if="scope.row.videoCoverImageUrl" class="video" @click="handlePlay(scope.row)">
+              <img :src="scope.row.videoCoverImageUrl" class="video-img" />
+              <i class="el-icon-video-play" />
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="视频描述" min-width="150" align="center" prop="videoDesc">
+          <template slot-scope="scope">
+            <el-popover placement="top-start" width="200" trigger="hover" :content="scope.row.videoDesc">
+              <span class="desc"
+                slot="reference">{{ scope.row.videoDesc }}</span>
+            </el-popover>
+          </template>
+        </el-table-column>
+        <el-table-column label="SKU" min-width="100" align="center" prop="sku" />
+
+        <el-table-column label="SKUID" min-width="100" align="center" prop="skuId" />
+        <el-table-column label="审核状态" min-width="100" align="center" prop="reviewStatus">
+          <template slot-scope="scope">
+            <span class="status-tag" :class="{
+              'status-pending': scope.row.reviewStatus === 0,
+              'status-success': scope.row.reviewStatus === 1,
+              'status-error': scope.row.reviewStatus === 2
+            }"></span>
+            {{ getValueByKey(scope.row.reviewStatus, auditStatus) }}
+          </template>
+        </el-table-column>
+
+        <el-table-column label="审核时间" min-width="100" align="center" prop="reviewTime" />
+        <el-table-column label="审核人" min-width="100" align="center" prop="reviewer" />
+        <el-table-column label="发布状态" min-width="100" align="center" prop="publishStatus">
+          <template slot-scope="scope">
+            <span class="status-tag" :class="{
+              'status-pending': scope.row.publishStatus === 0,
+              'status-success': scope.row.publishStatus === 1,
+              'status-error': scope.row.publishStatus === 2
+            }"></span>
+            {{ getValueByKey(scope.row.publishStatus, auditStatus) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="发布时间" min-width="100" align="center" prop="publishTime" />
+        <el-table-column label="发布人" min-width="100" align="center" prop="publisher" />
+        <el-table-column label="关联话题" min-width="100" align="center" prop="hotWords" />
+
+        <el-table-column label="直播时间" min-width="100" align="center" prop="liveTime" />
+        <!-- <el-table-column
+          label="直播场次"
+          min-width="100"
+          align="center"
+          prop="liveSession"
+        /> -->
+        <el-table-column label="直播中控" min-width="120" align="center" prop="liveController">
+        </el-table-column>
+
+        <el-table-column label="直播间号" min-width="100" align="center" prop="liveRoomId" />
+        <el-table-column label="操作人" min-width="100" align="center" prop="creator" />
+
+        <el-table-column label="创建时间" min-width="100" align="center" prop="createTime" />
+        <el-table-column label="操作" align="center" min-width="160"
+          v-bind="device !== 'mobile' ? { fixed: 'right' } : {}">
+          <template slot-scope="scope">
+            <el-tooltip class="item" effect="dark" content="下载" placement="top">
+              <el-button type="primary" :loading="scope.row && scope.row.id
+                ? downloadingRows[scope.row.id]
+                : false
+                " size="mini" icon="el-icon-download" circle @click="handleDownload(scope.row)" />
+            </el-tooltip>
+            <el-tooltip class="item" effect="dark" content="播放" placement="top">
+              <el-button type="success" size="mini" icon="el-icon-caret-right" circle @click="handlePlay(scope.row)" />
+            </el-tooltip>
+            <el-tooltip
+              class="more"
+              effect="dark"
+              content="删除"
+              placement="top"
+            >
+              <el-button
+                type="danger"
+                size="mini"
+                icon="el-icon-delete-solid"
+                circle
+                @click="handleDelete(scope.row)"
+              />
+            </el-tooltip>
+            <!-- <el-tooltip class="more" effect="dark" content="更多操作" placement="top">
+              <el-dropdown trigger="click" @command="command => handleMore(command, scope.row)">
+                <el-button size="mini" type="primary" icon="el-icon-more" circle title="更多操作" />
+                <el-dropdown-menu class="dropdown-menu" slot="dropdown">
+                  <el-dropdown-item command="changeTopic">修改话题</el-dropdown-item>
+                  <el-dropdown-item command="delete">删除</el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
+            </el-tooltip> -->
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- 分页 -->
+    <swPage v-if="total > 0" key="2" :listQuery="listQuery" :total="total" pos="btmRight" @retPage="retPage" />
+    <!-- 视频播放 -->
+    <video-player ref="videoPlayer" :video-url="currentVideoUrl" :video-title="currentVideoTitle"
+      @close="handlePlayerClose" />
+    <!-- 修改话题-->
+     <change-topic ref="changeTopicRef" :rows="currentVideo" @confirm="handleTopicChange" />
+  </div>
+</template>
+
+<script>
+import waves from "@/directive/waves";
+import swPage from "@/views/common/swPage";
+import { auditStatus } from "@/constants/index";
+import { getValueByKey } from "@/utils/index";
+import ExcelImport from "@/components/ExcelImport";
+import changeTopic from "@/views/oral-video/component/changeTopic";
+
+import { fetchOralVideoList, deleteOralVideoList } from "@/api/video";
+import downloadUtil from "@/utils/downloadUtil";
+import VideoPlayer from "@/components/VideoPlayer";
+import { mapGetters } from "vuex";
+
+
+export default {
+  name: "oralVideoIndex",
+  directives: {
+    waves
+  },
+  components: {
+    swPage,
+    VideoPlayer,
+    ExcelImport,
+    changeTopic
+  },
+  computed: {
+    ...mapGetters(["device"]) // 从 vuex 中获取设备类型
+  },
+  data() {
+    return {
+      auditStatus,
+      tableKey: 0,
+      list: [],
+      total: 0,
+      listLoading: false,
+      downloadingRows: {},
+      listQuery: {
+        currentPage: 1,
+        pageSize: 10,
+        videoTitle: null,
+        // sku:null
+      },
+      currentVideoUrl: null,
+      currentVideoTitle: null,
+      currentVideo:{},
+    };
+  },
+  //页面创建的时候执行
+  created() {
+    this.getList();
+  },
+  methods: {
+    getValueByKey,
+    getList() {
+      this.listLoading = true;
+      fetchOralVideoList(this.listQuery).then(res => {
+        if (200 == res.code) {
+          this.total = res.data.total;
+          this.list = res.data.rows;
+        }
+        this.listLoading = false;
+      });
+    },
+    retPage() {
+      //分页
+      this.getList();
+    },
+    handleFilter() {
+      this.listQuery.page = 1;
+      this.getList();
+    },
+    // 视频下载
+    async handleDownload(row) {
+      const { videoUrl, videoTitle, id, sku } = row;
+      this.$set(this.downloadingRows, id, true);
+      try {
+        const title = sku ? videoTitle + "_" + sku : videoTitle;
+        const res = await downloadUtil.videoDownload(videoUrl, title);
+        if (res) {
+          this.$message.success("下载成功");
+        }
+      } catch (error) {
+        this.$message.error(error.message);
+      } finally {
+        this.$set(this.downloadingRows, id, false);
+      }
+    },
+    // 视频播放
+    handlePlay(row) {
+      const { videoUrl, videoTitle } = row;
+      ``;
+      if (!videoUrl) {
+        this.$message.error("视频地址不存在");
+        return;
+      }
+      this.currentVideoUrl = videoUrl;
+      this.currentVideoTitle = videoTitle;
+      this.$refs.videoPlayer.show();
+    },
+    handlePlayerClose() {
+      this.currentVideoUrl = "";
+      this.currentVideoTitle = "";
+    },
+    handleDelete(row) {
+      //删除
+      this.$confirm("是否删除该视频?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        const { id } = row;
+        deleteOralVideoList([id]).then(() => {
+          this.$notify({
+            title: "成功",
+            message: "删除成功",
+            type: "success",
+            duration: 3000
+          });
+          const index = this.list.indexOf(row);
+          this.list.splice(index, 1);
+        });
+      });
+    },
+    // 导入话题回调
+    handleImportSuccess({ data, header, file }) {
+      console.log("导入的数据:", data);
+      console.log("表头:", header);
+      console.log("原始文件:", file);
+      // 处理导入的数据
+    },
+    //更多
+    handleMore(command, row) {
+      if (command === "delete") {
+        this.handleDelete(row); // 删除
+      } else if (command === "changeTopic") {
+        this.changeTopic(row); // 修改话题
+      }
+    },
+    // 修改话题
+    changeTopic(row) {
+      this.currentVideo = row;
+      this.$refs.changeTopicRef.show();
+    },
+    handleTopicChange() {
+      this.$refs.changeTopicRef.show();
+      this.retPage()
+    }
+  }
+};
+</script>
+
+<style rel="stylesheet/scss" lang="scss" scoped>
+@import "@/styles/layout.scss";
+
+.button {
+  .el-button {
+    margin-bottom: 10px;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+}
+
+.video {
+  position: relative;
+  display: flex;
+  align-content: center;
+  justify-content: center;
+
+  &:hover {
+    i {
+      opacity: 1;
+    }
+
+    .video-img {
+      opacity: 0.6;
+    }
+  }
+
+  &-img {
+    width: 60px;
+    height: 60px;
+    border: 1px solid #eee;
+    object-fit: cover;
+    cursor: pointer;
+  }
+
+  i {
+    width: 60px;
+    height: 60px;
+    position: absolute;
+    top: 85%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    font-size: 20px;
+    font-weight: bold;
+    color: #fff;
+    opacity: 0;
+    cursor: pointer;
+  }
+}
+
+.status-tag {
+  display: inline-block;
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  margin-right: 6px;
+}
+
+.status-pending {
+  background-color: #e6a23c;
+}
+
+.status-success {
+  background-color: #67c23a;
+}
+
+.status-error {
+  background-color: #f56c6c;
+}
+
+.filter-btn {
+  height: 40px;
+}
+
+.desc {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  line-height: 1.5;
+  max-height: 3em;
+}
+
+.more {
+  margin-left: 10px;
+}
+</style>

+ 376 - 0
src/views/product/product.vue

@@ -0,0 +1,376 @@
+<template>
+    <div class="app-container">
+      <div class="filter-container">
+        <div class="filter-container-lt">
+          <el-input placeholder="视频标题" class="filter-item" style="width: 200px;" clearable
+            v-model="listQuery.videoTitle" />
+          <el-input placeholder="SKU" class="filter-item" style="width: 200px;" clearable v-model="listQuery.sku" />
+          <el-button v-waves class="filter-btn" type="primary" icon="el-icon-search" @click="handleFilter">搜索</el-button>
+        </div>
+        <div class="filter-container-rt">
+          <!-- 导入话题 -->
+          <excel-import :upload-url="'/api/upload-excel'" :headers="{ token: 'your-token' }"
+            :upload-data="{ type: 'import' }" @on-success="handleImportSuccess">
+            <template slot="upload-text">
+              导入话题
+            </template>
+          </excel-import>
+        </div>
+      </div>
+  
+      <div class="table-container">
+        <el-table style="width: 100%" v-loading="listLoading" :key="tableKey" :data="list" row-key="id" stripe border fit
+          highlight-current-row>
+          <el-table-column label="序号" type="index" width="60" align="center" />
+          <el-table-column label="视频ID" min-width="100" align="center" prop="id" />
+  
+          <el-table-column label="视频标题" min-width="150" align="center" prop="videoTitle" />
+          <el-table-column label="视频封面" min-width="110" align="center" prop="videoCoverImageUrl">
+            <template slot-scope="scope">
+              <div v-if="scope.row.videoCoverImageUrl" class="video" @click="handlePlay(scope.row)">
+                <img :src="scope.row.videoCoverImageUrl" class="video-img" />
+                <i class="el-icon-video-play" />
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="视频描述" min-width="150" align="center" prop="videoDesc">
+            <template slot-scope="scope">
+              <el-popover placement="top-start" width="200" trigger="hover" :content="scope.row.videoDesc">
+                <span class="desc"
+                  slot="reference">测试测试测试测试测试测试测试测试测试测试测试测试试测试测试测试测试测试测试测试测试测试试测试测试测试测试测试测试测试测试测试试测试测试测试测试测试测试测试测试测试试测试测试测试测试测试测试测试测试测试</span>
+              </el-popover>
+            </template>
+          </el-table-column>
+          <el-table-column label="SKU" min-width="100" align="center" prop="sku" />
+  
+          <el-table-column label="SKUID" min-width="100" align="center" prop="skuId" />
+          <el-table-column label="审核状态" min-width="100" align="center" prop="reviewStatus">
+            <template slot-scope="scope">
+              <span class="status-tag" :class="{
+                'status-pending': scope.row.reviewStatus === 0,
+                'status-success': scope.row.reviewStatus === 1,
+                'status-error': scope.row.reviewStatus === 2
+              }"></span>
+              {{ getValueByKey(scope.row.reviewStatus, auditStatus) }}
+            </template>
+          </el-table-column>
+  
+          <el-table-column label="审核时间" min-width="100" align="center" prop="reviewTime" />
+          <el-table-column label="审核人" min-width="100" align="center" prop="reviewer" />
+          <el-table-column label="发布状态" min-width="100" align="center" prop="publishStatus">
+            <template slot-scope="scope">
+              <span class="status-tag" :class="{
+                'status-pending': scope.row.publishStatus === 0,
+                'status-success': scope.row.publishStatus === 1,
+                'status-error': scope.row.publishStatus === 2
+              }"></span>
+              {{ getValueByKey(scope.row.publishStatus, auditStatus) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="发布时间" min-width="100" align="center" prop="publishTime" />
+          <el-table-column label="发布人" min-width="100" align="center" prop="publisher" />
+          <el-table-column label="关联话题" min-width="100" align="center" prop="hotWords" />
+  
+          <el-table-column label="直播时间" min-width="100" align="center" prop="liveTime" />
+          <!-- <el-table-column
+            label="直播场次"
+            min-width="100"
+            align="center"
+            prop="liveSession"
+          /> -->
+          <el-table-column label="直播中控" min-width="120" align="center" prop="liveController">
+          </el-table-column>
+  
+          <el-table-column label="直播间号" min-width="100" align="center" prop="liveRoomId" />
+          <el-table-column label="操作人" min-width="100" align="center" prop="creator" />
+  
+          <el-table-column label="创建时间" min-width="100" align="center" prop="createTime" />
+          <el-table-column label="操作" align="center" min-width="160"
+            v-bind="device !== 'mobile' ? { fixed: 'right' } : {}">
+            <template slot-scope="scope">
+              <el-tooltip class="item" effect="dark" content="下载" placement="top">
+                <el-button type="primary" :loading="scope.row && scope.row.id
+                  ? downloadingRows[scope.row.id]
+                  : false
+                  " size="mini" icon="el-icon-download" circle @click="handleDownload(scope.row)" />
+              </el-tooltip>
+              <el-tooltip class="item" effect="dark" content="播放" placement="top">
+                <el-button type="success" size="mini" icon="el-icon-caret-right" circle @click="handlePlay(scope.row)" />
+              </el-tooltip>
+              <!-- <el-tooltip
+                class="more"
+                effect="dark"
+                content="删除"
+                placement="top"
+              >
+                <el-button
+                  type="danger"
+                  size="mini"
+                  icon="el-icon-delete-solid"
+                  circle
+                  @click="handleDelete(scope.row)"
+                />
+              </el-tooltip> -->
+              <el-tooltip class="more" effect="dark" content="更多" placement="top">
+                <el-dropdown trigger="click" @command="command => handleMore(command, scope.row)">
+                  <el-button size="mini" type="primary" icon="el-icon-more" circle title="更多" />
+                  <el-dropdown-menu class="dropdown-menu" slot="dropdown">
+                    <el-dropdown-item command="changeTopic">修改话题</el-dropdown-item>
+                    <el-dropdown-item command="delete">删除</el-dropdown-item>
+                  </el-dropdown-menu>
+                </el-dropdown>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+  
+      <!-- 分页 -->
+      <swPage v-if="total > 0" key="2" :listQuery="listQuery" :total="total" pos="btmRight" @retPage="retPage" />
+      <!-- 视频播放 -->
+      <video-player ref="videoPlayer" :video-url="currentVideoUrl" :video-title="currentVideoTitle"
+        @close="handlePlayerClose" />
+     
+    </div>
+  </template>
+  
+  <script>
+  import waves from "@/directive/waves";
+  import swPage from "@/views/common/swPage";
+  import { auditStatus } from "@/constants/index";
+  import { getValueByKey } from "@/utils/index";
+  import ExcelImport from "@/components/ExcelImport";
+
+  
+  import { fetchOralVideoList, deleteOralVideoList } from "@/api/video";
+  import downloadUtil from "@/utils/downloadUtil";
+  import VideoPlayer from "@/components/VideoPlayer";
+  import { mapGetters } from "vuex";
+  
+  
+  export default {
+    name: "product",
+    directives: {
+      waves
+    },
+    components: {
+      swPage,
+      VideoPlayer,
+      ExcelImport,
+    },
+    computed: {
+      ...mapGetters(["device"]) // 从 vuex 中获取设备类型
+    },
+    data() {
+      return {
+        auditStatus,
+        tableKey: 0,
+        list: [],
+        total: 0,
+        listLoading: false,
+        downloadingRows: {},
+        listQuery: {
+          currentPage: 1,
+          pageSize: 10,
+          videoTitle: null
+        },
+        currentVideoUrl: null,
+        currentVideoTitle: null,
+        currentVideo:{},
+      };
+    },
+    //页面创建的时候执行
+    created() {
+      this.getList();
+    },
+    methods: {
+      getValueByKey,
+      getList() {
+        this.listLoading = true;
+        fetchOralVideoList(this.listQuery).then(res => {
+          if (200 == res.code) {
+            this.total = res.data.total;
+            this.list = res.data.rows;
+          }
+          this.listLoading = false;
+        });
+      },
+      retPage() {
+        //分页
+        this.getList();
+      },
+      handleFilter() {
+        this.listQuery.page = 1;
+        this.getList();
+      },
+      // 视频下载
+      async handleDownload(row) {
+        const { videoUrl, videoTitle, id, sku } = row;
+        this.$set(this.downloadingRows, id, true);
+        try {
+          const title = sku ? videoTitle + "_" + sku : videoTitle;
+          const res = await downloadUtil.videoDownload(videoUrl, title);
+          if (res) {
+            this.$message.success("下载成功");
+          }
+        } catch (error) {
+          this.$message.error(error.message);
+        } finally {
+          this.$set(this.downloadingRows, id, false);
+        }
+      },
+      // 视频播放
+      handlePlay(row) {
+        const { videoUrl, videoTitle } = row;
+        ``;
+        if (!videoUrl) {
+          this.$message.error("视频地址不存在");
+          return;
+        }
+        this.currentVideoUrl = videoUrl;
+        this.currentVideoTitle = videoTitle;
+        this.$refs.videoPlayer.show();
+      },
+      handlePlayerClose() {
+        this.currentVideoUrl = "";
+        this.currentVideoTitle = "";
+      },
+      handleDelete(row) {
+        //删除
+        this.$confirm("是否删除该视频?", "提示", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          const { id } = row;
+          deleteOralVideoList([id]).then(() => {
+            this.$notify({
+              title: "成功",
+              message: "删除成功",
+              type: "success",
+              duration: 3000
+            });
+            const index = this.list.indexOf(row);
+            this.list.splice(index, 1);
+          });
+        });
+      },
+      // 导入话题回调
+      handleImportSuccess({ data, header, file }) {
+        console.log("导入的数据:", data);
+        console.log("表头:", header);
+        console.log("原始文件:", file);
+        // 处理导入的数据
+      },
+      //更多
+      handleMore(command, row) {
+        if (command === "delete") {
+          this.handleDelete(row); // 删除
+        } else if (command === "changeTopic") {
+          this.changeTopic(row); // 修改话题
+        }
+      },
+      // 修改话题
+      changeTopic(row) {
+        this.currentVideo = row;
+        this.$refs.changeTopicRef.show();
+      },
+      handleTopicChange() {
+        this.$refs.changeTopicRef.show();
+        this.retPage()
+      }
+    }
+  };
+  </script>
+  
+  <style rel="stylesheet/scss" lang="scss" scoped>
+  @import "@/styles/layout.scss";
+  
+  .button {
+    .el-button {
+      margin-bottom: 10px;
+  
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+  }
+  
+  .video {
+    position: relative;
+    display: flex;
+    align-content: center;
+    justify-content: center;
+  
+    &:hover {
+      i {
+        opacity: 1;
+      }
+  
+      .video-img {
+        opacity: 0.6;
+      }
+    }
+  
+    &-img {
+      width: 60px;
+      height: 60px;
+      border: 1px solid #eee;
+      object-fit: cover;
+      cursor: pointer;
+    }
+  
+    i {
+      width: 60px;
+      height: 60px;
+      position: absolute;
+      top: 85%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      font-size: 20px;
+      font-weight: bold;
+      color: #fff;
+      opacity: 0;
+      cursor: pointer;
+    }
+  }
+  
+  .status-tag {
+    display: inline-block;
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    margin-right: 6px;
+  }
+  
+  .status-pending {
+    background-color: #e6a23c;
+  }
+  
+  .status-success {
+    background-color: #67c23a;
+  }
+  
+  .status-error {
+    background-color: #f56c6c;
+  }
+  
+  .filter-btn {
+    height: 40px;
+  }
+  
+  .desc {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    line-height: 1.5;
+    max-height: 3em;
+  }
+  
+  .more {
+    margin-left: 10px;
+  }
+  </style>
+  

+ 0 - 465
src/views/video/oral-video.vue

@@ -1,465 +0,0 @@
-<template>
-  <div class="app-container">
-    <div class="filter-container">
-      <div class="filter-container-lt">
-        <el-input
-          placeholder="视频标题"
-          style="width: 200px;"
-          class="filter-item"
-          clearable
-          v-model="listQuery.videoTitle"
-        />
-
-        <!-- <el-select
-        clearable
-        filterable
-        class="filter-item"
-        v-model="listQuery.warehouseId"
-        :placeholder="$t('place.selectWarehouse')"
-      >
-        <el-option
-          v-for="item in warehouses"
-          :key="item.warehouseId"
-          :label="item.warehouseName"
-          :value="item.warehouseId"
-        >
-        </el-option>
-      </el-select> -->
-
-        <el-button
-          v-waves
-          class="filter-item"
-          type="primary"
-          icon="el-icon-search"
-          @click="handleFilter"
-          >搜索</el-button
-        >
-      </div>
-      <div class="filter-container-rt">
-        <!-- <el-button class="filter-item" @click="exportExcel" type="primary"
-          >导出EXCEL</el-button
-        >
-        <el-button
-          v-waves
-          class="filter-item"
-          type="primary"
-          icon="el-icon-upload"
-          @click="handleUpload"
-        >
-          导入
-        </el-button> -->
-      </div>
-    </div>
-
-    <div class="table-container">
-      <el-table
-        style="width: 100%"
-        v-loading="listLoading"
-        :key="tableKey"
-        :data="list"
-        row-key="id"
-        stripe
-        border
-        fit
-        highlight-current-row
-      >
-      <el-table-column
-          label="序号"
-          type="index"
-          width="60"
-          align="center"
-        />
-        <el-table-column
-          label="视频ID"
-          min-width="100"
-          align="center"
-          prop="id"
-        />
-
-        <el-table-column
-          label="视频标题"
-          min-width="150"
-          align="center"
-          prop="videoTitle"
-        />
-        <el-table-column
-          label="视频封面"
-          min-width="110"
-          align="center"
-          prop="videoCoverImageUrl"
-        >
-          <template slot-scope="scope">
-            <div
-              v-if="scope.row.videoCoverImageUrl"
-              class="video"
-              @click="handlePlay(scope.row)"
-            >
-              <img :src="scope.row.videoCoverImageUrl" class="video-img" />
-              <i class="el-icon-video-play" />
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column
-          label="SKU"
-          min-width="100"
-          align="center"
-          prop="sku"
-        />
-
-        <el-table-column
-          label="SKUID"
-          min-width="100"
-          align="center"
-          prop="skuId"
-        />
-        <el-table-column
-          label="审核状态"
-          min-width="100"
-          align="center"
-          prop="reviewStatus"
-        >
-          <template slot-scope="scope">
-            <span
-              class="status-tag"
-              :class="{
-                'status-pending': scope.row.reviewStatus === 0,
-                'status-success': scope.row.reviewStatus === 1,
-                'status-error': scope.row.reviewStatus === 2
-              }"
-            ></span>
-            {{ getValueByKey(scope.row.reviewStatus, auditStatus) }}
-          </template>
-        </el-table-column>
-
-        <el-table-column
-          label="审核时间"
-          min-width="100"
-          align="center"
-          prop="reviewTime"
-        />
-        <el-table-column
-          label="审核人"
-          min-width="100"
-          align="center"
-          prop="reviewer"
-        />
-        <el-table-column
-          label="发布状态"
-          min-width="100"
-          align="center"
-          prop="publishStatus"
-        >
-          <template slot-scope="scope">
-            <span
-              class="status-tag"
-              :class="{
-                'status-pending': scope.row.publishStatus === 0,
-                'status-success': scope.row.publishStatus === 1,
-                'status-error': scope.row.publishStatus === 2
-              }"
-            ></span>
-            {{ getValueByKey(scope.row.publishStatus, auditStatus) }}
-          </template>
-        </el-table-column>
-        <el-table-column
-          label="发布时间"
-          min-width="100"
-          align="center"
-          prop="publishTime"
-        />
-        <el-table-column
-          label="发布人"
-          min-width="100"
-          align="center"
-          prop="publisher"
-        />
-        <el-table-column
-          label="关联热词"
-          min-width="100"
-          align="center"
-          prop="hotWords"
-        />
-
-        <el-table-column
-          label="直播时间"
-          min-width="100"
-          align="center"
-          prop="liveTime"
-        />
-        <!-- <el-table-column
-          label="直播场次"
-          min-width="100"
-          align="center"
-          prop="liveSession"
-        /> -->
-        <el-table-column
-          label="直播中控"
-          min-width="120"
-          align="center"
-          prop="liveController"
-        >
-        </el-table-column>
-
-        <el-table-column
-          label="直播间号"
-          min-width="100"
-          align="center"
-          prop="liveRoomId"
-        />
-        <el-table-column
-          label="操作人"
-          min-width="100"
-          align="center"
-          prop="creator"
-        />
-
-        <el-table-column
-          label="创建时间"
-          min-width="100"
-          align="center"
-          prop="createTime"
-        />
-        <el-table-column
-          label="操作"
-          align="center"
-          min-width="250"
-          v-bind="device !== 'mobile' ? { fixed: 'right' } : {}"
-        >
-          <template slot-scope="scope">
-            <div class="button">
-              <el-button
-                type="primary"
-                :loading="
-                  scope.row && scope.row.id
-                    ? downloadingRows[scope.row.id]
-                    : false
-                "
-                size="mini"
-                @click="handleDownload(scope.row)"
-              >
-                下载
-              </el-button>
-              <el-button
-                type="success"
-                size="mini"
-                @click="handlePlay(scope.row)"
-              >
-                播放
-              </el-button>
-              <el-button
-                type="danger"
-                size="mini"
-                @click="handleDelete(scope.row)"
-              >
-                删除
-              </el-button>
-            </div>
-          </template>
-        </el-table-column>
-      </el-table>
-    </div>
-
-    <!-- 分页 -->
-    <swPage
-      v-if="total > 0"
-      key="2"
-      :listQuery="listQuery"
-      :total="total"
-      pos="btmRight"
-      @retPage="retPage"
-    />
-    <!-- 视频播放 -->
-    <video-player
-      ref="videoPlayer"
-      :video-url="currentVideoUrl"
-      :video-title="currentVideoTitle"
-      @close="handlePlayerClose"
-    />
-  </div>
-</template>
-
-<script>
-import waves from "@/directive/waves";
-import swPage from "@/views/common/swPage";
-import { auditStatus } from "@/constants/index";
-import { getValueByKey } from "@/utils/index";
-// import { getToken } from "@/utils/auth";
-import { fetchOralVideoList, deleteOralVideoList } from "@/api/video";
-import downloadUtil from "@/utils/downloadUtil";
-import VideoPlayer from "@/components/VideoPlayer";
-import { mapGetters } from "vuex";
-
-export default {
-  name: "oralVideo",
-  directives: {
-    waves
-  },
-  components: {
-    swPage,
-    VideoPlayer
-  },
-  computed: {
-    ...mapGetters(["device"]) // 从 vuex 中获取设备类型
-  },
-  data() {
-    return {
-      auditStatus,
-      tableKey: 0,
-      list: [],
-      total: 0,
-      listLoading: false,
-      downloadingRows: {},
-      listQuery: {
-        currentPage: 1,
-        pageSize: 10,
-        videoTitle: null
-      },
-      currentVideoUrl: null,
-      currentVideoTitle: null
-    };
-  },
-  //页面创建的时候执行
-  created() {
-    this.getList();
-  },
-  methods: {
-    getValueByKey,
-    getList() {
-      this.listLoading = true;
-      fetchOralVideoList(this.listQuery).then(res => {
-        if (200 == res.code) {
-          this.total = res.data.total;
-          this.list = res.data.rows;
-        }
-        this.listLoading = false;
-      });
-    },
-    retPage() {
-      //分页
-      this.getList();
-    },
-    handleFilter() {
-      this.listQuery.page = 1;
-      this.getList();
-    },
-    // 视频下载
-    async handleDownload(row) {
-      const { videoUrl, videoTitle, id, sku } = row;
-      this.$set(this.downloadingRows, id, true);
-      try {
-        const title = sku ? videoTitle + "_" + sku : videoTitle;
-        const res = await downloadUtil.videoDownload(videoUrl, title);
-        if (res) {
-          this.$message.success("下载成功");
-        }
-      } catch (error) {
-        this.$message.error(error.message);
-      } finally {
-        this.$set(this.downloadingRows, id, false);
-      }
-    },
-    // 视频播放
-    handlePlay(row) {
-      const { videoUrl, videoTitle } = row;
-      ``;
-      if (!videoUrl) {
-        this.$message.error("视频地址不存在");
-        return;
-      }
-      this.currentVideoUrl = videoUrl;
-      this.currentVideoTitle = videoTitle;
-      this.$refs.videoPlayer.show();
-    },
-    handlePlayerClose() {
-      this.currentVideoUrl = "";
-      this.currentVideoTitle = "";
-    },
-    handleDelete(row) {
-      //删除
-      this.$confirm("是否删除该视频?", "提示", {
-        confirmButtonText: "确定",
-        cancelButtonText: "取消",
-        type: "warning"
-      }).then(() => {
-        const { id } = row;
-        deleteOralVideoList([id]).then(() => {
-          this.$notify({
-            title: "成功",
-            message: "删除成功",
-            type: "success",
-            duration: 3000
-          });
-          const index = this.list.indexOf(row);
-          this.list.splice(index, 1);
-        });
-      });
-    }
-  }
-};
-</script>
-
-<style rel="stylesheet/scss" lang="scss" scoped>
-@import "@/styles/layout.scss";
-.button {
-  .el-button {
-    margin-bottom: 10px;
-    &:last-child {
-      margin-bottom: 0;
-    }
-  }
-}
-.video {
-  position: relative;
-  display: flex;
-  align-content: center;
-  justify-content: center;
-  &:hover {
-    i {
-      opacity: 1;
-    }
-    .video-img {
-      opacity: 0.6;
-    }
-  }
-  &-img {
-    width: 60px;
-    height: 60px;
-    border: 1px solid #eee;
-    object-fit: cover;
-    cursor: pointer;
-  }
-  i {
-    width: 60px;
-    height: 60px;
-    position: absolute;
-    top: 85%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    font-size: 20px;
-    font-weight: bold;
-    color: #fff;
-    opacity: 0;
-    cursor: pointer;
-  }
-}
-
-.status-tag {
-  display: inline-block;
-  width: 8px;
-  height: 8px;
-  border-radius: 50%;
-  margin-right: 6px;
-}
-
-.status-pending {
-  background-color: #e6a23c;
-}
-
-.status-success {
-  background-color: #67c23a;
-}
-
-.status-error {
-  background-color: #f56c6c;
-}
-</style>