Files

178 lines
6.4 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import type { TorrentView } from '~/types/api'
interface SearchItem {
_id: string
keyword: string
providers: string[]
resultCount: number
durationMs: number
ok: boolean
error: string | null
createdAt: string
}
const { data, refresh, pending } = await useFetch<{
items: SearchItem[]
hot: { keyword: string; count: number; last: string }[]
total: number
torrentTotal: number
offline: boolean
}>('/api/history')
const torrents = ref<TorrentView[]>([])
const torrentsPending = ref(false)
async function loadTorrents() {
torrentsPending.value = true
try {
const res = await $fetch<{ items: TorrentView[] }>('/api/torrents', { query: { limit: 30 } })
torrents.value = res.items
} catch {
torrents.value = []
} finally {
torrentsPending.value = false
}
}
async function removeOne(id: string) {
try {
await $fetch('/api/history', { method: 'DELETE', query: { id } })
pushToast('ok', '已删除该条记录')
await refresh()
} catch (err) {
pushToast('err', '删除失败', errorMessage(err))
}
}
async function clearAll() {
if (!confirm('确定清空全部搜索历史?')) return
try {
const res = await $fetch<{ deleted: number }>('/api/history', { method: 'DELETE' })
pushToast('ok', `已清空 ${res.deleted} 条记录`)
await refresh()
} catch (err) {
pushToast('err', '清空失败', errorMessage(err))
}
}
onMounted(loadTorrents)
</script>
<template>
<div class="page">
<section class="rise">
<p class="label">HISTORY · LIBRARY</p>
<h1>搜索历史与种子库</h1>
<p class="sub">每次搜索都会落到 MongoDB 的 searches 集合;搜到和解析过的种子按 infoHash 去重存在 torrents 集合。</p>
</section>
<div v-if="data?.offline" class="note warn mt rise rise-2">
MongoDB 未连接,历史记录不可用。启动本地 MongoDB 后刷新本页即可。
</div>
<div class="grid-4 mt rise rise-2">
<div class="stat"><span class="label">搜索总次数</span><span class="v">{{ formatNumber(data?.total) }}</span></div>
<div class="stat"><span class="label">库中种子数</span><span class="v">{{ formatNumber(data?.torrentTotal) }}</span></div>
<div class="stat"><span class="label">高频关键词</span><span class="v">{{ data?.hot?.length || 0 }}</span></div>
<div class="stat">
<span class="label">状态</span>
<span class="v small" :class="data?.offline ? 'err' : 'ok'">{{ data?.offline ? '未连接' : '正常' }}</span>
</div>
</div>
<div v-if="data?.hot?.length" class="panel mt rise rise-3">
<div class="panel-hd"><span class="label">TOP KEYWORDS</span><span class="spacer" /><span class="mute small">点击直接再搜一次</span></div>
<div class="panel-bd row wrap">
<NuxtLink
v-for="h in data.hot"
:key="h.keyword"
class="tag acc"
:to="{ path: '/', query: { q: h.keyword } }"
style="padding: 4px 10px; font-size: 12px"
>
{{ h.keyword }} ×{{ h.count }}
</NuxtLink>
</div>
</div>
<div class="panel mt rise rise-3">
<div class="panel-hd">
<span class="label">SEARCH LOG</span>
<span class="spacer" />
<button class="btn btn-sm btn-ghost" :disabled="pending" @click="refresh()">
{{ pending ? '刷新中…' : '刷新' }}
</button>
<button class="btn btn-sm btn-danger" :disabled="!data?.items?.length" @click="clearAll">清空</button>
</div>
<div class="panel-bd tight">
<table v-if="data?.items?.length" class="table">
<thead>
<tr>
<th style="width: 42%">关键词</th>
<th class="right">结果数</th>
<th class="right hide-sm">耗时</th>
<th class="hide-sm">源</th>
<th class="right">时间</th>
<th class="right">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="it in data.items" :key="it._id">
<td>
<NuxtLink class="name" :to="{ path: '/', query: { q: it.keyword } }">{{ it.keyword }}</NuxtLink>
<span v-if="!it.ok && it.error" class="small err" style="display: block">{{ it.error }}</span>
</td>
<td class="num right" :class="it.ok ? '' : 'err'">{{ it.resultCount }}</td>
<td class="num right dim hide-sm">{{ it.durationMs }}ms</td>
<td class="hide-sm"><span class="tag">{{ it.providers?.join(' + ') || '—' }}</span></td>
<td class="num right dim">{{ formatRelative(it.createdAt) }}</td>
<td class="right">
<button class="btn btn-sm btn-ghost" @click="removeOne(it._id)">删除</button>
</td>
</tr>
</tbody>
</table>
<div v-else class="empty">还没有搜索记录</div>
</div>
</div>
<div class="panel mt rise rise-4">
<div class="panel-hd">
<span class="label">RECENT TORRENTS</span>
<span class="spacer" />
<button class="btn btn-sm btn-ghost" :disabled="torrentsPending" @click="loadTorrents">
{{ torrentsPending ? '加载中…' : '刷新' }}
</button>
</div>
<div class="panel-bd tight">
<table v-if="torrents.length" class="table">
<thead>
<tr>
<th style="width: 48%">名称</th>
<th>来源</th>
<th class="right">大小</th>
<th class="right hide-sm">文件</th>
<th class="right">更新</th>
<th class="right">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="t in torrents" :key="t.infoHash">
<td class="name">{{ t.name }}</td>
<td><span class="tag">{{ t.source }}</span></td>
<td class="num right">{{ formatBytes(t.size) }}</td>
<td class="num right dim hide-sm">{{ t.files ? t.files.length : t.fileCount ?? '未解析' }}</td>
<td class="num right dim">{{ formatRelative(t.resolvedAt || t.creationDate) }}</td>
<td class="right">
<NuxtLink class="btn btn-sm" :to="{ path: `/torrent/${t.infoHash}`, query: { magnet: t.magnet } }">打开</NuxtLink>
</td>
</tr>
</tbody>
</table>
<div v-else class="empty">库里还没有种子</div>
</div>
</div>
</div>
</template>