Skip to content

Commit 4074b94

Browse files
authored
feat: Preview the current directory image (#8930)
Refs: #6117
1 parent 7f823ec commit 4074b94

2 files changed

Lines changed: 104 additions & 40 deletions

File tree

frontend/src/views/host/file-management/index.vue

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@
253253
</el-button>
254254
</template>
255255
<template v-else>
256-
<el-dropdown class="mr-2.5" style="border-left: 1px solid #ccc">
256+
<el-dropdown class="mr-2.5">
257257
<el-button class="btn">
258258
{{ hostMount[0]?.path }} ({{ $t('file.root') }})
259259
{{ formatFileSize(hostMount[0]?.free) }}
@@ -314,7 +314,7 @@
314314
</el-button>
315315
</template>
316316

317-
<el-dropdown v-if="moreButtons.length" style="border-left: 1px solid #ccc">
317+
<el-dropdown v-if="moreButtons.length">
318318
<el-button>
319319
{{ $t('tabs.more') }}
320320
<i class="el-icon-arrow-down el-icon--right" />
@@ -614,7 +614,7 @@ const fileCreate = reactive({ path: '/', isDir: false, mode: 0o755 });
614614
const fileCompress = reactive({ files: [''], name: '', dst: '', operate: 'compress' });
615615
const fileDeCompress = reactive({ path: '', name: '', dst: '', mimeType: '' });
616616
const fileEdit = reactive({ content: '', path: '', name: '', language: 'plaintext', extension: '' });
617-
const filePreview = reactive({ path: '', name: '', extension: '', fileType: '' });
617+
const filePreview = reactive({ path: '', name: '', extension: '', fileType: '', imageFiles: [] });
618618
const codeReq = reactive({ path: '', expand: false, page: 1, pageSize: 100, isDetail: false });
619619
const fileUpload = reactive({ path: '' });
620620
const fileRename = reactive({ path: '', oldName: '' });
@@ -653,6 +653,7 @@ const disableBtn = ref(false);
653653
const calculateBtn = ref(false);
654654
const dirNum = ref(0);
655655
const fileNum = ref(0);
656+
const imageFiles = ref([]);
656657
657658
const { searchableStatus, searchablePath, searchableInputRef, searchableInputBlur } = useSearchable(paths);
658659
@@ -1034,6 +1035,12 @@ const openDeCompress = (item: File.File) => {
10341035
10351036
const openView = (item: File.File) => {
10361037
const fileType = getFileType(item.extension);
1038+
if (fileType === 'image') {
1039+
imageFiles.value = data.value
1040+
.filter((item) => !item.isDir)
1041+
.filter((item) => getFileType(item.extension) == 'image')
1042+
.map((item) => (item.isSymlink ? item.linkPath : item.path));
1043+
}
10371044
10381045
const previewTypes = ['image', 'video', 'audio', 'word', 'excel'];
10391046
if (previewTypes.includes(fileType)) {
@@ -1058,6 +1065,7 @@ const openPreview = (item: File.File, fileType: string) => {
10581065
filePreview.name = item.name;
10591066
filePreview.extension = item.extension;
10601067
filePreview.fileType = fileType;
1068+
filePreview.imageFiles = imageFiles.value;
10611069
10621070
previewRef.value.acceptParams(filePreview);
10631071
};
@@ -1482,4 +1490,7 @@ onBeforeUnmount(() => {
14821490
.search-button {
14831491
width: 20vw;
14841492
}
1493+
.el-button-group > .el-dropdown > .el-button {
1494+
border-left-color: var(--el-border-color);
1495+
}
14851496
</style>

frontend/src/views/host/file-management/preview/index.vue

Lines changed: 90 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,68 @@
1515
<span>{{ $t('commons.button.preview') + ' - ' + filePath }}</span>
1616
<el-space alignment="center" :size="10" class="dialog-header-icon">
1717
<el-tooltip :content="loadTooltip()" placement="top" v-if="fileType !== 'excel'">
18-
<el-icon @click="toggleFullscreen"><FullScreen /></el-icon>
18+
<el-icon @click="toggleFullscreen" class="cursor-pointer hover:scale-110">
19+
<FullScreen />
20+
</el-icon>
1921
</el-tooltip>
20-
<el-icon @click="handleClose" size="20"><Close /></el-icon>
22+
<el-icon @click="handleClose" size="20" class="cursor-pointer hover:scale-110"><Close /></el-icon>
2123
</el-space>
2224
</div>
2325
</template>
24-
<div v-loading="loading" :style="isFullscreen ? 'height: 90vh' : 'height: 80vh'">
25-
<div class="flex items-center justify-center h-full">
26-
<el-image
27-
v-if="fileType === 'image'"
28-
:src="fileUrl"
29-
:style="isFullscreen ? 'height: 90vh' : 'height: 80vh'"
30-
fit="contain"
31-
:preview-src-list="[fileUrl]"
32-
/>
33-
34-
<video v-else-if="fileType === 'video'" :src="fileUrl" controls autoplay class="size-3/4"></video>
35-
36-
<audio v-else-if="fileType === 'audio'" :src="fileUrl" controls></audio>
37-
38-
<vue-office-docx
39-
v-else-if="fileType === 'word'"
40-
:src="fileUrl"
41-
:style="isFullscreen ? 'height: 90vh' : 'height: 80vh'"
42-
class="w-full"
43-
@rendered="renderedHandler"
44-
@error="errorHandler"
45-
/>
46-
47-
<vue-office-excel
48-
v-else-if="fileType === 'excel'"
49-
:src="fileUrl"
50-
:style="isFullscreen ? 'height: 90vh;' : 'height: 80vh'"
51-
class="w-full"
52-
@rendered="renderedHandler"
53-
@error="errorHandler"
54-
/>
55-
</div>
26+
<div
27+
v-loading="loading"
28+
:style="isFullscreen ? 'height: 90vh' : 'height: 80vh'"
29+
:class="fileType === 'image' ? 'overflow-y-auto' : 'flex justify-center items-center'"
30+
>
31+
<template v-if="fileType === 'image'">
32+
<div class="flex h-full">
33+
<aside class="w-[200px] overflow-y-auto p-2 sm:block hidden left-aside rounded">
34+
<template v-for="(item, index) in imageFiles" :key="index">
35+
<el-tooltip :content="item.path" placement="right">
36+
<div
37+
class="text-sm truncate mb-1 rounded p-1 left-item"
38+
@click="changeImg(item.path)"
39+
:class="item.path === filePath ? 'left-item-default' : ''"
40+
>
41+
{{ item.path }}
42+
</div>
43+
</el-tooltip>
44+
</template>
45+
</aside>
46+
<main class="flex-1 overflow-hidden">
47+
<el-tooltip :content="filePath" placement="bottom">
48+
<el-image
49+
loading="lazy"
50+
:src="fileUrl"
51+
:alt="filePath"
52+
fit="contain"
53+
class="w-full h-full"
54+
/>
55+
</el-tooltip>
56+
</main>
57+
</div>
58+
</template>
59+
<video v-else-if="fileType === 'video'" :src="fileUrl" controls autoplay class="size-3/4"></video>
60+
61+
<audio v-else-if="fileType === 'audio'" :src="fileUrl" controls></audio>
62+
63+
<vue-office-docx
64+
v-else-if="fileType === 'word'"
65+
:src="fileUrl"
66+
:style="isFullscreen ? 'height: 90vh' : 'height: 80vh'"
67+
class="w-full"
68+
@rendered="renderedHandler"
69+
@error="errorHandler"
70+
/>
71+
72+
<vue-office-excel
73+
v-else-if="fileType === 'excel'"
74+
:src="fileUrl"
75+
:style="isFullscreen ? 'height: 90vh;' : 'height: 80vh'"
76+
class="w-full"
77+
@rendered="renderedHandler"
78+
@error="errorHandler"
79+
/>
5680
</div>
5781
</el-dialog>
5882
</template>
@@ -73,6 +97,7 @@ interface EditProps {
7397
path: string;
7498
name: string;
7599
extension: string;
100+
imageFiles: [];
76101
}
77102
78103
const open = ref(false);
@@ -81,6 +106,7 @@ const filePath = ref('');
81106
const fileName = ref('');
82107
const fileType = ref('');
83108
const fileUrl = ref('');
109+
const imageFiles = ref([]);
84110
85111
const fileExtension = ref('');
86112
const isFullscreen = ref(false);
@@ -107,23 +133,38 @@ const toggleFullscreen = () => {
107133
isFullscreen.value = !isFullscreen.value;
108134
};
109135
136+
const getDownloadUrl = (path: string) => {
137+
const baseUrl = `${import.meta.env.VITE_API_URL as string}/files/download`;
138+
const encodedPath = encodeURIComponent(path);
139+
const timestamp = new Date().getTime();
140+
return `${baseUrl}?path=${encodedPath}&timestamp=${timestamp}`;
141+
};
142+
110143
const acceptParams = (props: EditProps) => {
144+
imageFiles.value = [];
111145
fileExtension.value = props.extension;
112146
fileName.value = props.name;
113147
filePath.value = props.path;
114148
fileType.value = props.fileType;
115149
isFullscreen.value = fileType.value === 'excel';
116150
117151
loading.value = true;
118-
fileUrl.value = `${import.meta.env.VITE_API_URL as string}/files/download?path=${encodeURIComponent(
119-
props.path,
120-
)}&timestamp=${new Date().getTime()}`;
152+
fileUrl.value = getDownloadUrl(props.path);
153+
imageFiles.value = props.imageFiles.map((item) => ({
154+
path: item,
155+
url: getDownloadUrl(item),
156+
}));
121157
open.value = true;
122158
loading.value = false;
123159
};
124160
125161
const onOpen = () => {};
126162
163+
const changeImg = (path: string) => {
164+
filePath.value = path;
165+
fileUrl.value = getDownloadUrl(path);
166+
};
167+
127168
defineExpose({ acceptParams });
128169
</script>
129170

@@ -135,4 +176,16 @@ defineExpose({ acceptParams });
135176
.dialog-header-icon {
136177
color: var(--el-color-info);
137178
}
179+
.left-aside {
180+
background-color: var(--panel-menu-bg-color);
181+
opacity: 85%;
182+
}
183+
.left-item {
184+
&:hover {
185+
background: var(--el-menu-item-bg-color-active) !important;
186+
}
187+
}
188+
.left-item-default {
189+
background: var(--el-menu-item-bg-color-active) !important;
190+
}
138191
</style>

0 commit comments

Comments
 (0)