Private
Public Access
1
0

重构:Wails v3 迁移 + 前端目录规范化 + Sidebar滚动优化

- web/ → frontend/ 目录重命名(Wails v3 标准结构)
- main.go: Middleware 修复 custom.js 404 + DevTools 延迟启动
- Sidebar: 收藏夹内部独立滚动 + 帮助区块固定底部
- useFavorites.ts: longPressTimer const→let 修复 TypeError
- App.vue: Arco Tabs padding-top 覆盖
- build: config.yml / Taskfile.yml 对齐官方模板,devtools build tag
- 新增 v3 bindings、vite.config.js、跨平台构建配置

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 11:03:53 +08:00
parent 44847e0d40
commit f54bf1c28d
185 changed files with 7768 additions and 914 deletions

View File

@@ -0,0 +1,199 @@
/**
* 连接管理器 — 管理本地/远程传输层切换
*/
import type { FsTransport } from './transport'
import { WailsTransport } from './wails-transport'
import { HttpTransport } from './http-transport'
export type ConnectionType = 'local' | 'remote'
export interface ConnectionProfile {
id: string
name: string
host: string
port: number
token: string
type: ConnectionType
lastConnected?: number
}
export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error'
const PROFILES_KEY = 'fs_connection_profiles'
const ACTIVE_KEY = 'fs_active_connection'
class ConnectionManagerImpl {
private _transport: FsTransport | null = null
private _profiles: ConnectionProfile[] = []
private _activeId: string | null = null
private _state: ConnectionState = 'disconnected'
private _stateChangeCallbacks: ((state: ConnectionState) => void)[] = []
private _connectSeq = 0
constructor() {
this.loadProfiles()
this.initDefaultLocal()
}
private initDefaultLocal() {
const localProfile: ConnectionProfile = {
id: 'local-default',
name: '本地',
host: '',
port: 0,
token: '',
type: 'local',
}
if (!this._profiles.find(p => p.id === localProfile.id)) {
this._profiles.unshift(localProfile)
}
// 默认连接本地
if (!this._activeId) {
this._activeId = localProfile.id
}
this.applyActive()
}
private loadProfiles() {
try {
const raw = localStorage.getItem(PROFILES_KEY)
if (raw) this._profiles = JSON.parse(raw)
this._activeId = localStorage.getItem(ACTIVE_KEY)
} catch { /* 首次使用 */ }
}
private saveProfiles() {
localStorage.setItem(PROFILES_KEY, JSON.stringify(this._profiles))
if (this._activeId) {
localStorage.setItem(ACTIVE_KEY, this._activeId)
}
}
private setState(state: ConnectionState) {
this._state = state
this.notifyChange()
}
private notifyChange() {
this._stateChangeCallbacks.forEach(cb => cb(this._state))
}
onStateChange(cb: (state: ConnectionState) => void) {
this._stateChangeCallbacks.push(cb)
}
get state(): ConnectionState {
return this._state
}
get profiles(): ConnectionProfile[] {
return [...this._profiles]
}
get activeProfile(): ConnectionProfile | null {
return this._profiles.find(p => p.id === this._activeId) ?? null
}
getTransport(): FsTransport {
if (!this._transport) {
this.applyActive()
}
return this._transport!
}
getFileServerBaseURL(): string {
if (this._transport instanceof HttpTransport) {
const profile = this.activeProfile
if (!profile) return ''
const scheme = profile.port === 443 ? 'https' : 'http'
const port = (profile.port === 80 || profile.port === 443) ? '' : `:${profile.port}`
return `${scheme}://${profile.host}${port}`
}
// Wails 模式返回空字符串,让 useFilePreview 走原有逻辑
return ''
}
isRemote(): boolean {
return this.activeProfile?.type === 'remote'
}
connect(profileId: string): void {
const profile = this._profiles.find(p => p.id === profileId)
if (!profile) return
this._activeId = profileId
this.saveProfiles()
this.applyActive()
}
disconnect(): void {
this._activeId = 'local-default'
this.saveProfiles()
this.applyActive()
}
addProfile(profile: Omit<ConnectionProfile, 'id'>): ConnectionProfile {
const newProfile: ConnectionProfile = {
...profile,
id: crypto.randomUUID(),
}
this._profiles.push(newProfile)
this.saveProfiles()
this.notifyChange()
return newProfile
}
updateProfile(id: string, updates: Partial<ConnectionProfile>): void {
const idx = this._profiles.findIndex(p => p.id === id)
if (idx >= 0) {
this._profiles[idx] = { ...this._profiles[idx], ...updates }
this.saveProfiles()
// 仅当连接参数变化时重新应用(避免 lastConnected 等元数据更新触发死循环)
const REAPPLY_KEYS = ['host', 'port', 'token'] as const
const needsReapply = REAPPLY_KEYS.some(k => k in updates)
if (needsReapply && id === this._activeId) {
this.applyActive()
}
this.notifyChange()
}
}
removeProfile(id: string): void {
if (id === 'local-default') return // 不允许删除本地配置
this._profiles = this._profiles.filter(p => p.id !== id)
if (this._activeId === id) {
this._activeId = 'local-default'
}
this.saveProfiles()
this.applyActive()
this.notifyChange()
}
private applyActive() {
const profile = this.activeProfile
const seq = ++this._connectSeq
if (!profile || profile.type === 'local') {
this._transport = new WailsTransport()
this.setState('connected')
} else {
this.setState('connecting')
try {
this._transport = new HttpTransport(profile.host, profile.port, profile.token)
// 快速连通性检查(用轻量 ping 代替 getCommonPaths
this._transport.getFileInfo('/').then(() => {
if (seq !== this._connectSeq) return // 已被后续连接覆盖
this.setState('connected')
this.updateProfile(profile.id!, { lastConnected: Date.now() })
}).catch(() => {
if (seq !== this._connectSeq) return
this.setState('error')
})
} catch {
this.setState('error')
}
}
}
}
export const connectionManager = new ConnectionManagerImpl()