Private
Public Access
1
0

重构:文件系统模块化架构,增强 Markdown 渲染

- 拆分 FileSystem.vue 为模块化组件架构
- 新增 Markdown Mermaid 图表渲染支持
- 新增 180+ 编程语言代码高亮
- 修复编辑/预览模式切换渲染问题
- 优化亮色/暗色模式主题适配
- 新增 TypeScript 类型定义
This commit is contained in:
2026-02-04 03:31:22 +08:00
parent eb2cbad17b
commit a5d30684ed
119 changed files with 11244 additions and 12042 deletions

View File

@@ -0,0 +1,369 @@
/**
* 文件编辑和保存逻辑 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<string>} options.filePath - 当前文件路径
* @param {Ref<string>} 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<boolean>}
*/
const isSaving = ref(false)
/**
* 是否是快捷键触发的保存
* @type {Ref<boolean>}
*/
const isShortcutSave = ref(false)
/**
* 保存成功提示消息
* @type {Ref<string>}
*/
const saveSuccessMessage = ref('')
/**
* 原始文件内容(用于检测变更)
* @type {Ref<string>}
*/
const originalContent = ref('')
/**
* 是否为编辑模式
* @type {Ref<boolean>}
*/
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<boolean>} 是否成功
*/
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<boolean>} 是否成功
*/
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<boolean>} 是否成功
*/
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<boolean>} isSaving - 是否正在保存
* @property {Ref<boolean>} isShortcutSave - 是否是快捷键触发
* @property {Ref<string>} saveSuccessMessage - 保存成功提示消息
* @property {Ref<string>} originalContent - 原始文件内容
* @property {Ref<boolean>} isEditMode - 是否为编辑模式
* @property {ComputedRef<boolean>} isFileModified - 文件内容是否已修改
* @property {ComputedRef<boolean>} canSaveFile - 是否可以保存文件
* @property {ComputedRef<boolean>} 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 - 切换编辑模式
*/