refactor: 将 WebUI 源码纳入仓库,移除打包产物

- 新增 webui/ 前端源码目录(React+Vite+TypeScript+Tailwind)
- 删除 api/webui/ 中旧的打包静态资源
- 更新 .gitignore 忽略 api/webui/ 构建输出和 webui/node_modules
- 更新 README 增加前端构建说明
This commit is contained in:
程序员阿江(Relakkes)
2026-07-01 23:05:03 +08:00
parent 2ac0716c41
commit 65ddaa8828
68 changed files with 9064 additions and 356 deletions
+153
View File
@@ -0,0 +1,153 @@
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,
}),
}))