一、空间使用
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>
<PreviewImage
ref="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,和默认不展示0
previewRef.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>
<!-- 内容 -->
<div
class="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-viewer 1. 必须使用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 = idx show.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; // 会携带cookie
xhr.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; // 会携带cookie
xhr.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;
}
// 来渲染从服务器获取的 Blob
docx.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-embed
ref="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-select
placeholder="请选择编码"
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博客