feat: 支持设置快捷订阅的路径默认值 (#502)
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<div style="max-width: 450px; width: 100%;">
|
||||
<SubscriptionCard
|
||||
item={collection}
|
||||
type="collection"
|
||||
type="collections"
|
||||
onSubscriptionSuccess={handleSubscriptionSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<div style="max-width: 450px; width: 100%;">
|
||||
<SubscriptionCard
|
||||
item={favorite}
|
||||
type="favorite"
|
||||
type="favorites"
|
||||
onSubscriptionSuccess={handleSubscriptionSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<div style="max-width: 450px; width: 100%;">
|
||||
<SubscriptionCard
|
||||
item={upper}
|
||||
type="upper"
|
||||
type="submissions"
|
||||
onSubscriptionSuccess={handleSubscriptionSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user