文件预览和实验数据管理页面
@ -13,6 +13,7 @@
|
|||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
"@headlessui/vue": "^1.7.12",
|
"@headlessui/vue": "^1.7.12",
|
||||||
"@heroicons/vue": "^2.0.17",
|
"@heroicons/vue": "^2.0.17",
|
||||||
|
"@kangc/v-md-editor": "^2.3.15",
|
||||||
"@soerenmartius/vue3-clipboard": "^0.1.2",
|
"@soerenmartius/vue3-clipboard": "^0.1.2",
|
||||||
"@tinymce/tinymce-vue": "^5.1.1",
|
"@tinymce/tinymce-vue": "^5.1.1",
|
||||||
"@types/js-cookie": "^3.0.2",
|
"@types/js-cookie": "^3.0.2",
|
||||||
@ -20,6 +21,8 @@
|
|||||||
"@vueuse/integrations": "^9.13.0",
|
"@vueuse/integrations": "^9.13.0",
|
||||||
"@wangeditor/editor": "^5.0.0",
|
"@wangeditor/editor": "^5.0.0",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||||
|
"aplayer": "^1.10.1",
|
||||||
|
"artplayer": "^4.6.1",
|
||||||
"axios": "^1.2.0",
|
"axios": "^1.2.0",
|
||||||
"beautify-qrcode": "^1.0.3",
|
"beautify-qrcode": "^1.0.3",
|
||||||
"better-scroll": "^2.4.2",
|
"better-scroll": "^2.4.2",
|
||||||
@ -27,13 +30,19 @@
|
|||||||
"docx-preview": "^0.3.0",
|
"docx-preview": "^0.3.0",
|
||||||
"echarts": "^5.2.2",
|
"echarts": "^5.2.2",
|
||||||
"element-plus": "^2.2.27",
|
"element-plus": "^2.2.27",
|
||||||
|
"github-markdown-css": "^5.1.0",
|
||||||
|
"highlight.js": "^10.7.2",
|
||||||
|
"hls.js": "^1.3.5",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"html2pdf.js": "^0.10.1",
|
"html2pdf.js": "^0.10.1",
|
||||||
"js-base64": "^3.7.5",
|
"js-base64": "^3.7.5",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
"jspdf": "^2.5.1",
|
"jspdf": "^2.5.1",
|
||||||
|
"marked": "^4.0.17",
|
||||||
"minimatch": "^5.1.0",
|
"minimatch": "^5.1.0",
|
||||||
|
"monaco-editor": "^0.36.1",
|
||||||
|
"mpegts.js": "^1.7.2",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"path-to-regexp": "^6.2.0",
|
"path-to-regexp": "^6.2.0",
|
||||||
@ -45,8 +54,10 @@
|
|||||||
"v-contextmenu": "^3.0.0",
|
"v-contextmenu": "^3.0.0",
|
||||||
"v3-img-preview-enhance": "^1.1.18",
|
"v3-img-preview-enhance": "^1.1.18",
|
||||||
"vue": "^3.2.40",
|
"vue": "^3.2.40",
|
||||||
|
"vue-3d-loader": "^2.0.8",
|
||||||
"vue-clipboard3": "^2.0.0",
|
"vue-clipboard3": "^2.0.0",
|
||||||
"vue-i18n": "^9.1.9",
|
"vue-i18n": "^9.1.9",
|
||||||
|
"vue-pdf-embed": "^1.1.4",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.1.6",
|
||||||
"vue3-print-nb": "^0.1.4",
|
"vue3-print-nb": "^0.1.4",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
|
19
web/src/api/common.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// import axios from "~/http/request"
|
||||||
|
import axiosOrigin from 'axios';
|
||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
// 直接获取文件内容
|
||||||
|
export const getFileTextReq = (url) => {
|
||||||
|
return axiosOrigin.get(url, {
|
||||||
|
withCredentials: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用服务端接口获取文件内容
|
||||||
|
export function getFileTextFromServerReq(data){
|
||||||
|
return request({
|
||||||
|
url: "/api/parse/content",
|
||||||
|
method: "get",
|
||||||
|
params:data
|
||||||
|
})
|
||||||
|
}
|
120
web/src/api/datamanagement/index.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
//查询所有试验数据管理试验任务管理
|
||||||
|
export function tstaskList() {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/tstask/list',
|
||||||
|
method: 'post',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//获取试验任务节点树形结构
|
||||||
|
export function getTsNodesTree(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-nodes/getTsNodesTree',
|
||||||
|
method: 'post',
|
||||||
|
params:params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//增加试验任务节点
|
||||||
|
export function addTsNodes(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-nodes/addTsNodes',
|
||||||
|
method: 'post',
|
||||||
|
data:params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 修改试验任务节点
|
||||||
|
export function updateTsNodes(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-nodes/updateTsNodes',
|
||||||
|
method: 'post',
|
||||||
|
data:params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//根据ID删除试验任务节点
|
||||||
|
export function deleteTsNodesById(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-nodes/deleteTsNodesById',
|
||||||
|
method: 'post',
|
||||||
|
params:params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//分页查询实验数据管理文档内容
|
||||||
|
export function tsFilesPage(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-files/page',
|
||||||
|
method: 'get',
|
||||||
|
params:params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//新增试验数据管理文档内容
|
||||||
|
export function addTsFiles(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-files/addTsFiles',
|
||||||
|
method: 'post',
|
||||||
|
data:params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//修改试验数据管理文档内容
|
||||||
|
export function updateTsFiles(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-files/updateTsFiles',
|
||||||
|
method: 'post',
|
||||||
|
data:params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//根据ID删除试验数据管理文档内容
|
||||||
|
export function deleteTsFilesById(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-files/deleteTsFilesById',
|
||||||
|
method: 'post',
|
||||||
|
params:params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//批量删除试验数据管理文档内容
|
||||||
|
export function deleteTsFilesByIds(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-files/deleteTsFilesByIds',
|
||||||
|
method: 'post',
|
||||||
|
params:params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//压缩
|
||||||
|
export function compress(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-files/compress',
|
||||||
|
method: 'post',
|
||||||
|
params:params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//解压
|
||||||
|
export function Decompression(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-files/decompression',
|
||||||
|
method: 'post',
|
||||||
|
params:params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//对比两个目录的文件差异
|
||||||
|
export function compare(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-files/compare',
|
||||||
|
method: 'post',
|
||||||
|
params:params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//从备份空间下载到工作空间
|
||||||
|
export function downloadToLocal(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-files/downloadToLocal',
|
||||||
|
method: 'post',
|
||||||
|
params:params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//将文件上传到备份空间
|
||||||
|
export function uploadToBackup(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/experimentalData/ts-files/uploadToBackup',
|
||||||
|
method: 'post',
|
||||||
|
params:params,
|
||||||
|
});
|
||||||
|
}
|
1
web/src/assets/image/notFound.svg
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
web/src/assets/image/video-download.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
web/src/assets/image/video-iina.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
web/src/assets/image/video-motrix.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
web/src/assets/image/video-mxplayer-pro.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
web/src/assets/image/video-mxplayer.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
web/src/assets/image/video-nplayer.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
web/src/assets/image/video-potplayer.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
web/src/assets/image/video-thunder.png
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
web/src/assets/image/video-vlc.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
web/src/assets/images/chayi.png
Normal file
After Width: | Height: | Size: 323 B |
BIN
web/src/assets/images/jieyasuo.png
Normal file
After Width: | Height: | Size: 285 B |
BIN
web/src/assets/images/yasuo.png
Normal file
After Width: | Height: | Size: 310 B |
@ -57,7 +57,7 @@
|
|||||||
class="top-0.5 relative inline text-gray-500 mr-1 text-lg cursor-pointer rounded-full hover:bg-gray-200 box animate__animated animate__fadeIn" /> -->
|
class="top-0.5 relative inline text-gray-500 mr-1 text-lg cursor-pointer rounded-full hover:bg-gray-200 box animate__animated animate__fadeIn" /> -->
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.status === 'finished'">
|
<div v-else-if="item.status === 'finished'">
|
||||||
<img src="@/assets/images/delete.png" @click="removeUploadFileByIndex(item.index)" alt=""
|
<img src="@/assets/images/delete.png" @click="removeUploadFileByIndex(item.index),delfile(item)" alt=""
|
||||||
class="inline text-red-400 mr-1 text-base cursor-pointer rounded-full hover:bg-gray-200 box animate__animated animate__fadeIn">
|
class="inline text-red-400 mr-1 text-base cursor-pointer rounded-full hover:bg-gray-200 box animate__animated animate__fadeIn">
|
||||||
<!-- <svg-icon @click="removeUploadFileByIndex(item.index)" name="delete"
|
<!-- <svg-icon @click="removeUploadFileByIndex(item.index)" name="delete"
|
||||||
class="inline text-red-400 mr-1 text-base cursor-pointer rounded-full hover:bg-gray-200 box animate__animated animate__fadeIn" /> -->
|
class="inline text-red-400 mr-1 text-base cursor-pointer rounded-full hover:bg-gray-200 box animate__animated animate__fadeIn" /> -->
|
||||||
@ -68,7 +68,7 @@
|
|||||||
class="inline text-red-500 mr-1 text-base cursor-pointer rounded-full hover:bg-gray-200 box animate__animated animate__fadeIn" /> -->
|
class="inline text-red-500 mr-1 text-base cursor-pointer rounded-full hover:bg-gray-200 box animate__animated animate__fadeIn" /> -->
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.status === 'waiting'">
|
<div v-else-if="item.status === 'waiting'">
|
||||||
<img src="@/assets/images/delete.png" @click="removeUploadFileByIndex(item.index)" alt=""
|
<img src="@/assets/images/delete.png" @click="removeUploadFileByIndex(item.index),delfile(item)" alt=""
|
||||||
class="inline text-red-400 mr-1 text-base cursor-pointer rounded-full hover:bg-gray-200 box animate__animated animate__fadeIn">
|
class="inline text-red-400 mr-1 text-base cursor-pointer rounded-full hover:bg-gray-200 box animate__animated animate__fadeIn">
|
||||||
<!-- <svg-icon @click="removeUploadFileByIndex(item.index)" name="delete"
|
<!-- <svg-icon @click="removeUploadFileByIndex(item.index)" name="delete"
|
||||||
class="inline text-red-400 mr-1 text-base cursor-pointer rounded-full hover:bg-gray-200 box animate__animated animate__fadeIn" /> -->
|
class="inline text-red-400 mr-1 text-base cursor-pointer rounded-full hover:bg-gray-200 box animate__animated animate__fadeIn" /> -->
|
||||||
@ -104,6 +104,7 @@
|
|||||||
import common from "@/components/file/common";
|
import common from "@/components/file/common";
|
||||||
import { ref, onMounted,defineEmits } from 'vue';
|
import { ref, onMounted,defineEmits } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { batchDeleteReq } from "@/api/file-operator";
|
||||||
let router = useRouter();
|
let router = useRouter();
|
||||||
let route = useRoute();
|
let route = useRoute();
|
||||||
|
|
||||||
@ -135,6 +136,32 @@ const dropBoxRef = ref();
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
listenDropFile();
|
listenDropFile();
|
||||||
})
|
})
|
||||||
|
function delfile(row){
|
||||||
|
// debugger
|
||||||
|
let fileArrOne = JSON.parse(localStorage.getItem('fileArr')) || [];
|
||||||
|
// 过滤掉不需要的条目
|
||||||
|
fileArrOne = fileArrOne.filter(item => item.name !== row.name);
|
||||||
|
if(fileArrOne.length > 0){
|
||||||
|
localStorage.setItem('fileArr', JSON.stringify(fileArrOne));
|
||||||
|
}else{
|
||||||
|
localStorage.setItem('fileArr', '');
|
||||||
|
}
|
||||||
|
// 回写 localStorage
|
||||||
|
|
||||||
|
const parmas= {
|
||||||
|
deleteItems: [
|
||||||
|
{
|
||||||
|
name: row.name,
|
||||||
|
password: "",
|
||||||
|
path: localStorage.getItem('filepath'),
|
||||||
|
type: "FILE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
storageKey: "minio"
|
||||||
|
}
|
||||||
|
batchDeleteReq(JSON.stringify(parmas)).then((res)=>{
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ export default function useFileData() {
|
|||||||
|
|
||||||
// 点击文件时,判断是文件夹则进入文件夹,是文件则进行预览
|
// 点击文件时,判断是文件夹则进入文件夹,是文件则进行预览
|
||||||
const openRow = (row) => {
|
const openRow = (row) => {
|
||||||
if (!row.name) {
|
if (!row.fileName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fileDataStore.updateCurrentClickRow(row);
|
fileDataStore.updateCurrentClickRow(row);
|
||||||
@ -179,8 +179,6 @@ export default function useFileData() {
|
|||||||
|
|
||||||
// 获取文件类型
|
// 获取文件类型
|
||||||
let fileType = row.fileType;
|
let fileType = row.fileType;
|
||||||
|
|
||||||
|
|
||||||
switch (fileType) {
|
switch (fileType) {
|
||||||
case 'video': openVideo(); break;
|
case 'video': openVideo(); break;
|
||||||
case 'image': openImage(row); break;
|
case 'image': openImage(row); break;
|
||||||
@ -192,7 +190,7 @@ export default function useFileData() {
|
|||||||
default: batchDownloadFile(row);
|
default: batchDownloadFile(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelection();
|
// clearSelection();
|
||||||
} else {
|
} else {
|
||||||
if (row.type === 'ROOT') {
|
if (row.type === 'ROOT') {
|
||||||
routerRef.value.push(row.path);
|
routerRef.value.push(row.path);
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
renameFileReq,
|
renameFileReq,
|
||||||
renameFolderReq,
|
renameFolderReq,
|
||||||
} from "@/api/file-operator";
|
} from "@/api/file-operator";
|
||||||
|
import { ElMessageBox, ElMessage } from "element-plus";
|
||||||
import useFileDataStore from "@/components/file/stores/file-data";
|
import useFileDataStore from "@/components/file/stores/file-data";
|
||||||
let fileDataStore = useFileDataStore();
|
let fileDataStore = useFileDataStore();
|
||||||
|
|
||||||
|
@ -25,25 +25,26 @@ export default function useFilePreview() {
|
|||||||
dialogVideoVisible.value = true;
|
dialogVideoVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const openAudio = () => {
|
const openAudio = (row) => {
|
||||||
fileDataStore.updateAudioList(fileDataStore.filterFileByType('audio'));
|
fileDataStore.updateAudioList([row]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const openImage = (row) => {
|
const openImage = (row) => {
|
||||||
// 过滤当前页面中所有图片,并记录当前打开的文件的索引位置
|
// 过滤当前页面中所有图片,并记录当前打开的文件的索引位置
|
||||||
let images = [];
|
let images = [];
|
||||||
let currIndex = 0;
|
let currIndex = 0;
|
||||||
let imagePreviewMode = globalConfigStore.zfileConfig.imagePreview.mode;
|
// let imagePreviewMode = globalConfigStore.zfileConfig.imagePreview.mode;
|
||||||
if (imagePreviewMode === 'only') {
|
images.push(row.url);
|
||||||
images.push(row.url);
|
// if (imagePreviewMode === 'only') {
|
||||||
} else {
|
|
||||||
fileDataStore.filterFileByType('image').forEach((image, index) => {
|
// } else {
|
||||||
if (row.name === image.name) {
|
// fileDataStore.filterFileByType('image').forEach((image, index) => {
|
||||||
currIndex = index;
|
// if (row.name === image.name) {
|
||||||
}
|
// currIndex = index;
|
||||||
images.push(image.url);
|
// }
|
||||||
})
|
// images.push(image.url);
|
||||||
}
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
v3ImgPreviewFn({
|
v3ImgPreviewFn({
|
||||||
images: images,
|
images: images,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {ElLoading} from "element-plus";
|
import {ElLoading} from "element-plus";
|
||||||
|
import { ElMessageBox, ElMessage } from "element-plus";
|
||||||
import { ref,reactive,computed,nextTick,watch} from "vue";
|
import { ref,reactive,computed,nextTick,watch} from "vue";
|
||||||
import {uploadFileReq} from "@/api/file-operator";
|
import {uploadFileReq} from "@/api/file-operator";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
@ -354,10 +355,14 @@ export default function useFileUpload() {
|
|||||||
* @param param
|
* @param param
|
||||||
*/
|
*/
|
||||||
const beforeUpload = (param) => {
|
const beforeUpload = (param) => {
|
||||||
uploadFile(param.file, param.uploadBasePath);
|
let storageKeyArr = JSON.parse(localStorage.getItem('storageKey'))
|
||||||
|
storageKeyArr.forEach(item=>{
|
||||||
|
uploadFile(param.file, param.uploadBasePath,item);
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
// 文件上传操作.
|
// 文件上传操作.
|
||||||
const uploadFile = (file, uploadBasePath) => {
|
const uploadFile = (file, uploadBasePath,item) => {
|
||||||
const fileIndex = uploadIndex++;
|
const fileIndex = uploadIndex++;
|
||||||
|
|
||||||
uploadBasePath = uploadBasePath || currentPath.value;
|
uploadBasePath = uploadBasePath || currentPath.value;
|
||||||
@ -385,7 +390,7 @@ export default function useFileUpload() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
let param = {
|
let param = {
|
||||||
storageKey: 'minio',
|
storageKey: item,
|
||||||
path: localStorage.getItem('filepath'),
|
path: localStorage.getItem('filepath'),
|
||||||
name: file.name,
|
name: file.name,
|
||||||
size: file.size
|
size: file.size
|
||||||
@ -438,13 +443,19 @@ export default function useFileUpload() {
|
|||||||
let proxyUploadType = common.storageType.proxyType;
|
let proxyUploadType = common.storageType.proxyType;
|
||||||
let s3UploadType = common.storageType.s3Type;
|
let s3UploadType = common.storageType.s3Type;
|
||||||
let onedriveUploadType = common.storageType.micrsoftType;
|
let onedriveUploadType = common.storageType.micrsoftType;
|
||||||
if (proxyUploadType.includes( param.storageKey)) {
|
let storagea
|
||||||
|
if( param.storageKey === 'local'){
|
||||||
|
storagea = 'local'
|
||||||
|
}else if( param.storageKey === 'minio'){
|
||||||
|
storagea = 'minio'
|
||||||
|
}
|
||||||
|
if (proxyUploadType.includes( storagea)) {
|
||||||
fileProxyUpload(file, res.data, fileIndex);
|
fileProxyUpload(file, res.data, fileIndex);
|
||||||
} else if (s3UploadType.includes( param.storageKey)) {
|
} else if (s3UploadType.includes( storagea)) {
|
||||||
s3FileUpload(file, res.data, fileIndex);
|
s3FileUpload(file, res.data, fileIndex);
|
||||||
} else if (onedriveUploadType.includes( param.storageKey)) {
|
} else if (onedriveUploadType.includes( storagea)) {
|
||||||
onedriveUpload(file, res.data, fileIndex);
|
onedriveUpload(file, res.data, fileIndex);
|
||||||
} else if ( param.storageKey === 'upyun') {
|
} else if ( storagea === 'upyun') {
|
||||||
upyunFileUpload(file, res.data, fileIndex);
|
upyunFileUpload(file, res.data, fileIndex);
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
88
web/src/components/file/preview/AudioPlayer.vue
Normal file
47
web/src/components/file/preview/CopyCode.vue
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<div class="copy-content">
|
||||||
|
<!-- 复制按钮 -->
|
||||||
|
<div
|
||||||
|
class="copy-btn code-data-copy"
|
||||||
|
@click="copyMessage">
|
||||||
|
<svg-icon name="copy2"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { toClipboard } from '@soerenmartius/vue3-clipboard'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
code: String
|
||||||
|
});
|
||||||
|
|
||||||
|
const copyMessage = () => {
|
||||||
|
toClipboard(props.code).then(() => {
|
||||||
|
ElMessage.success('复制成功');
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessage.success('复制失败');
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.copy-content {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.copy-btn {
|
||||||
|
user-select: none;
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
top: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: 0.3s;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
&:active {
|
||||||
|
background: rgba(253, 253, 253, 0.575);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
198
web/src/components/file/preview/FileGallery.vue
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 画廊模式 -->
|
||||||
|
<div class="zfile-gallery-body">
|
||||||
|
<div class="zfile-img-body" v-if="transferResult.length > 0">
|
||||||
|
<div class="zfile-img-row" v-for="(rowItem, index) in transferResult" :key="index">
|
||||||
|
<div class="zfile-img-col"
|
||||||
|
@click="openGalleryImage(colItem)"
|
||||||
|
:style="{ display: globalConfigStore.zfileConfig.gallery.showInfoMode === 'hover' ? 'flex' : 'block'}"
|
||||||
|
v-for="colItem in rowItem">
|
||||||
|
<template v-if="colItem?.url">
|
||||||
|
<img class="zfile-gallery-img lazyload"
|
||||||
|
data-sizes="auto"
|
||||||
|
@load="loadImg"
|
||||||
|
:class="globalConfigStore.zfileConfig.gallery.roundedBorder ? 'zfile-gallery-img-rounded' : ''"
|
||||||
|
:data-src="colItem.url"
|
||||||
|
loading="lazy"
|
||||||
|
:alt="colItem.name"/>
|
||||||
|
<div v-if="globalConfigStore.zfileConfig.gallery.showInfo &&
|
||||||
|
globalConfigStore.zfileConfig.gallery.showInfoMode === 'hover'"
|
||||||
|
:class="globalConfigStore.zfileConfig.gallery.roundedBorder ? 'zfile-gallery-img-rounded' : ''"
|
||||||
|
v-show="loadedList.includes(colItem.name)"
|
||||||
|
class="zfile-gallery-img-hover-info">
|
||||||
|
<span class="zfile-gallery-img-text">{{colItem.name}}</span>
|
||||||
|
<span class="zfile-gallery-img-text">{{common.fileSizeFormat(colItem.size)}}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-show="loadedList.includes(colItem.name)"
|
||||||
|
v-if="globalConfigStore.zfileConfig.gallery.showInfo &&
|
||||||
|
globalConfigStore.zfileConfig.gallery.showInfoMode === 'bottom'">
|
||||||
|
<span class="zfile-gallery-img-text"> {{ colItem.name }} </span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="h-full">
|
||||||
|
<el-empty class="h-full" description="当前文件夹无图片">
|
||||||
|
<el-button type="primary" @click="fileDataStore.imgMode = false">退出画廊模式</el-button>
|
||||||
|
</el-empty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {computed, reactive, ref} from "vue";
|
||||||
|
import common from "~/common";
|
||||||
|
|
||||||
|
import 'lazysizes';
|
||||||
|
import useGlobalConfigStore from "~/stores/global-config";
|
||||||
|
let globalConfigStore = useGlobalConfigStore();
|
||||||
|
|
||||||
|
import useFileDataStore from "~/stores/file-data";
|
||||||
|
let fileDataStore = useFileDataStore();
|
||||||
|
|
||||||
|
// 计算图片二维列表, 多行 * 多列
|
||||||
|
const transferResult = computed(() => {
|
||||||
|
// 获取图片列表
|
||||||
|
let imgList = fileDataStore.filterFileByType('image');
|
||||||
|
|
||||||
|
if (imgList.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片二维数组, 表示每行每列的图片
|
||||||
|
let imgArray = ref([]);
|
||||||
|
|
||||||
|
// 图片列数
|
||||||
|
let galleryColumn = globalConfigStore.zfileConfig.gallery.column;
|
||||||
|
|
||||||
|
// 当前行数
|
||||||
|
let currRow = 0;
|
||||||
|
|
||||||
|
imgList.forEach((item, index) => {
|
||||||
|
if (index % galleryColumn === 0) {
|
||||||
|
if (index !== 0) {
|
||||||
|
currRow++;
|
||||||
|
}
|
||||||
|
imgArray.value[currRow] = [];
|
||||||
|
}
|
||||||
|
imgArray.value[currRow].push(item);
|
||||||
|
})
|
||||||
|
|
||||||
|
// 图片行转列函数
|
||||||
|
function transfer(oldArr) {
|
||||||
|
return oldArr[0].map((col, i) => oldArr.map(row => row[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片行转列完成后的数组
|
||||||
|
return transfer(imgArray.value);
|
||||||
|
})
|
||||||
|
|
||||||
|
// 图片行间距 px
|
||||||
|
let galleryRowSpacingPx = computed(() => {
|
||||||
|
return globalConfigStore.zfileConfig.gallery.rowSpacing + 'px';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图片列间距 px
|
||||||
|
let galleryColSpacingPx = computed(() => {
|
||||||
|
return `0 ${globalConfigStore.zfileConfig.gallery.columnSpacing / 2}px`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图片外部容器宽度, 100 / 图片列数
|
||||||
|
let galleryRowWidth = computed(() => {
|
||||||
|
// 图片列数
|
||||||
|
let galleryColumn = globalConfigStore.zfileConfig.gallery.column;
|
||||||
|
let galleryColumnSpacing = globalConfigStore.zfileConfig.gallery.columnSpacing;
|
||||||
|
return `calc(calc(100% / ${globalConfigStore.zfileConfig.gallery.column}) - calc(${(galleryColumn - 1) * galleryColumnSpacing}px / ${galleryColumn}))`;
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 引入文件预览组件
|
||||||
|
import useFilePreview from '~/composables/file/useFilePreview';
|
||||||
|
const { openImage } = useFilePreview();
|
||||||
|
const openGalleryImage = (item) => {
|
||||||
|
if (!globalConfigStore.zfileConfig.imagePreview.gallery) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (loadedList.includes(item.name)) {
|
||||||
|
openImage(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已加载完的图片列表, 已加载完才悬浮显示标题
|
||||||
|
let loadedList = reactive([]);
|
||||||
|
const loadImg = (e) => {
|
||||||
|
loadedList.push(e.currentTarget.alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.zfile-gallery-body {
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
.zfile-img-body {
|
||||||
|
@apply flex h-full flex-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zfile-img-row {
|
||||||
|
width: v-bind('galleryRowWidth');
|
||||||
|
margin: v-bind('galleryColSpacingPx');
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.zfile-img-col {
|
||||||
|
@apply flex overflow-hidden relative text-center;
|
||||||
|
margin-bottom: v-bind('galleryRowSpacingPx');
|
||||||
|
|
||||||
|
.zfile-gallery-img {
|
||||||
|
@apply border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zfile-gallery-img:not(.loaded) {
|
||||||
|
@apply w-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zfile-gallery-img:not(.lazyloaded) {
|
||||||
|
@apply min-h-[150px];
|
||||||
|
}
|
||||||
|
|
||||||
|
.zfile-gallery-img-rounded {
|
||||||
|
@apply rounded-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zfile-gallery-img-text {
|
||||||
|
@apply overflow-ellipsis overflow-hidden whitespace-nowrap text-sm opacity-70;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zfile-gallery-img-hover-info {
|
||||||
|
@apply absolute top-0 h-1/2 left-0 right-0 text-sm p-2
|
||||||
|
transition-opacity duration-300
|
||||||
|
flex justify-between
|
||||||
|
text-white space-x-10 opacity-0;
|
||||||
|
background: linear-gradient(180deg,rgba(0,0,0,.6),transparent 120px);
|
||||||
|
|
||||||
|
.zfile-gallery-img-text:last-child {
|
||||||
|
@apply text-right min-w-fit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .zfile-gallery-img-hover-info {
|
||||||
|
@apply opacity-100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
102
web/src/components/file/preview/MarkdownViewer.vue
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<template>
|
||||||
|
<div class="content">
|
||||||
|
<div class="dialog-scroll markdown-body" v-html="markdownHtml"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {marked} from 'marked';
|
||||||
|
import { computed, onMounted, onUpdated, ref } from "vue";
|
||||||
|
import hljs from 'highlight.js';
|
||||||
|
import 'highlight.js/styles/github.css';
|
||||||
|
import 'github-markdown-css';
|
||||||
|
|
||||||
|
|
||||||
|
import {getFileTextFromServerReq, getFileTextReq} from "@/api/common";
|
||||||
|
|
||||||
|
let props = defineProps({
|
||||||
|
fileUrl: String,
|
||||||
|
fileName: String
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileContent = ref('');
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
let fileUrl = props.fileUrl;
|
||||||
|
getFileTextReq(fileUrl).then((res) => {
|
||||||
|
fileContent.value = res.data;
|
||||||
|
}).catch((e) => {
|
||||||
|
console.log(`加载文本文件: [${props.fileName}] - [${props.fileUrl}] 失败, 尝试从服务端加载.`, e);
|
||||||
|
alert('加载文件预览器失败,请检测文件下载链接是否正常');
|
||||||
|
ElMessage.warning({
|
||||||
|
message: `加载文件预览器失败,请检测文件下载链接是否正常或是否限制了跨域访问, ${fileUrl}`,
|
||||||
|
duration: 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
let markdownHtml = computed(() => {
|
||||||
|
// url 新窗口打开.
|
||||||
|
let renderer = new marked.Renderer();
|
||||||
|
renderer.link = function() {
|
||||||
|
let link = marked.Renderer.prototype.link.apply(this, arguments);
|
||||||
|
return link.replace("<a","<a target='_blank'");
|
||||||
|
};
|
||||||
|
marked.setOptions({
|
||||||
|
renderer: renderer
|
||||||
|
});
|
||||||
|
|
||||||
|
return marked(fileContent.value, {
|
||||||
|
highlight: function(code) {
|
||||||
|
return hljs.highlightAuto(code).value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
import CodeCopy from '@/components/file/preview/CopyCode.vue';
|
||||||
|
import { createVNode, render } from "vue";
|
||||||
|
|
||||||
|
onUpdated(() => {
|
||||||
|
document.querySelectorAll('pre').forEach(el => {
|
||||||
|
if (el.classList.contains('code-copy-added')) return
|
||||||
|
// https://cn.vuejs.org/v2/api/index.html#Vue-extend
|
||||||
|
/* 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象 */
|
||||||
|
|
||||||
|
let codeCopyVNode = createVNode(CodeCopy, {
|
||||||
|
code: el.innerText,
|
||||||
|
});
|
||||||
|
let mountNode = document.createElement("div");
|
||||||
|
render(codeCopyVNode, mountNode);
|
||||||
|
el.classList.add('code-copy-added')
|
||||||
|
el.classList.add('hljs')
|
||||||
|
el.appendChild(mountNode)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.content {
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content .markdown-body >>> pre {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.content .markdown-body >>> pre:hover .copy-btn {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-scroll {
|
||||||
|
height: calc(80vh);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content >>> .code-copy-added {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full h-full">
|
||||||
|
<el-skeleton animated>
|
||||||
|
<template #template>
|
||||||
|
<el-skeleton-item variant="p" class="!w-1/4"></el-skeleton-item>
|
||||||
|
<br>
|
||||||
|
<el-skeleton-item variant="p" class="!w-1/2"></el-skeleton-item>
|
||||||
|
</template>
|
||||||
|
</el-skeleton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
@ -0,0 +1,12 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-5 w-full h-[75vh]">
|
||||||
|
<el-skeleton animated count="5">
|
||||||
|
</el-skeleton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
75
web/src/components/file/preview/OfficeViewer.vue
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<div class="zfile-office-viewer">
|
||||||
|
<div id="office-body"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {onMounted} from "vue";
|
||||||
|
import { ElMessageBox, ElMessage } from "element-plus";
|
||||||
|
import useStorageConfigStore from "@/components/file/stores/storage-config";
|
||||||
|
let storageConfigStore = useStorageConfigStore();
|
||||||
|
|
||||||
|
// 组件接收的属性:
|
||||||
|
// fileUrl: 文件下载路径
|
||||||
|
// fileName: 文件名
|
||||||
|
const props = defineProps({
|
||||||
|
fileUrl: String,
|
||||||
|
fileName: String
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadScript(`https://office.zfile.vip/web-apps/apps/api/documents/api.js`, () => {
|
||||||
|
const index = props.fileName.lastIndexOf('.');
|
||||||
|
const fileType = props.fileName.substr(index + 1);
|
||||||
|
const config = {
|
||||||
|
"document": {
|
||||||
|
"fileType": fileType,
|
||||||
|
"title": props.fileName,
|
||||||
|
"url": props.fileUrl,
|
||||||
|
"lang": "zh-CN"
|
||||||
|
},
|
||||||
|
"width": '100%',
|
||||||
|
"editorConfig": {
|
||||||
|
mode: 'view',
|
||||||
|
"lang": "zh-CN"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const docEditor = new DocsAPI.DocEditor("office-body", config);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function loadScript(src, callback) {
|
||||||
|
console.log(src)
|
||||||
|
let script = document.createElement('script'),
|
||||||
|
head = document.getElementsByTagName('head')[0];
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.charset = 'UTF-8';
|
||||||
|
script.src = src;
|
||||||
|
if (script.addEventListener) {
|
||||||
|
script.addEventListener('load', function () {
|
||||||
|
callback();
|
||||||
|
}, false);
|
||||||
|
script.addEventListener('error', function () {
|
||||||
|
ElMessage.warning("调用在线文档服务失败,请检查在线文档服务是否正常");
|
||||||
|
}, false);
|
||||||
|
} else if (script.attachEvent) {
|
||||||
|
script.attachEvent('onreadystatechange', function () {
|
||||||
|
var target = window.event.srcElement;
|
||||||
|
if (target.readyState === 'loaded') {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
head.appendChild(script);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.zfile-office-viewer, #office-body {
|
||||||
|
height: 80vh;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
138
web/src/components/file/preview/PdfViewer.vue
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<template>
|
||||||
|
<div class="zfile-pdf-viewer">
|
||||||
|
<div class="app-header">
|
||||||
|
<div v-if="isLoading">
|
||||||
|
加载页数中...
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<span v-if="showAllPages">
|
||||||
|
共 {{ pageCount }} 页
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else>
|
||||||
|
<button :disabled="page <= 1" @click="page--">❮</button>
|
||||||
|
{{ page }} / {{ pageCount }}
|
||||||
|
<button :disabled="page >= pageCount" @click="page++">❯</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<button class="px-1 sm:px-4" @click="pdfViewCanvasWidthNum-=5">-</button>
|
||||||
|
<span>
|
||||||
|
<span class="hidden sm:inline">缩放比例</span>
|
||||||
|
{{pdfViewCanvasWidthNum}} %
|
||||||
|
</span>
|
||||||
|
<button class="px-1 sm:px-4" @click="pdfViewCanvasWidthNum+=5">+</button>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<label :class="isLoading ? 'invisible' : 'visible'">
|
||||||
|
<input v-model="showAllPages" type="checkbox">
|
||||||
|
显示所有页
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="app-content">
|
||||||
|
<vue-pdf-embed
|
||||||
|
ref="pdfRef"
|
||||||
|
:source="pdfSource"
|
||||||
|
:page="page"
|
||||||
|
@rendered="handleDocumentRender"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import VuePdfEmbed from "vue-pdf-embed";
|
||||||
|
import {ref,computed,watch } from "vue";
|
||||||
|
// 组件接收的属性:
|
||||||
|
// fileUrl: 文件下载路径
|
||||||
|
// fileName: 文件名
|
||||||
|
const props = defineProps({
|
||||||
|
fileUrl: String,
|
||||||
|
fileName: String
|
||||||
|
});
|
||||||
|
|
||||||
|
const pdfRef = ref();
|
||||||
|
|
||||||
|
const pdfViewCanvasWidthNum = ref(100);
|
||||||
|
|
||||||
|
// 图片外部容器宽度, 100 / 图片列数
|
||||||
|
let pdfViewCanvasWidth = computed(() => {
|
||||||
|
return `${pdfViewCanvasWidthNum.value}%`;
|
||||||
|
})
|
||||||
|
|
||||||
|
let isLoading = ref(true);
|
||||||
|
let page = ref(null);
|
||||||
|
let pageCount = ref(1);
|
||||||
|
let pdfSource = ref(props.fileUrl);
|
||||||
|
let showAllPages = ref(true);
|
||||||
|
|
||||||
|
watch(() => showAllPages.value, () => {
|
||||||
|
page.value = showAllPages.value ? null : 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDocumentRender = () => {
|
||||||
|
isLoading.value = false;
|
||||||
|
pageCount.value = pdfRef.value.pageCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
import { useMagicKeys } from '@vueuse/core'
|
||||||
|
const { ArrowLeft, ArrowRight, NumpadAdd, NumpadSubtract } = useMagicKeys()
|
||||||
|
|
||||||
|
// 支持按键翻页
|
||||||
|
watch(() => [ArrowLeft.value, ArrowRight.value], (value) => {
|
||||||
|
if (isLoading.value) return;
|
||||||
|
if (showAllPages.value) return;
|
||||||
|
if (value[0] && page.value > 1) {
|
||||||
|
page.value--;
|
||||||
|
}
|
||||||
|
if (value[1] && page.value < pageCount.value) {
|
||||||
|
page.value++;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 支持按键缩放
|
||||||
|
watch(() => [NumpadSubtract.value, NumpadAdd.value], (value) => {
|
||||||
|
if (value[0]) {
|
||||||
|
pdfViewCanvasWidthNum.value -= 5;
|
||||||
|
}
|
||||||
|
if (value[1]) {
|
||||||
|
pdfViewCanvasWidthNum.value += 5;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.zfile-pdf-viewer {
|
||||||
|
width: 100%;
|
||||||
|
height: 80vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-pdf-embed {
|
||||||
|
:deep(> div) {
|
||||||
|
box-shadow: 0 2px 8px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
@apply w-full h-full mt-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(canvas) {
|
||||||
|
width: v-bind('pdfViewCanvasWidth') !important;
|
||||||
|
height: 100% !important;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header {
|
||||||
|
box-shadow: 0 2px 8px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
background-color: #555;
|
||||||
|
color: #ddd;
|
||||||
|
@apply flex content-between justify-between p-2 sm:p-4 bg-gray-600;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-content {
|
||||||
|
padding: 24px 0;
|
||||||
|
}
|
||||||
|
</style>
|
135
web/src/components/file/preview/TextViewer.vue
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<div class="editor" id="zfile-monaco-editor"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {onMounted, ref, onUnmounted} from 'vue';
|
||||||
|
|
||||||
|
// import * as monaco from 'monaco-editor';
|
||||||
|
// 按需加载核心 api
|
||||||
|
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||||
|
// 按需加载语法高亮
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/html/html.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/css/css.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/scss/scss.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/go/go.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/graphql/graphql.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/ini/ini.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/java/java.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/kotlin/kotlin.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/less/less.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/mysql/mysql.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/php/php.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/cpp/cpp.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/python/python.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/xml/xml.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution'
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution'
|
||||||
|
|
||||||
|
import 'monaco-editor/esm/vs/editor/contrib/find/browser/findController.js'
|
||||||
|
// import 'monaco-editor/esm/vs/editor/contrib/codeAction/browser/codeAction.js'
|
||||||
|
|
||||||
|
// 组件接收的属性:
|
||||||
|
// fileUrl: 文件下载路径
|
||||||
|
// fileName: 文件名
|
||||||
|
const props = defineProps({
|
||||||
|
fileUrl: String,
|
||||||
|
fileName: String
|
||||||
|
});
|
||||||
|
|
||||||
|
// 文件内容
|
||||||
|
const fileContent = ref('');
|
||||||
|
|
||||||
|
// 定义 worker 加载
|
||||||
|
// import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
||||||
|
// import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
||||||
|
// import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
|
||||||
|
// import HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
|
||||||
|
//
|
||||||
|
// self.MonacoEnvironment = {
|
||||||
|
// getWorker(workerId, label) {
|
||||||
|
// if (label === 'json') {
|
||||||
|
// return new JsonWorker()
|
||||||
|
// }
|
||||||
|
// if (label === 'css' || label === 'scss' || label === 'less') {
|
||||||
|
// return new CssWorker()
|
||||||
|
// }
|
||||||
|
// if (label === 'html' || label === 'handlebars' || label === 'razor') {
|
||||||
|
// return new HtmlWorker()
|
||||||
|
// }
|
||||||
|
// return new EditorWorker();
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
import { getFileTextReq, getFileTextFromServerReq } from "@/api/common";
|
||||||
|
|
||||||
|
// 挂载时,加载文件内容,并初始化播放器
|
||||||
|
onMounted(() => {
|
||||||
|
let fileUrl = props.fileUrl;
|
||||||
|
getFileTextReq(fileUrl).then((res) => {
|
||||||
|
fileContent.value = res.data;
|
||||||
|
initMonaco();
|
||||||
|
}).catch((e) => {
|
||||||
|
console.log(`默认加载文本文件: [${props.fileName}] - [${props.fileUrl}] 失败, 尝试从服务端加载.`, e);
|
||||||
|
getFileTextFromServerReq({url: fileUrl}).then((res) => {
|
||||||
|
fileContent.value = res.data;
|
||||||
|
initMonaco();
|
||||||
|
}).catch((e) => {
|
||||||
|
console.log(`从服务器端加载文本文件: [${props.fileName}] - [${props.fileUrl}] 失败.`, e);
|
||||||
|
alert('加载文件预览器失败,请检测文件下载链接是否正常');
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 关闭时销毁所有组件
|
||||||
|
onUnmounted(() => {
|
||||||
|
monaco.editor.getModels().forEach((item) => {
|
||||||
|
item.dispose();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
import useCommon from "@/components/file/useCommon";
|
||||||
|
const { isNotMobile } = useCommon();
|
||||||
|
|
||||||
|
// 初始化编辑器
|
||||||
|
let initMonaco = () => {
|
||||||
|
let model = monaco.editor.createModel(
|
||||||
|
fileContent.value,
|
||||||
|
undefined,
|
||||||
|
monaco.Uri.parse(props.fileName)
|
||||||
|
);
|
||||||
|
|
||||||
|
monaco.editor.create(document.getElementById("zfile-monaco-editor"), {
|
||||||
|
model: model,
|
||||||
|
// table 个数
|
||||||
|
tabSize: 4,
|
||||||
|
// 自动布局
|
||||||
|
automaticLayout: true,
|
||||||
|
// 底部滚动超出
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
// 自动换行
|
||||||
|
wordWrap: true,
|
||||||
|
// 只读
|
||||||
|
readOnly: true,
|
||||||
|
minimap: {
|
||||||
|
enabled: isNotMobile.value
|
||||||
|
},
|
||||||
|
lineNumbers: isNotMobile.value ? 'on' : 'off'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
#zfile-monaco-editor {
|
||||||
|
height: 80vh;
|
||||||
|
//:deep() {
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
</style>
|
22
web/src/components/file/preview/TextViewerAsyncLoading.vue
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<div class="zfile-async-loading p-10 h-[75vh] w-full">
|
||||||
|
<div class="flex justify-between h-full">
|
||||||
|
<div class="w-1/4">
|
||||||
|
<el-skeleton animated count="6" />
|
||||||
|
</div>
|
||||||
|
<div class="w-20 h-full">
|
||||||
|
<el-skeleton animated class="h-full">
|
||||||
|
<template #template>
|
||||||
|
<el-skeleton-item variant="rect" class="!h-full"></el-skeleton-item>
|
||||||
|
</template>
|
||||||
|
</el-skeleton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
116
web/src/components/file/preview/Three3dPreview.vue
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="float-right mb-1">
|
||||||
|
<span>背景颜色: </span>
|
||||||
|
<el-color-picker v-model="color" show-alpha :predefine="predefineColors" />
|
||||||
|
</div>
|
||||||
|
<vue3dLoader
|
||||||
|
v-if="loadFinish"
|
||||||
|
class="h-[75vh] clear-right"
|
||||||
|
:fileType="fileSuffix"
|
||||||
|
:mtlPath="mtlPath"
|
||||||
|
:backgroundColor="color"
|
||||||
|
:filePath='fileLinkUrl'>
|
||||||
|
</vue3dLoader>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { vue3dLoader } from "vue-3d-loader";
|
||||||
|
|
||||||
|
import useFilePwd from "@/components/file/file/useFilePwd";
|
||||||
|
let { getPathPwd } = useFilePwd();
|
||||||
|
|
||||||
|
import useFilePreview from "@/components/file/file/useFilePreview";
|
||||||
|
const { dialogVideoVisible, dialogTextVisible, dialogPdfVisible, dialogOfficeVisible, dialog3dVisible } = useFilePreview();
|
||||||
|
|
||||||
|
import useRouterData from "@/components/file/useRouterData";
|
||||||
|
let { currentPath, storageKey } = useRouterData();
|
||||||
|
|
||||||
|
import useStorageConfigStore from "@/components/file/stores/storage-config";
|
||||||
|
let storageConfigStore = useStorageConfigStore();
|
||||||
|
|
||||||
|
import { loadFileItemReq } from "@/components/file/api/home";
|
||||||
|
import common from "@/components/file/common";
|
||||||
|
|
||||||
|
const loadFinish = ref(false);
|
||||||
|
let mtlPath = ref();
|
||||||
|
|
||||||
|
// 组件接收的属性:
|
||||||
|
// fileUrl: 文件下载路径
|
||||||
|
// fileName: 文件名
|
||||||
|
const props = defineProps({
|
||||||
|
fileUrl: String,
|
||||||
|
fileName: String
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
init();
|
||||||
|
})
|
||||||
|
|
||||||
|
let fileSuffix = common.getFileSuffix(props.fileName);
|
||||||
|
let fileLinkUrl = ref();
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
|
||||||
|
// 完整路径
|
||||||
|
let pathAndName = common.removeDuplicateSeparator(`${currentPath.value}/${props.fileName}`);
|
||||||
|
|
||||||
|
// 完整直链路径
|
||||||
|
fileLinkUrl.value = common.removeDuplicateSeparator(storageConfigStore.globalConfig.domain + "/" +
|
||||||
|
storageConfigStore.globalConfig.directLinkPrefix + "/" +
|
||||||
|
storageKey.value + "/" +
|
||||||
|
pathAndName);
|
||||||
|
|
||||||
|
if (fileSuffix === ".obj") {
|
||||||
|
let basicName = common.getFileName(props.fileName);
|
||||||
|
|
||||||
|
let mtlPathAndName = common.removeDuplicateSeparator(`${currentPath.value}/${basicName}.mtl`);
|
||||||
|
|
||||||
|
let mtlFileItemParam = {
|
||||||
|
storageKey: storageKey.value,
|
||||||
|
path: mtlPathAndName,
|
||||||
|
password: getPathPwd()
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileItemResult = await loadFileItemReq(mtlFileItemParam);
|
||||||
|
|
||||||
|
if (fileItemResult?.data?.code === 0) {
|
||||||
|
console.log('检测到当前存在 mtl 纹理文件: ' + mtlFileItemParam, fileItemResult);
|
||||||
|
mtlPath.value = fileItemResult.data.data.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadFinish.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
mtlPath.value = null;
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const color = useStorage('zfile-3d-color', '#ffffff');
|
||||||
|
|
||||||
|
const predefineColors = ref([
|
||||||
|
'#ff4500',
|
||||||
|
'#ff8c00',
|
||||||
|
'#ffd700',
|
||||||
|
'#90ee90',
|
||||||
|
'#00ced1',
|
||||||
|
'#1e90ff',
|
||||||
|
'#c71585',
|
||||||
|
'rgba(255, 69, 0, 0.68)',
|
||||||
|
'rgb(255, 120, 0)',
|
||||||
|
'hsv(51, 100, 98)',
|
||||||
|
'hsva(120, 40, 94, 0.5)',
|
||||||
|
'hsl(181, 100%, 37%)',
|
||||||
|
'hsla(209, 100%, 56%, 0.73)',
|
||||||
|
'#c7158577',
|
||||||
|
])
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
576
web/src/components/file/preview/VideoPlayer.vue
Normal file
@ -0,0 +1,576 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="artplayer-app"></div>
|
||||||
|
<div class="zfile-video-switch-tools" v-if="currentVideo && common.isMobile.value">
|
||||||
|
<!-- <el-button :disabled="!getPrevAndNextVideo(currentVideo.name).prev" :icon="IconPrev" @click="playPrevVideo">上一个视频</el-button>
|
||||||
|
<el-button :disabled="!getPrevAndNextVideo(currentVideo.name).next" :icon="IconNext" @click="playNextVideo">下一个视频</el-button> -->
|
||||||
|
</div>
|
||||||
|
<div class="zfile-video-tools" :class="hiddenTools ? 'hidden-important' : ''">
|
||||||
|
<template v-if="storageConfigStore.permission.download">
|
||||||
|
<div class="zfile-video-tools-item" @click="openTarget('download')">
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
直接下载
|
||||||
|
</template>
|
||||||
|
<img src="../../../assets/image/video-download.png" alt="下载">
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="zfile-video-tools-item" @click="openTarget('thunder')">
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
使用迅雷下载
|
||||||
|
</template>
|
||||||
|
<img src="../../../assets/image/video-thunder.png" alt="迅雷">
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="zfile-video-tools-item" @click="openTarget('motrix')">
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
使用 motrix 下载
|
||||||
|
</template>
|
||||||
|
<img src="../../../assets/image/video-motrix.png" alt="motrix">
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="zfile-video-tools-item" @click="openTarget('potplayer')">
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
使用 PotPlayer 播放
|
||||||
|
</template>
|
||||||
|
<img src="../../../assets/image/video-potplayer.png" alt="PotPlayer">
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="zfile-video-tools-item" @click="openTarget('iina')">
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
使用 IINA 播放
|
||||||
|
</template>
|
||||||
|
<img src="../../../assets/image/video-iina.png" alt="IINA">
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="zfile-video-tools-item" @click="openTarget('vlc')">
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
使用 VLC 播放
|
||||||
|
</template>
|
||||||
|
<img src="../../../assets/image/video-vlc.png" alt="VLC">
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="zfile-video-tools-item" @click="openTarget('nplayer')">
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
使用 nPlayer 播放
|
||||||
|
</template>
|
||||||
|
<img src="../../../assets/image/video-nplayer.png" alt="nPlayer">
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="zfile-video-tools-item" @click="openTarget('mxplayer')">
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
使用 MXPlayer(Free) 播放
|
||||||
|
</template>
|
||||||
|
<img src="../../../assets/image/video-mxplayer.png" alt="MXPlayer(Free)">
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="zfile-video-tools-item" @click="openTarget('mxplayer-pro')">
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
使用 MXPlayer(Pro) 播放
|
||||||
|
</template>
|
||||||
|
<img src="../../../assets/image/video-mxplayer-pro.png" alt="MXPlayer(Pro)">
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="zfile-video-tools-tips" :class="hiddenTools ? 'hidden-important' : ''">
|
||||||
|
tips: 可点击上方的软件图标进行下载播放, 本地播放器解码效果更佳.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Artplayer from "artplayer";
|
||||||
|
import useCommon from "@/components/file/useCommon";
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { ref, onMounted, nextTick, defineAsyncComponent } from "vue";
|
||||||
|
import { useStorage } from '@vueuse/core';
|
||||||
|
let common = useCommon();
|
||||||
|
let route = useRoute();
|
||||||
|
|
||||||
|
|
||||||
|
import useFilePwd from "@/components/file/file/useFilePwd";
|
||||||
|
let { getPathPwd } = useFilePwd();
|
||||||
|
|
||||||
|
|
||||||
|
import useRouterData from "@/components/file/useRouterData";
|
||||||
|
let { currentPath, storageKey } = useRouterData();
|
||||||
|
|
||||||
|
import { loadFileItemReq } from "@/api/home";
|
||||||
|
|
||||||
|
// import IconPrev from '~icons/custom/prev';
|
||||||
|
// import IconNext from '~icons/custom/next';
|
||||||
|
|
||||||
|
|
||||||
|
import useFileDataStore from "@/components/file/stores/file-data";
|
||||||
|
let fileDataStore = useFileDataStore();
|
||||||
|
|
||||||
|
import useStorageConfigStore from "@/components/file/stores/storage-config";
|
||||||
|
let storageConfigStore = useStorageConfigStore();
|
||||||
|
|
||||||
|
// 获取视频列表, 如果是当前视频, 则设置为默认勾选.
|
||||||
|
const getVideoList = (currentName) => {
|
||||||
|
let result = [];
|
||||||
|
fileDataStore.filterFileByType('video').forEach(file => {
|
||||||
|
result.push({
|
||||||
|
default: file.name === currentName,
|
||||||
|
html: file.name,
|
||||||
|
url: file.url
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentVideo = ref(null);
|
||||||
|
|
||||||
|
const openTarget = (mode) => {
|
||||||
|
switch (mode) {
|
||||||
|
case 'download':
|
||||||
|
window.location = currentVideo.value.url;
|
||||||
|
break;
|
||||||
|
case 'thunder':
|
||||||
|
window.location = `thunder://${btoa('AA' + currentVideo.value.url + 'ZZ')}`;
|
||||||
|
break;
|
||||||
|
case 'motrix':
|
||||||
|
window.location = `motrix://new-task?uri=${encodeURIComponent(currentVideo.value.url)}&out=${encodeURIComponent(currentVideo.value.name)}`;
|
||||||
|
break;
|
||||||
|
case 'potplayer':
|
||||||
|
window.location = `potplayer://${currentVideo.value.url}`;
|
||||||
|
break;
|
||||||
|
case 'iina':
|
||||||
|
window.location = `iina://weblink?url=${encodeURIComponent(currentVideo.value.url)}`;
|
||||||
|
break;
|
||||||
|
case 'vlc':
|
||||||
|
window.location = `vlc://${currentVideo.value.url}`;
|
||||||
|
break;
|
||||||
|
case 'nplayer':
|
||||||
|
window.location = `nplayer-${currentVideo.value.url}`;
|
||||||
|
break;
|
||||||
|
case 'mxplayer':
|
||||||
|
window.location = 'intent:' + currentVideo.value.url + '#Intent;package=com.mxtech.videoplayer.ad;S.title=' + currentVideo.value.name +';end';
|
||||||
|
break;
|
||||||
|
case 'mxplayer-pro':
|
||||||
|
window.location = 'intent:' + currentVideo.value.url + '#Intent;package=com.mxtech.videoplayer.pro;S.title=' + currentVideo.value.name +';end';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 获取上一个和下一个视频, 没有则返回空
|
||||||
|
const getPrevAndNextVideo = (currentName) => {
|
||||||
|
let videoList = fileDataStore.filterFileByType('video');
|
||||||
|
|
||||||
|
clearExceedProgress();
|
||||||
|
for (let i = 0; i < videoList.length; i++){
|
||||||
|
let videoItem = videoList[i];
|
||||||
|
if (videoItem.name === currentName) {
|
||||||
|
return {
|
||||||
|
prev: videoList[i - 1] || null,
|
||||||
|
next: videoList[i + 1] || null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清楚超过进度的视频缓存, 防止再次播放时直接结束.
|
||||||
|
const clearExceedProgress = () => {
|
||||||
|
let playProgress = JSON.parse(localStorage.getItem('_h5_player_play_progress_'));
|
||||||
|
if (playProgress) {
|
||||||
|
for (let key of Object.keys(playProgress)) {
|
||||||
|
if (key.endsWith(playProgress[key].progress)) {
|
||||||
|
delete playProgress[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
localStorage.setItem('_h5_player_play_progress_', JSON.stringify(playProgress));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 字幕列表, 如果是当前视频, 则设置为默认勾选.
|
||||||
|
const getSubtitles = (currentName) => {
|
||||||
|
let subtitleList = [];
|
||||||
|
fileDataStore.fileList.find((item) => {
|
||||||
|
let currentNameBase = currentName.split('.')[0];
|
||||||
|
let name = item.name;
|
||||||
|
if (name === (currentName + ".vtt") ||
|
||||||
|
name === (currentName + ".srt") ||
|
||||||
|
name === (currentName + ".ass") ||
|
||||||
|
name === (currentNameBase + ".vtt") ||
|
||||||
|
name === (currentNameBase + ".srt") ||
|
||||||
|
name === (currentNameBase + ".ass")) {
|
||||||
|
subtitleList.push({
|
||||||
|
default: subtitleList.length === 0,
|
||||||
|
url: item.url,
|
||||||
|
html: name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (subtitleList.length > 0) {
|
||||||
|
subtitleList.push({
|
||||||
|
url: '',
|
||||||
|
html: '关闭字幕'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return subtitleList;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hiddenIcon = '<i class="art-icon art-icon-not-hidden"><svg t="1662978336444" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="968" width="128" height="128"><path d="M711.456 377.184c32.736 31.52 60.928 72.256 84.64 122.208a40.64 40.64 0 0 1 0 34.72c-63.776 135.04-160.48 202.528-290.112 202.528-46.752 0-89.248-8.832-127.456-26.464l37.184-37.184c27.52 10.048 57.6 15.104 90.272 15.104 108.864 0 188.48-55.168 244.608-171.296-21.088-43.584-45.44-78.56-73.44-105.28l34.304-34.336z m13.024-122.816l28.768 28.8a5.376 5.376 0 0 1 0 7.616L272.96 771.04a5.408 5.408 0 0 1-7.648 0l-28.8-28.8a5.376 5.376 0 0 1 0-7.616l71.36-71.36c-35.84-32.384-66.56-75.392-92.032-129.088a40.64 40.64 0 0 1 0-34.72C279.68 364.48 376.384 296.96 505.984 296.96c50.752 0 96.448 10.4 137.12 31.168l73.76-73.728a5.376 5.376 0 0 1 7.616 0z m-218.496 91.136c-108.8 0-188.416 55.168-244.608 171.296 22.944 47.36 49.792 84.608 80.928 112.096l56.256-56.256a118.688 118.688 0 0 1 160.576-160.576l47.424-47.424c-30.336-12.832-63.776-19.136-100.576-19.136z m-70.016 137.088a75.616 75.616 0 0 0-4.64 57.28l95.04-95.04a75.616 75.616 0 0 0-90.4 37.76z m60.416 109.504a75.456 75.456 0 0 0 82.144-82.112l35.616-35.616a118.72 118.72 0 0 1-153.376 153.344l35.616-35.616z" p-id="969"></path></svg></i>';
|
||||||
|
let notHiddenIcon = '<i class="art-icon art-icon-hidden"><svg t="1662978261880" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4451" width="128" height="128"><path d="M512.032 648a136.128 136.128 0 0 1-136-136c0-74.976 60.992-136 136-136s136 61.024 136 136a136.128 136.128 0 0 1-136 136z m0-224c-48.544 0-88 39.456-88 88s39.456 88 88 88 88-39.456 88-88-39.456-88-88-88z" p-id="4452"></path><path d="M512.032 743.616a327.68 327.68 0 0 1-231.872-95.616L142.88 512l137.28-136a327.68 327.68 0 0 1 231.872-95.584c87.328 0 169.664 33.952 231.872 95.584l137.28 136-137.28 136a327.52 327.52 0 0 1-231.872 95.616zM206.528 512l107.424 101.952c53.152 52.672 123.488 81.696 198.048 81.696s144.896-29.024 198.08-81.696L817.536 512l-107.424-101.952c-53.152-52.672-123.488-81.696-198.048-81.696s-144.896 29.024-198.08 81.696L206.528 512z" p-id="4453"></path></svg></i>';
|
||||||
|
let videoListIcon = '<i class="art-icon art-icon-video-list"><svg t="1650551038453" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12028" width="20" height="20"><path d="M111.395066 179.64038l801.208844 0 0 87.866187-801.208844 0 0-87.866187Z" p-id="12029"></path><path d="M111.395066 468.067418l801.208844 0 0 87.866187-801.208844 0 0-87.866187Z" p-id="12030"></path><path d="M111.395066 756.493433l801.208844 0 0 87.866187-801.208844 0 0-87.866187Z" p-id="12031"></path></svg></i>';
|
||||||
|
let subtitleIcon = '<i class="art-icon art-icon-subtitle"><svg xmlns="http://www.w3.org/2000/svg" height="22" width="22" viewBox="0 0 48 48">\n' +
|
||||||
|
' <path d="M0 0h48v48H0z" fill="none"></path>\n' +
|
||||||
|
' <path d="M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zM8 24h8v4H8v-4zm20 12H8v-4h20v4zm12 0h-8v-4h8v4zm0-8H20v-4h20v4z"></path>\n' +
|
||||||
|
'</svg></i>';
|
||||||
|
|
||||||
|
let nextVideoIcon = '<i class="art-icon art-icon-next-video"><svg height="25" width="25" viewBox="0 0 22 22"><path d="M16 5a1 1 0 00-1 1v4.615a1.431 1.431 0 00-.615-.829L7.21 5.23A1.439 1.439 0 005 6.445v9.11a1.44 1.44 0 002.21 1.215l7.175-4.555a1.436 1.436 0 00.616-.828V16a1 1 0 002 0V6C17 5.448 16.552 5 16 5z"></path></svg></i>';
|
||||||
|
let prevVideoIcon = '<i class="art-icon art-icon-prev-video" style="transform: scale(-1,1);"><svg height="25" width="25" viewBox="0 0 22 22"><path d="M16 5a1 1 0 00-1 1v4.615a1.431 1.431 0 00-.615-.829L7.21 5.23A1.439 1.439 0 005 6.445v9.11a1.44 1.44 0 002.21 1.215l7.175-4.555a1.436 1.436 0 00.616-.828V16a1 1 0 002 0V6C17 5.448 16.552 5 16 5z"></path></svg></i>';
|
||||||
|
|
||||||
|
import Mpegts from "mpegts.js";
|
||||||
|
|
||||||
|
import Hls from 'hls.js';
|
||||||
|
|
||||||
|
const autoPlayNextVideo = useStorage('zfile-video-auto-player-next', false);
|
||||||
|
const autoPlayVideo = useStorage('zfile-video-auto-player', false);
|
||||||
|
const hiddenTools = useStorage('zfile-video-hiddle-tools', false);
|
||||||
|
|
||||||
|
let art = null;
|
||||||
|
const initArtPlayer = async (name, url) => {
|
||||||
|
// debugger
|
||||||
|
currentVideo.value = {
|
||||||
|
name,
|
||||||
|
url
|
||||||
|
}
|
||||||
|
document.querySelector(".zfile-video-dialog .el-dialog__title").innerHTML = name;
|
||||||
|
if (art) {
|
||||||
|
art.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
let videoType = 'mp4';
|
||||||
|
|
||||||
|
if (name.toLowerCase().endsWith('flv')) {
|
||||||
|
videoType = 'flv';
|
||||||
|
} else if (name.toLowerCase().endsWith('m3u8')) {
|
||||||
|
videoType = 'm3u8';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容 h5ai_dplayer https://github.com/Pearlulu/h5ai_dplayer
|
||||||
|
// 取 __{video_name}__/video.m3u8 完整路径
|
||||||
|
let originPathAndName = common.removeDuplicateSeparator(`${localStorage.getItem('filepath')}/${name}`);
|
||||||
|
let m3u8PathAndName = common.removeDuplicateSeparator(`${localStorage.getItem('filepath')}/__${name}__/video.m3u8`);
|
||||||
|
|
||||||
|
let m3u8FileItemParam = {
|
||||||
|
storageKey: 'minio',
|
||||||
|
path: m3u8PathAndName,
|
||||||
|
password: getPathPwd()
|
||||||
|
}
|
||||||
|
|
||||||
|
let h5aiDplayerMode = false;
|
||||||
|
|
||||||
|
// let fileItemResult = await loadFileItemReq(m3u8FileItemParam);
|
||||||
|
|
||||||
|
// if (fileItemResult?.data?.code === 0) {
|
||||||
|
// console.log('检测到当前为 h5ai_dplayer 兼容模式, 替换播放文件为: ' + m3u8PathAndName);
|
||||||
|
// videoType = 'm3u8';
|
||||||
|
// h5aiDplayerMode = true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
container: '.artplayer-app',
|
||||||
|
title: name,
|
||||||
|
url: currentVideo.value.url,
|
||||||
|
type: videoType,
|
||||||
|
setting: true,
|
||||||
|
playbackRate: true,
|
||||||
|
flip: true,
|
||||||
|
fullscreen: true,
|
||||||
|
fastForward: true,
|
||||||
|
autoOrientation: true,
|
||||||
|
aspectRatio: true,
|
||||||
|
fullscreenWeb: true,
|
||||||
|
theme: '#23ade5',
|
||||||
|
lock: true,
|
||||||
|
subtitleOffset: true,
|
||||||
|
miniProgressBar: true,
|
||||||
|
autoplay: autoPlayVideo.value,
|
||||||
|
whitelist: ['*'],
|
||||||
|
airplay: true,
|
||||||
|
moreVideoAttr: {
|
||||||
|
'x5-video-player-type': 'h5',
|
||||||
|
'x5-video-player-fullscreen': false,
|
||||||
|
'x5-video-orientation': 'portraint',
|
||||||
|
preload: "metadata",
|
||||||
|
crossOrigin: 'anonymous',
|
||||||
|
},
|
||||||
|
customType: {
|
||||||
|
flv: function(video, url) {
|
||||||
|
if (Mpegts.isSupported()) {
|
||||||
|
const flvPlayer = Mpegts.createPlayer({
|
||||||
|
type: 'flv',
|
||||||
|
url: url,
|
||||||
|
});
|
||||||
|
flvPlayer.attachMediaElement(video);
|
||||||
|
flvPlayer.load();
|
||||||
|
} else {
|
||||||
|
art.notice.show = '不支持播放格式:flv';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
m3u8: function(video, url) {
|
||||||
|
if (Hls.isSupported()) {
|
||||||
|
const hls = new Hls({
|
||||||
|
xhrSetup: (xhr, hlsUrl) => {
|
||||||
|
// 根据 URL 获取文件名
|
||||||
|
let fileName = hlsUrl.substring(hlsUrl.lastIndexOf('/') + 1);
|
||||||
|
// 如果不是当前的视频 URL, 那就是切片的 URL.
|
||||||
|
if (hlsUrl !== url) {
|
||||||
|
let realUrl = fileDataStore.getFileUrlByName(fileName);
|
||||||
|
if (realUrl) {
|
||||||
|
xhr.open('GET', realUrl, true);
|
||||||
|
} else {
|
||||||
|
xhr.open('GET', hlsUrl, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
hls.loadSource(url);
|
||||||
|
hls.attachMedia(video);
|
||||||
|
} else {
|
||||||
|
const canPlay = video.canPlayType('application/vnd.apple.mpegurl');
|
||||||
|
if (canPlay === 'probably' || canPlay == 'maybe') {
|
||||||
|
video.src = url;
|
||||||
|
} else {
|
||||||
|
art.notice.show = '不支持播放格式:m3u8';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
contextmenu: [
|
||||||
|
{
|
||||||
|
html: '下载',
|
||||||
|
click: function() {
|
||||||
|
window.open(url);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
settings: [
|
||||||
|
{
|
||||||
|
html: '自动播放',
|
||||||
|
tooltip: autoPlayVideo.value ? '开启' : '关闭',
|
||||||
|
icon: '<img width="22" heigth="22" src="data:image/svg+xml;base64,Cjxzdmcgdmlld0JveD0iMCAwIDgwIDgwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48ZGVmcz48cGF0aCBpZD0icGlkLTY0LXN2Z28tYSIgZD0iTTAgMGg4MHY4MEgweiI+PC9wYXRoPjxwYXRoIGQ9Ik01Mi41NDYgOC4wMTRhMy45OTggMy45OTggMCAwMTQuMjIyIDMuMDc3Yy4xMDQuNDQ2LjA5My44MDguMDM5IDEuMTM4YTIuNzQgMi43NCAwIDAxLS4zMTIuODgxYy0uMDczLjEzMi0uMTYuMjU0LS4yNDYuMzc2bC0uMjU3LjM2Ni0uNTIxLjczYy0uNy45NjktMS40MTUgMS45MjYtMi4xNTQgMi44NjZsLS4wMTUuMDJhMjQwLjk0NSAyNDAuOTQ1IDAgMDE1Ljk4Ni4zNDFsMS42NDMuMTIzLjgyMi4wNjYuNDEuMDM0LjIwNi4wMTguMTAzLjAwOC4xMTUuMDEyYzEuMjY2LjExNiAyLjUxNi40NSAzLjY3Ny45NzVhMTEuNjYzIDExLjY2MyAwIDAxMy4xNjYgMi4xMTRjLjkzMS44NyAxLjcxOSAxLjg5NSAyLjMyMSAzLjAyMmExMS41OTUgMTEuNTk1IDAgMDExLjIyNCAzLjYxM2MuMDMuMTU3LjA0Ni4zMTYuMDY4LjQ3NGwuMDE1LjExOS4wMTMuMTEyLjAyMi4yMDYuMDg1LjgyMi4xNTkgMS42NDZjLjEgMS4wOTguMTkgMi4xOTguMjcgMy4yOTguMzE1IDQuNC40NjMgOC44MjkuMzYgMTMuMjU1YTE2Ni40ODkgMTY2LjQ4OSAwIDAxLS44NDMgMTMuMjEzYy0uMDEyLjEyNy0uMDM0LjI5Ny0uMDUzLjQ1NGE3LjU4OSA3LjU4OSAwIDAxLS4wNzIuNDc1bC0uMDQuMjM3LS4wNS4yMzZhMTEuNzYyIDExLjc2MiAwIDAxLS43NCAyLjI4NyAxMS43NTUgMTEuNzU1IDAgMDEtNS4xMTggNS41NyAxMS43MDUgMTEuNzA1IDAgMDEtMy42MjMgMS4yNjNjLS4xNTguMDI0LS4zMTYuMDUyLS40NzUuMDcybC0uNDc3LjA1My0uODIxLjA3MS0xLjY0NC4xMzRjLTEuMDk2LjA4Ni0yLjE5Mi4xNi0zLjI4OC4yM2EyNjAuMDggMjYwLjA4IDAgMDEtNi41NzguMzI1Yy04Ljc3Mi4zMjQtMTcuNTQ2LjIyLTI2LjMxMy0uMzAyYTI0Mi40NTggMjQyLjQ1OCAwIDAxLTMuMjg3LS4yMmwtMS42NDMtLjEyOS0uODIyLS4wNjktLjQxLS4wMzUtLjIwNi0uMDE4Yy0uMDY4LS4wMDYtLjEzMy0uMDEtLjIxOC0uMDJhMTEuNTY2IDExLjU2NiAwIDAxLTMuNy0uOTkyIDExLjczMiAxMS43MzIgMCAwMS01LjQ5Ny01LjE3OCAxMS43MyAxMS43MyAwIDAxLTEuMjE1LTMuNjI3Yy0uMDI0LS4xNTgtLjA1MS0uMzE2LS4wNjctLjQ3NWwtLjAyNi0uMjM4LS4wMTMtLjExOS0uMDEtLjEwMy0uMDctLjgyMy0uMTMyLTEuNjQ4YTE5MC42MzcgMTkwLjYzNyAwIDAxLS4yMi0zLjI5OGMtLjI1Ni00LjM5OS0uMzU4LTguODE3LS4yNTgtMTMuMjMzLjA5OS00LjQxMi4zNzItOC44MTEuNzg4LTEzLjE5N2ExMS42NSAxMS42NSAwIDAxMy4wMzktNi44MzUgMTEuNTg1IDExLjU4NSAwIDAxNi41NzItMy41NjNjLjE1Ny0uMDIzLjMxMi0uMDUxLjQ3LS4wN2wuNDctLjA1LjgyLS4wNyAxLjY0My0uMTNhMjI4LjQ5MyAyMjguNDkzIDAgMDE2LjY0Ny0uNDA1bC0uMDQxLS4wNWE4OC4xNDUgODguMTQ1IDAgMDEtMi4xNTQtMi44NjdsLS41Mi0uNzMtLjI1OC0uMzY2Yy0uMDg2LS4xMjItLjE3My0uMjQ0LS4yNDYtLjM3NmEyLjc0IDIuNzQgMCAwMS0uMzEyLS44ODEgMi44MDggMi44MDggMCAwMS4wNC0xLjEzOCAzLjk5OCAzLjk5OCAwIDAxNC4yMi0zLjA3NyAyLjggMi44IDAgMDExLjA5My4zMTNjLjI5NC4xNTUuNTM4LjM0Ny43NDIuNTY4LjEwMi4xMS4xOS4yMy4yOC4zNWwuMjcuMzU5LjUzMi43MmE4OC4wNTkgODguMDU5IDAgMDEyLjA2IDIuOTM2IDczLjAzNiA3My4wMzYgMCAwMTEuOTI5IDMuMDNjLjE4Ny4zMTMuMzczLjYyOC41NTYuOTQ1IDIuNzI0LS4wNDcgNS40NDctLjA1NiA4LjE3LS4wMzguNzQ4LjAwNiAxLjQ5Ni4wMTUgMi4yNDQuMDI2LjE4LS4zMTMuMzY0LS42MjQuNTQ5LS45MzRhNzMuMjgxIDczLjI4MSAwIDAxMS45My0zLjAzIDg4LjczNyA4OC43MzcgMCAwMTIuMDU5LTIuOTM1bC41MzMtLjcyLjI2OC0uMzU5Yy4wOS0uMTIuMTc5LS4yNC4yODEtLjM1YTIuOCAyLjggMCAwMTEuODM0LS44ODF6TTMwLjEzIDM0LjYzMWE0IDQgMCAwMC0uNDE4IDEuNDIgOTEuMTU3IDkxLjE1NyAwIDAwLS40NDYgOS4xMjhjMCAyLjgyOC4xMjEgNS42NTYuMzY0IDguNDgzbC4xMSAxLjIxMmE0IDQgMCAwMDUuODU4IDMuMTQzYzIuODItMS40OTggNS41NS0zLjAzMyA4LjE5My00LjYwNmExNzcuNDEgMTc3LjQxIDAgMDA1Ljg5Ni0zLjY2NmwxLjQzNC0uOTQyYTQgNCAwIDAwLjA0Ny02LjYzMiAxMzcuNzAzIDEzNy43MDMgMCAwMC03LjM3Ny00LjcwOCAxNDYuODggMTQ2Ljg4IDAgMDAtNi44NzktMy44NDlsLTEuNC0uNzI1YTQgNCAwIDAwLTUuMzgyIDEuNzQyeiIgaWQ9InBpZC02NC1zdmdvLWQiPjwvcGF0aD48ZmlsdGVyIHg9Ii0xNS40JSIgeT0iLTE2LjMlIiB3aWR0aD0iMTMwLjklIiBoZWlnaHQ9IjEzMi41JSIgZmlsdGVyVW5pdHM9Im9iamVjdEJvdW5kaW5nQm94IiBpZD0icGlkLTY0LXN2Z28tYyI+PGZlT2Zmc2V0IGR5PSIyIiBpbj0iU291cmNlQWxwaGEiIHJlc3VsdD0ic2hhZG93T2Zmc2V0T3V0ZXIxIj48L2ZlT2Zmc2V0PjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjEiIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSI+PC9mZUdhdXNzaWFuQmx1cj48ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMyAwIiBpbj0ic2hhZG93Qmx1ck91dGVyMSIgcmVzdWx0PSJzaGFkb3dNYXRyaXhPdXRlcjEiPjwvZmVDb2xvck1hdHJpeD48ZmVPZmZzZXQgaW49IlNvdXJjZUFscGhhIiByZXN1bHQ9InNoYWRvd09mZnNldE91dGVyMiI+PC9mZU9mZnNldD48ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIzLjUiIGluPSJzaGFkb3dPZmZzZXRPdXRlcjIiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMiI+PC9mZUdhdXNzaWFuQmx1cj48ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMiAwIiBpbj0ic2hhZG93Qmx1ck91dGVyMiIgcmVzdWx0PSJzaGFkb3dNYXRyaXhPdXRlcjIiPjwvZmVDb2xvck1hdHJpeD48ZmVNZXJnZT48ZmVNZXJnZU5vZGUgaW49InNoYWRvd01hdHJpeE91dGVyMSI+PC9mZU1lcmdlTm9kZT48ZmVNZXJnZU5vZGUgaW49InNoYWRvd01hdHJpeE91dGVyMiI+PC9mZU1lcmdlTm9kZT48L2ZlTWVyZ2U+PC9maWx0ZXI+PC9kZWZzPjxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgb3BhY2l0eT0iLjgiPjxtYXNrIGlkPSJwaWQtNjQtc3Znby1iIiBmaWxsPSIjZmZmIj48dXNlIHhsaW5rOmhyZWY9IiNwaWQtNjQtc3Znby1hIj48L3VzZT48L21hc2s+PGcgbWFzaz0idXJsKCNwaWQtNjQtc3Znby1iKSI+PHVzZSBmaWxsPSIjMDAwIiBmaWx0ZXI9InVybCgjcGlkLTY0LXN2Z28tYykiIHhsaW5rOmhyZWY9IiNwaWQtNjQtc3Znby1kIj48L3VzZT48dXNlIGZpbGw9IiNGRkYiIHhsaW5rOmhyZWY9IiNwaWQtNjQtc3Znby1kIj48L3VzZT48L2c+PC9nPjwvc3ZnPg==">',
|
||||||
|
switch: autoPlayVideo.value,
|
||||||
|
onSwitch: function(item, $dom, event) {
|
||||||
|
const nextState = !item.switch;
|
||||||
|
|
||||||
|
autoPlayVideo.value = nextState;
|
||||||
|
art.autoplay = nextState;
|
||||||
|
// 改变提示文本
|
||||||
|
item.tooltip = nextState ? '开启' : '关闭';
|
||||||
|
|
||||||
|
// 改变按钮状态
|
||||||
|
return nextState;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
html: '自动播放下一个视频',
|
||||||
|
tooltip: autoPlayNextVideo.value ? '开启' : '关闭',
|
||||||
|
icon: '<img width="22" heigth="22" src="data:image/svg+xml;base64,Cjxzdmcgdmlld0JveD0iMCAwIDgwIDgwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48ZGVmcz48cGF0aCBpZD0icGlkLTY0LXN2Z28tYSIgZD0iTTAgMGg4MHY4MEgweiI+PC9wYXRoPjxwYXRoIGQ9Ik01Mi41NDYgOC4wMTRhMy45OTggMy45OTggMCAwMTQuMjIyIDMuMDc3Yy4xMDQuNDQ2LjA5My44MDguMDM5IDEuMTM4YTIuNzQgMi43NCAwIDAxLS4zMTIuODgxYy0uMDczLjEzMi0uMTYuMjU0LS4yNDYuMzc2bC0uMjU3LjM2Ni0uNTIxLjczYy0uNy45NjktMS40MTUgMS45MjYtMi4xNTQgMi44NjZsLS4wMTUuMDJhMjQwLjk0NSAyNDAuOTQ1IDAgMDE1Ljk4Ni4zNDFsMS42NDMuMTIzLjgyMi4wNjYuNDEuMDM0LjIwNi4wMTguMTAzLjAwOC4xMTUuMDEyYzEuMjY2LjExNiAyLjUxNi40NSAzLjY3Ny45NzVhMTEuNjYzIDExLjY2MyAwIDAxMy4xNjYgMi4xMTRjLjkzMS44NyAxLjcxOSAxLjg5NSAyLjMyMSAzLjAyMmExMS41OTUgMTEuNTk1IDAgMDExLjIyNCAzLjYxM2MuMDMuMTU3LjA0Ni4zMTYuMDY4LjQ3NGwuMDE1LjExOS4wMTMuMTEyLjAyMi4yMDYuMDg1LjgyMi4xNTkgMS42NDZjLjEgMS4wOTguMTkgMi4xOTguMjcgMy4yOTguMzE1IDQuNC40NjMgOC44MjkuMzYgMTMuMjU1YTE2Ni40ODkgMTY2LjQ4OSAwIDAxLS44NDMgMTMuMjEzYy0uMDEyLjEyNy0uMDM0LjI5Ny0uMDUzLjQ1NGE3LjU4OSA3LjU4OSAwIDAxLS4wNzIuNDc1bC0uMDQuMjM3LS4wNS4yMzZhMTEuNzYyIDExLjc2MiAwIDAxLS43NCAyLjI4NyAxMS43NTUgMTEuNzU1IDAgMDEtNS4xMTggNS41NyAxMS43MDUgMTEuNzA1IDAgMDEtMy42MjMgMS4yNjNjLS4xNTguMDI0LS4zMTYuMDUyLS40NzUuMDcybC0uNDc3LjA1My0uODIxLjA3MS0xLjY0NC4xMzRjLTEuMDk2LjA4Ni0yLjE5Mi4xNi0zLjI4OC4yM2EyNjAuMDggMjYwLjA4IDAgMDEtNi41NzguMzI1Yy04Ljc3Mi4zMjQtMTcuNTQ2LjIyLTI2LjMxMy0uMzAyYTI0Mi40NTggMjQyLjQ1OCAwIDAxLTMuMjg3LS4yMmwtMS42NDMtLjEyOS0uODIyLS4wNjktLjQxLS4wMzUtLjIwNi0uMDE4Yy0uMDY4LS4wMDYtLjEzMy0uMDEtLjIxOC0uMDJhMTEuNTY2IDExLjU2NiAwIDAxLTMuNy0uOTkyIDExLjczMiAxMS43MzIgMCAwMS01LjQ5Ny01LjE3OCAxMS43MyAxMS43MyAwIDAxLTEuMjE1LTMuNjI3Yy0uMDI0LS4xNTgtLjA1MS0uMzE2LS4wNjctLjQ3NWwtLjAyNi0uMjM4LS4wMTMtLjExOS0uMDEtLjEwMy0uMDctLjgyMy0uMTMyLTEuNjQ4YTE5MC42MzcgMTkwLjYzNyAwIDAxLS4yMi0zLjI5OGMtLjI1Ni00LjM5OS0uMzU4LTguODE3LS4yNTgtMTMuMjMzLjA5OS00LjQxMi4zNzItOC44MTEuNzg4LTEzLjE5N2ExMS42NSAxMS42NSAwIDAxMy4wMzktNi44MzUgMTEuNTg1IDExLjU4NSAwIDAxNi41NzItMy41NjNjLjE1Ny0uMDIzLjMxMi0uMDUxLjQ3LS4wN2wuNDctLjA1LjgyLS4wNyAxLjY0My0uMTNhMjI4LjQ5MyAyMjguNDkzIDAgMDE2LjY0Ny0uNDA1bC0uMDQxLS4wNWE4OC4xNDUgODguMTQ1IDAgMDEtMi4xNTQtMi44NjdsLS41Mi0uNzMtLjI1OC0uMzY2Yy0uMDg2LS4xMjItLjE3My0uMjQ0LS4yNDYtLjM3NmEyLjc0IDIuNzQgMCAwMS0uMzEyLS44ODEgMi44MDggMi44MDggMCAwMS4wNC0xLjEzOCAzLjk5OCAzLjk5OCAwIDAxNC4yMi0zLjA3NyAyLjggMi44IDAgMDExLjA5My4zMTNjLjI5NC4xNTUuNTM4LjM0Ny43NDIuNTY4LjEwMi4xMS4xOS4yMy4yOC4zNWwuMjcuMzU5LjUzMi43MmE4OC4wNTkgODguMDU5IDAgMDEyLjA2IDIuOTM2IDczLjAzNiA3My4wMzYgMCAwMTEuOTI5IDMuMDNjLjE4Ny4zMTMuMzczLjYyOC41NTYuOTQ1IDIuNzI0LS4wNDcgNS40NDctLjA1NiA4LjE3LS4wMzguNzQ4LjAwNiAxLjQ5Ni4wMTUgMi4yNDQuMDI2LjE4LS4zMTMuMzY0LS42MjQuNTQ5LS45MzRhNzMuMjgxIDczLjI4MSAwIDAxMS45My0zLjAzIDg4LjczNyA4OC43MzcgMCAwMTIuMDU5LTIuOTM1bC41MzMtLjcyLjI2OC0uMzU5Yy4wOS0uMTIuMTc5LS4yNC4yODEtLjM1YTIuOCAyLjggMCAwMTEuODM0LS44ODF6TTMwLjEzIDM0LjYzMWE0IDQgMCAwMC0uNDE4IDEuNDIgOTEuMTU3IDkxLjE1NyAwIDAwLS40NDYgOS4xMjhjMCAyLjgyOC4xMjEgNS42NTYuMzY0IDguNDgzbC4xMSAxLjIxMmE0IDQgMCAwMDUuODU4IDMuMTQzYzIuODItMS40OTggNS41NS0zLjAzMyA4LjE5My00LjYwNmExNzcuNDEgMTc3LjQxIDAgMDA1Ljg5Ni0zLjY2NmwxLjQzNC0uOTQyYTQgNCAwIDAwLjA0Ny02LjYzMiAxMzcuNzAzIDEzNy43MDMgMCAwMC03LjM3Ny00LjcwOCAxNDYuODggMTQ2Ljg4IDAgMDAtNi44NzktMy44NDlsLTEuNC0uNzI1YTQgNCAwIDAwLTUuMzgyIDEuNzQyeiIgaWQ9InBpZC02NC1zdmdvLWQiPjwvcGF0aD48ZmlsdGVyIHg9Ii0xNS40JSIgeT0iLTE2LjMlIiB3aWR0aD0iMTMwLjklIiBoZWlnaHQ9IjEzMi41JSIgZmlsdGVyVW5pdHM9Im9iamVjdEJvdW5kaW5nQm94IiBpZD0icGlkLTY0LXN2Z28tYyI+PGZlT2Zmc2V0IGR5PSIyIiBpbj0iU291cmNlQWxwaGEiIHJlc3VsdD0ic2hhZG93T2Zmc2V0T3V0ZXIxIj48L2ZlT2Zmc2V0PjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjEiIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSI+PC9mZUdhdXNzaWFuQmx1cj48ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMyAwIiBpbj0ic2hhZG93Qmx1ck91dGVyMSIgcmVzdWx0PSJzaGFkb3dNYXRyaXhPdXRlcjEiPjwvZmVDb2xvck1hdHJpeD48ZmVPZmZzZXQgaW49IlNvdXJjZUFscGhhIiByZXN1bHQ9InNoYWRvd09mZnNldE91dGVyMiI+PC9mZU9mZnNldD48ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIzLjUiIGluPSJzaGFkb3dPZmZzZXRPdXRlcjIiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMiI+PC9mZUdhdXNzaWFuQmx1cj48ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMiAwIiBpbj0ic2hhZG93Qmx1ck91dGVyMiIgcmVzdWx0PSJzaGFkb3dNYXRyaXhPdXRlcjIiPjwvZmVDb2xvck1hdHJpeD48ZmVNZXJnZT48ZmVNZXJnZU5vZGUgaW49InNoYWRvd01hdHJpeE91dGVyMSI+PC9mZU1lcmdlTm9kZT48ZmVNZXJnZU5vZGUgaW49InNoYWRvd01hdHJpeE91dGVyMiI+PC9mZU1lcmdlTm9kZT48L2ZlTWVyZ2U+PC9maWx0ZXI+PC9kZWZzPjxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgb3BhY2l0eT0iLjgiPjxtYXNrIGlkPSJwaWQtNjQtc3Znby1iIiBmaWxsPSIjZmZmIj48dXNlIHhsaW5rOmhyZWY9IiNwaWQtNjQtc3Znby1hIj48L3VzZT48L21hc2s+PGcgbWFzaz0idXJsKCNwaWQtNjQtc3Znby1iKSI+PHVzZSBmaWxsPSIjMDAwIiBmaWx0ZXI9InVybCgjcGlkLTY0LXN2Z28tYykiIHhsaW5rOmhyZWY9IiNwaWQtNjQtc3Znby1kIj48L3VzZT48dXNlIGZpbGw9IiNGRkYiIHhsaW5rOmhyZWY9IiNwaWQtNjQtc3Znby1kIj48L3VzZT48L2c+PC9nPjwvc3ZnPg==">',
|
||||||
|
switch: autoPlayNextVideo.value,
|
||||||
|
onSwitch: function(item, $dom, event) {
|
||||||
|
const nextState = !item.switch;
|
||||||
|
|
||||||
|
autoPlayNextVideo.value = nextState;
|
||||||
|
|
||||||
|
// 改变提示文本
|
||||||
|
item.tooltip = nextState ? '开启' : '关闭';
|
||||||
|
|
||||||
|
// 改变按钮状态
|
||||||
|
return nextState;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
controls: [
|
||||||
|
{
|
||||||
|
position: 'right',
|
||||||
|
html: hiddenTools.value ? hiddenIcon : notHiddenIcon,
|
||||||
|
click: function (_, event) {
|
||||||
|
hiddenTools.value = !hiddenTools.value;
|
||||||
|
event.target.parentNode.parentNode.innerHTML = (hiddenTools.value ? hiddenIcon : notHiddenIcon);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'video-list',
|
||||||
|
position: 'right',
|
||||||
|
html: videoListIcon,
|
||||||
|
selector: getVideoList(name),
|
||||||
|
onSelect: function(item, $dom) {
|
||||||
|
initArtPlayer(item.html, item.url);
|
||||||
|
return videoListIcon;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
let subtitles = getSubtitles(name);
|
||||||
|
if (subtitles.length > 0) {
|
||||||
|
// 如果字幕数量大于 1, 则支持切换字幕
|
||||||
|
if (subtitles.length > 1) {
|
||||||
|
options.controls.push({
|
||||||
|
name: 'video-subtitle',
|
||||||
|
position: 'right',
|
||||||
|
html: subtitleIcon,
|
||||||
|
selector: subtitles,
|
||||||
|
onSelect: (item) => {
|
||||||
|
if (item.html === '关闭字幕') {
|
||||||
|
art.subtitle.show = false;
|
||||||
|
} else {
|
||||||
|
art.subtitle.show = true;
|
||||||
|
art.subtitle.url = item.url;
|
||||||
|
}
|
||||||
|
return subtitleIcon;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
art = new Artplayer(options);
|
||||||
|
|
||||||
|
// art.on('error', (count) => {
|
||||||
|
// if (count === 5) {
|
||||||
|
// ElMessage.warning({
|
||||||
|
// title: '提示',
|
||||||
|
// message: '加载视频失败, 可能浏览器不支持此视频格式的解码,可尝试使用本地播放器打开',
|
||||||
|
// duration: 5000,
|
||||||
|
// showClose: true,
|
||||||
|
// grouping: true
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
art.on('destory', () => {
|
||||||
|
})
|
||||||
|
|
||||||
|
if (common.isMobile.value === false) {
|
||||||
|
art.on('ready', () => {
|
||||||
|
let prevAndNextVideo = getPrevAndNextVideo(art.option.title);
|
||||||
|
if (prevAndNextVideo.prev) {
|
||||||
|
art.controls.add({
|
||||||
|
name: 'prev-video',
|
||||||
|
position: 'left',
|
||||||
|
index: 5,
|
||||||
|
tooltip: '播放上一个',
|
||||||
|
html: prevVideoIcon,
|
||||||
|
click: () => {
|
||||||
|
playPrevVideo();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (prevAndNextVideo.next) {
|
||||||
|
art.controls.add({
|
||||||
|
name: 'next-video',
|
||||||
|
position: 'left',
|
||||||
|
index: 15,
|
||||||
|
tooltip: '播放下一个',
|
||||||
|
html: nextVideoIcon,
|
||||||
|
click: () => {
|
||||||
|
playNextVideo();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
art.on('video:ended', () => {
|
||||||
|
if (autoPlayNextVideo.value) {
|
||||||
|
playNextVideo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 默认第一个为字幕
|
||||||
|
if (subtitles.length > 0) {
|
||||||
|
art.subtitle.url = subtitles[0].url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const playNextVideo = () => {
|
||||||
|
const nextVideo = getPrevAndNextVideo(art.option.title).next;
|
||||||
|
if (nextVideo) {
|
||||||
|
initArtPlayer(nextVideo.name, nextVideo.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const playPrevVideo = () => {
|
||||||
|
const prevVideo = getPrevAndNextVideo(art.option.title).prev;
|
||||||
|
if (prevVideo) {
|
||||||
|
initArtPlayer(prevVideo.name, prevVideo.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
let videoObj = JSON.parse(localStorage.getItem('videorow'))
|
||||||
|
|
||||||
|
initArtPlayer(videoObj.fileName, videoObj.url);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.artplayer-app {
|
||||||
|
@apply h-[40vh] md:h-[60vh] lg:h-[70vh];
|
||||||
|
|
||||||
|
:deep(.art-controls svg) {
|
||||||
|
height: initial;
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.art-controls .art-control) {
|
||||||
|
min-height: 14px;
|
||||||
|
min-width: 14px;
|
||||||
|
.art-icon {
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.art-video-player) {
|
||||||
|
|
||||||
|
.art-control-fullscreenWeb {
|
||||||
|
@apply hidden md:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.art-fullscreen {
|
||||||
|
.art-control-fullscreenWeb {
|
||||||
|
@apply block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.zfile-video-switch-tools {
|
||||||
|
@apply px-5 py-2 bg-gray-50 flex justify-between;
|
||||||
|
}
|
||||||
|
.zfile-video-tools {
|
||||||
|
@apply bg-gray-50 p-3
|
||||||
|
grid grid-cols-3 gap-0
|
||||||
|
sm:space-x-10 sm:flex sm:justify-center sm:flex-wrap;
|
||||||
|
|
||||||
|
.zfile-video-tools-item {
|
||||||
|
@apply bg-white shadow hover:shadow-2xl m-2 px-2 py-1 rounded-md flex-shrink-0 cursor-pointer text-center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
@apply w-8 h-8 inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.zfile-video-tools-tips {
|
||||||
|
@apply bg-gray-50 text-center pb-2 text-gray-500 text-sm;
|
||||||
|
}
|
||||||
|
</style>
|
44
web/src/components/file/preview/VideoPlayerAsyncLoading.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div class="zfile-async-loading">
|
||||||
|
<el-skeleton class="w-full h-[40vh] sm:h-[80vh]" animated>
|
||||||
|
<template #template>
|
||||||
|
<div class="flex justify-center items-center h-full loading">
|
||||||
|
<svg-icon name="file-type-video" class="w-12 h-12"></svg-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-skeleton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
onMounted(() => {
|
||||||
|
document.querySelector(".el-dialog .el-dialog__title").innerHTML = '加载中...';
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.zfile-async-loading {
|
||||||
|
:deep(.el-skeleton__item) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
background: linear-gradient(
|
||||||
|
100deg,
|
||||||
|
rgba(255, 255, 255, 0) 40%,
|
||||||
|
rgba(255, 255, 255, .5) 50%,
|
||||||
|
rgba(255, 255, 255, 0) 60%
|
||||||
|
) #ededed;
|
||||||
|
background-size: 200% 100%;
|
||||||
|
background-position-x: 180%;
|
||||||
|
animation: 1s loading ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading {
|
||||||
|
to {
|
||||||
|
background-position-x: -20%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,101 +1,65 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="viewItemFile">
|
<div class="viewItemFile">
|
||||||
<!-- 预览文件 -->
|
<!-- 预览文件 -->
|
||||||
<el-dialog
|
<el-dialog width="61%" class="viewItemFileDialog" :title="title" v-model='dialogVisible' :before-close="handleClose"
|
||||||
width="70%"
|
:close-on-click-modal="false" :close-on-press-escape="false" draggable>
|
||||||
class="viewItemFileDialog"
|
<div class="image"
|
||||||
:title="title"
|
v-if="props.type == 'JPG' || props.type == 'jpg' || props.type == 'JPEG' || props.type == 'jpeg' || props.type == 'PNG' || props.type == 'png'">
|
||||||
v-model='dialogVisible'
|
|
||||||
:before-close="handleClose"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
:close-on-press-escape="false"
|
|
||||||
draggable
|
|
||||||
|
|
||||||
>
|
|
||||||
<button v-print="print" style="position: absolute;right: 50px;top: 22px;cursor: pointer;" v-if="onprint !='no'">
|
|
||||||
<img src="@/assets/tableicon/u884.png" title="打印" >
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="image" v-if="props.type == 'JPG' || props.type == 'jpg' || props.type == 'JPEG' || props.type == 'jpeg' || props.type == 'PNG' || props.type == 'png'" >
|
|
||||||
<div>
|
<div>
|
||||||
<img style="display: block; max-width: 100%; margin: 0 auto" :src="imgUrl" alt="" />
|
<img style="display: block; max-width: 100%; margin: 0 auto" :src="imgUrl" alt="" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- element-loading-background="rgba(122, 122, 122, 0.8)" -->
|
<div v-loading="loading" element-loading-text="正在加载报告..." element-loading-background="rgba(122, 122, 122, 0.8)"
|
||||||
<div id="printBoxs" v-loading="loading" element-loading-text="正在加载报告..."
|
class="docWrap" v-if="props.type == 'docx' || props.type == 'doc'">
|
||||||
class="docWrap" v-if="props.type == 'docx'||props.type == 'doc'">
|
|
||||||
<!-- 预览文件的地方(用于渲染) -->
|
<!-- 预览文件的地方(用于渲染) -->
|
||||||
<div id="printBox" ref="file"></div>
|
<div ref="file"></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="props.type == 'xlsx'" v-loading="loading" element-loading-text="正在加载报告..."
|
<div v-if="props.type == 'xlsx'" v-loading="loading" element-loading-text="正在加载报告..."
|
||||||
class="docWrap">
|
element-loading-background="rgba(122, 122, 122, 0.8)" class="docWrap">
|
||||||
<div class="tab">
|
<div class="tab">
|
||||||
<el-radio-group size="small" v-model="excel.sheetNameActive" @change="getSheetNameTable">
|
<el-radio-group size="small" v-model="excel.sheetNameActive" @change="getSheetNameTable">
|
||||||
<el-radio-button v-for="(item,index) in excel.sheetNames" :key="index" :label="item"></el-radio-button>
|
<el-radio-button v-for="(item, index) in excel.sheetNames" :key="index" :label="item"></el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div style="margin-top: 5px;border: 1px solid #a0a0a0;overflow:auto;">
|
||||||
style="margin-top: 5px;border: 1px solid #a0a0a0;overflow:auto;">
|
|
||||||
<div v-html="excel.SheetActiveTable" style="padding: 10px 15px"></div>
|
<div v-html="excel.SheetActiveTable" style="padding: 10px 15px"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="props.type == 'pdf'" v-loading="loading" element-loading-text="正在加载文件...">
|
<div v-if="props.type == 'pdf'">
|
||||||
<iframe
|
<iframe :src="pdfurl" type="application/x-google-chrome-pdf" width="100%" height="800px" />
|
||||||
:src="pdfurl"
|
|
||||||
type="application/x-google-chrome-pdf"
|
|
||||||
width="100%"
|
|
||||||
height="800px"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="props.type === 'bin'" v-loading="loading" class="hex-container">
|
||||||
|
<pre>{{ hexContent }}</pre>
|
||||||
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {ref, nextTick, onMounted, getCurrentInstance, reactive,toRefs} from 'vue'
|
import { ref, nextTick, onMounted, getCurrentInstance, reactive, toRefs } from 'vue'
|
||||||
import * as docx from 'docx-preview'
|
import * as docx from 'docx-preview'
|
||||||
|
|
||||||
import * as XLSX from "xlsx";
|
import * as XLSX from "xlsx";
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
showTime:{
|
showTime: {
|
||||||
type:Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
onprint:{
|
title: {
|
||||||
type:String,
|
type: String,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
title:{
|
|
||||||
type:String,
|
url: {
|
||||||
default: false
|
type: String,
|
||||||
|
default: ''
|
||||||
},
|
},
|
||||||
|
type: {
|
||||||
url:{
|
type: String,
|
||||||
type:String,
|
default: 'image'
|
||||||
default:''
|
|
||||||
},
|
|
||||||
type:{
|
|
||||||
type:String,
|
|
||||||
default:'image'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const print=ref({
|
|
||||||
id: 'printBox',//这里的id就是上面我们的打印区域id,实现指哪打哪
|
|
||||||
popTitle: '配置页眉标题', // 打印配置页上方的标题
|
|
||||||
extraHead: '', // 最上方的头部文字,附加在head标签上的额外标签,使用逗号分割
|
|
||||||
preview: false, // 是否启动预览模式,默认是false
|
|
||||||
previewTitle: '预览的标题', // 打印预览的标题
|
|
||||||
previewPrintBtnLabel: '预览结束,开始打印', // 打印预览的标题下方的按钮文本,点击可进入打印
|
|
||||||
zIndex: 20002, // 预览窗口的z-index,默认是20002,最好比默认值更高
|
|
||||||
previewBeforeOpenCallback() { console.log('正在加载预览窗口!'); }, // 预览窗口打开之前的callback
|
|
||||||
previewOpenCallback() { console.log('已经加载完预览窗口,预览打开了!') }, // 预览窗口打开时的callback
|
|
||||||
beforeOpenCallback() { console.log('开始打印之前!') }, // 开始打印之前的callback
|
|
||||||
openCallback() { console.log('执行打印了!') }, // 调用打印时的callback
|
|
||||||
closeCallback() { console.log('关闭了打印工具!') }, // 关闭打印的callback(无法区分确认or取消)
|
|
||||||
clickMounted() { console.log('点击v-print绑定的按钮了!') },
|
|
||||||
|
|
||||||
})
|
|
||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
const typeName = ref(props.type);
|
const typeName = ref(props.type);
|
||||||
const imgUrl = ref('');
|
const imgUrl = ref('');
|
||||||
@ -106,153 +70,162 @@ const dialogVisible = ref(props.showTime);
|
|||||||
const emits = defineEmits();
|
const emits = defineEmits();
|
||||||
const emptyTips = ref('暂无内容');
|
const emptyTips = ref('暂无内容');
|
||||||
const fullscreen = ref(false);
|
const fullscreen = ref(false);
|
||||||
|
const hexContent = ref('');
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
excel: {
|
excel: {
|
||||||
// 数据
|
// 数据
|
||||||
workbook: {},
|
workbook: {},
|
||||||
// 表名称集合
|
// 表名称集合
|
||||||
sheetNames: [],
|
sheetNames: [],
|
||||||
// 激活项
|
// 激活项
|
||||||
sheetNameActive: "",
|
sheetNameActive: "",
|
||||||
// 当前激活表格
|
// 当前激活表格
|
||||||
SheetActiveTable: ""
|
SheetActiveTable: ""
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const {excel} = toRefs(data);
|
const { excel } = toRefs(data);
|
||||||
|
|
||||||
onMounted(()=>{
|
onMounted(() => {
|
||||||
init(props.type);
|
init(props.type);
|
||||||
|
})
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// 前一个页面调用的init 我在前一个页面根据文件名字后缀已经判断是什么类型的文件了
|
// 前一个页面调用的init 我在前一个页面根据文件名字后缀已经判断是什么类型的文件了
|
||||||
function init(type) {
|
function init(type) {
|
||||||
typeName.value = type;
|
typeName.value = type;
|
||||||
if (type == "JPG" || type =='JPEG' || type == 'PNG' || type == 'jpg' || type == 'jpeg') {
|
if (type == "JPG" || type == 'JPEG' || type == 'PNG' || type == 'jpg' || type == 'jpeg') {
|
||||||
axios.request({
|
axios.request({
|
||||||
method: "GET", //这个不解释了吧
|
method: "GET", //这个不解释了吧
|
||||||
url: props.url, //路径
|
url: props.url, //路径
|
||||||
responseType: "blob", //告诉服务器想到的响应格式
|
responseType: "blob", //告诉服务器想到的响应格式
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/octet-stream",
|
Accept: "application/octet-stream",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res) {
|
if (res) {
|
||||||
let blob = new Blob([res.data], { type: "image/jpg" });
|
let blob = new Blob([res.data], { type: "image/jpg" });
|
||||||
const imageUrl = URL.createObjectURL(blob);
|
const imageUrl = URL.createObjectURL(blob);
|
||||||
imgUrl.value = imageUrl;
|
imgUrl.value = imageUrl;
|
||||||
srcList.value = [imageUrl],
|
srcList.value = [imageUrl],
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
} else {
|
} else {
|
||||||
// Message.error({ title: "失败", message: "接口请求失败" });
|
// Message.error({ title: "失败", message: "接口请求失败" });
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
// Message.error({ title: "失败", message: "接口请求失败" });
|
// Message.error({ title: "失败", message: "接口请求失败" });
|
||||||
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if (type == "pdf") {
|
loading.value = false;
|
||||||
loading.value = true;
|
});
|
||||||
axios.request({
|
|
||||||
method: "GET", //这个不解释了吧
|
|
||||||
url: props.url,//路径
|
|
||||||
responseType: "blob", //告诉服务器想到的响应格式
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/pdf;charset=UTF-8",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (res) {
|
|
||||||
let blob = new Blob([res.data], { type: "application/pdf" });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
loading.value = false;
|
|
||||||
pdfurl.value = url;
|
|
||||||
} else {
|
|
||||||
// Message.error({ title: "失败", message: "接口请求失败" });
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
// Message.error({ title: "失败", message: "接口请求失败" });
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if (type == "xlsx") {
|
} else if (type == "pdf") {
|
||||||
// //表格
|
axios.request({
|
||||||
loading.value = true;
|
method: "GET", //这个不解释了吧
|
||||||
axios.request({
|
url: props.url,//路径
|
||||||
method: "GET", //这个不解释了吧
|
responseType: "blob", //告诉服务器想到的响应格式
|
||||||
url: props.url, //路径
|
headers: {
|
||||||
responseType: "arraybuffer", //告诉服务器想到的响应格式
|
"Content-Type": "application/pdf;charset=UTF-8",
|
||||||
headers: {
|
},
|
||||||
"Content-Type":
|
})
|
||||||
"application/vnd.ms-excel;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
.then((res) => {
|
||||||
}
|
if (res) {
|
||||||
}).then((res) => {
|
let blob = new Blob([res.data], { type: "application/pdf" });
|
||||||
if (res) {
|
const url = URL.createObjectURL(blob);
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
const workbook = XLSX.read(new Uint8Array(res.data), {
|
pdfurl.value = url;
|
||||||
type: "array",
|
} else {
|
||||||
});
|
// Message.error({ title: "失败", message: "接口请求失败" });
|
||||||
const sheetNames = workbook.SheetNames // 工作表名称集合
|
loading.value = false;
|
||||||
excel.value.workbook = workbook
|
}
|
||||||
excel.value.sheetNames = sheetNames
|
})
|
||||||
excel.value.sheetNameActive = sheetNames[0]
|
.catch(function (error) {
|
||||||
getSheetNameTable(sheetNames[0])
|
// Message.error({ title: "失败", message: "接口请求失败" });
|
||||||
} else {
|
loading.value = false;
|
||||||
// Message.error({ title: "失败", message: "接口请求失败" });
|
});
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
// Message.error({ title: "失败", message: "接口请求失败" });
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
} else if (type == "docx"||type == "doc") {
|
|
||||||
loading.value = true;
|
|
||||||
axios.request({
|
|
||||||
method: "GET", //这个不解释了吧
|
|
||||||
url: props.url, //路径
|
|
||||||
responseType: "blob", //告诉服务器想到的响应格式
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
|
|
||||||
if (res) {
|
} else if (type == "xlsx") {
|
||||||
// let docx = require("docx-preview");
|
// //表格
|
||||||
docx.renderAsync(res.data, proxy.$refs.file);
|
loading.value = true;
|
||||||
loading.value = false;
|
axios.request({
|
||||||
setTimeout(()=>{
|
method: "GET", //这个不解释了吧
|
||||||
var ceshi = document.getElementById("printBoxs")
|
url: props.url, //路径
|
||||||
ceshi.getElementsByClassName("docx").id="printBox"
|
responseType: "arraybuffer", //告诉服务器想到的响应格式
|
||||||
|
headers: {
|
||||||
|
"Content-Type":
|
||||||
|
"application/vnd.ms-excel;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
}
|
||||||
|
}).then((res) => {
|
||||||
|
if (res) {
|
||||||
|
loading.value = false;
|
||||||
|
const workbook = XLSX.read(new Uint8Array(res.data), {
|
||||||
|
type: "array",
|
||||||
|
});
|
||||||
|
const sheetNames = workbook.SheetNames // 工作表名称集合
|
||||||
|
excel.value.workbook = workbook
|
||||||
|
excel.value.sheetNames = sheetNames
|
||||||
|
excel.value.sheetNameActive = sheetNames[0]
|
||||||
|
getSheetNameTable(sheetNames[0])
|
||||||
|
} else {
|
||||||
|
// Message.error({ title: "失败", message: "接口请求失败" });
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
// Message.error({ title: "失败", message: "接口请求失败" });
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
} else if (type == "docx" || type == "doc") {
|
||||||
|
loading.value = true;
|
||||||
|
axios.request({
|
||||||
|
method: "GET", //这个不解释了吧
|
||||||
|
url: props.url, //路径
|
||||||
|
responseType: "blob", //告诉服务器想到的响应格式
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
|
||||||
},200)
|
if (res) {
|
||||||
} else {
|
// let docx = require("docx-preview");
|
||||||
// Message.error({ title: "失败", message: "接口请求失败" });
|
docx.renderAsync(res.data, proxy.$refs.file);
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
} else {
|
||||||
})
|
// Message.error({ title: "失败", message: "接口请求失败" });
|
||||||
.catch(function (error) {
|
loading.value = false;
|
||||||
// Message.error({ title: "失败", message: "接口请求失败" });
|
}
|
||||||
loading.value = false;
|
})
|
||||||
});
|
.catch(function (error) {
|
||||||
|
// Message.error({ title: "失败", message: "接口请求失败" });
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
}else if (type === "bin") {
|
||||||
|
loading.value = true;
|
||||||
|
axios.request({
|
||||||
|
method: "GET",
|
||||||
|
url: props.url,
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.data) {
|
||||||
|
const buffer = new Uint8Array(res.data);
|
||||||
|
hexContent.value = arrayBufferToHex(buffer);
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
// 处理错误
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSheetNameTable(sheetName) {
|
function getSheetNameTable(sheetName) {
|
||||||
try {
|
try {
|
||||||
// 获取当前工作表的数据
|
// 获取当前工作表的数据
|
||||||
const worksheet = excel.value.workbook.Sheets[sheetName]
|
const worksheet = excel.value.workbook.Sheets[sheetName]
|
||||||
// 转换为数据 1.json数据有些问题,2.如果是html那么样式需修改
|
// 转换为数据 1.json数据有些问题,2.如果是html那么样式需修改
|
||||||
let htmlData = XLSX.utils.sheet_to_html(worksheet, {header: '', footer: ''})
|
let htmlData = XLSX.utils.sheet_to_html(worksheet, { header: '', footer: '' })
|
||||||
htmlData = htmlData === '' ? htmlData : htmlData.replace(/<table/, '<table class="default-table" border="1px solid #ccc" cellpadding="0" cellspacing="0"')
|
htmlData = htmlData === '' ? htmlData : htmlData.replace(/<table/, '<table class="default-table" border="1px solid #ccc" cellpadding="0" cellspacing="0"')
|
||||||
// 第一行进行改颜色
|
// 第一行进行改颜色
|
||||||
htmlData = htmlData === '' ? htmlData : htmlData.replace(/<tr/, '<tr style="background:#b4c9e8"')
|
htmlData = htmlData === '' ? htmlData : htmlData.replace(/<tr/, '<tr style="background:#b4c9e8"')
|
||||||
@ -262,46 +235,56 @@ function getSheetNameTable(sheetName) {
|
|||||||
excel.value.SheetActiveTable = '<h4 style="text-align: center">' + emptyTips.value + '</h4>'
|
excel.value.SheetActiveTable = '<h4 style="text-align: center">' + emptyTips.value + '</h4>'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 新增转换方法
|
||||||
|
function arrayBufferToHex(buffer) {
|
||||||
|
return Array.from(buffer)
|
||||||
|
.map(byte => byte.toString(16).padStart(2, '0'))
|
||||||
|
.join(' ')
|
||||||
|
.replace(/([\da-f]{2} ){16}/g, '$&\n'); // 每16字节换行
|
||||||
|
}
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
emits('update',false)
|
emits('update', false)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
.viewItemFile {
|
.viewItemFile {
|
||||||
.image {
|
.image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
div {
|
|
||||||
height: 600px;
|
div {
|
||||||
width: 600px;
|
height: 600px;
|
||||||
}
|
width: 600px;
|
||||||
}
|
}
|
||||||
.divContent {
|
}
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
.divContent {
|
||||||
justify-content: center;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
// :deep(.el-dialog) {
|
justify-content: center;
|
||||||
// margin: 0 !important;
|
}
|
||||||
// height: 100vh !important;
|
|
||||||
// .el-dialog__footer {
|
// :deep(.el-dialog) {
|
||||||
// margin-bottom: 30px;
|
// margin: 0 !important;
|
||||||
// padding: 0px;
|
// height: 100vh !important;
|
||||||
// }
|
// .el-dialog__footer {
|
||||||
// }
|
// margin-bottom: 30px;
|
||||||
:deep( .el-dialog__body ){
|
// padding: 0px;
|
||||||
height: 96%;
|
// }
|
||||||
width: 100%;
|
// }
|
||||||
padding: 0;
|
:deep(.el-dialog__body) {
|
||||||
overflow: auto;
|
height: 96%;
|
||||||
}
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewItemFile {
|
.viewItemFile {
|
||||||
.file-pre {
|
.file-pre {
|
||||||
height: calc(100vh - 40px);
|
height: calc(100vh - 40px);
|
||||||
@ -346,44 +329,48 @@ div {
|
|||||||
// height: calc(100vh - 40px);;
|
// height: calc(100vh - 40px);;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tabs--border-card > .el-tabs__content {
|
.el-tabs--border-card>.el-tabs__content {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: calc(100vh - 110px);
|
height: calc(100vh - 110px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep( table) {
|
|
||||||
width: 100% !important;
|
|
||||||
border-collapse: collapse !important;
|
|
||||||
border-spacing: 0 !important;
|
|
||||||
text-align: center !important;
|
|
||||||
border: 0px !important;
|
|
||||||
overflow-x: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep( table tr td) {
|
:deep(table) {
|
||||||
/* border: 1px solid gray !important; */
|
width: 100% !important;
|
||||||
border-right: 1px solid gray !important;
|
border-collapse: collapse !important;
|
||||||
border-bottom: 1px solid gray !important;
|
border-spacing: 0 !important;
|
||||||
width: 300px !important;
|
text-align: center !important;
|
||||||
height: 33px !important;
|
border: 0px !important;
|
||||||
}
|
overflow-x: auto !important;
|
||||||
/**整体样式 */
|
}
|
||||||
:deep(.excel-view-container) {
|
|
||||||
background-color: #ffffff;
|
:deep(table tr td) {
|
||||||
}
|
/* border: 1px solid gray !important; */
|
||||||
/**标题样式 */
|
border-right: 1px solid gray !important;
|
||||||
:deep( .class4Title) {
|
border-bottom: 1px solid gray !important;
|
||||||
font-size: 22px !important;
|
width: 300px !important;
|
||||||
font-weight: bold !important;
|
height: 33px !important;
|
||||||
padding: 10px !important;
|
}
|
||||||
}
|
|
||||||
/**表格表头样式 */
|
/**整体样式 */
|
||||||
:deep(.class4TableTh ){
|
:deep(.excel-view-container) {
|
||||||
/* font-size: 14px !important; */
|
background-color: #ffffff;
|
||||||
font-weight: bold !important;
|
}
|
||||||
padding: 2px !important;
|
|
||||||
background-color: #ccc !important;
|
/**标题样式 */
|
||||||
}
|
:deep(.class4Title) {
|
||||||
|
font-size: 22px !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
padding: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格表头样式 */
|
||||||
|
:deep(.class4TableTh) {
|
||||||
|
/* font-size: 14px !important; */
|
||||||
|
font-weight: bold !important;
|
||||||
|
padding: 2px !important;
|
||||||
|
background-color: #ccc !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html body {
|
html body {
|
||||||
@ -391,26 +378,28 @@ html body {
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.docWrap{
|
|
||||||
|
.docWrap {
|
||||||
height: calc(100vh - 150px);
|
height: calc(100vh - 150px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-loading-spinner .el-loading-text){
|
:deep(.el-loading-spinner .el-loading-text) {
|
||||||
// color: #079263;
|
color: #079263;
|
||||||
}
|
}
|
||||||
:deep(.docx-wrapper>section.docx){
|
.hex-container {
|
||||||
padding: 40px !important;
|
height: 600px;
|
||||||
padding-top: 10px !important;
|
overflow: auto;
|
||||||
padding-bottom: 10px !important;
|
padding: 10px;
|
||||||
min-height: 965px !important;
|
background: #f8f8f8;
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0) !important;
|
border: 1px solid #ddd;
|
||||||
}
|
pre {
|
||||||
:deep(.docx-wrapper>section.docx){
|
font-family: monospace;
|
||||||
margin-bottom: 0;
|
white-space: pre-wrap;
|
||||||
}
|
word-break: break-all;
|
||||||
:deep(.docx-wrapper){
|
margin: 0;
|
||||||
padding-top: 5px !important;
|
color: #333;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -5,16 +5,29 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, nextTick } from "vue";
|
import { ref, onMounted, nextTick, defineAsyncComponent } from "vue";
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { ElMessageBox, ElMessage } from "element-plus";
|
import { ElMessageBox, ElMessage } from "element-plus";
|
||||||
import Page from '@/components/Pagination/page.vue';
|
import Page from '@/components/Pagination/page.vue';
|
||||||
|
import AudioPlayer from '@/components/file/preview/AudioPlayer.vue';
|
||||||
|
import { batchDeleteReq } from "@/api/file-operator";
|
||||||
import { projectList, getNodesTree, addNodes, updateNodes, deleteNodesById, getFilesPage, addFiles, updateFiles, deleteFilesById, deleteFilesByIds } from "@/api/document";
|
import { projectList, getNodesTree, addNodes, updateNodes, deleteNodesById, getFilesPage, addFiles, updateFiles, deleteFilesById, deleteFilesByIds } from "@/api/document";
|
||||||
import ZUpload from '@/components/file/ZUpload.vue'
|
import ZUpload from '@/components/file/ZUpload.vue'
|
||||||
import useFileUpload from "@/components/file/file/useFileUpload";
|
import useFileUpload from "@/components/file/file/useFileUpload";
|
||||||
import useHeaderStorageList from "@/components/header/useHeaderStorageList";
|
import useHeaderStorageList from "@/components/header/useHeaderStorageList";
|
||||||
|
import useFileData from "@/components/file/file/useFileData";
|
||||||
|
|
||||||
|
// 文件预览相关
|
||||||
|
import Viewfile from '@/views/component/Viewfile.vue'
|
||||||
|
import useFilePreview from "@/components/file/file/useFilePreview";
|
||||||
|
import MarkdownViewerAsyncLoading from "@/components/file/preview/MarkdownViewerAsyncLoading.vue";
|
||||||
|
import VideoPlayerAsyncLoading from "@/components/file/preview/VideoPlayerAsyncLoading.vue";
|
||||||
|
import TextViewerAsyncLoading from "@/components/file/preview/TextViewerAsyncLoading.vue";
|
||||||
|
import MarkdownViewerDialogAsyncLoading from "@/components/file/preview/MarkdownViewerDialogAsyncLoading.vue";
|
||||||
|
const { dialogVideoVisible, dialogTextVisible, dialogPdfVisible, dialogOfficeVisible, dialog3dVisible } = useFilePreview();
|
||||||
const { openUploadDialog, openUploadFolderDialog, uploadProgressInfoStatistics } = useFileUpload();
|
const { openUploadDialog, openUploadFolderDialog, uploadProgressInfoStatistics } = useFileUpload();
|
||||||
const { currentStorageKey } = useHeaderStorageList();
|
const { currentStorageKey } = useHeaderStorageList();
|
||||||
|
const { openRow } = useFileData();
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getProject()
|
getProject()
|
||||||
});
|
});
|
||||||
@ -59,12 +72,18 @@ const treeForm = ref({
|
|||||||
nodeName: '',
|
nodeName: '',
|
||||||
})
|
})
|
||||||
//获取树数据
|
//获取树数据
|
||||||
|
const treeRef = ref();
|
||||||
function gettreedata() {
|
function gettreedata() {
|
||||||
treeloading.value = true
|
treeloading.value = true
|
||||||
treeForm.value.projectId = projectId.value
|
treeForm.value.projectId = projectId.value
|
||||||
getNodesTree(treeForm.value).then((res: any) => {
|
getNodesTree(treeForm.value).then((res: any) => {
|
||||||
treedata.value = res.data
|
treedata.value = res.data
|
||||||
treeloading.value = false
|
treeloading.value = false
|
||||||
|
pathid.value = treedata.value[0].id
|
||||||
|
nextTick(() => {
|
||||||
|
treeRef.value?.setCurrentKey(pathid.value);
|
||||||
|
});
|
||||||
|
getdata()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const pathid = ref()
|
const pathid = ref()
|
||||||
@ -253,6 +272,7 @@ const fileObj: any = ref({
|
|||||||
const upfile = ref(false)
|
const upfile = ref(false)
|
||||||
function openFile() {
|
function openFile() {
|
||||||
localStorage.setItem('filepath', findPathById(treedata.value, pathid.value));
|
localStorage.setItem('filepath', findPathById(treedata.value, pathid.value));
|
||||||
|
localStorage.setItem('storageKey', JSON.stringify(['minio']));
|
||||||
upfile.value = true
|
upfile.value = true
|
||||||
fileObj.value = {
|
fileObj.value = {
|
||||||
projectId: '', //所属项目ID
|
projectId: '', //所属项目ID
|
||||||
@ -271,6 +291,27 @@ function editfile(row: any) {
|
|||||||
}
|
}
|
||||||
function fileClose() {
|
function fileClose() {
|
||||||
upfile.value = false
|
upfile.value = false
|
||||||
|
let fileArr = JSON.parse(localStorage.getItem('fileArr'))
|
||||||
|
fileArr.forEach((item: any) => {
|
||||||
|
delfileOne(item)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
//删除文件
|
||||||
|
function delfileOne(row: any) {
|
||||||
|
const parmas = {
|
||||||
|
deleteItems: [
|
||||||
|
{
|
||||||
|
name: row.name,
|
||||||
|
password: "",
|
||||||
|
path: findPathById(treedata.value, pathid.value),
|
||||||
|
type: "FILE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
storageKey: "minio"
|
||||||
|
}
|
||||||
|
batchDeleteReq(JSON.stringify(parmas)).then((res) => {
|
||||||
|
})
|
||||||
}
|
}
|
||||||
//字节转mb
|
//字节转mb
|
||||||
function bytesToMB(bytes: any) {
|
function bytesToMB(bytes: any) {
|
||||||
@ -289,7 +330,14 @@ function submitfile() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let fileArr = JSON.parse(localStorage.getItem('fileArr'))
|
let fileArr: any
|
||||||
|
if (localStorage.getItem('fileArr')) {
|
||||||
|
fileArr = JSON.parse(localStorage.getItem('fileArr'))
|
||||||
|
} else {
|
||||||
|
ElMessage.warning('请先选择文件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let fileName = []
|
let fileName = []
|
||||||
let fileSize = []
|
let fileSize = []
|
||||||
if (fileArr.length > 0) {
|
if (fileArr.length > 0) {
|
||||||
@ -308,6 +356,7 @@ function submitfile() {
|
|||||||
ElMessage.success('增加成功')
|
ElMessage.success('增加成功')
|
||||||
getdata()
|
getdata()
|
||||||
upfile.value = false
|
upfile.value = false
|
||||||
|
localStorage.setItem('fileArr', '');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -404,27 +453,27 @@ function xiafilemony() {
|
|||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
tableIdarr.value.forEach((item: any) => {
|
tableIdarr.value.forEach((item: any) => {
|
||||||
if(item.url){
|
if (item.url) {
|
||||||
downloadFileUseIframeMode(item.url);
|
downloadFileUseIframeMode(item.url);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 使用 iframe 模式下载文件
|
* 使用 iframe 模式下载文件
|
||||||
*
|
*
|
||||||
* @param url 下载文件 url
|
* @param url 下载文件 url
|
||||||
*/
|
*/
|
||||||
const downloadFileUseIframeMode = (url:any) => {
|
const downloadFileUseIframeMode = (url: any) => {
|
||||||
const iframe:any = document.createElement("iframe");
|
const iframe: any = document.createElement("iframe");
|
||||||
iframe.style.display = "none"; // 防止影响页面
|
iframe.style.display = "none"; // 防止影响页面
|
||||||
iframe.style.height = 0; // 防止影响页面
|
iframe.style.height = 0; // 防止影响页面
|
||||||
iframe.src = url;
|
iframe.src = url;
|
||||||
document.body.appendChild(iframe);
|
document.body.appendChild(iframe);
|
||||||
setTimeout(()=>{
|
setTimeout(() => {
|
||||||
iframe.remove();
|
iframe.remove();
|
||||||
}, 5 * 60 * 1000);
|
}, 5 * 60 * 1000);
|
||||||
}
|
}
|
||||||
function findPathById(array: any, targetId: any) {
|
function findPathById(array: any, targetId: any) {
|
||||||
// 辅助函数,用于递归查找路径
|
// 辅助函数,用于递归查找路径
|
||||||
function recursiveSearch(items: any, target: any, path: any) {
|
function recursiveSearch(items: any, target: any, path: any) {
|
||||||
@ -451,7 +500,111 @@ function findPathById(array: any, targetId: any) {
|
|||||||
let patharr = recursiveSearch(array, targetId, [])
|
let patharr = recursiveSearch(array, targetId, [])
|
||||||
return '/' + patharr; // 注意这里我们传入了一个空数组作为初始路径
|
return '/' + patharr; // 注意这里我们传入了一个空数组作为初始路径
|
||||||
}
|
}
|
||||||
|
// markdown viewer 组件懒加载, 节约首屏打开时间
|
||||||
|
const VMdPreview = defineAsyncComponent({
|
||||||
|
loader: () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
; (async function () {
|
||||||
|
try {
|
||||||
|
const res = await import('@kangc/v-md-editor/lib/preview')
|
||||||
|
import('@kangc/v-md-editor/lib/style/preview.css');
|
||||||
|
import('@kangc/v-md-editor/lib/theme/style/github.css');
|
||||||
|
const hljs = await import('highlight.js');
|
||||||
|
const githubTheme = await import('@kangc/v-md-editor/lib/theme/github.js');
|
||||||
|
res.use(githubTheme, {
|
||||||
|
Hljs: hljs,
|
||||||
|
});
|
||||||
|
resolve(res)
|
||||||
|
} catch (error) {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadingComponent: MarkdownViewerAsyncLoading
|
||||||
|
})
|
||||||
|
// 文件预览相关, 视频、音频、文本、图片
|
||||||
|
const VideoPlayer = defineAsyncComponent({
|
||||||
|
loader: () => import("@/components/file/preview/VideoPlayer.vue"),
|
||||||
|
loadingComponent: VideoPlayerAsyncLoading
|
||||||
|
})
|
||||||
|
const TextViewer = defineAsyncComponent({
|
||||||
|
loader: () => import("@/components/file/preview/TextViewer.vue"),
|
||||||
|
loadingComponent: TextViewerAsyncLoading
|
||||||
|
})
|
||||||
|
const MarkdownViewer = defineAsyncComponent({
|
||||||
|
loader: () => import("@/components/file/preview/MarkdownViewer.vue"),
|
||||||
|
loadingComponent: MarkdownViewerDialogAsyncLoading
|
||||||
|
})
|
||||||
|
const FileGallery = defineAsyncComponent(() => import("@/components/file/preview/FileGallery.vue"))
|
||||||
|
//单击预览
|
||||||
|
const filePreview: any = ref({})
|
||||||
|
//预览pdf
|
||||||
|
const ViewfileUrl = ref("")
|
||||||
|
const title1 = ref('')
|
||||||
|
const isViewfile = ref(false)
|
||||||
|
const fileType = ref('')
|
||||||
|
function openPreview(row: any) {
|
||||||
|
|
||||||
|
if (getFileExtension(row.fileName) == 'pdf' || getFileExtension(row.fileName) == 'xlsx' || getFileExtension(row.fileName) == 'docx' || getFileExtension(row.fileName) == 'doc' || getFileExtension(row.fileName) == 'bin') {
|
||||||
|
title1.value = row.fileName
|
||||||
|
ViewfileUrl.value = row.url
|
||||||
|
isViewfile.value = true
|
||||||
|
fileType.value = getFileExtension(row.fileName)
|
||||||
|
} else {
|
||||||
|
row.fileType = getFileType(row.fileName)
|
||||||
|
filePreview.value = row
|
||||||
|
localStorage.setItem('videorow', JSON.stringify(row));
|
||||||
|
openRow(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
function getFileExtension(filename: any) {
|
||||||
|
// 获取点号在文件名中的位置(从后往前查找)
|
||||||
|
const lastIndex = filename.lastIndexOf('.');
|
||||||
|
|
||||||
|
// 如果点号存在且不是文件名的第一个字符,则返回后缀名
|
||||||
|
if (lastIndex !== -1 && lastIndex !== 0) {
|
||||||
|
return filename.substring(lastIndex + 1);
|
||||||
|
} else {
|
||||||
|
// 如果没有找到点号,则返回空字符串或根据需要返回其他值
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function CloseView() {
|
||||||
|
isViewfile.value = false
|
||||||
|
}
|
||||||
|
// 文件分类
|
||||||
|
const fileTypeMap = {
|
||||||
|
image: ['gif', 'jpg', 'jpeg', 'png', 'bmp', 'webp', 'ico'],
|
||||||
|
video: ['mp4', 'webm', 'm3u8', 'rmvb', 'avi', 'swf', '3gp', 'mkv', 'flv'],
|
||||||
|
audio: ['mp3', 'wav', 'wma', 'ogg', 'aac', 'flac', 'm4a'],
|
||||||
|
text: ['scss', 'sass', 'kt', 'gitignore', 'bat', 'properties', 'yml', 'css', 'js', 'md', 'xml', 'txt', 'py', 'go', 'html', 'less', 'php', 'rb', 'rust', 'script', 'java', 'sh', 'sql'],
|
||||||
|
executable: ['exe', 'dll', 'com', 'vbs'],
|
||||||
|
archive: ['7z', 'zip', 'rar', 'tar', 'gz'],
|
||||||
|
pdf: ['pdf'],
|
||||||
|
office: ['doc', 'docx', 'csv', 'xls', 'xlsx', "ppt", 'pptx'],
|
||||||
|
three3d: ['dae', 'fbx', 'gltf', 'glb', 'obj', 'ply', 'stl'],
|
||||||
|
document: ['txt', 'pages', 'epub', 'numbers', 'keynote']
|
||||||
|
};
|
||||||
|
function getFileType(name: any) {
|
||||||
|
let fileType: any;
|
||||||
|
for (let key in fileTypeMap) {
|
||||||
|
let suffix = getFileSuffix(name);
|
||||||
|
if (fileTypeMap[key].indexOf(suffix) !== -1) {
|
||||||
|
fileType = key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileType;
|
||||||
|
}
|
||||||
|
function getFileSuffix(name: any) {
|
||||||
|
let lastIndex = name.lastIndexOf('.');
|
||||||
|
if (lastIndex === -1) {
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
return name.substring(lastIndex + 1).toLowerCase();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -518,7 +671,12 @@ function findPathById(array: any, targetId: any) {
|
|||||||
style="width: 100%; height: calc(100vh - 275px);margin-bottom: 20px;" border>
|
style="width: 100%; height: calc(100vh - 275px);margin-bottom: 20px;" border>
|
||||||
<el-table-column type="selection" width="40" />
|
<el-table-column type="selection" width="40" />
|
||||||
<!-- <el-table-column type="index" label="序号" width="70" align="center"></el-table-column> -->
|
<!-- <el-table-column type="index" label="序号" width="70" align="center"></el-table-column> -->
|
||||||
<el-table-column prop="fileName" label="文件名称"></el-table-column>
|
<el-table-column prop="fileName" label="文件名称">
|
||||||
|
<template #default="scope">
|
||||||
|
<div>{{ scope.row.fileName }}<span class="tableHover"
|
||||||
|
@click="openPreview(scope.row)">(点击预览)</span></div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="keywords" label="关键字"></el-table-column>
|
<el-table-column prop="keywords" label="关键字"></el-table-column>
|
||||||
<el-table-column prop="description" label="文件描述"></el-table-column>
|
<el-table-column prop="description" label="文件描述"></el-table-column>
|
||||||
<el-table-column prop="uploader" width="80" label="上传人"></el-table-column>
|
<el-table-column prop="uploader" width="80" label="上传人"></el-table-column>
|
||||||
@ -583,6 +741,40 @@ function findPathById(array: any, targetId: any) {
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
<!-- 组件预览 -->
|
||||||
|
<!-- 视频播放器 -->
|
||||||
|
<el-dialog draggable class="zfile-video-dialog" :destroy-on-close="true" v-model="dialogVideoVisible">
|
||||||
|
<video-player v-if="dialogVideoVisible" ref="videoPlayer" />
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 文本编辑器 -->
|
||||||
|
<el-dialog draggable class="zfile-text-dialog zfile-dialog-mini-close" :destroy-on-close="true"
|
||||||
|
:title="filePreview.fileName" v-model="dialogTextVisible">
|
||||||
|
<TextViewer :file-name="filePreview.fileName" :file-url="filePreview.url"
|
||||||
|
v-if="dialogTextVisible && filePreview.fileName.indexOf('.md') === -1" />
|
||||||
|
<MarkdownViewer :file-name="filePreview.fileName" :file-url="filePreview.url"
|
||||||
|
v-if="dialogTextVisible && filePreview.fileName.indexOf('.md') !== -1" />
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- pdf 在线预览 -->
|
||||||
|
<el-dialog draggable class="zfile-pdf-dialog" :title="filePreview.fileName" v-model="dialogPdfVisible">
|
||||||
|
<PdfViewer :file-name="filePreview.fileName" :file-url="filePreview.url" v-if="dialogPdfVisible" />
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- office 在线预览 -->
|
||||||
|
<el-dialog draggable class="zfile-office-dialog zfile-dialog-mini-close zfile-dialog-hidden-title"
|
||||||
|
:title="filePreview.fileName" v-model="dialogOfficeVisible">
|
||||||
|
<OfficeViewer :file-name="filePreview.fileName" :file-url="filePreview.url" v-if="dialogOfficeVisible" />
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 3d 在线预览 -->
|
||||||
|
<el-dialog draggable class="zfile-3d-dialog" :title="filePreview.fileName" v-model="dialog3dVisible">
|
||||||
|
<Three3dPreview :file-name="filePreview.fileName" :file-url="filePreview.url" v-if="dialog3dVisible" />
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 音频播放器 -->
|
||||||
|
<AudioPlayer></AudioPlayer>
|
||||||
|
<!-- <audio-player></audio-player> -->
|
||||||
|
<Viewfile v-if="isViewfile" :showTime="true" :title="title1" :url="ViewfileUrl" :type="fileType"
|
||||||
|
@update="CloseView" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -683,4 +875,12 @@ function findPathById(array: any, targetId: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.tableHover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableHover:hover {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|