新增:图片文件预览功能
This commit is contained in:
@@ -108,7 +108,30 @@
|
||||
<a-col :span="12">
|
||||
<a-card title="📝 文件内容">
|
||||
<a-space direction="vertical" :size="8" style="width: 100%">
|
||||
<div
|
||||
|
||||
<!-- 图片预览 -->
|
||||
<div v-if="isImageFile" class="image-preview-container">
|
||||
<div class="image-preview-wrapper">
|
||||
<img
|
||||
:src="imagePreviewUrl"
|
||||
class="preview-image"
|
||||
@load="onImageLoad"
|
||||
@error="onImageError"
|
||||
alt="图片预览"
|
||||
/>
|
||||
<div v-if="imageLoading" class="image-loading">
|
||||
<a-spin />
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-info">
|
||||
<a-tag>{{ getImageType() }}</a-tag>
|
||||
<span v-if="imageSize">{{ formatBytes(imageSize) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文本编辑器 -->
|
||||
<div v-else
|
||||
class="file-content-wrapper"
|
||||
:style="{ height: fileContentHeight + 'px' }"
|
||||
>
|
||||
@@ -118,7 +141,9 @@
|
||||
placeholder="文件内容将显示在这里,点击文件列表中的文件即可查看"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!isImageFile"
|
||||
class="resize-handle"
|
||||
@mousedown="startResize"
|
||||
title="拖拽调整高度"
|
||||
@@ -130,9 +155,9 @@
|
||||
<template #icon>
|
||||
<icon-file />
|
||||
</template>
|
||||
读取
|
||||
{{ isImageFile ? '预览' : '读取' }}
|
||||
</a-button>
|
||||
<a-button @click="writeFile" :loading="fileLoading">
|
||||
<a-button @click="writeFile" :loading="fileLoading" v-if="!isImageFile">
|
||||
<template #icon>
|
||||
<icon-save />
|
||||
</template>
|
||||
@@ -260,6 +285,12 @@ const pathHistory = ref([])
|
||||
const fileContentHeight = ref(250)
|
||||
const favoriteFiles = ref([])
|
||||
|
||||
// 图片预览相关
|
||||
const isImageFile = ref(false)
|
||||
const imagePreviewUrl = ref('')
|
||||
const imageLoading = ref(false)
|
||||
const imageSize = ref(0)
|
||||
|
||||
// 格式化文件大小
|
||||
const formatBytes = (bytes) => {
|
||||
if (!bytes) return '0 B'
|
||||
@@ -382,13 +413,10 @@ const readFile = async () => {
|
||||
// 检查文件类型
|
||||
const ext = filePath.value.split('.').pop()?.toLowerCase() || ''
|
||||
|
||||
// 图片文件 - 提示无法预览
|
||||
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp', 'ico', 'heic', 'heif']
|
||||
// 图片文件 - 显示预览
|
||||
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp', 'ico']
|
||||
if (imageExts.includes(ext)) {
|
||||
Message.warning({
|
||||
content: '图片文件暂不支持预览,请使用图片查看器打开',
|
||||
duration: 3000
|
||||
})
|
||||
await previewImage()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -438,8 +466,71 @@ const readFile = async () => {
|
||||
await performFileRead()
|
||||
}
|
||||
|
||||
// 预览图片
|
||||
const previewImage = async () => {
|
||||
if (!filePath.value) {
|
||||
Message.error('请输入文件路径')
|
||||
return
|
||||
}
|
||||
|
||||
isImageFile.value = true
|
||||
imageLoading.value = true
|
||||
imagePreviewUrl.value = ''
|
||||
imageSize.value = 0
|
||||
|
||||
try {
|
||||
// 使用文件协议直接显示图片
|
||||
// 转换 Windows 路径格式
|
||||
let imagePath = filePath.value.replace(/\\/g, '/')
|
||||
imagePreviewUrl.value = `file:///${imagePath}`
|
||||
|
||||
// 获取文件大小
|
||||
const file = fileList.value.find(f => f.path === filePath.value)
|
||||
if (file && file.size) {
|
||||
imageSize.value = file.size
|
||||
}
|
||||
|
||||
Message.success('图片加载成功')
|
||||
} catch (error) {
|
||||
console.error('图片预览失败:', error)
|
||||
Message.error('图片预览失败: ' + (error.message || error))
|
||||
isImageFile.value = false
|
||||
} finally {
|
||||
imageLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 图片加载完成
|
||||
const onImageLoad = () => {
|
||||
imageLoading.value = false
|
||||
}
|
||||
|
||||
// 图片加载失败
|
||||
const onImageError = () => {
|
||||
imageLoading.value = false
|
||||
Message.error('图片加载失败,可能是格式不支持或文件损坏')
|
||||
}
|
||||
|
||||
// 获取图片类型标签
|
||||
const getImageType = () => {
|
||||
if (!filePath.value) return ''
|
||||
const ext = filePath.value.split('.').pop()?.toLowerCase() || ''
|
||||
const typeMap = {
|
||||
'jpg': 'JPEG',
|
||||
'jpeg': 'JPEG',
|
||||
'png': 'PNG',
|
||||
'gif': 'GIF',
|
||||
'bmp': 'BMP',
|
||||
'svg': 'SVG',
|
||||
'webp': 'WebP',
|
||||
'ico': 'ICO'
|
||||
}
|
||||
return typeMap[ext] || ext.toUpperCase()
|
||||
}
|
||||
|
||||
// 执行文件读取
|
||||
const performFileRead = async () => {
|
||||
isImageFile.value = false
|
||||
fileLoading.value = true
|
||||
try {
|
||||
const content = await readFileApi(filePath.value)
|
||||
@@ -526,6 +617,8 @@ const deleteFile = async () => {
|
||||
// 清空内容
|
||||
const clearContent = () => {
|
||||
fileContent.value = ''
|
||||
isImageFile.value = false
|
||||
imagePreviewUrl.value = ''
|
||||
Message.info('内容已清空')
|
||||
}
|
||||
|
||||
@@ -750,6 +843,54 @@ onMounted(() => {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 图片预览容器 */
|
||||
.image-preview-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image-preview-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
max-height: 600px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-fill-1);
|
||||
border: 1px solid var(--color-border-2);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
max-width: 100%;
|
||||
max-height: 600px;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.image-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
.image-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 0;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
.file-content-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
Reference in New Issue
Block a user