feat: 支持前端编辑、提交 Config (#370)
This commit is contained in:
@@ -3,28 +3,72 @@
|
||||
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 { Switch } from '$lib/components/ui/switch/index.js';
|
||||
import * as Tabs from '$lib/components/ui/tabs/index.js';
|
||||
import { Separator } from '$lib/components/ui/separator/index.js';
|
||||
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||
import api from '$lib/api';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { setBreadcrumb } from '$lib/stores/breadcrumb';
|
||||
import { goto } from '$app/navigation';
|
||||
import { appStateStore, ToQuery } from '$lib/stores/filter';
|
||||
import type { Config, ApiError } from '$lib/types';
|
||||
|
||||
let apiToken = '';
|
||||
let frontendToken = ''; // 前端认证token
|
||||
let config: Config | null = null;
|
||||
let formData: Config | null = null;
|
||||
let saving = false;
|
||||
let loading = false;
|
||||
|
||||
async function saveApiToken() {
|
||||
if (!apiToken.trim()) {
|
||||
toast.error('请输入有效的API Token');
|
||||
async function loadConfig() {
|
||||
loading = true;
|
||||
try {
|
||||
const response = await api.getConfig();
|
||||
config = response.data;
|
||||
formData = { ...config };
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error);
|
||||
toast.error('加载配置失败', {
|
||||
description: (error as ApiError).message
|
||||
});
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function authenticateFrontend() {
|
||||
if (!frontendToken.trim()) {
|
||||
toast.error('请输入前端认证Token');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
api.setAuthToken(frontendToken.trim());
|
||||
localStorage.setItem('authToken', frontendToken.trim());
|
||||
toast.success('前端认证成功');
|
||||
loadConfig(); // 认证成功后加载配置
|
||||
} catch (error) {
|
||||
console.error('前端认证失败:', error);
|
||||
toast.error('认证失败,请检查Token是否正确');
|
||||
}
|
||||
}
|
||||
|
||||
async function saveConfig() {
|
||||
if (!formData) {
|
||||
toast.error('配置未加载');
|
||||
return;
|
||||
}
|
||||
saving = true;
|
||||
try {
|
||||
api.setAuthToken(apiToken.trim());
|
||||
toast.success('API Token 已保存');
|
||||
let resp = await api.updateConfig(formData);
|
||||
formData = resp.data;
|
||||
config = { ...formData };
|
||||
toast.success('配置已保存');
|
||||
} catch (error) {
|
||||
console.error('保存API Token失败:', error);
|
||||
toast.error('保存失败,请重试');
|
||||
console.error('保存配置失败:', error);
|
||||
toast.error('保存配置失败', {
|
||||
description: (error as ApiError).message
|
||||
});
|
||||
} finally {
|
||||
saving = false;
|
||||
}
|
||||
@@ -40,10 +84,14 @@
|
||||
},
|
||||
{ label: '设置', isActive: true }
|
||||
]);
|
||||
|
||||
const savedToken = localStorage.getItem('authToken');
|
||||
if (savedToken) {
|
||||
apiToken = savedToken;
|
||||
frontendToken = savedToken;
|
||||
api.setAuthToken(savedToken);
|
||||
}
|
||||
|
||||
loadConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -51,31 +99,570 @@
|
||||
<title>设置 - Bili Sync</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-4xl">
|
||||
<div class="space-y-8">
|
||||
<!-- API Token 配置 -->
|
||||
<div class="border-border border-b pb-6">
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
<div class="lg:col-span-1">
|
||||
<Label class="text-base font-semibold">API Token</Label>
|
||||
<p class="text-muted-foreground mt-1 text-sm">用于身份验证的API令牌</p>
|
||||
<div class="space-y-6">
|
||||
<!-- 前端认证状态栏 -->
|
||||
<div class="bg-card rounded-lg border p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="space-y-1">
|
||||
<h2 class="font-semibold">前端认证状态</h2>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
{formData ? '已认证 - 可以正常加载数据' : '未认证 - 请输入 Token 进行鉴权'}
|
||||
</p>
|
||||
</div>
|
||||
{#if !formData}
|
||||
<div class="flex gap-3">
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="输入认证Token"
|
||||
bind:value={frontendToken}
|
||||
class="w-64"
|
||||
/>
|
||||
<Button onclick={authenticateFrontend} disabled={!frontendToken.trim()}>认证</Button>
|
||||
</div>
|
||||
<div class="space-y-4 lg:col-span-2">
|
||||
<div class="space-y-2">
|
||||
<Input
|
||||
id="api-token"
|
||||
type="password"
|
||||
placeholder="请输入API Token"
|
||||
bind:value={apiToken}
|
||||
class="max-w-lg"
|
||||
/>
|
||||
<p class="text-muted-foreground text-xs">请确保令牌的安全性,不要与他人分享</p>
|
||||
{:else}
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="h-2 w-2 rounded-full bg-green-500"></div>
|
||||
<span class="text-sm text-green-600">已认证</span>
|
||||
</div>
|
||||
<Button onclick={saveApiToken} disabled={saving} size="sm">
|
||||
{saving ? '保存中...' : '保存'}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => {
|
||||
formData = null;
|
||||
config = null;
|
||||
localStorage.removeItem('authToken');
|
||||
api.clearAuthToken();
|
||||
frontendToken = '';
|
||||
}}
|
||||
>
|
||||
退出认证
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 应用配置 -->
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center py-16">
|
||||
<div class="space-y-2 text-center">
|
||||
<div
|
||||
class="border-primary mx-auto h-6 w-6 animate-spin rounded-full border-2 border-t-transparent"
|
||||
></div>
|
||||
<p class="text-muted-foreground">加载配置中...</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else if formData}
|
||||
<div class="space-y-6">
|
||||
<Tabs.Root value="basic" class="w-full">
|
||||
<Tabs.List class="grid w-full grid-cols-5">
|
||||
<Tabs.Trigger value="basic">基本设置</Tabs.Trigger>
|
||||
<Tabs.Trigger value="auth">B站认证</Tabs.Trigger>
|
||||
<Tabs.Trigger value="filter">视频质量</Tabs.Trigger>
|
||||
<Tabs.Trigger value="danmaku">弹幕渲染</Tabs.Trigger>
|
||||
<Tabs.Trigger value="advanced">高级设置</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
<!-- 基本设置 -->
|
||||
<Tabs.Content value="basic" class="mt-6 space-y-6">
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
<div class="space-y-2">
|
||||
<Label for="bind-address">绑定地址</Label>
|
||||
<Input
|
||||
id="bind-address"
|
||||
placeholder="127.0.0.1:9999"
|
||||
bind:value={formData.bind_address}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="interval">同步间隔(秒)</Label>
|
||||
<Input id="interval" type="number" min="60" bind:value={formData.interval} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="video-name">视频名称模板</Label>
|
||||
<Input id="video-name" bind:value={formData.video_name} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="page-name">分页名称模板</Label>
|
||||
<Input id="page-name" bind:value={formData.page_name} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="upper-path">UP主头像保存路径</Label>
|
||||
<Input
|
||||
id="upper-path"
|
||||
placeholder="/path/to/upper/faces"
|
||||
bind:value={formData.upper_path}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="time-format">时间格式</Label>
|
||||
<Input
|
||||
id="time-format"
|
||||
placeholder="%Y-%m-%d %H:%M:%S"
|
||||
bind:value={formData.time_format}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="backend-auth-token">后端API认证Token</Label>
|
||||
<Input
|
||||
id="backend-auth-token"
|
||||
type="password"
|
||||
placeholder="设置后端API认证Token"
|
||||
bind:value={formData.auth_token}
|
||||
/>
|
||||
<p class="text-muted-foreground text-xs">
|
||||
修改此Token后,前端需要使用新Token重新认证才能访问API
|
||||
</p>
|
||||
</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} />
|
||||
<Label for="cdn-sorting">启用CDN排序</Label>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- B站认证 -->
|
||||
<Tabs.Content value="auth" class="mt-6 space-y-6">
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="sessdata">SESSDATA</Label>
|
||||
<Input
|
||||
id="sessdata"
|
||||
type="password"
|
||||
placeholder="请输入SESSDATA"
|
||||
bind:value={formData.credential.sessdata}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="bili-jct">bili_jct</Label>
|
||||
<Input
|
||||
id="bili-jct"
|
||||
type="password"
|
||||
placeholder="请输入bili_jct"
|
||||
bind:value={formData.credential.bili_jct}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="buvid3">buvid3</Label>
|
||||
<Input
|
||||
id="buvid3"
|
||||
placeholder="请输入buvid3"
|
||||
bind:value={formData.credential.buvid3}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="dedeuserid">dedeuserid</Label>
|
||||
<Input
|
||||
id="dedeuserid"
|
||||
placeholder="请输入dedeuserid"
|
||||
bind:value={formData.credential.dedeuserid}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="ac-time-value">ac_time_value</Label>
|
||||
<Input
|
||||
id="ac-time-value"
|
||||
placeholder="请输入ac_time_value"
|
||||
bind:value={formData.credential.ac_time_value}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- 过滤规则 -->
|
||||
<Tabs.Content value="filter" class="mt-6 space-y-6">
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
<div class="space-y-2">
|
||||
<Label for="video-max-quality">最高视频质量</Label>
|
||||
<select
|
||||
id="video-max-quality"
|
||||
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
bind:value={formData.filter_option.video_max_quality}
|
||||
>
|
||||
<option value="Quality360p">360p</option>
|
||||
<option value="Quality480p">480p</option>
|
||||
<option value="Quality720p">720p</option>
|
||||
<option value="Quality1080p">1080p</option>
|
||||
<option value="Quality1080pPLUS">1080p+</option>
|
||||
<option value="Quality1080p60">1080p60</option>
|
||||
<option value="Quality4k">4K</option>
|
||||
<option value="QualityHdr">HDR</option>
|
||||
<option value="QualityDolby">杜比视界</option>
|
||||
<option value="Quality8k">8K</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="video-min-quality">最低视频质量</Label>
|
||||
<select
|
||||
id="video-min-quality"
|
||||
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
bind:value={formData.filter_option.video_min_quality}
|
||||
>
|
||||
<option value="Quality360p">360p</option>
|
||||
<option value="Quality480p">480p</option>
|
||||
<option value="Quality720p">720p</option>
|
||||
<option value="Quality1080p">1080p</option>
|
||||
<option value="Quality1080pPLUS">1080p+</option>
|
||||
<option value="Quality1080p60">1080p60</option>
|
||||
<option value="Quality4k">4K</option>
|
||||
<option value="QualityHdr">HDR</option>
|
||||
<option value="QualityDolby">杜比视界</option>
|
||||
<option value="Quality8k">8K</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="audio-max-quality">最高音频质量</Label>
|
||||
<select
|
||||
id="audio-max-quality"
|
||||
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
bind:value={formData.filter_option.audio_max_quality}
|
||||
>
|
||||
<option value="Quality64k">64k</option>
|
||||
<option value="Quality132k">132k</option>
|
||||
<option value="Quality192k">192k</option>
|
||||
<option value="QualityDolby">杜比全景声</option>
|
||||
<option value="QualityHiRES">Hi-RES</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="audio-min-quality">最低音频质量</Label>
|
||||
<select
|
||||
id="audio-min-quality"
|
||||
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
bind:value={formData.filter_option.audio_min_quality}
|
||||
>
|
||||
<option value="Quality64k">64k</option>
|
||||
<option value="Quality132k">132k</option>
|
||||
<option value="Quality192k">192k</option>
|
||||
<option value="QualityDolby">杜比全景声</option>
|
||||
<option value="QualityHiRES">Hi-RES</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div class="space-y-4">
|
||||
<Label>视频编码格式偏好(按优先级排序)</Label>
|
||||
<p class="text-muted-foreground text-sm">排在前面的编码格式优先级更高</p>
|
||||
<div class="space-y-2">
|
||||
{#each formData.filter_option.codecs as codec, index (index)}
|
||||
<div class="flex items-center space-x-2 rounded-lg border p-3">
|
||||
<Badge variant="secondary">{index + 1}</Badge>
|
||||
<span class="flex-1 font-medium">{codec}</span>
|
||||
<div class="flex space-x-1">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={index === 0}
|
||||
onclick={() => {
|
||||
if (formData && index > 0) {
|
||||
const newCodecs = [...formData.filter_option.codecs];
|
||||
[newCodecs[index - 1], newCodecs[index]] = [
|
||||
newCodecs[index],
|
||||
newCodecs[index - 1]
|
||||
];
|
||||
formData.filter_option.codecs = newCodecs;
|
||||
}
|
||||
}}
|
||||
>
|
||||
↑
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={index === formData.filter_option.codecs.length - 1}
|
||||
onclick={() => {
|
||||
if (formData && index < formData.filter_option.codecs.length - 1) {
|
||||
const newCodecs = [...formData.filter_option.codecs];
|
||||
[newCodecs[index], newCodecs[index + 1]] = [
|
||||
newCodecs[index + 1],
|
||||
newCodecs[index]
|
||||
];
|
||||
formData.filter_option.codecs = newCodecs;
|
||||
}
|
||||
}}
|
||||
>
|
||||
↓
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onclick={() => {
|
||||
if (formData) {
|
||||
formData.filter_option.codecs = formData.filter_option.codecs.filter(
|
||||
(_, i) => i !== index
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
{#if formData.filter_option.codecs.length < 3}
|
||||
<div class="space-y-2">
|
||||
<Label>添加编码格式</Label>
|
||||
<div class="flex gap-2">
|
||||
{#each ['AV1', 'HEV', 'AVC'] as codec (codec)}
|
||||
{#if !formData.filter_option.codecs.includes(codec)}
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onclick={() => {
|
||||
if (formData) {
|
||||
formData.filter_option.codecs = [
|
||||
...formData.filter_option.codecs,
|
||||
codec
|
||||
];
|
||||
}
|
||||
}}
|
||||
>
|
||||
+ {codec}
|
||||
</Button>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="no-dolby-video" bind:checked={formData.filter_option.no_dolby_video} />
|
||||
<Label for="no-dolby-video">排除杜比视界视频</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="no-dolby-audio" bind:checked={formData.filter_option.no_dolby_audio} />
|
||||
<Label for="no-dolby-audio">排除杜比全景声音频</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="no-hdr" bind:checked={formData.filter_option.no_hdr} />
|
||||
<Label for="no-hdr">排除HDR视频</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="no-hires" bind:checked={formData.filter_option.no_hires} />
|
||||
<Label for="no-hires">排除Hi-RES音频</Label>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- 弹幕设置 -->
|
||||
<Tabs.Content value="danmaku" class="mt-6 space-y-6">
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
<div class="space-y-2">
|
||||
<Label for="danmaku-duration">弹幕持续时间(秒)</Label>
|
||||
<Input
|
||||
id="danmaku-duration"
|
||||
type="number"
|
||||
min="1"
|
||||
step="0.1"
|
||||
bind:value={formData.danmaku_option.duration}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="danmaku-font">字体</Label>
|
||||
<Input
|
||||
id="danmaku-font"
|
||||
placeholder="黑体"
|
||||
bind:value={formData.danmaku_option.font}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="danmaku-font-size">字体大小</Label>
|
||||
<Input
|
||||
id="danmaku-font-size"
|
||||
type="number"
|
||||
min="8"
|
||||
max="72"
|
||||
bind:value={formData.danmaku_option.font_size}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="danmaku-width-ratio">宽度比例</Label>
|
||||
<Input
|
||||
id="danmaku-width-ratio"
|
||||
type="number"
|
||||
min="0.1"
|
||||
max="3"
|
||||
step="0.1"
|
||||
bind:value={formData.danmaku_option.width_ratio}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="danmaku-horizontal-gap">水平间距</Label>
|
||||
<Input
|
||||
id="danmaku-horizontal-gap"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
bind:value={formData.danmaku_option.horizontal_gap}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="danmaku-lane-size">轨道大小</Label>
|
||||
<Input
|
||||
id="danmaku-lane-size"
|
||||
type="number"
|
||||
min="8"
|
||||
max="128"
|
||||
bind:value={formData.danmaku_option.lane_size}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="danmaku-float-percentage">滚动弹幕高度百分比</Label>
|
||||
<Input
|
||||
id="danmaku-float-percentage"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
bind:value={formData.danmaku_option.float_percentage}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="danmaku-bottom-percentage">底部弹幕高度百分比</Label>
|
||||
<Input
|
||||
id="danmaku-bottom-percentage"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
bind:value={formData.danmaku_option.bottom_percentage}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="danmaku-opacity">透明度 (0-255)</Label>
|
||||
<Input
|
||||
id="danmaku-opacity"
|
||||
type="number"
|
||||
min="0"
|
||||
max="255"
|
||||
bind:value={formData.danmaku_option.opacity}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="danmaku-outline">描边宽度</Label>
|
||||
<Input
|
||||
id="danmaku-outline"
|
||||
type="number"
|
||||
min="0"
|
||||
max="5"
|
||||
step="0.1"
|
||||
bind:value={formData.danmaku_option.outline}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="danmaku-time-offset">时间偏移(秒)</Label>
|
||||
<Input
|
||||
id="danmaku-time-offset"
|
||||
type="number"
|
||||
step="0.1"
|
||||
bind:value={formData.danmaku_option.time_offset}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="danmaku-bold" bind:checked={formData.danmaku_option.bold} />
|
||||
<Label for="danmaku-bold">粗体显示</Label>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- 高级设置 -->
|
||||
<Tabs.Content value="advanced" class="mt-6 space-y-6">
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
<div class="space-y-2">
|
||||
<Label for="video-concurrent">视频并发数</Label>
|
||||
<Input
|
||||
id="video-concurrent"
|
||||
type="number"
|
||||
min="1"
|
||||
max="20"
|
||||
bind:value={formData.concurrent_limit.video}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="page-concurrent">分页并发数</Label>
|
||||
<Input
|
||||
id="page-concurrent"
|
||||
type="number"
|
||||
min="1"
|
||||
max="20"
|
||||
bind:value={formData.concurrent_limit.page}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="nfo-time-type">NFO时间类型</Label>
|
||||
<select
|
||||
id="nfo-time-type"
|
||||
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
bind:value={formData.nfo_time_type}
|
||||
>
|
||||
<option value="FavTime">收藏时间</option>
|
||||
<option value="PubTime">发布时间</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- Token管理 -->
|
||||
<Tabs.Content value="token" class="mt-6 space-y-6">
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="backend-auth-token">后端认证Token</Label>
|
||||
<Input
|
||||
id="backend-auth-token"
|
||||
type="password"
|
||||
placeholder="设置后端API认证Token"
|
||||
bind:value={formData.auth_token}
|
||||
/>
|
||||
<p class="text-muted-foreground text-xs">
|
||||
用于保护后端API的认证令牌,修改后需要重新进行前端认证
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
|
||||
<div class="flex justify-end pt-6">
|
||||
<Button onclick={saveConfig} disabled={saving} size="lg">
|
||||
{saving ? '保存中...' : '保存配置'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center justify-center py-16">
|
||||
<div class="space-y-4 text-center">
|
||||
<p class="text-muted-foreground">请先进行前端认证以加载配置</p>
|
||||
<Button onclick={() => frontendToken && authenticateFrontend()} disabled={!frontendToken}>
|
||||
重新加载配置
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user