Vue实现在线文档预览

news2024/11/17 10:26:16

目录

  • 背景
  • 在线预览
  • Office文档文件在线预览
    • pdf文档预览
      • 方案一
      • 方案二
    • Word文档预览
    • Excel文档预览
    • PPT文档预览
  • 纯文本、各种代码文件预览
  • 图片文件预览
  • 视频文件预览
    • Aliplayer阿里云播放器
    • Xgplayer西瓜播放器
    • Bilibiliplayer仿哔哩哔哩弹幕播放器
  • 音频文件预览
  • 在线文档预览项目(整合)
  • 源码下载

背景

在项目开发中,遇到很多次有关文件的需求,如不同文件类型的文件上传、文件下载、文件预览。文件上传在https://qkongtao.cn/?p=1410中有相关大文件分片上传、断点续传及秒传的介绍;文件下载在https://qkongtao.cn/?p=560#h2-0的第14个方法中有下载的工具方法介绍;各种文件的预览在项目中用的也比较频繁,大多数情况下的文件预览都会用第三方的服务或者后端服务进行实现,但是也有些情况适合纯web端实现各种文件的预览,本次就记录一下使用纯web端实现各种文档文件的预览,并且全部封装成单独的组件,开箱即用。

如果使用第三方服务,有以下的方案:

  1. XDOC文档预览服务:http://view.xdocin.com/
  2. kkFileView在线文件预览:https://kkfileview.keking.cn/zh-cn/docs/home.html
  3. Microsoft在线预览api:https://view.officeapps.live.com/op/view.aspx?src= 后面拼上你的服务器word文件地址

使用第三方的服务当然效果很好,但是成本更高,实现起来也没有那么灵活。

本次实现的文档预览的类型有:docx, xlsx, pptx, pdf,以及纯文本、代码文件和各种图片、视频格式的在线预览

在线预览

纯web端文档预览项目在线地址:http://file-viewer.qkongtao.cn/

Office文档文件在线预览

Office文档文件包括常见的docx、excel、pdf三种文件的预览,当然还有PPT文件预览,但是ppt使用纯前端实现预览效果不是很好,正确的做法一般会讲ppt文件在服务端转换成PDF文件后再进行预览。
本次的文档预览使用了vue-office组件库,安装方式如下:

//docx文档预览组件
npm install @vue-office/docx

//excel文档预览组件
npm install @vue-office/excel

//pdf文档预览组件
npm install @vue-office/pdf

pdf文档预览

方案一

使用vue-office组件库的pdf组件
安装vue-office组件:npm install @vue-office/pdf

实现代码如下:

<template>
  <vue-office-pdf :src="pdf" @rendered="rendered" />
</template>

<script>
//引入VueOfficePdf组件
import VueOfficePdf from "@vue-office/pdf";

export default {
  components: {
    VueOfficePdf,
  },
  props: {
    pdf: {
      type: String,
      default:
        "http://qncdn.qkongtao.cn/lib/teamadmin/files/%E6%B7%B1%E5%9C%B3-xx-Java%E9%AB%98%E7%BA%A7.pdf",
    },
  },
  data() {
    return {};
  },
  methods: {
    rendered() {
      console.log("渲染完成");
    },
  },
};
</script>

实现效果如下:
在线预览:http://file-viewer.qkongtao.cn/pdf
20230707-142149-Cs.png

方案二

使用 vue-pdf 插件来实现
安装 vue-pdf 插件:npm install --save vue-pdf

实现代码如下:

<template>
  <div style="background-color: #fff">
    <div style="margin-bottom: 15px; text-align: center">
      <span>当前第{{ pdfpage }}页 / 共{{ pageCount ? pageCount : 0 }}页</span>
      <span
        style="margin-left: 20px"
      >前往第
        <el-input
          v-model="num"
          :oninput="
            'if(!/^[0-9]+$/.test(value)) value=value.replace(/\\D/g,\'\');if(value>' +
              pageCount +
              ')value=' +
              pageCount +
              ';if(value<0)value=1'
          "
          style="width: 55px; display: inline-block"
          @blur="goPage"
        /></span>
      <el-button
        type="primary"
        size="small"
        style="margin-left: 20px"
        @click.stop="prevIoUsPage"
      >
        上一页
      </el-button>
      <el-button type="primary" size="small" @click.stop="nextPage">
        下一页
      </el-button>
      <el-button size="mini" @click.stop="zoomA">放大</el-button>
      <el-button
        size="mini"
        style="margin-left: 5px"
        @click.stop="zoomB"
      >缩小</el-button>
      <span style="margin-left: 5px">大小 {{ parseInt(scale * 100) }}%</span>
    </div>

    <div>
      <pdf
        :src="src"
        :page="pdfpage"
        :style="scaleFun"
        :class="pageCount ? 'load' : 'load_pdf'"
        @num-pages="loading($event)"
        @page-loaded="pdfpage = $event"
      />
      <div v-if="!pageCount" class="loading">
        <img src="../../assets/images/loading.gif" alt="">
        <span>加载中...</span>
      </div>
    </div>
  </div>
</template>

<script>
import pdf from 'vue-pdf'
export default {
  components: {
    pdf
  },
  props: {
    src: {
      type: String,
      require: true,
      default:
        'http://qncdn.qkongtao.cn/lib/teamadmin/files/%E6%B7%B1%E5%9C%B3-xx-Java%E9%AB%98%E7%BA%A7.pdf'
    }
  },
  data() {
    return {
      // PDF预览
      pdfpage: 1,
      pageCount: 0,
      num: 1,
      scale: 1 // 缩放比例
    }
  },

  computed: {
    scaleFun(index) {
      // 缩放
      var scale = this.scale
      return `width: ${scale * 100}% !important`
    }
  },
  mounted() {},
  methods: {
    loading(event) {
      this.pageCount = event
    },
    // PDF改变页数
    prevIoUsPage() {
      var p = this.pdfpage
      p = p > 1 ? p - 1 : this.pageCount
      this.pdfpage = p
    },
    nextPage() {
      var p = this.pdfpage
      p = p < this.pageCount ? p + 1 : 1
      this.pdfpage = p
    },
    goPage() {
      this.pdfpage = parseInt(this.num)
    },
    zoomA() {
      this.scale += 0.1
    },
    zoomB() {
      this.scale -= 0.1
    }
  }
}
</script>

<style lang="scss" scoped>
/deep/ .el-input__inner {
  text-align: center;
}
.pdf {
  display: inline-block;
  width: 100%;
  // height: 100%;
  transform-origin: 0 0;
}

.load_pdf {
  display: none !important;
}

.loading {
  width: 100%;
  margin-top: 25%;
  margin-bottom: 20%;
  text-align: center;
  img {
    width: 20%;
  }
  span {
    display: block;
    text-align: center;
    margin-top: 30px;
    margin-bottom: 30px;
  }
}
</style>

由于实在项目中用到了,所以加了一下翻页、缩放的功能

实现效果如下:
20230707-142736-XN.png

Word文档预览

使用vue-office组件库的docx组件
安装 vue-office 插件:npm install @vue-office/docx

实现代码如下:

<template>
  <div>
    <vue-office-docx
      :src="docx"
      style="height: 100%; margin: 0; padding: 0"
      @rendered="rendered"
    />
  </div>
</template>

<script>
//引入VueOfficeDocx组件
import VueOfficeDocx from "@vue-office/docx";
//引入相关样式
import "@vue-office/docx/lib/index.css";

export default {
  components: {
    VueOfficeDocx,
  },
  props: {
    docx: {
      type: String,
      default:
        "http://qncdn.qkongtao.cn/lib/teamadmin/files/Hadoop2.7.1%E4%BC%AA%E5%88%86%E5%B8%83%E5%BC%8F%E9%9B%86%E7%BE%A4%E5%AE%89%E8%A3%85%E6%96%87%E6%A1%A3.docx", //设置文档网络地址,可以是相对地址
    },
  },
  data() {
    return {};
  },
  methods: {
    rendered() {
      console.log("渲染完成");
    },
  },
};
</script>

实现效果如下:
在线预览:http://file-viewer.qkongtao.cn/doc

20230707-143031-oQ.png

Excel文档预览

使用vue-office组件库的excel组件
安装vue-office插件:npm install @vue-office/excel

实现代码如下:

<template>
  <vue-office-excel
    :src="excel"
    @rendered="renderedHandler"
    @error="errorHandler"
    style="height: calc(100vh - 90px)"
  />
</template>

<script>
//引入VueOfficeExcel组件
import VueOfficeExcel from "@vue-office/excel";
//引入相关样式
import "@vue-office/excel/lib/index.css";

export default {
  components: {
    VueOfficeExcel,
  },
  props: {
    excel: {
      type: String,
      default:
        "http://qncdn.qkongtao.cn/lib/teamadmin/files/2021%E5%B1%8A%E5%85%A8%E5%9B%BD%E5%90%84%E5%9C%B0%E6%B4%BE%E9%81%A3%E5%9C%B0%E5%9D%80.xlsx", //设置文档地址
    },
  },
  data() {
    return {};
  },
  methods: {
    renderedHandler() {
      console.log("渲染完成");
    },
    errorHandler() {
      console.log("渲染失败");
    },
  },
};
</script>

实现代码如下:
在线预览:http://file-viewer.qkongtao.cn/excel

20230707-143439-Yq.png

PPT文档预览

PPT文档预览纯前端实现起来比较困难,效果也不怎么好,建议可以先在服务端转换成PDF文档,使用PDF文档预览的效果比较好。
纯web实现方案:
需要安装以下插件:

"core-js": "^3.3.2",
"d3": "^3.5.17",
"dimple": "git+https://gitee.com/mirrors/dimple.git#2.3.0",
"jszip": "^3.10.0",

具体实现代码直接查看gitee:https://gitee.com/qkongtao/document-preview-project/tree/master/src/components/pptx

实现效果如下:
20230707-144553-lB.png

可以看到很多图片样式信息都丢失了,所以还是直接使用第三方的

可以尝试XDOC文档的:https://view.xdocin.com/view?src=https://upyun.qkongtao.cn/others/document/C006.pptx
他的实现方案是后台转成PDF然后再预览。

纯文本、各种代码文件预览

文本文件预览使用了vue-codemirror插件

  • 实现的方法也很简单,判断上传的文件时文本或者代码文件后,将其内容文本读取出来,然后放到codemirror,并且设置对应的代码高亮的mode。
  • codemirror有非常多的代码主题,高亮的模式也不一样。
  • 本次实现至此的文本有:json,java,sql,js,css,xml,html,yaml,md,py,txt。
  • codemirror插件中其实还有许多代码格式的mode,当设置对应代码的mode的时候,改代码类型的关键字就会高亮,并且在编写的时候会有关键字代码的提示。

安装vue-codemirror插件:npm install vue-codemirror@4.0.6 --save
在main.js中引入插件:

// 引入jshint用于实现js自动补全提示
import jshint from "jshint";
window.JSHINT = jshint.JSHINT;
// 引入代码编辑器
import {
    codemirror
} from "vue-codemirror";
import "codemirror/lib/codemirror.css";
Vue.use(codemirror);

实现代码如下:

  1. Codemirror.vue 编辑器组件
<template>
  <codemirror
    ref="myCm"
    :value="value"
    :options="cmOptions"
    @changes="onCmCodeChanges"
    @blur="onCmBlur"
    @keydown.native="onKeyDown"
    @mousedown.native="onMouseDown"
    @paste.native="OnPaste"
  ></codemirror>
</template>
<script>
import { codemirror } from "vue-codemirror";

import "codemirror/mode/clike/clike";
import "codemirror/theme/blackboard.css";
import "codemirror/mode/javascript/javascript.js";
import "codemirror/mode/xml/xml.js";
import "codemirror/mode/htmlmixed/htmlmixed.js";
import "codemirror/mode/css/css.js";
import "codemirror/mode/yaml/yaml.js";
import "codemirror/mode/sql/sql.js";
import "codemirror/mode/python/python.js";
import "codemirror/mode/markdown/markdown.js";
import "codemirror/addon/hint/show-hint.css";
import "codemirror/addon/hint/show-hint.js";
import "codemirror/addon/hint/javascript-hint.js";
import "codemirror/addon/hint/xml-hint.js";
import "codemirror/addon/hint/css-hint.js";
import "codemirror/addon/hint/html-hint.js";
import "codemirror/addon/hint/sql-hint.js";
import "codemirror/addon/hint/anyword-hint.js";
import "codemirror/addon/lint/lint.css";
import "codemirror/addon/lint/lint.js";
import "codemirror/addon/lint/json-lint";
require("script-loader!jsonlint");
import "codemirror/addon/lint/javascript-lint.js";
import "codemirror/addon/fold/foldcode.js";
import "codemirror/addon/fold/foldgutter.js";
import "codemirror/addon/fold/foldgutter.css";
import "codemirror/addon/fold/brace-fold.js";
import "codemirror/addon/fold/xml-fold.js";
import "codemirror/addon/fold/comment-fold.js";
import "codemirror/addon/fold/markdown-fold.js";
import "codemirror/addon/fold/indent-fold.js";
import "codemirror/addon/edit/closebrackets.js";
import "codemirror/addon/edit/closetag.js";
import "codemirror/addon/edit/matchtags.js";
import "codemirror/addon/edit/matchbrackets.js";
import "codemirror/addon/selection/active-line.js";
import "codemirror/addon/search/jump-to-line.js";
import "codemirror/addon/dialog/dialog.js";
import "codemirror/addon/dialog/dialog.css";
import "codemirror/addon/search/searchcursor.js";
import "codemirror/addon/search/search.js";
import "codemirror/addon/display/autorefresh.js";
import "codemirror/addon/selection/mark-selection.js";
import "codemirror/addon/search/match-highlighter.js";
export default {
  components: {
    codemirror,
  },
  props: ["cmTheme", "cmMode", "autoFormatJson", "jsonIndentation", "value"],
  data() {
    return {
      editorValue: "",
      cmOptions: {
        theme:
          !this.cmTheme || this.cmTheme == "default"
            ? "blackboard"
            : this.cmTheme,
        mode:
          !this.cmMode || this.cmMode == "default"
            ? "application/json"
            : this.cmMode,
        extraKeys: {
          Tab: "autocomplete",
          "Shift-Alt-F": () => {
            try {
              if (this.cmOptions.mode == "application/json" && this.value) {
                this.editorValue = this.formatStrInJson(this.value);
              }
            } catch (e) {
              this.$message.error("格式化代码出错:" + e.toString());
            }
          },
        },
        lineWrapping: true, //代码折叠
        lineNumbers: true, //是否显示行号
        autofocus: true,
        smartIndent: 4, // 自动缩进
        indentUnit: 4, //缩进单位
        tabSize: 4, //tab字符的宽度
        autocorrect: true, //自动更正
        spellcheck: true, //拼写检查
        lint: true,
        gutters: [
          "CodeMirror-lint-markers", //代码错误检测
          "CodeMirror-linenumbers",
          "CodeMirror-foldgutter", //展开收起
        ],
        foldGutter: true,
        matchTags: { bothTags: true },
        matchBrackets: true,
        styleActiveLine: true,
        autoRefresh: true,
        highlightSelectionMatches: {
          //显示当前所选单词
          minChars: 2,
          style: "matchhighlight",
          showToken: true,
        },
        styleSelectedText: true,
        enableAutoFormatJson:
          this.autoFormatJson == null ? true : this.autoFormatJson,
        defaultJsonIndentation:
          !this.jsonIndentation || typeof this.jsonIndentation != typeof 1
            ? 2
            : this.jsonIndentation,
      },
      enableAutoFormatJson:
        this.autoFormatJson == null ? true : this.autoFormatJson,
      defaultJsonIndentation:
        !this.jsonIndentation || typeof this.jsonIndentation != typeof 1
          ? 2
          : this.jsonIndentation,
    };
  },
  watch: {
    cmTheme: function (newValue, oldValue) {
      try {
        let theme = this.cmTheme == "default" ? "blackboard" : this.cmTheme;
        require("codemirror/theme/" + theme + ".css");
        this.cmOptions.theme = theme;
        this.resetLint();
      } catch (e) {
        this.$message.error("切换编辑器主题出错:" + e.toString());
      }
    },
    cmMode: function (newValue, oldValue) {
      this.$set(this.cmOptions, "mode", this.cmMode);
      this.resetLint();
      this.resetFoldGutter();
    },
  },
  methods: {
    resetLint() {
      if (!this.$refs.myCm.codemirror.getValue()) {
        this.$nextTick(() => {
          this.$refs.myCm.codemirror.setOption("lint", false);
        });
        return;
      }
      this.$refs.myCm.codemirror.setOption("lint", false);
      this.$nextTick(() => {
        this.$refs.myCm.codemirror.setOption("lint", true);
      });
    },
    resetFoldGutter() {
      this.$refs.myCm.codemirror.setOption("foldGutter", false);
      this.$nextTick(() => {
        this.$refs.myCm.codemirror.setOption("foldGutter", true);
      });
    },

    // 黏贴事件处理函数
    OnPaste(event) {
      if (this.cmOptions.mode == "application/json") {
        try {
          this.editorValue = this.formatStrInJson(this.value);
        } catch (e) {
          // 啥都不做
        }
      }
    },
    // 失去焦点时处理函数
    onCmBlur(cm, event) {
      try {
        let editorValue = cm.getValue();
        if (this.cmOptions.mode == "application/json" && editorValue) {
          if (!this.enableAutoFormatJson) {
            return;
          }
          this.editorValue = this.formatStrInJson(editorValue);
        }
      } catch (e) {
        // 啥也不做
      }
    },
    // 按下键盘事件处理函数
    onKeyDown(event) {
      const keyCode = event.keyCode || event.which || event.charCode;
      const keyCombination = event.ctrlKey || event.altKey || event.metaKey;
      if (!keyCombination && keyCode > 64 && keyCode < 123) {
        this.$refs.myCm.codemirror.showHint({ completeSingle: false });
      }
    },
    // 按下鼠标时事件处理函数
    onMouseDown(event) {
      this.$refs.myCm.codemirror.closeHint();
    },
    onCmCodeChanges(cm, changes) {
      this.editorValue = cm.getValue();
      this.resetLint();
      this.$emit("onChangeCode", cm.getValue());
    },
    // 格式化字符串为json格式字符串
    formatStrInJson(strValue) {
      return JSON.stringify(
        JSON.parse(strValue),
        null,
        this.defaultJsonIndentation
      );
    },
  },
  created() {
    try {
      if (!this.value) {
        this.cmOptions.lint = false;
        return;
      }
      if (this.cmOptions.mode == "application/json") {
        if (!this.enableAutoFormatJson) {
          return;
        }
        this.editorValue = this.formatStrInJson(this.value);
      }
    } catch (e) {
      // console.log("初始化codemirror出错:" + e);
    }
  },
};
</script>
<style scope>
/* 选中背景颜色 */
.CodeMirror-selected {
  background-color: #3b3c37 !important;
}
/* 选中字体 */
.CodeMirror-selectedtext {
  /* color: white !important; */
}

.cm-matchhighlight {
  /* background-color: #ae00ae; */
}
</style>
  1. index.vue 编辑器调用组件
<template>
  <div class="code-mirror-div">
    <!-- <h2 style="text-align: center">代码编辑器</h2> -->
    <div class="tool-bar">
      <span>请选择主题:</span>
      <el-select
        v-model="cmTheme"
        placeholder="请选择"
        size="small"
        style="width: 150px; margin-left: 10px"
      >
        <el-option
          v-for="item in cmThemeOptions"
          :key="item"
          :label="item"
          :value="item"
        ></el-option>
      </el-select>
      <span style="margin-left: 30px">请选择编辑模式:</span>
      <el-select
        v-model="cmEditorMode"
        placeholder="请选择"
        size="small"
        style="width: 150px; margin-left: 10px"
        @change="onEditorModeChange"
      >
        <el-option
          v-for="item in cmEditorModeOptions"
          :key="item"
          :label="item"
          :value="item"
        ></el-option>
      </el-select>
      <span style="margin-left: 30px">字体大小:</span>
      <el-select
        v-model="cmEditorSize"
        placeholder="请选择"
        size="small"
        style="width: 150px; margin-left: 10px"
        @change="onEditorSizeChange"
      >
        <el-option
          v-for="item in cmEditorSizeOptions"
          :key="item"
          :label="item"
          :value="item"
        ></el-option>
      </el-select>
      <el-button
        type="primary"
        size="small"
        style="margin-left: 30px"
        @click="jsonFormatter"
        v-if="cmEditorMode == 'json'"
        >格式化json</el-button
      >
      <el-button
        type="primary"
        size="small"
        style="margin-left: 30px"
        @click="jsonPress"
        v-if="cmEditorMode == 'json'"
        >压缩json</el-button
      >
    </div>
    <Codemirror
      ref="cmEditor"
      :cmTheme="cmTheme"
      :cmMode="cmMode"
      :autoFormatJson="autoFormatJson"
      :jsonIndentation="jsonIndentation"
      :value="codeValue"
      @onChangeCode="onChangeCode"
    ></Codemirror>
  </div>
</template>
<script>
import Codemirror from "./Codemirror.vue";
export default {
  components: {
    Codemirror,
  },
  props: {
    initCodeValue: {
      type: String,
      default:
        '{"canvasStyleData":{"width":1280,"height":720,"scale":100,"color":"#000","opacity":1,"background":"#fff","fontSize":14,"backgroundColor":"rgba(232, 244, 200, 1)"}}', //代码
    },
    initEditorMode: {
      type: String,
      default: "json", // 编辑模式
    },
  },
  data() {
    return {
      cmTheme: "default", // codeMirror主题
      // codeMirror主题选项
      cmEditorSizeOptions: [
        "10",
        "12",
        "14",
        "16",
        "18",
        "20",
        "24",
        "28",
        "32",
      ],
      cmThemeOptions: [
        "default",
        "idea",
        "3024-day",
        "3024-night",
        "abcdef",
        "ambiance",
        "ayu-dark",
        "ayu-mirage",
        "base16-dark",
        "base16-light",
        "bespin",
        "blackboard",
        "cobalt",
        "colorforth",
        "darcula",
        "dracula",
        "duotone-dark",
        "duotone-light",
        "eclipse",
        "elegant",
        "erlang-dark",
        "gruvbox-dark",
        "hopscotch",
        "icecoder",
        "isotope",
        "lesser-dark",
        "liquibyte",
        "lucario",
        "material",
        "material-darker",
        "material-palenight",
        "material-ocean",
        "mbo",
        "mdn-like",
        "midnight",
        "monokai",
        "moxer",
        "neat",
        "neo",
        "night",
        "nord",
        "oceanic-next",
        "panda-syntax",
        "paraiso-dark",
        "paraiso-light",
        "pastel-on-dark",
        "railscasts",
        "rubyblue",
        "seti",
        "shadowfox",
        "solarized dark",
        "solarized light",
        "the-matrix",
        "tomorrow-night-bright",
        "tomorrow-night-eighties",
        "ttcn",
        "twilight",
        "vibrant-ink",
        "xq-dark",
        "xq-light",
        "yeti",
        "yonce",
        "zenburn",
      ],
      // 编辑模式选项
      cmEditorModeOptions: [
        "json",
        "java",
        "sql",
        "js",
        "css",
        "xml",
        "html",
        "yaml",
        "md",
        "py",
        "txt",
      ],
      cmEditorMode: this.initEditorMode, // 编辑模式
      cmMode: "application/json", //codeMirror模式
      jsonIndentation: 2, // json编辑模式下,json格式化缩进 支持字符或数字,最大不超过10,默认缩进2个空格
      autoFormatJson: true, // json编辑模式下,输入框失去焦点时是否自动格式化,true 开启, false 关闭,
      cmEditorSize: "16",
      codeValue: this.initCodeValue,
    };
  },

  mounted() {
    this.onEditorModeChange(this.initEditorMode);
    var sd = document.getElementsByClassName("CodeMirror");
    sd[0].style.fontSize = this.cmEditorSize + "px";
  },
  methods: {
    // 切换编辑模式事件处理函数
    onEditorModeChange(value) {
      switch (value) {
        case "json":
          this.cmMode = "application/json";
          break;
        case "java":
          this.cmMode = "text/x-java";
          break;
        case "sql":
          this.cmMode = "sql";
          break;
        case "js":
          this.cmMode = "javascript";
          break;
        case "xml":
          this.cmMode = "xml";
          break;
        case "css":
          this.cmMode = "css";
          break;
        case "html":
          this.cmMode = "htmlmixed";
          break;
        case "yaml":
          this.cmMode = "yaml";
          break;
        case "md":
          this.cmMode = "markdown";
          break;
        case "txt":
          this.cmMode = "markdown";
          break;
        case "py":
          this.cmMode = "python";
          break;
        default:
          this.cmMode = "application/json";
      }
    },
    //代码修改
    changeCode(value) {
      this.codeValue = value;
    },
    // 选择字体大小
    onEditorSizeChange() {
      var sd = document.getElementsByClassName("CodeMirror");
      sd[0].style.fontSize = this.cmEditorSize + "px";
    },
    // 获取代码
    onChangeCode(code) {
      this.codeValue = code;
    },
    // 格式化字符串为json格式字符串
    jsonFormatter() {
      try {
        this.codeValue = JSON.stringify(JSON.parse(this.codeValue), null, "\t");
      } catch {
        alert("json代码有误");
      }
    },
    // 压缩json
    jsonPress() {
      try {
        this.codeValue = JSON.stringify(JSON.parse(this.codeValue));
      } catch {
        alert("json代码有误");
      }
    },
  },
};
</script>
<style scope>
.CodeMirror {
  position: absolute;
  top: 0px;
  left: 2px;
  right: 5px;
  bottom: 0px;
  padding: 2px;
  height: calc(100vh - 170px);
  margin-bottom: 50px;
}
.code-mirror-div {
  position: absolute;
  top: 0px;
  left: 2px;
  right: 5px;
  bottom: 0px;
  padding: 2px;
}
.tool-bar {
  margin: -50px 2px 0px 20px;
}
</style>

在调用编辑器的插件里面加入了一些小功能:

  1. 选择编辑器主题
  2. 编辑代码的模式
  3. 设置代码字体大小
  4. 代码为json文本的时候,可以对代码进行压缩和格式化

实现效果如下:
在线预览:http://file-viewer.qkongtao.cn/code
20230707-151406-3W.png

图片文件预览

图片文件预览可以直接使用img标签,或者用UI库的图片标签,如 el-image等,但是这种使用起来功能没有那么多,并且灵活性也不是很高,这次实现图片预览使用了v-viewer插件。

安装插件:npm install v-viewer --save
在main.js中挂载插件

import VueViewer from 'v-viewer';
import 'viewerjs/dist/viewer.css'

Vue.use(VueViewer, {
    defaultOptions: {
        // 自定义默认配置
        zIndex: 9999,
        inline: false, // 默认值:false。启用内联模式。
        button: true, // 右上角关闭按钮
        navbar: false, // 指定导航栏(图片组)的可见性。
        title: true, //指定标题的可见性和内容
        toolbar: true, // 指定工具栏及其按钮的可见性和布局
        tooltip: true, //放大或缩小时显示带有图像比率(百分比)的工具提示。
        movable: true, // 启用以移动图像。
        zoomable: true, // 启用以缩放图像。
        rotatable: true, // 启用以旋转图像
        scalable: true, // 用以反转图像。
        transition: false, // 为某些特殊元素启用CSS3转换。
        fullscreen: false, // 启用以在播放时请求全屏。
        keyboard: true, //启用键盘支持。
        url: 'src', //默认值:"src"。定义获取原始图像URL以供查看的位置。
    },
});

功能可以自己选择开关:
在这里插入图片描述

图片预览组件实现代码如下:

<template>
  <div>
    <viewer>
      <img alt="图片" :src="src" class="image" />
    </viewer>
  </div>
</template>

<script>
export default {
  props: {
    src: {
      type: String,
      default: "http://upyun.qkongtao.cn/chevereto/2022/08/30/onepiece.png",
    },
  },
  data() {
    return {};
  },
};
</script>

<style scoped>
.image {
  display: block;
  width: auto;
  width: 600px;
  margin: 100px auto;
}
</style>

实现效果如下:
在线预览(可点击):http://file-viewer.qkongtao.cn/img
20230707-154854-SM.png

视频文件预览

本次视频文件预览尝试使用了三种插件实现:

  1. Aliplayer:https://player.alicdn.com/aliplayer/presentation/index.html?type=memoryPlay
  2. Xgplayer:https://h5player.bytedance.com/guide/
  3. Bilibiliplayer:https://qkongtao.cn/?p=1481

这三种开源的播放器功能比较全,样式也比较好看,可以适用于大部分视频播放的场景,可以真正的告别video标签了。
本次实现播放器父组件向子组件传参结构示例:

video: {
        poster:
          "https://upyun.qkongtao.cn/others/video/%E8%8D%89%E5%B8%BD%E4%B8%80%E4%BC%99%E6%82%AC%E8%B5%8F%E4%BB%A4%E4%BC%A0%E9%81%8D%E5%85%A8%E4%B8%96%E7%95%8C.mp4.jpg",
        thumbnailUrl:
          "https://upyun.qkongtao.cn/others/video/%E8%8D%89%E5%B8%BD%E4%B8%80%E4%BC%99%E6%82%AC%E8%B5%8F%E4%BB%A4%E4%BC%A0%E9%81%8D%E5%85%A8%E4%B8%96%E7%95%8C.mp4.vtx",
        src: "https://upyun.qkongtao.cn/AList/%E8%8D%89%E5%B8%BD%E4%B8%80%E4%BC%99%E6%82%AC%E8%B5%8F%E4%BB%A4%E4%BC%A0%E9%81%8D%E5%85%A8%E4%B8%96%E7%95%8C.mp4",
      },

Aliplayer阿里云播放器

官方文档:https://player.alicdn.com/aliplayer/presentation/index.html?type=memoryPlay

安装方法:在项目中引入阿里云播放器

<!-- 引用阿里云播放器 -->
    <link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.8.2/skins/default/aliplayer-min.css" />
    <script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/de/prismplayer/2.8.2/aliplayer-min.js">

封装组件实现代码:

<template>
  <!--播放器-->
  <div
    class="prism-player"
    id="J_prismPlayer"
    style="margin-left: auto; margin-right: auto"
  ></div>
</template>
<script>
export default {
  props: {
    //组件需要的参数
    video: Object,
  },
  data() {
    return {};
  },
  created() {
    //console.log("组件成功")
  },
  mounted() {
    this.initPlayer();
  },
  methods: {
    initPlayer() {
      //console.log(this.video)
      var player = new Aliplayer(
        {
          id: "J_prismPlayer", // id选择器
          source: this.video.src, //视频地址
          width: "800px",
          height: "600px",
          //cover: 'http://images.qkongtao.cn/images/2021/08/15/t012cde4a5058c156b7.jpg', // 封面
          qualitySort: "asc", // 清晰度排序
          mediaType: "video", // 返回音频还是视频
          autoplay: false, // 自动播放
          isLive: false, // 直播
          rePlay: false, // 循环播
          preload: true,
          controlBarVisibility: "hover", // 控制条的显示方式:鼠标悬停
          useH5Prism: true, // 播放器类型:html5
          thumbnailUrl: this.video.thumbnailUrl,
          // 允许匿名跨域访问属性
          extraInfo: {
            crossOrigin: "anonymous",
          },
          skinLayout: [
            { name: "bigPlayButton", align: "blabs", x: 30, y: 80 },
            { name: "H5Loading", align: "cc" },
            { name: "errorDisplay", align: "tlabs", x: 0, y: 0 },
            { name: "infoDisplay" },
            { name: "tooltip", align: "blabs", x: 0, y: 56 },
            { name: "thumbnail" },
            {
              name: "controlBar",
              align: "blabs",
              x: 0,
              y: 0,
              children: [
                { name: "progress", align: "blabs", x: 0, y: 44 },
                { name: "playButton", align: "tl", x: 15, y: 12 },
                { name: "timeDisplay", align: "tl", x: 10, y: 7 },
                { name: "fullScreenButton", align: "tr", x: 10, y: 12 },
                // { name: "subtitle", align: "tr", x: 15, y: 12 },
                // { name: "setting", align: "tr", x: 15, y: 12 },
                { name: "volume", align: "tr", x: 5, y: 10 },
                { name: "snapshot", align: "tr", x: 5, y: 9 },
              ],
            },
          ],
        },
        function (player) {
          console.log("播放器创建成功");
        }
      );
      // 绑定鼠标事件(暂停、播放)
      var _video = document.querySelector("video");
      _video.addEventListener("click", play);
      player.on("play", function (e) {
        _video.removeEventListener("click", play);
        _video.addEventListener("click", pause);
      });
      player.on("pause", function (e) {
        _video.removeEventListener("click", pause);
        _video.addEventListener("click", play);
      });
      function play() {
        if (player) player.play();
        document.getElementsByClassName("prism-big-play-btn")[0].style.display =
          "none";
      }

      function pause() {
        if (player) player.pause();
        document.getElementsByClassName("prism-big-play-btn")[0].style.display =
          "block";
      }

      /* h5截图按钮, 截图成功回调 */
      player.on("snapshoted", function (data) {
        var pictureData = data.paramData.base64;
        var downloadElement = document.createElement("a");
        downloadElement.setAttribute("href", pictureData);
        var fileName = "Aliplayer" + Date.now() + ".png";
        downloadElement.setAttribute("download", fileName);
        downloadElement.click();
        pictureData = null;
      });
    },
  },
};
</script>

<style scoped>
.prism-player {
  margin: 0;
  padding: 0;
}

实现效果如下:
20230707-160650-uh.png

Xgplayer西瓜播放器

官方文档:https://h5player.bytedance.com/guide/

安装方法:npm install xgplayer --save

封装组件实现代码:

<template>
  <div>
    <div id="myPlayer"></div>
  </div>
</template>

<script>
import Player from "xgplayer";
import "xgplayer/dist/index.min.css";
import Danmu from "xgplayer/es/plugins/danmu";
import "xgplayer/es/plugins/danmu/index.css";
export default {
  props: {
    //组件需要的参数
    video: Object,
  },
  data() {
    return {
      player: null,
    };
  },
  mounted() {
    this.initXgplayer();
  },
  methods: {
    initXgplayer() {
      const config = {
        // 播放器ID
        id: "myPlayer",
        width: "800px",
        height: "500px",
        // 视频链接
        url: this.video.src,
        playsinline: true,
        // 自动播放
        autoplay: false,
        // 视频封面链接
        poster: this.video.poster,
        // 播放器插件
        plugins: [Danmu],
        // 流式布局
        fluid: true,
        // 初始音量
        volume: 1,
        // 倍数配置
        playbackRate: [0.5, 1, 1.5, 2],
        // 缩略图集
        thumbnail: {
          pic_num: 44,
          width: 160,
          height: 90,
          col: 10,
          row: 10,
          urls: [this.video.thumbnailUrl],
        },
        // 显示下载按钮
        download: true,
        // 显示截图按钮
        screenShot: true,
        // 防止canvas toDataURL跨域画布污染
        videoAttributes: {
          crossOrigin: "anonymous",
        },
        /***********************  弹幕配置start ***********************/
        danmu: {
          // 预设弹幕内容
          comments: [
            {
              duration: 15000,
              id: "2",
              start: 3000,
              txt: "长弹幕长弹幕长弹幕",
              mode: "top",
              style: {
                //弹幕自定义样式
                color: "#ff9500", //例:'#ff9500',
                "font-size": "30px", // 例:'20px',
                padding: "2px 11px", //例: 2px 11px',
              },
            },
            {
              duration: 15000,
              id: "3",
              start: 4000,
              txt: "长弹幕长弹幕长弹幕",
              mode: "bottom",
              style: {
                //弹幕自定义样式
                color: "#ff9500", //例:'#ff9500',
                "font-size": "40px", // 例:'20px',
                padding: "2px 11px", //例: 2px 11px',
              },
            },
            {
              duration: 15000,
              id: "4",
              start: 5000,
              txt: "长弹幕长弹幕长弹幕",
              mode: "scroll",
              style: {
                //弹幕自定义样式
                color: "#de1c31", //例:'#ff9500',
                "font-size": "48px", // 例:'20px',
                padding: "2px 11px", //例: 2px 11px',
              },
            },
            {
              duration: 15000,
              id: "5",
              start: 8000,
              txt: "长弹幕长弹幕长弹幕",
              mode: "scroll",
              style: {
                //弹幕自定义样式
                color: "#813c85", //例:'#ff9500',
                "font-size": "30px", // 例:'20px',
                padding: "2px 11px", //例: 2px 11px',
              },
            },
          ],
          area: {
            start: 0,
            end: 1,
          },
          // 不使用默认提供弹幕开关
          closeDefaultBtn: false,
          // 关闭弹幕初始化
          defaultOff: true,
          // 弹幕控制面板
          panel: false,
        },
        /***********************  弹幕配置end ***********************/
      };
      this.player = new Player(config);
    },
  },
};
</script>

<style scoped>
</style>

实现效果如下:
20230707-160849-sZ.png

Bilibiliplayer仿哔哩哔哩弹幕播放器

参考文档:https://qkongtao.cn/?p=1481
具体的实现方法可以参考上面的文章,项目中直接使用iframe嵌入播放器。

封装组件实现代码:

<template>
  <div>
    <iframe
      :src="playerApi + src"
      allowfullscreen="allowfullscreen"
      mozallowfullscreen="mozallowfullscreen"
      msallowfullscreen="msallowfullscreen"
      oallowfullscreen="oallowfullscreen"
      webkitallowfullscreen="webkitallowfullscreen"
      width="800px"
      height="500px"
      frameborder="0"
      style="player"
    >
    </iframe>
  </div>
</template>

<script>
export default {
  props: {
    //组件需要的参数
    video: Object,
  },

  data() {
    return {
      playerApi: "http://code.qkongtao.cn/video/player/?url=",
      src: "",
    };
  },
  mounted() {
    this.src = this.video.src;
  },
  methods: {},
};
</script>

<style scoped>
.player {
  margin-top: 30px;
}
</style>

实现效果如下:
在这里插入图片描述

音频文件预览

音频文件预览的使用场景相对比较少,这里就简单的集成一下APlayer插件实现一下友好的音乐播放器。

APlayer官方文档:https://aplayer.js.org/#/zh-Hans/?id=%E5%AE%89%E8%A3%85

安装方法:npm install aplayer --save

封装组件实现代码

<template>
  <div>
    <div id="aplayer" class="aplayer"></div>
  </div>
</template>

<script>
import APlayer from "aplayer";
import "aplayer/dist/APlayer.min.css";

export default {
  props: {
    musicList: {
      type: Array,
      default: [],
    },
  },
  data() {
    return {
      myAPlayer: null,
    };
  },
  mounted() {
    this.initAPlayer();
  },
  methods: {
    initAPlayer() {
      const options = {
        container: document.getElementById("aplayer"),
        theme: "#e9e9e9",
        audio: this.musicList,
      };
      const ap = new APlayer(options);
      this.myAPlayer = ap;
    },
  },
};
</script>

<style scoped>
</style>

调用组件传入参数示例:

musicList: [
        {
          name: "新时代",
          artist: "Ado",
          url: "https://upyun.qkongtao.cn/wordpress/files/%E6%96%B0%E6%97%B6%E4%BB%A3.mp3",
          cover:
            "https://qkongtao.cn/file/music/pic/%E6%96%B0%E6%97%B6%E4%BB%A3.png",
        },
        {
          name: "偏爱",
          artist: "张芸京",
          url: "https://qkongtao.cn/file/music/%E5%81%8F%E7%88%B1-%E5%BC%A0%E8%8A%B8%E4%BA%AC.mp3",
          cover:
            "https://qkongtao.cn/file/music/pic/我们的爱我不放手-张芸京.jpg",
        },
        {
          name: "残酷月光",
          artist: "林宥嘉",
          url: "https://qkongtao.cn/file/music/%E6%AE%8B%E9%85%B7%E6%9C%88%E5%85%89%20%E6%9E%97%E5%AE%A5%E5%98%89.mp3",
          cover:
            "https://qkongtao.cn/file/music/pic/%E6%AE%8B%E9%85%B7%E6%9C%88%E5%85%89.png",
        },
      ],

实现效果如下:
在线预览:http://file-viewer.qkongtao.cn/music
20230707-162017-b7.png

可以根据官方文档设置播放器场景样式。

在线文档预览项目(整合)

上述的组件是本项目主要实现的功能,最后我将组件进行了整合一下,封装成一个文件上传、下载、预览的demo。
在线预览地址:http://file-viewer.qkongtao.cn/
20230707-162545-zZ.png

源码下载

https://gitee.com/qkongtao/document-preview-project

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/728456.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(vue)人工智能,区分对话框各自内容区域样式

(vue)人工智能&#xff0c;区分对话框各自内容区域样式 效果&#xff1a; 思路&#xff1a; 1.一行里包含 对方头像、内容、我方头像 三部分 2.根据消息数组的下标&#xff0c;确定是我方消息还是对方消息&#xff08;偶数我方&#xff0c;奇数对方&#xff09; 3.根据奇偶数显…

MapStruct转换时的一些问题

1.当属性名相同 类型不同时&#xff0c;需要手动指明映射 Mappings({Mapping(source "customerType",target "customerType"),Mapping(source "customerStatus",target "customerStatus"))List<CustomerBindDetailExcelVO> …

tf.contrib.training.HParams在tensorflow2.0版本以上失效的解决

tf.contrib.training.HParams在tensorflow2.0版本以上失效的解决_我也想学习的博客-CSDN博客 1. # Copyright 2016 The TensorFlow Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this fi…

学习matplotlib第一步

下边代码会画出一个ycos(x)的图像&#xff1a; import numpy as np import matplotlib.pyplot as pltx np.linspace(-2,2,100) ynp.cos(np.pi*x)plt.plot(x,y,go) plt.title(r"$y\cos(\pi | time x$") plt.show()在Jupyter lab运行的时候&#xff0c;发现报错如下&…

干货-卷起来,企业级web自动化测试实战落地(一)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 开始前-项目讨论 …

18 SAR图像和光学图像的配准算法(matlab程序)

1.简述 合成孔径雷达(synthetic aperture radar,SAR)图像配准的主要目标是对同一或不同传感器在不同时间、不同视点捕获的SAR图像进行配准。SAR因具有全天候成像能力和地物穿透能力&#xff0c;因此具有非常广泛的应用&#xff0c;如变化检测[1]、图像融合[2]、目标检测与识别[…

提取背景音乐去掉人声的方法是什么?这几个方法简单做到!

当制作视频或音频时&#xff0c;我们常常需要提取背景音乐并去掉人声。这一过程并不简单&#xff0c;需要一些专业的技巧和工具。相信有些新手小伙伴也不知道该如何操作&#xff0c;下面我们将分享三个方法&#xff0c;希望能够帮助到你。 方法一&#xff1a;使用记灵在线工具…

gma 2 教程(二)数据操作:1. 相关模块组成

考虑到数据读写是地理空间数据分析和应用的基础&#xff0c;因此将本章作为正文第一部分&#xff0c;以便为后续章节应用提供基础支持。本章以gma栅格/矢量数据输入输出模块&#xff08;io&#xff09;栅格/矢量数据的读取、创建、变换等主要操作为基础&#xff0c;配合gma地理…

【达摩院OpenVI】开源CVPR快速实例分割FasInst模型

团队模型、论文、博文、直播合集&#xff0c;点击此处浏览 一、论文&代码 论文&#xff1a;https://arxiv.org/abs/2303.08594 模型&代码&#xff1a;https://modelscope.cn/models/damo/cv_resnet50_fast-instance-segmentation_coco/summary 二、背景 实例分割旨…

2023-7-8-第十四式策略模式

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f4a5;&#x1f4a5;&#x1f4a5;欢迎来到&#x1f91e;汤姆&#x1f91e;的csdn博文&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f49f;&#x1f49f;喜欢的朋友可以关注一下&#xf…

axios拦截器

在请求或响应被 then 或 catch 处理前拦截它们。 // 添加请求拦截器 axios.interceptors.request.use(function (config) {// 在发送请求之前做些什么return config;}, function (error) {// 对请求错误做些什么return Promise.reject(error);});// 添加响应拦截器 axios.inte…

PMSG永磁风机VSG网侧虚拟同步控制一次调频四机两区域系统,离散模型。

PMSGM永磁同步风机VSG虚拟同步机调频两区域系统&#xff0c;离散模型&#xff0c;非无穷大电网。 风机为网侧VSG控制。四机两区域系统&#xff0c;渗透率可调。当前渗透率为区域1&#xff0c;一台900MW同步机&#xff0c;区域2一台900MW同步机&#xff0c;永磁同步风电场容量5…

minio 升级相关问题

系列文章目录 文章目录 系列文章目录前言一、集群部署二、单机部署三、写一个启动脚本四、性能方面 前言 minio版本升级&#xff0c;目的主要是为了解决由 Direct buffer memory 引发的附件下载优化方案 minio version RELEASE.2021-01-16T02-19-44Z > minio version RELEA…

60题学会动态规划系列:动态规划算法第四讲

买卖股票相关的动态规划题目 文章目录 1.买卖股票的最佳时机含冷冻期2.买卖股票的最佳时期含⼿续费3.买卖股票的最佳时机III4.买卖股票的最佳时机IV 1.最佳买卖股票时机含冷冻期 力扣链接&#xff1a;力扣 给定一个整数数组prices&#xff0c;其中第 prices[i] 表示第 i 天的…

Delphi XE编写OCX控件

1、new->other 2、Active libary 3、再次New->Other,才出现ActiveX组件内容 设置类名及参数

在 Vue 3 中使用阿里巴巴矢量图标库

在项目中基本会用到图标&#xff0c;比较常见的就是阿里图标库。这篇文章主要介绍如何在vue3中使用图标库。 下载并全局注册自定义图标库 手动下载阿里巴巴矢量图标库的字体文件&#xff1a; 在阿里巴巴矢量图标库网站上选择您需要的图标&#xff0c;并将其添加到购物车。然后…

CMake之CPack

文章目录 一、CPack1.用CPack打包成为deb包2.如何确定的Depends依赖包?3.如何确定编译Build-Depends&#xff1f;4.Cpakc打包RPM包 二、deb的简单使用三、deb包相关文件说明1.control文件2.preinst文件3.postinst文件4.prerm文件5.postrm文件 一、CPack CPack 是 CMake 2.4.2…

SpringBoot 集成 EasyExcel 3.x 实现 Excel 导出

目录 EasyExcel官方文档 EasyExcel是什么&#xff1f; EasyExcel注解 springboot集成EasyExcel 简单入门导出 &#xff1a; 实体类 自定义转换类 测试一下 复杂表头一对多导出 &#xff1a; 自定义注解 定义实体类 自定义单元格合并策略 测试一下 EasyExcel官方文档 …

The Sandbox 展示泰国 2023 年元宇宙生态系统

The Sandbox 举办了 2023 年泰国合作伙伴日活动&#xff0c;宣布创建泰国元宇宙生态系统&#xff0c;并对泰国创客社区的巨大合作和发展表示认可。 The Sandbox 联合创始人兼首席运营官 Sebastien BORGET 说&#xff1a;“我们很高兴见证 The Sandbox 泰国生态系统的发展&#…

第七章 网络安全【计算机网络】

第七章 网络安全【计算机网络】 前言推荐第7章 网络安全7.1网络安全问题概述7.1.1计算机网络面临的安全性威胁7.1.2安全的计算机网络7.1.3数据加密模型 7.2两类密码体制7.2.1对称密钥密码体制7.2.2公钥密码体制 7.3鉴别7.3.1报文鉴别7.3.2实体鉴别 7.4密钥分配7.4.1 对称密钥的…