feat: DSL 磁力聚合 — 搜索/解析/预览/投递 + cilisousuo 源 + 中英双语 + Docker 部署
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
import type { TorrentFileEntry } from '~/types/api'
|
||||
|
||||
const props = defineProps<{
|
||||
files: TorrentFileEntry[] | null
|
||||
total: number
|
||||
resolving?: boolean
|
||||
resolveError?: string | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{ resolve: []; preview: [file: TorrentFileEntry] }>()
|
||||
|
||||
const keyword = ref('')
|
||||
const sort = ref<'size' | 'name'>('size')
|
||||
|
||||
/** 图片且体积没超上限,才值得从 swarm 拉下来预览 */
|
||||
function previewable(file: TorrentFileEntry): boolean {
|
||||
return file.length > 0 && file.length <= MAX_IMAGE_PREVIEW_BYTES && isImagePath(file.path)
|
||||
}
|
||||
|
||||
const visible = computed(() => {
|
||||
let list = props.files || []
|
||||
if (keyword.value) {
|
||||
const kw = keyword.value.toLowerCase()
|
||||
list = list.filter((f) => f.path.toLowerCase().includes(kw))
|
||||
}
|
||||
const sorted = [...list]
|
||||
if (sort.value === 'size') sorted.sort((a, b) => b.length - a.length)
|
||||
else sorted.sort((a, b) => a.path.localeCompare(b.path))
|
||||
return sorted.slice(0, 500)
|
||||
})
|
||||
|
||||
const truncated = computed(() => (props.files?.length || 0) > 500)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="resolving" class="empty">
|
||||
<span class="spin" /> 正在通过 DHT / tracker 获取种子元数据,通常几秒到几十秒…
|
||||
</div>
|
||||
|
||||
<div v-else-if="!files" class="stack sm" style="padding: 18px">
|
||||
<div class="note warn">
|
||||
这个种子只有 infoHash,还没有文件清单。可以点击下面的按钮在线拉取元数据(需要本机能连上 DHT / tracker)。
|
||||
</div>
|
||||
<div v-if="resolveError" class="note err">{{ resolveError }}</div>
|
||||
<div>
|
||||
<button class="btn btn-primary" @click="emit('resolve')">在线解析文件清单</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="panel-hd" style="border-top: none">
|
||||
<span class="label">FILE LIST</span>
|
||||
<span class="mono small dim">{{ formatNumber(files.length) }} 个文件 · {{ formatBytes(total) }}</span>
|
||||
<span class="spacer" />
|
||||
<input v-model="keyword" class="input" style="width: 200px" placeholder="过滤文件名…" />
|
||||
<select v-model="sort" class="select" style="width: 120px">
|
||||
<option value="size">按大小</option>
|
||||
<option value="name">按名称</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="max-height: 520px; overflow: auto">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 58%">路径</th>
|
||||
<th class="right" style="width: 14%">大小</th>
|
||||
<th class="right" style="width: 10%">占比</th>
|
||||
<th style="width: 76px" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(f, i) in visible" :key="i">
|
||||
<td class="file-path">
|
||||
<span class="file-dir">{{ splitPath(f.path).dir }}</span><span class="file-name">{{ splitPath(f.path).name }}</span>
|
||||
</td>
|
||||
<td class="num right">{{ formatBytes(f.length) }}</td>
|
||||
<td class="num right mute">{{ filePercent(f.length, total) }}</td>
|
||||
<td class="right">
|
||||
<button
|
||||
v-if="previewable(f)"
|
||||
class="btn btn-sm btn-ghost"
|
||||
:title="`从做种节点拉取这张图片用于预览:${f.path}`"
|
||||
@click="emit('preview', f)"
|
||||
>
|
||||
预览
|
||||
</button>
|
||||
<span v-else-if="isImagePath(f.path)" class="dim small" title="图片体积超出在线预览上限">过大</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-if="truncated" class="empty">结果较多,表格只展示前 500 条,可用过滤框缩小范围</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user