feat: 支持设置快捷订阅的路径默认值 (#502)

This commit is contained in:
ᴀᴍᴛᴏᴀᴇʀ
2025-10-14 18:44:33 +08:00
committed by GitHub
parent c7e0d31811
commit 84d353365a
13 changed files with 153 additions and 80 deletions

View File

@@ -221,6 +221,10 @@ class ApiClient {
return this.post<boolean>(`/video-sources/${type}/${id}/evaluate`, null);
}
async getDefaultPath(type: string, name: string): Promise<ApiResponse<string>> {
return this.get<string>(`/video-sources/${type}/default-path`, { name });
}
async getConfig(): Promise<ApiResponse<Config>> {
return this.get<Config>('/config');
}
@@ -268,6 +272,7 @@ const api = {
apiClient.updateVideoSource(type, id, request),
evaluateVideoSourceRules: (type: string, id: number) =>
apiClient.evaluateVideoSourceRules(type, id),
getDefaultPath: (type: string, name: string) => apiClient.getDefaultPath(type, name),
getConfig: () => apiClient.getConfig(),
updateConfig: (config: Config) => apiClient.updateConfig(config),
getDashboard: () => apiClient.getDashboard(),

View File

@@ -20,18 +20,18 @@
| FavoriteWithSubscriptionStatus
| CollectionWithSubscriptionStatus
| UpperWithSubscriptionStatus;
export let type: 'favorite' | 'collection' | 'upper' = 'favorite';
export let type: 'favorites' | 'collections' | 'submissions' = 'favorites';
export let onSubscriptionSuccess: (() => void) | null = null;
let dialogOpen = false;
function getIcon() {
switch (type) {
case 'favorite':
case 'favorites':
return HeartIcon;
case 'collection':
case 'collections':
return FolderIcon;
case 'upper':
case 'submissions':
return UserIcon;
default:
return VideoIcon;
@@ -40,11 +40,11 @@
function getTypeLabel() {
switch (type) {
case 'favorite':
case 'favorites':
return '收藏夹';
case 'collection':
case 'collections':
return '合集';
case 'upper':
case 'submissions':
return 'UP 主';
default:
return '';
@@ -53,11 +53,11 @@
function getTitle(): string {
switch (type) {
case 'favorite':
case 'favorites':
return (item as FavoriteWithSubscriptionStatus).title;
case 'collection':
case 'collections':
return (item as CollectionWithSubscriptionStatus).title;
case 'upper':
case 'submissions':
return (item as UpperWithSubscriptionStatus).uname;
default:
return '';
@@ -66,12 +66,10 @@
function getSubtitle(): string {
switch (type) {
case 'favorite':
case 'favorites':
return `uid: ${(item as FavoriteWithSubscriptionStatus).mid}`;
case 'collection':
case 'collections':
return `uid: ${(item as CollectionWithSubscriptionStatus).mid}`;
case 'upper':
return '';
default:
return '';
}
@@ -79,7 +77,7 @@
function getDescription(): string {
switch (type) {
case 'upper':
case 'submissions':
return (item as UpperWithSubscriptionStatus).sign || '';
default:
return '';
@@ -88,9 +86,9 @@
function isDisabled(): boolean {
switch (type) {
case 'collection':
case 'collections':
return (item as CollectionWithSubscriptionStatus).invalid;
case 'upper': {
case 'submissions': {
return (item as UpperWithSubscriptionStatus).invalid;
}
default:
@@ -100,9 +98,9 @@
function getDisabledReason(): string {
switch (type) {
case 'collection':
case 'collections':
return '已失效';
case 'upper':
case 'submissions':
return '账号已注销';
default:
return '';
@@ -111,7 +109,7 @@
function getCount(): number | null {
switch (type) {
case 'favorite':
case 'favorites':
return (item as FavoriteWithSubscriptionStatus).media_count;
default:
return null;
@@ -124,7 +122,7 @@
function getAvatarUrl(): string {
switch (type) {
case 'upper':
case 'submissions':
return (item as UpperWithSubscriptionStatus).face;
default:
return '';
@@ -171,7 +169,7 @@
? 'opacity-50'
: ''}"
>
{#if avatarUrl && type === 'upper'}
{#if avatarUrl && type === 'submissions'}
<img
src={avatarUrl}
alt={title}
@@ -265,5 +263,3 @@
<!-- 订阅对话框 -->
<SubscriptionDialog bind:open={dialogOpen} {item} {type} onSuccess={handleSubscriptionSuccess} />
<!-- 订阅对话框 -->
<SubscriptionDialog bind:open={dialogOpen} {item} {type} onSuccess={handleSubscriptionSuccess} />

View File

@@ -2,6 +2,7 @@
import { Button } from '$lib/components/ui/button/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import { toast } from 'svelte-sonner';
import {
Sheet,
SheetContent,
@@ -10,7 +11,6 @@
SheetHeader,
SheetTitle
} from '$lib/components/ui/sheet/index.js';
import { toast } from 'svelte-sonner';
import api from '$lib/api';
import type {
FavoriteWithSubscriptionStatus,
@@ -22,47 +22,40 @@
ApiError
} from '$lib/types';
export let open = false;
export let item:
| FavoriteWithSubscriptionStatus
| CollectionWithSubscriptionStatus
| UpperWithSubscriptionStatus
| null = null;
export let type: 'favorite' | 'collection' | 'upper' = 'favorite';
export let onSuccess: (() => void) | null = null;
interface Props {
open: boolean;
item:
| FavoriteWithSubscriptionStatus
| CollectionWithSubscriptionStatus
| UpperWithSubscriptionStatus
| null;
type: 'favorites' | 'collections' | 'submissions';
onSuccess: (() => void) | null;
}
let customPath = '';
let loading = false;
let {
open = $bindable(false),
item = null,
type = 'favorites',
onSuccess = null
}: Props = $props();
let customPath = $state('');
let loading = $state(false);
// 根据类型和 item 生成默认路径
function generateDefaultPath(): string {
if (!item) return '';
switch (type) {
case 'favorite': {
const favorite = item as FavoriteWithSubscriptionStatus;
return `收藏夹/${favorite.title}`;
}
case 'collection': {
const collection = item as CollectionWithSubscriptionStatus;
return `合集/${collection.title}`;
}
case 'upper': {
const upper = item as UpperWithSubscriptionStatus;
return `UP 主/${upper.uname}`;
}
default:
return '';
}
async function generateDefaultPath(): Promise<string> {
if (!itemTitle) return '';
return (await api.getDefaultPath(type, itemTitle)).data;
}
function getTypeLabel(): string {
switch (type) {
case 'favorite':
case 'favorites':
return '收藏夹';
case 'collection':
case 'collections':
return '合集';
case 'upper':
case 'submissions':
return 'UP 主';
default:
return '';
@@ -73,11 +66,11 @@
if (!item) return '';
switch (type) {
case 'favorite':
case 'favorites':
return (item as FavoriteWithSubscriptionStatus).title;
case 'collection':
case 'collections':
return (item as CollectionWithSubscriptionStatus).title;
case 'upper':
case 'submissions':
return (item as UpperWithSubscriptionStatus).uname;
default:
return '';
@@ -92,7 +85,7 @@
let response;
switch (type) {
case 'favorite': {
case 'favorites': {
const favorite = item as FavoriteWithSubscriptionStatus;
const request: InsertFavoriteRequest = {
fid: favorite.fid,
@@ -101,7 +94,7 @@
response = await api.insertFavorite(request);
break;
}
case 'collection': {
case 'collections': {
const collection = item as CollectionWithSubscriptionStatus;
const request: InsertCollectionRequest = {
sid: collection.sid,
@@ -111,7 +104,7 @@
response = await api.insertCollection(request);
break;
}
case 'upper': {
case 'submissions': {
const upper = item as UpperWithSubscriptionStatus;
const request: InsertSubmissionRequest = {
upper_id: upper.mid,
@@ -145,10 +138,20 @@
open = false;
}
// 当对话框打开时重置 path
$: if (open && item) {
customPath = generateDefaultPath();
}
$effect(() => {
if (open && item) {
generateDefaultPath()
.then((path) => {
customPath = path;
})
.catch((error) => {
toast.error('获取默认路径失败', {
description: (error as ApiError).message
});
customPath = '';
});
}
});
const typeLabel = getTypeLabel();
const itemTitle = getItemTitle();
@@ -173,14 +176,14 @@
<span class="text-muted-foreground text-sm font-medium">{typeLabel}名称:</span>
<span class="text-sm">{itemTitle}</span>
</div>
{#if type === 'favorite'}
{#if type === 'favorites'}
{@const favorite = item as FavoriteWithSubscriptionStatus}
<div class="flex items-center gap-2">
<span class="text-muted-foreground text-sm font-medium">视频数量:</span>
<span class="text-sm">{favorite.media_count}</span>
</div>
{/if}
{#if type === 'upper'}
{#if type === 'submissions'}
{@const upper = item as UpperWithSubscriptionStatus}
{#if upper.sign}
<div class="flex items-start gap-2">

View File

@@ -281,6 +281,9 @@ export interface Config {
skip_option: SkipOption;
video_name: string;
page_name: string;
favorite_default_path: string;
collection_default_path: string;
submission_default_path: string;
interval: number;
upper_path: string;
nfo_time_type: string;

View File

@@ -77,7 +77,7 @@
<div style="max-width: 450px; width: 100%;">
<SubscriptionCard
item={collection}
type="collection"
type="collections"
onSubscriptionSuccess={handleSubscriptionSuccess}
/>
</div>

View File

@@ -63,7 +63,7 @@
<div style="max-width: 450px; width: 100%;">
<SubscriptionCard
item={favorite}
type="favorite"
type="favorites"
onSubscriptionSuccess={handleSubscriptionSuccess}
/>
</div>

View File

@@ -78,7 +78,7 @@
<div style="max-width: 450px; width: 100%;">
<SubscriptionCard
item={upper}
type="upper"
type="submissions"
onSubscriptionSuccess={handleSubscriptionSuccess}
/>
</div>

View File

@@ -191,8 +191,6 @@
</div>
</div>
<Separator />
<div class="space-y-4">
<div class="space-y-2">
<Label for="backend-auth-token">后端 API 认证Token</Label>
@@ -209,6 +207,23 @@
<Separator />
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
<div class="space-y-2">
<Label for="favorite-default-path">收藏夹快捷订阅路径模板</Label>
<Input id="favorite-default-path" bind:value={formData.favorite_default_path} />
</div>
<div class="space-y-2">
<Label for="collection-default-path">合集快捷订阅路径模板</Label>
<Input id="collection-default-path" bind:value={formData.collection_default_path} />
</div>
<div class="space-y-2">
<Label for="submission-default-path">UP 主投稿快捷订阅路径模板</Label>
<Input id="submission-default-path" bind:value={formData.submission_default_path} />
</div>
</div>
<Separator />
<div class="space-y-4">
<div class="flex items-center space-x-2">
<Switch id="cdn-sorting" bind:checked={formData.cdn_sorting} />