在项目开发中,遇到很多次有关文件的需求,如不同文件类型的文件上传、文件下载、文件预览。文件上传在https://qkongtao.cn/?p=1410中有相关大文件分片上传、断点续传及秒传的介绍;文件下载在https://qkongtao.cn/?p=560#h2-0的第14个方法中有下载的工具方法介绍;各种文件的预览在项目中用的也比较频繁,大多数情况下的文件预览都会用第三方的服务或者后端服务进行实现,但是也有些情况适合纯web端实现各种文件的预览,本次就记录一下使用纯web端实现各种文档文件的预览,并且全部封装成单独的组件,开箱即用。
如果使用第三方服务,有以下的方案:
使用第三方的服务当然效果很好,但是成本更高,实现起来也没有那么灵活。
本次实现的文档预览的类型有:docx, xlsx, pptx, pdf,以及纯文本、代码文件和各种图片、视频格式的在线预览
纯web端文档预览项目在线地址:http://file-viewer.qkongtao.cn/
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
使用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
使用 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>
由于实在项目中用到了,所以加了一下翻页、缩放的功能
使用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
使用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
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
可以看到很多图片样式信息都丢失了,所以还是直接使用第三方的
可以尝试XDOC文档的:https://view.xdocin.com/view?src=https://upyun.qkongtao.cn/others/document/C006.pptx
他的实现方案是后台转成PDF然后再预览。
文本文件预览使用了vue-codemirror插件
安装vue-codemirror插件:npm install [email protected] --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);
实现代码如下:
<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>
<template>
<div class="code-mirror-div">
<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'"
>格式化jsonel-button
>
<el-button
type="primary"
size="small"
style="margin-left: 30px"
@click="jsonPress"
v-if="cmEditorMode == 'json'"
>压缩jsonel-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>
在调用编辑器的插件里面加入了一些小功能:
实现效果如下:
在线预览:http://file-viewer.qkongtao.cn/code
图片文件预览可以直接使用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
本次视频文件预览尝试使用了三种插件实现:
这三种开源的播放器功能比较全,样式也比较好看,可以适用于大部分视频播放的场景,可以真正的告别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",
},
官方文档:https://player.alicdn.com/aliplayer/presentation/index.html?type=memoryPlay
安装方法:在项目中引入阿里云播放器