二次封裝element-plus上傳組件,提供校驗、回顯等功能

二次封裝element-plus上傳組件

  • 0 相關介紹
  • 1 效果展示
  • 2 組件主體
  • 3 視頻組件
  • 4 Demo

0 相關介紹

基于element-plus框架,視頻播放器使用西瓜視頻播放器組件

相關能力

  • 提供圖片、音頻、視頻的預覽功能
  • 提供是否為空、文件類型、文件大小、文件數量、圖片寬高校驗
  • 提供圖片回顯功能,并保證回顯的文件不會重新上傳
  • 提供達到數量限制不顯示element自帶的加號

相關文檔

  • 西瓜播放器
  • element-plus

1 效果展示

在這里插入圖片描述
在這里插入圖片描述

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

2 組件主體

<template><el-uploadlist-type="picture-card":auto-upload="false":on-change="onChange":on-remove="onChange":multiple="props.multiple":limit="props.limit":accept="accept"ref="elUploadElement":file-list="fileList":id="fileUploadId"><el-icon><Plus /></el-icon><template #file="{ file }"><div><imgclass="el-upload-list__item-thumbnail":src="file.viewUrl"alt=""v-if="isShow"/><span class="el-upload-list__item-actions"><spanclass="el-upload-list__item-preview"@click="handlePictureCardPreview(file)"><el-icon><zoom-in /></el-icon></span><span class="el-upload-list__item-delete" @click="handleRemove(file)"><el-icon><Delete /></el-icon></span></span></div></template><template #tip><div class="el-upload__tip">{{ tip }}</div></template></el-upload><!-- 文件預覽彈窗 --><el-dialogv-model="dialogVisible"style="width: 800px"@close="close"@open="open"><el-imagev-if="previewFile.type === 'image'"style="width: 100%; height: 400px":src="previewFile.url"fit="contain"alt="Preview Image"/><videoComponentref="videoRef"v-if="previewFile.type === 'video'"style="width: 100%; height: 400px":url="previewFile.url":poster="previewFile.viewUrl"/><audioref="audioRef"v-if="previewFile.type === 'audio'":src="previewFile.url"controlsstyle="width: 100%; height: 400px"/></el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { Delete, Plus, ZoomIn } from "@element-plus/icons-vue";
import type { UploadFile } from "element-plus";
// 多個組件同時使用的時候做區分
const fileUploadId = ref(`ID${Math.floor(Math.random() * 100000)}`);
type UploadFileNew = {[Property in keyof UploadFile]: UploadFile[Property];
} & {type: string;viewUrl: string | undefined;needUpload: boolean;duration: number;width: number;height: number;
};const imageRegex = RegExp(/(jpg|bmp|gif|ico|pcx|jpeg|tif|png|raw|tga)/);
const audioRegex = RegExp(/(mp3|wav|flac|ogg|aac|wma)/);
const videoRegex = RegExp(/(avi|wmv|mpeg|mp4|m4v|mov|asf|flv|f4v|rmvb|rm|3gp|vob)/
);
// 導入視頻組件
import videoComponent from "./videoComponent.vue";
const props = defineProps({// 數量限制limit: {type: Number,default: 1,},// 是否多選multiple: {type: Boolean,default: false,},// 要選擇的文件類型fileType: {type: String,required: true,validator(value: string) {return ["audio", "image", "video"].includes(value);},},// 追加的提示appendTip: {type: String,default: "",},widthLimit: {type: Number,default: 0,},heightLimit: {type: Number,default: 0,},
});
// 可上傳文件類型
const accept = ref("");
// 最大上傳文件大小
const maxSize = ref(0);
const tip = ref("");
// 根據類型設置默認值
if (props.fileType) {switch (props.fileType) {case "image":accept.value = ".png, .jpg, .jpeg";maxSize.value = 20;tip.value = `請上傳${accept.value}格式的文件,圖片大小不能超過${maxSize.value}MB。${props.appendTip}`;break;case "audio":accept.value = ".mp3, .wma, .aac, .flac, .ape";maxSize.value = 500;tip.value = `請上傳${accept.value}格式的文件,音頻大小不能超過${maxSize.value}MB。${props.appendTip}`;break;case "video":accept.value = ".mp4, .rmvb, .avi, .mov";maxSize.value = 500;tip.value = `請上傳${accept.value}格式的文件,視頻大小不能超過${maxSize.value}MB。${props.appendTip}`;break;case "musiVideo":accept.value = ".mp4, .rmvb, .avi, .mov, .mp3, .wma, .aac, .flac, .ape";maxSize.value = 500;tip.value = `請上傳${accept.value}格式的文件,音視頻大小不能超過${maxSize.value}MB。${props.appendTip}`;break;default:throw new Error("類型錯誤");}
}
const isShow = ref(true);
const elUploadElement = ref();
// 控制圖片預覽的路徑
const previewFile = ref();
// 控制是否顯示圖片預覽的彈窗
const dialogVisible = ref(false);
// 雙向綁定的文件列表
const fileList = ref<UploadFileNew[]>([]);
// 定義組件ref
const videoRef = ref(),audioRef = ref();
async function onChange(uploadFile: UploadFileNew,uploadFiles: UploadFileNew[]
) {// 如果是遠程原件不需要任何處理if (!uploadFile.name) return;isShow.value = false;const suffix = uploadFile.name.split(".").at(-1) as string;if (videoRegex.test(suffix.toLocaleLowerCase())) {const res = (await findvideodetail(uploadFile.url as string)) as {viewUrl: string;duration: number;};uploadFile.type = "video";uploadFile.viewUrl = res.viewUrl;uploadFile.duration = res.duration;} else if (imageRegex.test(suffix)) {uploadFile.type = "image";uploadFile.viewUrl = uploadFile.url;const res = (await findImageDetail(uploadFile.url as string)) as {width: number;height: number;};uploadFile.width = res.width;uploadFile.height = res.height;} else if (audioRegex.test(suffix)) {uploadFile.type = "audio";uploadFile.viewUrl = new URL("@/assets/goods/audio.svg",import.meta.url).href;const res = (await findAudioDetail(uploadFile.url as string)) as {duration: number;};uploadFile.duration = res.duration;}console.log(uploadFile);uploadFile.needUpload = true;fileList.value = uploadFiles;isShow.value = true;verifyLength();
}// 刪除文件
function handleRemove(uploadFile: UploadFile) {elUploadElement.value.handleRemove(uploadFile);verifyLength();
}// 檢驗已選擇的文件數量是否超過閾值,并做顯示/隱藏處理
function verifyLength() {const element = document.querySelector(`#${fileUploadId.value} .el-upload--picture-card`) as HTMLDivElement;console.log(fileUploadId.value, element);if (fileList.value.length === props.limit) {element.style.visibility = "hidden";} else {element.style.visibility = "visible";}
}// 預覽文件
const handlePictureCardPreview = (file: UploadFile) => {previewFile.value = file;dialogVisible.value = true;
};//截取視頻第一幀作為播放前默認圖片
function findvideodetail(url: string) {const video = document.createElement("video"); // 也可以自己創建videovideo.src = url; // url地址 url跟 視頻流是一樣的const canvas = document.createElement("canvas"); // 獲取 canvas 對象const ctx = canvas.getContext("2d"); // 繪制2dvideo.crossOrigin = "anonymous"; // 解決跨域問題,也就是提示污染資源無法轉換視頻video.currentTime = 1; // 第一幀return new Promise((resolve) => {video.oncanplay = () => {canvas.width = video.clientWidth || video.width || 320; // 獲取視頻寬度canvas.height = video.clientHeight || video.height || 240; //獲取視頻高度// 利用canvas對象方法繪圖ctx!.drawImage(video, 0, 0, canvas.width, canvas.height);// 轉換成base64形式const viewUrl = canvas.toDataURL("image/png"); // 截取后的視頻封面resolve({viewUrl: viewUrl,duration: Math.ceil(video.duration),width: video.width,height: video.height,});video.remove();canvas.remove();};});
}
//截取視頻第一幀作為播放前默認圖片
function findAudioDetail(url: string) {const audio = document.createElement("audio"); // 也可以自己創建videoaudio.src = url; // url地址 url跟 視頻流是一樣的audio.crossOrigin = "anonymous"; // 解決跨域問題,也就是提示污染資源無法轉換視頻return new Promise((resolve) => {audio.oncanplay = () => {resolve({duration: Math.ceil(audio.duration),});audio.remove();};});
}
//
function findImageDetail(url: string) {const img = document.createElement("img"); // 也可以自己創建videoimg.src = url; // url地址 url跟 視頻流是一樣的return new Promise((resolve) => {img.onload = () => {resolve({width: img.width,height: img.height,});img.remove();};});
}type validateReturnValue = {code: number;success: boolean;msg: string;
};
// 驗證文件格式
function verification(): validateReturnValue {if (fileList.value.length <= 0) {return {code: 0,success: false,msg: "請選擇上傳文件",};}if (fileList.value.length > props.limit) {return {code: 0,success: false,msg: `文件數量超出限制,請上傳${props.limit}以內的文件`,};}for (let i = 0; i < fileList.value.length; i++) {const element = fileList.value[i];if (!element.needUpload) break;const suffix = element.name.split(".").at(-1) as string;if (!accept.value.includes(suffix.toLowerCase())) {return {code: 0,success: false,msg: `文件類型不正確,請上傳${accept.value}類型的文件`,};}if ((element.size as number) / 1024 / 1024 > maxSize.value) {return {code: 0,success: false,msg: "文件大小超出限制",};}if (element.type === "image") {if (props.widthLimit && element.width != props.widthLimit) {return {code: 0,success: false,msg: `圖片寬度不等于${props.widthLimit}像素`,};}if (props.heightLimit && element.height != props.heightLimit) {return {code: 0,success: false,msg: `圖片高度不等于${props.heightLimit}像素`,};}}}return {code: 200,success: true,msg: "格式正確",};
}// 添加遠程圖片
async function addFileList(url: string, name?: string) {const uploadFile: any = {url,name,};const suffix = url.split(".").at(-1) as string;if (videoRegex.test(suffix.toLocaleLowerCase())) {const res = (await findvideodetail(url)) as {viewUrl: string;};uploadFile.type = "video";uploadFile.viewUrl = res.viewUrl;} else if (imageRegex.test(suffix)) {uploadFile.type = "image";uploadFile.viewUrl = uploadFile.url;} else if (audioRegex.test(suffix)) {uploadFile.type = "audio";uploadFile.viewUrl = new URL("@/assets/goods/audio.svg",import.meta.url).href;}uploadFile.needUpload = false;fileList.value.push(uploadFile);verifyLength();
}
// 關閉彈窗的時候停止音視頻的播放
function close() {if (previewFile.value.type === "audio") {audioRef.value.pause();}if (previewFile.value.type === "video") {videoRef.value.pause();}
}
// 打開彈窗的時候修改視頻路徑和封面
function open() {if (previewFile.value.type === "video") {videoRef.value.changeUrl();}
}
// 獲取文件對象
function getFiles(): UploadFileNew[] {return fileList.value;
}
defineExpose({getFiles,verification,addFileList,handlePictureCardPreview,handleRemove,
});
</script>

3 視頻組件

<script setup lang="ts">
import { onMounted } from "vue";import Player from "xgplayer";
import "xgplayer/dist/index.min.css";const props = defineProps({// 視頻路徑url: {type: String,},// 封皮poster: {type: String,},
});
let player: any;
onMounted(() => {player = new Player({id: "mse",lang: "zh",// 默認靜音volume: 0,autoplay: false,screenShot: true,videoAttributes: {crossOrigin: "anonymous",},url: props.url,poster: props.poster,//傳入倍速可選數組playbackRate: [0.5, 0.75, 1, 1.5, 2],});
});
// 對外暴露暫停事件
function pause() {player.pause();
}
// 對外暴露修改視頻源事件
function changeUrl() {player.playNext({url: props.url,poster: props.poster,});
}
defineExpose({ pause, changeUrl });
</script><template><div id="mse" />
</template><style scoped>
#mse {flex: auto;margin: 0px auto;
}
</style>

4 Demo

<script setup lang="ts">
import { ref, onMounted } from "vue";
import FileUpload from "./FileUpload/index.vue";
import type { FormInstance } from "element-plus";
// el-form實例
const ruleFormRef = ref();
// form綁定參數
const formData = ref({headImage: undefined,headImageList: [],
});// ------上傳文件校驗用 start-------
const FileUploadRef = ref();
let uploadRules = ref();
const isShowUpLoad = ref(true);
onMounted(() => {isShowUpLoad.value = false;uploadRules = ref([{validator(rule: any, value: any, callback: any) {const res = FileUploadRef.value.verification();if (!res.success) {callback(new Error(res.msg));}setTimeout(() => {callback();}, 500);},trigger: "blur",},// 需要顯示出星號,但是由于沒有做數據綁定,所以放在后邊{ required: true, message: "請上傳頭像", trigger: "blur" },]);isShowUpLoad.value = true;
});
// ------上傳文件校驗用 end-------const submitLoading = ref(false);
function submitForm(ruleFormRef: FormInstance) {// 避免必需的校驗無法通過formData.value.headImageList = FileUploadRef.value.getFiles();ruleFormRef.validate(async (valid) => {if (valid) {submitLoading.value = true;const params = { ...formData.value };// 單文件這么寫,多文件需要循環if (params.headImageList[0].needUpload)params.headImage = await uploadFile(params.headImageList[0].raw);delete params.headImageList;submitLoading.value = false;}});
}
function uploadFile(file: File) {return "路徑";
}
</script>
<!--  -->
<template><el-formref="ruleFormRef":model="formData"label-width="120px"v-loading="submitLoading"element-loading-text="數據傳輸中..."style="margin-top: 20px;"><el-form-itemlabel="頭像"prop="headImageList":rules="uploadRules"v-if="isShowUpLoad"><FileUpload ref="FileUploadRef" :multiple="false" fileType="image" /></el-form-item><el-form-item label=""><el-button>取 消</el-button><el-button type="primary" @click="submitForm(ruleFormRef)">確 認</el-button></el-form-item></el-form>
</template>
<style lang="scss" scoped></style>

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

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

相關文章

el-table實現懶加載(el-table-infinite-scroll)

2023.8.15今天我學習了用el-table對大量的數據進行懶加載。 效果如下&#xff1a; 1.首先安裝&#xff1a; npm install --save el-table-infinite-scroll2 2.全局引入&#xff1a; import ElTableInfiniteScroll from "el-table-infinite-scroll";// 懶加載 V…

clion2020.3配置clang-format

標題clion 啟用clang-format 文件->設置->編輯器->代碼樣式. 為了保持原有代碼風格不變&#xff0c;可以把原始的配置風格先導出&#xff0c;最好直接保存到自己的工程下&#xff0c;.clang-format是隱藏文件&#xff0c;需要用ctrlH才能看到 文件->設置->編輯…

SpringBoot復習:(45)@Component定義的bean會被@Bean定義的同名的bean覆蓋

有同名的bean需要配置&#xff1a; spring.main.allow-bean-definition-overridingtrue 否則報錯。 package cn.edu.tju.component;import org.springframework.stereotype.Component;Component public class Person {private String name;private int age;{this.name "…

室溫超導是什么?有哪些應用場景?

目錄 一、應用場景&#xff1a;二、案例分析&#xff1a; 室溫超導是指在室溫下&#xff08;即約 20C 至 30C&#xff09;實現超導現象的材料。超導是指某些材料在低溫下電阻為零的物理現象&#xff0c;室溫超導材料是超導材料的一種。室溫超導現象的發現和研究是超導領域的一個…

ChatGPT在智能游戲和游戲AI中的應用如何?

ChatGPT在智能游戲和游戲AI領域具有廣泛的應用潛力&#xff0c;可以為游戲體驗增添智能和交互性&#xff0c;同時也有助于游戲開發者創造更豐富、更引人入勝的游戲內容。以下將詳細探討ChatGPT在智能游戲和游戲AI中的應用。 ## 1. 游戲角色的智能化 在角色扮演游戲&#xff0…

103.216.154.X服務器出現漏洞了有什么辦法?

服務器出現漏洞是一種嚴重的安全風險&#xff0c;需要及時采取措施來應對。以下是一些常見的應對措施&#xff1a; 及時更新補丁&#xff1a;確保服務器上的操作系統、應用程序和軟件都是最新版本&#xff0c;并及時應用相關的安全補丁&#xff0c;以修復已知的漏洞。 強化訪問…

OpenHarmony Meetup 廣州站 OpenHarmony正當時—技術開源

招募令 OpenHarmony Meetup 廣州站 火熱招募中&#xff0c;等待激情四射的開發者&#xff0c;線下參與OpenHarmonyMeetup線下交流 展示前沿技術、探討未來可能、讓你了解更多專屬OpenHarmony的魅力 線下參與&#xff0c;先到先得,僅限20個名額&#xff01; 報名截止時間8月23日…

【云原生】Docker 詳解(三):Docker 鏡像管理基礎

Docker 詳解&#xff08;三&#xff09;&#xff1a;Docker 鏡像管理基礎 1.鏡像的概念 鏡像可以理解為應用程序的集裝箱&#xff0c;而 Docker 用來裝卸集裝箱。 Docker 鏡像含有啟動容器所需要的文件系統及其內容&#xff0c;因此&#xff0c;其用于創建并啟動容器。 Dock…

-L和-rpath-link和-rpath

知識點 現代連接器在處理動態庫時將鏈接時路徑&#xff08;Link-time path&#xff09;和運行時路徑&#xff08;Run-time path&#xff09;分開,用戶可以通過-L指定連接時庫的路徑&#xff0c;通過-R&#xff08;或-rpath&#xff09;指定程序運行時庫的路徑&#xff0c;大大提…

Go學習-Day1

Go學習-Day1 個人博客&#xff1a;CSDN博客 打卡。 Go語言的核心開發團隊&#xff1a; Ken Thompson (C語言&#xff0c;B語言&#xff0c;Unix的發明者&#xff0c;牛人)Rob Pike(UTF-8發明人)Robert Griesemer(協助HotSpot編譯器&#xff0c;Js引擎V8) Go語言有靜態語言的…

MongoDB安裝

文章目錄 MongoDB安裝設置yum源安裝指定版本的mongodb配置文件連接MongoDB的工具MongoDBCompass MongoDB安裝 設置yum源 [rootWDQCVM sbin]# vim /etc/yum.repos.d/mongodb-org-6.0.repo [mongodb-org-6.0] nameMongoDB Repository baseurlhttps://repo.mongodb.org/yum/red…

文件預覽/下載方式:通過二進制流(Blob)下載、或者通過文件Url下載

一、 通過二進制流&#xff08;Blob&#xff09;下載 1 、API請求時候帶上類型 /*** 文件--下載* */ export function download(fphm) {return axios({url: "/ynpst/download-invoice?fphm" fphm,method: get,responseType: blob}) }2、文件預覽和下載 /*** 預覽…

JavaScript如何執行語句

目錄 語法/詞法分析 預編譯 解釋執行 預編譯什么時候發生 js運行三步曲 預編譯前奏 預編譯步驟 鞏固基礎練習 語法/詞法分析 按語句塊的粒度解析成抽象語法樹 ,分析該js腳本代碼塊的語法是否正確&#xff0c;如果出現不正確&#xff0c;則向外拋出一個語法錯誤&#x…

第4章:決策樹

停止 當前分支樣本均為同一類時&#xff0c;變成該類的葉子節點。當前分支類型不同&#xff0c;但是已經沒有可以用來分裂的屬性時&#xff0c;變成類別樣本更多的那個類別的葉子節點。當前分支為空時&#xff0c;變成父節點類別最多的類的葉子節點。 ID3 C4.5 Cart 過擬合 缺…

文本挖掘 day5:文本挖掘與貝葉斯網絡方法識別化學品安全風險因素

文本挖掘與貝葉斯網絡方法識別化學品安全風險因素 1. Introduction現實意義理論意義提出方法&#xff0c;目標 2. 材料與方法2.1 數據集2.2 數據預處理2.3 關鍵字提取2.3.1 TF-IDF2.3.2 改進的BM25——BM25WBM25BM25W 2.3.3 關鍵詞的產生(相關系數) 2.4 關聯規則分析2.5 貝葉斯…

css冒號對齊

實現后的樣式效果 實現方式 html&#xff1a; <el-col v-if"item.showInSingle ! false" :span"6" style"padding: 4px 0"><label>{{ item.label }}&#xff1a;</label><span v-if"singleData[item.prop] ! 0 &…

iOS字體像素與磅的對應關系

注意&#xff1a;低于iOS10的系統&#xff0c;顯示的字寬和字高比高于iOS10的系統小。 這就是iOS10系統發布時&#xff0c;很多app顯示的內容后面出現…&#xff0c;因而出現很多app為了適配iOS10系統而重新發布新版本。 用PS設計的iOS效果圖中&#xff0c;字體是以像素&#x…

SRM訂單管理:優化供應商關系

一、概述SRM訂單管理的概念&#xff1a; SRM訂單管理是指在供應商關系管理過程中&#xff0c;有效管理和控制訂單的創建、處理和交付。它涉及與供應商之間的溝通、合作和協調&#xff0c;旨在實現訂單的準確性、可靠性和及時性。 二、SRM訂單管理的流程&#xff1a; 1. 訂單創…

關于onload事件

onload事件是在網頁中的所有內容&#xff08;包括圖片、樣式表、腳本等&#xff09;都加載完成后觸發的事件。它常用于在頁面加載完成后執行一些操作&#xff0c;例如初始化頁面元素、綁定事件監聽器等。 可以通過以下方式來使用onload事件&#xff1a; 在HTML標簽中直接添加…

NGINX源碼安裝

文章目錄 NGINX源碼安裝安裝依賴包獲取源碼NGINX官方網站各個目錄的用途 編譯安裝安裝結束后的文件設置為服務 NGINX源碼安裝 安裝依賴包 root執行 yum -y install gcc gcc-c make libtool zlib zlib-devel openssl openssl-devel pcre pcre-devel這些包是用于開發和構建軟件…