/** * 文件编辑和保存逻辑 composable * * @module composables/useFileEdit * @description 封装文件编辑、保存、草稿管理等逻辑 */ import { ref, computed, watch } from 'vue' import { Message, Modal } from '@arco-design/web-vue' import { STORAGE_KEYS } from '@/utils/constants' /** * 草稿存储键 */ const DRAFT_STORAGE_KEY = 'filesystem_draft_content' /** * 文件编辑 composable * @param {Object} options - 配置选项 * @param {Ref} options.filePath - 当前文件路径 * @param {Ref} options.fileContent - 文件内容 * @param {Function} options.onWriteFile - 写入文件的函数 * @param {Function} options.onReset - 重置内容的函数 * @returns {UseFileEditReturn} 文件编辑操作 API */ export function useFileEdit(options = {}) { const { filePath, fileContent, onWriteFile, onReset, } = options // ========== 编辑状态 ========== /** * 是否正在保存 * @type {Ref} */ const isSaving = ref(false) /** * 是否是快捷键触发的保存 * @type {Ref} */ const isShortcutSave = ref(false) /** * 保存成功提示消息 * @type {Ref} */ const saveSuccessMessage = ref('') /** * 原始文件内容(用于检测变更) * @type {Ref} */ const originalContent = ref('') /** * 是否为编辑模式 * @type {Ref} */ const isEditMode = ref(localStorage.getItem(STORAGE_KEYS.FILESYSTEM.EDIT_MODE) === 'true') // ========== 计算属性 ========== /** * 文件内容是否已修改 */ const isFileModified = computed(() => { return originalContent.value !== undefined && originalContent.value !== fileContent.value }) /** * 内容是否发生变化(用于按钮禁用判断) */ const contentChanged = computed(() => { return fileContent.value !== '' && fileContent.value !== originalContent.value }) /** * 是否可以保存文件 */ const canSaveFile = computed(() => { return isEditMode.value && contentChanged.value }) /** * 是否可以重置内容 */ const canResetContent = computed(() => { return isEditMode.value && contentChanged.value && originalContent.value !== undefined }) // ========== 草稿管理 ========== /** * 保存草稿到 localStorage */ const saveDraft = () => { try { const draft = { content: fileContent.value, path: filePath.value, timestamp: Date.now(), } localStorage.setItem(DRAFT_STORAGE_KEY, JSON.stringify(draft)) localStorage.setItem(DRAFT_STORAGE_KEY + '_time', Date.now().toString()) } catch (error) { console.warn('[saveDraft] 保存草稿失败:', error) } } /** * 清除草稿 */ const clearDraft = () => { try { localStorage.removeItem(DRAFT_STORAGE_KEY) localStorage.removeItem(DRAFT_STORAGE_KEY + '_time') } catch (error) { console.warn('[clearDraft] 清除草稿失败:', error) } } /** * 加载草稿 * @returns {Object|null} 草稿数据 */ const loadDraft = () => { try { const draftStr = localStorage.getItem(DRAFT_STORAGE_KEY) if (!draftStr) return null const draft = JSON.parse(draftStr) // 检查草稿是否过期(24小时) const timeStr = localStorage.getItem(DRAFT_STORAGE_KEY + '_time') if (timeStr) { const time = parseInt(timeStr, 10) const now = Date.now() const hours = (now - time) / (1000 * 60 * 60) if (hours > 24) { clearDraft() return null } } return draft } catch (error) { console.warn('[loadDraft] 加载草稿失败:', error) return null } } // ========== 保存操作 ========== /** * 显示手动保存对话框 * @param {boolean} isShortcut - 是否是快捷键触发 */ const showManualSaveDialog = (isShortcut) => { isShortcutSave.value = isShortcut Modal.confirm({ title: '保存文件', content: `确定要保存文件 ${filePath.value} 吗?`, okText: '保存', cancelText: '取消', onOk: () => { saveToFile(filePath.value, getFileName(filePath.value), isShortcut) }, }) } /** * 保存到文件 * @param {string} targetPath - 目标路径 * @param {string} fileName - 文件名 * @param {boolean} isShortcut - 是否是快捷键触发 * @returns {Promise} 是否成功 */ const saveToFile = async (targetPath, fileName, isShortcut) => { isSaving.value = true try { const success = await onWriteFile(fileContent.value, targetPath, fileName, isShortcut) if (success) { originalContent.value = fileContent.value clearDraft() } return success } finally { isSaving.value = false } } /** * 处理保存内容 * @returns {Promise} 是否成功 */ const handleSaveContent = async () => { if (!canSaveFile.value) { return false } return await saveToFile(filePath.value, getFileName(filePath.value), false) } /** * 另存为 */ const handleSaveAs = async () => { try { // 简单实现:使用 prompt 获取路径 const targetPath = prompt('请输入保存路径:', filePath.value) if (!targetPath) { return false } const fileName = getFileName(targetPath) return await saveToFile(targetPath, fileName, false) } catch (error) { Message.error(`保存对话框失败: ${error.message || error}`) return false } } /** * 处理写入文件(快捷键或按钮) * @param {boolean} isShortcut - 是否是快捷键触发 * @returns {Promise} 是否成功 */ const handleWriteFile = async (isShortcut = false) => { if (!fileContent.value || !filePath.value) { Message.warning('没有可保存的内容') return false } // 如果内容未修改,快捷键保存时静默返回 if (!isFileModified.value && isShortcut) { return false } // 快捷键:静默保存 if (isShortcut) { return await saveToFile(filePath.value, getFileName(filePath.value), true) } // 按钮:显示确认对话框 showManualSaveDialog(false) return false } // ========== 重置操作 ========== /** * 重置内容到原始状态 */ const resetContent = () => { if (onReset) { onReset() } else { fileContent.value = originalContent.value } } // ========== 编辑模式切换 ========== /** * 切换编辑模式 */ const toggleEditMode = () => { isEditMode.value = !isEditMode.value // 持久化 try { localStorage.setItem(STORAGE_KEYS.FILESYSTEM.EDIT_MODE, isEditMode.value.toString()) } catch (e) { console.warn('[toggleEditMode] 保存编辑模式失败:', e) } // 进入编辑模式时,记录原始内容 if (isEditMode.value) { originalContent.value = fileContent.value } } // ========== 工具函数 ========== /** * 从路径获取文件名 * @param {string} path - 文件路径 * @returns {string} 文件名 */ const getFileName = (path) => { if (!path) return '' const parts = path.split(/[/\\]/) return parts[parts.length - 1] || path } // ========== 监听内容变化 ========== /** * 监听文件内容变化,自动保存草稿 */ watch(fileContent, () => { if (fileContent.value && fileContent.value !== originalContent.value) { saveDraft() } }) /** * 监听文件路径变化,更新原始内容 */ watch(filePath, () => { originalContent.value = fileContent.value }) return { // 状态 isSaving, isShortcutSave, saveSuccessMessage, originalContent, isEditMode, isFileModified, canSaveFile, canResetContent, // 方法 saveDraft, clearDraft, loadDraft, handleSaveContent, handleSaveAs, handleWriteFile, resetContent, toggleEditMode, } } /** * @typedef {Object} UseFileEditReturn * @property {Ref} isSaving - 是否正在保存 * @property {Ref} isShortcutSave - 是否是快捷键触发 * @property {Ref} saveSuccessMessage - 保存成功提示消息 * @property {Ref} originalContent - 原始文件内容 * @property {Ref} isEditMode - 是否为编辑模式 * @property {ComputedRef} isFileModified - 文件内容是否已修改 * @property {ComputedRef} canSaveFile - 是否可以保存文件 * @property {ComputedRef} canResetContent - 是否可以重置内容 * @property {Function} saveDraft - 保存草稿 * @property {Function} clearDraft - 清除草稿 * @property {Function} loadDraft - 加载草稿 * @property {Function} handleSaveContent - 处理保存内容 * @property {Function} handleSaveAs - 另存为 * @property {Function} handleWriteFile - 处理写入文件 * @property {Function} resetContent - 重置内容 * @property {Function} toggleEditMode - 切换编辑模式 */