VideoList.vue 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. <template>
  2. <el-card class="dr-task" body-class="task-card" shadow="never">
  3. <template #header>
  4. 视频任务
  5. <!-- TODO @fan:看看,怎么优化下这个样子哈。 -->
  6. <!-- <el-button @click="handleViewPublic">视频作品</el-button>-->
  7. </template>
  8. <!-- 视频列表 -->
  9. <div class="task-video-list" ref="videoListRef">
  10. <VideoCard
  11. v-for="video in videoList"
  12. :key="video.id"
  13. :detail="video"
  14. @on-btn-click="handleVideoButtonClick"
  15. />
  16. </div>
  17. <div class="task-video-pagination">
  18. <Pagination
  19. :total="pageTotal"
  20. v-model:page="queryParams.pageNo"
  21. v-model:limit="queryParams.pageSize"
  22. @pagination="getVideoList"
  23. />
  24. </div>
  25. </el-card>
  26. <!-- 视频详情 -->
  27. <VideoDetail
  28. :show="isShowVideoDetail"
  29. :id="showVideoDetailId"
  30. @handle-drawer-close="handleDetailClose"
  31. />
  32. </template>
  33. <script setup lang="ts">
  34. import {
  35. VideoApi,
  36. VideoVO
  37. } from '@/api/ai/video'
  38. import VideoDetail from './VideoDetail.vue'
  39. import VideoCard from './VideoCard.vue'
  40. import { ElLoading, LoadingOptionsResolved } from 'element-plus'
  41. import { AiVideoStatusEnum } from '@/views/ai/utils/constants'
  42. import download from '@/utils/download'
  43. const message = useMessage() // 消息弹窗
  44. const router = useRouter() // 路由
  45. // 视频分页相关的参数
  46. const queryParams = reactive({
  47. pageNo: 1,
  48. pageSize: 10
  49. })
  50. const pageTotal = ref<number>(0) // page size
  51. const videoList = ref<VideoVO[]>([]) // video 列表
  52. const videoListLoadingInstance = ref<any>() // video 列表是否正在加载中
  53. const videoListRef = ref<any>() // ref
  54. // 视频轮询相关的参数(正在生成中的)
  55. const inProgressVideoMap = ref<{}>({}) // 监听的 video 映射,一般是生成中(需要轮询),key 为 video 编号,value 为 video
  56. const inProgressTimer = ref<any>() // 生成中的 video 定时器,轮询生成进展
  57. // 视频详情相关的参数
  58. const isShowVideoDetail = ref<boolean>(false) // 视频详情是否展示
  59. const showVideoDetailId = ref<number>(0) // 视频详情的视频编号
  60. /** 处理查看视频作品 */
  61. const handleViewPublic = () => {
  62. router.push({
  63. name: 'AiVideoSquare'
  64. })
  65. }
  66. /** 查看视频的详情 */
  67. const handleDetailOpen = async () => {
  68. isShowVideoDetail.value = true
  69. }
  70. /** 关闭视频的详情 */
  71. const handleDetailClose = async () => {
  72. isShowVideoDetail.value = false
  73. }
  74. /** 获得 video 视频列表 */
  75. const getVideoList = async () => {
  76. try {
  77. // 1. 加载视频列表
  78. videoListLoadingInstance.value = ElLoading.service({
  79. target: videoListRef.value,
  80. text: '加载中...'
  81. } as LoadingOptionsResolved)
  82. const { list, total } = await VideoApi.getVideoPageMy(queryParams)
  83. videoList.value = list
  84. pageTotal.value = total
  85. // 2. 计算需要轮询的视频
  86. const newWatVideos = {}
  87. videoList.value.forEach((item) => {
  88. if (item.status === AiVideoStatusEnum.IN_PROGRESS) {
  89. newWatVideos[item.id] = item
  90. }
  91. })
  92. inProgressVideoMap.value = newWatVideos
  93. } finally {
  94. // 关闭正在“加载中”的 Loading
  95. if (videoListLoadingInstance.value) {
  96. videoListLoadingInstance.value.close()
  97. videoListLoadingInstance.value = null
  98. }
  99. }
  100. }
  101. /** 轮询生成中的 video 列表 */
  102. const refreshWatchVideos = async () => {
  103. const videoIds = Object.keys(inProgressVideoMap.value).map(Number)
  104. if (videoIds.length == 0) {
  105. return
  106. }
  107. const list = (await VideoApi.getVideoListMyByIds(videoIds)) as VideoVO[]
  108. const newWatchVideos = {}
  109. list.forEach((video) => {
  110. if (video.status === AiVideoStatusEnum.IN_PROGRESS) {
  111. newWatchVideos[video.id] = video
  112. } else {
  113. const index = videoList.value.findIndex((oldVideo) => video.id === oldVideo.id)
  114. if (index >= 0) {
  115. // 更新 videoList
  116. videoList.value[index] = video
  117. }
  118. }
  119. })
  120. inProgressVideoMap.value = newWatchVideos
  121. }
  122. /** 视频的点击事件 */
  123. const handleVideoButtonClick = async (type: string, videoDetail: VideoVO) => {
  124. // 详情
  125. if (type === 'more') {
  126. showVideoDetailId.value = videoDetail.id
  127. await handleDetailOpen()
  128. return
  129. }
  130. // 删除
  131. if (type === 'delete') {
  132. await message.confirm(`是否删除照片?`)
  133. await VideoApi.deleteVideoMy(videoDetail.id)
  134. await getVideoList()
  135. message.success('删除成功!')
  136. return
  137. }
  138. // 下载
  139. if (type === 'download') {
  140. window.open(videoDetail.videoUrl)
  141. // await download.video({ url: videoDetail.videoUrl })
  142. return
  143. }
  144. // 重新生成
  145. if (type === 'regeneration') {
  146. // await emits('onRegeneration', videoDetail)
  147. return
  148. }
  149. }
  150. defineExpose({ getVideoList }) // 暴露组件方法
  151. const emits = defineEmits(['onRegeneration'])
  152. /** 组件挂在的时候 */
  153. onMounted(async () => {
  154. // 获取 video 列表
  155. await getVideoList()
  156. // 自动刷新 video 列表
  157. inProgressTimer.value = setInterval(async () => {
  158. await refreshWatchVideos()
  159. }, 1000 * 3)
  160. })
  161. /** 组件取消挂在的时候 */
  162. onUnmounted(async () => {
  163. if (inProgressTimer.value) {
  164. clearInterval(inProgressTimer.value)
  165. }
  166. })
  167. </script>
  168. <style lang="scss">
  169. .dr-task {
  170. width: 100%;
  171. height: 100%;
  172. }
  173. .task-card {
  174. margin: 0;
  175. padding: 0;
  176. height: 100%;
  177. position: relative;
  178. }
  179. .task-video-list {
  180. position: relative;
  181. display: flex;
  182. flex-direction: row;
  183. flex-wrap: wrap;
  184. align-content: flex-start;
  185. height: 100%;
  186. overflow: auto;
  187. padding: 20px 20px 140px;
  188. box-sizing: border-box; /* 确保内边距不会增加高度 */
  189. > div {
  190. margin-right: 20px;
  191. margin-bottom: 20px;
  192. }
  193. > div:last-of-type {
  194. //margin-bottom: 100px;
  195. }
  196. }
  197. .task-video-pagination {
  198. position: absolute;
  199. bottom: 60px;
  200. height: 50px;
  201. line-height: 90px;
  202. width: 100%;
  203. z-index: 999;
  204. background-color: #ffffff;
  205. display: flex;
  206. flex-direction: row;
  207. justify-content: center;
  208. align-items: center;
  209. }
  210. </style>