import axios from 'axios' import type { SearchResponse, VideoParseResult, SaveResult, QueryResponse, RankingItem, Promotion, CloudConfig, StatsData, } from '../types' const api = axios.create({ baseURL: '/api', timeout: 30000, }) // 请求拦截器 — 添加管理员 Token api.interceptors.request.use((config) => { const token = localStorage.getItem('admin_token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截器 — 统一错误处理 api.interceptors.response.use( (res) => res, (err) => { if (err.response?.status === 401) { localStorage.removeItem('admin_token') // Don't redirect if already on the login page or if this was a login attempt itself if (!window.location.pathname.startsWith('/admin/login') && !err.config?.url?.includes('/admin/login')) { window.location.href = '/admin/login' } } return Promise.reject(err) } ) // ===== 搜索与解析 ===== export async function query(q: string, page = 1): Promise { const { data } = await api.post('/query', { q, page }) return data } /** * 流式搜索 — 使用 NDJSON stream,逐条返回验证结果 * callback 接收五种事件: * onSearching() - 搜索开始(立即返回) * onSaved({results, total}) - 本地已保存资源(DB缓存,即时返回) * onStats({total, channels, content_info, content_tags}) - 统计信息 * onResult(id, valid, message) - 单条链接验证结果 * onComplete({results, channels, total, filtered}) - 全部完成 */ export async function streamSearch( q: string, callbacks: { onSearching?: () => void onSaved?: (data: { results: any[]; total: number }) => void onStats: (stats: any) => void onResult: (id: string, valid: boolean, message?: string) => void onComplete: (data: any) => void onError?: (err: any) => void } ): Promise { const token = localStorage.getItem('admin_token') const headers: Record = { 'Content-Type': 'application/json', } if (token) { headers['Authorization'] = `Bearer ${token}` } try { const response = await fetch('/api/query', { method: 'POST', headers, body: JSON.stringify({ q }), }) if (!response.ok) { throw new Error(`HTTP ${response.status}`) } const reader = response.body!.getReader() const decoder = new TextDecoder() let buffer = '' while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const lines = buffer.split('\n') buffer = lines.pop() || '' for (const line of lines) { if (!line.trim()) continue try { const msg = JSON.parse(line) switch (msg.type) { case 'searching': callbacks.onSearching?.() break case 'saved': callbacks.onSaved?.(msg) break case 'stats': callbacks.onStats(msg) break case 'result': callbacks.onResult(msg.id, msg.valid, msg.message) break case 'complete': callbacks.onComplete(msg) break } } catch { // skip malformed lines } } } } catch (err: any) { callbacks.onError?.(err) } } export async function searchPanSou( kw: string, page = 1, pageSize = 20 ): Promise { const { data } = await api.get('/search', { params: { kw, page, page_size: pageSize }, }) return data } export async function parseVideo(url: string): Promise { const { data } = await api.post('/video/parse', { url }) return data } // ===== 保存与分享 ===== export async function saveToCloud(params: { type: 'search' | 'video' source: any target_cloud: string }): Promise { const { data } = await api.post('/save', params) return data } export async function saveVideoToCloud(params: { video_url: string title: string target_cloud: string }): Promise { const { data } = await api.post('/video/save-to-cloud', params) return data } // ===== 排行榜 ===== export async function getRankings(): Promise { const { data } = await api.get('/rankings') return data } export async function getHotKeywords(): Promise { const { data } = await api.get('/rankings/hot') return data } export async function getCategorizedRankings(): Promise { const { data } = await api.get('/rankings/categorized') return data } // ===== 管理员 ===== export async function adminLogin( username: string, password: string ): Promise<{ token: string }> { const { data } = await api.post('/admin/login', { username, password }) return data } export async function getMe(): Promise<{ loggedIn: boolean; id?: number; username?: string }> { const { data } = await api.get('/me') return data } export async function getCloudConfigs(): Promise { const { data } = await api.get('/admin/cloud-configs') return data } export async function saveCloudConfig( config: CloudConfig & { cookie?: string } ): Promise { const { data } = await api.post('/admin/cloud-configs', config) return data } export async function updateCloudConfig( config: CloudConfig & { cookie?: string } ): Promise { const { data } = await api.put(`/admin/cloud-configs/${config.id}`, config) return data } export async function testCloudConnection( cloudType: string, cookie?: string, id?: number ): Promise<{ success: boolean message: string nickname?: string storage_used?: string storage_total?: string }> { const { data } = await api.post(`/admin/cloud-configs/${cloudType}/test`, { cookie, id }) return data } export async function dailyCheckIn( id: number ): Promise<{ success: boolean message: string signedDays?: number }> { const { data } = await api.post(`/admin/cloud-configs/${id}/checkin`) return data } export async function skipCheckin(id: number): Promise { const { data } = await api.post(`/admin/cloud-configs/${id}/skip-checkin`) return data.success } export async function checkinAll(): Promise<{ total: number results: { id: number; nickname: string; success: boolean; message: string }[] }> { const { data } = await api.post('/admin/cloud-configs/checkin-all') return data } export async function checkinSummary(): Promise<{ total: number success: number failed: number pending: number skipped: number }> { const { data } = await api.get('/admin/cloud-configs/checkin-summary') return data } export async function deleteCloudConfig( id: number ): Promise { await api.delete(`/admin/cloud-configs/${id}`) } export async function setPrimary( id: number, primary: boolean ): Promise { const { data } = await api.put(`/admin/cloud-configs/${id}/primary`, { primary }) return data } export async function getStats(days?: number): Promise { const params: Record = {} if (days) params.days = days const { data } = await api.get('/admin/stats', { params }) return data } // ===== 转存日志 ===== export async function getSaveRecords(page = 1, pageSize = 20, startDate?: string, endDate?: string, status?: string, cloud?: string, keyword?: string): Promise<{ total: number records: SaveRecord[] summary?: { total: number; success: number; failed: number; reused: number } }> { const params: Record = { page, pageSize } if (startDate) params.startDate = startDate if (endDate) params.endDate = endDate if (status) params.status = status if (cloud) params.sourceType = cloud if (keyword) params.keyword = keyword const { data } = await api.get('/admin/save-records', { params }) return data } export interface SaveRecord { id: number source_type: string source_title: string | null source_url: string target_cloud: string share_url: string | null share_pwd: string | null file_size: string | null file_count: number duration_ms: number status: string error_message: string | null folder_name: string | null folder_count: number original_folder_name: string | null ip_address: string | null ip_location: string | null created_at: string } // ===== 系统配置 ===== /** Save/update per-config notification settings */ export async function saveConfigNotify( configId: number, settings: Record ): Promise<{ success: boolean; message: string }> { const { data } = await api.put(`/admin/cloud-configs/${configId}/notify`, settings) return data } /** Get per-config notification settings */ export async function getConfigNotify( configId: number ): Promise> { const { data } = await api.get(`/admin/cloud-configs/${configId}/notify`) return data } /** Test a notification channel (global or per-config) */ export async function getAllNotifierProviders(): Promise> { const { data } = await api.get('/admin/notify/providers') return data } export async function testNotifyChannel( channelType: string, configId?: number ): Promise<{ success: boolean; message: string }> { const { data } = await api.post('/admin/notify/test', { channelType, configId }) return data } export async function getSystemConfigs(): Promise<{ key: string; value: string; description: string }[]> { const { data } = await api.get('/admin/system-configs') return data } export async function updateSystemConfigs( entries: { key: string; value: string }[] ): Promise { await api.put('/admin/system-configs', { entries }) } // ===== 网盘类型开关 ===== export async function getCloudTypes(): Promise<{ types: { type: string; label: string; icon: string; enabled: boolean }[] }> { const { data } = await api.get('/admin/cloud-types') return data } export async function toggleCloudType(type: string, enabled: boolean): Promise { await api.put('/admin/cloud-types', { type, enabled }) } // ===== 修改密码 ===== export async function changePassword( oldPassword: string, newPassword: string ): Promise<{ success: boolean; message: string }> { const { data } = await api.post('/admin/change-password', { oldPassword, newPassword }) return data } export default api export { query as searchQuery } // ===== 系统设置(SettingsManage.vue) ===== export async function getSettings(): Promise { const { data } = await api.get('/admin/system-configs') return data } export async function updateSetting(key: string, value: string): Promise { await api.put('/admin/system-configs', { entries: [{ key, value }] }) } export async function uploadFallbackImage(file: File): Promise<{ success: boolean; url: string; message: string }> { const form = new FormData() form.append('image', file) const { data } = await api.post('/admin/upload-fallback-image', form, { headers: { 'Content-Type': 'multipart/form-data' }, }) return data } export async function uploadLogo(file: File): Promise<{ success: boolean; url: string; message: string }> { const form = new FormData() form.append('image', file) const { data } = await api.post('/admin/upload-logo', form, { headers: { 'Content-Type': 'multipart/form-data' }, }) return data } export async function getSiteConfig(): Promise<{ site_logo: string; site_name: string; search_fallback_image: string; site_disclaimer: string }> { const { data } = await api.get('/site-config') return data } // ===== Redis 连接测试 ===== export async function testRedisConnection(url: string): Promise<{ ok: boolean; latency: number; info: string }> { const { data } = await api.post('/admin/test-redis', { url }) return data } // ===== 外部服务连接测试 ===== export async function testExternalService(params: { type: 'pansou' | 'video_parser' | 'tmdb' | 'proxy' | 'ip_geo' url?: string token?: string }): Promise<{ ok: boolean; latency: number; info: string }> { const { data } = await api.post('/admin/test-external-service', params) return data } // ===== 数据库状态 ===== export async function getDbStatus(): Promise<{ db_size: string db_path: string save_records: number search_stats: number system_configs: number cloud_configs: number content_cache: number redis_status: string redis_url: string }> { const { data } = await api.get('/admin/db-status') return data } // ===== 存储清理 ===== export async function runCleanup(): Promise<{ success: boolean files_trashed: number logs_deleted: number trash_emptied: boolean errors: string[] message: string }> { const { data } = await api.post('/admin/cleanup/run') return data } export async function emptyAllTrash(): Promise<{ success: boolean emptied: boolean errors: string[] message: string }> { const { data } = await api.post('/admin/cleanup/empty-trash') return data }