优化:Sidebar服务器状态区块+布局重构+连接对话框优化+gitignore更新
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,4 +3,6 @@ bin
|
|||||||
frontend/dist
|
frontend/dist
|
||||||
frontend/node_modules
|
frontend/node_modules
|
||||||
build/linux/appimage/build
|
build/linux/appimage/build
|
||||||
build/windows/nsis/MicrosoftEdgeWebview2Setup.exe
|
build/windows/nsis/MicrosoftEdgeWebview2Setup.exe
|
||||||
|
.idea/
|
||||||
|
.claude/
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-modal :visible="props.visible" :title="editingId ? '编辑连接' : '添加服务器'" unmount-on-close @cancel="emit('update:visible', false)" @before-ok="handleOk" :ok-loading="submitting">
|
<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 style="display: flex; flex-direction: column; gap: 10px; max-width: 400px">
|
||||||
<div>
|
<div style="display: flex; align-items: center; gap: 8px">
|
||||||
<div style="margin-bottom: 4px; font-size: 14px">名称</div>
|
<label style="font-size: 13px; width: 36px; flex-shrink: 0">名称</label>
|
||||||
<a-input v-model="form.name" placeholder="如:生产服务器" />
|
<a-input v-model="form.name" placeholder="如:生产服务器" style="flex: 1" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div style="display: flex; align-items: center; gap: 8px">
|
||||||
<div style="margin-bottom: 4px; font-size: 14px">地址</div>
|
<label style="font-size: 13px; width: 36px; flex-shrink: 0">地址</label>
|
||||||
<a-input v-model="form.host" placeholder="192.168.1.100" />
|
<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>
|
<div style="display: flex; align-items: center; gap: 8px">
|
||||||
<div style="margin-bottom: 4px; font-size: 14px">端口</div>
|
<label style="font-size: 13px; width: 36px; flex-shrink: 0">Token</label>
|
||||||
<a-input-number v-model="form.port" :min="1" :max="65535" placeholder="9876" style="width: 100%" />
|
<div style="flex: 1">
|
||||||
</div>
|
<a-input v-model="form.token" type="password" placeholder="留空则不认证" allow-clear />
|
||||||
<div>
|
<div style="font-size: 11px; color: var(--color-text-3); margin-top: 2px">API 认证令牌(与服务器配置一致)</div>
|
||||||
<div style="margin-bottom: 4px; font-size: 14px">
|
|
||||||
Token <span style="color: var(--color-text-3); font-size: 12px">API 认证令牌(与服务器配置一致)</span>
|
|
||||||
</div>
|
</div>
|
||||||
<a-input v-model="form.token" type="password" placeholder="留空则不认证" allow-clear />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|||||||
@@ -154,12 +154,12 @@ function handleDelete(p: { id: string; name: string }) {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot.connected { background: rgb(var(--green-6)); }
|
.dot.connected { background: var(--color-success-6); }
|
||||||
.dot.connecting { background: #f5a623; animation: pulse 1.5s infinite; }
|
.dot.connecting { background: var(--color-warning-6); animation: pulse 1.5s infinite; }
|
||||||
.dot.disconnected { background: var(--color-danger-6); }
|
.dot.disconnected { background: var(--color-danger-6); }
|
||||||
.dot.error { background: var(--color-danger-6); }
|
.dot.error { background: var(--color-danger-6); }
|
||||||
.dot.local { background: var(--color-text-3); }
|
.dot.local { background: var(--color-text-3); }
|
||||||
.dot.remote { background: #165dff; }
|
.dot.remote { background: var(--color-primary-6); }
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
max-width: 70px;
|
max-width: 70px;
|
||||||
|
|||||||
@@ -1,6 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="slide">
|
<transition name="slide">
|
||||||
<div v-show="config.visible" class="sidebar">
|
<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="sidebar-section">
|
||||||
<div class="section-header" @click="favCollapsed = !favCollapsed">
|
<div class="section-header" @click="favCollapsed = !favCollapsed">
|
||||||
@@ -9,7 +52,7 @@
|
|||||||
<icon-down v-if="!favCollapsed" class="section-toggle" />
|
<icon-down v-if="!favCollapsed" class="section-toggle" />
|
||||||
<icon-right v-else class="section-toggle" />
|
<icon-right v-else class="section-toggle" />
|
||||||
</div>
|
</div>
|
||||||
<div class="section-content" :class="{ collapsed: favCollapsed }">
|
<div class="section-content fav-content" :class="{ collapsed: favCollapsed }">
|
||||||
<div
|
<div
|
||||||
v-for="(fav, index) in config.favoriteFiles"
|
v-for="(fav, index) in config.favoriteFiles"
|
||||||
:key="fav.path"
|
:key="fav.path"
|
||||||
@@ -87,6 +130,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import type { SidebarConfig, FavoriteFile } from '@/types/file-system'
|
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
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -96,9 +142,32 @@ interface Props {
|
|||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
// 折叠状态(组件内部,不污染父组件)
|
// 折叠状态(组件内部,不污染父组件)
|
||||||
|
const serverCollapsed = ref(false)
|
||||||
const favCollapsed = ref(false)
|
const favCollapsed = ref(false)
|
||||||
const helpCollapsed = 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(() => {
|
const pinnedIndices = computed(() => {
|
||||||
return props.config.favoriteFiles
|
return props.config.favoriteFiles
|
||||||
@@ -129,14 +198,11 @@ interface Emits {
|
|||||||
(e: 'dragOver', event: DragEvent): void
|
(e: 'dragOver', event: DragEvent): void
|
||||||
(e: 'drop', event: DragEvent, targetIndex: number): void
|
(e: 'drop', event: DragEvent, targetIndex: number): void
|
||||||
(e: 'dragEnd'): void
|
(e: 'dragEnd'): void
|
||||||
|
(e: 'openConnectionDialog'): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<Emits>()
|
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) => {
|
const handleOpenFavorite = (file: FavoriteFile) => {
|
||||||
emit('openFavorite', file)
|
emit('openFavorite', file)
|
||||||
@@ -173,6 +239,20 @@ const handleDrop = (event: DragEvent, targetIndex: number) => {
|
|||||||
const handleDragEnd = () => {
|
const handleDragEnd = () => {
|
||||||
emit('dragEnd')
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -248,7 +328,7 @@ const handleDragEnd = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 收藏夹内容 - 内部独立滚动 */
|
/* 收藏夹内容 - 内部独立滚动 */
|
||||||
.section-content:not(.help-content) {
|
.fav-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -283,6 +363,40 @@ const handleDragEnd = () => {
|
|||||||
color: var(--color-text-3);
|
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 {
|
.sidebar-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ import ConnectionDialog from './ConnectionDialog.vue'
|
|||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
config: ToolbarConfig
|
config: ToolbarConfig
|
||||||
|
openConnectionDialog?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
@@ -170,6 +171,9 @@ const emit = defineEmits<Emits>()
|
|||||||
// 连接对话框
|
// 连接对话框
|
||||||
const showConnectionDialog = ref(false)
|
const showConnectionDialog = ref(false)
|
||||||
const connectionDialogRef = ref<InstanceType<typeof ConnectionDialog>>()
|
const connectionDialogRef = ref<InstanceType<typeof ConnectionDialog>>()
|
||||||
|
|
||||||
|
watch(() => props.openConnectionDialog, (v) => { if (v > 0) showConnectionDialog.value = true })
|
||||||
|
|
||||||
const onConnectionChanged = async (_id: string) => {
|
const onConnectionChanged = async (_id: string) => {
|
||||||
emit('connectionChanged')
|
emit('connectionChanged')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="file-system-container">
|
<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">
|
<div class="main-content">
|
||||||
<!-- 左侧收藏夹侧边栏 -->
|
<!-- 左侧收藏夹侧边栏(全高) -->
|
||||||
<Sidebar
|
<Sidebar
|
||||||
v-if="showSidebar"
|
v-if="showSidebar"
|
||||||
:config="sidebarConfig"
|
:config="sidebarConfig"
|
||||||
@@ -31,10 +15,30 @@
|
|||||||
@drag-over="handleDragOver"
|
@drag-over="handleDragOver"
|
||||||
@drop="handleDrop"
|
@drop="handleDrop"
|
||||||
@drag-end="handleDragEnd"
|
@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
|
<FileListPanel
|
||||||
:config="fileListPanelConfig"
|
:config="fileListPanelConfig"
|
||||||
@@ -72,6 +76,7 @@
|
|||||||
@open-local-file="handleOpenLocalFile"
|
@open-local-file="handleOpenLocalFile"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
@@ -156,6 +161,7 @@ const fileList = ref<FileItem[]>([])
|
|||||||
const fileLoading = ref(false)
|
const fileLoading = ref(false)
|
||||||
const selectedFileItem = ref<FileItem | null>(null)
|
const selectedFileItem = ref<FileItem | null>(null)
|
||||||
const toolbarRef = ref<InstanceType<typeof import('./components/Toolbar.vue').default> | null>(null)
|
const toolbarRef = ref<InstanceType<typeof import('./components/Toolbar.vue').default> | null>(null)
|
||||||
|
const triggerConnectionDialog = ref(0)
|
||||||
|
|
||||||
// 排序状态(带 localStorage 持久化)
|
// 排序状态(带 localStorage 持久化)
|
||||||
const SORT_STORAGE_KEY = STORAGE_KEYS.FILESYSTEM.SORT
|
const SORT_STORAGE_KEY = STORAGE_KEYS.FILESYSTEM.SORT
|
||||||
@@ -1555,6 +1561,14 @@ watch(() => themeStore.isDark, async () => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workspace-area {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.file-workspace {
|
.file-workspace {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -77,7 +77,7 @@ func main() {
|
|||||||
// TODO: 替换为 OnDomReady 回调,当前 alpha.80 可能未稳定支持
|
// TODO: 替换为 OnDomReady 回调,当前 alpha.80 可能未稳定支持
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
_ = window.OpenDevTools()
|
window.OpenDevTools()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := wailsApp.Run(); err != nil {
|
if err := wailsApp.Run(); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user