feat(web): simplify subscription and manual download flows and sync UHD styles

This commit is contained in:
root committed 2026-10-13 14:37:03 +08:00
1 parent 27c0f27434
commit c3c4456913
33 files changed
+1083 -633

No files matched your search

@@ -3,6 +3,7 @@ import { Pencil, Plus, Target } from "lucide-react";
import { useId, useState } from "react";
import { toast } from "sonner";
import { z } from "zod";
import { useRuleGroups } from "~/api/rules.ts";
import { useCreateSlot, useSlots, useUpdateSlot } from "~/api/slots.ts";
import { DetailSheet } from "~/components/console/detail-sheet.tsx";
import { EmptyState, ErrorState, LoadingState } from "~/components/console/empty-state.tsx";
@@ -12,6 +13,7 @@ import { Input } from "~/components/ui/input.tsx";
import { Switch } from "~/components/ui/switch.tsx";
import { ApiRequestError, errorMessage } from "~/lib/api.ts";
import { formatBytes } from "~/lib/utils.ts";
import { ChipToggleGroup } from "./parts.tsx";
import { SlotCard } from "./slot-card.tsx";
import { draftFromJson, draftToJson, emptyDraft, type SlotRulesDraft, SlotRulesEditor } from "./slot-rules.tsx";
@@ -46,7 +48,9 @@ export function SlotsPanel({
}) {
const q = useSlots({ mediaEntityId });
const [editing, setEditing] = useState<AcquisitionSlot | "new" | null>(null);
const slots = q.data?.items ?? [];
const slots = (q.data?.items ?? []).filter(
(s) => !subscriptionId || s.subscriptionId === subscriptionId || s.subscriptionId === null,
);
return (
<div className="space-y-3">
@@ -89,9 +93,14 @@ export function SlotsPanel({
) : undefined
}
actions={
<Button variant="ghost" size="icon-sm" aria-label={`编辑 ${s.name}`} onClick={() => setEditing(s)}>
<Pencil />
</Button>
<div className="flex items-center gap-2">
{subscriptionId && s.subscriptionId === null ? (
<span className="text-meta text-muted-foreground">作品共用</span>
) : null}
<Button variant="ghost" size="icon-sm" aria-label={`编辑 ${s.name}`} onClick={() => setEditing(s)}>
<Pencil />
</Button>
</div>
}
/>
))}
@@ -127,6 +136,8 @@ function SlotEditorSheet({
const id = useId();
const create = useCreateSlot();
const update = useUpdateSlot();
const groups = useRuleGroups({ enabled: "true" });
const [ruleGroupIds, setRuleGroupIds] = useState<string[]>(slot?.ruleGroupIds ?? []);
const [name, setName] = useState(slot?.name ?? "");
const [distinctGroup, setDistinctGroup] = useState(slot?.distinctGroup ?? "");
const [requireDistinct, setRequireDistinct] = useState(slot?.requireDistinctAsset ?? false);
@@ -145,6 +156,7 @@ function SlotEditorSheet({
if (Object.keys(errs).length || !meta.success || !rules.ok) return;
const common = {
...rules.value,
ruleGroupIds,
distinctGroup: meta.data.distinctGroup || null,
requireDistinctAsset: requireDistinct,
};
@@ -232,6 +244,20 @@ function SlotEditorSheet({
</div>
<Switch checked={requireDistinct} onCheckedChange={setRequireDistinct} aria-label="要求独立文件" />
</div>
<FormField label="收录组合规则" hint="不选时沿用订阅兼容设置或默认规则;最后一个带档位的组决定优先级">
<ChipToggleGroup
label="收录组合规则"
ordered
options={[
...(groups.data?.items ?? []).map((g) => ({ value: g.id, label: g.name })),
...ruleGroupIds
.filter((gid) => !(groups.data?.items ?? []).some((g) => g.id === gid))
.map((gid) => ({ value: gid, label: `${gid}(规则不可用)` })),
]}
value={ruleGroupIds}
onChange={setRuleGroupIds}
/>
</FormField>
<SlotRulesEditor value={draft} onChange={setDraft} errors={errors} />
</DetailSheet>
);