修复cookie过期bug,增加排序字段切换

This commit is contained in:
yuany3721 2025-12-31 22:42:34 +08:00
parent e3452e6a3c
commit a4cf9b310b
6 changed files with 90 additions and 16 deletions

View File

@ -34,11 +34,17 @@ async def verify_password(request: PasswordRequest):
@router.get("/list", response_model=FileListResponse, dependencies=[Depends(verify_token)]) @router.get("/list", response_model=FileListResponse, dependencies=[Depends(verify_token)])
async def get_files_list( 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: 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() total = file_service.get_total_files_count()
return FileListResponse(files=files, total=total, page=page, limit=limit) return FileListResponse(files=files, total=total, page=page, limit=limit)

View File

@ -12,6 +12,7 @@ class TextFile(BaseModel):
class FileListItem(BaseModel): class FileListItem(BaseModel):
filename: str filename: str
created_at: datetime created_at: datetime
updated_at: datetime
size: int size: int
preview: Optional[str] = None preview: Optional[str] = None

View File

@ -95,7 +95,7 @@ class FileService:
"""创建新文件""" """创建新文件"""
return self.write_file(filename, "") 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: if page < 1:
page = 1 page = 1
@ -125,6 +125,7 @@ class FileService:
FileListItem( FileListItem(
filename=file_path.name, filename=file_path.name,
created_at=datetime.fromtimestamp(stat.st_ctime), created_at=datetime.fromtimestamp(stat.st_ctime),
updated_at=datetime.fromtimestamp(stat.st_mtime),
size=stat.st_size, size=stat.st_size,
preview=preview, preview=preview,
) )
@ -132,6 +133,10 @@ class FileService:
except Exception as e: except Exception as e:
raise RuntimeError(f"Error listing files: {str(e)}") raise RuntimeError(f"Error listing files: {str(e)}")
# 根据参数排序
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) files.sort(key=lambda x: x.created_at, reverse=True)
start = (page - 1) * limit start = (page - 1) * limit

View File

@ -21,9 +21,10 @@ api.interceptors.response.use(
if (error.response?.status === 401) { if (error.response?.status === 401) {
// 令牌过期,清除本地存储 // 令牌过期,清除本地存储
localStorage.removeItem('authToken') localStorage.removeItem('authToken')
// 只在非密码验证页面刷新 // 只在非密码验证页面触发认证状态变化事件
if (!error.config.url?.includes('verify-password')) { if (!error.config.url?.includes('verify-password')) {
window.location.reload() // 通过自定义事件通知应用认证状态变化
window.dispatchEvent(new CustomEvent('auth-expired'))
} }
} }
return Promise.reject(error) return Promise.reject(error)
@ -41,6 +42,7 @@ export interface TextFile {
export interface FileListItem { export interface FileListItem {
filename: string filename: string
created_at: string created_at: string
updated_at: string
size: number size: number
preview?: string preview?: string
} }
@ -64,9 +66,9 @@ export const notesApi = {
} }
export const filesApi = { export const filesApi = {
getList: (page = 1, limit = 50) => getList: (page = 1, limit = 50, sortBy = 'created_at') =>
api.get<FileListResponse>( api.get<FileListResponse>(
'/notepad/files/list', { params: { page, limit } } '/notepad/files/list', { params: { page, limit, sort_by: sortBy } }
), ),
verifyPassword: (password: string) => verifyPassword: (password: string) =>
api.post<AuthToken>('/notepad/files/verify-password', { password }), api.post<AuthToken>('/notepad/files/verify-password', { password }),

View File

@ -344,10 +344,16 @@ const loadFile = async (filename: string) => {
const response = await notesApi.getFile(filename) const response = await notesApi.getFile(filename)
editorStore.setContent(response.data.content) editorStore.setContent(response.data.content)
editorStore.setSaveStatus('saved') editorStore.setSaveStatus('saved')
} catch (error) { } catch (error: any) {
console.error('Load failed:', error) console.error('Load failed:', error)
if (error.response?.status === 401 || error.response?.status === 403) {
//
disconnect()
router.push('/list')
} else {
editorStore.setContent('') editorStore.setContent('')
editorStore.setSaveStatus('error') editorStore.setSaveStatus('error')
}
} finally { } finally {
editorStore.setLoading(false) editorStore.setLoading(false)
} }
@ -367,6 +373,19 @@ onMounted(() => {
emitter.on('connect-websocket', (filename: string) => { emitter.on('connect-websocket', (filename: string) => {
connect(filename) connect(filename)
}) })
//
const handleAuthExpired = () => {
disconnect()
router.push('/list')
}
window.addEventListener('auth-expired', handleAuthExpired)
//
onUnmounted(() => {
window.removeEventListener('auth-expired', handleAuthExpired)
})
}) })
onUnmounted(() => { onUnmounted(() => {

View File

@ -52,6 +52,11 @@
清除 清除
</button> </button>
</div> </div>
<div class="sort-box">
<button @click="toggleSort" class="btn btn-secondary btn-sm">
{{ sortBy === 'created_at' ? '按创建时间' : '按修改时间' }}
</button>
</div>
<div class="header-actions"> <div class="header-actions">
<RouterLink to="/" class="btn btn-primary"> <RouterLink to="/" class="btn btn-primary">
新建文件 新建文件
@ -100,7 +105,8 @@
{{ file.preview }} {{ file.preview }}
</div> </div>
<div class="file-meta"> <div class="file-meta">
创建时间: {{ formatDate(file.created_at) }} | 创建: {{ formatDate(file.created_at) }} |
修改: {{ formatDate(file.updated_at) }} |
大小: {{ formatFileSize(file.size) }} 大小: {{ formatFileSize(file.size) }}
</div> </div>
</div> </div>
@ -165,7 +171,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, nextTick, watch } from 'vue' import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue'
import { RouterLink, useRouter } from 'vue-router' import { RouterLink, useRouter } from 'vue-router'
import { filesApi, type FileListItem } from '@/services/api' import { filesApi, type FileListItem } from '@/services/api'
import { emitter } from '@/utils/eventBus' import { emitter } from '@/utils/eventBus'
@ -176,6 +182,7 @@ const filteredFiles = ref<FileListItem[]>([])
const loading = ref(false) const loading = ref(false)
const error = ref<string | null>(null) const error = ref<string | null>(null)
const searchQuery = ref('') const searchQuery = ref('')
const sortBy = ref<'created_at' | 'updated_at'>('updated_at')
const deleting = ref<string | null>(null) const deleting = ref<string | null>(null)
const renamingFile = ref<string | null>(null) const renamingFile = ref<string | null>(null)
const newFilename = ref('') const newFilename = ref('')
@ -190,13 +197,14 @@ const fetchFiles = async () => {
error.value = null error.value = null
try { try {
const response = await filesApi.getList() const response = await filesApi.getList(1, 50, sortBy.value)
files.value = response.data.files files.value = response.data.files
filterFiles() filterFiles()
} catch (err: any) { } catch (err: any) {
if (err.response?.status === 401) { if (err.response?.status === 401 || err.response?.status === 403) {
// //
isAuthenticated.value = false isAuthenticated.value = false
error.value = null
} else { } else {
error.value = err instanceof Error ? err.message : '获取文件列表失败' error.value = err instanceof Error ? err.message : '获取文件列表失败'
} }
@ -342,6 +350,11 @@ const confirmRename = async () => {
} }
} }
const toggleSort = () => {
sortBy.value = sortBy.value === 'created_at' ? 'updated_at' : 'created_at'
fetchFiles()
}
const checkAuth = () => { const checkAuth = () => {
const token = localStorage.getItem('authToken') const token = localStorage.getItem('authToken')
if (token) { if (token) {
@ -392,6 +405,20 @@ const getDisplayName = (filename: string) => {
onMounted(() => { onMounted(() => {
checkAuth() checkAuth()
//
const handleAuthExpired = () => {
isAuthenticated.value = false
error.value = null
files.value = []
}
window.addEventListener('auth-expired', handleAuthExpired)
//
onUnmounted(() => {
window.removeEventListener('auth-expired', handleAuthExpired)
})
}) })
</script> </script>
@ -630,6 +657,11 @@ onMounted(() => {
background-color: #7f8c8d; background-color: #7f8c8d;
} }
.sort-box {
display: flex;
align-items: center;
}
.header-actions { .header-actions {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
@ -683,6 +715,7 @@ onMounted(() => {
.header-right { .header-right {
flex-direction: column; flex-direction: column;
align-items: stretch;
gap: 1rem; gap: 1rem;
} }
@ -701,6 +734,14 @@ onMounted(() => {
gap: 0.5rem; gap: 0.5rem;
} }
.sort-box {
width: 100%;
}
.sort-box .btn {
width: 100%;
}
.header-actions .btn { .header-actions .btn {
flex: 1; flex: 1;
} }