Private
Public Access
1
0

新增: SFTP直连+网站预览+OSS区域嗅探+热键+BGM播放

This commit is contained in:
2026-05-12 11:06:28 +08:00
parent 545d7a864d
commit 2a363fd729
62 changed files with 6687 additions and 660 deletions

View File

@@ -5,6 +5,7 @@
import { ref, computed, watch } from 'vue'
import { STORAGE_KEYS } from '@/utils/constants'
import { connectionManager } from '@/api/connection-manager'
import type { FileItem } from '@/types/file-system'
export interface PreviewTab {
@@ -12,6 +13,8 @@ export interface PreviewTab {
id: string
/** 文件信息 */
fileItem: FileItem
/** 所属连接 profileId */
profileId?: string
/** 缓存的预览 URL */
previewUrl: string
/** 缓存的文件内容 */
@@ -30,6 +33,7 @@ export interface PreviewTab {
interface PersistedTab {
path: string
active: boolean
profileId?: string
/** 未保存的内容(有修改时才存) */
unsavedContent?: string
originalContent?: string
@@ -48,11 +52,13 @@ interface UnsavedEntry {
interface RestoredSession {
paths: string[]
activePath: string | null
profileMap: Map<string, string | undefined>
unsavedMap: Map<string, UnsavedEntry>
}
function pathToId(path: string): string {
return path.replace(/\\/g, '/').toLowerCase()
const normalized = path.replace(/\\/g, '/')
return connectionManager.isRemote() ? normalized : normalized.toLowerCase()
}
export function isDirty(tab: PreviewTab): boolean {
@@ -69,9 +75,10 @@ export function useMultiPreview() {
})
/** 创建一个新 tab */
const createTab = (fileItem: FileItem): PreviewTab => ({
const createTab = (fileItem: FileItem, profileId?: string): PreviewTab => ({
id: pathToId(fileItem.path),
fileItem,
profileId,
previewUrl: '',
fileContent: '',
originalContent: '',
@@ -85,19 +92,21 @@ export function useMultiPreview() {
/** 从 localStorage 恢复会话 */
const restoreSession = (): RestoredSession => {
const unsavedMap = new Map<string, UnsavedEntry>()
const profileMap = new Map<string, string | undefined>()
let activePath: string | null = null
const paths: string[] = []
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return { paths, activePath, unsavedMap }
if (!raw) return { paths, activePath, profileMap, unsavedMap }
const persisted: PersistedTab[] = JSON.parse(raw)
if (!Array.isArray(persisted)) return { paths, activePath, unsavedMap }
if (!Array.isArray(persisted)) return { paths, activePath, profileMap, unsavedMap }
for (const p of persisted) {
if (!p.path) continue
paths.push(p.path)
profileMap.set(pathToId(p.path), p.profileId)
if (p.active) activePath = p.path
if (p.unsavedContent !== undefined) {
unsavedMap.set(pathToId(p.path), {
@@ -111,18 +120,19 @@ export function useMultiPreview() {
localStorage.removeItem(STORAGE_KEY)
}
return { paths, activePath, unsavedMap }
return { paths, activePath, profileMap, unsavedMap }
}
/** 保存会话到 localStorage */
const persistSession = () => {
const persisted: PersistedTab[] = tabs.value.map(tab => {
const hasUnsaved = tab.fileContent && tab.originalContent !== undefined && tab.fileContent !== tab.originalContent
const hasUnsaved = isDirty(tab)
// 限制存储大小,超过 100KB 的内容不存入 localStorage
const canSave = hasUnsaved && tab.fileContent.length <= 100_000
return {
path: tab.fileItem.path,
active: tab.id === activeTabId.value,
profileId: tab.profileId,
unsavedContent: canSave ? tab.fileContent : undefined,
originalContent: canSave ? tab.originalContent : undefined,
isEditMode: canSave ? tab.isEditMode : undefined
@@ -148,7 +158,7 @@ export function useMultiPreview() {
}
/** 添加或激活 tab返回 { tab, isNew } */
const addTab = (fileItem: FileItem): { tab: PreviewTab; isNew: boolean } => {
const addTab = (fileItem: FileItem, profileId?: string): { tab: PreviewTab; isNew: boolean } => {
const id = pathToId(fileItem.path)
const existing = tabs.value.find(t => t.id === id)
if (existing) {
@@ -162,7 +172,7 @@ export function useMultiPreview() {
if (victimIdx !== -1) tabs.value.splice(victimIdx, 1)
}
const tab = createTab(fileItem)
const tab = createTab(fileItem, profileId)
tabs.value.push(tab)
activeTabId.value = tab.id
return { tab, isNew: true }