修复cookie过期bug,增加排序字段切换
This commit is contained in:
parent
e3452e6a3c
commit
a4cf9b310b
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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,7 +133,11 @@ 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)}")
|
||||||
|
|
||||||
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
|
start = (page - 1) * limit
|
||||||
end = start + limit
|
end = start + limit
|
||||||
|
|||||||
@ -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 }),
|
||||||
|
|||||||
@ -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)
|
||||||
editorStore.setContent('')
|
if (error.response?.status === 401 || error.response?.status === 403) {
|
||||||
editorStore.setSaveStatus('error')
|
// 认证过期或未授权,跳转到文件列表页面
|
||||||
|
disconnect()
|
||||||
|
router.push('/list')
|
||||||
|
} else {
|
||||||
|
editorStore.setContent('')
|
||||||
|
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(() => {
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user