新增: SFTP直连+网站预览+OSS区域嗅探+热键+BGM播放
This commit is contained in:
@@ -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 }
|
||||
|
||||
Reference in New Issue
Block a user