最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vue3上傳文件、圖片、視頻組件實例代碼

 更新時間:2026年05月22日 09:16:47   作者:小周同學  
文件上傳是一個老生常談的話題了,這篇文章主要介紹了vue3上傳文件、圖片、視頻組件的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下

上傳文件

<!-- 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>
<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],
  // 數(shù)量限制
  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'; // 上傳文件服務(wù)器地址
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();
  }
};
// 文件個數(shù)超出
function handleExceed() {
  ElMessage.error(`上傳文件數(shù)量不能超過 ${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>

上傳圖片

<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"
      >
        <!-- 圖片預(yù)覽 -->
        <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,
    },
    /** 最大上傳數(shù)量,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);
    //   };
    // });
  };
  /**
   * 上傳成功回調(diào)
   * @param {Object} response - 服務(wù)器響應(yīng)數(shù)據(jù)
   * @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);
  };
  /**
   * 上傳失敗回調(diào)
   * @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);
  };
  /**
   * 超出限制回調(diào)
   * @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;
        }
      }
    }
    // 禁用狀態(tài)樣式
    .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>

上傳視頻

<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,
    },
    /** 最大上傳數(shù)量,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 - 文件大小(字節(jié))
   * @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;
  };

  /**
   * 上傳進度回調(diào)
   * @param {Object} event - 進度事件對象
   * @param {Object} uploadFile - 上傳文件對象
   * @param {Array} uploadFiles - 上傳文件列表
   */
  const handleProgress = (event, uploadFile, uploadFiles) => {
    emit('progress', event, uploadFile, uploadFiles);
  };

  /**
   * 上傳成功回調(diào)
   * @param {Object} response - 服務(wù)器響應(yīng)數(shù)據(jù)
   * @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);
  };

  /**
   * 上傳失敗回調(diào)
   * @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);
  };

  /**
   * 超出限制回調(diào)
   * @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;

    // 創(chuàng)建視頻播放彈窗
    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;
    `;

    // 創(chuàng)建視頻元素
    const videoWrapper = document.createElement('div');
    videoWrapper.style.cssText = `
      position: relative;
      max-width: 90%;
      max-height: 90%;
    `;

    // 創(chuàng)建加載提示
    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;

    // 根據(jù)文件擴展名設(shè)置正確的 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秒后自動關(guān)閉
      setTimeout(() => {
        if (document.body.contains(videoDialog)) {
          document.body.removeChild(videoDialog);
        }
      }, 3000);
    };

    // 創(chuàng)建關(guān)閉按鈕
    const closeButton = document.createElement('button');
    closeButton.innerHTML = '&times;';
    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);

    // 點擊背景關(guān)閉
    videoDialog.onclick = (e) => {
      if (e.target === videoDialog) {
        videoElement.pause();
        if (document.body.contains(videoDialog)) {
          document.body.removeChild(videoDialog);
        }
      }
    };

    // ESC鍵關(guān)閉
    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;
      }
    }

    // 禁用狀態(tài)樣式
    .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>

總結(jié) 

到此這篇關(guān)于vue3上傳文件、圖片、視頻組件的文章就介紹到這了,更多相關(guān)vue3上傳文件、圖片、視頻組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用vue-router完成簡單導航功能【推薦】

    使用vue-router完成簡單導航功能【推薦】

    vue-router是Vue.js官方提供的一套專用的路由工具庫。這篇文章主要介紹了使用vue-router完成簡單導航功能,需要的朋友可以參考下
    2018-06-06
  • vue鼠標懸停事件監(jiān)聽實現(xiàn)方法

    vue鼠標懸停事件監(jiān)聽實現(xiàn)方法

    頁面在鼠標懸停(不動)n秒之后,頁面進行相應(yīng)的事件,下面這篇文章主要給大家介紹了關(guān)于vue鼠標懸停事件監(jiān)聽的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-09-09
  • vue如何使用 Slot 分發(fā)內(nèi)容實例詳解

    vue如何使用 Slot 分發(fā)內(nèi)容實例詳解

    本篇文章主要介紹了vue如何使用 Slot 分發(fā)內(nèi)容實例詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • vue動態(tài)組件實現(xiàn)選項卡切換效果

    vue動態(tài)組件實現(xiàn)選項卡切換效果

    這篇文章主要為大家詳細介紹了vue動態(tài)組件實現(xiàn)選項卡切換效果的相關(guān)資料,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • element-plus+Vue3實現(xiàn)表格數(shù)據(jù)動態(tài)渲染

    element-plus+Vue3實現(xiàn)表格數(shù)據(jù)動態(tài)渲染

    在Vue中,el-table是element-ui提供的強大表格組件,可以用于展示靜態(tài)和動態(tài)表格數(shù)據(jù),本文主要介紹了element-plus+Vue3實現(xiàn)表格數(shù)據(jù)動態(tài)渲染,感興趣的可以了解一下
    2024-03-03
  • vue3中引入class類的寫法代碼示例

    vue3中引入class類的寫法代碼示例

    最近一直在做vue項目,從網(wǎng)上搜索到的資料不太多,這篇文章主要給大家介紹了關(guān)于vue3中引入class類的寫法的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-05-05
  • Vue3響應(yīng)式對象是如何實現(xiàn)的(2)

    Vue3響應(yīng)式對象是如何實現(xiàn)的(2)

    這篇文章主要介紹了Vue3響應(yīng)式對象是如何實現(xiàn)的,文章基于上篇文章展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-08-08
  • vue項目引入百度地圖BMapGL鼠標繪制和BMap輔助工具

    vue項目引入百度地圖BMapGL鼠標繪制和BMap輔助工具

    這篇文章主要為大家介紹了vue項目引入百度地圖BMapGL鼠標繪制和BMap輔助工具的踩坑分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • Vue vm.$attrs使用場景詳解

    Vue vm.$attrs使用場景詳解

    這篇文章主要介紹了vm.$attrs使用場景詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-03-03
  • vue+element 實現(xiàn)商城主題開發(fā)的示例代碼

    vue+element 實現(xiàn)商城主題開發(fā)的示例代碼

    這篇文章主要介紹了vue+element 實現(xiàn)商城主題開發(fā)的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-03-03

最新評論

扶绥县| 元谋县| 南阳市| 河津市| 霸州市| 叙永县| 仁怀市| 什邡市| 阳谷县| 博乐市| 木里| 靖宇县| 津南区| 昭平县| 正镶白旗| 驻马店市| 临湘市| 锦屏县| 苏尼特左旗| 通道| 香格里拉县| 甘南县| 湘潭市| 霍林郭勒市| 哈巴河县| 安陆市| 呼和浩特市| 盐边县| 龙州县| 封开县| 临洮县| 定州市| 大冶市| 乌拉特中旗| 梨树县| 福海县| 阿鲁科尔沁旗| 莎车县| 定西市| 西青区| 胶州市|