Compare commits

...
1 Commits
Author SHA1 Message Date
kevin 7ba2011ead feat: priority crawl UI to dashboard 2026-04-11 19:54:24 +08:00
3 changed files with 59 additions and 189 deletions
+1 -3
View File
@@ -2,7 +2,6 @@
import { ref } from 'vue'
import Dashboard from './views/Dashboard.vue'
import RecentCrawls from './views/RecentCrawls.vue'
import PriorityCrawl from './views/PriorityCrawl.vue'
import SearchView from './views/SearchView.vue'
const tab = ref('dashboard')
@@ -11,7 +10,6 @@ const nav = [
{ id: 'dashboard', label: '概览', icon: '📊' },
{ id: 'recent', label: '最近', icon: '🕷️' },
{ id: 'search', label: '搜索', icon: '🔍' },
{ id: 'priority', label: '插入', icon: '🚀' },
]
</script>
@@ -49,7 +47,7 @@ const nav = [
<Dashboard v-if="tab === 'dashboard'" />
<RecentCrawls v-else-if="tab === 'recent'" />
<SearchView v-else-if="tab === 'search'" />
<PriorityCrawl v-else-if="tab === 'priority'" />
</main>
<!-- Mobile Bottom Navigation -->
+58 -2
View File
@@ -1,6 +1,6 @@
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { fetchStats, flushIndex, fetchFlushStatus, fetchWorkers, setWorkers, fetchBacklink, triggerBacklink, fetchPriorityStatus, fetchCrawlStatus } from '../api.js'
import { fetchStats, flushIndex, fetchFlushStatus, fetchWorkers, setWorkers, fetchBacklink, triggerBacklink, fetchPriorityStatus, fetchCrawlStatus, addPriority } from '../api.js'
const stats = ref(null)
const loading = ref(true)
@@ -15,7 +15,9 @@ const workersInput = ref(0)
const workersSaving = ref(false)
// Priority 相关状态
const priorityStatus = ref(null) // { pending, active, max_workers, children_queue }
const priorityStatus = ref(null) // { level1, level2_queue, level2_inflight, active, max_workers }
const batchUrls = ref('')
const batchAdding = ref(false)
// 爬取状态
const crawlStatus = ref(null) // { current_epoch, max_epoch, queue_length, completed_count, visited_total, is_running }
@@ -172,6 +174,44 @@ function langColor(lang) {
const map = { zh: '#e53e3e', en: '#3182ce', ja: '#e53e3e', ko: '#3182ce', fr: '#38a169', de: '#d69e2e', es: '#38a169', ru: '#805ad5', other: '#718096' }
return map[lang] || map.other
}
function isValidUrl(s) {
try {
const u = new URL(s)
return u.protocol === 'http:' || u.protocol === 'https:'
} catch {
return false
}
}
async function doBatchAdd() {
const lines = batchUrls.value.split('\n').map(l => l.trim()).filter(l => l)
const valid = lines.filter(isValidUrl)
if (valid.length === 0) {
error.value = '未检测到有效 URL'
return
}
batchAdding.value = true
let ok = 0, fail = 0
try {
for (const url of valid) {
try {
await addPriority(url)
ok++
} catch {
fail++
}
}
batchUrls.value = ''
await loadPriorityStatus()
error.value = null
if (fail > 0) {
error.value = `添加完成:${ok} 成功,${fail} 失败`
}
} finally {
batchAdding.value = false
}
}
</script>
<template>
@@ -255,6 +295,22 @@ function langColor(lang) {
<div class="text-2xl font-bold text-blue-400">{{ priorityStatus.max_workers }}</div>
</div>
</div>
<!-- 批量添加 -->
<div class="mt-4 flex gap-2">
<textarea
v-model="batchUrls"
rows="3"
placeholder="批量添加 URL(每行一个,仅发送有效链接)"
class="flex-1 bg-gray-800 border border-gray-700 text-gray-200 text-sm rounded-lg px-3 py-2 resize-none focus:outline-none focus:border-blue-500 placeholder-gray-600"
></textarea>
<button
class="bg-blue-700 hover:bg-blue-600 disabled:bg-gray-700 text-white text-sm font-medium px-4 py-2 rounded-lg transition-colors cursor-pointer whitespace-nowrap self-end"
:disabled="batchAdding || !batchUrls.trim()"
@click="doBatchAdd"
>
{{ batchAdding ? '添加中...' : '批量添加' }}
</button>
</div>
</div>
<!-- 爬取进度 -->
-184
View File
@@ -1,184 +0,0 @@
<script setup>
import { ref, onMounted } from 'vue'
import { fetchPriority, addPriority, removePriority } from '../api.js'
const items = ref([])
const loading = ref(true)
const error = ref(null)
const submitting = ref(false)
const submitError = ref(null)
const submitSuccess = ref(false)
const inputUrl = ref('')
async function load() {
loading.value = true
error.value = null
try {
const data = await fetchPriority()
items.value = data.items || []
} catch (e) {
error.value = '加载失败,请检查人服务器是否启动'
console.error(e)
} finally {
loading.value = false
}
}
async function submit() {
const raw = inputUrl.value.trim()
if (!raw) return
submitting.value = true
submitError.value = null
submitSuccess.value = false
try {
await addPriority(raw)
inputUrl.value = ''
submitSuccess.value = true
setTimeout(() => { submitSuccess.value = false }, 3000)
await load()
} catch (e) {
submitError.value = e?.response?.data?.error || '添加失败'
} finally {
submitting.value = false
}
}
async function del(url) {
try {
await removePriority(url)
await load()
} catch (e) {
error.value = '删除失败'
console.error(e)
}
}
function fmtTime(ts) {
if (!ts) return '-'
return new Date(ts * 1000).toLocaleString('zh-CN', {
month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit'
})
}
onMounted(load)
</script>
<template>
<div class="p-4 md:p-8">
<h1 class="text-xl md:text-2xl font-semibold text-white mb-2">插入爬取</h1>
<p class="text-sm text-gray-500 mb-6 md:mb-8">
添加 URL 或域名下一轮爬取时会优先抓取纯域名会自动补全为 https://www.域名/。
</p>
<!-- Input -->
<div class="bg-gray-900 rounded-xl p-4 md:p-6 mb-4 md:mb-6 border border-gray-800">
<div class="flex flex-col sm:flex-row gap-3">
<input
v-model="inputUrl"
@keyup.enter="submit"
type="text"
placeholder="输入 URL 或域名,例如 https://example.com 或 example.com"
class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-4 py-2.5 text-gray-100 placeholder-gray-500 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 text-sm"
:disabled="submitting"
/>
<button
@click="submit"
:disabled="submitting || !inputUrl.trim()"
class="px-6 py-2.5 bg-blue-600 hover:bg-blue-500 disabled:bg-gray-700 disabled:text-gray-500 text-white text-sm font-medium rounded-lg transition-colors cursor-pointer whitespace-nowrap"
>
{{ submitting ? '添加中...' : '插入队列' }}
</button>
</div>
<!-- Error -->
<div v-if="submitError" class="mt-3 text-sm text-red-400">
{{ submitError }}
</div>
<!-- Success -->
<div v-if="submitSuccess" class="mt-3 text-sm text-green-400">
已添加到优先队列将在下一轮爬取时优先抓取
</div>
</div>
<!-- List -->
<div class="bg-gray-900 rounded-xl border border-gray-800">
<div class="px-4 md:px-6 py-3 md:py-4 border-b border-gray-800 flex items-center justify-between">
<span class="text-sm font-medium text-gray-300">待爬取队列</span>
<span class="text-xs text-gray-500">{{ items.length }} </span>
</div>
<!-- Loading -->
<div v-if="loading" class="p-8 text-center text-gray-500 text-sm">
加载中...
</div>
<!-- Error -->
<div v-else-if="error" class="p-8 text-center text-red-400 text-sm">
{{ error }}
</div>
<!-- Empty -->
<div v-else-if="items.length === 0" class="p-8 text-center text-gray-500 text-sm">
暂无待爬取的优先 URL
</div>
<!-- Desktop Table -->
<table v-else class="hidden md:table w-full text-sm">
<thead>
<tr class="text-left text-gray-500 text-xs border-b border-gray-800">
<th class="px-6 py-3 font-medium">URL</th>
<th class="px-6 py-3 font-medium w-28">类型</th>
<th class="px-6 py-3 font-medium w-40">添加时间</th>
<th class="px-6 py-3 font-medium w-16">操作</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-800">
<tr v-for="item in items" :key="item.url" class="hover:bg-gray-800/50">
<td class="px-6 py-3">
<span class="text-gray-300 break-all">{{ item.url }}</span>
</td>
<td class="px-6 py-3">
<span v-if="item.domain" class="inline-block px-2 py-0.5 text-xs rounded bg-purple-900 text-purple-300">域名</span>
<span v-else class="inline-block px-2 py-0.5 text-xs rounded bg-blue-900 text-blue-300">URL</span>
</td>
<td class="px-6 py-3 text-gray-500">{{ fmtTime(item.added_at) }}</td>
<td class="px-6 py-3">
<button
@click="del(item.url)"
class="text-red-400 hover:text-red-300 text-xs cursor-pointer"
>
删除
</button>
</td>
</tr>
</tbody>
</table>
<!-- Mobile Cards -->
<div class="md:hidden divide-y divide-gray-800">
<div
v-for="item in items"
:key="item.url"
class="p-4 hover:bg-gray-800/50 flex items-center justify-between gap-3"
>
<div class="flex-1 min-w-0">
<div class="text-gray-300 text-sm break-all mb-1">{{ item.url }}</div>
<div class="flex items-center gap-2">
<span v-if="item.domain" class="inline-block px-2 py-0.5 text-xs rounded bg-purple-900 text-purple-300">域名</span>
<span v-else class="inline-block px-2 py-0.5 text-xs rounded bg-blue-900 text-blue-300">URL</span>
<span class="text-xs text-gray-500">{{ fmtTime(item.added_at) }}</span>
</div>
</div>
<button
@click="del(item.url)"
class="text-red-400 hover:text-red-300 text-xs cursor-pointer shrink-0 px-2 py-1"
>
删除
</button>
</div>
</div>
</div>
</div>
</template>