Vue3实战Easy云盘(四):使用空间+文件预览+文件分享+文件下载

一、空间使用

Framework.vue中

(1)引入接口

const api = {getUseSpace: "/getUseSpace",logout: "/logout",
};

(2)回调

// 使用空间
const useSpaceInfo = ref({ useSpace: 0, totalSpace: 1 });
const getUseSpace = async () => {// 存储请求信息let result = await proxy.Request({// 请求路径url: api.getUseSpace,// 不显示加载showLoading: false,});if (!result) {return;}// 把请求到的信息存到使用空间useSpaceInfo.value = result.data;
};
// 调用
getUseSpace();

上传文件结束后,更新使用空间:

// 上传文件回调
const uploadCallbackHandler = () => {nextTick(() => {
...........................// 并最后调用一个函数来获取空间使用情况。getUseSpace();});
};

(3)结构中使用

 <!-- 下方空间使用 --><div class="space-info"><div>空间使用</div><div class="percent"><!-- 占用空间进度条 --><!-- 结果除以 100 是为了将前面乘以的 10000 还原为百分比形式 --><el-progress:percentage="Math.floor((useSpaceInfo.useSpace / useSpaceInfo.totalSpace) * 10000)/100"color="#409eff"/></div><!-- 文字说明和图标 --><div class="space-use"><div class="use">{{ proxy.Utils.size2Str(useSpaceInfo.useSpace) }}/{{ proxy.Utils.size2Str(useSpaceInfo.totalSpace) }}</div><div class="iconfont icon-refresh" @click="getUseSpace"></div></div></div>

效果:

二、文件预览(难点)

1.封装Preview.vue组件

  • 根据文件分类类型传入不同的width给Window.vue组件,以决定展示的宽度,而Window.vue组件中,又使用计算属性根据此传递的width值与当前窗口宽度作比较(width值不允许超过当前窗口宽度),返回作为windowContentWidth
  • showPreview方法( 入口)暴露给父组件Main.vue调用(其实,要展示弹框有2种方法,要么在父组件中定义一个响应式数据,然后以prop传给子组件,子组件根据此响应式数据作出对应展示。要么子组件暴露一个方法给外界调用,让外界通过此方法传入数据。很显然,这里用的是第二种方式)
  • 如何展示多种不同类型的文件?Main组件中使用Preview组件,调用<Preview ref=“previewRef”>组件的previewRef.value.showPreview(row, 0)方法,将文件数据传递了过去,并且指定url使用FILE_URL_MAP[0],然后,在<Preview>组件中,根据文件数据中的文件类型使用不同的组件作展示(不然,所有的根据文件类型展示不同的组件,都要写在Main.vue组件中,那这样的话,Main.vue组件就过于复杂了)
  • <Preview>组件用到了Window.vue(用于模拟弹窗)配合展示不同文件类型的组件(不包括图片类型,PreviewXXX组件) 和 <el-image-viewer>组件(专门展示图片)
  • 不同文件类型请求路径(后端处理这些请求的详细代码在上面已贴出)

非视频文件类型文件 预览的url
0 - fileUrl: "/file/getFile"
1 - fileUrl: "/admin/getFile"
2 - fileUrl: "/showShare/getFile"
视频文件类型文件的url取
0 - videoUrl: /file/ts/getVideoInfo"
1 - videoUrl: /admin/ts/getVideoInfo"
2 - videoUrl: /showShare/ts/getVideoInfo"

components/preview/Preview.vue

<template><PreviewImageref="imageViewerRef":imageList="[imageUrl]"v-if="fileInfo.fileCategory == 3"></PreviewImage><Window:show="windowShow"@close="closeWindow":width="fileInfo.fileCategory == 1 ? 1300 : 900":title="fileInfo.fileName":align="fileInfo.fileCategory == 1 ? 'center' : 'top'"v-else><!--  `file_type` 1:视频 2:音频  3:图片 4:pdf 5:doc 6:excel 7:txt 8:code 9:zip 10:其他', --><PreviewVideo :url="url" v-if="fileInfo.fileCategory == 1"></PreviewVideo><PreviewExcel :url="url" v-if="fileInfo.fileType == 6"></PreviewExcel><PreviewDoc :url="url" v-if="fileInfo.fileType == 5"></PreviewDoc><PreviewPdf :url="url" v-if="fileInfo.fileType == 4"></PreviewPdf><PreviewTxt:url="url"v-if="fileInfo.fileType == 7 || fileInfo.fileType == 8"></PreviewTxt><PreviewMusic:url="url"v-if="fileInfo.fileCategory == 2":fileName="fileInfo.fileName"></PreviewMusic><PreviewDownload:createDownloadUrl="createDownloadUrl":downloadUrl="downloadUrl":fileInfo="fileInfo"v-if="fileInfo.fileCategory == 5 && fileInfo.fileType != 8"></PreviewDownload></Window>
</template><script setup>
// @ 符号表示一个特定路径的别称,这个设置可以在 build/webpack.base.conf.js中设置
import PreviewImage from "@/components/preview/PreviewImage.vue";
import PreviewVideo from "@/components/preview/PreviewVideo.vue";
import PreviewDoc from "@/components/preview/PreviewDoc.vue";
import PreviewExcel from "@/components/preview/PreviewExcel.vue";
import PreviewPdf from "@/components/preview/PreviewPdf.vue";
import PreviewTxt from "@/components/preview/PreviewTxt.vue";
import PreviewMusic from "@/components/preview/PreviewMusic.vue";
import PreviewDownload from "@/components/preview/PreviewDownload.vue";import { ref, reactive, getCurrentInstance, nextTick, computed } from "vue";
import { useRouter, useRoute } from "vue-router";
const { proxy } = getCurrentInstance();
const router = useRouter();
const route = useRoute();// 计算图片地址(缩略图原图)
const imageUrl = computed(() => {return (// 区分缩略图和原图proxy.globalInfo.imageUrl + fileInfo.value.fileCover.replaceAll("_.", "."));
});// 默认不展示
const windowShow = ref(false);
// 关闭方法
const closeWindow = () => {windowShow.value = false;
};// 定义接口地址
const FILE_URL_MAP = {0: {fileUrl: "/file/getFile",videoUrl: "/file/ts/getVideoInfo",createDownloadUrl: "/file/createDownloadUrl",downloadUrl: "/api/file/download",},1: {fileUrl: "/admin/getFile",videoUrl: "/admin/ts/getVideoInfo",createDownloadUrl: "/admin/createDownloadUrl",downloadUrl: "/api/admin/download",},2: {fileUrl: "/showShare/getFile",videoUrl: "/showShare/ts/getVideoInfo",createDownloadUrl: "/showShare/createDownloadUrl",downloadUrl: "/api/showShare/download",},
};// 文件信息
const fileInfo = ref({});
// 视频文件地址
const url = ref(null);const imageViewerRef = ref();// 下载地址
const createDownloadUrl = ref(null);
const downloadUrl = ref(null);
// 各种类型预览实现
const showPreview = (data, showPart) => {fileInfo.value = data;// `file_category`  '1:视频 2:音频  3:图片 4:文档 5:其他',// 图片if (data.fileCategory == 3) {nextTick(() => {// 图片预览展示imageViewerRef.value.show(0);});} else {// 如果是图片之外的类型,就通过window组件去展示windowShow.value = true;let _url = FILE_URL_MAP[showPart].fileUrl;// 视频地址单独处理if (data.fileCategory == 1) {_url = FILE_URL_MAP[showPart].videoUrl;}// 文件下载let _createDownloadUrl = FILE_URL_MAP[showPart].createDownloadUrl;let _downloadUrl = FILE_URL_MAP[showPart].downloadUrl;if (showPart == 0) {_url = _url + "/" + data.fileId;_createDownloadUrl = _createDownloadUrl + "/" + data.fileId;} else if (showPart == 1) {_url = _url + "/" + data.uerId + "/" + data.fileId;_createDownloadUrl =_createDownloadUrl + "/" + data.uerId + "/" + data.fileId;} else if (showPart == 2) {_url = _url + "/" + data.shareId + "/" + data.fileId;_createDownloadUrl =_createDownloadUrl + "/" + data.shareId + "/" + data.fileId;}url.value = _url;createDownloadUrl.value = _createDownloadUrl;downloadUrl.value = _downloadUrl;}
};
// 将此方法暴露出去
defineExpose({ showPreview });
</script><style lang="scss" scoped>
</style>

Main.vue 

// 预览
const previewRef = ref();
const preview = (data) => {// 如果是文件夹(目录)if (data.folderType == 1) {// 就调用Navigation组件中的openFolder(打开文件夹)方法,实现预览navigationRef.value.openFolder(data);return;}// 如果是文件if (data.status != 2) {proxy.Message.warning("文件未完成转码,无法预览");return;}// 展示,传入文件类型data,和默认不展示0previewRef.value.showPreview(data, 0);
};

2.封装window组件

  • 相当于手动封装一个弹框组件
  • 使用window.innerWidth获取当前窗口宽度作为响应式数据windowWidth的初始值,并使用计算属性绑定给style,并且监听窗口大小变化事件(window.addEventListener('resize',handler),其中handler去修改计算属性中使用的windowWidth响应式数据的值),以此达到此弹框的宽度永远最大不能超过当前窗口的宽度(即使弹框指定的宽度大于当前窗口宽度),并且当窗口变化时,Window组件的宽度能随着窗口变化而变化(最大不超过当前窗口宽度)。
  • 使用计算属性,计算Window组件内容居中时,距离左侧的的left值,绑定给style属性,以此达到让弹框内容永远居中

 components/Window.vue

<template><div class="window" v-if="show"><div class="window-mask" v-if="show" @click="close"></div><!-- x图标 --><div class="close" @click="close"><span class="iconfont icon-close2"> </span></div><!-- 内容 --><divclass="window-content":style="{top: '0px',left: windowContentLeft + 'px',width: windowContentWidth + 'px',}"><div class="title">{{ title }}</div><div class="content-body" :style="{ 'align-items': align }"><slot></slot></div></div></div>
</template><script setup>
import { computed, onMounted, onUnmounted, ref } from "vue";// 定义数据类型
const props = defineProps({show: {type: Boolean,},width: {type: Number,default: 1000,},title: {type: String,},align: {type: String,default: "top",},
});// 窗口宽度
const windowWidth = ref(window.innerWidth);
// 窗口里面内容宽度
const windowContentWidth = computed(() => {return props.width > windowWidth.value ? windowWidth.value : props.width;
});// 计算窗口到屏幕左边的宽度
const windowContentLeft = computed(() => {let left = windowWidth.value - props.width;return left < 0 ? 0 : left / 2;
});const emit = defineEmits(["close"]);
const close = () => {emit("close");
};// 适应屏幕宽度,窗口大小调整
const resizeWindow = () => {windowWidth.value = window.innerWidth;
};// 挂载时,处理窗口大小调整(resize)事件
onMounted(() => {// 将resizeWindow函数绑定为window对象的resize事件的事件处理器。每当窗口大小改变时,resizeWindow函数就会被调用window.addEventListener("resize", resizeWindow);
});// 卸载时
onUnmounted(() => {// 移除了之前通过addEventListener添加的resize事件监听器。这是非常重要的,因为如果不在组件卸载时移除这个监听器,即使组件已经被销毁,resizeWindow函数仍然可能会在窗口大小改变时被调用,这可能会导致错误或不必要的计算。window.removeEventListener("resize", resizeWindow);
});
</script><style lang="scss" scoped>
.window {.window-mask {top: 0px;left: 0px;width: 100%;height: calc(100vh);z-index: 200;opacity: 0.5;background: #000;position: fixed;}.close {z-index: 202;cursor: pointer;position: absolute;top: 40px;right: 30px;width: 44px;height: 44px;border-radius: 22px;background: #606266;display: flex;justify-content: center;align-items: center;.iconfont {font-size: 20px;color: #fff;z-index: 100000;}}.window-content {top: 0px;z-index: 201;position: absolute;background: #fff;.title {text-align: center;line-height: 40px;border-bottom: 1px solid #ddd;font-weight: bold;}.content-body {height: calc(100vh - 41px);display: flex;overflow: auto;}}
}
</style>

3.图片预览

(1). 引用 Element UI 提供的 el-image-viewer 组件的标签

 :initial-index="previewImgIndex"`:这个属性用于设置初始时显示的图片索引。 
. hide-on-click-modal:这个属性是一个布尔值(默认为 `false`),如果设置为 `true`,则点击模态框时会关闭图片查看器。

. el-image-viewer使用示例
第一种: 使用el-image - 通过点击小图, 然后预览大图, 这是官方文档提供的方法
第二种: 使用el-image-viewer
可以通过这个示例,看下element-ui是怎么做的图片预览

<template><div class="preview-box"><!-- 第一种: 使用el-image - 通过点击小图, 然后预览大图, 这是官方文档提供的方法 --><el-image :preview-src-list="['/api/file/getImage/202307/3178033358P0KiZY3YV2.png','/api/file/getImage/202307/3178033358bd1LTA0mLK.png']" :initial-index="0" src="/api/file/getImage/202307/3178033358P0KiZY3YV2_.png"/><!-- 第二种: 使用el-image-viewer1. 必须使用v-if来控制预览效果的显示和隐藏,不能使用v-show(使用v-show无效)2. 需要监听close事件, 当点击蒙层 或 关闭按钮时, 会触发close事件, 此时需要手动关闭预览, 否则预览不会关闭3. initial-index属性为显示图片的索引--><el-button @click="showImage(0)">显示图片0</el-button><el-button @click="showImage(1)">显示图片1</el-button><el-image-viewer v-if="show" :url-list="['/api/file/getImage/202307/3178033358P0KiZY3YV2.png','/api/file/getImage/202307/3178033358bd1LTA0mLK.png']" :initial-index="initialIndex"@close="closeImageViewer" :hide-on-click-modal="true" /></div>
</template><script setup>import { ref, reactive } from 'vue'// 预览图片显示的初始索引
const initialIndex = ref(0)// 是否展示图片预览
const show = ref(false)// 显示图片预览的方法
function showImage(idx) {initialIndex.value = idxshow.value = true // 展示预览
}// 关闭图片预览的方法
function closeImageViewer() {show.value = false
}
</script><style lang="scss"></style>

(2)区分缩略图和原图

不需要存两张图片,只需要计算图片地址
首先,从 fileInfo.value.fileCover 中获取文件名或路径,并将其中的所有 "_." 替换为 "."。
然后,将这个修改后的文件名或路径与 proxy.globalInfo.imageUrl 拼接起来,以形成一个完整的图片URL。
例如,如果:
proxy.globalInfo.imageUrl 是 "https://example.com/images/"
fileInfo.value.fileCover 是 "cover_123_.jpg"
那么,上述代码将返回 "https://example.com/images/cover_123.jpg"。

(3)在使用滚轮缩放预览图片时,禁止页面跟随滚动

components/preview/PreviewImage.vue

<template><div class="image-viewer"><!-- * `:initial-index="previewImgIndex"`:这个属性用于设置初始时显示的图片索引。`previewImgIndex` 是 Vue 组件中的一个数据属性(data property),它应该是一个数字,表示图片列表中的位置。  * `hide-on-click-modal`:这个属性是一个布尔值(默认为 `false`),如果设置为 `true`,则点击模态框时会关闭图片查看器。  --><el-image-viewer:initial-index="previewImgIndex"hide-on-click-modal:url-list="imageList"@close="closeImgViewer"v-if="previewImgIndex != null"></el-image-viewer></div>
</template><script setup>
import { ref } from "vue";
const props = defineProps({imageList: {type: Array,},
});const previewImgIndex = ref(null);const show = (index) => {// 缩小放大图片时,禁止页面滚动stopScroll();previewImgIndex.value = index;
};
defineExpose({ show });const closeImgViewer = () => {// 关闭预览时,允许页面滚动startScroll();previewImgIndex.value = null;
};//禁止滚动
const stopScroll = () => {document.body.style.overflow = "hidden";
};// 开始滚动
const startScroll = () => {document.body.style.overflow = "auto";
};
</script><style lang="scss" scoped>
.image-viewer {.el-image-viewer__mask {opacity: 0.7;}
}
</style>

图片预览效果

3.视频预览

  • 使用DPlayer
  • 引入hls(如果导入hls的包报错的话,可考虑在index.html中直接cdn引入hls.min.js)

components/preview/PreviewVideo.vue

<template><div ref="player" id="player"></div>
</template><script setup>
import DPlayer from "dplayer";
import { nextTick, onMounted, ref, getCurrentInstance } from "vue";const { proxy } = getCurrentInstance();// 定义数据
const props = defineProps({url: {type: String,},
});const videoInfo = ref({video: null,
});const player = ref();
const initPlayer = () => {// theme	'#b7daff'	主题色// screenshot	false	开启截图,如果开启,视频和视频封面需要允许跨域// video	-	视频信息// video.url	-	视频链接// video.type	'auto'	可选值: 'auto', 'hls', 'flv', 'dash', 'webtorrent', 'normal' 或其他自定义类型,// video.customType	-	自定义类型const dp = new DPlayer({element: player.value,theme: "#b7daff",screenshot: true,video: {url: `/api${props.url}`,type: "customHls",customType: {customHls: function (video, player) {const hls = new Hls();hls.loadSource(video.src);hls.attachMedia(video);},},},});
};onMounted(() => {initPlayer();
});
</script><style lang="scss" scoped>
#player {width: 100%;:deep .dplayer-video-wrap {text-align: center;.dplayer-video {margin: 0px auto;max-height: calc(100vh - 41px);}}
}
</style>

DPlayer使用

<template><div class="preview-box"><div id="dplayer"></div><el-button @click="changeVideo">切换视频</el-button></div>
</template><script setup>import { ref, reactive, onMounted } from 'vue'
import Hls from 'hls.js';
import DPlayer from 'dplayer';// DPlayers实例
let dp = null// 另一种方式,使用 customType
onMounted(() => {dp = new DPlayer({container: document.getElementById('dplayer'),video: {url: '/api/file/ts/getVideoInfo/zwizcojhc7',// url: '/api/file/ts/getVideoInfo/PakZTUpyp9',type: 'customHls',customType: {customHls: function (video, player) {let config = {xhrSetup: function (xhr, url) {xhr.withCredentials = true; // 会携带cookiexhr.setRequestHeader('token', "my-token")},}const hls = new Hls(config);hls.loadSource(video.src);hls.attachMedia(video);},},},});
})// 切换视频
function changeVideo() {dp.switchVideo({// url: '/api/file/ts/getVideoInfo/zwizcojhc7',url: '/api/file/ts/getVideoInfo/PakZTUpyp9',type: 'customHls',customType: {customHls: function (video, player) {let config = {xhrSetup: function (xhr, url) {xhr.withCredentials = true; // 会携带cookiexhr.setRequestHeader('token', "my-token")},}const hls = new Hls(config);hls.loadSource(video.src);hls.attachMedia(video);},},})
}</script><style lang="scss">
#dplayer {width: 600px;height: 300px;
}
</style>

4.Docx文档预览

PreviewDocx.vue组件

  • 使用docx-preview这个插件(npm i docx-preview -S)
  • axios的responseType配置为blob
  • 后台返回的是二进制数据(后台读取文件流,将流数据写入response),前端接受此流数据,传入给docx-preview插件处理

<template><div ref="docRef" class="doc-content"></div>
</template><script setup>
import * as docx from "docx-preview";
import { ref, reactive, getCurrentInstance, onMounted } from "vue";
const { proxy } = getCurrentInstance();const props = defineProps({url: {type: String,},
});const docRef = ref();const initDoc = async () => {// 它向 props.url 指定的 URL 发起请求,并设置响应类型为 "blob"。Blob 对象表示一个不可变、原始数据的类文件对象。let result = await proxy.Request({url: props.url,responseType: "blob",});if (!result) {return;}// 来渲染从服务器获取的 Blobdocx.renderAsync(result, docRef.value);
};onMounted(() => {initDoc();
});
</script><style lang="scss" scoped>
.doc-content {margin: 0px auto;:deep .docx-wrapper {background: #fff;padding: 10px 0px;}:deep .docx-wrapper > section.docx {margin-bottom: 0px;}
}
</style>

docx-preview使用示例

<template><div class="doc-box"><div ref="docRef" id="doc-content"></div></div>
</template><script setup>
import { ref,reactive } from 'vue'
import axios from 'axios'
import {renderAsync} from 'docx-preview'const props = defineProps({url:{type: String},fileInfo: {type: Object}
})
const docRef = ref()
axios({url:`${props.url}${props.fileInfo.fileId}`,method: 'POST',responseType: 'blob',
}).then(res=>{console.log(res.data,'res.data');renderAsync(res.data, docRef.value)
})</script><style lang="scss" scoped>.doc-box {height: 100%;overflow: auto;
}
</style>

 5.Excel文件预览

PreviewExcel.vue组件

  • 安装xlsx这个插件
  • axios的responseType配置为arraybuffer(注意都是小写)
  • 后台返回的是二进制数据(后台读取文件流,将流数据写入response),前端接受此流数据,传入给xlsx插件处理
  • l将插件处理得到的html,使用v-html 插入到 div标签中
<template><div v-html="excelContent" class="talbe-info"></div>
</template><script setup>
import * as XLSX from "xlsx";
import { ref, reactive, getCurrentInstance, onMounted } from "vue";
const { proxy } = getCurrentInstance();const props = defineProps({url: {type: String,},
});const excelContent = ref();const initExcel = async () => {let result = await proxy.Request({url: props.url,responseType: "arraybuffer",});if (!result) {return;}// 使用 XLSX.read 方法来解析一个 Uint8Array,这个 Uint8Array 很可能是从一个 Excel 文件(如 XLSX 格式)的 Blob 数据中得到的。{ type: "array" } 选项告诉 XLSX.read 方法输入数据的类型是一个数组。let workbook = XLSX.read(new Uint8Array(result), { type: "array" }); // 解析数据// 通过 workbook.SheetNames 获取工作簿中所有工作表的名字数组。然后,通过索引 [0] 获取第一个工作表的名字。最后,使用这个名字从 workbook.Sheets 对象中取出对应的工作表对象。var worksheet = workbook.Sheets[workbook.SheetNames[0]]; // workbook.SheetNames 下存的是该文件每个工作表名字,这里取出第一个工作表// 将工作表对象转换为 HTML 字符串excelContent.value = XLSX.utils.sheet_to_html(worksheet);
};onMounted(() => {initExcel();
});
</script><style lang="scss" scoped>
.talbe-info {width: 100%;padding: 10px;:deep table {width: 100%;border-collapse: collapse;td {border: 1px solid #ddd;border-collapse: collapse;padding: 5px;height: 30px;min-width: 50px;}}
}
</style>

Xlsx组件使用示例

下面的responseType一定要写成arraybuffer
如果responseType写的是blob的话,那么一定要调用res.data.arraybuffer(),这个调用返回结果是个Promise,把此Promise得到的结果给到new Uint8Array(promise的结果)也可以

<template><div class="xlsx-box"><div ref="xlsxRef" id="xlsx-content" v-html="excelContent"></div></div></template><script setup>import { ref,reactive } from 'vue'import axios from 'axios'import * as XLSX from 'xlsx'const props = defineProps({url:{type: String},fileInfo: {type: Object}})
const excelContent = ref();axios({url:`${props.url}${props.fileInfo.fileId}`,method: 'POST',responseType: 'arraybuffer',}).then(res=>{console.log(res.data,'res.data');let workbook = XLSX.read(new Uint8Array(res.data), { type: "array" });var worksheet = workbook.Sheets[workbook.SheetNames[0]];excelContent.value = XLSX.utils.sheet_to_html(worksheet);})</script><style lang="scss" scoped>.xlsx-box {height: 100%;width: 100%;overflow: auto;padding: 20px;:deep table {width: 100%;border-collapse: collapse;td {border: 1px solid #ddd;line-height: 2;padding: 0 5px 0;min-width: 30px;height: 30px;}}}</style>

6.PDF预览

PreviewPDF.vue

  • 须安装VuePdfEmbed 、vue3-pdfjs插件
<template><div class="pdf"><vue-pdf-embedref="pdfRef":source="state.url"class="vue-pdf-embed"width="850":page="state.pageNum"></vue-pdf-embed></div>
</template><script setup>
import VuePdfEmbed from "vue-pdf-embed";
import { ref, reactive, getCurrentInstance, onMounted } from "vue";
const { proxy } = getCurrentInstance();const props = defineProps({url: {type: String,},
});const state = ref({// 预览pdf文件地址url: "",// 当前页面pageNum: 0,// 总页数numPages: 0,
});const initPdf = async () => {state.value.url = "/api" + props.url;
};initPdf();
</script><style lang="scss" scoped>
.pdf {width: 100%;
}
</style>

7.文本预览

PreviewTxt.vue

  • 允许手动选择编码格式(使用了FileReader#readAsText(blob,encode)指定编码,将文件流读取为文本字符串)

  • 如果是代码,允许复制(使用了vue-clipboard3插件)

  • 代码高亮(使用了@highlightjs/vue-plugin插件)

<template><div class="code"><div class="top-op"><div class="encode-select"><el-selectplaceholder="请选择编码"v-model="encode"@change="changeEncode"><el-option value="utf8" label="utf8编码"></el-option><el-option value="gbk" label="gbk编码"></el-option></el-select><div class="tips">乱码了?切换编码试试</div></div><div class="copy-btn"><el-button type="primary" @click="copy">复制</el-button></div></div><!-- 代码高亮 --><highlightjs autodetect :code="txtContent" /></div>
</template><script setup>
// 引入实现复制的文件
import useClipboard from "vue-clipboard3";
const { toClipboard } = useClipboard();import { ref, reactive, getCurrentInstance, onMounted, nextTick } from "vue";
const { proxy } = getCurrentInstance();const props = defineProps({url: {type: String,},
});// 文本内容
const txtContent = ref("");
// 文本流结果
const blobResult = ref();
// 编码类型
const encode = ref("utf8");const readTxt = async () => {let result = await proxy.Request({url: props.url,responseType: "blob",});if (!result) {return;}blobResult.value = result;showTxt();
};// 选择编码
const changeEncode = (e) => {encode.value = e;showTxt();
};const showTxt = () => {const reader = new FileReader();// 当读取操作成功完成时调用// 2. 再执行该异步操作reader.onload = () => {let txt = reader.result;txtContent.value = txt; //获取的数据data};// 异步按字符读取文件内容,结果用字符串形式表示// 1. 先走这步,获取读取文件操作reader.readAsText(blobResult.value, encode.value);
};
onMounted(() => {readTxt();
});const copy = async () => {await toClipboard(txtContent.value);proxy.Message.success("复制成功");
};
</script><style lang="scss" scoped>
.code {width: 100%;.top-op {display: flex;align-items: center;justify-content: space-around;}.encode-select {flex: 1;display: flex;align-items: center;margin: 5px 10px;.tips {margin-left: 10px;color: #828282;}}.copy-btn {margin-right: 10px;}pre {margin: 0px;}
}
</style>
// main.js中引入代码高亮//引入代码高亮
import HljsVuePlugin from '@highlightjs/vue-plugin'
import "highlight.js/styles/atom-one-light.css";
import 'highlight.js/lib/common'

8. 音频预览

PreviewVideo.vue

  • 使用APlayer,官方使用文档:APlayer
  • 使用new URL(`@/assets/music_icon.png`, import.meta.url).href,引入本地图片做封面,这个是写在script标签里用的(而在模板中仍然用的是使用@/assets/music_cover.png去引用)
<template><div class="music"><div class="body-content"><div class="cover"><img src="@/assets/music_cover.png" /></div><div ref="playerRef" class="music-player"></div></div></div>
</template><script setup>
import APlayer from "APlayer";
import "APlayer/dist/APlayer.min.css";import {ref,reactive,getCurrentInstance,computed,onMounted,onUnmounted,
} from "vue";
import { useRouter, useRoute } from "vue-router";
const { proxy } = getCurrentInstance();
const router = useRouter();
const route = useRoute();const props = defineProps({url: {type: String,},fileName: {type: String,},
});const playerRef = ref();
const player = ref();onMounted(() => {player.value = new APlayer({container: playerRef.value,audio: {url: `/api${props.url}`,name: `${props.fileName}`,cover: new URL(`@/assets/music_icon.png`, import.meta.url).href,artist: "",},});
});onUnmounted(() => {player.value.destroy();
});
</script><style lang="scss" scoped>
.music {display: flex;align-items: center;justify-content: center;width: 100%;.body-content {text-align: center;width: 80%;.cover {margin: 0px auto;width: 200px;text-align: center;img {width: 100%;}}.music-player {margin-top: 20px;}}
}
</style>

9.文件下载 

PreviewDowndload.vue

  • 先获取一个临时的code,再以此code请求另外一个下载链接(直接使用location.href指向下载链接去做下载,如果当前地址栏有地址,则不会地址栏;如果当前地址栏是空的-比如浏览器直接打开一个空白网页,然后在控制台输入location.href=‘下载地址’,此时地址栏就会变成下载地址)
  • 文件列表中的下载也是同样的做法
  • 不支持预览,下载之后查看
<template><div class="others"><div class="body-content"><div><Icon:iconName="fileInfo.fileType == 9 ? 'zip' : 'others'":width="80"></Icon></div><div class="file-name">{{ fileInfo.fileName }}</div><div class="tips">该类型的文件暂不支持预览,请下载后查看</div><div class="download-btn"><el-button type="primary" @click="download">点击下载 {{ proxy.Utils.size2Str(fileInfo.fileSize) }}</el-button></div></div></div>
</template><script setup>
import { ref, reactive, getCurrentInstance } from "vue";
import { useRouter, useRoute } from "vue-router";
const { proxy } = getCurrentInstance();
const router = useRouter();
const route = useRoute();const props = defineProps({createDownloadUrl: {type: String,},downloadUrl: {type: String,},fileInfo: {type: Object,},
});const download = async () => {let result = await proxy.Request({url: props.createDownloadUrl,});if (!result) {return;}window.location.href = props.downloadUrl + "/" + result.data;
};
</script><style lang="scss" scoped>
.others {display: flex;align-items: center;justify-content: center;width: 100%;.body-content {text-align: center;.file-name {font-weight: bold;}.tips {color: #999898;margin-top: 5px;font-size: 13px;}.download-btn {margin-top: 20px;}}
}
</style>

 Main.vue中

// 下载文件
const download = async (row) => {let result = await proxy.Request({url: api.createDownloadUrl + "/" + row.fileId,});if (!result) {return;}window.location.href = api.download + "/" + result.data;
};

参考:easypan前端学习(二)_easypan源码-CSDN博客

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

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

相关文章

unreal engine 5.0.3 创建游戏项目

根据虚幻官网介绍&#xff0c;虚幻引擎5可免费用于创建线性内容、定制项目和内部项目。你可以免费用它开发游戏&#xff0c;只有当你的产品营收超过100万美元时&#xff0c;才收取5%的分成费用。所以目前国内也有许多游戏厂商在使用UE制作游戏。UE5源码也已开源&#xff0c;有U…

[数据集][目标检测]吸烟检测数据集VOC+YOLO格式1449张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1449 标注数量(xml文件个数)&#xff1a;1449 标注数量(txt文件个数)&#xff1a;1449 标注…

huggingface笔记:LLama 2

1 前提tip 1.1 使用什么数据类型训练模型&#xff1f; Llama2模型是使用bfloat16训练的 上传到Hub的检查点使用torch_dtype float16&#xff0c;这将通过AutoModel API将检查点从torch.float32转换为torch.float16。在线权重的数据类型通常无关紧要&#xff0c;这是因为模型…

机器学习300问】95、什么是KNN算法?它和K-means什么关系?

一、KNN算法的定义 KNN&#xff08;K-Nearest Neighbors&#xff09;算法&#xff0c;是一种简单而有效的监督学习方法。它既可以用在分类任务&#xff0c;也可用在回归任务中。KNN算法的核心思想&#xff1a;在特征空间中&#xff0c;如果有一个数据点周围的大多数邻居属于某个…

5. JVM面试题汇总

Java全栈面试题汇总目录-CSDN博客 1. 说一下JVM的主要组成部分及其作用? JVM包含两个子系统和两个组件&#xff0c;两个子系统为Class loader(类装载)、Execution engine(执行引擎)&#xff1b;两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。 Cl…

linux mail命令及其历史

一、【问题描述】 最近隔壁组有人把crontab删了&#xff0c;crontab这个命令有点反人类&#xff0c;它的参数特别容易误操作&#xff1a; crontab - 是删除计划表 crontab -e 是编辑&#xff0c;总之就是特别容易输入错误。 好在可以通过mail命令找回&#xff0c;但是mai…

【计算机网络】初识Tcp协议

&#x1f4bb;文章目录 &#x1f4c4;前言Tcp基础概念Tcp 的报文格式三次握手四次挥手 Tcp的滑动窗口机制概念超时重传机制高速重传 TCP传输控制机制流量控制拥堵控制慢启动 Tcp的性能优化机制延迟应答捎带应答 &#x1f4d3;总结 &#x1f4c4;前言 TCP三次握手、四次挥手&…

Java刷题总结(面试)

1、String类 String不可变 java 中String是 immutable的&#xff0c;也就是不可变&#xff0c;一旦初始化&#xff0c;其引用指向的内容是不可变的。 也就是说&#xff0c;String str “aa”&#xff1b;str“bb”&#xff1b;第二句不是改变“aa”所存储地址的内容&#xf…

Overleaf是什么?如何升级到标准版OR专业版?

1. Overleaf介绍 Overleaf是一个使用LaTeX进行多人协同编辑的平台&#xff0c;可以免费注册和使用&#xff0c;不用下载LaTeX软件&#xff0c;是最为著名的LaTeX在线协作系统。 主要特色是有LaTeX插件&#xff0c;编辑功能十分完善&#xff0c;有实时预览&#xff08;即编即看…

Java 使用继承和重写父类方法写一个商品入库案例

package 练习.商品入库;import java.util.Scanner; // 抽象手机类 public abstract class Phone {//测试方法public static void main(String[] args){// 华为手机huawei h new huawei();h.setName("华为P40");h.setPrice(1999.99);h.setConfiguration("8128GB…

【排名公布】绵阳男科医院排名发布,绵阳高水男科医院究竟咋样啊?

【排名公布】绵阳男科医院排名发布&#xff0c;绵阳高水男科医院究竟咋样啊&#xff1f; 绵阳高水医院&#xff0c;是一家医保定点单位&#xff0c;地址位于绵阳市涪城区长虹大道北段113号。一所与国际接轨的现代化男子医院&#xff0c;有良好地就医环境,拥有多名有经验的专家…

基于Tensorflow实现了三个模型对MNIST数据集的识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 MNIST手写数字数据集是计算机视觉和机器学习领域的一个经典数据集&#xff0c;常用于评估图像…

攻防世界-mobile-easy-app详解

序言 这道题网上很多分析&#xff0c;但是分析的都是arm版本的&#xff0c;我选了arm64的来分析&#xff0c;arm64相比arm难度高一些&#xff0c;因为arm64编译器搞了inline优化&#xff0c;看起来略抽象 分析 这道题逻辑很简单&#xff0c;输入flag然后一个check函数验证&a…

改进rust代码的35种具体方法-类型(十八)-不要惊慌

上一篇文章 它看起来非常复杂&#xff0c;这就是为什么它贴合的塑料盖上用大号友好字母印上“不要恐慌”的原因之一。——道格拉斯亚当斯 此项目的标题将更准确地描述为更喜欢返回Result而不是使用panic!&#xff08;但不要惊慌更吸引人&#xff09;。 Rust的panic机制主要是为…

算法入门----小话算法(1)

下面就首先从一些数学问题入手。 Q1&#xff1a; 如何证明时间复杂度O(logN) < O(N) < O(NlogN) < O(N2) < O(2N) < O(N!) < O(NN)? A&#xff1a; 如果一个以整数为参数的不等式不能很容易看出不等的关系&#xff0c;那么最好用图示或者数学归纳法。 很显…

Python3 笔记:sort() 和 sorted() 的区别

1、sort() 可以对列表中的元素进行排序&#xff0c;会改变原列表&#xff0c;之前的顺序不复存在。 list.sort&#xff08;key&#xff0c; reverse None&#xff09; key&#xff1a;默认值是None&#xff0c;可指定项目进行排序&#xff0c;此参数可省略。 reverse&#…

rmxprt转换的3D模型只有一半?---模大狮模型网

在3D建模和渲染的工作流程中&#xff0c;我们经常需要用到各种转换工具来兼容不同平台或软件之间的模型格式。rmxprt(或其他类似的模型转换工具)就是其中的一种&#xff0c;它能够将模型从一种格式转换为另一种格式。然而&#xff0c;有时在转换过程中可能会遇到一些问题&#…

微服务雪崩问题、Sentinel(请求限流、线程隔离、服务熔断)、Seata分布式事务

文章目录 前言一、微服务保护二、Sentinel2.1 微服务整合2.2 簇点链路2.3 请求限流2.4 线程隔离2.5 服务熔断 三、分布式事务3.1 Seata3.1.1 Seata架构3.1.2 部署TC服务3.1.3 微服务集成Seata 3.2 XA模式3.3 AT模式 前言 微服务之间为什么会雪崩&#xff1f;怎么解决雪崩问题&…

Oracle体系结构初探:数据库启动与停止

往期内容 参数管理 控制文件添加 启动 在启动Oracle数据库时&#xff0c;我们一般会使用如下命令&#xff1a; startup 虽然命令只有一个&#xff0c;但其中却是经历了3个阶段&#xff0c;从下面执行 startup 命令返回也可以看出来。 总结为3个阶段&#xff1a; nomount&…

ubuntu下python导入.so库

ubuntu下python导入.so库 文章目录 ubuntu下python导入.so库1. 什么是.so文件&#xff1f;2. 使用python脚本编译.so库文件Reference 最近遇到了python导入c编译的 .so库的问题&#xff0c;发觉挺有意思&#xff0c;于是写下这篇blog以作记录。 1. 什么是.so文件&#xff1f; …