diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 223d937..1a3bf4b 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -16,6 +16,8 @@ declare module 'vue' { MemoryMonitor: typeof import('./src/components/monitor/MemoryMonitor.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + TabContainer: typeof import('./src/components/tabs/TabContainer.vue')['default'] + TabItem: typeof import('./src/components/tabs/TabItem.vue')['default'] Toolbar: typeof import('./src/components/toolbar/Toolbar.vue')['default'] WindowsTitleBar: typeof import('./src/components/titlebar/WindowsTitleBar.vue')['default'] WindowTitleBar: typeof import('./src/components/titlebar/WindowTitleBar.vue')['default'] diff --git a/frontend/src/common/async-operation/index.ts b/frontend/src/common/async-operation/index.ts new file mode 100644 index 0000000..a565f44 --- /dev/null +++ b/frontend/src/common/async-operation/index.ts @@ -0,0 +1,3 @@ +export { AsyncOperationManager } from './manager'; +export * from './types'; +export { AsyncOperationManager as default } from './manager'; \ No newline at end of file diff --git a/frontend/src/common/async-operation/manager.ts b/frontend/src/common/async-operation/manager.ts new file mode 100644 index 0000000..ff7c651 --- /dev/null +++ b/frontend/src/common/async-operation/manager.ts @@ -0,0 +1,296 @@ +import { + OperationStatus, + OperationInfo, + AsyncOperationManagerConfig, + OperationCallbacks, + OperationExecutor, + OperationResult +} from './types'; + +/** + * 异步操作管理器 + * 用于控制异步操作的竞态条件,确保操作的正确性和一致性 + */ +export class AsyncOperationManager { + private operationSequence = 0; + private pendingOperations = new Map(); + private currentResourceOperation = new Map(); + private config: Required; + private callbacks: OperationCallbacks; + + constructor( + config: AsyncOperationManagerConfig = {}, + callbacks: OperationCallbacks = {} + ) { + this.config = { + timeout: 0, + autoCleanup: true, + maxConcurrent: 0, + debug: false, + ...config + }; + this.callbacks = callbacks; + } + + /** + * 生成新的操作ID + */ + private getNextOperationId(): number { + return ++this.operationSequence; + } + + /** + * 记录日志(调试模式下) + */ + private log(message: string, ...args: any[]): void { + if (this.config.debug) { + console.log(`[AsyncOperationManager] ${message}`, ...args); + } + } + + /** + * 创建操作信息 + */ + private createOperation( + resourceId: string | number, + type?: string + ): OperationInfo { + const operation: OperationInfo = { + id: this.getNextOperationId(), + resourceId, + status: OperationStatus.PENDING, + abortController: new AbortController(), + createdAt: Date.now(), + type + }; + + this.log(`Created operation ${operation.id} for resource ${resourceId}`, operation); + return operation; + } + + /** + * 清理已完成的操作 + */ + private cleanupCompletedOperations(): void { + if (!this.config.autoCleanup) return; + + const completedStatuses = [ + OperationStatus.COMPLETED, + OperationStatus.CANCELLED, + OperationStatus.FAILED + ]; + + for (const [id, operation] of this.pendingOperations.entries()) { + if (completedStatuses.includes(operation.status)) { + this.pendingOperations.delete(id); + this.log(`Cleaned up operation ${id}`); + } + } + } + + /** + * 取消指定资源的所有操作(除了指定的操作ID) + */ + public cancelResourceOperations( + resourceId: string | number, + excludeOperationId?: number + ): void { + this.log(`Cancelling operations for resource ${resourceId}, exclude: ${excludeOperationId}`); + + for (const [id, operation] of this.pendingOperations.entries()) { + if ( + operation.resourceId === resourceId && + id !== excludeOperationId && + operation.status === OperationStatus.RUNNING + ) { + this.cancelOperation(id); + } + } + } + + /** + * 取消指定操作 + */ + public cancelOperation(operationId: number): boolean { + const operation = this.pendingOperations.get(operationId); + if (!operation) return false; + + if (operation.status === OperationStatus.RUNNING) { + operation.abortController.abort(); + operation.status = OperationStatus.CANCELLED; + this.log(`Cancelled operation ${operationId}`); + + this.callbacks.onCancel?.(operation); + this.cleanupCompletedOperations(); + return true; + } + + return false; + } + + /** + * 取消所有操作 + */ + public cancelAllOperations(): void { + this.log('Cancelling all operations'); + + for (const [id] of this.pendingOperations.entries()) { + this.cancelOperation(id); + } + + this.currentResourceOperation.clear(); + } + + /** + * 检查操作是否仍然有效 + */ + public isOperationValid( + operationId: number, + resourceId?: string | number + ): boolean { + const operation = this.pendingOperations.get(operationId); + + if (!operation) return false; + if (operation.abortController.signal.aborted) return false; + if (operation.status !== OperationStatus.RUNNING) return false; + + // 如果指定了资源ID,检查是否为当前资源的活跃操作 + if (resourceId !== undefined) { + const currentOperationId = this.currentResourceOperation.get(resourceId); + if (currentOperationId !== operationId) return false; + } + + return true; + } + + /** + * 执行异步操作 + */ + public async executeOperation( + resourceId: string | number, + executor: OperationExecutor, + operationType?: string + ): Promise> { + // 检查并发限制 + if (this.config.maxConcurrent > 0) { + const runningCount = Array.from(this.pendingOperations.values()) + .filter(op => op.status === OperationStatus.RUNNING).length; + + if (runningCount >= this.config.maxConcurrent) { + throw new Error(`Maximum concurrent operations limit reached: ${this.config.maxConcurrent}`); + } + } + + const operation = this.createOperation(resourceId, operationType); + + try { + // 取消同一资源的其他操作 + this.cancelResourceOperations(resourceId, operation.id); + + // 设置当前资源的活跃操作 + this.currentResourceOperation.set(resourceId, operation.id); + + // 添加到待处理操作列表 + this.pendingOperations.set(operation.id, operation); + + // 设置超时 + if (this.config.timeout > 0) { + setTimeout(() => { + if (this.isOperationValid(operation.id)) { + this.cancelOperation(operation.id); + } + }, this.config.timeout); + } + + // 更新状态为运行中 + operation.status = OperationStatus.RUNNING; + this.callbacks.onStart?.(operation); + this.log(`Started operation ${operation.id} for resource ${resourceId}`); + + // 执行操作 + const result = await executor(operation.abortController.signal, operation.id); + + // 检查操作是否仍然有效 + if (!this.isOperationValid(operation.id, resourceId)) { + throw new Error('Operation was cancelled'); + } + + // 操作成功完成 + operation.status = OperationStatus.COMPLETED; + this.callbacks.onComplete?.(operation); + this.log(`Completed operation ${operation.id}`); + + this.cleanupCompletedOperations(); + + return { + success: true, + data: result, + operation + }; + + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + + if (operation.abortController.signal.aborted) { + operation.status = OperationStatus.CANCELLED; + this.log(`Operation ${operation.id} was cancelled`); + } else { + operation.status = OperationStatus.FAILED; + this.callbacks.onError?.(operation, err); + this.log(`Operation ${operation.id} failed:`, err.message); + } + + this.cleanupCompletedOperations(); + + return { + success: false, + error: err, + operation + }; + } finally { + // 清理当前资源操作记录 + if (this.currentResourceOperation.get(resourceId) === operation.id) { + this.currentResourceOperation.delete(resourceId); + } + } + } + + /** + * 获取操作信息 + */ + public getOperation(operationId: number): OperationInfo | undefined { + return this.pendingOperations.get(operationId); + } + + /** + * 获取资源的当前操作ID + */ + public getCurrentOperationId(resourceId: string | number): number | undefined { + return this.currentResourceOperation.get(resourceId); + } + + /** + * 获取所有待处理操作 + */ + public getPendingOperations(): OperationInfo[] { + return Array.from(this.pendingOperations.values()); + } + + /** + * 获取运行中的操作数量 + */ + public getRunningOperationsCount(): number { + return Array.from(this.pendingOperations.values()) + .filter(op => op.status === OperationStatus.RUNNING).length; + } + + /** + * 销毁管理器,取消所有操作 + */ + public destroy(): void { + this.log('Destroying AsyncOperationManager'); + this.cancelAllOperations(); + this.pendingOperations.clear(); + this.currentResourceOperation.clear(); + } +} \ No newline at end of file diff --git a/frontend/src/common/async-operation/types.ts b/frontend/src/common/async-operation/types.ts new file mode 100644 index 0000000..4c3243e --- /dev/null +++ b/frontend/src/common/async-operation/types.ts @@ -0,0 +1,82 @@ +/** + * 异步操作竞态条件控制相关类型定义 + */ + +/** + * 操作状态枚举 + */ +export enum OperationStatus { + PENDING = 'pending', + RUNNING = 'running', + COMPLETED = 'completed', + CANCELLED = 'cancelled', + FAILED = 'failed' +} + +/** + * 操作信息接口 + */ +export interface OperationInfo { + /** 操作ID */ + id: number; + /** 资源ID(如文档ID、用户ID等) */ + resourceId: string | number; + /** 操作状态 */ + status: OperationStatus; + /** 取消控制器 */ + abortController: AbortController; + /** 创建时间 */ + createdAt: number; + /** 操作类型(可选,用于调试) */ + type?: string; +} + +/** + * 操作管理器配置 + */ +export interface AsyncOperationManagerConfig { + /** 操作超时时间(毫秒),0表示不超时 */ + timeout?: number; + /** 是否自动清理已完成的操作 */ + autoCleanup?: boolean; + /** 最大并发操作数,0表示无限制 */ + maxConcurrent?: number; + /** 调试模式 */ + debug?: boolean; +} + +/** + * 操作回调函数类型 + */ +export interface OperationCallbacks { + /** 操作开始回调 */ + onStart?: (operation: OperationInfo) => void; + /** 操作完成回调 */ + onComplete?: (operation: OperationInfo) => void; + /** 操作取消回调 */ + onCancel?: (operation: OperationInfo) => void; + /** 操作失败回调 */ + onError?: (operation: OperationInfo, error: Error) => void; +} + +/** + * 操作执行器函数类型 + */ +export type OperationExecutor = ( + signal: AbortSignal, + operationId: number +) => Promise; + +/** + * 操作结果类型 + */ +export interface OperationResult { + /** 操作是否成功 */ + success: boolean; + /** 操作结果数据 */ + data?: T; + /** 错误信息 */ + error?: Error; + /** 操作信息 */ + operation: OperationInfo; +} \ No newline at end of file diff --git a/frontend/src/common/cache/README.md b/frontend/src/common/cache/README.md new file mode 100644 index 0000000..59c5799 --- /dev/null +++ b/frontend/src/common/cache/README.md @@ -0,0 +1,106 @@ +# 缓存系统 + +简洁高效的 LRU 缓存实现,支持泛型和自动清理。 + +## 特性 + +- 🚀 高性能 LRU 缓存算法 +- 🔧 TypeScript 泛型支持 +- 🧹 自动资源清理 +- 📊 缓存统计信息 +- ⏰ TTL 过期支持 +- 🎯 简洁易用的 API + +## 基础用法 + +### 创建缓存 + +```typescript +import { LRUCache, CacheManager, createCacheItem } from '@/common/cache'; + +// 直接创建缓存 +const cache = new LRUCache({ + maxSize: 100, + ttl: 5 * 60 * 1000, // 5 分钟 + onEvict: (item) => console.log('Evicted:', item) +}); + +// 使用缓存管理器 +const manager = new CacheManager(); +const myCache = manager.createCache('myCache', { maxSize: 50 }); +``` + +### 缓存操作 + +```typescript +// 创建缓存项 +const item = createCacheItem('key1', { + name: 'example', + data: { foo: 'bar' } +}); + +// 设置缓存 +cache.set('key1', item); + +// 获取缓存 +const cached = cache.get('key1'); + +// 检查存在 +if (cache.has('key1')) { + // 处理逻辑 +} + +// 移除缓存 +cache.remove('key1'); +``` + +### 自动清理资源 + +```typescript +interface MyItem extends CacheItem, DisposableCacheItem { + resource: SomeResource; + dispose(): void; +} + +const item: MyItem = { + id: 'resource1', + lastAccessed: new Date(), + createdAt: new Date(), + resource: new SomeResource(), + dispose() { + this.resource.cleanup(); + } +}; + +// 当项被驱逐或移除时,dispose 方法会自动调用 +cache.set('resource1', item); +``` + +## API 参考 + +### LRUCache + +- `get(id)` - 获取缓存项 +- `set(id, item)` - 设置缓存项 +- `remove(id)` - 移除缓存项 +- `has(id)` - 检查是否存在 +- `clear()` - 清空缓存 +- `size()` - 获取缓存大小 +- `getStats()` - 获取统计信息 +- `cleanup()` - 清理过期项 + +### CacheManager + +- `getCache(name, config?)` - 获取或创建缓存 +- `createCache(name, config)` - 创建新缓存 +- `removeCache(name)` - 删除缓存 +- `clearAll()` - 清空所有缓存 +- `getAllStats()` - 获取所有统计信息 + +## 工具函数 + +- `generateCacheKey(...parts)` - 生成缓存键 +- `createCacheItem(id, data)` - 创建缓存项 +- `createContentHash(content)` - 创建内容哈希 +- `debounce(func, wait)` - 防抖函数 +- `throttle(func, limit)` - 节流函数 \ No newline at end of file diff --git a/frontend/src/common/cache/cache-manager.ts b/frontend/src/common/cache/cache-manager.ts new file mode 100644 index 0000000..ad4f606 --- /dev/null +++ b/frontend/src/common/cache/cache-manager.ts @@ -0,0 +1,133 @@ +import type { CacheItem, CacheConfig, CacheStats } from './types'; +import { LRUCache } from './lru-cache'; + +export class CacheManager { + private caches = new Map>(); + private cleanupInterval?: number; + + constructor(options?: { + /** 自动清理间隔(毫秒),默认 5 分钟 */ + cleanupInterval?: number; + }) { + if (options?.cleanupInterval) { + this.startAutoCleanup(options.cleanupInterval); + } + } + + /** + * 创建或获取缓存实例 + */ + getCache(name: string, config?: CacheConfig): LRUCache { + if (!this.caches.has(name)) { + if (!config) { + throw new Error(`Cache "${name}" does not exist and no config provided`); + } + this.caches.set(name, new LRUCache(config)); + } + return this.caches.get(name)!; + } + + /** + * 创建新的缓存实例 + */ + createCache(name: string, config: CacheConfig): LRUCache { + if (this.caches.has(name)) { + throw new Error(`Cache "${name}" already exists`); + } + const cache = new LRUCache(config); + this.caches.set(name, cache); + return cache; + } + + /** + * 删除缓存实例 + */ + removeCache(name: string): boolean { + const cache = this.caches.get(name); + if (cache) { + cache.clear(); + this.caches.delete(name); + return true; + } + return false; + } + + /** + * 检查缓存是否存在 + */ + hasCache(name: string): boolean { + return this.caches.has(name); + } + + /** + * 获取所有缓存名称 + */ + getCacheNames(): string[] { + return Array.from(this.caches.keys()); + } + + /** + * 清空所有缓存 + */ + clearAll(): void { + for (const cache of this.caches.values()) { + cache.clear(); + } + } + + /** + * 获取所有缓存的统计信息 + */ + getAllStats(): Record { + const stats: Record = {}; + for (const [name, cache] of this.caches.entries()) { + stats[name] = cache.getStats(); + } + return stats; + } + + /** + * 清理所有缓存中的过期项 + */ + cleanupAll(): Record { + const results: Record = {}; + for (const [name, cache] of this.caches.entries()) { + results[name] = cache.cleanup(); + } + return results; + } + + /** + * 启动自动清理 + */ + startAutoCleanup(interval: number): void { + this.stopAutoCleanup(); + this.cleanupInterval = window.setInterval(() => { + this.cleanupAll(); + }, interval); + } + + /** + * 停止自动清理 + */ + stopAutoCleanup(): void { + if (this.cleanupInterval) { + clearInterval(this.cleanupInterval); + this.cleanupInterval = undefined; + } + } + + /** + * 销毁管理器 + */ + destroy(): void { + this.stopAutoCleanup(); + this.clearAll(); + this.caches.clear(); + } +} + +// 全局缓存管理器实例 +export const globalCacheManager = new CacheManager({ + cleanupInterval: 5 * 60 * 1000 // 5 分钟 +}); \ No newline at end of file diff --git a/frontend/src/common/cache/index.ts b/frontend/src/common/cache/index.ts new file mode 100644 index 0000000..1ac3ecd --- /dev/null +++ b/frontend/src/common/cache/index.ts @@ -0,0 +1,19 @@ +export type { + CacheItem, + DisposableCacheItem, + CacheConfig, + CacheStats +} from './types'; +export { LRUCache } from './lru-cache'; +export { CacheManager, globalCacheManager } from './cache-manager'; + +export { + generateCacheKey, + createCacheItem, + calculateHitRate, + formatCacheSize, + isExpired, + createContentHash, + debounce, + throttle +} from './utils'; \ No newline at end of file diff --git a/frontend/src/common/cache/lru-cache.ts b/frontend/src/common/cache/lru-cache.ts new file mode 100644 index 0000000..f52ccb6 --- /dev/null +++ b/frontend/src/common/cache/lru-cache.ts @@ -0,0 +1,200 @@ +import type { CacheItem, CacheConfig, CacheStats, DisposableCacheItem } from './types'; + +export class LRUCache { + private items = new Map(); + private accessOrder: (string | number)[] = []; + private config: CacheConfig; + private stats = { + hits: 0, + misses: 0 + }; + + constructor(config: CacheConfig) { + this.config = config; + } + + /** + * 获取缓存项 + */ + get(id: string | number): T | null { + const item = this.items.get(id); + + if (!item) { + this.stats.misses++; + return null; + } + + // 检查是否过期 + if (this.isExpired(item)) { + this.remove(id); + this.stats.misses++; + return null; + } + + // 更新访问时间和顺序 + item.lastAccessed = new Date(); + this.updateAccessOrder(id); + this.stats.hits++; + + return item; + } + + /** + * 设置缓存项 + */ + set(id: string | number, item: T): void { + // 如果已存在,先移除旧的 + if (this.items.has(id)) { + this.remove(id); + } + + // 检查容量,必要时驱逐最旧的项 + while (this.items.size >= this.config.maxSize) { + const oldestId = this.accessOrder.shift(); + if (oldestId !== undefined) { + this.evict(oldestId); + } + } + + // 添加新项 + this.items.set(id, item); + this.accessOrder.push(id); + } + + /** + * 移除缓存项 + */ + remove(id: string | number): boolean { + const item = this.items.get(id); + if (!item) return false; + + this.items.delete(id); + const index = this.accessOrder.indexOf(id); + if (index > -1) { + this.accessOrder.splice(index, 1); + } + + // 调用清理逻辑 + this.disposeItem(item); + return true; + } + + /** + * 检查是否存在 + */ + has(id: string | number): boolean { + const item = this.items.get(id); + if (!item) return false; + + if (this.isExpired(item)) { + this.remove(id); + return false; + } + + return true; + } + + /** + * 清空缓存 + */ + clear(): void { + for (const item of this.items.values()) { + this.disposeItem(item); + } + this.items.clear(); + this.accessOrder = []; + this.stats.hits = 0; + this.stats.misses = 0; + } + + /** + * 获取所有项 + */ + getAll(): T[] { + return Array.from(this.items.values()); + } + + /** + * 获取缓存大小 + */ + size(): number { + return this.items.size; + } + + /** + * 获取统计信息 + */ + getStats(): CacheStats { + const total = this.stats.hits + this.stats.misses; + return { + size: this.items.size, + maxSize: this.config.maxSize, + hits: this.stats.hits, + misses: this.stats.misses, + hitRate: total > 0 ? this.stats.hits / total : 0 + }; + } + + /** + * 清理过期项 + */ + cleanup(): number { + let cleanedCount = 0; + const now = Date.now(); + + if (!this.config.ttl) return cleanedCount; + + for (const [id, item] of this.items.entries()) { + if (now - item.lastAccessed.getTime() > this.config.ttl) { + this.remove(id); + cleanedCount++; + } + } + + return cleanedCount; + } + + // 私有方法 + + private isExpired(item: T): boolean { + if (!this.config.ttl) return false; + return Date.now() - item.lastAccessed.getTime() > this.config.ttl; + } + + private updateAccessOrder(id: string | number): void { + const index = this.accessOrder.indexOf(id); + if (index > -1) { + this.accessOrder.splice(index, 1); + } + this.accessOrder.push(id); + } + + private evict(id: string | number): void { + const item = this.items.get(id); + if (item) { + if (this.config.onEvict) { + this.config.onEvict(item); + } + this.remove(id); + } + } + + private disposeItem(item: T): void { + if (this.isDisposable(item)) { + try { + const result = item.dispose(); + if (result instanceof Promise) { + result.catch(error => { + console.warn('Failed to dispose cache item:', error); + }); + } + } catch (error) { + console.warn('Failed to dispose cache item:', error); + } + } + } + + private isDisposable(item: T): item is T & DisposableCacheItem { + return 'dispose' in item && typeof (item as any).dispose === 'function'; + } +} \ No newline at end of file diff --git a/frontend/src/common/cache/types.ts b/frontend/src/common/cache/types.ts new file mode 100644 index 0000000..575e55e --- /dev/null +++ b/frontend/src/common/cache/types.ts @@ -0,0 +1,39 @@ +// 缓存项基础接口 +export interface CacheItem { + /** 缓存项的唯一标识 */ + id: string | number; + /** 最后访问时间 */ + lastAccessed: Date; + /** 创建时间 */ + createdAt: Date; +} + +// 可清理的缓存项接口 +export interface DisposableCacheItem extends CacheItem { + /** 清理资源的方法 */ + dispose(): void | Promise; +} + +// 缓存配置 +export interface CacheConfig { + /** 最大缓存数量 */ + maxSize: number; + /** 生存时间(毫秒),可选 */ + ttl?: number; + /** 驱逐回调函数,可选 */ + onEvict?: (item: any) => void | Promise; +} + +// 缓存统计信息 +export interface CacheStats { + /** 当前缓存项数量 */ + size: number; + /** 最大容量 */ + maxSize: number; + /** 命中次数 */ + hits: number; + /** 未命中次数 */ + misses: number; + /** 命中率 */ + hitRate: number; +} \ No newline at end of file diff --git a/frontend/src/common/cache/utils.ts b/frontend/src/common/cache/utils.ts new file mode 100644 index 0000000..dfc2f14 --- /dev/null +++ b/frontend/src/common/cache/utils.ts @@ -0,0 +1,98 @@ +import type { CacheItem } from './types'; + +/** + * 生成缓存键 + */ +export function generateCacheKey(...parts: (string | number)[]): string { + return parts.join(':'); +} + +/** + * 创建基础缓存项 + */ +export function createCacheItem>( + id: string | number, + data: T +): CacheItem & T { + const now = new Date(); + return { + id, + lastAccessed: now, + createdAt: now, + ...data + }; +} + +/** + * 计算缓存命中率 + */ +export function calculateHitRate(hits: number, misses: number): number { + const total = hits + misses; + return total > 0 ? hits / total : 0; +} + +/** + * 格式化缓存大小 + */ +export function formatCacheSize(size: number): string { + if (size < 1024) return `${size} B`; + if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`; + if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(1)} MB`; + return `${(size / (1024 * 1024 * 1024)).toFixed(1)} GB`; +} + +/** + * 检查项是否过期 + */ +export function isExpired(item: CacheItem, ttl: number): boolean { + return Date.now() - item.lastAccessed.getTime() > ttl; +} + +/** + * 创建内容哈希(简单实现) + */ +export function createContentHash(content: string): string { + let hash = 0; + if (content.length === 0) return hash.toString(); + + for (let i = 0; i < content.length; i++) { + const char = content.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // 转换为 32 位整数 + } + + return Math.abs(hash).toString(36); +} + +/** + * 防抖函数,用于缓存操作 + */ +export function debounce any>( + func: T, + wait: number +): (...args: Parameters) => void { + let timeout: number | undefined; + + return (...args: Parameters) => { + clearTimeout(timeout); + timeout = window.setTimeout(() => func(...args), wait); + }; +} + +/** + * 节流函数,用于缓存清理 + */ +export function throttle any>( + func: T, + limit: number +): (...args: Parameters) => void { + let inThrottle: boolean; + + return (...args: Parameters) => { + if (!inThrottle) { + func(...args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + }; +} \ No newline at end of file diff --git a/frontend/src/stores/editorCacheStore.ts b/frontend/src/stores/editorCacheStore.ts new file mode 100644 index 0000000..75252a4 --- /dev/null +++ b/frontend/src/stores/editorCacheStore.ts @@ -0,0 +1,257 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { EditorView } from '@codemirror/view'; +import { ensureSyntaxTree } from '@codemirror/language'; +import { LRUCache, type CacheItem, type DisposableCacheItem, createContentHash } from '@/common/cache'; +import { removeExtensionManagerView } from '@/views/editor/manager'; + +// 编辑器缓存项接口 +export interface EditorCacheItem extends CacheItem, DisposableCacheItem { + view: EditorView; + documentId: number; + content: string; + isDirty: boolean; + lastModified: Date; + autoSaveTimer: number | null; + syntaxTreeCache: { + lastDocLength: number; + lastContentHash: string; + lastParsed: Date; + } | null; +} + +export const useEditorCacheStore = defineStore('editorCache', () => { + // 清理编辑器实例的函数 + const cleanupEditorInstance = (item: EditorCacheItem) => { + try { + // 清除自动保存定时器 + if (item.autoSaveTimer) { + clearTimeout(item.autoSaveTimer); + item.autoSaveTimer = null; + } + + // 从扩展管理器中移除视图 + removeExtensionManagerView(item.documentId); + + // 移除DOM元素 + if (item.view && item.view.dom && item.view.dom.parentElement) { + item.view.dom.remove(); + } + + // 销毁编辑器 + if (item.view && item.view.destroy) { + item.view.destroy(); + } + } catch (error) { + console.error('Error cleaning up editor instance:', error); + } + }; + + // 创建编辑器缓存项 + const createEditorCacheItem = ( + documentId: number, + view: EditorView, + content: string + ): EditorCacheItem => { + const now = new Date(); + + const item: EditorCacheItem = { + id: documentId, + lastAccessed: now, + createdAt: now, + view, + documentId, + content, + isDirty: false, + lastModified: now, + autoSaveTimer: null, + syntaxTreeCache: null, + dispose: () => cleanupEditorInstance(item) + }; + + return item; + }; + + // 编辑器缓存配置 + const EDITOR_CACHE_CONFIG = { + maxSize: 5, // 最多缓存5个编辑器实例 + onEvict: (item: EditorCacheItem) => { + // 清理被驱逐的编辑器实例 + cleanupEditorInstance(item); + } + }; + // 编辑器缓存实例 + const cache = new LRUCache(EDITOR_CACHE_CONFIG); + + // 容器元素 + const containerElement = ref(null); + + // 设置容器元素 + const setContainer = (element: HTMLElement | null) => { + containerElement.value = element; + }; + + // 获取容器元素 + const getContainer = () => containerElement.value; + + // 添加编辑器到缓存 + const addEditor = (documentId: number, view: EditorView, content: string) => { + const item = createEditorCacheItem(documentId, view, content); + cache.set(documentId, item); + + // 初始化语法树缓存 + ensureSyntaxTreeCached(view, documentId); + }; + + // 获取编辑器实例 + const getEditor = (documentId: number): EditorCacheItem | null => { + return cache.get(documentId); + }; + + // 检查编辑器是否存在 + const hasEditor = (documentId: number): boolean => { + return cache.has(documentId); + }; + + // 移除编辑器 + const removeEditor = (documentId: number): boolean => { + return cache.remove(documentId); + }; + + // 获取所有编辑器实例 + const getAllEditors = (): EditorCacheItem[] => { + return cache.getAll(); + }; + + // 清空所有编辑器 + const clearAll = () => { + cache.clear(); + }; + + // 获取缓存大小 + const size = (): number => { + return cache.size(); + }; + + // 获取缓存统计信息 + const getStats = () => { + return cache.getStats(); + }; + + // 缓存化的语法树确保方法 + const ensureSyntaxTreeCached = (view: EditorView, documentId: number): void => { + const item = cache.get(documentId); + if (!item) return; + + const docLength = view.state.doc.length; + const content = view.state.doc.toString(); + const contentHash = createContentHash(content); + const now = new Date(); + + // 检查是否需要重新构建语法树 + const syntaxCache = item.syntaxTreeCache; + const shouldRebuild = !syntaxCache || + syntaxCache.lastDocLength !== docLength || + syntaxCache.lastContentHash !== contentHash || + (now.getTime() - syntaxCache.lastParsed.getTime()) > 30000; // 30秒过期 + + if (shouldRebuild) { + try { + ensureSyntaxTree(view.state, docLength, 5000); + + // 更新缓存 + item.syntaxTreeCache = { + lastDocLength: docLength, + lastContentHash: contentHash, + lastParsed: now + }; + } catch (error) { + console.warn('Failed to ensure syntax tree:', error); + } + } + }; + + // 更新编辑器内容 + const updateEditorContent = (documentId: number, content: string) => { + const item = cache.get(documentId); + if (item) { + item.content = content; + item.isDirty = false; + item.lastModified = new Date(); + // 清理语法树缓存,因为内容已更新 + item.syntaxTreeCache = null; + } + }; + + // 标记编辑器为脏状态 + const markEditorDirty = (documentId: number) => { + const item = cache.get(documentId); + if (item) { + item.isDirty = true; + item.lastModified = new Date(); + // 清理语法树缓存,下次访问时重新构建 + item.syntaxTreeCache = null; + } + }; + + // 设置自动保存定时器 + const setAutoSaveTimer = (documentId: number, timer: number) => { + const item = cache.get(documentId); + if (item) { + // 清除之前的定时器 + if (item.autoSaveTimer) { + clearTimeout(item.autoSaveTimer); + } + item.autoSaveTimer = timer; + } + }; + + // 清除自动保存定时器 + const clearAutoSaveTimer = (documentId: number) => { + const item = cache.get(documentId); + if (item && item.autoSaveTimer) { + clearTimeout(item.autoSaveTimer); + item.autoSaveTimer = null; + } + }; + + // 获取脏状态的编辑器 + const getDirtyEditors = (): EditorCacheItem[] => { + return cache.getAll().filter(item => item.isDirty); + }; + + // 清理过期的语法树缓存 + const cleanupExpiredSyntaxTrees = () => { + const now = new Date(); + cache.getAll().forEach(item => { + if (item.syntaxTreeCache && + (now.getTime() - item.syntaxTreeCache.lastParsed.getTime()) > 30000) { + item.syntaxTreeCache = null; + } + }); + }; + + return { + // 容器管理 + setContainer, + getContainer, + + // 基础缓存操作 + addEditor, + getEditor, + hasEditor, + removeEditor, + getAllEditors, + clearAll, + size, + getStats, + + ensureSyntaxTreeCached, + updateEditorContent, + markEditorDirty, + setAutoSaveTimer, + clearAutoSaveTimer, + getDirtyEditors, + cleanupExpiredSyntaxTrees + }; +}); \ No newline at end of file diff --git a/frontend/src/stores/editorStore.ts b/frontend/src/stores/editorStore.ts index 42ed48f..3f58603 100644 --- a/frontend/src/stores/editorStore.ts +++ b/frontend/src/stores/editorStore.ts @@ -5,9 +5,9 @@ import {EditorState, Extension} from '@codemirror/state'; import {useConfigStore} from './configStore'; import {useDocumentStore} from './documentStore'; import {useThemeStore} from './themeStore'; +import {useEditorCacheStore} from './editorCacheStore'; import {ExtensionID, SystemThemeType} from '@/../bindings/voidraft/internal/models/models'; import {DocumentService, ExtensionService} from '@/../bindings/voidraft/internal/services'; -import {ensureSyntaxTree} from "@codemirror/language"; import {createBasicSetup} from '@/views/editor/basic/basicSetup'; import {createThemeExtension, updateEditorTheme} from '@/views/editor/basic/themeExtension'; import {getTabExtensions, updateTabConfig} from '@/views/editor/basic/tabExtension'; @@ -18,8 +18,7 @@ import {createDynamicKeymapExtension, updateKeymapExtension} from '@/views/edito import {createDynamicExtensions, getExtensionManager, setExtensionManagerView, removeExtensionManagerView} from '@/views/editor/manager'; import {useExtensionStore} from './extensionStore'; import createCodeBlockExtension from "@/views/editor/extensions/codeblock"; - -const NUM_EDITOR_INSTANCES = 5; // 最多缓存5个编辑器实例 +import {AsyncOperationManager} from '@/common/async-operation'; export interface DocumentStats { lines: number; @@ -27,38 +26,15 @@ export interface DocumentStats { selectedCharacters: number; } -interface EditorInstance { - view: EditorView; - documentId: number; - content: string; - isDirty: boolean; - lastModified: Date; - autoSaveTimer: number | null; - syntaxTreeCache: { - lastDocLength: number; - lastContentHash: string; - lastParsed: Date; - } | null; -} - export const useEditorStore = defineStore('editor', () => { // === 依赖store === const configStore = useConfigStore(); const documentStore = useDocumentStore(); const themeStore = useThemeStore(); const extensionStore = useExtensionStore(); + const editorCacheStore = useEditorCacheStore(); // === 核心状态 === - const editorCache = ref<{ - lru: number[]; - instances: Record; - containerElement: HTMLElement | null; - }>({ - lru: [], - instances: {}, - containerElement: null - }); - const currentEditor = ref(null); const documentStats = ref({ lines: 0, @@ -69,94 +45,29 @@ export const useEditorStore = defineStore('editor', () => { // 编辑器加载状态 const isLoading = ref(false); - // 异步操作竞态条件控制 - const operationSequence = ref(0); - const pendingOperations = ref(new Map()); - const currentLoadingDocumentId = ref(null); + // 异步操作管理器 + const operationManager = new AsyncOperationManager({ + debug: false, + autoCleanup: true + }); - // 自动保存设置 - 从配置动态获取 + // 自动保存设置 const getAutoSaveDelay = () => configStore.config.editing.autoSaveDelay; - // 生成新的操作序列号 - const getNextOperationId = () => ++operationSequence.value; - - // 取消之前的操作 - const cancelPreviousOperations = (excludeId?: number) => { - pendingOperations.value.forEach((controller, id) => { - if (id !== excludeId) { - controller.abort(); - pendingOperations.value.delete(id); - } - }); - }; - - // 检查操作是否仍然有效 - const isOperationValid = (operationId: number, documentId: number) => { - return ( - pendingOperations.value.has(operationId) && - !pendingOperations.value.get(operationId)?.signal.aborted && - currentLoadingDocumentId.value === documentId - ); - }; - // === 私有方法 === - // 生成内容哈希 - const generateContentHash = (content: string): string => { - let hash = 0; - for (let i = 0; i < content.length; i++) { - const char = content.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; // Convert to 32bit integer - } - return hash.toString(); - }; - - // 缓存化的语法树确保方法 - const ensureSyntaxTreeCached = (view: EditorView, documentId: number): void => { - const instance = editorCache.value.instances[documentId]; - if (!instance) return; - - const docLength = view.state.doc.length; - const content = view.state.doc.toString(); - const contentHash = generateContentHash(content); - const now = new Date(); - - // 检查是否需要重新构建语法树 - const cache = instance.syntaxTreeCache; - const shouldRebuild = !cache || - cache.lastDocLength !== docLength || - cache.lastContentHash !== contentHash || - (now.getTime() - cache.lastParsed.getTime()) > 30000; // 30秒过期 - - if (shouldRebuild) { - try { - ensureSyntaxTree(view.state, docLength, 5000); - - // 更新缓存 - instance.syntaxTreeCache = { - lastDocLength: docLength, - lastContentHash: contentHash, - lastParsed: now - }; - } catch (error) { - console.warn('Failed to ensure syntax tree:', error); - } - } - }; - // 创建编辑器实例 const createEditorInstance = async ( content: string, - operationId: number, + signal: AbortSignal, documentId: number ): Promise => { - if (!editorCache.value.containerElement) { + if (!editorCacheStore.getContainer()) { throw new Error('Editor container not set'); } - // 检查操作是否仍然有效 - if (!isOperationValid(operationId, documentId)) { + // 检查操作是否被取消 + if (signal.aborted) { throw new Error('Operation cancelled'); } @@ -195,24 +106,24 @@ export const useEditorStore = defineStore('editor', () => { enableAutoDetection: true }); - // 再次检查操作有效性 - if (!isOperationValid(operationId, documentId)) { + // 再次检查操作是否被取消 + if (signal.aborted) { throw new Error('Operation cancelled'); } // 快捷键扩展 const keymapExtension = await createDynamicKeymapExtension(); - // 检查操作有效性 - if (!isOperationValid(operationId, documentId)) { + // 检查操作是否被取消 + if (signal.aborted) { throw new Error('Operation cancelled'); } // 动态扩展,传递文档ID以便扩展管理器可以预初始化 const dynamicExtensions = await createDynamicExtensions(documentId); - // 最终检查操作有效性 - if (!isOperationValid(operationId, documentId)) { + // 最终检查操作是否被取消 + if (signal.aborted) { throw new Error('Operation cancelled'); } @@ -250,91 +161,43 @@ export const useEditorStore = defineStore('editor', () => { return view; }; - // 添加编辑器到缓存 - const addEditorToCache = (documentId: number, view: EditorView, content: string) => { - // 如果缓存已满,移除最少使用的编辑器 - if (editorCache.value.lru.length >= NUM_EDITOR_INSTANCES) { - const oldestId = editorCache.value.lru.shift(); - if (oldestId && editorCache.value.instances[oldestId]) { - const oldInstance = editorCache.value.instances[oldestId]; - // 清除自动保存定时器 - if (oldInstance.autoSaveTimer) { - clearTimeout(oldInstance.autoSaveTimer); - } - // 移除DOM元素 - if (oldInstance.view.dom.parentElement) { - oldInstance.view.dom.remove(); - } - oldInstance.view.destroy(); - delete editorCache.value.instances[oldestId]; - } - } - - // 添加新的编辑器实例 - editorCache.value.instances[documentId] = { - view, - documentId, - content, - isDirty: false, - lastModified: new Date(), - autoSaveTimer: null, - syntaxTreeCache: null - }; - - // 添加到LRU列表 - editorCache.value.lru.push(documentId); - - // 初始化语法树缓存 - ensureSyntaxTreeCached(view, documentId); - }; - - // 更新LRU - const updateLRU = (documentId: number) => { - const lru = editorCache.value.lru; - const index = lru.indexOf(documentId); - if (index > -1) { - lru.splice(index, 1); - } - lru.push(documentId); - }; - // 获取或创建编辑器 const getOrCreateEditor = async ( documentId: number, content: string, - operationId: number + signal: AbortSignal ): Promise => { // 检查缓存 - const cached = editorCache.value.instances[documentId]; + const cached = editorCacheStore.getEditor(documentId); if (cached) { - updateLRU(documentId); return cached.view; } - // 检查操作是否仍然有效 - if (!isOperationValid(operationId, documentId)) { + // 检查操作是否被取消 + if (signal.aborted) { throw new Error('Operation cancelled'); } // 创建新的编辑器实例 - const view = await createEditorInstance(content, operationId, documentId); + const view = await createEditorInstance(content, signal, documentId); - // 最终检查操作有效性 - if (!isOperationValid(operationId, documentId)) { + // 最终检查操作是否被取消 + if (signal.aborted) { // 如果操作已取消,清理创建的实例 view.destroy(); throw new Error('Operation cancelled'); } - addEditorToCache(documentId, view, content); + // 添加到缓存 + editorCacheStore.addEditor(documentId, view, content); return view; }; // 显示编辑器 const showEditor = (documentId: number) => { - const instance = editorCache.value.instances[documentId]; - if (!instance || !editorCache.value.containerElement) return; + const instance = editorCacheStore.getEditor(documentId); + if (!instance || !editorCacheStore.getContainer()) return; try { // 移除当前编辑器DOM @@ -343,18 +206,19 @@ export const useEditorStore = defineStore('editor', () => { } // 确保容器为空 - editorCache.value.containerElement.innerHTML = ''; - - // 将目标编辑器DOM添加到容器 - editorCache.value.containerElement.appendChild(instance.view.dom); + const container = editorCacheStore.getContainer(); + if (container) { + container.innerHTML = ''; + + // 将目标编辑器DOM添加到容器 + container.appendChild(instance.view.dom); + } + currentEditor.value = instance.view; // 设置扩展管理器视图 setExtensionManagerView(instance.view, documentId); - // 更新LRU - updateLRU(documentId); - // 重新测量和聚焦编辑器 nextTick(() => { // 将光标定位到文档末尾并滚动到该位置 @@ -364,11 +228,11 @@ export const useEditorStore = defineStore('editor', () => { scrollIntoView: true }); - // 滚动到文档底部(将光标位置滚动到可见区域) + // 滚动到文档底部 instance.view.focus(); // 使用缓存的语法树确保方法 - ensureSyntaxTreeCached(instance.view, documentId); + editorCacheStore.ensureSyntaxTreeCached(instance.view, documentId); }); } catch (error) { console.error('Error showing editor:', error); @@ -377,7 +241,7 @@ export const useEditorStore = defineStore('editor', () => { // 保存编辑器内容 const saveEditorContent = async (documentId: number): Promise => { - const instance = editorCache.value.instances[documentId]; + const instance = editorCacheStore.getEditor(documentId); if (!instance || !instance.isDirty) return true; try { @@ -388,9 +252,8 @@ export const useEditorStore = defineStore('editor', () => { // 检查在保存期间内容是否又被修改了 if (instance.lastModified === lastModified) { - instance.content = content; - instance.isDirty = false; - instance.lastModified = new Date(); + editorCacheStore.updateEditorContent(documentId, content); + // isDirty 已在 updateEditorContent 中设置为 false } // 如果内容在保存期间被修改了,保持 isDirty 状态 @@ -403,31 +266,23 @@ export const useEditorStore = defineStore('editor', () => { // 内容变化处理 const onContentChange = (documentId: number) => { - const instance = editorCache.value.instances[documentId]; + const instance = editorCacheStore.getEditor(documentId); if (!instance) return; - instance.isDirty = true; - instance.lastModified = new Date(); - - // 清理语法树缓存,下次访问时重新构建 - instance.syntaxTreeCache = null; + editorCacheStore.markEditorDirty(documentId); - // 清除之前的定时器 - if (instance.autoSaveTimer) { - clearTimeout(instance.autoSaveTimer); - } - - // 设置新的自动保存定时器 - instance.autoSaveTimer = window.setTimeout(() => { + // 清除之前的定时器并设置新的自动保存定时器 + const timer = setTimeout(() => { saveEditorContent(documentId); }, getAutoSaveDelay()); + editorCacheStore.setAutoSaveTimer(documentId, timer as unknown as number); }; // === 公共API === // 设置编辑器容器 const setEditorContainer = (container: HTMLElement | null) => { - editorCache.value.containerElement = container; + editorCacheStore.setContainer(container); // 如果设置容器时已有当前文档,立即加载编辑器 if (container && documentStore.currentDocument) { @@ -439,9 +294,6 @@ export const useEditorStore = defineStore('editor', () => { const loadEditor = async (documentId: number, content: string) => { // 设置加载状态 isLoading.value = true; - // 生成新的操作ID - const operationId = getNextOperationId(); - const abortController = new AbortController(); try { // 验证参数 @@ -449,72 +301,69 @@ export const useEditorStore = defineStore('editor', () => { throw new Error('Invalid parameters for loadEditor'); } - // 取消之前的操作并设置当前操作 - cancelPreviousOperations(); - currentLoadingDocumentId.value = documentId; - pendingOperations.value.set(operationId, abortController); - - // 保存当前编辑器内容 - if (currentEditor.value) { - const currentDocId = documentStore.currentDocumentId; - if (currentDocId && currentDocId !== documentId) { - await saveEditorContent(currentDocId); - - // 检查操作是否仍然有效 - if (!isOperationValid(operationId, documentId)) { - return; - } - } - } - - // 获取或创建编辑器 - const view = await getOrCreateEditor(documentId, content, operationId); - - // 检查操作是否仍然有效 - if (!isOperationValid(operationId, documentId)) { - return; - } - - // 更新内容(如果需要) - const instance = editorCache.value.instances[documentId]; - if (instance && instance.content !== content) { - // 确保编辑器视图有效 - if (view && view.state && view.dispatch) { - view.dispatch({ - changes: { - from: 0, - to: view.state.doc.length, - insert: content + // 使用异步操作管理器执行加载操作 + const result = await operationManager.executeOperation( + documentId, + async (signal) => { + // 保存当前编辑器内容 + if (currentEditor.value) { + const currentDocId = documentStore.currentDocumentId; + if (currentDocId && currentDocId !== documentId) { + await saveEditorContent(currentDocId); + + // 检查操作是否被取消 + if (signal.aborted) { + throw new Error('Operation cancelled'); + } } - }); - instance.content = content; - instance.isDirty = false; - // 清理语法树缓存,因为内容已更新 - instance.syntaxTreeCache = null; + } + + // 获取或创建编辑器 + const view = await getOrCreateEditor(documentId, content, signal); + + // 检查操作是否被取消 + if (signal.aborted) { + throw new Error('Operation cancelled'); + } + + // 更新内容 + const instance = editorCacheStore.getEditor(documentId); + if (instance && instance.content !== content) { + // 确保编辑器视图有效 + if (view && view.state && view.dispatch) { + view.dispatch({ + changes: { + from: 0, + to: view.state.doc.length, + insert: content + } + }); + editorCacheStore.updateEditorContent(documentId, content); + } + } + + // 最终检查操作是否被取消 + if (signal.aborted) { + throw new Error('Operation cancelled'); + } + + // 显示编辑器 + showEditor(documentId); + + return view; + }, + 'loadEditor' + ); + + if (!result.success) { + if (result.error?.message !== 'Operation cancelled') { + console.error('Failed to load editor:', result.error); } } - // 最终检查操作有效性 - if (!isOperationValid(operationId, documentId)) { - return; - } - - // 显示编辑器 - showEditor(documentId); - } catch (error) { - if (error instanceof Error && error.message === 'Operation cancelled') { - console.log(`Editor loading cancelled for document ${documentId}`); - } else { - console.error('Failed to load editor:', error); - } + console.error('Failed to load editor:', error); } finally { - // 清理操作记录 - pendingOperations.value.delete(operationId); - if (currentLoadingDocumentId.value === documentId) { - currentLoadingDocumentId.value = null; - } - // 延迟一段时间后再取消加载状态 setTimeout(() => { isLoading.value = false; @@ -524,49 +373,20 @@ export const useEditorStore = defineStore('editor', () => { // 移除编辑器 const removeEditor = (documentId: number) => { - const instance = editorCache.value.instances[documentId]; - if (instance) { - try { - // 如果正在加载这个文档,取消操作 - if (currentLoadingDocumentId.value === documentId) { - cancelPreviousOperations(); - currentLoadingDocumentId.value = null; - } + // 取消该文档的所有操作 + operationManager.cancelResourceOperations(documentId); - // 清除自动保存定时器 - if (instance.autoSaveTimer) { - clearTimeout(instance.autoSaveTimer); - instance.autoSaveTimer = null; - } + // 从扩展管理器中移除视图 + removeExtensionManagerView(documentId); - // 从扩展管理器中移除视图 - removeExtensionManagerView(documentId); - - // 移除DOM元素 - if (instance.view && instance.view.dom && instance.view.dom.parentElement) { - instance.view.dom.remove(); - } - - // 销毁编辑器 - if (instance.view && instance.view.destroy) { - instance.view.destroy(); - } - - // 清理引用 - if (currentEditor.value === instance.view) { - currentEditor.value = null; - } - - delete editorCache.value.instances[documentId]; - - const lruIndex = editorCache.value.lru.indexOf(documentId); - if (lruIndex > -1) { - editorCache.value.lru.splice(lruIndex, 1); - } - } catch (error) { - console.error('Error removing editor:', error); - } + // 清除当前编辑器引用 + const instance = editorCacheStore.getEditor(documentId); + if (instance && currentEditor.value === instance.view) { + currentEditor.value = null; } + + // 从缓存中移除编辑器 + editorCacheStore.removeEditor(documentId); }; // 更新文档统计 @@ -576,7 +396,7 @@ export const useEditorStore = defineStore('editor', () => { // 应用字体设置 const applyFontSettings = () => { - Object.values(editorCache.value.instances).forEach(instance => { + editorCacheStore.getAllEditors().forEach(instance => { updateFontConfig(instance.view, { fontFamily: configStore.config.editing.fontFamily, fontSize: configStore.config.editing.fontSize, @@ -588,7 +408,7 @@ export const useEditorStore = defineStore('editor', () => { // 应用主题设置 const applyThemeSettings = () => { - Object.values(editorCache.value.instances).forEach(instance => { + editorCacheStore.getAllEditors().forEach(instance => { updateEditorTheme(instance.view, themeStore.currentTheme || SystemThemeType.SystemThemeAuto ); @@ -597,7 +417,7 @@ export const useEditorStore = defineStore('editor', () => { // 应用Tab设置 const applyTabSettings = () => { - Object.values(editorCache.value.instances).forEach(instance => { + editorCacheStore.getAllEditors().forEach(instance => { updateTabConfig( instance.view, configStore.config.editing.tabSize, @@ -611,37 +431,21 @@ export const useEditorStore = defineStore('editor', () => { const applyKeymapSettings = async () => { // 确保所有编辑器实例的快捷键都更新 await Promise.all( - Object.values(editorCache.value.instances).map(instance => + editorCacheStore.getAllEditors().map(instance => updateKeymapExtension(instance.view) ) ); }; - // 清空所有编辑器 + // 清理所有编辑器 const clearAllEditors = () => { // 取消所有挂起的操作 - cancelPreviousOperations(); - currentLoadingDocumentId.value = null; + operationManager.cancelAllOperations(); - Object.values(editorCache.value.instances).forEach(instance => { - // 清除自动保存定时器 - if (instance.autoSaveTimer) { - clearTimeout(instance.autoSaveTimer); - } - - // 从扩展管理器移除 - removeExtensionManagerView(instance.documentId); - - // 移除DOM元素 - if (instance.view.dom.parentElement) { - instance.view.dom.remove(); - } - // 销毁编辑器 - instance.view.destroy(); - }); - - editorCache.value.instances = {}; - editorCache.value.lru = []; + // 清理所有编辑器 + editorCacheStore.clearAll(); + + // 清除当前编辑器引用 currentEditor.value = null; }; @@ -672,7 +476,7 @@ export const useEditorStore = defineStore('editor', () => { // 监听文档切换 watch(() => documentStore.currentDocument, (newDoc) => { - if (newDoc && editorCache.value.containerElement) { + if (newDoc && editorCacheStore.getContainer()) { // 使用 nextTick 确保DOM更新完成后再加载编辑器 nextTick(() => { loadEditor(newDoc.id, newDoc.content);