mirror of
https://github.com/NanmiCoder/MediaCrawler.git
synced 2026-09-19 19:07:55 +08:00
- 新增 webui/ 前端源码目录(React+Vite+TypeScript+Tailwind) - 删除 api/webui/ 中旧的打包静态资源 - 更新 .gitignore 忽略 api/webui/ 构建输出和 webui/node_modules - 更新 README 增加前端构建说明
154 lines
4.3 KiB
TypeScript
154 lines
4.3 KiB
TypeScript
import { create } from 'zustand'
|
||
import type { LogEntry, CrawlerConfig } from '@/types/crawler'
|
||
|
||
interface CrawlerState {
|
||
// Status
|
||
status: 'idle' | 'running' | 'stopping' | 'error'
|
||
platform: string | null
|
||
crawlerType: string | null
|
||
startedAt: string | null
|
||
|
||
// Logs
|
||
logs: LogEntry[]
|
||
clearedAfterLogId: number | null // 清除日志后,只显示 id 大于此值的日志
|
||
|
||
// Config
|
||
config: CrawlerConfig
|
||
|
||
// Actions
|
||
setStatus: (status: CrawlerState['status']) => void
|
||
setRunningInfo: (platform: string | null, crawlerType: string | null, startedAt: string | null) => void
|
||
addLog: (log: LogEntry) => void
|
||
setLogs: (logs: LogEntry[]) => void
|
||
clearLogs: () => void
|
||
restoreLogs: () => void
|
||
updateConfig: (config: Partial<CrawlerConfig>) => void
|
||
reset: () => void
|
||
}
|
||
|
||
// 持久化相关的 localStorage key
|
||
const CLEARED_LOG_ID_KEY = 'mediacrawler_cleared_log_id'
|
||
|
||
// 从 localStorage 读取清除标记
|
||
function getClearedLogIdFromStorage(): number | null {
|
||
const stored = localStorage.getItem(CLEARED_LOG_ID_KEY)
|
||
if (stored === null) return null
|
||
const value = parseInt(stored, 10)
|
||
return isNaN(value) ? null : value
|
||
}
|
||
|
||
// 保存清除标记到 localStorage
|
||
function saveClearedLogIdToStorage(id: number | null): void {
|
||
if (id === null) {
|
||
localStorage.removeItem(CLEARED_LOG_ID_KEY)
|
||
} else {
|
||
localStorage.setItem(CLEARED_LOG_ID_KEY, id.toString())
|
||
}
|
||
}
|
||
|
||
const defaultConfig: CrawlerConfig = {
|
||
platform: 'bili',
|
||
login_type: 'qrcode',
|
||
crawler_type: 'search',
|
||
keywords: '',
|
||
specified_ids: '',
|
||
creator_ids: '',
|
||
start_page: 1,
|
||
enable_comments: true,
|
||
enable_sub_comments: false,
|
||
save_option: 'json',
|
||
cookies: '',
|
||
headless: false,
|
||
}
|
||
|
||
export const useCrawlerStore = create<CrawlerState>((set, get) => ({
|
||
status: 'idle',
|
||
platform: null,
|
||
crawlerType: null,
|
||
startedAt: null,
|
||
logs: [],
|
||
clearedAfterLogId: getClearedLogIdFromStorage(), // 从 localStorage 初始化
|
||
config: defaultConfig,
|
||
|
||
setStatus: (status) => {
|
||
set({ status })
|
||
// 当开始新的爬虫任务时,清除之前的清除标记
|
||
if (status === 'running') {
|
||
const currentClearedId = get().clearedAfterLogId
|
||
if (currentClearedId !== null) {
|
||
set({ clearedAfterLogId: null })
|
||
saveClearedLogIdToStorage(null)
|
||
}
|
||
}
|
||
},
|
||
|
||
setRunningInfo: (platform, crawlerType, startedAt) => {
|
||
set({ platform, crawlerType, startedAt })
|
||
// 当设置新的运行信息时,也清除之前的清除标记
|
||
if (startedAt !== null) {
|
||
const currentClearedId = get().clearedAfterLogId
|
||
if (currentClearedId !== null) {
|
||
set({ clearedAfterLogId: null })
|
||
saveClearedLogIdToStorage(null)
|
||
}
|
||
}
|
||
},
|
||
|
||
addLog: (log) => {
|
||
const { clearedAfterLogId, logs } = get()
|
||
// 如果有清除标记,过滤掉 id 小于等于该标记的日志
|
||
if (clearedAfterLogId !== null && log.id <= clearedAfterLogId) {
|
||
return
|
||
}
|
||
// 防止 WebSocket 重连/重复连接导致的重复日志
|
||
if (logs.length > 0 && logs[logs.length - 1].id === log.id) {
|
||
return
|
||
}
|
||
if (logs.some((existing) => existing.id === log.id)) {
|
||
return
|
||
}
|
||
set((state) => ({
|
||
logs: [...state.logs.slice(-499), log], // Keep last 500 logs
|
||
}))
|
||
},
|
||
|
||
setLogs: (logs) => {
|
||
const { clearedAfterLogId } = get()
|
||
// 如果有清除标记,过滤掉 id 小于等于该标记的日志
|
||
const filteredLogs = clearedAfterLogId !== null
|
||
? logs.filter((log) => log.id > clearedAfterLogId)
|
||
: logs
|
||
set({ logs: filteredLogs })
|
||
},
|
||
|
||
clearLogs: () => {
|
||
const { logs } = get()
|
||
// 记录当前最大的 log id,清除后只显示比这个 id 大的日志
|
||
const maxLogId = logs.length > 0 ? Math.max(...logs.map(l => l.id)) : 0
|
||
set({ logs: [], clearedAfterLogId: maxLogId })
|
||
// 持久化到 localStorage
|
||
saveClearedLogIdToStorage(maxLogId)
|
||
},
|
||
|
||
restoreLogs: () => {
|
||
// 清除清除标记,这样下次重新加载日志时就会显示所有日志
|
||
set({ clearedAfterLogId: null })
|
||
saveClearedLogIdToStorage(null)
|
||
// 触发日志重新加载(通过刷新页面或重新连接 WebSocket)
|
||
window.location.reload()
|
||
},
|
||
|
||
updateConfig: (config) =>
|
||
set((state) => ({
|
||
config: { ...state.config, ...config },
|
||
})),
|
||
|
||
reset: () =>
|
||
set({
|
||
status: 'idle',
|
||
platform: null,
|
||
crawlerType: null,
|
||
startedAt: null,
|
||
}),
|
||
}))
|