feat(web): nav regroup, site RSS tab and site types (IA-2)
- sidebar: 概览 / 获取 / 整理 / 系统. Tasks moves to 系统; sites, subscriptions and rules form the 获取 group (ACQUIRE_GROUP_TITLE in nav.ts, the single place to rename it); standalone RSS nav item removed - site detail gets an RSS tab with everything the RSS page had (create, edit, enable, fetch now, fetch state) plus recent items; tabs and header actions follow the site's capabilities - old /console/sources links redirect to the site's RSS tab (?feed= looks up the owning site) or the site list; task links to RSS point at sites - site list shows and filters by type; add-site wizard starts with a type step (PT still picks from the definition library; public BT / custom RSS source only need name, URL and RSS URL) Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ec65435328
commit
614262378e
20 files changed
+772
-304
No files matched your search
@@ -1,76 +1,25 @@
|
||||
import type { FeedHealth } from "@umt/contracts";
|
||||
import { Plus, RefreshCw } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useFeeds } from "~/api/feeds.ts";
|
||||
import { useSites } from "~/api/sites.ts";
|
||||
import { DetailSection } from "~/components/console/detail-sheet.tsx";
|
||||
import { FilterBar, FilterSelect } from "~/components/console/filter-bar.tsx";
|
||||
import { PageHeader } from "~/components/console/page-header.tsx";
|
||||
import { useQueryState } from "~/components/console/use-query-state.ts";
|
||||
import { Button } from "~/components/ui/button.tsx";
|
||||
import { FEED_HEALTH, FeedTable } from "~/components/workbench/feed-table.tsx";
|
||||
import { legacyRedirectLoader } from "./redirects.ts";
|
||||
import { FeedSource } from "@umt/contracts";
|
||||
import { redirect } from "react-router";
|
||||
import { apiParsed } from "~/api/parse.ts";
|
||||
import { consolePath } from "~/components/console/nav.ts";
|
||||
import { legacyRedirect } from "./redirects.ts";
|
||||
|
||||
// RSS:跨站点的 RSS 列表;站点健康只在站点页(DEAI-2 去掉重复的站点表)。
|
||||
// IA-2:独立 RSS 页取消,RSS 属于站点(站点详情的 RSS Tab)。这里只做旧链接跳转,深链不失效:
|
||||
// ?siteId= → 该站点 RSS Tab;?feed=<id> → 查 RSS 所属站点后打开它的详情;其余 → 站点列表。
|
||||
|
||||
export const meta = () => [{ title: "RSS · UHD 媒体自动化控制台" }];
|
||||
|
||||
// 旧的 ?tab=snapshots&siteId= 跳到站点详情的"快照与 Feed"
|
||||
export const clientLoader = legacyRedirectLoader;
|
||||
export async function clientLoader({ request }: { request: Request }) {
|
||||
const url = new URL(request.url);
|
||||
const feed = url.searchParams.get("feed");
|
||||
if (feed) {
|
||||
const f = await apiParsed(FeedSource, "/api/v1/feed-sources/:id", { pathParams: { id: feed } }).catch(() => null);
|
||||
if (f?.siteId) return redirect(`${consolePath("sites", f.siteId)}?tab=rss&feed=${encodeURIComponent(feed)}`);
|
||||
return redirect(consolePath("sites"));
|
||||
}
|
||||
return redirect(legacyRedirect(url.pathname, url.search) ?? consolePath("sites"));
|
||||
}
|
||||
|
||||
export default function SourcesPage() {
|
||||
const f = useQueryState(["health", "siteId"] as const);
|
||||
const [creating, setCreating] = useState(false);
|
||||
const sites = useSites();
|
||||
const feeds = useFeeds({
|
||||
health: (f.values.health || undefined) as FeedHealth | undefined,
|
||||
siteId: f.values.siteId || undefined,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title="RSS"
|
||||
actions={
|
||||
<Button size="sm" onClick={() => setCreating(true)}>
|
||||
<Plus />
|
||||
新建 RSS
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<div className="space-y-8">
|
||||
<DetailSection title="RSS 地址">
|
||||
<FilterBar
|
||||
onReset={f.active ? f.reset : undefined}
|
||||
actions={
|
||||
<Button variant="ghost" size="sm" onClick={() => void feeds.refetch()} disabled={feeds.isFetching}>
|
||||
<RefreshCw className={feeds.isFetching ? "animate-spin" : undefined} />
|
||||
刷新
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<FilterSelect
|
||||
label="健康"
|
||||
value={f.values.health}
|
||||
onChange={(v) => f.set("health", v)}
|
||||
options={Object.entries(FEED_HEALTH).map(([value, h]) => ({ value, label: h.label }))}
|
||||
/>
|
||||
<FilterSelect
|
||||
label="站点"
|
||||
value={f.values.siteId}
|
||||
onChange={(v) => f.set("siteId", v)}
|
||||
options={(sites.data?.items ?? []).map((s) => ({ value: s.id, label: s.name }))}
|
||||
/>
|
||||
</FilterBar>
|
||||
<FeedTable
|
||||
health={(f.values.health || undefined) as FeedHealth | undefined}
|
||||
siteId={f.values.siteId || undefined}
|
||||
filtered={f.active}
|
||||
creating={creating}
|
||||
onCreatingChange={setCreating}
|
||||
/>
|
||||
</DetailSection>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
export default function LegacySourcesRedirect() {
|
||||
return null;
|
||||
}
|
||||
Reference in new issue
Block a user