- 新增 VideoThumbnailService,基于 ffmpeg 截取视频缩略图,ffprobe 提取时长
- 新增 ManagedThumbnailMap 模型及多数据库迁移,存储缩略图元数据
- 新增 /api/thumbnails/{id} 缩略图流端点
- 新增最近添加/最近播放 API 与前端面板,支持列表/网格双视图切换
- FileRecordDto 扩展 thumbnailUrl、videoDuration、lastPlayedAt 字段
- 前端新增文件库 Tab 导航、卡片网格视图、视频海报与时长信息栏
- 添加文件库目录不再同步全量扫描,改为后台异步自动扫描
111 lines
3.5 KiB
TypeScript
111 lines
3.5 KiB
TypeScript
import { apiUrl, request } from './http'
|
|
|
|
export type MediaType = 'all' | 'text' | 'video' | 'audio'
|
|
|
|
export interface DriveDto {
|
|
name: string
|
|
displayName: string
|
|
rootDirectory: string
|
|
driveType: string
|
|
totalSize: number | null
|
|
availableFreeSpace: number | null
|
|
isReady: boolean
|
|
}
|
|
|
|
export interface DirectoryDto {
|
|
name: string
|
|
fullPath: string
|
|
}
|
|
|
|
export interface LibraryRootDto {
|
|
id: number
|
|
path: string
|
|
displayName: string
|
|
isEnabled: boolean
|
|
isAvailable: boolean
|
|
scanIntervalMinutes: number
|
|
lastScanStartedAt: string | null
|
|
lastScanCompletedAt: string | null
|
|
lastScanError: string | null
|
|
fileCount: number
|
|
}
|
|
|
|
export interface FileRecordDto {
|
|
id: number
|
|
libraryRootId: number
|
|
fileName: string
|
|
relativePath: string
|
|
extension: string
|
|
sizeBytes: number
|
|
lastWriteTimeUtc: string
|
|
mediaType: 'text' | 'video' | 'audio'
|
|
contentType: string
|
|
streamUrl: string
|
|
textUrl: string | null
|
|
browserPlayable: boolean
|
|
thumbnailUrl: string | null
|
|
videoDuration: number | null
|
|
lastPlayedAt: string | null
|
|
}
|
|
|
|
export interface BrowseDirectoryResponse {
|
|
currentPath: string
|
|
subdirectories: string[]
|
|
files: FileRecordDto[]
|
|
}
|
|
|
|
export interface TextPreviewDto {
|
|
id: number
|
|
fileName: string
|
|
content: string
|
|
truncated: boolean
|
|
}
|
|
|
|
export interface PagedResponse<T> {
|
|
items: T[]
|
|
total: number
|
|
page: number
|
|
pageSize: number
|
|
totalPages: number
|
|
}
|
|
|
|
const qs = (params: Record<string, string | number | undefined | null>) => {
|
|
const search = new URLSearchParams()
|
|
for (const [key, value] of Object.entries(params)) {
|
|
if (value !== undefined && value !== null && value !== '') search.set(key, String(value))
|
|
}
|
|
const value = search.toString()
|
|
return value ? `?${value}` : ''
|
|
}
|
|
|
|
// 业务接口定义,新增接口在此处添加一行即可
|
|
export const api = {
|
|
getUser: () => request('getUser'),
|
|
processData: (input: string) => request('processData', { method: 'POST', body: { input } }),
|
|
wData: (input: string) => request('wData', { method: 'POST', body: { input } }),
|
|
getDrives: () => request<DriveDto[]>('library/drives'),
|
|
getDirectories: (path: string) => request<DirectoryDto[]>(`library/directories${qs({ path })}`),
|
|
getRoots: () => request<LibraryRootDto[]>('library/roots'),
|
|
addRoot: (body: { path: string; displayName?: string; scanIntervalMinutes?: number }) =>
|
|
request<LibraryRootDto>('library/roots', { method: 'POST', body }),
|
|
setRootEnabled: (id: number, isEnabled: boolean) =>
|
|
request<LibraryRootDto>('library/roots/enabled', { method: 'POST', body: { id, isEnabled } }),
|
|
deleteRoot: (id: number) =>
|
|
request('library/roots/delete', { method: 'POST', body: { id } }),
|
|
scanRoot: (id: number) =>
|
|
request<LibraryRootDto>('library/roots/scan', { method: 'POST', body: { id } }),
|
|
searchFiles: (params: { page: number; pageSize: number; mediaType?: MediaType; keyword?: string; rootId?: number }) =>
|
|
request<PagedResponse<FileRecordDto>>(`files${qs(params)}`),
|
|
browseDirectory: (rootId: number, path: string) =>
|
|
request<BrowseDirectoryResponse>(`files/browse${qs({ rootId, path })}`),
|
|
getTextPreview: (id: number) =>
|
|
request<TextPreviewDto>(`files/text${qs({ id })}`),
|
|
mediaUrl: (path: string) => apiUrl(path),
|
|
thumbnailUrl: (path: string) => apiUrl(path),
|
|
getRecentFiles: (type: string, count = 12) =>
|
|
request<FileRecordDto[]>(`files/recent${qs({ type, count })}`),
|
|
markFilePlayed: (id: number) =>
|
|
request('files/played', { method: 'POST', body: { id } }),
|
|
qrCode: () => request<{ url: string; qrCodeBase64: string }>('qrcode'),
|
|
}
|