實現多路視頻截圖預覽之后上傳到后臺系統

********************父組件**********************

<div class="camera-box" v-loading="i.loading">

? ? ? ? ? ? ? ? ? ? ? ? ? ? <div

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? class="camera-box-inner"

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? v-for="(x, y) in i.children"

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? :key="y + 'children' + x.featureCode"

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? v-show="x.isShow"

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? :style="`width: ${i.videoInnerStyle!.width + '%'};max-height: ${i.videoInnerStyle!.height + '%'

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? };`">

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <div class="userName-box" v-if="!i.isApp">

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? {{ `${i.userName} — ${x.cameraName || i.cameraName}` }}

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? </div>

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <div class="userName-box" v-else>{{ `${i.userName}` }}</div>

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <video

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? :class="{ appVideo: i.isApp }"

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? :id="x.featureCode"

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? muted

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? autoplay

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? controls

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? v-show="x.camera"

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? :style="x.PhotoShow ? { border: '2px solid red' } : {}"></video>

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <Photo

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? v-if="x.PhotoShow"

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? :video="x.videoEl"

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? :userName="i.userName"

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @close="photoClose(x)" />

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <div class="takePhoto cusp iconfont icon-a-commonphoto" @click="takePhoto(x)"></div>

? ? ? ? ? ? ? ? ? ? ? ? ? ? </div>

? ? ? ? ? ? ? ? ? ? ? ? </div>

// 截圖

function takePhoto(data) {

? ? const videoEl = document.getElementById(data.featureCode) as HTMLVideoElement | null

? ? if (videoEl) {

? ? ? ? data.PhotoShow = true

? ? ? ? data.videoEl = videoEl

? ? }

}

// 截圖預覽關閉

function photoClose(data) {

? ? data.PhotoShow = false

}

********************子組件***********************

<template>

? ? <div class="canvas-photo">

? ? ? ? <canvas ref="photoCanvas"></canvas>

? ? ? ? <el-dialog

? ? ? ? ? ? v-model="previewDialog.show"

? ? ? ? ? ? :title="previewDialog.title"

? ? ? ? ? ? width="600px"

? ? ? ? ? ? @close="handlePreviewClose"

? ? ? ? ? ? append-to-body>

? ? ? ? ? ? <div>

? ? ? ? ? ? ? ? <img :src="previewDialog.imageUrl" alt="" />

? ? ? ? ? ? </div>

? ? ? ? ? ? <template #footer>

? ? ? ? ? ? ? ? <el-button @click="previewDialog.show = false">取消</el-button>

? ? ? ? ? ? ? ? <el-button type="primary" @click="handleConfirmUpload">確定上傳</el-button>

? ? ? ? ? ? </template>

? ? ? ? </el-dialog>

? ? </div>

</template>

<script setup lang="ts">

import { onMounted, PropType, ref } from 'vue'

import request from '../../../utils/request'

import { Session } from '../../../utils/storage'

import { formatTime, base64ToFile } from '/@/utils'

import { ElNotification, ElForm, ElFormItem, ElProgress } from 'element-plus'

const props = defineProps({

? ? video: {

? ? ? ? type: Object as PropType<HTMLVideoElement>,

? ? ? ? required: true

? ? },

? ? userName: {

? ? ? ? type: String as PropType<string>,

? ? ? ? default: ''

? ? }

})

const emit = defineEmits(['close'])

const fileName = ref<string>('')

const canvas = ref<any>()

const imgFile = ref<any>()

const previewDialog = reactive<{

? ? show: boolean

? ? title: string

? ? imageUrl: string

}>({

? ? show: false,

? ? title: '預覽截圖',

? ? imageUrl: ''

})

const photoCanvas = ref()

const ctx = ref<any>()

// // 下載圖片

// function downloadCanvas() {

// ?const link = document.createElement('a')

// ?link.download = `用戶-${props.userName}-視頻截圖${formatTime(new Date().getTime(), 'yyyy-mm-dd hh-MM-ss')}.png`

// ?link.href = photoCanvas.value.toDataURL('image/png')

// ?link.click()

// }

const upLoadProgress = ref<number>(0)

// 截圖本地保存和上傳到文件管理

const captureAndSaveFrame = () => {

? ? const video = props.video

? ? if (!video || !photoCanvas.value) return

? ? const fileName = `用戶-${props.userName}-視頻截圖${formatTime(new Date().getTime(), 'yyyy-mm-dd hh-MM-ss')}.png`

? ? // 創建canvas元素

? ? canvas.value = document.createElement('canvas')

? ? const ctx = canvas.value.getContext('2d')!

? ? // 設置canvas尺寸與視頻一致

? ? canvas.value.width = video.videoWidth

? ? canvas.value.height = video.videoHeight

? ? // 將視頻幀繪制到canvas

? ? ctx.drawImage(video, 0, 0, canvas.value.width, canvas.value.height)

? ? // 生成圖片數據URL(支持PNG格式)

? ? const imageDataUrl = canvas.value.toDataURL('image/png')

? ? const link = document.createElement('a')

? ? link.href = imageDataUrl

? ? const imageName = `${fileName}_${Date.now()}.png`

? ? link.download = imageName

? ? // base64轉為file文件

? ? imgFile.value = base64ToFile(imageDataUrl, imageName)

? ? // 模擬點擊下載

? ? document.body.appendChild(link)

? ? link.click()

? ? document.body.removeChild(link)

? ? // 更新預覽

? ? previewDialog.imageUrl = imageDataUrl

? ? previewDialog.show = true

}

const handleConfirmUpload = () => {

? ? // 上傳到服務器

? ? const formData = new FormData()

? ? // 將文件添加到 FormData 中,以便后續發送請求

? ? formData.append('file', imgFile.value)

? ? // 添加額外的請求參數,這里 dir 為空字符串

? ? formData.append('dir', '')

? ? // 添加額外的請求參數,這里 type 為 10

? ? formData.append('type', '10')

? ? ElNotification({

? ? ? ? type: 'info',

? ? ? ? title: '一個截圖文件正在上傳',

? ? ? ? dangerouslyUseHTMLString: true,

? ? ? ? message: h(ElForm, { model: {}, 'label-width': '80px' }, [

? ? ? ? ? ? h(ElFormItem, { label: '文件名:' }, fileName.value),

? ? ? ? ? ? h(ElFormItem, { label: '上傳進度:' }, [

? ? ? ? ? ? ? ? h(ElProgress, {

? ? ? ? ? ? ? ? ? ? id: 'meetingRoomUploadProgress',

? ? ? ? ? ? ? ? ? ? percentage: 0,

? ? ? ? ? ? ? ? ? ? style: {

? ? ? ? ? ? ? ? ? ? ? ? width: '200px'

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? })

? ? ? ? ? ? ])

? ? ? ? ]),

? ? ? ? showClose: true,

? ? ? ? duration: 0

? ? })

? ? const el = document

? ? ? ? .getElementById('meetingRoomUploadProgress')

? ? ? ? ?.getElementsByClassName('el-progress-bar__inner')[0] as HTMLElement

? ? const elText = document

? ? ? ? .getElementById('meetingRoomUploadProgress')

? ? ? ? ?.getElementsByClassName('el-progress__text')[0]

? ? ? ? .getElementsByTagName('span')[0] as HTMLElement

? ? request('/admin/sys-file/upload', {

? ? ? ? method: 'POST',

? ? ? ? headers: {

? ? ? ? ? ? 'Content-Type': 'multipart/form-data',

? ? ? ? ? ? Authorization: 'Bearer ' + Session.get('token'),

? ? ? ? ? ? 'TENANT-ID': Session.getTenant()

? ? ? ? },

? ? ? ? onUploadProgress: (progressEvent: any) => {

? ? ? ? ? ? upLoadProgress.value = Number(progressEvent.progress.toFixed(2)) * 100

? ? ? ? ? ? if (upLoadProgress.value === 100) {

? ? ? ? ? ? ? ? el.style.width = '100%'

? ? ? ? ? ? ? ? elText.innerHTML = '100%'

? ? ? ? ? ? ? ? setTimeout(() => {

? ? ? ? ? ? ? ? ? ? ElNotification.closeAll()

? ? ? ? ? ? ? ? }, 1000)

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? el.style.width = upLoadProgress.value + '%'

? ? ? ? ? ? ? ? elText.innerHTML = upLoadProgress.value + '%'

? ? ? ? ? ? }

? ? ? ? },

? ? ? ? data: formData

? ? })

? ? ? ? .then(response => {

? ? ? ? ? ? if (!response.ok) {

? ? ? ? ? ? ? ? throw new Error('Network response was not ok')

? ? ? ? ? ? }

? ? ? ? ? ? return response.json()

? ? ? ? })

? ? ? ? .then(data => {

? ? ? ? ? ? // 請求成功時的處理,打印返回的數據

? ? ? ? ? ? console.log('success', data)

? ? ? ? })

? ? ? ? .catch(err => {

? ? ? ? ? ? // 請求失敗時的處理,打印錯誤信息

? ? ? ? ? ? console.log('error', err)

? ? ? ? })

? ? previewDialog.show = false

? ? // 清理資源

? ? cleanupCanvas()

}

// 清理canvas

const cleanupCanvas = () => {

? ? if (canvas.value) {

? ? ? ? canvas.value.remove()

? ? ? ? canvas.value = null

? ? }

}

const handlePreviewClose = () => {

? ? emit('close')

? ? cleanupCanvas()

}

// 更新canvas尺寸

const updateCanvasSize = () => {

? ? if (!photoCanvas.value || !props.video) return

? ? const width = photoCanvas.value.offsetWidth

? ? const height = photoCanvas.value.offsetHeight

? ? photoCanvas.value.width = width

? ? photoCanvas.value.height = height

? ? if (ctx.value && props.video.videoWidth) {

? ? ? ? ctx.value.drawImage(props.video, 0, 0, width, height)

? ? }

}

onMounted(() => {

? ? if (!photoCanvas.value) return

? ? ctx.value = photoCanvas.value.getContext('2d')

? ? // 等待DOM渲染完成后執行

? ? nextTick(() => {

? ? ? ? updateCanvasSize()

? ? ? ? // 初始捕獲一幀

? ? ? ? captureAndSaveFrame()

? ? })

? ? // 監聽窗口大小變化

? ? window.addEventListener('resize', updateCanvasSize)

})

onUnmounted(() => {

? ? // 移除事件監聽器

? ? window.removeEventListener('resize', updateCanvasSize)

? ? // 清理資源

? ? cleanupCanvas()

? ? // 關閉所有通知

? ? ElNotification.closeAll()

})

</script>

<style scoped lang="scss">

.canvas-photo {

? ? width: 100%;

? ? height: 100%;

? ? position: absolute;

? ? top: 0;

? ? left: 0;

? ? opacity: 0;

? ? z-index: -10;

? ? canvas {

? ? ? ? width: 100%;

? ? ? ? height: 100%;

? ? }

? ? .preview-container {

? ? ? ? padding: 20px;

? ? ? ? text-align: center;

? ? ? ? .preview-image {

? ? ? ? ? ? max-width: 100%;

? ? ? ? ? ? max-height: calc(50vh - 100px);

? ? ? ? ? ? border: 1px solid #ebeef5;

? ? ? ? ? ? border-radius: 4px;

? ? ? ? ? ? box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);

? ? ? ? }

? ? }

}

</style>

**********工具函數********************

/**

?* 將Base64字符串轉換為File對象

?* @param {string} base64 - Base64編碼的字符串

?* @param {string} filename - 生成文件的名稱

?* @param {string} [mimeType] - 文件的MIME類型,默認為'image/png'

?* @returns {File} - 返回的File對象

?*/

export function base64ToFile(base64, filename, mimeType = 'image/png') {

? ? // 1. 移除Base64前綴(如果有)

? ? const base64WithoutPrefix = base64.replace(/^data:.+;base64,/, '')

? ? // 2. 將Base64字符串轉換為字節數組

? ? const byteCharacters = atob(base64WithoutPrefix)

? ? const byteArrays = []

? ? for (let offset = 0; offset < byteCharacters.length; offset += 512) {

? ? ? ? const slice = byteCharacters.slice(offset, offset + 512)

? ? ? ? const byteNumbers = new Array(slice.length)

? ? ? ? for (let i = 0; i < slice.length; i++) {

? ? ? ? ? ? byteNumbers[i] = slice.charCodeAt(i)

? ? ? ? }

? ? ? ? const byteArray = new Uint8Array(byteNumbers)

? ? ? ? byteArrays.push(byteArray)

? ? }

? ? // 3. 創建Blob對象

? ? const blob = new Blob(byteArrays, { type: mimeType })

? ? // 4. 將Blob轉換為File對象

? ? return new File([blob], filename, { type: mimeType })

}

實現效果圖

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/83255.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/83255.shtml
英文地址,請注明出處:http://en.pswp.cn/web/83255.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

分布式鎖-Redisson實現

目錄 本地鎖的局限性 Redisson解決分布式鎖問題 在分布式環境下&#xff0c;分布式鎖可以保證在多個節點上的并發操作時數據的一致性和互斥性。分布式鎖有多種實現方案&#xff0c;最常用的兩種方案是&#xff1a;zookeeper和redis&#xff0c;本文介紹redis實現分布式鎖方案…

【辦公類-48-04】202506每月電子屏臺賬匯總成docx-5(問卷星下載5月范圍內容,自動獲取excel文件名,并轉移處理)

背景需求&#xff1a; 1-4月電子屏表格&#xff0c;都是用這個代碼將EXCEL數據整理成分類成3個WORD表格。 【辦公類-48-04】20250118每月電子屏臺賬匯總成docx-4&#xff08;提取EXCLE里面1月份的內容&#xff0c;自制月份文件夾&#xff09;-CSDN博客文章瀏覽閱讀1.2k次&…

【websocket】安裝與使用

websocket安裝與使用 1. 介紹2. 安裝3. websocketpp常用接口4. Websocketpp使用4.1 服務端4.2 客戶端 1. 介紹 WebSocket 是從 HTML5 開始支持的一種網頁端和服務端保持長連接的 消息推送機制。 傳統的 web 程序都是屬于 “一問一答” 的形式&#xff0c;即客戶端給服務器發送…

微算法科技(NASDAQ:MLGO)基于信任的集成共識和灰狼優化(GWO)算法,搭建高信任水平的區塊鏈網絡

隨著數字化轉型的加速&#xff0c;區塊鏈技術作為去中心化、透明且不可篡改的數據存儲與交換平臺&#xff0c;正逐步滲透到金融、供應鏈管理、物聯網等多個領域&#xff0c;探索基于信任的集成共識機制&#xff0c;并結合先進的優化算法來提升區塊鏈網絡的信任水平&#xff0c;…

【項目實戰】通過多模態+LangGraph實現PPT生成助手

PPT自動生成系統 基于LangGraph的PPT自動生成系統&#xff0c;可以將Markdown文檔自動轉換為PPT演示文稿。 功能特點 Markdown解析&#xff1a;自動解析Markdown文檔結構PPT模板分析&#xff1a;分析PPT模板的布局和風格智能布局決策&#xff1a;匹配內容與合適的PPT布局自動…

貝葉斯優化+LSTM+時序預測=Nature子刊!

貝葉斯優化與LSTM的融合在時間序列預測領域取得了顯著成效&#xff0c;特別是在處理那些涉及眾多超參數調整的復雜問題時。 1.這種結合不僅極大提高了預測的精確度&#xff0c;還優化了模型訓練流程&#xff0c;提升了效率和成本效益。超參數優化的新篇章&#xff1a;LSTM因其…

AWSLambda之設置時區

目標 希望Lambda運行的時區是東八區。 解決 只需要設置lambda的環境變量TZ為東八區時區即可&#xff0c;即Asia/Shanghai。 參考 使用 Lambda 環境變量

RAG系統向量數據庫選型與Prompt Engineering魯棒性測試實踐

引言 在AI應用不斷落地的今天&#xff0c;RAG&#xff08;Retrieval-Augmented Generation&#xff0c;檢索增強生成&#xff09;和Prompt Engineering&#xff08;提示工程&#xff09;成為大模型工程師和測試工程師的核心武器。 一方面&#xff0c;RAG系統依賴強大的向量數據…

2.Socket 編程 UDP

1.UDP網絡編程 0.背景知識 自實現IP轉化 相關函數理解 IP相關理解 1. V2版本 - DictServer封裝版 實現一個簡單的英譯漢的網絡字典 Dict.hpp dictionary.txt InetAddr.hpp ? 在 InetAddr 中&#xff0c;重載一下方便對用戶是否是同一個進行比較 Log.hpp makefile Mutex.hpp…

數據可視化交互

目錄 【實驗目的】 【實驗原理】 【實驗環境】 【實驗步驟】 一、安裝 pyecharts 二、下載數據 三、實驗任務 實驗 1&#xff1a;AQI 橫向對比條形圖 代碼說明&#xff1a; 運行結果&#xff1a; 實驗 2&#xff1a;AQI 等級分布餅圖 實驗 3&#xff1a;多城市 AQI…

【MATLAB去噪算法】基于CEEMDAN聯合小波閾值去噪算法(第四期)

CEEMDAN聯合小波閾值去噪算法相關文獻 一、EMD 與 EEMD 的局限性 &#xff08;1&#xff09;EMD (經驗模態分解) 旨在自適應地將非線性、非平穩信號分解成一系列 本征模態函數 (IMFs)&#xff0c;這些 IMFs 從高頻到低頻排列。 核心問題&#xff1a;模態混合 (Mode Mixing) 同…

大話軟工筆記—架構模型

1. 架構模型1—拓撲圖 &#xff08;1&#xff09;拓撲圖概念 拓撲圖&#xff0c;將多個軟件系統用網絡圖連接起來的表達方式。 &#xff08;2&#xff09;拓撲圖分類 總線型結構 比較普遍采用的方式&#xff0c;將所有的系統接到一條總線上。 星狀結構 各個系統通過點到…

24-Oracle 23 ai ?Lock-Free Reservations?(無鎖列值保留)

數據庫領域為了解決ACID的平衡&#xff0c;嘗試了各種鎖、各種模式&#xff0c; 引擎技術特性、廠家實現方式各放異彩&#xff0c;被各種鎖折磨的小伙伴&#xff0c;是不是感同身受。 一、數據庫鎖 1. 鎖的類型與特點 ?全局鎖?&#xff1a;鎖定整個數據庫實例&#xff0c;備…

OpenPrompt 和直接對提示詞的嵌入向量進行訓練有什么區別

OpenPrompt 和直接對提示詞的嵌入向量進行訓練有什么區別 直接訓練提示詞嵌入向量的核心區別 您提到的代碼: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding

從零寫一個ALSA聲卡驅動學習(1)

前言&#xff1a; 本文檔描述了如何編寫 ALSA&#xff08;高級 Linux 音頻架構&#xff09;驅動程序。文檔主要聚焦于 PCI 聲卡的實現。對于其他類型的設備&#xff0c;可能會使用不同的 API。不過&#xff0c;至少 ALSA 的內核 API 是一致的&#xff0c;因此本文檔在編寫這些驅…

鏈結構與工作量證明7??:用 Go 實現比特幣的核心機制

鏈結構與工作量證明:用 Go 實現比特幣的核心機制 如果你用 Go 寫過區塊、算過哈希,也大致理解了非對稱加密、數據序列化這些“硬核知識”,那么恭喜你,現在我們終于可以把這些拼成一條完整的“區塊鏈”。 不過別急,這一節我們重點搞懂兩件事: 區塊之間是怎么連接成“鏈”…

深入理解 React 樣式方案

React 的樣式方案較多,在應用開發初期,開發者需要根據項目業務具體情況選擇對應樣式方案。React 樣式方案主要有: 1. 內聯樣式 2. module css 3. css in js 4. tailwind css 這些方案中,均有各自的優勢和缺點。 1. 方案優劣勢 1. 內聯樣式: 簡單直觀,適合動態樣式和…

YOLO電力物目標檢測訓練

最近需要進行電力物檢測相關的業務&#xff0c;因此制作了一個電力物數據集&#xff0c;使用YOLO目標檢測方法進行實驗&#xff0c;記錄實驗過程如下&#xff1a; 數據集標注 首先需要對電力物相關設備進行標注&#xff0c;這里我們選用labelme進行標注&#xff0c;使用無人機…

從阿里云域名解析異常事件看下域名解析過程

近日阿里云核心域名aliyuncs.com解析異常&#xff0c;涉及眾多依賴阿里云服務的網站和應用&#xff0c;故障從發現到修復耗時5個多小時。本文簡要分析下整個事件的過程&#xff0c;并分析域名解析的過程&#xff0c;了解根域名服務器在其中的作用&#xff0c;以了解。 1、aliyu…

應用分享 | 精準生成和時序控制!AWG在確定性三量子比特糾纏光子源中的應用

在量子技術飛速發展的今天&#xff0c;實現高效穩定的量子態操控是推動量子計算、量子通信等領域邁向實用化的關鍵。任意波形發生器&#xff08;AWG&#xff09;作為精準信號控制的核心設備&#xff0c;在量子實驗中發揮著不可或缺的作用。丹麥哥本哈根大學的研究團隊基于單個量…