康鲁杰
2025-04-18 044d520a74256e435e55f215060bf06bc7442d7f
easegen-front/src/views/myCourse/index.vue
@@ -147,7 +147,22 @@
  </ContentWrap>
  <!-- 视频播放弹框 -->
  <videoDialog ref="videoRef" />
  <el-dialog
    v-model="videoPlayDialogVisible"
    title="视频预览"
    width="60%"
    @close="handleVideoPlayClose"
  >
    <div class="video-play-container">
      <video
        ref="currentPlayVideo"
        v-if="currentPlayUrl"
        :src="currentPlayUrl"
        controls
        class="play-video"
      ></video>
    </div>
  </el-dialog>
  <!-- 字幕生成弹框 -->
  <el-dialog
@@ -276,24 +291,11 @@
  <!-- 片头片尾设置弹框 -->
  <el-dialog
    v-model="headerFooterDialogVisible"
    title="片头片尾设置"
    title="片头片尾"
    width="70%"
    @close="pauseAllVideos('headerFooter')"
  >
    <el-tabs v-model="activeTab">
      <el-tab-pane label="设置片头片尾" name="setting">
        <el-form :model="headerFooterForm" label-width="120px">
          <el-form-item label="片头视频">
            <UploadFile v-model="headerFooterForm.titles" :fileType="['mp4']" :limit="1" @on-success="handleFileSuccess('audition', $event)"/>
          </el-form-item>
          <el-form-item label="片尾视频">
            <UploadFile v-model="headerFooterForm.trailer" :fileType="['mp4']" :limit="1" @on-success="handleFileSuccess1('audition', $event)"/>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="applyHeaderFooter" :loading="applyingHeaderFooter">保存设置</el-button>
          </el-form-item>
        </el-form>
      </el-tab-pane>
      <el-tab-pane label="合成视频" name="merge">
        <el-form :model="formData1" label-width="120px">
          <el-form-item label="视频格式">
@@ -305,49 +307,81 @@
          <el-row :gutter="20">
            <el-col :span="12">
              <el-form-item label="片头视频">
                <div class="video-container">
                  <video
                    v-if="formData1.value.titles"
                    :src="formData1.value.titles"
                    controls
                    class="preview-video"
                    @error="handleVideoError('titles')"
                  ></video>
                  <div v-else class="no-video">暂无片头视频</div>
                <div class="video-select-container">
                  <div class="video-grid">
                    <div
                      v-for="item in titlesList"
                      :key="item.id"
                      class="video-card"
                      :class="{ 'is-selected': formData1.value.titles === item.url }"
                      @click="handleTitlesSelect(item)"
                    >
                      <div class="video-thumbnail">
                        <video
                          :src="item.url"
                          class="thumbnail-video"
                          controls
                        ></video>
                      </div>
                      <div class="video-info">
                        <span class="video-name">{{ item.name }}</span>
                        <el-icon v-if="formData1.value.titles === item.url" class="selected-icon"><Check /></el-icon>
                      </div>
                    </div>
                  </div>
                </div>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="片尾视频">
                <div class="video-container">
                  <video
                    v-if="formData1.value.trailer"
                    :src="formData1.value.trailer"
                    controls
                    class="preview-video"
                    @error="handleVideoError('trailer')"
                  ></video>
                  <div v-else class="no-video">暂无片尾视频</div>
                <div class="video-select-container">
                  <div class="video-grid">
                    <div
                      v-for="item in trailerList"
                      :key="item.id"
                      class="video-card"
                      :class="{ 'is-selected': formData1.value.trailer === item.url }"
                      @click="handleTrailerSelect(item)"
                    >
                      <div class="video-thumbnail">
                        <video
                          :src="item.url"
                          class="thumbnail-video"
                          controls
                        ></video>
                      </div>
                      <div class="video-info">
                        <span class="video-name">{{ item.name }}</span>
                        <el-icon v-if="formData1.value.trailer === item.url" class="selected-icon"><Check /></el-icon>
                      </div>
                    </div>
                  </div>
                </div>
              </el-form-item>
            </el-col>
          </el-row>
          <el-form-item>
            <el-button
              type="primary"
              @click="hecheng"
              :loading="applyingHeaderFooter"
              :disabled="!formData1.value?.titles || !formData1.value?.trailer"
            >
              开始合成
            </el-button>
            <span v-if="!formData1.value?.titles || !formData1.value?.trailer" class="ml-10px text-red-500">
              请先设置片头和片尾视频
            </span>
            <div style="display: flex; width: 100%;">
              <div style="flex: 1;">
                <span v-if="!formData1.value?.titles || !formData1.value?.trailer" class="text-red-500">
                  请先选择片头和片尾视频
                </span>
              </div>
              <div style="margin-right: 165px;">
                <el-button
                  type="primary"
                  @click="hecheng"
                  :loading="applyingHeaderFooter"
                  :disabled="!formData1.value?.titles || !formData1.value?.trailer"
                >
                  开始合成
                </el-button>
              </div>
            </div>
          </el-form-item>
        </el-form>
      </el-tab-pane>
      <el-tab-pane label="预览与下载" name="preview">
      <el-tab-pane label="预览下载" name="preview">
        <el-row :gutter="20">
          <el-col :span="12">
            <div class="preview-section">
@@ -436,9 +470,9 @@
import axios from 'axios'
import { config } from '@/config/axios/config'
import {createVideo, createVideoMeger, videoMeger} from "@/api/pptTemplate";
import { ArrowDown } from '@element-plus/icons-vue'
import { ArrowDown, Check, VideoPlay, VideoPause } from '@element-plus/icons-vue'
import { ElMessageBox } from 'element-plus'
import { listFile } from '@/api/system/file'
const router = useRouter()
const message = useMessage()
const { t } = useI18n()
@@ -492,7 +526,7 @@
// 片头片尾弹框相关
const headerFooterDialogVisible = ref(false)
const activeTab = ref('setting')
const activeTab = ref('merge')
const headerFooterForm = reactive({
  id: null as number | null,
  titles: '',
@@ -500,16 +534,13 @@
})
const applyingHeaderFooter = ref(false)
const handleFileSuccess = (fileType,response) => {
  if (fileType === 'audition') {
    headerFooterForm.titles = response.data
  }
};
const handleFileSuccess1 = (fileType,response) => {
  if (fileType === 'audition') {
    headerFooterForm.trailer = response.data
  }
};
const titlesList = ref([])
const trailerList = ref([])
// 视频播放相关
const currentPlayUrl = ref('')
const videoRefs = ref<HTMLVideoElement[]>([])
// 获取视频列表
const getList = async () => {
  loading.value = true
@@ -1005,6 +1036,31 @@
  polling.value = false
}
// 获取片头片尾列表
const getTitlesTrailerList = async () => {
  try {
    // 获取片头列表
    const titlesRes = await listFile({ type: 1 })
    titlesList.value = titlesRes.list || []
    // 获取片尾列表
    const trailerRes = await listFile({ type: 2 })
    trailerList.value = trailerRes.list || []
  } catch (error) {
    console.error('获取片头片尾列表失败:', error)
  }
}
// 处理片头选择
const handleTitlesSelect = (item) => {
  formData1.value.titles = item.url
}
// 处理片尾选择
const handleTrailerSelect = (item) => {
  formData1.value.trailer = item.url
}
// 处理片头片尾按钮点击
const handleHeaderFooter = async (row) => {
  headerFooterForm.id = row.id
@@ -1013,7 +1069,8 @@
  headerFooterForm.trailer = details.trailer || ''
  formData1.value = details
  headerFooterDialogVisible.value = true
  activeTab.value = 'setting'
  activeTab.value = 'merge'
  await getTitlesTrailerList()
}
// 应用片头片尾设置
@@ -1041,6 +1098,16 @@
const hecheng = async () => {
  try {
    applyingHeaderFooter.value = true
    // 1. 先保存配置
    const saveParams = {
      id: headerFooterForm.id,
      titles: formData1.value.titles,
      trailer: formData1.value.trailer
    }
    await pptTemplateApi.createVideo(saveParams)
    // 2. 再调用合成接口
    let obj = {}
    if (formData1.isvideo == '2') {
      obj = {
@@ -1108,6 +1175,14 @@
onMounted(() => {
  getList()
})
// 播放预览
const playPreview = (type: 'titles' | 'trailer') => {
  const video = type === 'titles' ? formData1.value.titles : formData1.value.trailer
  if (video) {
    window.open(video, '_blank')
  }
}
</script>
<style scoped>
@@ -1188,6 +1263,7 @@
  padding: 20px;
  border-radius: 4px;
  height: 100%;
  min-height: 500px;
}
.preview-section h4 {
@@ -1197,7 +1273,7 @@
.video-container {
  width: 100%;
  height: 200px;
  height: 400px;
  background: #000;
  border-radius: 4px;
  overflow: hidden;
@@ -1223,61 +1299,109 @@
  background: #f5f7fa;
}
.button-group {
  display: flex;
  justify-content: center;
  gap: 10px;
}
.ml-10px {
  margin-left: 10px;
}
.text-red-500 {
  color: #f56c6c;
}
.subtitle-dialog :deep(.el-dialog__body) {
  padding: 20px;
  min-height: 500px;
}
.preview-section {
  background: #f5f7fa;
  padding: 20px;
  border-radius: 4px;
  height: 100%;
  min-height: 400px;
}
.video-container {
  width: 100%;
  height: 300px;
  background: #000;
  border-radius: 4px;
  overflow: hidden;
  margin-bottom: 15px;
  position: relative;
}
.preview-video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #000;
}
.no-video {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #909399;
  font-size: 14px;
  background: #f5f7fa;
}
.video-container1{
  height: 490px !important;
  height: 700px !important;
}
.video-select-container {
  display: flex;
  flex-direction: column;
  gap: 15px;
}
.video-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 20px;
  padding: 16px;
  background: #f5f7fa;
  border-radius: 8px;
  max-height: 400px;
  overflow-y: auto;
}
.video-card {
  position: relative;
  background: #fff;
  border-radius: 8px;
  overflow: hidden;
  cursor: pointer;
  transition: all 0.3s ease;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.video-card.is-selected {
  box-shadow: 0 4px 16px 0 rgba(64, 158, 255, 0.2);
}
.video-thumbnail {
  position: relative;
  width: 100%;
  padding-top: 56.25%; /* 16:9 比例 */
  background: #000;
  overflow: hidden;
}
.thumbnail-video {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.video-info {
  padding: 12px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: #fff;
}
.video-name {
  flex: 1;
  font-size: 14px;
  color: #303133;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-right: 8px;
}
.selected-icon {
  color: var(--el-color-primary);
  font-size: 18px;
  flex-shrink: 0;
}
/* 自定义滚动条样式 */
.video-grid::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
.video-grid::-webkit-scrollbar-thumb {
  background: #c0c4cc;
  border-radius: 3px;
}
.video-grid::-webkit-scrollbar-track {
  background: #f5f7fa;
  border-radius: 3px;
}
.video-play-container {
  width: 100%;
  background: #000;
  border-radius: 4px;
  overflow: hidden;
  aspect-ratio: 16/9;
}
.play-video {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
</style>