Browse Source

视频预览

master
han\hanst 1 week ago
parent
commit
d56ce21d18
  1. 12
      src/api/oss/oss.js
  2. 96
      src/views/modules/longtron/production-plan-home-order.vue
  3. 96
      src/views/modules/longtron/production-plan-renovation-order.vue

12
src/api/oss/oss.js

@ -1,5 +1,10 @@
import { createAPI } from "@/utils/httpRequest.js"; import { createAPI } from "@/utils/httpRequest.js";
const resolveApiBaseUrl = () => {
const rawBaseUrl = process.env.NODE_ENV !== 'production' && process.env.OPEN_PROXY ? '/proxyApi/' : window.SITE_CONFIG.baseUrl
return String(rawBaseUrl || '').replace(/\/$/, '')
}
export const updateOssRef = data => createAPI(`/sys/oss/updateOssRef`,'post',data) export const updateOssRef = data => createAPI(`/sys/oss/updateOssRef`,'post',data)
@ -21,6 +26,13 @@ export const previewOssFileById = (data) => createAPI(`/oss/${data.id}`,'post',7
export const previewOssFileById2 = (data) => createAPI(`/oss/2/${data.id}`,'post',777) export const previewOssFileById2 = (data) => createAPI(`/oss/2/${data.id}`,'post',777)
export const getOssVideoStreamUrl = (id) => {
if (!id && id !== 0) {
return ''
}
return `${resolveApiBaseUrl()}/oss/video/${id}`
}
export const ossUploadNoSaveOSS = (data) => createAPI(`/oss/ossUploadNoSaveOSS`,'post',data) export const ossUploadNoSaveOSS = (data) => createAPI(`/oss/ossUploadNoSaveOSS`,'post',data)
export const uploadWithTtpe = data => createAPI(`/oss/uploadWithTtpe`,'post',data) export const uploadWithTtpe = data => createAPI(`/oss/uploadWithTtpe`,'post',data)

96
src/views/modules/longtron/production-plan-home-order.vue

@ -123,16 +123,16 @@
</div> </div>
<div class="logs-table-wrapper"> <div class="logs-table-wrapper">
<el-table :data="selectedOrderLogList" border size="small" class="detail-table" v-loading="detailLogLoading" height="295px"> <el-table :data="selectedOrderLogList" border size="small" class="detail-table" v-loading="detailLogLoading" height="295px">
<el-table-column prop="logTime" label="时间" width="160" align="center"></el-table-column>
<el-table-column prop="action" label="操作" width="95" align="center"></el-table-column>
<el-table-column prop="nodeName" label="节点" width="130" align="center">
<el-table-column prop="logTime" label="时间" min-width="160" align="center"></el-table-column>
<el-table-column prop="action" label="操作" min-width="95" align="center"></el-table-column>
<el-table-column prop="nodeName" label="节点" min-width="130" align="center">
<template slot-scope="scope">{{ scope.row.nodeName || scope.row.nodeCode || '-' }}</template> <template slot-scope="scope">{{ scope.row.nodeName || scope.row.nodeCode || '-' }}</template>
</el-table-column> </el-table-column>
<el-table-column prop="operatorName" label="操作人" width="100" align="center"></el-table-column>
<el-table-column prop="comment" label="备注" min-width="220" show-overflow-tooltip>
<el-table-column prop="operatorName" label="操作人" min-width="100" align="center"></el-table-column>
<!-- <el-table-column prop="comment" label="备注" min-width="220" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.comment || '-' }}</template> <template slot-scope="scope">{{ scope.row.comment || '-' }}</template>
</el-table-column>
<el-table-column label="影像" width="95" align="center">
</el-table-column>-->
<el-table-column label="影像" min-width="95" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<a <a
v-if="isMediaNodeLog(scope.row)" v-if="isMediaNodeLog(scope.row)"
@ -266,8 +266,8 @@
alt="media-thumb" alt="media-thumb"
@click="previewMediaFile(scope.row)"> @click="previewMediaFile(scope.row)">
<video <video
v-else-if="resolveMediaKind(scope.row) === 'video' && scope.row.previewUrl"
:src="scope.row.previewUrl"
v-else-if="resolveMediaKind(scope.row) === 'video'"
:src="getVideoStreamUrl(scope.row)"
class="media-thumb media-thumb-video" class="media-thumb media-thumb-video"
muted muted
playsinline playsinline
@ -306,10 +306,15 @@
@click.stop="closeMediaPreview"> @click.stop="closeMediaPreview">
<video <video
v-else-if="mediaPreviewType === 'video'" v-else-if="mediaPreviewType === 'video'"
ref="mediaPreviewVideo"
:key="mediaPreviewUrl"
:src="mediaPreviewUrl" :src="mediaPreviewUrl"
class="media-preview-video" class="media-preview-video"
controls controls
autoplay autoplay
playsinline
preload="auto"
@error="handleMediaPreviewError"
@click.stop> @click.stop>
</video> </video>
</div> </div>
@ -318,7 +323,7 @@
<script> <script>
import { deleteHomeLiftOrder, finishHomeLiftOrder, getHomeLiftOrderList, getNodeAssigneeList, getNodeAssigneeUsers, getReportLogList, reportHomeLiftOrderNode, saveHomeLiftOrder, saveNodeAssignee } from '@/api/longchuang/productionPlan' import { deleteHomeLiftOrder, finishHomeLiftOrder, getHomeLiftOrderList, getNodeAssigneeList, getNodeAssigneeUsers, getReportLogList, reportHomeLiftOrderNode, saveHomeLiftOrder, saveNodeAssignee } from '@/api/longchuang/productionPlan'
import { previewOssFileById2, queryOssFilePlus } from '@/api/oss/oss'
import { getOssVideoStreamUrl, previewOssFileById2, queryOssFilePlus } from '@/api/oss/oss'
export default { export default {
name: 'ProductionPlanHomeOrder', name: 'ProductionPlanHomeOrder',
@ -466,7 +471,7 @@ export default {
})) }))
this.mediaFileList.forEach(item => { this.mediaFileList.forEach(item => {
const kind = this.resolveMediaKind(item) const kind = this.resolveMediaKind(item)
if (kind !== 'other') {
if (kind === 'image') {
this.loadMediaPreviewUrl(item, kind, false) this.loadMediaPreviewUrl(item, kind, false)
} }
}) })
@ -495,9 +500,17 @@ export default {
if (!fileRow) { if (!fileRow) {
return '' return ''
} }
const ext = fileRow.fileType || fileRow.fileSuffix || ''
const ext = fileRow.fileType || fileRow.fileSuffix || this.getExtFromFileName(fileRow.fileName || fileRow.newFileName || '')
return String(ext).replace(/^\./, '').toLowerCase() return String(ext).replace(/^\./, '').toLowerCase()
}, },
getExtFromFileName(fileName) {
const name = String(fileName || '')
const dotIndex = name.lastIndexOf('.')
if (dotIndex < 0 || dotIndex >= name.length - 1) {
return ''
}
return name.slice(dotIndex + 1)
},
resolveMediaKind(fileRow) { resolveMediaKind(fileRow) {
const ext = this.getMediaExt(fileRow) const ext = this.getMediaExt(fileRow)
const imageExtList = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'] const imageExtList = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']
@ -523,13 +536,26 @@ export default {
buildPreviewMimeType(fileRow, kind) { buildPreviewMimeType(fileRow, kind) {
const ext = this.getMediaExt(fileRow) const ext = this.getMediaExt(fileRow)
if (kind === 'image') { if (kind === 'image') {
if (ext === 'jpg') {
return 'image/jpeg'
const imageMimeMap = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
gif: 'image/gif',
bmp: 'image/bmp',
webp: 'image/webp'
} }
return ext ? `image/${ext}` : 'image/jpeg'
return imageMimeMap[ext] || 'image/jpeg'
} }
if (kind === 'video') { if (kind === 'video') {
return ext ? `video/${ext}` : 'video/mp4'
const videoMimeMap = {
mp4: 'video/mp4',
m4v: 'video/mp4',
webm: 'video/webm',
mov: 'video/quicktime',
avi: 'video/x-msvideo',
'3gp': 'video/3gpp'
}
return videoMimeMap[ext] || 'video/mp4'
} }
return 'application/octet-stream' return 'application/octet-stream'
}, },
@ -551,7 +577,9 @@ export default {
const mimeType = this.buildPreviewMimeType(fileRow, kind) const mimeType = this.buildPreviewMimeType(fileRow, kind)
try { try {
const { data } = await previewOssFileById2({ id: fileRow.id, fileType: mimeType }) const { data } = await previewOssFileById2({ id: fileRow.id, fileType: mimeType })
const blob = data instanceof Blob ? data : new Blob([data], { type: mimeType })
const sourceBlob = data instanceof Blob ? data : new Blob([data])
const blobType = mimeType || sourceBlob.type || 'application/octet-stream'
const blob = new Blob([sourceBlob], { type: blobType })
this.$set(fileRow, 'previewUrl', URL.createObjectURL(blob)) this.$set(fileRow, 'previewUrl', URL.createObjectURL(blob))
return true return true
} catch (e) { } catch (e) {
@ -563,6 +591,12 @@ export default {
this.$set(fileRow, 'previewLoading', false) this.$set(fileRow, 'previewLoading', false)
} }
}, },
getVideoStreamUrl(fileRow) {
if (!fileRow || !fileRow.id) {
return ''
}
return getOssVideoStreamUrl(fileRow.id)
},
async previewMediaFile(fileRow) { async previewMediaFile(fileRow) {
const kind = this.resolveMediaKind(fileRow) const kind = this.resolveMediaKind(fileRow)
if (kind === 'other') { if (kind === 'other') {
@ -573,6 +607,27 @@ export default {
this.$message.warning('文件信息不完整,无法预览') this.$message.warning('文件信息不完整,无法预览')
return return
} }
if (kind === 'video') {
const videoUrl = this.getVideoStreamUrl(fileRow)
if (!videoUrl) {
this.$message.warning('视频地址无效,无法预览')
return
}
this.mediaPreviewType = 'video'
this.mediaPreviewName = fileRow.fileName || fileRow.newFileName || ''
this.mediaPreviewUrl = videoUrl
this.mediaPreviewVisible = true
this.$nextTick(() => {
const previewVideo = this.$refs.mediaPreviewVideo
if (previewVideo && typeof previewVideo.play === 'function') {
const playPromise = previewVideo.play()
if (playPromise && typeof playPromise.catch === 'function') {
playPromise.catch(() => {})
}
}
})
return
}
const loaded = await this.loadMediaPreviewUrl(fileRow, kind, true) const loaded = await this.loadMediaPreviewUrl(fileRow, kind, true)
if (!loaded) { if (!loaded) {
return return
@ -583,11 +638,18 @@ export default {
this.mediaPreviewVisible = true this.mediaPreviewVisible = true
}, },
closeMediaPreview() { closeMediaPreview() {
const previewVideo = this.$refs.mediaPreviewVideo
if (previewVideo && typeof previewVideo.pause === 'function') {
previewVideo.pause()
}
this.mediaPreviewVisible = false this.mediaPreviewVisible = false
this.mediaPreviewType = '' this.mediaPreviewType = ''
this.mediaPreviewName = '' this.mediaPreviewName = ''
this.mediaPreviewUrl = '' this.mediaPreviewUrl = ''
}, },
handleMediaPreviewError() {
this.$message.warning('视频播放失败,请稍后重试')
},
resetQuery() { resetQuery() {
this.searchData = { projectNo: '', modelNo: '', color: '', status: '', deliveryStartDate: '', deliveryEndDate: '', page: 1, limit: 20 } this.searchData = { projectNo: '', modelNo: '', color: '', status: '', deliveryStartDate: '', deliveryEndDate: '', page: 1, limit: 20 }
this.getDataList('Y') this.getDataList('Y')

96
src/views/modules/longtron/production-plan-renovation-order.vue

@ -120,16 +120,16 @@
</div> </div>
<div class="logs-table-wrapper"> <div class="logs-table-wrapper">
<el-table :data="selectedOrderLogList" border size="small" class="detail-table" v-loading="detailLogLoading" height="295px"> <el-table :data="selectedOrderLogList" border size="small" class="detail-table" v-loading="detailLogLoading" height="295px">
<el-table-column prop="logTime" label="时间" width="160" align="center"></el-table-column>
<el-table-column prop="action" label="操作" width="95" align="center"></el-table-column>
<el-table-column prop="nodeName" label="节点" width="130" align="center">
<el-table-column prop="logTime" label="时间" min-width="160" align="center"></el-table-column>
<el-table-column prop="action" label="操作" min-width="95" align="center"></el-table-column>
<el-table-column prop="nodeName" label="节点" min-width="130" align="center">
<template slot-scope="scope">{{ scope.row.nodeName || scope.row.nodeCode || '-' }}</template> <template slot-scope="scope">{{ scope.row.nodeName || scope.row.nodeCode || '-' }}</template>
</el-table-column> </el-table-column>
<el-table-column prop="operatorName" label="操作人" width="100" align="center"></el-table-column>
<el-table-column prop="comment" label="备注" min-width="220" show-overflow-tooltip>
<el-table-column prop="operatorName" label="操作人" min-width="100" align="center"></el-table-column>
<!-- <el-table-column prop="comment" label="备注" min-width="220" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.comment || '-' }}</template> <template slot-scope="scope">{{ scope.row.comment || '-' }}</template>
</el-table-column>
<el-table-column label="影像" width="95" align="center">
</el-table-column>-->
<el-table-column label="影像" min-width="95" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<a <a
v-if="isMediaNodeLog(scope.row)" v-if="isMediaNodeLog(scope.row)"
@ -262,8 +262,8 @@
alt="media-thumb" alt="media-thumb"
@click="previewMediaFile(scope.row)"> @click="previewMediaFile(scope.row)">
<video <video
v-else-if="resolveMediaKind(scope.row) === 'video' && scope.row.previewUrl"
:src="scope.row.previewUrl"
v-else-if="resolveMediaKind(scope.row) === 'video'"
:src="getVideoStreamUrl(scope.row)"
class="media-thumb media-thumb-video" class="media-thumb media-thumb-video"
muted muted
playsinline playsinline
@ -302,10 +302,15 @@
@click.stop="closeMediaPreview"> @click.stop="closeMediaPreview">
<video <video
v-else-if="mediaPreviewType === 'video'" v-else-if="mediaPreviewType === 'video'"
ref="mediaPreviewVideo"
:key="mediaPreviewUrl"
:src="mediaPreviewUrl" :src="mediaPreviewUrl"
class="media-preview-video" class="media-preview-video"
controls controls
autoplay autoplay
playsinline
preload="auto"
@error="handleMediaPreviewError"
@click.stop> @click.stop>
</video> </video>
</div> </div>
@ -314,7 +319,7 @@
<script> <script>
import { deleteRenovationOrder, finishRenovationOrder, getNodeAssigneeList, getNodeAssigneeUsers, getRenovationOrderList, getReportLogList, reportRenovationOrderNode, saveNodeAssignee, saveRenovationOrder } from '@/api/longchuang/productionPlan' import { deleteRenovationOrder, finishRenovationOrder, getNodeAssigneeList, getNodeAssigneeUsers, getRenovationOrderList, getReportLogList, reportRenovationOrderNode, saveNodeAssignee, saveRenovationOrder } from '@/api/longchuang/productionPlan'
import { previewOssFileById2, queryOssFilePlus } from '@/api/oss/oss'
import { getOssVideoStreamUrl, previewOssFileById2, queryOssFilePlus } from '@/api/oss/oss'
export default { export default {
name: 'ProductionPlanRenovationOrder', name: 'ProductionPlanRenovationOrder',
@ -499,7 +504,7 @@ export default {
})) }))
this.mediaFileList.forEach(item => { this.mediaFileList.forEach(item => {
const kind = this.resolveMediaKind(item) const kind = this.resolveMediaKind(item)
if (kind !== 'other') {
if (kind === 'image') {
this.loadMediaPreviewUrl(item, kind, false) this.loadMediaPreviewUrl(item, kind, false)
} }
}) })
@ -528,9 +533,17 @@ export default {
if (!fileRow) { if (!fileRow) {
return '' return ''
} }
const ext = fileRow.fileType || fileRow.fileSuffix || ''
const ext = fileRow.fileType || fileRow.fileSuffix || this.getExtFromFileName(fileRow.fileName || fileRow.newFileName || '')
return String(ext).replace(/^\./, '').toLowerCase() return String(ext).replace(/^\./, '').toLowerCase()
}, },
getExtFromFileName(fileName) {
const name = String(fileName || '')
const dotIndex = name.lastIndexOf('.')
if (dotIndex < 0 || dotIndex >= name.length - 1) {
return ''
}
return name.slice(dotIndex + 1)
},
resolveMediaKind(fileRow) { resolveMediaKind(fileRow) {
const ext = this.getMediaExt(fileRow) const ext = this.getMediaExt(fileRow)
const imageExtList = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'] const imageExtList = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']
@ -556,13 +569,26 @@ export default {
buildPreviewMimeType(fileRow, kind) { buildPreviewMimeType(fileRow, kind) {
const ext = this.getMediaExt(fileRow) const ext = this.getMediaExt(fileRow)
if (kind === 'image') { if (kind === 'image') {
if (ext === 'jpg') {
return 'image/jpeg'
const imageMimeMap = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
gif: 'image/gif',
bmp: 'image/bmp',
webp: 'image/webp'
} }
return ext ? `image/${ext}` : 'image/jpeg'
return imageMimeMap[ext] || 'image/jpeg'
} }
if (kind === 'video') { if (kind === 'video') {
return ext ? `video/${ext}` : 'video/mp4'
const videoMimeMap = {
mp4: 'video/mp4',
m4v: 'video/mp4',
webm: 'video/webm',
mov: 'video/quicktime',
avi: 'video/x-msvideo',
'3gp': 'video/3gpp'
}
return videoMimeMap[ext] || 'video/mp4'
} }
return 'application/octet-stream' return 'application/octet-stream'
}, },
@ -584,7 +610,9 @@ export default {
const mimeType = this.buildPreviewMimeType(fileRow, kind) const mimeType = this.buildPreviewMimeType(fileRow, kind)
try { try {
const { data } = await previewOssFileById2({ id: fileRow.id, fileType: mimeType }) const { data } = await previewOssFileById2({ id: fileRow.id, fileType: mimeType })
const blob = data instanceof Blob ? data : new Blob([data], { type: mimeType })
const sourceBlob = data instanceof Blob ? data : new Blob([data])
const blobType = mimeType || sourceBlob.type || 'application/octet-stream'
const blob = new Blob([sourceBlob], { type: blobType })
this.$set(fileRow, 'previewUrl', URL.createObjectURL(blob)) this.$set(fileRow, 'previewUrl', URL.createObjectURL(blob))
return true return true
} catch (e) { } catch (e) {
@ -596,6 +624,12 @@ export default {
this.$set(fileRow, 'previewLoading', false) this.$set(fileRow, 'previewLoading', false)
} }
}, },
getVideoStreamUrl(fileRow) {
if (!fileRow || !fileRow.id) {
return ''
}
return getOssVideoStreamUrl(fileRow.id)
},
async previewMediaFile(fileRow) { async previewMediaFile(fileRow) {
const kind = this.resolveMediaKind(fileRow) const kind = this.resolveMediaKind(fileRow)
if (kind === 'other') { if (kind === 'other') {
@ -606,6 +640,27 @@ export default {
this.$message.warning('文件信息不完整,无法预览') this.$message.warning('文件信息不完整,无法预览')
return return
} }
if (kind === 'video') {
const videoUrl = this.getVideoStreamUrl(fileRow)
if (!videoUrl) {
this.$message.warning('视频地址无效,无法预览')
return
}
this.mediaPreviewType = 'video'
this.mediaPreviewName = fileRow.fileName || fileRow.newFileName || ''
this.mediaPreviewUrl = videoUrl
this.mediaPreviewVisible = true
this.$nextTick(() => {
const previewVideo = this.$refs.mediaPreviewVideo
if (previewVideo && typeof previewVideo.play === 'function') {
const playPromise = previewVideo.play()
if (playPromise && typeof playPromise.catch === 'function') {
playPromise.catch(() => {})
}
}
})
return
}
const loaded = await this.loadMediaPreviewUrl(fileRow, kind, true) const loaded = await this.loadMediaPreviewUrl(fileRow, kind, true)
if (!loaded) { if (!loaded) {
return return
@ -616,11 +671,18 @@ export default {
this.mediaPreviewVisible = true this.mediaPreviewVisible = true
}, },
closeMediaPreview() { closeMediaPreview() {
const previewVideo = this.$refs.mediaPreviewVideo
if (previewVideo && typeof previewVideo.pause === 'function') {
previewVideo.pause()
}
this.mediaPreviewVisible = false this.mediaPreviewVisible = false
this.mediaPreviewType = '' this.mediaPreviewType = ''
this.mediaPreviewName = '' this.mediaPreviewName = ''
this.mediaPreviewUrl = '' this.mediaPreviewUrl = ''
}, },
handleMediaPreviewError() {
this.$message.warning('视频播放失败,请稍后重试')
},
resetQuery() { resetQuery() {
this.searchData = { projectNo: '', modelNo: '', color: '', status: '', deliveryStartDate: '', deliveryEndDate: '', page: 1, limit: 20 } this.searchData = { projectNo: '', modelNo: '', color: '', status: '', deliveryStartDate: '', deliveryEndDate: '', page: 1, limit: 20 }
this.getDataList('Y') this.getDataList('Y')

Loading…
Cancel
Save