From a4cf9b310b12248ea0c17c6d9c241e9241ef7f4c Mon Sep 17 00:00:00 2001 From: yuany3721 Date: Wed, 31 Dec 2025 22:42:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dcookie=E8=BF=87=E6=9C=9Fbug?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=8E=92=E5=BA=8F=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/api/files.py | 10 ++++-- backend/src/models/text_file.py | 1 + backend/src/services/file_service.py | 9 +++-- frontend/src/services/api.ts | 10 +++--- frontend/src/views/EditorView.vue | 25 ++++++++++++-- frontend/src/views/FileListView.vue | 51 +++++++++++++++++++++++++--- 6 files changed, 90 insertions(+), 16 deletions(-) diff --git a/backend/src/api/files.py b/backend/src/api/files.py index 895d468..3e88561 100644 --- a/backend/src/api/files.py +++ b/backend/src/api/files.py @@ -34,11 +34,17 @@ async def verify_password(request: PasswordRequest): @router.get("/list", response_model=FileListResponse, dependencies=[Depends(verify_token)]) async def get_files_list( - page: int = Query(1, ge=1, description="页码"), limit: int = Query(50, ge=1, le=100, description="每页文件数") + page: int = Query(1, ge=1, description="页码"), + limit: int = Query(50, ge=1, le=100, description="每页文件数"), + sort_by: str = Query("created_at", description="排序字段: created_at 或 updated_at") ): """获取文件列表(需要认证)""" try: - files = file_service.list_files(page, limit) + # 验证排序参数 + if sort_by not in ["created_at", "updated_at"]: + sort_by = "created_at" + + files = file_service.list_files(page, limit, sort_by) total = file_service.get_total_files_count() return FileListResponse(files=files, total=total, page=page, limit=limit) diff --git a/backend/src/models/text_file.py b/backend/src/models/text_file.py index 9f63cf3..591dacb 100644 --- a/backend/src/models/text_file.py +++ b/backend/src/models/text_file.py @@ -12,6 +12,7 @@ class TextFile(BaseModel): class FileListItem(BaseModel): filename: str created_at: datetime + updated_at: datetime size: int preview: Optional[str] = None diff --git a/backend/src/services/file_service.py b/backend/src/services/file_service.py index 044b027..6c86944 100644 --- a/backend/src/services/file_service.py +++ b/backend/src/services/file_service.py @@ -95,7 +95,7 @@ class FileService: """创建新文件""" return self.write_file(filename, "") - def list_files(self, page: int = 1, limit: int = 50) -> List[FileListItem]: + def list_files(self, page: int = 1, limit: int = 50, sort_by: str = "created_at") -> List[FileListItem]: if page < 1: page = 1 @@ -125,6 +125,7 @@ class FileService: FileListItem( filename=file_path.name, created_at=datetime.fromtimestamp(stat.st_ctime), + updated_at=datetime.fromtimestamp(stat.st_mtime), size=stat.st_size, preview=preview, ) @@ -132,7 +133,11 @@ class FileService: except Exception as e: raise RuntimeError(f"Error listing files: {str(e)}") - files.sort(key=lambda x: x.created_at, reverse=True) + # 根据参数排序 + if sort_by == "updated_at": + files.sort(key=lambda x: x.updated_at, reverse=True) + else: # 默认按创建时间排序 + files.sort(key=lambda x: x.created_at, reverse=True) start = (page - 1) * limit end = start + limit diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 4b41e30..0878806 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -21,9 +21,10 @@ api.interceptors.response.use( if (error.response?.status === 401) { // 令牌过期,清除本地存储 localStorage.removeItem('authToken') - // 只在非密码验证页面刷新 + // 只在非密码验证页面触发认证状态变化事件 if (!error.config.url?.includes('verify-password')) { - window.location.reload() + // 通过自定义事件通知应用认证状态变化 + window.dispatchEvent(new CustomEvent('auth-expired')) } } return Promise.reject(error) @@ -41,6 +42,7 @@ export interface TextFile { export interface FileListItem { filename: string created_at: string + updated_at: string size: number preview?: string } @@ -64,9 +66,9 @@ export const notesApi = { } export const filesApi = { - getList: (page = 1, limit = 50) => + getList: (page = 1, limit = 50, sortBy = 'created_at') => api.get( - '/notepad/files/list', { params: { page, limit } } + '/notepad/files/list', { params: { page, limit, sort_by: sortBy } } ), verifyPassword: (password: string) => api.post('/notepad/files/verify-password', { password }), diff --git a/frontend/src/views/EditorView.vue b/frontend/src/views/EditorView.vue index 5e1d728..b75d707 100644 --- a/frontend/src/views/EditorView.vue +++ b/frontend/src/views/EditorView.vue @@ -344,10 +344,16 @@ const loadFile = async (filename: string) => { const response = await notesApi.getFile(filename) editorStore.setContent(response.data.content) editorStore.setSaveStatus('saved') - } catch (error) { + } catch (error: any) { console.error('Load failed:', error) - editorStore.setContent('') - editorStore.setSaveStatus('error') + if (error.response?.status === 401 || error.response?.status === 403) { + // 认证过期或未授权,跳转到文件列表页面 + disconnect() + router.push('/list') + } else { + editorStore.setContent('') + editorStore.setSaveStatus('error') + } } finally { editorStore.setLoading(false) } @@ -367,6 +373,19 @@ onMounted(() => { emitter.on('connect-websocket', (filename: string) => { connect(filename) }) + + // 监听认证过期事件 + const handleAuthExpired = () => { + disconnect() + router.push('/list') + } + + window.addEventListener('auth-expired', handleAuthExpired) + + // 组件卸载时移除事件监听器 + onUnmounted(() => { + window.removeEventListener('auth-expired', handleAuthExpired) + }) }) onUnmounted(() => { diff --git a/frontend/src/views/FileListView.vue b/frontend/src/views/FileListView.vue index 8c32004..ea51c87 100644 --- a/frontend/src/views/FileListView.vue +++ b/frontend/src/views/FileListView.vue @@ -52,6 +52,11 @@ 清除 +
+ +
新建文件 @@ -100,7 +105,8 @@ {{ file.preview }}
- 创建时间: {{ formatDate(file.created_at) }} | + 创建: {{ formatDate(file.created_at) }} | + 修改: {{ formatDate(file.updated_at) }} | 大小: {{ formatFileSize(file.size) }}
@@ -165,7 +171,7 @@ @@ -630,6 +657,11 @@ onMounted(() => { background-color: #7f8c8d; } +.sort-box { + display: flex; + align-items: center; +} + .header-actions { display: flex; gap: 1rem; @@ -683,6 +715,7 @@ onMounted(() => { .header-right { flex-direction: column; + align-items: stretch; gap: 1rem; } @@ -701,6 +734,14 @@ onMounted(() => { gap: 0.5rem; } + .sort-box { + width: 100%; + } + + .sort-box .btn { + width: 100%; + } + .header-actions .btn { flex: 1; }