新增:独立的文件管理模块,优化文件操作功能
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
<h2>Go Desk</h2>
|
||||
<a-tabs v-model:active-key="activeTab" class="header-tabs">
|
||||
<a-tab-pane key="db-cli" title="数据库客户端"/>
|
||||
<a-tab-pane key="file-system" title="文件管理"/>
|
||||
<a-tab-pane key="user" title="用户查询"/>
|
||||
<a-tab-pane key="device" title="设备调用测试"/>
|
||||
</a-tabs>
|
||||
@@ -24,6 +25,9 @@
|
||||
<!-- 数据库客户端 -->
|
||||
<DbCli v-if="activeTab === 'db-cli'"/>
|
||||
|
||||
<!-- 文件管理 -->
|
||||
<FileSystem v-if="activeTab === 'file-system'"/>
|
||||
|
||||
<!-- 用户查询页面 -->
|
||||
<div v-if="activeTab === 'user'">
|
||||
<!-- 查询表单 -->
|
||||
@@ -110,6 +114,7 @@ import DeviceTest from './components/DeviceTest.vue'
|
||||
import DbCli from './views/db-cli/index.vue'
|
||||
import ThemeToggle from './components/ThemeToggle.vue'
|
||||
import UpdatePanel from './components/UpdatePanel.vue'
|
||||
import FileSystem from './components/FileSystem.vue'
|
||||
|
||||
const activeTab = ref('db-cli')
|
||||
const showUpdateModal = ref(false)
|
||||
|
||||
@@ -63,6 +63,26 @@
|
||||
<a-button @click="browseDirectory">浏览</a-button>
|
||||
<a-button type="primary" @click="listDirectory">列出目录</a-button>
|
||||
</a-input-group>
|
||||
|
||||
<!-- 收藏的文件 -->
|
||||
<a-card size="small" title="⭐ 收藏的文件" v-if="favoriteFiles.length > 0">
|
||||
<a-space wrap>
|
||||
<a-tag
|
||||
v-for="fav in favoriteFiles"
|
||||
:key="fav.path"
|
||||
closable
|
||||
@close="removeFavorite(fav.path)"
|
||||
@click="openFavoriteFile(fav.path)"
|
||||
style="cursor: pointer; margin-bottom: 4px"
|
||||
>
|
||||
<template #icon>
|
||||
<span>{{ fav.is_dir ? '📁' : '📄' }}</span>
|
||||
</template>
|
||||
{{ fav.name }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
</a-card>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-card size="small" title="文件列表">
|
||||
@@ -78,6 +98,14 @@
|
||||
<a-space>
|
||||
<span>{{ item.is_dir ? '📁' : '📄' }}</span>
|
||||
<a @click="selectFile(item.path)">{{ item.name }}</a>
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
@click.stop="toggleFavorite(item)"
|
||||
:style="{ color: isFavorite(item.path) ? '#ffcd00' : '' }"
|
||||
>
|
||||
{{ isFavorite(item.path) ? '⭐' : '☆' }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #description>
|
||||
@@ -93,11 +121,23 @@
|
||||
<a-col :span="12">
|
||||
<a-card size="small" title="文件内容">
|
||||
<a-space direction="vertical" :size="8" style="width: 100%">
|
||||
<a-textarea
|
||||
v-model="fileContent"
|
||||
:rows="10"
|
||||
placeholder="文件内容将显示在这里"
|
||||
/>
|
||||
<div
|
||||
class="file-content-wrapper"
|
||||
:style="{ height: fileContentHeight + 'px' }"
|
||||
>
|
||||
<a-textarea
|
||||
v-model="fileContent"
|
||||
class="file-content-textarea"
|
||||
placeholder="文件内容将显示在这里"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="resize-handle"
|
||||
@mousedown="startResize"
|
||||
title="拖拽调整高度"
|
||||
>
|
||||
<div class="resize-handle-bar"></div>
|
||||
</div>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="readFile" :loading="fileLoading">读取文件</a-button>
|
||||
<a-button @click="writeFile" :loading="fileLoading">写入文件</a-button>
|
||||
@@ -145,7 +185,9 @@ const STORAGE_KEYS = {
|
||||
FILE_PATH: 'device-test-file-path',
|
||||
FILE_LIST: 'device-test-file-list',
|
||||
FILE_CONTENT: 'device-test-file-content',
|
||||
PATH_HISTORY: 'device-test-path-history'
|
||||
PATH_HISTORY: 'device-test-path-history',
|
||||
FILE_CONTENT_HEIGHT: 'device-test-file-content-height',
|
||||
FAVORITE_FILES: 'device-test-favorite-files'
|
||||
}
|
||||
|
||||
const systemInfo = ref(null)
|
||||
@@ -159,6 +201,9 @@ const fileLoading = ref(false)
|
||||
const envVars = ref(null)
|
||||
const envLoading = ref(false)
|
||||
const pathHistory = ref([]) // 路径历史记录
|
||||
const fileContentHeight = ref(200) // 文件内容区域高度(默认200px)
|
||||
const isResizing = ref(false) // 是否正在拖拽
|
||||
const favoriteFiles = ref([]) // 收藏的文件列表
|
||||
|
||||
const diskColumns = [
|
||||
{title: '设备', dataIndex: 'device', width: 120},
|
||||
@@ -317,6 +362,37 @@ const formatBytes = (bytes) => {
|
||||
return (bytes / Math.pow(unit, exp)).toFixed(2) + ' ' + 'KMGTPE'[exp - 1] + 'B'
|
||||
}
|
||||
|
||||
// 开始拖拽
|
||||
const startResize = (e) => {
|
||||
isResizing.value = true
|
||||
const startY = e.clientY
|
||||
const startHeight = fileContentHeight.value
|
||||
|
||||
const onMouseMove = (moveEvent) => {
|
||||
if (!isResizing.value) return
|
||||
|
||||
const deltaY = moveEvent.clientY - startY
|
||||
const newHeight = startHeight + deltaY
|
||||
|
||||
// 限制高度范围
|
||||
if (newHeight >= 100 && newHeight <= 800) {
|
||||
fileContentHeight.value = newHeight
|
||||
}
|
||||
}
|
||||
|
||||
const onMouseUp = () => {
|
||||
isResizing.value = false
|
||||
document.removeEventListener('mousemove', onMouseMove)
|
||||
document.removeEventListener('mouseup', onMouseUp)
|
||||
|
||||
// 保存高度到 localStorage
|
||||
saveToStorage(STORAGE_KEYS.FILE_CONTENT_HEIGHT, fileContentHeight.value.toString())
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove)
|
||||
document.addEventListener('mouseup', onMouseUp)
|
||||
}
|
||||
|
||||
// 从 localStorage 加载数据
|
||||
const loadFromStorage = () => {
|
||||
try {
|
||||
@@ -324,11 +400,20 @@ const loadFromStorage = () => {
|
||||
const savedFileList = localStorage.getItem(STORAGE_KEYS.FILE_LIST)
|
||||
const savedFileContent = localStorage.getItem(STORAGE_KEYS.FILE_CONTENT)
|
||||
const savedHistory = localStorage.getItem(STORAGE_KEYS.PATH_HISTORY)
|
||||
const savedHeight = localStorage.getItem(STORAGE_KEYS.FILE_CONTENT_HEIGHT)
|
||||
const savedFavorites = localStorage.getItem(STORAGE_KEYS.FAVORITE_FILES)
|
||||
|
||||
if (savedPath) filePath.value = savedPath
|
||||
if (savedFileList) fileList.value = JSON.parse(savedFileList)
|
||||
if (savedFileContent) fileContent.value = savedFileContent
|
||||
if (savedHistory) pathHistory.value = JSON.parse(savedHistory)
|
||||
if (savedFavorites) favoriteFiles.value = JSON.parse(savedFavorites)
|
||||
if (savedHeight) {
|
||||
const height = parseInt(savedHeight)
|
||||
if (!isNaN(height) && height >= 100 && height <= 800) {
|
||||
fileContentHeight.value = height
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('从 localStorage 加载数据失败:', error)
|
||||
}
|
||||
@@ -368,6 +453,58 @@ const addToHistory = (path) => {
|
||||
saveToStorage(STORAGE_KEYS.PATH_HISTORY, pathHistory.value)
|
||||
}
|
||||
|
||||
// 检查是否已收藏
|
||||
const isFavorite = (path) => {
|
||||
return favoriteFiles.value.some(fav => fav.path === path)
|
||||
}
|
||||
|
||||
// 切换收藏状态
|
||||
const toggleFavorite = (item) => {
|
||||
const index = favoriteFiles.value.findIndex(fav => fav.path === item.path)
|
||||
|
||||
if (index > -1) {
|
||||
// 已收藏,取消收藏
|
||||
favoriteFiles.value.splice(index, 1)
|
||||
Message.info('已取消收藏: ' + item.name)
|
||||
} else {
|
||||
// 未收藏,添加收藏
|
||||
favoriteFiles.value.push({
|
||||
path: item.path,
|
||||
name: item.name,
|
||||
is_dir: item.is_dir
|
||||
})
|
||||
Message.success('已收藏: ' + item.name)
|
||||
}
|
||||
|
||||
// 保存到 localStorage
|
||||
saveToStorage(STORAGE_KEYS.FAVORITE_FILES, favoriteFiles.value)
|
||||
}
|
||||
|
||||
// 移除收藏
|
||||
const removeFavorite = (path) => {
|
||||
const index = favoriteFiles.value.findIndex(fav => fav.path === path)
|
||||
if (index > -1) {
|
||||
const name = favoriteFiles.value[index].name
|
||||
favoriteFiles.value.splice(index, 1)
|
||||
saveToStorage(STORAGE_KEYS.FAVORITE_FILES, favoriteFiles.value)
|
||||
Message.info('已取消收藏: ' + name)
|
||||
}
|
||||
}
|
||||
|
||||
// 打开收藏的文件
|
||||
const openFavoriteFile = (path) => {
|
||||
filePath.value = path
|
||||
addToHistory(path)
|
||||
|
||||
// 判断是文件还是目录
|
||||
const fav = favoriteFiles.value.find(f => f.path === path)
|
||||
if (fav && fav.is_dir) {
|
||||
listDirectory()
|
||||
} else {
|
||||
readFile()
|
||||
}
|
||||
}
|
||||
|
||||
// 监听数据变化自动保存
|
||||
watch(filePath, (newPath) => {
|
||||
saveToStorage(STORAGE_KEYS.FILE_PATH, newPath)
|
||||
@@ -397,4 +534,47 @@ onMounted(() => {
|
||||
.test-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 文件内容区域容器 */
|
||||
.file-content-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: height 0.1s ease;
|
||||
}
|
||||
|
||||
/* 文件内容文本框 */
|
||||
.file-content-textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
/* 拖拽手柄 */
|
||||
.resize-handle {
|
||||
height: 8px;
|
||||
cursor: ns-resize;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-fill-2);
|
||||
border-radius: 4px;
|
||||
margin: 4px 0;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.resize-handle:hover {
|
||||
background: var(--color-fill-3);
|
||||
}
|
||||
|
||||
/* 拖拽手柄的视觉指示条 */
|
||||
.resize-handle-bar {
|
||||
width: 40px;
|
||||
height: 3px;
|
||||
background: var(--color-border-3);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.resize-handle:hover .resize-handle-bar {
|
||||
background: rgb(var(--primary-6));
|
||||
}
|
||||
</style>
|
||||
|
||||
559
web/src/components/FileSystem.vue
Normal file
559
web/src/components/FileSystem.vue
Normal file
@@ -0,0 +1,559 @@
|
||||
<template>
|
||||
<div class="file-system">
|
||||
<a-space direction="vertical" style="width: 100%" :size="16">
|
||||
|
||||
<!-- 路径输入 -->
|
||||
<a-card title="📂 文件浏览">
|
||||
<a-space direction="vertical" :size="12" style="width: 100%">
|
||||
<a-input-group>
|
||||
<a-auto-complete
|
||||
v-model="filePath"
|
||||
:data="pathHistory"
|
||||
placeholder="输入文件或目录路径 (如: C:\Users 或 /home/user)"
|
||||
style="flex: 1"
|
||||
@select="onPathSelect"
|
||||
/>
|
||||
<a-button @click="browseDirectory">
|
||||
<template #icon>
|
||||
<icon-folder />
|
||||
</template>
|
||||
浏览
|
||||
</a-button>
|
||||
<a-button type="primary" @click="listDirectory" :loading="fileLoading">
|
||||
<template #icon>
|
||||
<icon-list />
|
||||
</template>
|
||||
列出目录
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
|
||||
<!-- 常用路径快捷按钮 -->
|
||||
<a-space wrap>
|
||||
<a-tag
|
||||
v-for="shortcut in commonPaths"
|
||||
:key="shortcut.path"
|
||||
@click="openPath(shortcut.path)"
|
||||
style="cursor: pointer"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-forward />
|
||||
</template>
|
||||
{{ shortcut.name }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
</a-space>
|
||||
</a-card>
|
||||
|
||||
<!-- 收藏的文件 -->
|
||||
<a-card title="⭐ 收藏夹" v-if="favoriteFiles.length > 0">
|
||||
<a-space wrap>
|
||||
<a-tag
|
||||
v-for="fav in favoriteFiles"
|
||||
:key="fav.path"
|
||||
closable
|
||||
@close="removeFavorite(fav.path)"
|
||||
@click="openFavoriteFile(fav.path)"
|
||||
style="cursor: pointer; margin-bottom: 4px; padding: 4px 8px"
|
||||
>
|
||||
<template #icon>
|
||||
<span style="font-size: 16px">{{ fav.is_dir ? '📁' : '📄' }}</span>
|
||||
</template>
|
||||
{{ fav.name }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
</a-card>
|
||||
|
||||
<!-- 文件列表和编辑器 -->
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-card title="📋 文件列表" style="height: 100%">
|
||||
<a-list
|
||||
:data="fileList"
|
||||
:loading="fileLoading"
|
||||
:bordered="false"
|
||||
style="max-height: 500px; overflow-y: auto"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<a-list-item class="file-item">
|
||||
<a-list-item-meta>
|
||||
<template #title>
|
||||
<a-space>
|
||||
<span style="font-size: 18px">{{ item.is_dir ? '📁' : '📄' }}</span>
|
||||
<a @click="selectFile(item.path)" class="file-name">{{ item.name }}</a>
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
@click.stop="toggleFavorite(item)"
|
||||
:style="{ color: isFavorite(item.path) ? '#ffcd00' : '#86909c' }"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-star-fill v-if="isFavorite(item.path)" />
|
||||
<icon-star v-else />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #description>
|
||||
<a-space split="|">
|
||||
<span v-if="!item.is_dir">{{ formatBytes(item.size) }}</span>
|
||||
<span>{{ item.mod_time }}</span>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12">
|
||||
<a-card title="📝 文件内容">
|
||||
<a-space direction="vertical" :size="8" style="width: 100%">
|
||||
<div
|
||||
class="file-content-wrapper"
|
||||
:style="{ height: fileContentHeight + 'px' }"
|
||||
>
|
||||
<a-textarea
|
||||
v-model="fileContent"
|
||||
class="file-content-textarea"
|
||||
placeholder="文件内容将显示在这里,点击文件列表中的文件即可查看"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="resize-handle"
|
||||
@mousedown="startResize"
|
||||
title="拖拽调整高度"
|
||||
>
|
||||
<div class="resize-handle-bar"></div>
|
||||
</div>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="readFile" :loading="fileLoading">
|
||||
<template #icon>
|
||||
<icon-file />
|
||||
</template>
|
||||
读取
|
||||
</a-button>
|
||||
<a-button @click="writeFile" :loading="fileLoading">
|
||||
<template #icon>
|
||||
<icon-save />
|
||||
</template>
|
||||
保存
|
||||
</a-button>
|
||||
<a-button status="danger" @click="deleteFile" :loading="fileLoading">
|
||||
<template #icon>
|
||||
<icon-delete />
|
||||
</template>
|
||||
删除
|
||||
</a-button>
|
||||
<a-button @click="clearContent">
|
||||
<template #icon>
|
||||
<icon-eraser />
|
||||
</template>
|
||||
清空
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-space>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { Message, Modal } from '@arco-design/web-vue'
|
||||
import {
|
||||
IconFolder,
|
||||
IconList,
|
||||
IconForward,
|
||||
IconStarFill,
|
||||
IconStar,
|
||||
IconFile,
|
||||
IconSave,
|
||||
IconDelete,
|
||||
IconEraser
|
||||
} from '@arco-design/web-vue/es/icon'
|
||||
import {
|
||||
listDir,
|
||||
readFile as readFileApi,
|
||||
writeFile as writeFileApi,
|
||||
deletePath
|
||||
} from '@/api'
|
||||
|
||||
// localStorage 键名
|
||||
const STORAGE_KEYS = {
|
||||
FILE_PATH: 'filesystem-file-path',
|
||||
FILE_LIST: 'filesystem-file-list',
|
||||
FILE_CONTENT: 'filesystem-file-content',
|
||||
PATH_HISTORY: 'filesystem-path-history',
|
||||
FILE_CONTENT_HEIGHT: 'filesystem-file-content-height',
|
||||
FAVORITE_FILES: 'filesystem-favorite-files'
|
||||
}
|
||||
|
||||
// 常用路径快捷方式
|
||||
const commonPaths = computed(() => {
|
||||
const platform = window.navigator.platform
|
||||
if (platform.includes('Win')) {
|
||||
return [
|
||||
{ name: '桌面', path: `${process.env.USERPROFILE || ''}\\Desktop` },
|
||||
{ name: '文档', path: `${process.env.USERPROFILE || ''}\\Documents` },
|
||||
{ name: '下载', path: `${process.env.USERPROFILE || ''}\\Downloads` },
|
||||
{ name: 'C盘根目录', path: 'C:\\' },
|
||||
{ name: 'D盘根目录', path: 'D:\\' }
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{ name: '用户主目录', path: '~' },
|
||||
{ name: '桌面', path: '~/Desktop' },
|
||||
{ name: '文档', path: '~/Documents' },
|
||||
{ name: '下载', path: '~/Downloads' },
|
||||
{ name: '根目录', path: '/' }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
// 状态
|
||||
const filePath = ref('')
|
||||
const fileContent = ref('')
|
||||
const fileList = ref([])
|
||||
const fileLoading = ref(false)
|
||||
const pathHistory = ref([])
|
||||
const fileContentHeight = ref(250)
|
||||
const favoriteFiles = ref([])
|
||||
|
||||
// 格式化文件大小
|
||||
const formatBytes = (bytes) => {
|
||||
if (!bytes) return '0 B'
|
||||
const unit = 1024
|
||||
if (bytes < unit) return bytes + ' B'
|
||||
const exp = Math.floor(Math.log(bytes) / Math.log(unit))
|
||||
return (bytes / Math.pow(unit, exp)).toFixed(2) + ' ' + 'KMGTPE'[exp - 1] + 'B'
|
||||
}
|
||||
|
||||
// 列出目录
|
||||
const listDirectory = async () => {
|
||||
if (!filePath.value) {
|
||||
Message.error('请输入目录路径')
|
||||
return
|
||||
}
|
||||
|
||||
addToHistory(filePath.value)
|
||||
fileLoading.value = true
|
||||
try {
|
||||
fileList.value = await listDir(filePath.value)
|
||||
} catch (error) {
|
||||
console.error('列出目录失败:', error)
|
||||
Message.error('列出目录失败: ' + (error.message || error))
|
||||
} finally {
|
||||
fileLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 路径选择
|
||||
const onPathSelect = (value) => {
|
||||
filePath.value = value
|
||||
listDirectory()
|
||||
}
|
||||
|
||||
// 打开路径
|
||||
const openPath = (path) => {
|
||||
filePath.value = path
|
||||
listDirectory()
|
||||
}
|
||||
|
||||
// 浏览目录
|
||||
const browseDirectory = () => {
|
||||
const path = prompt('请输入目录路径(例如: C:\\Users 或 /home/user)')
|
||||
if (path) {
|
||||
filePath.value = path
|
||||
listDirectory()
|
||||
}
|
||||
}
|
||||
|
||||
// 选择文件
|
||||
const selectFile = (path) => {
|
||||
filePath.value = path
|
||||
addToHistory(path)
|
||||
}
|
||||
|
||||
// 读取文件
|
||||
const readFile = async () => {
|
||||
if (!filePath.value) {
|
||||
Message.error('请输入文件路径')
|
||||
return
|
||||
}
|
||||
|
||||
addToHistory(filePath.value)
|
||||
fileLoading.value = true
|
||||
try {
|
||||
fileContent.value = await readFileApi(filePath.value)
|
||||
Message.success('文件读取成功')
|
||||
} catch (error) {
|
||||
console.error('读取文件失败:', error)
|
||||
Message.error('读取文件失败: ' + (error.message || error))
|
||||
} finally {
|
||||
fileLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
const writeFile = async () => {
|
||||
if (!filePath.value) {
|
||||
Message.error('请输入文件路径')
|
||||
return
|
||||
}
|
||||
|
||||
fileLoading.value = true
|
||||
try {
|
||||
await writeFileApi(filePath.value, fileContent.value)
|
||||
Message.success('文件保存成功')
|
||||
} catch (error) {
|
||||
console.error('写入文件失败:', error)
|
||||
Message.error('写入文件失败: ' + (error.message || error))
|
||||
} finally {
|
||||
fileLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
const deleteFile = async () => {
|
||||
if (!filePath.value) {
|
||||
Message.error('请输入文件路径')
|
||||
return
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确定要删除 ${filePath.value} 吗?此操作不可恢复!`,
|
||||
onOk: async () => {
|
||||
fileLoading.value = true
|
||||
try {
|
||||
await deletePath(filePath.value)
|
||||
Message.success('删除成功')
|
||||
// 清空状态
|
||||
filePath.value = ''
|
||||
fileContent.value = ''
|
||||
fileList.value = []
|
||||
// 重新列出目录
|
||||
if (pathHistory.value.length > 0) {
|
||||
filePath.value = pathHistory.value[0]
|
||||
listDirectory()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error)
|
||||
Message.error('删除失败: ' + (error.message || error))
|
||||
} finally {
|
||||
fileLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 清空内容
|
||||
const clearContent = () => {
|
||||
fileContent.value = ''
|
||||
Message.info('内容已清空')
|
||||
}
|
||||
|
||||
// 拖拽调整高度
|
||||
const startResize = (e) => {
|
||||
const startY = e.clientY
|
||||
const startHeight = fileContentHeight.value
|
||||
|
||||
const onMouseMove = (moveEvent) => {
|
||||
const deltaY = moveEvent.clientY - startY
|
||||
const newHeight = startHeight + deltaY
|
||||
if (newHeight >= 150 && newHeight <= 800) {
|
||||
fileContentHeight.value = newHeight
|
||||
}
|
||||
}
|
||||
|
||||
const onMouseUp = () => {
|
||||
document.removeEventListener('mousemove', onMouseMove)
|
||||
document.removeEventListener('mouseup', onMouseUp)
|
||||
saveToStorage(STORAGE_KEYS.FILE_CONTENT_HEIGHT, fileContentHeight.value.toString())
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove)
|
||||
document.addEventListener('mouseup', onMouseUp)
|
||||
}
|
||||
|
||||
// 历史记录
|
||||
const addToHistory = (path) => {
|
||||
if (!path || path.trim() === '') return
|
||||
|
||||
const index = pathHistory.value.indexOf(path)
|
||||
if (index > -1) {
|
||||
pathHistory.value.splice(index, 1)
|
||||
}
|
||||
|
||||
pathHistory.value.unshift(path)
|
||||
if (pathHistory.value.length > 20) {
|
||||
pathHistory.value = pathHistory.value.slice(0, 20)
|
||||
}
|
||||
|
||||
saveToStorage(STORAGE_KEYS.PATH_HISTORY, pathHistory.value)
|
||||
}
|
||||
|
||||
// 收藏功能
|
||||
const isFavorite = (path) => {
|
||||
return favoriteFiles.value.some(fav => fav.path === path)
|
||||
}
|
||||
|
||||
const toggleFavorite = (item) => {
|
||||
const index = favoriteFiles.value.findIndex(fav => fav.path === item.path)
|
||||
|
||||
if (index > -1) {
|
||||
favoriteFiles.value.splice(index, 1)
|
||||
Message.info('已取消收藏: ' + item.name)
|
||||
} else {
|
||||
favoriteFiles.value.push({
|
||||
path: item.path,
|
||||
name: item.name,
|
||||
is_dir: item.is_dir
|
||||
})
|
||||
Message.success('已收藏: ' + item.name)
|
||||
}
|
||||
|
||||
saveToStorage(STORAGE_KEYS.FAVORITE_FILES, favoriteFiles.value)
|
||||
}
|
||||
|
||||
const removeFavorite = (path) => {
|
||||
const index = favoriteFiles.value.findIndex(fav => fav.path === path)
|
||||
if (index > -1) {
|
||||
const name = favoriteFiles.value[index].name
|
||||
favoriteFiles.value.splice(index, 1)
|
||||
saveToStorage(STORAGE_KEYS.FAVORITE_FILES, favoriteFiles.value)
|
||||
Message.info('已取消收藏: ' + name)
|
||||
}
|
||||
}
|
||||
|
||||
const openFavoriteFile = (path) => {
|
||||
filePath.value = path
|
||||
addToHistory(path)
|
||||
|
||||
const fav = favoriteFiles.value.find(f => f.path === path)
|
||||
if (fav && fav.is_dir) {
|
||||
listDirectory()
|
||||
} else {
|
||||
readFile()
|
||||
}
|
||||
}
|
||||
|
||||
// localStorage 操作
|
||||
const loadFromStorage = () => {
|
||||
try {
|
||||
const savedPath = localStorage.getItem(STORAGE_KEYS.FILE_PATH)
|
||||
const savedFileList = localStorage.getItem(STORAGE_KEYS.FILE_LIST)
|
||||
const savedFileContent = localStorage.getItem(STORAGE_KEYS.FILE_CONTENT)
|
||||
const savedHistory = localStorage.getItem(STORAGE_KEYS.PATH_HISTORY)
|
||||
const savedHeight = localStorage.getItem(STORAGE_KEYS.FILE_CONTENT_HEIGHT)
|
||||
const savedFavorites = localStorage.getItem(STORAGE_KEYS.FAVORITE_FILES)
|
||||
|
||||
if (savedPath) filePath.value = savedPath
|
||||
if (savedFileList) fileList.value = JSON.parse(savedFileList)
|
||||
if (savedFileContent) fileContent.value = savedFileContent
|
||||
if (savedHistory) pathHistory.value = JSON.parse(savedHistory)
|
||||
if (savedFavorites) favoriteFiles.value = JSON.parse(savedFavorites)
|
||||
if (savedHeight) {
|
||||
const height = parseInt(savedHeight)
|
||||
if (!isNaN(height) && height >= 150 && height <= 800) {
|
||||
fileContentHeight.value = height
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('从 localStorage 加载数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const saveToStorage = (key, value) => {
|
||||
try {
|
||||
if (typeof value === 'string') {
|
||||
localStorage.setItem(key, value)
|
||||
} else {
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存到 localStorage 失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听数据变化自动保存
|
||||
watch(filePath, (newPath) => {
|
||||
saveToStorage(STORAGE_KEYS.FILE_PATH, newPath)
|
||||
})
|
||||
|
||||
watch(fileContent, (newContent) => {
|
||||
saveToStorage(STORAGE_KEYS.FILE_CONTENT, newContent)
|
||||
})
|
||||
|
||||
watch(fileList, (newList) => {
|
||||
saveToStorage(STORAGE_KEYS.FILE_LIST, newList)
|
||||
}, { deep: true })
|
||||
|
||||
onMounted(() => {
|
||||
loadFromStorage()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.file-system {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
background: var(--color-fill-2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.file-content-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: height 0.1s ease;
|
||||
border: 1px solid var(--color-border-2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.file-content-textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
resize: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.resize-handle {
|
||||
height: 8px;
|
||||
cursor: ns-resize;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-fill-2);
|
||||
border-radius: 4px;
|
||||
margin: 4px 0;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.resize-handle:hover {
|
||||
background: var(--color-fill-3);
|
||||
}
|
||||
|
||||
.resize-handle-bar {
|
||||
width: 40px;
|
||||
height: 3px;
|
||||
background: var(--color-border-3);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.resize-handle:hover .resize-handle-bar {
|
||||
background: rgb(var(--primary-6));
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user