index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. <template>
  2. <div class="table-box">
  3. <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listVideoStableApi">
  4. <!-- 表格 header 按钮 -->
  5. <template #tableHeader="scope">
  6. <el-button type="primary" v-auth="['demo:videoStable:add']" icon="CirclePlus" @click="openDialog(1, '视频去抖动新增')"> 新增 </el-button>
  7. <!-- <el-button type="primary" v-auth="['demo:videoStable:import']" icon="Upload" plain @click="batchAdd"> 导入</el-button>-->
  8. <!-- <el-button type="primary" v-auth="['demo:videoStable:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button>-->
  9. <el-button
  10. type="danger"
  11. v-auth="['demo:videoStable:remove']"
  12. icon="Delete"
  13. plain
  14. :disabled="!scope.isSelected"
  15. @click="batchDelete(scope.selectedListIds)"
  16. >
  17. 批量删除
  18. </el-button>
  19. </template>
  20. <!-- 表格操作 -->
  21. <template #operation="scope">
  22. <el-button
  23. type="primary"
  24. link
  25. icon="View"
  26. @click="startVideoStable(scope.row)"
  27. v-if="scope.row.status == '0' || scope.row.status == '3' || scope.row.status == '4'"
  28. >
  29. 开始
  30. </el-button>
  31. <el-popconfirm title="确定终止此任务吗?" @confirm="stopVideoStable(scope.row)" v-if="scope.row.status == '1'">
  32. <template #reference>
  33. <el-button type="primary" link icon="View"> 终止 </el-button>
  34. </template>
  35. </el-popconfirm>
  36. <el-button type="primary" link icon="View" @click="compareVideoStable(scope.row)" v-if="scope.row.status == '2'"> 预览 </el-button>
  37. <el-button type="primary" link icon="View" v-auth="['demo:videoStable:query']" @click="openDialog(3, '视频去抖动查看', scope.row)">
  38. 查看
  39. </el-button>
  40. <!-- <el-button type="primary" link icon="EditPen" v-auth="['demo:videoStable:edit']" @click="openDialog(2, '视频去抖动编辑', scope.row)">
  41. 编辑
  42. </el-button> -->
  43. <el-button type="primary" link icon="Delete" v-auth="['demo:videoStable:remove']" @click="deleteVideoStable(scope.row)"> 删除 </el-button>
  44. </template>
  45. </ProTable>
  46. <FormDialog ref="formDialogRef" />
  47. <ImportExcel ref="dialogRef" />
  48. <el-dialog v-model="dialogVisible" :title="dialogTitle" width="80%">
  49. <el-form :inline="true">
  50. <el-form-item label="帧率">
  51. <el-select v-model="imageFps" placeholder="选择帧率" style="width: 200px" @change="changeFps">
  52. <el-option label="0" value="0"></el-option>
  53. <el-option label="5" value="5"></el-option>
  54. <el-option label="15" value="15"></el-option>
  55. <el-option label="30" value="30"></el-option>
  56. </el-select>
  57. </el-form-item>
  58. <el-form-item label="跳转至">
  59. <el-input v-model="newImageIdx" type="number" style="width: 100px" />
  60. <el-button type="primary" @click="confirmNewImageIdx" style="margin-left: 10px">确认</el-button>
  61. </el-form-item>
  62. </el-form>
  63. <div class="image-dialog">
  64. <el-image :src="imageUrlList[imageIdx].inputUrl" style="width: 45%"></el-image>
  65. <el-image :src="imageUrlList[imageIdx].outputUrl" style="width: 45%"></el-image>
  66. </div>
  67. <div class="image-dialog-btn" v-if="imageFps == 0">
  68. <el-button type="primary" @click="pre_picture" :disabled="imageIdx <= 0">上一个</el-button>
  69. <el-button type="primary" @click="next_picture" :disabled="imageIdx >= imageUrlList.length - 1">下一个</el-button>
  70. </div>
  71. </el-dialog>
  72. </div>
  73. </template>
  74. <script setup lang="tsx" name="VideoStable">
  75. import { ref, reactive } from 'vue'
  76. import { useHandleData } from '@/hooks/useHandleData'
  77. import { useDownload } from '@/hooks/useDownload'
  78. import { ElMessageBox, ElMessage } from 'element-plus'
  79. import ProTable from '@/components/ProTable/index.vue'
  80. import ImportExcel from '@/components/ImportExcel/index.vue'
  81. import FormDialog from '@/components/FormDialog/index.vue'
  82. import { ProTableInstance, ColumnProps, EnumProps } from '@/components/ProTable/interface'
  83. import {
  84. listVideoStableApi,
  85. delVideoStableApi,
  86. addVideoStableApi,
  87. updateVideoStableApi,
  88. importTemplateApi,
  89. importVideoStableDataApi,
  90. exportVideoStableApi,
  91. getVideoStableApi,
  92. startVideoStableApi,
  93. stopVideoStableApi,
  94. // getCompareImageApi,
  95. // getCompareImageCountApi,
  96. getImagesApi
  97. } from '@/api/modules/demo/videoStable'
  98. const dialogVisible = ref(false)
  99. const imageIdx = ref(0)
  100. const imageUrlList: any = ref([])
  101. const newImageIdx = ref('')
  102. // 直接缓存所有图片
  103. const dialogTitle = ref('')
  104. const imageFps = ref(0)
  105. const intervalChangeFps: any = ref()
  106. const changeFps = () => {
  107. console.log('changeFps')
  108. if (intervalChangeFps.value) {
  109. clearInterval(intervalChangeFps.value)
  110. }
  111. if (imageFps.value == 0) {
  112. return
  113. }
  114. imageIdx.value = 1
  115. intervalChangeFps.value = setInterval(() => {
  116. next_picture()
  117. }, 1000 / imageFps.value)
  118. }
  119. const startVideoStable = async (params: any) => {
  120. const res = await startVideoStableApi(params.id)
  121. if (res.code === 200) {
  122. ElMessage.success('任务已经开始,请等待')
  123. } else {
  124. ElMessage.error('任务开始失败,请检查!')
  125. }
  126. proTable.value?.getTableList()
  127. }
  128. const stopVideoStable = async (params: any) => {
  129. const res = await stopVideoStableApi(params.id)
  130. if (res.code === 200) {
  131. ElMessage.success('任务终止成功')
  132. } else {
  133. ElMessage.error('任务终止失败!')
  134. }
  135. proTable.value?.getTableList()
  136. }
  137. const confirmNewImageIdx = () => {
  138. const val = parseInt(newImageIdx.value)
  139. if (val > 0 && val <= imageUrlList.value.length) {
  140. imageIdx.value = val - 1
  141. } else {
  142. ElMessageBox.alert('跳转索引有误,请检查!')
  143. }
  144. }
  145. const compareVideoStable = async (params: any) => {
  146. console.log('compareVideoStable')
  147. const data: any = await getImagesApi(params.inputOssId)
  148. imageUrlList.value = data.data
  149. imageIdx.value = 0
  150. dialogTitle.value = '预览: 第' + (imageIdx.value + 1) + '张图片 共' + imageUrlList.value.length + '张图片'
  151. dialogVisible.value = true
  152. }
  153. const next_picture = async () => {
  154. if (imageIdx.value < imageUrlList.value.length - 1) {
  155. imageIdx.value = imageIdx.value + 1
  156. }
  157. dialogTitle.value = '预览: 第' + (imageIdx.value + 1) + '张图片 共' + imageUrlList.value.length + '张图片'
  158. }
  159. const pre_picture = async () => {
  160. if (imageIdx.value > 0) {
  161. imageIdx.value = imageIdx.value - 1
  162. }
  163. dialogTitle.value = '预览: 第' + (imageIdx.value + 1) + '张图片 共' + imageUrlList.value.length + '张图片'
  164. }
  165. // ProTable 实例
  166. const proTable = ref<ProTableInstance>()
  167. // 删除视频去抖动信息
  168. const deleteVideoStable = async (params: any) => {
  169. await useHandleData(delVideoStableApi, params.id, '删除任务【' + params.name + '】?')
  170. proTable.value?.getTableList()
  171. }
  172. // 批量删除视频去抖动信息
  173. const batchDelete = async (ids: string[]) => {
  174. await useHandleData(delVideoStableApi, ids, '删除所选任务?')
  175. proTable.value?.clearSelection()
  176. proTable.value?.getTableList()
  177. }
  178. // 导出视频去抖动列表
  179. const downloadFile = async () => {
  180. ElMessageBox.confirm('确认导出视频去抖动数据?', '温馨提示', { type: 'warning' }).then(() =>
  181. useDownload(exportVideoStableApi, '视频去抖动列表', proTable.value?.searchParam)
  182. )
  183. }
  184. // 批量添加视频去抖动
  185. const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
  186. const batchAdd = () => {
  187. const params = {
  188. title: '视频去抖动',
  189. tempApi: importTemplateApi,
  190. importApi: importVideoStableDataApi,
  191. getTableList: proTable.value?.getTableList
  192. }
  193. dialogRef.value?.acceptParams(params)
  194. }
  195. const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
  196. // 打开弹框的功能
  197. const openDialog = async (type: number, title: string, row?: any) => {
  198. let res = { data: {} }
  199. if (row?.id) {
  200. res = await getVideoStableApi(row?.id || null)
  201. }
  202. // 重置表单
  203. setItemsOptions()
  204. const params = {
  205. title,
  206. width: 580,
  207. isEdit: type !== 3,
  208. itemsOptions: itemsOptions,
  209. model: type == 1 ? {} : res.data,
  210. api: type == 1 ? addVideoStableApi : updateVideoStableApi,
  211. getTableList: proTable.value?.getTableList
  212. }
  213. formDialogRef.value?.openDialog(params)
  214. }
  215. const statusEnums: EnumProps[] = [
  216. {
  217. label: '未开始',
  218. value: '0',
  219. disabled: false,
  220. tagType: 'default'
  221. },
  222. {
  223. label: '进行中',
  224. value: '1',
  225. disabled: false,
  226. tagType: 'primary'
  227. },
  228. {
  229. label: '完成',
  230. value: '2',
  231. disabled: false,
  232. tagType: 'success'
  233. },
  234. {
  235. label: '失败',
  236. value: '3',
  237. disabled: false,
  238. tagType: 'danger'
  239. },
  240. {
  241. label: '已中断',
  242. value: '4',
  243. disabled: false,
  244. tagType: 'default'
  245. }
  246. ]
  247. // 表格配置项
  248. const columns = reactive<ColumnProps<any>[]>([
  249. { type: 'selection', fixed: 'left', width: 70 },
  250. { prop: 'id', label: '主键ID', width: 180 },
  251. {
  252. prop: 'name',
  253. label: '视频名称',
  254. search: {
  255. el: 'input'
  256. },
  257. width: 150
  258. },
  259. {
  260. prop: 'status',
  261. label: '任务状态',
  262. search: {
  263. el: 'select'
  264. },
  265. width: 100,
  266. tag: true,
  267. enum: statusEnums
  268. },
  269. {
  270. prop: 'startTime',
  271. label: '开始时间',
  272. // search: {
  273. // el: 'date-picker',
  274. // props: {
  275. // type: 'datetimerange',
  276. // valueFormat: 'YYYY-MM-DD HH:mm:ss'
  277. // }
  278. // },
  279. width: 180
  280. },
  281. {
  282. prop: 'endTime',
  283. label: '结束时间',
  284. // search: {
  285. // el: 'date-picker',
  286. // props: {
  287. // type: 'datetimerange',
  288. // valueFormat: 'YYYY-MM-DD HH:mm:ss'
  289. // }
  290. // },
  291. width: 180
  292. },
  293. {
  294. prop: 'costSecond',
  295. label: '耗时',
  296. search: {
  297. el: 'input'
  298. },
  299. width: 80
  300. },
  301. {
  302. prop: 'log',
  303. label: '日志',
  304. width: 120
  305. },
  306. {
  307. prop: 'block_size',
  308. label: '网格大小',
  309. width: 120
  310. },
  311. {
  312. prop: 'radius',
  313. label: '扩散半径',
  314. width: 120
  315. },
  316. {
  317. prop: 'buffer_size',
  318. label: '缓冲区大小',
  319. width: 120
  320. },
  321. {
  322. prop: 'cornerquality',
  323. label: '角点质量',
  324. width: 130
  325. },
  326. {
  327. prop: 'cornerminDistance',
  328. label: '角点最小距离',
  329. width: 180
  330. },
  331. {
  332. prop: 'lklevel',
  333. label: '光流层级',
  334. width: 120
  335. },
  336. {
  337. prop: 'lkwinSiz',
  338. label: '光流窗口大小',
  339. width: 120
  340. },
  341. {
  342. prop: 'remarks',
  343. label: '备注',
  344. search: {
  345. el: 'input'
  346. },
  347. width: 120
  348. },
  349. {
  350. prop: 'operation',
  351. label: '操作',
  352. width: 230,
  353. fixed: 'right'
  354. }
  355. ])
  356. // 表单配置项
  357. let itemsOptions: ProForm.ItemsOptions[] = []
  358. const setItemsOptions = () => {
  359. itemsOptions = [
  360. {
  361. label: '任务名称',
  362. prop: 'name',
  363. rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
  364. compOptions: {
  365. placeholder: '请输入任务名称'
  366. }
  367. },
  368. {
  369. label: '图片集压缩包',
  370. prop: 'inputOssId',
  371. rules: [{ required: true, message: '图片集压缩包不能为空', trigger: 'blur' }],
  372. compOptions: {
  373. elTagName: 'file-upload',
  374. fileSize: 4096,
  375. fileType: ['zip'],
  376. placeholder: '请上传图片集压缩包'
  377. }
  378. },
  379. {
  380. label: '网格大小',
  381. prop: 'block_size',
  382. rules: [],
  383. compOptions: {
  384. type: 'input',
  385. clearable: true,
  386. placeholder: '默认50'
  387. }
  388. },
  389. {
  390. label: '扩散半径',
  391. prop: 'radius',
  392. rules: [],
  393. compOptions: {
  394. type: 'input',
  395. clearable: true,
  396. placeholder: '默认500'
  397. }
  398. },
  399. {
  400. label: '缓冲区大小',
  401. prop: 'buffer_size',
  402. rules: [],
  403. compOptions: {
  404. type: 'input',
  405. clearable: true,
  406. placeholder: '默认200'
  407. }
  408. },
  409. {
  410. label: '角点质量',
  411. prop: 'cornerquality',
  412. rules: [],
  413. compOptions: {
  414. type: 'input',
  415. clearable: true,
  416. placeholder: '默认0.2'
  417. }
  418. },
  419. {
  420. label: '角点最小距离',
  421. prop: 'cornerminDistance',
  422. rules: [],
  423. compOptions: {
  424. type: 'input',
  425. clearable: true,
  426. placeholder: '默认5'
  427. }
  428. },
  429. {
  430. label: '光流层级',
  431. prop: 'lklevel',
  432. rules: [],
  433. compOptions: {
  434. type: 'input',
  435. clearable: true,
  436. placeholder: '默认3'
  437. }
  438. },
  439. {
  440. label: '光流窗口大小',
  441. prop: 'lkwinSiz',
  442. rules: [],
  443. compOptions: {
  444. type: 'input',
  445. clearable: true,
  446. placeholder: '默认15'
  447. }
  448. },
  449. {
  450. label: '备注',
  451. prop: 'remarks',
  452. rules: [
  453. {
  454. required: false,
  455. message: '备注不能为空',
  456. trigger: 'blur'
  457. }
  458. ],
  459. compOptions: {
  460. placeholder: '请输入备注'
  461. }
  462. }
  463. ]
  464. }
  465. </script>
  466. <style lang="scss" scoped>
  467. .image-dialog {
  468. display: flex;
  469. justify-content: center;
  470. .el-image {
  471. margin-right: 20px;
  472. margin-bottom: 20px;
  473. }
  474. }
  475. .image-dialog-btn {
  476. display: flex;
  477. justify-content: center;
  478. margin-top: 20px;
  479. }
  480. </style>