优化:Sidebar服务器状态区块+布局重构+连接对话框优化+gitignore更新
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,4 +3,6 @@ bin
|
||||
frontend/dist
|
||||
frontend/node_modules
|
||||
build/linux/appimage/build
|
||||
build/windows/nsis/MicrosoftEdgeWebview2Setup.exe
|
||||
build/windows/nsis/MicrosoftEdgeWebview2Setup.exe
|
||||
.idea/
|
||||
.claude/
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
<template>
|
||||
<a-modal :visible="props.visible" :title="editingId ? '编辑连接' : '添加服务器'" unmount-on-close @cancel="emit('update:visible', false)" @before-ok="handleOk" :ok-loading="submitting">
|
||||
<div style="display: flex; flex-direction: column; gap: 16px; max-width: 400px">
|
||||
<div>
|
||||
<div style="margin-bottom: 4px; font-size: 14px">名称</div>
|
||||
<a-input v-model="form.name" placeholder="如:生产服务器" />
|
||||
<div style="display: flex; flex-direction: column; gap: 10px; max-width: 400px">
|
||||
<div style="display: flex; align-items: center; gap: 8px">
|
||||
<label style="font-size: 13px; width: 36px; flex-shrink: 0">名称</label>
|
||||
<a-input v-model="form.name" placeholder="如:生产服务器" style="flex: 1" />
|
||||
</div>
|
||||
<div>
|
||||
<div style="margin-bottom: 4px; font-size: 14px">地址</div>
|
||||
<a-input v-model="form.host" placeholder="192.168.1.100" />
|
||||
<div style="display: flex; align-items: center; gap: 8px">
|
||||
<label style="font-size: 13px; width: 36px; flex-shrink: 0">地址</label>
|
||||
<a-input v-model="form.host" placeholder="192.168.1.100" style="flex: 1" />
|
||||
<a-input-number v-model="form.port" :min="1" :max="65535" placeholder="端口" style="width: 90px" hide-button />
|
||||
</div>
|
||||
<div>
|
||||
<div style="margin-bottom: 4px; font-size: 14px">端口</div>
|
||||
<a-input-number v-model="form.port" :min="1" :max="65535" placeholder="9876" style="width: 100%" />
|
||||
</div>
|
||||
<div>
|
||||
<div style="margin-bottom: 4px; font-size: 14px">
|
||||
Token <span style="color: var(--color-text-3); font-size: 12px">API 认证令牌(与服务器配置一致)</span>
|
||||
<div style="display: flex; align-items: center; gap: 8px">
|
||||
<label style="font-size: 13px; width: 36px; flex-shrink: 0">Token</label>
|
||||
<div style="flex: 1">
|
||||
<a-input v-model="form.token" type="password" placeholder="留空则不认证" allow-clear />
|
||||
<div style="font-size: 11px; color: var(--color-text-3); margin-top: 2px">API 认证令牌(与服务器配置一致)</div>
|
||||
</div>
|
||||
<a-input v-model="form.token" type="password" placeholder="留空则不认证" allow-clear />
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
@@ -154,12 +154,12 @@ function handleDelete(p: { id: string; name: string }) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dot.connected { background: rgb(var(--green-6)); }
|
||||
.dot.connecting { background: #f5a623; animation: pulse 1.5s infinite; }
|
||||
.dot.connected { background: var(--color-success-6); }
|
||||
.dot.connecting { background: var(--color-warning-6); animation: pulse 1.5s infinite; }
|
||||
.dot.disconnected { background: var(--color-danger-6); }
|
||||
.dot.error { background: var(--color-danger-6); }
|
||||
.dot.local { background: var(--color-text-3); }
|
||||
.dot.remote { background: #165dff; }
|
||||
.dot.remote { background: var(--color-primary-6); }
|
||||
|
||||
.label {
|
||||
max-width: 70px;
|
||||
|
||||
@@ -1,6 +1,49 @@
|
||||
<template>
|
||||
<transition name="slide">
|
||||
<div v-show="config.visible" class="sidebar">
|
||||
<!-- 服务器区块 -->
|
||||
<div class="sidebar-section">
|
||||
<div class="section-header" @click="serverCollapsed = !serverCollapsed">
|
||||
<span class="section-title">🖥️ 服务器</span>
|
||||
<a-tag :color="statusTagColor" size="small">{{ statusLabel }}</a-tag>
|
||||
<icon-down v-if="!serverCollapsed" class="section-toggle" />
|
||||
<icon-right v-else class="section-toggle" />
|
||||
</div>
|
||||
<div class="section-content server-content" :class="{ collapsed: serverCollapsed }">
|
||||
<div class="server-info">
|
||||
<div class="server-row">
|
||||
<span class="server-label">模式</span>
|
||||
<a-tag :color="isRemote ? 'blue' : 'green'" size="small">{{ isRemote ? '远程' : '本地' }}</a-tag>
|
||||
</div>
|
||||
<div v-if="activeProfile" class="server-row">
|
||||
<span class="server-label">服务器</span>
|
||||
<span class="server-val">{{ activeProfile.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="server-actions">
|
||||
<a-button
|
||||
v-if="!isRemote"
|
||||
type="outline"
|
||||
size="mini"
|
||||
long
|
||||
@click.stop="handleConnectRemote"
|
||||
>
|
||||
连接远程
|
||||
</a-button>
|
||||
<a-button
|
||||
v-else
|
||||
type="outline"
|
||||
status="danger"
|
||||
size="mini"
|
||||
long
|
||||
@click.stop="handleDisconnect"
|
||||
>
|
||||
断开连接
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 收藏夹区块 -->
|
||||
<div class="sidebar-section">
|
||||
<div class="section-header" @click="favCollapsed = !favCollapsed">
|
||||
@@ -9,7 +52,7 @@
|
||||
<icon-down v-if="!favCollapsed" class="section-toggle" />
|
||||
<icon-right v-else class="section-toggle" />
|
||||
</div>
|
||||
<div class="section-content" :class="{ collapsed: favCollapsed }">
|
||||
<div class="section-content fav-content" :class="{ collapsed: favCollapsed }">
|
||||
<div
|
||||
v-for="(fav, index) in config.favoriteFiles"
|
||||
:key="fav.path"
|
||||
@@ -87,6 +130,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import type { SidebarConfig, FavoriteFile } from '@/types/file-system'
|
||||
import { connectionManager } from '@/api/connection-manager'
|
||||
import { IconStar, IconClose, IconPushpin, IconDown, IconRight } from '@arco-design/web-vue/es/icon'
|
||||
import { getFileIcon } from '@/utils/fileUtils'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
@@ -96,9 +142,32 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// 折叠状态(组件内部,不污染父组件)
|
||||
const serverCollapsed = ref(false)
|
||||
const favCollapsed = ref(false)
|
||||
const helpCollapsed = ref(false)
|
||||
|
||||
// 服务器响应式状态(connectionManager 非响应式,需手动桥接)
|
||||
const connState = ref(connectionManager.state)
|
||||
const isRemote = ref(connectionManager.isRemote())
|
||||
const activeProfile = ref(connectionManager.activeProfile)
|
||||
|
||||
connectionManager.onStateChange(() => {
|
||||
connState.value = connectionManager.state
|
||||
isRemote.value = connectionManager.isRemote()
|
||||
activeProfile.value = connectionManager.activeProfile
|
||||
})
|
||||
|
||||
const statusMap: Record<string, string> = {
|
||||
connecting: '连接中...',
|
||||
connected: '已连接',
|
||||
error: '异常',
|
||||
}
|
||||
const statusLabel = computed(() => statusMap[connState.value] || connState.value)
|
||||
const statusTagColor = computed(() => {
|
||||
const map: Record<string, string> = { connecting: 'orangered', connected: 'blue', error: 'red' }
|
||||
return map[connState.value] || 'gray'
|
||||
})
|
||||
|
||||
// 计算第一个和最后一个置顶项的索引
|
||||
const pinnedIndices = computed(() => {
|
||||
return props.config.favoriteFiles
|
||||
@@ -129,14 +198,11 @@ interface Emits {
|
||||
(e: 'dragOver', event: DragEvent): void
|
||||
(e: 'drop', event: DragEvent, targetIndex: number): void
|
||||
(e: 'dragEnd'): void
|
||||
(e: 'openConnectionDialog'): void
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
// 图标导入
|
||||
import { IconStar, IconClose, IconPushpin, IconDown, IconRight } from '@arco-design/web-vue/es/icon'
|
||||
import { getFileIcon } from '@/utils/fileUtils'
|
||||
|
||||
// 事件处理
|
||||
const handleOpenFavorite = (file: FavoriteFile) => {
|
||||
emit('openFavorite', file)
|
||||
@@ -173,6 +239,20 @@ const handleDrop = (event: DragEvent, targetIndex: number) => {
|
||||
const handleDragEnd = () => {
|
||||
emit('dragEnd')
|
||||
}
|
||||
|
||||
// 服务器操作
|
||||
const handleConnectRemote = () => {
|
||||
const remote = connectionManager.profiles.find(p => p.type === 'remote')
|
||||
if (remote) {
|
||||
connectionManager.connect(remote.id)
|
||||
} else {
|
||||
emit('openConnectionDialog')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDisconnect = () => {
|
||||
connectionManager.disconnect()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -248,7 +328,7 @@ const handleDragEnd = () => {
|
||||
}
|
||||
|
||||
/* 收藏夹内容 - 内部独立滚动 */
|
||||
.section-content:not(.help-content) {
|
||||
.fav-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
@@ -283,6 +363,40 @@ const handleDragEnd = () => {
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
/* 服务器内容 */
|
||||
.server-content {
|
||||
padding: 8px 12px;
|
||||
background: var(--color-fill-1);
|
||||
border-radius: 0 0 6px 6px;
|
||||
}
|
||||
|
||||
.server-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.server-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.server-label {
|
||||
color: var(--color-text-3);
|
||||
min-width: 42px;
|
||||
}
|
||||
|
||||
.server-val {
|
||||
color: var(--color-text-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
.server-actions :deep(.arco-btn) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 收藏项 */
|
||||
.sidebar-item {
|
||||
display: flex;
|
||||
|
||||
@@ -148,6 +148,7 @@ import ConnectionDialog from './ConnectionDialog.vue'
|
||||
// Props
|
||||
interface Props {
|
||||
config: ToolbarConfig
|
||||
openConnectionDialog?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -170,6 +171,9 @@ const emit = defineEmits<Emits>()
|
||||
// 连接对话框
|
||||
const showConnectionDialog = ref(false)
|
||||
const connectionDialogRef = ref<InstanceType<typeof ConnectionDialog>>()
|
||||
|
||||
watch(() => props.openConnectionDialog, (v) => { if (v > 0) showConnectionDialog.value = true })
|
||||
|
||||
const onConnectionChanged = async (_id: string) => {
|
||||
emit('connectionChanged')
|
||||
}
|
||||
|
||||
@@ -1,24 +1,8 @@
|
||||
<template>
|
||||
<div class="file-system-container">
|
||||
<!-- 顶部工具栏 -->
|
||||
<Toolbar
|
||||
ref="toolbarRef"
|
||||
:config="toolbarConfig"
|
||||
@update:file-path="handleFilePathUpdate"
|
||||
@update:show-sidebar="handleSidebarToggle"
|
||||
@refresh="handleRefresh"
|
||||
@exit-zip="handleExitZip"
|
||||
@go-to-path="handleGoToPath"
|
||||
@open-file="handleOpenFile"
|
||||
@navigate-to-zip-directory="handleNavigateToZipDirectory"
|
||||
@update:search-keyword="handleSearchKeywordUpdate"
|
||||
@show-message="handleShowMessage"
|
||||
@connection-changed="handleConnectionChanged"
|
||||
/>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<!-- 主内容区:左侧边栏 + 右侧工作区 -->
|
||||
<div class="main-content">
|
||||
<!-- 左侧收藏夹侧边栏 -->
|
||||
<!-- 左侧收藏夹侧边栏(全高) -->
|
||||
<Sidebar
|
||||
v-if="showSidebar"
|
||||
:config="sidebarConfig"
|
||||
@@ -31,10 +15,30 @@
|
||||
@drag-over="handleDragOver"
|
||||
@drop="handleDrop"
|
||||
@drag-end="handleDragEnd"
|
||||
@open-connection-dialog="triggerConnectionDialog++"
|
||||
/>
|
||||
|
||||
<!-- 文件列表和编辑器区域 -->
|
||||
<div ref="workspaceRef" class="file-workspace">
|
||||
<!-- 右侧工作区:面包屑工具栏 + 文件列表/编辑器 -->
|
||||
<div class="workspace-area">
|
||||
<!-- 面包屑导航工具栏 -->
|
||||
<Toolbar
|
||||
ref="toolbarRef"
|
||||
:config="toolbarConfig"
|
||||
:open-connection-dialog="triggerConnectionDialog"
|
||||
@update:file-path="handleFilePathUpdate"
|
||||
@update:show-sidebar="handleSidebarToggle"
|
||||
@refresh="handleRefresh"
|
||||
@exit-zip="handleExitZip"
|
||||
@go-to-path="handleGoToPath"
|
||||
@open-file="handleOpenFile"
|
||||
@navigate-to-zip-directory="handleNavigateToZipDirectory"
|
||||
@update:search-keyword="handleSearchKeywordUpdate"
|
||||
@show-message="handleShowMessage"
|
||||
@connection-changed="handleConnectionChanged"
|
||||
/>
|
||||
|
||||
<!-- 文件列表和编辑器区域 -->
|
||||
<div ref="workspaceRef" class="file-workspace">
|
||||
<!-- 文件列表面板 -->
|
||||
<FileListPanel
|
||||
:config="fileListPanelConfig"
|
||||
@@ -72,6 +76,7 @@
|
||||
@open-local-file="handleOpenLocalFile"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
@@ -156,6 +161,7 @@ const fileList = ref<FileItem[]>([])
|
||||
const fileLoading = ref(false)
|
||||
const selectedFileItem = ref<FileItem | null>(null)
|
||||
const toolbarRef = ref<InstanceType<typeof import('./components/Toolbar.vue').default> | null>(null)
|
||||
const triggerConnectionDialog = ref(0)
|
||||
|
||||
// 排序状态(带 localStorage 持久化)
|
||||
const SORT_STORAGE_KEY = STORAGE_KEYS.FILESYSTEM.SORT
|
||||
@@ -1555,6 +1561,14 @@ watch(() => themeStore.isDark, async () => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.workspace-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.file-workspace {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user