diff --git a/frontend/package.json b/frontend/package.json index 5d0799a22..058a352c6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,6 +28,9 @@ "@codemirror/theme-one-dark": "^6.1.2", "@element-plus/icons-vue": "^1.1.4", "@highlightjs/vue-plugin": "^2.1.0", + "@vue-office/docx": "^1.6.2", + "@vue-office/excel": "^1.7.8", + "@vue-office/pdf": "^2.0.2", "@vueuse/core": "^8.9.4", "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", @@ -49,6 +52,7 @@ "vue": "^3.4.27", "vue-clipboard3": "^2.0.0", "vue-codemirror": "^6.1.1", + "vue-demi": "^0.14.6", "vue-i18n": "^9.13.1", "vue-router": "^4.3.3" }, diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 1e0bc4742..23a8d0597 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -63,6 +63,7 @@ const message = { hideSome: 'Hide Some', agree: 'Agree', notAgree: 'Not Agree', + preview: 'Preview', }, search: { timeStart: 'Time start', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index ae45e20bc..0d77f031d 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -62,6 +62,7 @@ const message = { hideSome: '隱藏部分', agree: '同意', notAgree: '不同意', + preview: '預覽', }, search: { timeStart: '開始時間', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 489a7c84b..eb7c7336e 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -62,6 +62,7 @@ const message = { hideSome: '隐藏部分', agree: '同意', notAgree: '不同意', + preview: '预览', }, search: { timeStart: '开始时间', diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts index b4d4f0ab8..a16eb2151 100644 --- a/frontend/src/utils/util.ts +++ b/frontend/src/utils/util.ts @@ -196,7 +196,10 @@ let icons = new Map([ ['.tar.bz2', 'p-file-zip'], ['.tar', 'p-file-zip'], ['.tar.gz', 'p-file-zip'], - ['.tar.xz', 'p-file-zip'], + ['.war', 'p-file-zip'], + ['.tgz', 'p-file-zip'], + ['.7z', 'p-file-zip'], + ['.rar', 'p-file-zip'], ['.mp3', 'p-file-mp3'], ['.svg', 'p-file-svg'], ['.txt', 'p-file-txt'], @@ -204,9 +207,32 @@ let icons = new Map([ ['.word', 'p-file-word'], ['.ppt', 'p-file-ppt'], ['.jpg', 'p-file-jpg'], + ['.jpeg', 'p-file-jpg'], + ['.png', 'p-file-png'], ['.xlsx', 'p-file-excel'], ['.doc', 'p-file-word'], + ['.xls', 'p-file-excel'], + ['.docx', 'p-file-word'], ['.pdf', 'p-file-pdf'], + ['.bmp', 'p-file-png'], + ['.gif', 'p-file-png'], + ['.tiff', 'p-file-png'], + ['.ico', 'p-file-png'], + ['.webp', 'p-file-png'], + ['.mp4', 'p-file-video'], + ['.webm', 'p-file-video'], + ['.mov', 'p-file-video'], + ['.wmv', 'p-file-video'], + ['.mkv', 'p-file-video'], + ['.avi', 'p-file-video'], + ['.wma', 'p-file-video'], + ['.flv', 'p-file-video'], + ['.wav', 'p-file-mp3'], + ['.wma', 'p-file-mp3'], + ['.ape', 'p-file-mp3'], + ['.acc', 'p-file-mp3'], + ['.ogg', 'p-file-mp3'], + ['.flac', 'p-file-mp3'], ]); export function getIcon(extension: string): string { @@ -526,3 +552,25 @@ export function emptyLineFilter(str: string, spilt: string) { } return results.join(spilt); } + +// 文件类型映射 +let fileTypes = { + image: ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.ico', '.svg', '.webp'], + compress: ['.zip', '.rar', '.gz', '.war', '.tgz', '.7z', '.tar.gz', '.tar'], + video: ['.mp4', '.webm', '.mov', '.wmv', '.mkv', '.avi', '.wma', '.flv'], + audio: ['.mp3', '.wav', '.wma', '.ape', '.acc', '.ogg', '.flac'], + pdf: ['.pdf'], + word: ['.doc', '.docx'], + excel: ['.xls', '.xlsx'], + text: ['.iso', '.tiff', '.exe', '.so', '.bz', '.dmg', '.apk', '.pptx', '.ppt', '.xlsb'], +}; + +export const getFileType = (extension: string) => { + let type = 'text'; + Object.entries(fileTypes).forEach(([key, extensions]) => { + if (extensions.includes(extension.toLowerCase())) { + type = key; + } + }); + return type; +}; diff --git a/frontend/src/views/host/file-management/index.vue b/frontend/src/views/host/file-management/index.vue index 8e9e0c8df..60f70ac84 100644 --- a/frontend/src/views/host/file-management/index.vue +++ b/frontend/src/views/host/file-management/index.vue @@ -307,6 +307,7 @@ + @@ -321,8 +322,8 @@ import { RemoveFavorite, SearchFavorite, } from '@/api/modules/files'; -import { computeSize, copyText, dateFormat, downloadFile, getIcon, getRandomStr } from '@/utils/util'; -import { StarFilled, Star, Top, Right } from '@element-plus/icons-vue'; +import { computeSize, copyText, dateFormat, downloadFile, getFileType, getIcon, getRandomStr } from '@/utils/util'; +import { StarFilled, Star, Top, Right, Close } from '@element-plus/icons-vue'; import { File } from '@/api/interface/file'; import { Mimetypes, Languages } from '@/global/mimetype'; import { useRouter } from 'vue-router'; @@ -350,6 +351,7 @@ import Detail from './detail/index.vue'; import RecycleBin from './recycle-bin/index.vue'; import Favorite from './favorite/index.vue'; import BatchRole from './batch-role/index.vue'; +import Preview from './preview/index.vue'; import VscodeOpenDialog from '@/components/vscode-open/index.vue'; const globalStore = GlobalStore(); @@ -387,6 +389,7 @@ const fileCreate = reactive({ path: '/', isDir: false, mode: 0o755 }); const fileCompress = reactive({ files: [''], name: '', dst: '', operate: 'compress' }); const fileDeCompress = reactive({ path: '', name: '', dst: '', mimeType: '' }); const fileEdit = reactive({ content: '', path: '', name: '', language: 'plaintext', extension: '' }); +const filePreview = reactive({ path: '', name: '', extension: '', fileType: '' }); const codeReq = reactive({ path: '', expand: false, page: 1, pageSize: 100 }); const fileUpload = reactive({ path: '' }); const fileRename = reactive({ path: '', oldName: '' }); @@ -416,6 +419,7 @@ const hoveredRowIndex = ref(-1); const favorites = ref([]); const batchRoleRef = ref(); const dialogVscodeOpenRef = ref(); +const previewRef = ref(); // editablePath const { searchableStatus, searchablePath, searchableInputRef, searchableInputBlur } = useSearchable(paths); @@ -484,7 +488,7 @@ const open = async (row: File.File) => { }); jump(req.path); } else { - openCodeEditor(row.path, row.extension); + openView(row); } }; @@ -680,6 +684,31 @@ const openDeCompress = (item: File.File) => { deCompressRef.value.acceptParams(fileDeCompress); }; +const openView = (item: File.File) => { + const fileType = getFileType(item.extension); + + const previewTypes = ['image', 'video', 'audio', 'pdf', 'word', 'excel']; + if (previewTypes.includes(fileType)) { + return openPreview(item, fileType); + } + + const actionMap = { + compress: openDeCompress, + text: () => openCodeEditor(item.path, item.extension), + }; + + return actionMap[fileType] ? actionMap[fileType](item) : openCodeEditor(item.path, item.extension); +}; + +const openPreview = (item: File.File, fileType: string) => { + filePreview.path = item.path; + filePreview.name = item.name; + filePreview.extension = item.extension; + filePreview.fileType = fileType; + + previewRef.value.acceptParams(filePreview); +}; + const openCodeEditor = (path: string, extension: string) => { codeReq.path = path; codeReq.expand = true; @@ -839,10 +868,11 @@ const getFavoriates = async () => { const toFavorite = (row: File.Favorite) => { if (row.isDir) { jump(row.path); - } else if (row.isTxt) { - openCodeEditor(row.path, '.' + row.name.split('.').pop()); } else { - jump(row.path.substring(0, row.path.lastIndexOf('/'))); + let file = {} as File.File; + file.path = row.path; + file.extension = '.' + row.name.split('.').pop(); + openView(file); } }; diff --git a/frontend/src/views/host/file-management/preview/index.vue b/frontend/src/views/host/file-management/preview/index.vue new file mode 100644 index 000000000..a89bce934 --- /dev/null +++ b/frontend/src/views/host/file-management/preview/index.vue @@ -0,0 +1,146 @@ + + + + +