【sgFileViewDialog】自定义组件:基于video、audio、el-image标签、PDF.js、l-dialog构建弹窗预览视频、音频、图片、文档文件

特性更新:

2025.05.27----------------------------------------

  1. 支持pdf预览默认左侧展开侧边栏目录
  2. 支持默认【适合页面】全部显示
  3. 默认【双页模式】

sgFileViewDialog.vue 

<template>
  <!-- 文件预览弹窗 -->
  <div :class="$options.name" v-if="visible">
    <!-- 如果不加v-if="visible"弹窗中使用el-tabs组件就会死循环卡死,这个是elementUI的bug -->
    <el-dialog
      :append-to-body="true"
      :close-on-click-modal="true"
      :close-on-press-escape="true"
      :custom-class="`${$options.name}-el-dialog ${
        suffixCN === `文档` ? `noRadius` : ``
      }`"
      :destroy-on-close="true"
      :fullscreen="fullscreen"
      :show-close="true"
      :title="`预览${form.NAME || form.filename || suffixCN}`"
      :top="dialogTop"
      :width="`${dialogWidth}px`"
      :visible.sync="visible"
      v-if="suffixCN !== `图片`"
      @keyup.ctrl.enter.native="save"
      style="animation: none"
      v-loading="loading"
      :element-loading-text="elementLoadingText"
    >
      <div class="video-container" v-if="suffixCN === `视频`">
        <!-- 视频 -->
        <video
          :autoplay="typeof form.autoplay === 'undefined' ? true : form.autoplay"
          class="video"
          controls
          :loop="form.loop"
          :muted="form.muted"
          playsinline="true"
          preload
          ref="video"
          :src="form.src"
          webkit-playsinline="true"
          controlslist="nodownload"
        />
      </div>

      <div class="audio-container" v-else-if="suffixCN === `音频`">
        <!-- 音频 -->
        <audio
          :autoplay="typeof form.autoplay === 'undefined' ? true : form.autoplay"
          class="audio"
          controls
          :loop="form.loop"
          :muted="form.muted"
          preload
          ref="audio"
          :src="form.src"
          webkit-playsinline="true"
          controlslist="nodownload"
        />
      </div>
      <div class="document-container" v-else-if="suffixCN === `文档`">
        <iframe
          ref="iframe"
          @load="load_iframe"
          :src="form.src"
          frameborder="no"
          scrolling="no"
          style="position: absolute; left: 0; top: 0; width: 100%; height: 100%"
        />
      </div>
    </el-dialog>
    <!-- 图片 -->
    <el-image
      v-if="suffixCN === `图片`"
      ref="image"
      style="display: none"
      src=""
      :initial-index="form.initialIndex || 0"
      :preview-src-list="form.previewSrcList || [form.src]"
    />
  </div>
</template>
<script>
export default {
  name: "sgFileViewDialog",
  components: {},
  data() {
    return {
      spreadMode: 1, //默认双页模式
      currentTime: 0,
      fullscreen: false,
      dialogWidth: 1000,
      dialogTop: ``,
      suffix: ``,
      suffixCN: ``,
      // 视频、音频、图片、文档格式后缀名----------------------------------------
      videoSuffixs: [
        "mp4",
        "wmv",
        "mpeg",
        "mpg",
        "avi",
        "mov",
        "rm",
        "flv",
        "swf",
        "mkv",
      ],

      audioSuffixs: ["mp3", "m4a", "ogg", "wav", "flac", "aac"],

      imageSuffixs: ["jpg", "jpeg", "gif", "bmp", "png", "svg"],

      documentSuffixs: [
        // office文档
        "doc",
        "docx",
        "xls",
        "xlsx",
        "ppt",
        "pptx",

        "pdf", //Adobe Reader 文档
        "txt", //记事本

        // 其他文档
        "ofd",
        "xml",
        "rtf",
        "csv",
      ],
      // ----------------------------------------

      loading: false, //加载状态
      elementLoadingText: ``, //加载提示文字

      visible: false,
      form: {}, //表单信息
      disabled: false, //是否只读
      labelWidth: `120px`,
    };
  },
  props: ["value", "data"],
  computed: {},
  watch: {
    loading(newValue, oldValue) {
      newValue || (this.elementLoadingText = ``);
    },
    value: {
      handler(d) {
        this.visible = d;
        // 每次显示的时候,重置一些参数值
        if (d) {
        }
      },
      deep: true,
      immediate: true,
    },
    visible(d) {
      this.$nextTick(() => {
        this.$refs.image && (this.$refs.image.showViewer = d); //隐藏大图
      });
      this.$emit("input", d);
    },

    data: {
      handler(newValue, oldValue) {
        // console.log(`深度监听${this.$options.name}:`, newValue, oldValue);
        if (Object.keys(newValue || {}).length) {
          this.form = JSON.parse(JSON.stringify(newValue));
          this.$g.convertForm2ComponentParam(`spreadMode`, this);
          this.disabled = this.form.disabled;

          this.suffix = (this.form.src || ``).split(".").slice(-1)[0];
          this.suffix.length > 10 &&
            (this.suffix = (this.form.NAME || ``).split(".").slice(-1)[0]); //后缀名超过10的长度就不正常,需要重新通过名称获取后缀名
          this.form.suffix && (this.suffix = this.form.suffix);

          // 判断文件格式后缀中文
          if (this.videoSuffixs.includes(this.suffix)) {
            this.suffixCN = `视频`;
            this.dialogWidth = 1000;
            this.dialogTop = `calc((100vh - 560px) / 2 - 70px)`;
            this.playRecordCurrentTime();
            this.addEvents();
          }
          if (this.audioSuffixs.includes(this.suffix)) {
            this.suffixCN = `音频`;
            this.dialogWidth = 400;
            this.dialogTop = `calc(100vh / 2 - 70px)`;
            this.playRecordCurrentTime();
            this.addEvents();
          }
          if (
            this.imageSuffixs.includes(this.suffix) ||
            this.$g.image.isBase64Image(this.form.src)
          ) {
            this.suffixCN = `图片`;
            this.$nextTick(
              () => this.$refs.image && (this.$refs.image.showViewer = true)
            ); //显示大图
          }
          if (this.documentSuffixs.includes(this.suffix)) {
            this.suffixCN = `文档`;
            this.fullscreen = true;
            let { src, filename, keyword, keywords } = this.form;
            this.form.src = this.$g.getPDFsrc({ file: src, filename, keyword, keywords });
          }
        } else {
          this.disabled = false; //添加的时候,编辑态
          this.form = {
            // 默认字段名: 缺省值,
          };
        }
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
    currentTime(d) {
      localStorage[`${this.$options.name}/${this.form.src}/currentTime`] = d;
    },
  },
  created() {},
  mounted() {},
  beforeDestroy() {
    this.removeEvents();
  },
  methods: {
    // 播放上次记录的位置----------------------------------------
    playRecordCurrentTime(d) {
      this.$nextTick(() => {
        let currentTime =
          localStorage[`${this.$options.name}/${this.form.src}/currentTime`];
        if (currentTime) {
          this.$refs.video && (this.$refs.video.currentTime = currentTime); //播放上次的位置
          this.$refs.audio && (this.$refs.audio.currentTime = currentTime); //播放上次的位置
        }
      });
    },
    // 记录视频、音频播放到哪里了----------------------------------------
    addEvents(d) {
      this.$nextTick(() => {
        this.removeEvents();
        this.$refs.video &&
          this.$refs.video.addEventListener("timeupdate", this.timeupdate);
        this.$refs.audio &&
          this.$refs.audio.addEventListener("timeupdate", this.timeupdate);
        this.timeupdate();
      });
    },
    removeEvents(d) {
      this.$refs.video &&
        this.$refs.video.removeEventListener("timeupdate", this.timeupdate);
      this.$refs.audio &&
        this.$refs.audio.removeEventListener("timeupdate", this.timeupdate);
    },
    timeupdate(d) {
      this.$refs.video && (this.currentTime = this.$refs.video.currentTime);
      this.$refs.audio && (this.currentTime = this.$refs.audio.currentTime);
    },
    // PDF预览■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
    load_iframe(d) {
      // iframe加载完成后等待一秒再对PDF进行操作,否则可能没有加载完PDF相关组件无法操作----------------------------------------
      setTimeout(() => {
        this.$nextTick(() => {
          this.PDFViewerApplication = this.getPDFViewerApplication();
          this.pdfSidebar({ command: `open` }); //打开左侧侧边栏
          this.pdfSpreadMode({ spreadMode: this.spreadMode }); //回显单双页模式
        });
      }, 1000);
    },
    // 获取PDF控制器
    getPDFViewerApplication() {
      let PDFViewerApplication;
      if (this.$refs.iframe && this.$refs.iframe.contentWindow) {
        PDFViewerApplication = this.$refs.iframe.contentWindow.PDFViewerApplication;
      }
      return PDFViewerApplication;
    },
    // 跳转到封面
    // 跳转到目录
    jumpPage({
      PDFViewerApplication = this.getPDFViewerApplication(),
      currentPageNumber = 1,
    } = {}) {
      PDFViewerApplication.pdfViewer.currentPageNumber = currentPageNumber;
    },
    // 单双页模式
    pdfSpreadMode({
      PDFViewerApplication = this.getPDFViewerApplication(),
      spreadMode = 1,
    } = {}) {
      /* spreadMode:
      单页模式: 0,
      双页模式: 1,
      书籍模式: 2 */
      PDFViewerApplication && (PDFViewerApplication.pdfViewer.spreadMode = spreadMode);
    },
    // 打开侧边栏
    pdfSidebar({
      PDFViewerApplication = this.getPDFViewerApplication(),
      command = `open`,
    } = {}) {
      PDFViewerApplication && PDFViewerApplication.pdfSidebar[command]();
    },

    // 适合页面
    pdf_page_fit(d) {
      let scaleSelect = this.$refs.iframe.contentDocument.querySelector(`#scaleSelect`);
      scaleSelect && (scaleSelect.value = `page-fit`);
    },
    // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
    cancel() {
      this.visible = false;
    },
  },
};
</script>
<style lang="scss" scoped>
.sgFileViewDialog {
}

.el-dialog__wrapper:has(.sgFileViewDialog-el-dialog) {
  /*遮罩模糊*/
  backdrop-filter: blur(5px);
}
>>> .sgFileViewDialog-el-dialog {
  border-radius: 30px;
  &.noRadius {
    border-radius: 0;
  }
  box-shadow: 0 10px 50px 0 #00000033;
  //关闭按钮
  .el-dialog__headerbtn {
    .el-dialog__close {
      background-color: #409eff;
      border-radius: 88px;
      box-sizing: border-box;
      padding: 5px;
      color: white;
    }
    &:hover {
      .el-dialog__close {
        background-color: #1f75d5;
        color: white;
      }
    }
  }

  .video-container {
    margin: -20px 0px -10px;
    border-radius: 20px;
    overflow: hidden;
    height: calc(100vh - 180px);
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 560px;
    overflow: hidden;
    background: black;
    /*背景图片*/
    background-image: url("~@/../static/components/sgVideoPlayer/poster.png");
    background-repeat: repeat;
    background-position: center;

    .video {
      transition: 0.382s;
      width: 100%;
      height: 100%;
      object-fit: contain;
      object-position: center;
      background: transparent;
    }
  }
  .audio-container {
    display: flex;
    justify-content: center;
    align-items: center;
    margin: -20px 0px -10px;
    border-radius: 20px;
    overflow: hidden;
    height: calc(100vh - 180px);
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 60px;
    overflow: hidden;
    .audio {
      width: 100%;
      height: 100%;
    }
  }
  .document-container {
    position: relative;
    width: 100%;
    height: 100%;
  }
}
</style>

demo.vue

<template>
  <div :class="$options.name">
    <el-button @click="readVideo">视频</el-button>
    <el-button @click="readAudio">音频</el-button>
    <el-button @click="readImage">图片</el-button>
    <el-button @click="readDocument">文档</el-button>
    
    <sgFileViewDialog
      :data="data_sgFileViewDialog"
      v-model="show_sgFileViewDialog"
      v-if="show_sgFileViewDialog"
    />
  </div>
</template>
<script>
import sgFileViewDialog from "@/vue/components/admin/sgFileViewDialog";

export default {
  components: {
    sgFileViewDialog,
  },
  data() {
    return {
      data_sgFileViewDialog: {},
      show_sgFileViewDialog: false,
    };
  },
  props: ["data"],
  computed: {},
  watch: {},
  created() {},
  mounted() {},
  destroyed() {},
  methods: {
    rw_sgFileViewDialog({ w = true, d = {} } = {}) {
      this.data_sgFileViewDialog = JSON.parse(JSON.stringify(d));
      this.data_sgFileViewDialog.disabled = !w;
      this.show_sgFileViewDialog = true;
    },

    readVideo(d) {
      this.rw_sgFileViewDialog({
        d: {
          src: `https://www.*******.cn/share/video/1.mp4`,
        },
      });
    },
    readAudio(d) {
      this.rw_sgFileViewDialog({
        d: {
          src: `https://www.*******.cn/share/audio/1.mp3`,
        },
      });
    },
    readImage(d) {
      this.rw_sgFileViewDialog({
        d: {
          initialIndex: 1,
          src: `https://www.*******.cn/share/image/2.jpg`,
          previewSrcList: [
            `https://www.*******.cn/share/image/1.jpg`,
            `https://www.*******.cn/share/image/2.jpg`,
            `https://www.*******.cn/share/image/3.jpg`,
            `https://www.*******.cn/share/image/4.jpg`,
            `https://www.*******.cn/share/image/5.jpg`,
          ],
        },
      });
    },
    readDocument(d) {
      this.rw_sgFileViewDialog({
        d: {
          src: `http://sr.*******.cn:82/static/help/help.pdf`,
        },
      });
    },
  },
};
</script>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值