Private
Public Access
1
0

新增:Markdown编辑器/数据库优化/安全修复

- Markdown 编辑器:实时预览、PDF 导出、独立查看器
- 数据库优化:动态连接池、查询缓存、Redis Pipeline
- 窗口置顶功能
- 文件系统增强:右键菜单、编辑器集成、收藏夹重构
- 安全修复:XSS 防护、路径穿越、HTML 注入
- 代码质量:正则预编译、缓存锁优化、死代码清理
This commit is contained in:
2026-03-31 09:18:06 +08:00
parent 5f94ccf13b
commit e5dbe89a6f
59 changed files with 5289 additions and 1316 deletions

View File

@@ -3,8 +3,10 @@
* 提供收藏文件的添加、删除、排序等功能
*/
import { ref, watch } from 'vue'
import { STORAGE_KEYS } from '@/utils/constants'
import { ref } from 'vue'
import { STORAGE_KEYS, DEFAULTS } from '@/utils/constants'
import { getPathSeparator } from '@/utils/fileUtils'
import { Message } from '@arco-design/web-vue'
import type { FavoriteFile, FileItem, DraggingState } from '@/types/file-system'
export function useFavorites() {
@@ -67,13 +69,23 @@ export function useFavorites() {
}
}
/**
* 标准化路径用于比较Windows 大小写不敏感)
*/
const normalizePath = (path: string): string => {
return path.toLowerCase()
}
/**
* 添加收藏
*/
const addFavorite = (file: FileItem) => {
// 检查是否已存在
const exists = favorites.value.some(fav => fav.path === file.path)
if (exists) {
if (isFavorite(file.path)) {
return false
}
if (favorites.value.length >= DEFAULTS.MAX_FAVORITES_LENGTH) {
Message.warning(`收藏夹已满,最多收藏 ${DEFAULTS.MAX_FAVORITES_LENGTH}`)
return false
}
@@ -81,17 +93,11 @@ export function useFavorites() {
...file,
addedAt: Date.now()
} as FavoriteFile)
sortFavorites()
saveFavorites()
return true
}
/**
* 标准化路径用于比较(后端已统一为 /,直接转小写)
*/
const normalizePath = (path: string): string => {
return path.toLowerCase()
}
/**
* 删除收藏
*/
@@ -108,14 +114,12 @@ export function useFavorites() {
* 切换收藏状态
*/
const toggleFavorite = (file: FileItem) => {
const exists = isFavorite(file.path)
if (exists) {
if (isFavorite(file.path)) {
removeFavorite(file.path)
return false
} else {
addFavorite(file)
return true
}
addFavorite(file)
return true
}
/**
@@ -131,15 +135,9 @@ export function useFavorites() {
*/
const togglePin = (path: string) => {
const normalizedPath = normalizePath(path)
const fav = favorites.value.find(f => normalizePath(f.path) === normalizedPath)
const fav = favorites.value.find(f => normalizePath(fav.path) === normalizedPath)
if (fav) {
if (fav.pinnedAt) {
// 取消置顶
fav.pinnedAt = undefined
} else {
// 设置置顶
fav.pinnedAt = Date.now()
}
fav.pinnedAt = fav.pinnedAt ? undefined : Date.now()
sortFavorites()
saveFavorites()
}
@@ -150,28 +148,37 @@ export function useFavorites() {
*/
const isPinned = (path: string): boolean => {
const normalizedPath = normalizePath(path)
const fav = favorites.value.find(f => normalizePath(f.path) === normalizedPath)
const fav = favorites.value.find(f => normalizePath(fav.path) === normalizedPath)
return !!fav?.pinnedAt
}
/**
* 长按开始
* 更新收藏项路径(重命名时使用,保留置顶状态和添加时间)
*/
const onLongPressStart = (event: MouseEvent | TouchEvent, index: number) => {
const isMouse = event instanceof MouseEvent
const isTouch = event instanceof TouchEvent
const updateFavoritePath = (oldPath: string, newName: string) => {
const normalizedOld = normalizePath(oldPath)
const fav = favorites.value.find(f => normalizePath(fav.path) === normalizedOld)
if (!fav) return
// 只支持鼠标左键或触摸
if (isMouse && event.button !== 0) return
if (!isMouse && !isTouch) return
const separator = getPathSeparator(oldPath)
const parentPath = oldPath.substring(
0,
Math.max(oldPath.lastIndexOf('\\'), oldPath.lastIndexOf('/'))
)
fav.path = parentPath + separator + newName
fav.name = newName
saveFavorites()
}
// 拖拽方法
const onLongPressStart = (event: MouseEvent | TouchEvent, index: number) => {
if (event instanceof MouseEvent && event.button !== 0) return
if (!(event instanceof MouseEvent) && !(event instanceof TouchEvent)) return
draggingState.value.pressedIndex = index
draggingState.value.draggedIndex = index
}
/**
* 长按取消
*/
const onLongPressCancel = () => {
if (!draggingState.value.isDragging) {
draggingState.value.pressedIndex = -1
@@ -179,23 +186,15 @@ export function useFavorites() {
}
}
/**
* 拖拽开始
*/
const onDragStart = (event: DragEvent, index: number) => {
draggingState.value.isDragging = true
draggingState.value.draggedIndex = index
// 设置拖拽数据
if (event.dataTransfer) {
event.dataTransfer.effectAllowed = 'move'
event.dataTransfer.setData('text/plain', index.toString())
}
}
/**
* 拖拽经过
*/
const onDragOver = (event: DragEvent) => {
event.preventDefault()
if (event.dataTransfer) {
@@ -203,81 +202,53 @@ export function useFavorites() {
}
}
/**
* 放置
*/
const onDrop = (event: DragEvent, targetIndex: number) => {
event.preventDefault()
const fromIndex = draggingState.value.draggedIndex
const toIndex = targetIndex
if (fromIndex === toIndex || fromIndex === -1) {
if (fromIndex === targetIndex || fromIndex === -1) {
resetDragging()
return
}
// 移动元素
const item = favorites.value.splice(fromIndex, 1)[0]
favorites.value.splice(toIndex, 0, item)
favorites.value.splice(targetIndex, 0, item)
saveFavorites()
resetDragging()
}
/**
* 拖拽结束
*/
const onDragEnd = () => {
resetDragging()
}
/**
* 重置拖拽状态
*/
const resetDragging = () => {
draggingState.value.isDragging = false
draggingState.value.draggedIndex = -1
draggingState.value.pressedIndex = -1
}
/**
* 重新排序
*/
const reorder = (fromIndex: number, toIndex: number) => {
if (fromIndex === toIndex) return
const item = favorites.value.splice(fromIndex, 1)[0]
favorites.value.splice(toIndex, 0, item)
saveFavorites()
}
// 组件挂载时加载收藏列表
loadFavorites()
return {
// 状态
favorites,
draggingState,
// 方法
addFavorite,
removeFavorite,
toggleFavorite,
isFavorite,
togglePin,
isPinned,
updateFavoritePath,
// 拖拽方法
onLongPressStart,
onLongPressCancel,
onDragStart,
onDragOver,
onDrop,
onDragEnd,
reorder,
// 工具方法
loadFavorites,
saveFavorites,
resetDragging