文件预览和实验数据管理页面

This commit is contained in:
wangxk 2025-02-21 10:02:05 +08:00
parent 6a2d0e08b0
commit 090724799e
37 changed files with 3458 additions and 317 deletions

View File

@ -13,6 +13,7 @@
"@element-plus/icons-vue": "^2.3.1",
"@headlessui/vue": "^1.7.12",
"@heroicons/vue": "^2.0.17",
"@kangc/v-md-editor": "^2.3.15",
"@soerenmartius/vue3-clipboard": "^0.1.2",
"@tinymce/tinymce-vue": "^5.1.1",
"@types/js-cookie": "^3.0.2",
@ -20,6 +21,8 @@
"@vueuse/integrations": "^9.13.0",
"@wangeditor/editor": "^5.0.0",
"@wangeditor/editor-for-vue": "^5.1.10",
"aplayer": "^1.10.1",
"artplayer": "^4.6.1",
"axios": "^1.2.0",
"beautify-qrcode": "^1.0.3",
"better-scroll": "^2.4.2",
@ -27,13 +30,19 @@
"docx-preview": "^0.3.0",
"echarts": "^5.2.2",
"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",
"html2pdf.js": "^0.10.1",
"js-base64": "^3.7.5",
"js-cookie": "^3.0.1",
"jsencrypt": "^3.3.2",
"jspdf": "^2.5.1",
"marked": "^4.0.17",
"minimatch": "^5.1.0",
"monaco-editor": "^0.36.1",
"mpegts.js": "^1.7.2",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.0",
@ -45,8 +54,10 @@
"v-contextmenu": "^3.0.0",
"v3-img-preview-enhance": "^1.1.18",
"vue": "^3.2.40",
"vue-3d-loader": "^2.0.8",
"vue-clipboard3": "^2.0.0",
"vue-i18n": "^9.1.9",
"vue-pdf-embed": "^1.1.4",
"vue-router": "^4.1.6",
"vue3-print-nb": "^0.1.4",
"vuedraggable": "^4.1.0",

19
web/src/api/common.js Normal file
View 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
})
}

View 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,
});
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

View File

@ -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" /> -->
</div>
<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">
<!-- <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" /> -->
@ -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" /> -->
</div>
<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">
<!-- <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" /> -->
@ -104,6 +104,7 @@
import common from "@/components/file/common";
import { ref, onMounted,defineEmits } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { batchDeleteReq } from "@/api/file-operator";
let router = useRouter();
let route = useRoute();
@ -135,6 +136,32 @@ const dropBoxRef = ref();
onMounted(() => {
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>

View File

@ -169,7 +169,7 @@ export default function useFileData() {
// 点击文件时,判断是文件夹则进入文件夹,是文件则进行预览
const openRow = (row) => {
if (!row.name) {
if (!row.fileName) {
return;
}
fileDataStore.updateCurrentClickRow(row);
@ -179,8 +179,6 @@ export default function useFileData() {
// 获取文件类型
let fileType = row.fileType;
switch (fileType) {
case 'video': openVideo(); break;
case 'image': openImage(row); break;
@ -192,7 +190,7 @@ export default function useFileData() {
default: batchDownloadFile(row);
}
clearSelection();
// clearSelection();
} else {
if (row.type === 'ROOT') {
routerRef.value.push(row.path);

View File

@ -4,7 +4,7 @@ import {
renameFileReq,
renameFolderReq,
} from "@/api/file-operator";
import { ElMessageBox, ElMessage } from "element-plus";
import useFileDataStore from "@/components/file/stores/file-data";
let fileDataStore = useFileDataStore();

View File

@ -25,25 +25,26 @@ export default function useFilePreview() {
dialogVideoVisible.value = true;
}
const openAudio = () => {
fileDataStore.updateAudioList(fileDataStore.filterFileByType('audio'));
const openAudio = (row) => {
fileDataStore.updateAudioList([row]);
}
const openImage = (row) => {
// 过滤当前页面中所有图片,并记录当前打开的文件的索引位置
let images = [];
let currIndex = 0;
let imagePreviewMode = globalConfigStore.zfileConfig.imagePreview.mode;
if (imagePreviewMode === 'only') {
// let imagePreviewMode = globalConfigStore.zfileConfig.imagePreview.mode;
images.push(row.url);
} else {
fileDataStore.filterFileByType('image').forEach((image, index) => {
if (row.name === image.name) {
currIndex = index;
}
images.push(image.url);
})
}
// if (imagePreviewMode === 'only') {
// } else {
// fileDataStore.filterFileByType('image').forEach((image, index) => {
// if (row.name === image.name) {
// currIndex = index;
// }
// images.push(image.url);
// })
// }
v3ImgPreviewFn({
images: images,

View File

@ -1,4 +1,5 @@
import {ElLoading} from "element-plus";
import { ElMessageBox, ElMessage } from "element-plus";
import { ref,reactive,computed,nextTick,watch} from "vue";
import {uploadFileReq} from "@/api/file-operator";
import axios from "axios";
@ -354,10 +355,14 @@ export default function useFileUpload() {
* @param 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++;
uploadBasePath = uploadBasePath || currentPath.value;
@ -385,7 +390,7 @@ export default function useFileUpload() {
})
}
let param = {
storageKey: 'minio',
storageKey: item,
path: localStorage.getItem('filepath'),
name: file.name,
size: file.size
@ -438,13 +443,19 @@ export default function useFileUpload() {
let proxyUploadType = common.storageType.proxyType;
let s3UploadType = common.storageType.s3Type;
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);
} else if (s3UploadType.includes( param.storageKey)) {
} else if (s3UploadType.includes( storagea)) {
s3FileUpload(file, res.data, fileIndex);
} else if (onedriveUploadType.includes( param.storageKey)) {
} else if (onedriveUploadType.includes( storagea)) {
onedriveUpload(file, res.data, fileIndex);
} else if ( param.storageKey === 'upyun') {
} else if ( storagea === 'upyun') {
upyunFileUpload(file, res.data, fileIndex);
}
}).catch((err) => {

File diff suppressed because one or more lines are too long

View 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>

View 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>

View 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>

View File

@ -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>

View File

@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@ -1,101 +1,65 @@
<template>
<div class="viewItemFile">
<!-- 预览文件 -->
<el-dialog
width="70%"
class="viewItemFileDialog"
:title="title"
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'" >
<el-dialog width="61%" class="viewItemFileDialog" :title="title" v-model='dialogVisible' :before-close="handleClose"
:close-on-click-modal="false" :close-on-press-escape="false" draggable>
<div class="image"
v-if="props.type == 'JPG' || props.type == 'jpg' || props.type == 'JPEG' || props.type == 'jpeg' || props.type == 'PNG' || props.type == 'png'">
<div>
<img style="display: block; max-width: 100%; margin: 0 auto" :src="imgUrl" alt="" />
</div>
</div>
<!-- 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'">
<div v-loading="loading" element-loading-text="正在加载报告..." element-loading-background="rgba(122, 122, 122, 0.8)"
class="docWrap" v-if="props.type == 'docx' || props.type == 'doc'">
<!-- 预览文件的地方用于渲染 -->
<div id="printBox" ref="file"></div>
<div ref="file"></div>
</div>
<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">
<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>
</div>
<div
style="margin-top: 5px;border: 1px solid #a0a0a0;overflow:auto;">
<div style="margin-top: 5px;border: 1px solid #a0a0a0;overflow:auto;">
<div v-html="excel.SheetActiveTable" style="padding: 10px 15px"></div>
</div>
</div>
<div v-if="props.type == 'pdf'" v-loading="loading" element-loading-text="正在加载文件...">
<iframe
:src="pdfurl"
type="application/x-google-chrome-pdf"
width="100%"
height="800px"
/>
<div v-if="props.type == 'pdf'">
<iframe :src="pdfurl" type="application/x-google-chrome-pdf" width="100%" height="800px" />
</div>
<div v-if="props.type === 'bin'" v-loading="loading" class="hex-container">
<pre>{{ hexContent }}</pre>
</div>
</el-dialog>
</div>
</template>
<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 XLSX from "xlsx";
import axios from 'axios';
const props = defineProps({
showTime:{
type:Boolean,
showTime: {
type: Boolean,
default: false
},
onprint:{
type:String,
default: false
},
title:{
type:String,
title: {
type: String,
default: false
},
url:{
type:String,
default:''
url: {
type: String,
default: ''
},
type:{
type:String,
default:'image'
type: {
type: String,
default: 'image'
}
});
const print=ref({
id: 'printBox',//idid
popTitle: '配置页眉标题', //
extraHead: '', // head使
preview: false, // false
previewTitle: '预览的标题', //
previewPrintBtnLabel: '预览结束,开始打印', //
zIndex: 20002, // z-index20002
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 typeName = ref(props.type);
const imgUrl = ref('');
@ -106,6 +70,7 @@ const dialogVisible = ref(props.showTime);
const emits = defineEmits();
const emptyTips = ref('暂无内容');
const fullscreen = ref(false);
const hexContent = ref('');
const data = reactive({
excel: {
//
@ -120,18 +85,16 @@ const data = reactive({
})
const {excel} = toRefs(data);
const { excel } = toRefs(data);
onMounted(()=>{
onMounted(() => {
init(props.type);
})
})
// init
function init(type) {
function init(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({
method: "GET", //
url: props.url, //
@ -159,7 +122,6 @@ const data = reactive({
});
} else if (type == "pdf") {
loading.value = true;
axios.request({
method: "GET", //
url: props.url,//
@ -215,7 +177,7 @@ const data = reactive({
// Message.error({ title: "", message: "" });
loading.value = false;
});
} else if (type == "docx"||type == "doc") {
} else if (type == "docx" || type == "doc") {
loading.value = true;
axios.request({
method: "GET", //
@ -228,11 +190,6 @@ const data = reactive({
// let docx = require("docx-preview");
docx.renderAsync(res.data, proxy.$refs.file);
loading.value = false;
setTimeout(()=>{
var ceshi = document.getElementById("printBoxs")
ceshi.getElementsByClassName("docx").id="printBox"
},200)
} else {
// Message.error({ title: "", message: "" });
loading.value = false;
@ -242,17 +199,33 @@ const data = reactive({
// 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;
}
}
function getSheetNameTable(sheetName) {
try {
//
const worksheet = excel.value.workbook.Sheets[sheetName]
// 1.json2.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(/<tr/, '<tr style="background:#b4c9e8"')
@ -262,46 +235,56 @@ function getSheetNameTable(sheetName) {
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() {
emits('update',false)
emits('update', false)
}
</script>
<style lang="scss" scoped>
.viewItemFile {
.image {
.image {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
div {
div {
height: 600px;
width: 600px;
}
}
.divContent {
}
}
.divContent {
display: flex;
align-items: center;
justify-content: center;
}
// :deep(.el-dialog) {
// margin: 0 !important;
// height: 100vh !important;
// .el-dialog__footer {
// margin-bottom: 30px;
// padding: 0px;
// }
// }
:deep( .el-dialog__body ){
}
// :deep(.el-dialog) {
// margin: 0 !important;
// height: 100vh !important;
// .el-dialog__footer {
// margin-bottom: 30px;
// padding: 0px;
// }
// }
:deep(.el-dialog__body) {
height: 96%;
width: 100%;
padding: 0;
overflow: auto;
}
}
}
.viewItemFile {
.file-pre {
height: calc(100vh - 40px);
@ -346,44 +329,48 @@ div {
// height: calc(100vh - 40px);;
}
.el-tabs--border-card > .el-tabs__content {
.el-tabs--border-card>.el-tabs__content {
overflow: auto;
height: calc(100vh - 110px);
}
}
:deep( table) {
: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 tr td) {
/* border: 1px solid gray !important; */
border-right: 1px solid gray !important;
border-bottom: 1px solid gray !important;
width: 300px !important;
height: 33px !important;
}
/**整体样式 */
:deep(.excel-view-container) {
}
/**整体样式 */
:deep(.excel-view-container) {
background-color: #ffffff;
}
/**标题样式 */
:deep( .class4Title) {
}
/**标题样式 */
:deep(.class4Title) {
font-size: 22px !important;
font-weight: bold !important;
padding: 10px !important;
}
/**表格表头样式 */
:deep(.class4TableTh ){
}
/**表格表头样式 */
:deep(.class4TableTh) {
/* font-size: 14px !important; */
font-weight: bold !important;
padding: 2px !important;
background-color: #ccc !important;
}
}
}
html body {
@ -391,26 +378,28 @@ html body {
height: 100vh;
margin: 0;
}
.docWrap{
.docWrap {
height: calc(100vh - 150px);
overflow: auto;
}
:deep(.el-loading-spinner .el-loading-text){
// color: #079263;
:deep(.el-loading-spinner .el-loading-text) {
color: #079263;
}
:deep(.docx-wrapper>section.docx){
padding: 40px !important;
padding-top: 10px !important;
padding-bottom: 10px !important;
min-height: 965px !important;
box-shadow: 0 0 10px rgba(0, 0, 0, 0) !important;
}
:deep(.docx-wrapper>section.docx){
margin-bottom: 0;
}
:deep(.docx-wrapper){
padding-top: 5px !important;
.hex-container {
height: 600px;
overflow: auto;
padding: 10px;
background: #f8f8f8;
border: 1px solid #ddd;
pre {
font-family: monospace;
white-space: pre-wrap;
word-break: break-all;
margin: 0;
color: #333;
}
}
</style>

View File

@ -5,16 +5,29 @@ export default {
</script>
<script setup lang="ts">
import { ref, onMounted, nextTick } from "vue";
import { ref, onMounted, nextTick, defineAsyncComponent } from "vue";
import { useAppStore } from '@/store/modules/app';
import { ElMessageBox, ElMessage } from "element-plus";
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 ZUpload from '@/components/file/ZUpload.vue'
import useFileUpload from "@/components/file/file/useFileUpload";
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 { currentStorageKey } = useHeaderStorageList();
const { openRow } = useFileData();
onMounted(() => {
getProject()
});
@ -59,12 +72,18 @@ const treeForm = ref({
nodeName: '',
})
//
const treeRef = ref();
function gettreedata() {
treeloading.value = true
treeForm.value.projectId = projectId.value
getNodesTree(treeForm.value).then((res: any) => {
treedata.value = res.data
treeloading.value = false
pathid.value = treedata.value[0].id
nextTick(() => {
treeRef.value?.setCurrentKey(pathid.value);
});
getdata()
})
}
const pathid = ref()
@ -253,6 +272,7 @@ const fileObj: any = ref({
const upfile = ref(false)
function openFile() {
localStorage.setItem('filepath', findPathById(treedata.value, pathid.value));
localStorage.setItem('storageKey', JSON.stringify(['minio']));
upfile.value = true
fileObj.value = {
projectId: '', //ID
@ -271,6 +291,27 @@ function editfile(row: any) {
}
function fileClose() {
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
function bytesToMB(bytes: any) {
@ -289,7 +330,14 @@ function submitfile() {
}
})
} 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 fileSize = []
if (fileArr.length > 0) {
@ -308,6 +356,7 @@ function submitfile() {
ElMessage.success('增加成功')
getdata()
upfile.value = false
localStorage.setItem('fileArr', '');
}
})
}
@ -404,27 +453,27 @@ function xiafilemony() {
)
.then(() => {
tableIdarr.value.forEach((item: any) => {
if(item.url){
if (item.url) {
downloadFileUseIframeMode(item.url);
}
})
})
}
/**
/**
* 使用 iframe 模式下载文件
*
* @param url 下载文件 url
*/
const downloadFileUseIframeMode = (url:any) => {
const iframe:any = document.createElement("iframe");
const downloadFileUseIframeMode = (url: any) => {
const iframe: any = document.createElement("iframe");
iframe.style.display = "none"; //
iframe.style.height = 0; //
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(()=>{
setTimeout(() => {
iframe.remove();
}, 5 * 60 * 1000);
}
}
function findPathById(array: any, targetId: any) {
//
function recursiveSearch(items: any, target: any, path: any) {
@ -451,7 +500,111 @@ function findPathById(array: any, targetId: any) {
let patharr = recursiveSearch(array, targetId, [])
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>
<template>
@ -518,7 +671,12 @@ function findPathById(array: any, targetId: any) {
style="width: 100%; height: calc(100vh - 275px);margin-bottom: 20px;" border>
<el-table-column type="selection" width="40" />
<!-- <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="description" 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>
</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>
</template>
@ -683,4 +875,12 @@ function findPathById(array: any, targetId: any) {
}
}
}
.tableHover {
cursor: pointer;
}
.tableHover:hover {
color: #409eff;
}
</style>

File diff suppressed because it is too large Load Diff