上传文件
ini
复制代码
<!-- eslint-disable vue/multi-word-component-names -->
<template>
<div class="upload-file">
<el-upload
ref="uploadRef"
:multiple="true"
:action="uploadFileUrl"
:before-upload="handleBeforeUpload"
v-model="fileList"
:file-list="fileList"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
:on-success="handleUploadSuccess"
:show-file-list="false"
:headers="headers"
:auto-upload="true"
class="upload-file-uploader"
>
<!-- 上传按钮 -->
<el-button type="primary" v-show="isShow">选取文件</el-button>
</el-upload>
<!-- 上传提示 -->
<div class="el-upload__tip" v-if="showTip" v-show="isShow">
请上传
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template>
<template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
</template>
的文件
</div>
<!-- 文件列表 -->
<transition-group
class="upload-file-list el-upload-list el-upload-list--text"
name="el-fade-in-linear"
tag="ul"
>
<li
:key="file.uid"
class="el-upload-list__item ele-upload-list__item-content"
v-for="(file, index) in fileList"
>
<el-link :underline="false" target="_blank">
<span class="el-icon-document">
{{ file.attachmentName }}
</span>
</el-link>
<div class="ele-upload-list__item-content-action">
<el-link v-show="isShow" :underline="false" @click="handleDelete(index)" type="danger"
>删除</el-link
>
<el-link
:underline="false"
type="primary"
@click="downloadFile(file)"
v-if="dowloadStatus"
>下载</el-link
>
</div>
</li>
</transition-group>
typescript
复制代码
<script lang="ts" setup>
import cache from '@/utils/cache';
import { log } from 'console';
import { ElMessage, UploadUserFile } from 'element-plus';
import { ref, computed, watch } from 'vue';
import { Download } from '@element-plus/icons-vue';
import { useUserStore } from '@/stores';
const uploadRef = ref();
const props = defineProps({
modelValue: [String, Object, Array],
// 数量限制
limit: {
type: Number,
default: 5
},
// 大小限制(MB)
fileSize: {
type: Number,
default: 1100
},
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array,
default: () => ['doc', 'xls', 'xlsx', 'pdf', 'docx']
},
// 是否显示提示
isShowTip: {
type: Boolean,
default: true
},
//是否显示删除按钮
isShow: {
type: Boolean,
default: true
},
//是否显示下载
dowloadStatus: {
type: Boolean,
default: false
}
});
// @ts-ignore
const { proxy } = getCurrentInstance();
// eslint-disable-next-line vue/valid-define-emits
const emit = defineEmits();
const number = ref(0);
const uploadFileUrl = import.meta.env.VITE_BASE_API + '/minio/upload'; // 上传文件服务器地址
const headers = ref({
Authorization: 'Bearer ' + useUserStore().token,
'bg-debug': 1
});
const fileList = ref<UploadUserFile[]>([]);
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize));
watch(
[() => props.modelValue, () => props.dowloadStatus],
(val: any) => {
if (val) {
fileList.value = props.modelValue;
}
},
{ deep: true, immediate: true }
);
// 上传前校检格式和大小
function handleBeforeUpload(file: { name: string; size: number }) {
// 校检文件类型
if (props.fileType.length) {
const fileName = file.name.split('.');
const fileExt = fileName[fileName.length - 1];
const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
if (!isTypeOk) {
ElMessage.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式文件!`);
return false;
}
}
// 校检文件大小
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
ElMessage.error(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
number.value++;
return true;
}
//下载文件
const downloadPdf = (data: any) => {
const fileName = data.attachmentName;
const fileUrl = data.attachmentPath;
const request = new XMLHttpRequest();
request.responseType = 'blob';
request.open('Get', fileUrl);
request.onload = () => {
const url = window.URL.createObjectURL(request.response);
const a = document.createElement('a');
document.body.appendChild(a);
a.href = url;
a.download = fileName;
a.click();
};
request.send();
};
//下载文件
const downloadFile = (file: { attachmentPath: any; attachmentName: any }) => {
const lastDotIdx = file.attachmentPath.lastIndexOf('.');
const type = file.attachmentPath.slice(lastDotIdx + 1).toUpperCase();
if (type === 'PDF') {
downloadPdf(file);
} else {
const link = document.createElement('a');
link.href = file.attachmentPath;
link.download = file.attachmentName;
document.body.appendChild(link);
link.click();
}
};
// 文件个数超出
function handleExceed() {
ElMessage.error(`上传文件数量不能超过 ${props.limit} 个!`);
}
// 上传失败
function handleUploadError(err: any) {
ElMessage.error('上传文件失败');
}
/** 文件上传成功处理 */
const handleUploadSuccess: UploadProps['onSuccess'] = (
response: { data: { url: any } },
file: { name: any }
) => {
const newFile = { attachmentName: file.name, attachmentPath: response.data.url };
fileList.value.push(newFile);
uploadRef.value.submit();
emit('update:modelValue', fileList.value);
};
// 删除文件
function handleDelete(index: number) {
fileList.value.splice(index, 1);
// @ts-ignore
emit('update:modelValue', fileList.value);
}
</script>
<style scoped lang="scss">
.upload-file-uploader {
margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
border: 1px solid #e4e7ed;
line-height: 2;
margin-bottom: 10px;
position: relative;
}
.upload-file-list .ele-upload-list__item-content {
display: flex;
justify-content: space-between;
align-items: center;
color: inherit;
}
.ele-upload-list__item-content-action .el-link {
margin-right: 10px;
margin-left: 20px;
}
.ele-upload-list__item-content-action .el-icon {
margin-right: 10px;
margin-top: 10px;
}
</style>
上传图片
xml
复制代码
<template>
<div class="pro-upload-img-box">
<div class="pro-upload-img-content">
<!-- 已上传图片列表 -->
<div
class="upload-img-card"
v-for="(item, index) in fileList"
:key="index"
>
<!-- 图片预览 -->
<el-image
class="img-sty"
:preview-src-list="[item.url]"
fit="cover"
:src="item.url"
alt=""
/>
<!-- 删除按钮 -->
<el-image
v-if="!disabled"
src="https://static.wxb.com.cn/frontEnd/images/ideacome-vue3-component/img-close.png"
class="img-close"
@click="handleRemove(item, index)"
/>
<!-- 图片遮罩层 -->
<div class="img-mask">
<el-image
src="https://static.wxb.com.cn/frontEnd/images/ideacome-vue3-component/img-preview.png"
class="img-preview"
/>
</div>
</div>
<!-- 上传组件 -->
<el-upload
v-loading="loading"
ref="proUploadImgRef"
:class="['pro-upload-img', { 'is-disabled': disabled }]"
v-bind="uploadProps"
:before-upload="beforeUpload"
:on-success="handleSuccess"
:on-error="handleError"
:on-exceed="handleExceed"
>
<slot>
<div class="upload-card">
<el-icon class="upload-icon" style="font-size: 30px;">
<CirclePlus />
</el-icon>
<div v-if="uploadText" class="upload-text">
{{ uploadText }}
</div>
</div>
</slot>
</el-upload>
</div>
<!-- 提示信息 -->
<slot name="tip">
<div class="upload-tip" v-if="tip">
{{ tip }}
</div>
</slot>
</div>
</template>
<script setup name="ProUploadImg">
import { ref, computed } from 'vue';
import { Plus } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
// Props 定义
const props = defineProps({
/** 上传地址 */
action: {
type: String,
required: true,
},
/** 请求头 */
headers: {
type: Object,
default: () => ({}),
},
/** 是否支持多选 */
multiple: {
type: Boolean,
default: false,
},
/** 最大上传数量,0表示不限制 */
limit: {
type: Number,
default: 0,
},
/** 接受的文件类型,如:.jpg,.png,.jpeg */
accept: {
type: String,
default: '.jpg,.png,.jpeg',
},
/** 文件大小限制 */
maxSize: {
type: Number,
default: 0,
},
/** 文件大小单位(KB/MB) */
sizeUnit: {
type: String,
default: 'MB',
validator: (value) => ['KB', 'MB'].includes(value),
},
/** 图片宽度限制 */
width: {
type: Number,
default: 0,
},
/** 图片高度限制 */
height: {
type: Number,
default: 0,
},
/** 上传提示文字 */
uploadText: {
type: String,
default: '点击上传',
},
/** 上传提示说明 */
tip: {
type: String,
default: '',
},
/** 是否禁用 */
disabled: {
type: Boolean,
default: false,
},
});
/** 初始文件列表 */
const fileList = defineModel('fileList', {
type: Array,
default: () => [],
});
// 事件定义
const emit = defineEmits(['success', 'error', 'exceed', 'remove']);
const proUploadImgRef = ref();
const loading = ref(false);
const uploadProps = computed(() => ({
action: props.action,
accept: props.accept,
limit: props.limit,
multiple: props.multiple,
listType: 'picture-card',
showFileList: false,
headers: props.headers,
fileList: fileList.value,
disabled: props.disabled,
}));
/**
* 验证图片尺寸是否符合要求
* @param {number} width - 图片宽度
* @param {number} height - 图片高度
* @returns {boolean} 是否符合要求
*/
const validateImageSize = (width, height) => {
if (props.width && props.height) {
return width === props.width && height === props.height;
}
if (props.width) {
return width === props.width;
}
if (props.height) {
return height === props.height;
}
return true;
};
/**
* 上传前校验
* @param {File} file - 待上传的文件
* @returns {Promise<boolean>} 是否通过校验
*/
const beforeUpload = async (file) => {
// 校验文件类型
const fileTypeList = props.accept
.split(',')
.map((item) => item.replace('.', ''));
const fileType = file.name.split('.').pop();
if (!fileTypeList.includes(fileType)) {
ElMessage({
message: `仅支持 ${fileTypeList.join('、')} 格式`,
type: 'warning',
});
return false;
}
// 校验文件大小
if (props.maxSize) {
const fileSize = file.size / 1024;
const maxSizeInKB =
props.sizeUnit === 'MB' ? props.maxSize * 1024 : props.maxSize;
if (fileSize > maxSizeInKB) {
ElMessage({
message: `大小不能超过 ${props.maxSize}${props.sizeUnit}!`,
type: 'warning',
});
return false;
}
}
// 校验图片尺寸
// return new Promise((resolve, reject) => {
// const img = new Image();
// img.src = URL.createObjectURL(file);
// img.onload = () => {
// URL.revokeObjectURL(img.src);
// const { width, height } = img;
// if (!validateImageSize(width, height)) {
// const message =
// props.width && props.height
// ? `图片尺寸必须为 ${props.width}x${props.height}`
// : props.width
// ? `图片宽度必须为 ${props.width}px`
// : `图片高度必须为 ${props.height}px`;
// ElMessage({
// message,
// type: 'warning',
// });
// reject(false);
// return;
// }
// loading.value = true;
// resolve(true);
// };
// img.onerror = () => {
// URL.revokeObjectURL(img.src);
// ElMessage({
// message: '图片加载失败',
// type: 'error',
// });
// reject(false);
// };
// });
};
/**
* 上传成功回调
* @param {Object} response - 服务器响应数据
* @param {Object} uploadFile - 上传文件对象
* @param {Array} uploadFiles - 上传文件列表
*/
const handleSuccess = (response, uploadFile, uploadFiles) => {
console.log(response, uploadFile, uploadFiles,12345666)
loading.value = false;
if (response.code === 200) {
fileList.value.push({ url: response.data.url });
console.log(fileList.value,12345)
} else {
proUploadImgRef.value.handleRemove(uploadFile);
ElMessage({
message: response.msg || response.message || '上传失败',
type: 'error',
});
}
emit('success', response, uploadFile, uploadFiles);
};
/**
* 上传失败回调
* @param {Error} error - 错误信息
* @param {Object} uploadFile - 上传文件对象
* @param {Array} uploadFiles - 上传文件列表
*/
const handleError = (error, uploadFile, uploadFiles) => {
loading.value = false;
ElMessage({
message: '上传失败',
type: 'error',
});
emit('error', error, uploadFile, uploadFiles);
};
/**
* 超出限制回调
* @param {Array} files - 超出限制的文件列表
* @param {Array} uploadFiles - 已上传的文件列表
*/
const handleExceed = (files, uploadFiles) => {
ElMessage({
message: `最多只能上传 ${props.limit} 张图片`,
type: 'warning',
});
emit('exceed', files, uploadFiles);
};
/**
* 移除图片
* @param {Object} file - 要移除的文件对象
* @param {number} index - 文件索引
*/
const handleRemove = (file, index) => {
fileList.value.splice(index, 1);
proUploadImgRef.value.handleRemove(file);
emit('remove', file);
};
</script>
<style lang="scss" scoped>
.pro-upload-img-box {
.pro-upload-img-content {
display: flex;
flex-wrap: wrap;
// 已上传图片卡片样式
.upload-img-card {
width: 100px;
height: 100px;
position: relative;
margin: 0 12px 12px 0;
// 图片样式
.img-sty {
width: 100%;
height: 100%;
overflow: hidden;
border-radius: 6px;
}
// 删除按钮样式
.img-close {
position: absolute;
right: -6px;
top: -6px;
width: 20px;
height: 20px;
cursor: pointer;
z-index: 2;
}
// 遮罩层样式
.img-mask {
background: rgba(0, 0, 0, 0.3);
border-radius: 6px;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
.img-preview {
position: absolute;
right: 8px;
bottom: 8px;
width: 20px;
height: 20px;
pointer-events: none;
}
}
}
// 禁用状态样式
.is-disabled {
:deep(.el-upload--picture-card) {
cursor: not-allowed;
}
}
// 上传按钮样式
.pro-upload-img {
margin-bottom: 12px;
.upload-card {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.upload-icon {
font-size: 20px;
color: #333;
text-align: center;
line-height: 100px;
}
.upload-text {
line-height: 24px;
color: #333;
font-size: 14px;
text-align: center;
margin-top: 10px;
}
}
}
// 上传组件样式覆盖
:deep(.el-upload--picture-card) {
width: 100px;
height: 100px;
background-color: #F8F8F9;
}
:deep(.el-upload-list__item) {
width: auto;
height: auto;
overflow: visible;
}
}
// 提示文字样式
.upload-tip {
font-size: 12px;
color: #909399;
}
}
</style>
上传视频
xml
复制代码
<template>
<div class="pro-upload-video-box">
<div class="pro-upload-video-content">
<!-- 已上传视频列表 -->
<div
class="upload-video-card"
v-for="(item, index) in fileList"
:key="index"
>
<!-- 视频缩略图/播放按钮 -->
<div class="video-thumbnail" @click="playVideo(item.url)">
<el-icon class="video-icon"><VideoPlay /></el-icon>
</div>
<!-- 视频信息 -->
<div class="video-info" @click="playVideo(item.url)">
<div class="video-name">{{ getFileName(item.url) }}</div>
<div class="video-size">{{ getFileSize(item.size) }}</div>
</div>
<!-- 删除按钮 -->
<el-image
v-if="!disabled"
src="https://static.wxb.com.cn/frontEnd/images/ideacome-vue3-component/img-close.png"
class="video-close"
@click="handleRemove(item, index)"
/>
</div>
<!-- 上传组件 -->
<el-upload
v-if="!disabled"
v-loading="loading"
ref="proUploadVideoRef"
:class="['pro-upload-video', { 'is-disabled': disabled }]"
v-bind="uploadProps"
:before-upload="beforeUpload"
:on-success="handleSuccess"
:on-error="handleError"
:on-exceed="handleExceed"
:on-progress="handleProgress"
>
<slot>
<div class="upload-card">
<el-icon class="upload-icon">
<Plus />
</el-icon>
<div v-if="uploadText" class="upload-text">
{{ uploadText }}
</div>
</div>
</slot>
</el-upload>
</div>
<!-- 提示信息 -->
<slot name="tip" v-if="!disabled">
<div class="upload-tip" v-if="tip">
{{ tip }}
</div>
</slot>
</div>
</template>
<script setup name="ProUploadVideo">
import { ref, computed } from 'vue';
import { Plus, VideoPlay } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
// Props 定义
const props = defineProps({
/** 上传地址 */
action: {
type: String,
required: true,
},
/** 请求头 */
headers: {
type: Object,
default: () => ({}),
},
/** 是否支持多选 */
multiple: {
type: Boolean,
default: false,
},
/** 最大上传数量,0表示不限制 */
limit: {
type: Number,
default: 0,
},
/** 接受的文件类型,如:.mp4,.avi,.mov */
accept: {
type: String,
default: '.mp4,.avi,.mov,.wmv,.flv,.webm',
},
/** 文件大小限制 */
maxSize: {
type: Number,
default: 0,
},
/** 文件大小单位(KB/MB) */
sizeUnit: {
type: String,
default: 'MB',
validator: (value) => ['KB', 'MB'].includes(value),
},
/** 上传提示文字 */
uploadText: {
type: String,
default: '上传视频',
},
/** 上传提示说明 */
tip: {
type: String,
default: '',
},
/** 是否禁用 */
disabled: {
type: Boolean,
default: false,
},
});
/** 初始文件列表 */
const fileList = defineModel('fileList', {
type: Array,
default: () => [],
});
// 事件定义
const emit = defineEmits(['success', 'error', 'exceed', 'remove', 'deleteAnnex', 'progress']);
const proUploadVideoRef = ref();
const loading = ref(false);
const uploadProps = computed(() => ({
action: props.action,
accept: props.accept,
limit: props.limit,
multiple: props.multiple,
listType: 'text',
showFileList: false,
headers: props.headers,
fileList: fileList.value,
disabled: props.disabled,
}));
/**
* 获取文件名
* @param {string} url - 文件路径
* @returns {string} 文件名
*/
const getFileName = (url) => {
if (!url) return '';
const fileName = url.substring(url.lastIndexOf('/') + 1);
return fileName.length > 15 ? fileName.substring(0, 15) + '...' : fileName;
};
/**
* 获取文件大小显示
* @param {number} size - 文件大小(字节)
* @returns {string} 格式化后的文件大小
*/
const getFileSize = (size) => {
if (!size) return '';
const units = ['B', 'KB', 'MB', 'GB'];
let unitIndex = 0;
let fileSize = size;
while (fileSize >= 1024 && unitIndex < units.length - 1) {
fileSize /= 1024;
unitIndex++;
}
return `${fileSize.toFixed(2)} ${units[unitIndex]}`;
};
/**
* 上传前校验
* @param {File} file - 待上传的文件
* @returns {Promise<boolean>} 是否通过校验
*/
const beforeUpload = async (file) => {
// 校验文件类型
const fileTypeList = props.accept
.split(',')
.map((item) => item.replace('.', '').toLowerCase());
const fileType = file.name.split('.').pop().toLowerCase();
if (!fileTypeList.includes(fileType)) {
ElMessage({
message: `仅支持 ${props.accept} 格式`,
type: 'warning',
});
return false;
}
// 校验文件大小
if (props.maxSize) {
const fileSize = file.size;
const maxSizeInBytes =
props.sizeUnit === 'MB' ? props.maxSize * 1024 * 1024 : props.maxSize * 1024;
if (fileSize > maxSizeInBytes) {
ElMessage({
message: `大小不能超过 ${props.maxSize}${props.sizeUnit}!`,
type: 'warning',
});
return false;
}
}
loading.value = true;
return true;
};
/**
* 上传进度回调
* @param {Object} event - 进度事件对象
* @param {Object} uploadFile - 上传文件对象
* @param {Array} uploadFiles - 上传文件列表
*/
const handleProgress = (event, uploadFile, uploadFiles) => {
emit('progress', event, uploadFile, uploadFiles);
};
/**
* 上传成功回调
* @param {Object} response - 服务器响应数据
* @param {Object} uploadFile - 上传文件对象
* @param {Array} uploadFiles - 上传文件列表
*/
const handleSuccess = (response, uploadFile, uploadFiles) => {
loading.value = false;
if (response.code === 200) {
fileList.value.push({
url: response.data.url,
name: uploadFile.name,
size: uploadFile.size
});
} else {
proUploadVideoRef.value.handleRemove(uploadFile);
ElMessage({
message: response.msg || response.message || '上传失败',
type: 'error',
});
}
emit('success', response, uploadFile, uploadFiles);
};
/**
* 上传失败回调
* @param {Error} error - 错误信息
* @param {Object} uploadFile - 上传文件对象
* @param {Array} uploadFiles - 上传文件列表
*/
const handleError = (error, uploadFile, uploadFiles) => {
loading.value = false;
ElMessage({
message: '上传失败',
type: 'error',
});
emit('error', error, uploadFile, uploadFiles);
};
/**
* 超出限制回调
* @param {Array} files - 超出限制的文件列表
* @param {Array} uploadFiles - 已上传的文件列表
*/
const handleExceed = (files, uploadFiles) => {
ElMessage({
message: `最多只能上传 ${props.limit} 个视频`,
type: 'warning',
});
emit('exceed', files, uploadFiles);
};
/**
* 移除视频
* @param {Object} file - 要移除的文件对象
* @param {number} index - 文件索引
*/
const handleRemove = (file, index) => {
fileList.value.splice(index, 1);
proUploadVideoRef.value.handleRemove(file);
emit('deleteAnnex', index);
};
/**
* 播放视频
* @param {string} url - 视频地址
*/
const playVideo = (url) => {
if (!url) return;
// 创建视频播放弹窗
const videoDialog = document.createElement('div');
videoDialog.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
`;
// 创建视频元素
const videoWrapper = document.createElement('div');
videoWrapper.style.cssText = `
position: relative;
max-width: 90%;
max-height: 90%;
`;
// 创建加载提示
const loadingIndicator = document.createElement('div');
loadingIndicator.innerHTML = '视频加载中...';
loadingIndicator.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 16px;
z-index: 1;
`;
videoWrapper.appendChild(loadingIndicator);
const videoElement = document.createElement('video');
videoElement.controls = true;
videoElement.autoplay = true;
videoElement.style.cssText = `
max-width: 100%;
max-height: 80vh;
outline: none;
background: black;
display: none; /* 初始隐藏,等待加载完成后再显示 */
`;
// 尝试多种视频格式
const fileExtension = url.split('.').pop().toLowerCase();
const sourceElement = document.createElement('source');
sourceElement.src = url;
// 根据文件扩展名设置正确的 MIME 类型
const mimeTypes = {
'mp4': 'video/mp4',
'webm': 'video/webm',
'ogg': 'video/ogg',
'avi': 'video/avi',
'mov': 'video/quicktime',
'wmv': 'video/x-ms-wmv',
'flv': 'video/x-flv'
};
sourceElement.type = mimeTypes[fileExtension] || 'video/mp4';
videoElement.appendChild(sourceElement);
// 视频加载成功的处理
videoElement.onloadeddata = () => {
// 隐藏加载指示器并显示视频
if (videoWrapper.contains(loadingIndicator)) {
videoWrapper.removeChild(loadingIndicator);
}
videoElement.style.display = 'block';
};
// 视频加载失败的处理
videoElement.onerror = () => {
// 隐藏加载指示器
if (videoWrapper.contains(loadingIndicator)) {
videoWrapper.removeChild(loadingIndicator);
}
// 显示错误信息
const errorIndicator = document.createElement('div');
errorIndicator.innerHTML = '视频加载失败,请稍后重试';
errorIndicator.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #ff6b6b;
font-size: 16px;
background: rgba(0, 0, 0, 0.7);
padding: 10px 20px;
border-radius: 4px;
z-index: 1;
`;
videoWrapper.appendChild(errorIndicator);
// 3秒后自动关闭
setTimeout(() => {
if (document.body.contains(videoDialog)) {
document.body.removeChild(videoDialog);
}
}, 3000);
};
// 创建关闭按钮
const closeButton = document.createElement('button');
closeButton.innerHTML = '×';
closeButton.style.cssText = `
position: absolute;
top: -40px;
right: 0;
background: transparent;
border: none;
color: white;
font-size: 36px;
cursor: pointer;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.2s;
`;
closeButton.onmouseover = () => {
closeButton.style.transform = 'scale(1.1)';
};
closeButton.onmouseout = () => {
closeButton.style.transform = 'scale(1)';
};
closeButton.onclick = () => {
// 暂停视频并移除弹窗
videoElement.pause();
if (document.body.contains(videoDialog)) {
document.body.removeChild(videoDialog);
}
};
videoWrapper.appendChild(videoElement);
videoWrapper.appendChild(closeButton);
videoDialog.appendChild(videoWrapper);
document.body.appendChild(videoDialog);
// 点击背景关闭
videoDialog.onclick = (e) => {
if (e.target === videoDialog) {
videoElement.pause();
if (document.body.contains(videoDialog)) {
document.body.removeChild(videoDialog);
}
}
};
// ESC键关闭
const handleEscKey = (e) => {
if (e.key === 'Escape') {
videoElement.pause();
if (document.body.contains(videoDialog)) {
document.body.removeChild(videoDialog);
}
document.removeEventListener('keydown', handleEscKey);
}
};
document.addEventListener('keydown', handleEscKey);
};
</script>
<style lang="scss" scoped>
.pro-upload-video-box {
.pro-upload-video-content {
display: flex;
// flex-wrap: wrap;
// 已上传视频卡片样式
.upload-video-card {
width: 100%;
max-width: 300px;
height: 100px;
position: relative;
margin: 0 12px 12px 0;
display: flex;
align-items: center;
border: 1px solid #ebeef5;
border-radius: 6px;
padding: 10px;
box-sizing: border-box;
// 视频缩略图样式
.video-thumbnail {
width: 50px;
height: 50px;
background-color: #ecf5ff;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background-color: #409eff;
.video-icon {
color: white;
}
}
.video-icon {
font-size: 24px;
color: #409eff;
}
}
// 视频信息样式
.video-info {
flex: 1;
min-width: 0;
cursor: pointer;
.video-name {
font-size: 14px;
color: #606266;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 5px;
}
.video-size {
font-size: 12px;
color: #909399;
}
}
// 删除按钮样式
.video-close {
position: absolute;
right: -8px;
top: -8px;
width: 20px;
height: 20px;
cursor: pointer;
z-index: 2;
}
}
// 禁用状态样式
.is-disabled {
:deep(.el-upload--text) {
cursor: not-allowed;
}
}
// 上传按钮样式
.pro-upload-video {
margin-bottom: 12px;
.upload-card {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100px;
height: 100px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
transition: border-color 0.3s;
&:hover {
border-color: #409eff;
}
.upload-icon {
font-size: 28px;
color: #8c939d;
margin-bottom: 5px;
}
.upload-text {
line-height: 24px;
color: #8c939d;
font-size: 14px;
text-align: center;
}
}
}
// 上传组件样式覆盖
:deep(.el-upload) {
width: auto;
height: auto;
}
}
// 提示文字样式
.upload-tip {
font-size: 12px;
color: #909399;
}
}
</style>