From 630f9257254d49410c309896521f0671363e390d Mon Sep 17 00:00:00 2001 From: ShukeBta Date: Mon, 18 May 2026 01:22:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=AB=99=E7=82=B9?= =?UTF-8?q?=E7=AE=A1=E7=90=86API=20Key=E4=BF=9D=E5=AD=98=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E5=8A=A8RSS=E5=AD=97=E6=AE=B5=E5=88=B0?= =?UTF-8?q?=E4=B8=BB=E8=A1=A8=E5=8D=95=EF=BC=8C=E4=BF=AE=E5=A4=8DDiscover?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/handler/site_handler.go | 65 ++++++++- web/src/pages/DiscoverPage.tsx | 10 +- web/src/pages/LoginPage.tsx | 88 +++++++------ web/src/pages/SettingsPage.tsx | 3 - web/src/pages/SitesPage.tsx | 219 ++++++++++++++++++++++++++----- 5 files changed, 300 insertions(+), 85 deletions(-) diff --git a/internal/handler/site_handler.go b/internal/handler/site_handler.go index 4b4324e..117a6ba 100644 --- a/internal/handler/site_handler.go +++ b/internal/handler/site_handler.go @@ -49,11 +49,70 @@ func (h *SiteHandler) GetSite(c *gin.Context) { // CreateSite 创建站点。 func (h *SiteHandler) CreateSite(c *gin.Context) { - var site model.Site - if err := c.ShouldBindJSON(&site); err != nil { + var body map[string]any + if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "message": err.Error()}) return } + + var site model.Site + // 手动映射字段(因为敏感字段有 json:"-" 标签,不会自动反序列化) + if v, ok := body["name"].(string); ok { + site.Name = v + } + if v, ok := body["url"].(string); ok { + site.URL = v + } + if v, ok := body["type"].(string); ok { + site.Type = v + } + if v, ok := body["auth_type"].(string); ok { + site.AuthType = v + } + if v, ok := body["api_key"].(string); ok { + site.APIKey = v + } + if v, ok := body["cookie"].(string); ok { + site.Cookie = v + } + if v, ok := body["auth_header"].(string); ok { + site.AuthHeader = v + } + if v, ok := body["enabled"].(bool); ok { + site.Enabled = v + } + if v, ok := body["is_default"].(bool); ok { + site.IsDefault = v + } + if v, ok := body["extra"].(string); ok { + site.Extra = v + } + // 高级设置字段 + if v, ok := body["user_agent"].(string); ok { + site.UserAgent = v + } + if v, ok := body["rss_url"].(string); ok { + site.RSSURL = v + } + if v, ok := body["timeout"].(float64); ok { + site.Timeout = int(v) + } + if v, ok := body["priority"].(float64); ok { + site.Priority = int(v) + } + if v, ok := body["use_proxy"].(bool); ok { + site.UseProxy = v + } + if v, ok := body["rate_limit"].(bool); ok { + site.RateLimit = v + } + if v, ok := body["browser_emulation"].(bool); ok { + site.BrowserEmulation = v + } + if v, ok := body["downloader"].(string); ok { + site.Downloader = v + } + if err := h.svc.Site.Create(c.Request.Context(), &site); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "message": err.Error()}) return @@ -68,6 +127,8 @@ func (h *SiteHandler) UpdateSite(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "message": err.Error()}) return } + // 字段名映射:前端使用蛇形命名,GORM 会正确映射到列名 + // 无需额外处理,Updates 直接使用 patch 中的 key-value if err := h.svc.Site.Update(c.Request.Context(), c.Param("id"), patch); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "message": err.Error()}) return diff --git a/web/src/pages/DiscoverPage.tsx b/web/src/pages/DiscoverPage.tsx index cc03654..d82de2d 100644 --- a/web/src/pages/DiscoverPage.tsx +++ b/web/src/pages/DiscoverPage.tsx @@ -29,8 +29,8 @@ export function DiscoverPage() {

发现

-

- 来自 TMDb 的当日热门与流行榜单(需在 secrets.tmdb_api_key 配置 API Key)。 +

+ 来自 TMDb 的当日热门与流行榜单(需在外部 API 中配置 TMDb API Key)。

@@ -41,9 +41,9 @@ export function DiscoverPage() { {popular.length > 0 && } {!loading && trending.length === 0 && popular.length === 0 && (
-

- 还没有可用的发现数据。前往 管理后台 → 设置 - ,在 secrets.tmdb_api_key 中填写一个有效的 TMDb API Key。 +

+ 还没有可用的发现数据。请前往 管理后台 → 外部API + ,配置 TMDb API Key 后刷新页面。

)} diff --git a/web/src/pages/LoginPage.tsx b/web/src/pages/LoginPage.tsx index a670de4..730ae78 100644 --- a/web/src/pages/LoginPage.tsx +++ b/web/src/pages/LoginPage.tsx @@ -35,53 +35,57 @@ export function LoginPage() { } return ( -
-
-
- -

- MediaStationGo -

-

登录到你的家庭媒体中心

-
+
+ {/* 表单居中区域:flex-grow + flex center 使表单在剩余空间内垂直居中 */} +
+ +
+ +

+ MediaStationGo +

+

登录到你的家庭媒体中心

+
- + - + - + -

- 首次部署默认账号:admin / admin123 -

- +

+ 首次部署默认账号:admin / admin123 +

+ +
- + {/* 页脚始终在底部 */} +
) } diff --git a/web/src/pages/SettingsPage.tsx b/web/src/pages/SettingsPage.tsx index f614802..19d9cfd 100644 --- a/web/src/pages/SettingsPage.tsx +++ b/web/src/pages/SettingsPage.tsx @@ -2,7 +2,6 @@ import { FormEvent, useEffect, useState } from 'react' import { FolderOpen, Loader2, Save, SettingsIcon } from 'lucide-react' import toast from 'react-hot-toast' -import { AppFooter } from '../components/AppFooter' import { adminAPI } from '../api/admin' import { libraryAPI } from '../api/library' import type { Library, Setting } from '../types' @@ -340,8 +339,6 @@ export function SettingsPage() {
)} - - ) } diff --git a/web/src/pages/SitesPage.tsx b/web/src/pages/SitesPage.tsx index 8150bdf..671f857 100644 --- a/web/src/pages/SitesPage.tsx +++ b/web/src/pages/SitesPage.tsx @@ -51,6 +51,15 @@ const defaultForm = () => ({ enabled: true, is_default: false, extra: '', + // 高级设置 + user_agent: '', + rss_url: '', + timeout: 15, + priority: 50, + use_proxy: false, + rate_limit: false, + browser_emulation: false, + downloader: '', }) export function SitesPage() { @@ -103,6 +112,15 @@ export function SitesPage() { enabled: s.enabled !== false, is_default: s.is_default || false, extra: s.extra || '', + // 高级设置 + user_agent: s.user_agent || '', + rss_url: s.rss_url || '', + timeout: s.timeout ?? 15, + priority: s.priority ?? 50, + use_proxy: s.use_proxy || false, + rate_limit: s.rate_limit || false, + browser_emulation: s.browser_emulation || false, + downloader: s.downloader || '', }) setAdvancedOpen(false) setShowModal(true) @@ -116,49 +134,69 @@ export function SitesPage() { setEditingId(null) } - // ── 保存 ── - const handleSave = async (e: FormEvent) => { - e.preventDefault() - if (!form.name.trim() || !form.url.trim()) { - toast.error('站点名称和地址不能为空') - return + // ── 保存(支持静默模式)── + const silentSave = async (): Promise => { + if (!form.name.trim() || !form.url.trim()) return false + const payload: Record = { + name: form.name.trim(), + url: form.url.trim(), + type: form.type, + auth_type: form.auth_type, + cookie: form.cookie || '', + api_key: form.api_key || '', + auth_header: form.auth_header || '', + enabled: form.enabled, + is_default: form.is_default, + extra: form.extra || '', + user_agent: form.user_agent || '', + rss_url: form.rss_url || '', + timeout: Number(form.timeout) || 15, + priority: Number(form.priority) || 50, + use_proxy: !!form.use_proxy, + rate_limit: !!form.rate_limit, + browser_emulation: !!form.browser_emulation, + downloader: form.downloader || '', } - setSaving(true) try { - const payload: Record = { - name: form.name.trim(), - url: form.url.trim(), - type: form.type, - auth_type: form.auth_type, - cookie: form.cookie || '', - api_key: form.api_key || '', - auth_header: form.auth_header || '', - enabled: form.enabled, - is_default: form.is_default, - extra: form.extra || '', - } - if (editingId) { await sitesAPI.update(editingId, payload) - toast.success('站点已更新') } else { - await sitesAPI.create(payload) - toast.success('站点已添加') + const res = await sitesAPI.create(payload) + setEditingId((res.data as Site)?.id ?? null) } - closeModal() - await loadSites() - } catch (err: unknown) { - const msg = - (err as { response?: { data?: { message?: string } } })?.response?.data?.message ?? - '保存失败' - toast.error(msg) - } finally { - setSaving(false) + return true + } catch { + return false } } - // ── 测试 ── + const handleSave = async (e: FormEvent) => { + e.preventDefault() + setSaving(true) + const ok = await silentSave() + if (ok) { + toast.success(editingId ? '站点已更新' : '站点已添加') + closeModal() + await loadSites() + } else { + const msg = '保存失败' + toast.error(msg) + } + setSaving(false) + } + + // 测试(先自动保存) const handleTest = async (id: string) => { + // 如果正在编辑当前站点,先保存 + if (editingId === id) { + setSaving(true) + const ok = await silentSave() + setSaving(false) + if (!ok) { + toast.error('保存失败,无法测试') + return + } + } setTestingId(id) try { const res = await sitesAPI.test(id) @@ -247,6 +285,33 @@ export function SitesPage() { {site.is_default && ( 默认 )} + {site.use_proxy && ( + 代理 + )} + {site.rate_limit && ( + 限流 + )} + {site.browser_emulation && ( + 浏览器 + )} + + + {/* 状态与统计(只读) */} +
+
状态: + + {site.login_status || 'unknown'} + +
+ {(site.upload_bytes || 0) > 0 && ( +
↑ {Math.round((site.upload_bytes ?? 0) / 1073741824 * 100) / 100} GB / ↓ {Math.round((site.download_bytes ?? 0) / 1073741824 * 100) / 100} GB
+ )} + {site.priority !== 50 && ( +
优先级:{site.priority}
+ )}
{/* 操作按钮 */} @@ -466,6 +531,18 @@ export function SitesPage() { /> )} + + {/* RSS 地址(主表单) */} +
+ + setForm((f) => ({ ...f, rss_url: e.target.value }))} + /> +

站点 RSS 订阅地址,用于获取最新资源

+
{/* 高级选项 */} @@ -479,6 +556,82 @@ export function SitesPage() { {advancedOpen && (
+ {/* 新增高级字段 */} +
+
+ + setForm((f) => ({ ...f, user_agent: e.target.value }))} + /> +
+
+ + setForm((f) => ({ ...f, timeout: Number(e.target.value) }))} + /> +
+
+ + setForm((f) => ({ ...f, priority: Number(e.target.value) }))} + /> +
+
+ + setForm((f) => ({ ...f, downloader: e.target.value }))} + /> +
+
+ + {/* 开关类字段 */} +
+ + + +
+ + {/* Extra JSON */}