You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
125 lines
3.9 KiB
125 lines
3.9 KiB
<script setup lang="ts">
|
|
import { request, unwrapApiBody, type ApiResponse } from '../../../utils/http/factory'
|
|
|
|
definePageMeta({ title: 'RSS' })
|
|
|
|
type Feed = { id: number; feedUrl: string; title: string | null; lastError: string | null }
|
|
type Item = { id: number; title: string | null; canonicalUrl: string; visibility: string; feedId: number }
|
|
|
|
const feeds = ref<Feed[]>([])
|
|
const items = ref<Item[]>([])
|
|
const feedUrl = ref('')
|
|
const loading = ref(true)
|
|
|
|
async function load() {
|
|
loading.value = true
|
|
try {
|
|
const [f, i] = await Promise.all([
|
|
request<ApiResponse<{ feeds: Feed[] }>>('/api/me/rss/feeds'),
|
|
request<ApiResponse<{ items: Item[] }>>('/api/me/rss/items'),
|
|
])
|
|
feeds.value = unwrapApiBody(f).feeds
|
|
items.value = unwrapApiBody(i).items
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(load)
|
|
|
|
async function addFeed() {
|
|
await request('/api/me/rss/feeds', { method: 'POST', body: { feedUrl: feedUrl.value } })
|
|
feedUrl.value = ''
|
|
await load()
|
|
}
|
|
|
|
async function syncAll() {
|
|
await request('/api/me/rss/sync', { method: 'POST', body: {} })
|
|
await load()
|
|
}
|
|
|
|
async function removeFeed(id: number) {
|
|
await request(`/api/me/rss/feeds/${id}`, { method: 'DELETE' })
|
|
await load()
|
|
}
|
|
|
|
async function setItemVis(id: number, visibility: string) {
|
|
await request(`/api/me/rss/items/${id}`, { method: 'PATCH', body: { visibility } })
|
|
await load()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<UContainer class="py-8 space-y-8 max-w-4xl">
|
|
<h1 class="text-2xl font-semibold">
|
|
RSS 收件箱
|
|
</h1>
|
|
|
|
<UCard>
|
|
<div class="flex flex-col sm:flex-row gap-2">
|
|
<UInput v-model="feedUrl" placeholder="https://example.com/feed.xml" class="flex-1" />
|
|
<UButton @click="addFeed">
|
|
添加订阅
|
|
</UButton>
|
|
<UButton color="neutral" variant="outline" @click="syncAll">
|
|
全部同步
|
|
</UButton>
|
|
</div>
|
|
</UCard>
|
|
|
|
<div v-if="loading" class="text-muted">
|
|
加载中…
|
|
</div>
|
|
<div v-else class="grid md:grid-cols-3 gap-6">
|
|
<UCard class="md:col-span-1">
|
|
<template #header>
|
|
订阅源(仅自己可见)
|
|
</template>
|
|
<UEmpty v-if="!feeds.length" title="暂无订阅" />
|
|
<ul v-else class="space-y-2 text-sm">
|
|
<li v-for="f in feeds" :key="f.id" class="border-b border-default pb-2">
|
|
<div class="font-medium truncate" :title="f.feedUrl">
|
|
{{ f.title || f.feedUrl }}
|
|
</div>
|
|
<div v-if="f.lastError" class="text-error text-xs">
|
|
{{ f.lastError }}
|
|
</div>
|
|
<UButton size="xs" color="error" variant="ghost" @click="removeFeed(f.id)">
|
|
删除
|
|
</UButton>
|
|
</li>
|
|
</ul>
|
|
</UCard>
|
|
<UCard class="md:col-span-2">
|
|
<template #header>
|
|
条目
|
|
</template>
|
|
<UEmpty v-if="!items.length" title="暂无条目" description="添加订阅并点击同步" />
|
|
<ul v-else class="space-y-3 text-sm">
|
|
<li v-for="it in items" :key="it.id" class="border border-default rounded-lg p-3">
|
|
<a
|
|
:href="it.canonicalUrl"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="text-primary font-medium hover:underline"
|
|
>{{ it.title || '未命名' }}</a>
|
|
<div class="text-xs text-muted mt-1">
|
|
{{ it.visibility }}
|
|
</div>
|
|
<div class="flex gap-1 mt-2">
|
|
<UButton size="xs" variant="soft" @click="setItemVis(it.id, 'private')">
|
|
私密
|
|
</UButton>
|
|
<UButton size="xs" variant="soft" @click="setItemVis(it.id, 'public')">
|
|
公开
|
|
</UButton>
|
|
<UButton size="xs" variant="soft" @click="setItemVis(it.id, 'unlisted')">
|
|
仅链接
|
|
</UButton>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</UCard>
|
|
</div>
|
|
</UContainer>
|
|
</template>
|
|
|