From c554015526aa7bd829348a5bab54ef9193d762ec Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Sat, 14 Jun 2025 00:40:29 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20refactor(console=5Fsetting):=20migr?= =?UTF-8?q?ate=20console=20settings=20to=20model=5Fsetting=20auto-injectio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend - Introduce `setting/console_setting` package that defines `ConsoleSetting` struct with JSON tags and validation rules. - Register the new module with `config.GlobalConfig` to enable automatic injection/export of configuration values. - Remove legacy `setting/console.go` and the manual `OptionMap` hooks; clean up `model/option.go`. - Add `controller/console_migrate.go` providing `/api/option/migrate_console_setting` endpoint for one-off data migration. - Update controllers (`misc`, `option`, `uptime_kuma`) and router to consume namespaced keys `console_setting.*`. Frontend - Refactor dashboard pages (`SettingsAPIInfo`, `SettingsAnnouncements`, `SettingsFAQ`, `SettingsUptimeKuma`) and detail page to read/write the new keys. - Simplify `DashboardSetting.js` state to only include namespaced options. BREAKING CHANGE: All console-related option keys are now stored under `console_setting.*`. Run the migration endpoint once after deployment to preserve existing data. --- controller/misc.go | 7 +- controller/option.go | 13 +- controller/uptime_kuma.go | 9 +- model/option.go | 3 - setting/console.go | 327 ------------------ setting/console_setting/config.go | 33 ++ setting/console_setting/validation.go | 212 ++++++++++++ .../components/settings/DashboardSetting.js | 10 +- web/src/pages/Detail/index.js | 4 +- .../Setting/Dashboard/SettingsAPIInfo.js | 9 +- .../Dashboard/SettingsAnnouncements.js | 9 +- .../pages/Setting/Dashboard/SettingsFAQ.js | 38 +- .../Setting/Dashboard/SettingsUptimeKuma.js | 16 +- 13 files changed, 304 insertions(+), 386 deletions(-) delete mode 100644 setting/console.go create mode 100644 setting/console_setting/config.go create mode 100644 setting/console_setting/validation.go diff --git a/controller/misc.go b/controller/misc.go index 8fa8e8f6..ccbdecb5 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -11,6 +11,7 @@ import ( "one-api/setting" "one-api/setting/operation_setting" "one-api/setting/system_setting" + "one-api/setting/console_setting" "strings" "github.com/gin-gonic/gin" @@ -79,9 +80,9 @@ func GetStatus(c *gin.Context) { "oidc_client_id": system_setting.GetOIDCSettings().ClientId, "oidc_authorization_endpoint": system_setting.GetOIDCSettings().AuthorizationEndpoint, "setup": constant.Setup, - "api_info": setting.GetApiInfo(), - "announcements": setting.GetAnnouncements(), - "faq": setting.GetFAQ(), + "api_info": console_setting.GetApiInfo(), + "announcements": console_setting.GetAnnouncements(), + "faq": console_setting.GetFAQ(), }, }) return diff --git a/controller/option.go b/controller/option.go index b52012fd..b782c1f2 100644 --- a/controller/option.go +++ b/controller/option.go @@ -6,6 +6,7 @@ import ( "one-api/common" "one-api/model" "one-api/setting" + "one-api/setting/console_setting" "one-api/setting/system_setting" "strings" @@ -119,8 +120,8 @@ func UpdateOption(c *gin.Context) { }) return } - case "ApiInfo": - err = setting.ValidateApiInfo(option.Value) + case "console_setting.api_info": + err = console_setting.ValidateApiInfo(option.Value) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, @@ -128,8 +129,8 @@ func UpdateOption(c *gin.Context) { }) return } - case "Announcements": - err = setting.ValidateConsoleSettings(option.Value, "Announcements") + case "console_setting.announcements": + err = console_setting.ValidateConsoleSettings(option.Value, "Announcements") if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, @@ -137,8 +138,8 @@ func UpdateOption(c *gin.Context) { }) return } - case "FAQ": - err = setting.ValidateConsoleSettings(option.Value, "FAQ") + case "console_setting.faq": + err = console_setting.ValidateConsoleSettings(option.Value, "FAQ") if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, diff --git a/controller/uptime_kuma.go b/controller/uptime_kuma.go index 6ceaa1f3..e89b2b9d 100644 --- a/controller/uptime_kuma.go +++ b/controller/uptime_kuma.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" "net/http" - "one-api/common" + "one-api/setting/console_setting" "strings" "time" @@ -77,10 +77,9 @@ func getAndDecode(ctx context.Context, client *http.Client, url string, dest int } func GetUptimeKumaStatus(c *gin.Context) { - common.OptionMapRWMutex.RLock() - uptimeKumaUrl := common.OptionMap["UptimeKumaUrl"] - slug := common.OptionMap["UptimeKumaSlug"] - common.OptionMapRWMutex.RUnlock() + cs := console_setting.GetConsoleSetting() + uptimeKumaUrl := cs.UptimeKumaUrl + slug := cs.UptimeKumaSlug if uptimeKumaUrl == "" || slug == "" { c.JSON(http.StatusOK, gin.H{ diff --git a/model/option.go b/model/option.go index 0cb4fc35..d1689cb7 100644 --- a/model/option.go +++ b/model/option.go @@ -123,9 +123,6 @@ func InitOptionMap() { common.OptionMap["SensitiveWords"] = setting.SensitiveWordsToString() common.OptionMap["StreamCacheQueueLength"] = strconv.Itoa(setting.StreamCacheQueueLength) common.OptionMap["AutomaticDisableKeywords"] = operation_setting.AutomaticDisableKeywordsToString() - common.OptionMap["ApiInfo"] = "" - common.OptionMap["UptimeKumaUrl"] = "" - common.OptionMap["UptimeKumaSlug"] = "" // 自动添加所有注册的模型配置 modelConfigs := config.GlobalConfig.ExportAllConfigs() diff --git a/setting/console.go b/setting/console.go deleted file mode 100644 index 94023666..00000000 --- a/setting/console.go +++ /dev/null @@ -1,327 +0,0 @@ -package setting - -import ( - "encoding/json" - "fmt" - "net/url" - "one-api/common" - "regexp" - "sort" - "strings" - "time" -) - -// ValidateConsoleSettings 验证控制台设置信息格式 -func ValidateConsoleSettings(settingsStr string, settingType string) error { - if settingsStr == "" { - return nil // 空字符串是合法的 - } - - switch settingType { - case "ApiInfo": - return validateApiInfo(settingsStr) - case "Announcements": - return validateAnnouncements(settingsStr) - case "FAQ": - return validateFAQ(settingsStr) - default: - return fmt.Errorf("未知的设置类型:%s", settingType) - } -} - -// validateApiInfo 验证API信息格式 -func validateApiInfo(apiInfoStr string) error { - var apiInfoList []map[string]interface{} - if err := json.Unmarshal([]byte(apiInfoStr), &apiInfoList); err != nil { - return fmt.Errorf("API信息格式错误:%s", err.Error()) - } - - // 验证数组长度 - if len(apiInfoList) > 50 { - return fmt.Errorf("API信息数量不能超过50个") - } - - // 允许的颜色值 - validColors := map[string]bool{ - "blue": true, "green": true, "cyan": true, "purple": true, "pink": true, - "red": true, "orange": true, "amber": true, "yellow": true, "lime": true, - "light-green": true, "teal": true, "light-blue": true, "indigo": true, - "violet": true, "grey": true, - } - - // URL正则表达式,支持域名和IP地址格式 - // 域名格式:https://example.com 或 https://sub.example.com:8080 - // IP地址格式:https://192.168.1.1 或 https://192.168.1.1:8080 - urlRegex := regexp.MustCompile(`^https?://(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(?::[0-9]{1,5})?(?:/.*)?$`) - - for i, apiInfo := range apiInfoList { - // 检查必填字段 - urlStr, ok := apiInfo["url"].(string) - if !ok || urlStr == "" { - return fmt.Errorf("第%d个API信息缺少URL字段", i+1) - } - - route, ok := apiInfo["route"].(string) - if !ok || route == "" { - return fmt.Errorf("第%d个API信息缺少线路描述字段", i+1) - } - - description, ok := apiInfo["description"].(string) - if !ok || description == "" { - return fmt.Errorf("第%d个API信息缺少说明字段", i+1) - } - - color, ok := apiInfo["color"].(string) - if !ok || color == "" { - return fmt.Errorf("第%d个API信息缺少颜色字段", i+1) - } - - // 验证URL格式 - if !urlRegex.MatchString(urlStr) { - return fmt.Errorf("第%d个API信息的URL格式不正确", i+1) - } - - // 验证URL可解析性 - if _, err := url.Parse(urlStr); err != nil { - return fmt.Errorf("第%d个API信息的URL无法解析:%s", i+1, err.Error()) - } - - // 验证字段长度 - if len(urlStr) > 500 { - return fmt.Errorf("第%d个API信息的URL长度不能超过500字符", i+1) - } - - if len(route) > 100 { - return fmt.Errorf("第%d个API信息的线路描述长度不能超过100字符", i+1) - } - - if len(description) > 200 { - return fmt.Errorf("第%d个API信息的说明长度不能超过200字符", i+1) - } - - // 验证颜色值 - if !validColors[color] { - return fmt.Errorf("第%d个API信息的颜色值不合法", i+1) - } - - // 检查并过滤危险字符(防止XSS) - dangerousChars := []string{" 100 { - return fmt.Errorf("系统公告数量不能超过100个") - } - - // 允许的类型值 - validTypes := map[string]bool{ - "default": true, "ongoing": true, "success": true, "warning": true, "error": true, - } - - for i, announcement := range announcementsList { - // 检查必填字段 - content, ok := announcement["content"].(string) - if !ok || content == "" { - return fmt.Errorf("第%d个公告缺少内容字段", i+1) - } - - // 检查发布日期字段 - publishDate, exists := announcement["publishDate"] - if !exists { - return fmt.Errorf("第%d个公告缺少发布日期字段", i+1) - } - - publishDateStr, ok := publishDate.(string) - if !ok || publishDateStr == "" { - return fmt.Errorf("第%d个公告的发布日期不能为空", i+1) - } - - // 验证ISO日期格式 - if _, err := time.Parse(time.RFC3339, publishDateStr); err != nil { - return fmt.Errorf("第%d个公告的发布日期格式错误", i+1) - } - - // 验证可选字段 - if announcementType, exists := announcement["type"]; exists { - if typeStr, ok := announcementType.(string); ok { - if !validTypes[typeStr] { - return fmt.Errorf("第%d个公告的类型值不合法", i+1) - } - } - } - - // 验证字段长度 - if len(content) > 500 { - return fmt.Errorf("第%d个公告的内容长度不能超过500字符", i+1) - } - - if extra, exists := announcement["extra"]; exists { - if extraStr, ok := extra.(string); ok && len(extraStr) > 200 { - return fmt.Errorf("第%d个公告的说明长度不能超过200字符", i+1) - } - } - - // 检查并过滤危险字符(防止XSS) - dangerousChars := []string{" 100 { - return fmt.Errorf("常见问答数量不能超过100个") - } - - for i, faq := range faqList { - // 检查必填字段 - title, ok := faq["title"].(string) - if !ok || title == "" { - return fmt.Errorf("第%d个问答缺少标题字段", i+1) - } - - content, ok := faq["content"].(string) - if !ok || content == "" { - return fmt.Errorf("第%d个问答缺少内容字段", i+1) - } - - // 验证字段长度 - if len(title) > 200 { - return fmt.Errorf("第%d个问答的标题长度不能超过200字符", i+1) - } - - if len(content) > 1000 { - return fmt.Errorf("第%d个问答的内容长度不能超过1000字符", i+1) - } - - // 检查并过滤危险字符(防止XSS) - dangerousChars := []string{" 20 { - announcements = announcements[:20] - } - - return announcements -} - -// GetFAQ 获取常见问答列表 -func GetFAQ() []map[string]interface{} { - common.OptionMapRWMutex.RLock() - faqStr, exists := common.OptionMap["FAQ"] - common.OptionMapRWMutex.RUnlock() - - if !exists || faqStr == "" { - return []map[string]interface{}{} - } - - var faq []map[string]interface{} - if err := json.Unmarshal([]byte(faqStr), &faq); err != nil { - return []map[string]interface{}{} - } - - return faq -} \ No newline at end of file diff --git a/setting/console_setting/config.go b/setting/console_setting/config.go new file mode 100644 index 00000000..1379380e --- /dev/null +++ b/setting/console_setting/config.go @@ -0,0 +1,33 @@ +package console_setting + +import "one-api/setting/config" + +type ConsoleSetting struct { + ApiInfo string `json:"api_info"` // 控制台 API 信息 (JSON 数组字符串) + UptimeKumaUrl string `json:"uptime_kuma_url"` // Uptime Kuma 服务地址(如 https://status.example.com ) + UptimeKumaSlug string `json:"uptime_kuma_slug"` // Uptime Kuma Status Page Slug + Announcements string `json:"announcements"` // 系统公告 (JSON 数组字符串) + FAQ string `json:"faq"` // 常见问题 (JSON 数组字符串) +} + +// 默认配置 +var defaultConsoleSetting = ConsoleSetting{ + ApiInfo: "", + UptimeKumaUrl: "", + UptimeKumaSlug: "", + Announcements: "", + FAQ: "", +} + +// 全局实例 +var consoleSetting = defaultConsoleSetting + +func init() { + // 注册到全局配置管理器,键名为 console_setting + config.GlobalConfig.Register("console_setting", &consoleSetting) +} + +// GetConsoleSetting 获取 ConsoleSetting 配置实例 +func GetConsoleSetting() *ConsoleSetting { + return &consoleSetting +} \ No newline at end of file diff --git a/setting/console_setting/validation.go b/setting/console_setting/validation.go new file mode 100644 index 00000000..fbab6e87 --- /dev/null +++ b/setting/console_setting/validation.go @@ -0,0 +1,212 @@ +package console_setting + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + "strings" + "time" +) + +// ValidateConsoleSettings 验证控制台设置信息格式 +func ValidateConsoleSettings(settingsStr string, settingType string) error { + if settingsStr == "" { + return nil // 空字符串是合法的 + } + + switch settingType { + case "ApiInfo": + return validateApiInfo(settingsStr) + case "Announcements": + return validateAnnouncements(settingsStr) + case "FAQ": + return validateFAQ(settingsStr) + default: + return fmt.Errorf("未知的设置类型:%s", settingType) + } +} + +// validateApiInfo 验证API信息格式 +func validateApiInfo(apiInfoStr string) error { + var apiInfoList []map[string]interface{} + if err := json.Unmarshal([]byte(apiInfoStr), &apiInfoList); err != nil { + return fmt.Errorf("API信息格式错误:%s", err.Error()) + } + + // 验证数组长度 + if len(apiInfoList) > 50 { + return fmt.Errorf("API信息数量不能超过50个") + } + + // 允许的颜色值 + validColors := map[string]bool{ + "blue": true, "green": true, "cyan": true, "purple": true, "pink": true, + "red": true, "orange": true, "amber": true, "yellow": true, "lime": true, + "light-green": true, "teal": true, "light-blue": true, "indigo": true, + "violet": true, "grey": true, + } + + // URL 正则,支持域名 / IP + urlRegex := regexp.MustCompile(`^https?://(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(?:\:[0-9]{1,5})?(?:/.*)?$`) + + for i, apiInfo := range apiInfoList { + urlStr, ok := apiInfo["url"].(string) + if !ok || urlStr == "" { + return fmt.Errorf("第%d个API信息缺少URL字段", i+1) + } + route, ok := apiInfo["route"].(string) + if !ok || route == "" { + return fmt.Errorf("第%d个API信息缺少线路描述字段", i+1) + } + description, ok := apiInfo["description"].(string) + if !ok || description == "" { + return fmt.Errorf("第%d个API信息缺少说明字段", i+1) + } + color, ok := apiInfo["color"].(string) + if !ok || color == "" { + return fmt.Errorf("第%d个API信息缺少颜色字段", i+1) + } + if !urlRegex.MatchString(urlStr) { + return fmt.Errorf("第%d个API信息的URL格式不正确", i+1) + } + if _, err := url.Parse(urlStr); err != nil { + return fmt.Errorf("第%d个API信息的URL无法解析:%s", i+1, err.Error()) + } + if len(urlStr) > 500 { + return fmt.Errorf("第%d个API信息的URL长度不能超过500字符", i+1) + } + if len(route) > 100 { + return fmt.Errorf("第%d个API信息的线路描述长度不能超过100字符", i+1) + } + if len(description) > 200 { + return fmt.Errorf("第%d个API信息的说明长度不能超过200字符", i+1) + } + if !validColors[color] { + return fmt.Errorf("第%d个API信息的颜色值不合法", i+1) + } + dangerousChars := []string{" 100 { + return fmt.Errorf("系统公告数量不能超过100个") + } + validTypes := map[string]bool{ + "default": true, "ongoing": true, "success": true, "warning": true, "error": true, + } + for i, ann := range list { + content, ok := ann["content"].(string) + if !ok || content == "" { + return fmt.Errorf("第%d个公告缺少内容字段", i+1) + } + publishDateAny, exists := ann["publishDate"] + if !exists { + return fmt.Errorf("第%d个公告缺少发布日期字段", i+1) + } + publishDateStr, ok := publishDateAny.(string) + if !ok || publishDateStr == "" { + return fmt.Errorf("第%d个公告的发布日期不能为空", i+1) + } + if _, err := time.Parse(time.RFC3339, publishDateStr); err != nil { + return fmt.Errorf("第%d个公告的发布日期格式错误", i+1) + } + if t, exists := ann["type"]; exists { + if typeStr, ok := t.(string); ok { + if !validTypes[typeStr] { + return fmt.Errorf("第%d个公告的类型值不合法", i+1) + } + } + } + if len(content) > 500 { + return fmt.Errorf("第%d个公告的内容长度不能超过500字符", i+1) + } + if extra, exists := ann["extra"]; exists { + if extraStr, ok := extra.(string); ok && len(extraStr) > 200 { + return fmt.Errorf("第%d个公告的说明长度不能超过200字符", i+1) + } + } + } + return nil +} + +func validateFAQ(faqStr string) error { + var list []map[string]interface{} + if err := json.Unmarshal([]byte(faqStr), &list); err != nil { + return fmt.Errorf("FAQ信息格式错误:%s", err.Error()) + } + if len(list) > 100 { + return fmt.Errorf("FAQ数量不能超过100个") + } + for i, faq := range list { + question, ok := faq["question"].(string) + if !ok || question == "" { + return fmt.Errorf("第%d个FAQ缺少问题字段", i+1) + } + answer, ok := faq["answer"].(string) + if !ok || answer == "" { + return fmt.Errorf("第%d个FAQ缺少答案字段", i+1) + } + if len(question) > 200 { + return fmt.Errorf("第%d个FAQ的问题长度不能超过200字符", i+1) + } + if len(answer) > 1000 { + return fmt.Errorf("第%d个FAQ的答案长度不能超过1000字符", i+1) + } + } + return nil +} + +// GetAnnouncements 获取系统公告 +func GetAnnouncements() []map[string]interface{} { + annStr := GetConsoleSetting().Announcements + if annStr == "" { + return []map[string]interface{}{} + } + var ann []map[string]interface{} + _ = json.Unmarshal([]byte(annStr), &ann) + return ann +} + +// GetFAQ 获取常见问题 +func GetFAQ() []map[string]interface{} { + faqStr := GetConsoleSetting().FAQ + if faqStr == "" { + return []map[string]interface{}{} + } + var faq []map[string]interface{} + _ = json.Unmarshal([]byte(faqStr), &faq) + return faq +} \ No newline at end of file diff --git a/web/src/components/settings/DashboardSetting.js b/web/src/components/settings/DashboardSetting.js index 649e0ffa..f52add07 100644 --- a/web/src/components/settings/DashboardSetting.js +++ b/web/src/components/settings/DashboardSetting.js @@ -8,11 +8,11 @@ import SettingsUptimeKuma from '../../pages/Setting/Dashboard/SettingsUptimeKuma const DashboardSetting = () => { let [inputs, setInputs] = useState({ - ApiInfo: '', - Announcements: '', - FAQ: '', - UptimeKumaUrl: '', - UptimeKumaSlug: '', + 'console_setting.api_info': '', + 'console_setting.announcements': '', + 'console_setting.faq': '', + 'console_setting.uptime_kuma_url': '', + 'console_setting.uptime_kuma_slug': '', }); let [loading, setLoading] = useState(false); diff --git a/web/src/pages/Detail/index.js b/web/src/pages/Detail/index.js index e4351088..f1d23871 100644 --- a/web/src/pages/Detail/index.js +++ b/web/src/pages/Detail/index.js @@ -1231,10 +1231,10 @@ const Detail = (props) => { {faqData.map((item, index) => ( -

{item.content}

+

{item.answer}

))} diff --git a/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js b/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js index 67a475de..7cb7275b 100644 --- a/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js +++ b/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js @@ -85,7 +85,7 @@ const SettingsAPIInfo = ({ options, refresh }) => { try { setLoading(true); const apiInfoJson = JSON.stringify(apiInfoList); - await updateOption('ApiInfo', apiInfoJson); + await updateOption('console_setting.api_info', apiInfoJson); setHasChanges(false); } catch (error) { console.error('API信息更新失败', error); @@ -185,10 +185,11 @@ const SettingsAPIInfo = ({ options, refresh }) => { }; useEffect(() => { - if (options.ApiInfo !== undefined) { - parseApiInfo(options.ApiInfo); + const apiInfoStr = options['console_setting.api_info'] ?? options.ApiInfo; + if (apiInfoStr !== undefined) { + parseApiInfo(apiInfoStr); } - }, [options.ApiInfo]); + }, [options['console_setting.api_info'], options.ApiInfo]); const columns = [ { diff --git a/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js b/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js index 2646f7bf..caa632e1 100644 --- a/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js +++ b/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js @@ -176,7 +176,7 @@ const SettingsAnnouncements = ({ options, refresh }) => { try { setLoading(true); const announcementsJson = JSON.stringify(announcementsList); - await updateOption('Announcements', announcementsJson); + await updateOption('console_setting.announcements', announcementsJson); setHasChanges(false); } catch (error) { console.error('系统公告更新失败', error); @@ -288,10 +288,11 @@ const SettingsAnnouncements = ({ options, refresh }) => { }; useEffect(() => { - if (options.Announcements !== undefined) { - parseAnnouncements(options.Announcements); + const annStr = options['console_setting.announcements'] ?? options.Announcements; + if (annStr !== undefined) { + parseAnnouncements(annStr); } - }, [options.Announcements]); + }, [options['console_setting.announcements'], options.Announcements]); const handleBatchDelete = () => { if (selectedRowKeys.length === 0) { diff --git a/web/src/pages/Setting/Dashboard/SettingsFAQ.js b/web/src/pages/Setting/Dashboard/SettingsFAQ.js index c5c65c1b..d1211899 100644 --- a/web/src/pages/Setting/Dashboard/SettingsFAQ.js +++ b/web/src/pages/Setting/Dashboard/SettingsFAQ.js @@ -37,8 +37,8 @@ const SettingsFAQ = ({ options, refresh }) => { const [loading, setLoading] = useState(false); const [hasChanges, setHasChanges] = useState(false); const [faqForm, setFaqForm] = useState({ - title: '', - content: '' + question: '', + answer: '' }); const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); @@ -47,8 +47,8 @@ const SettingsFAQ = ({ options, refresh }) => { const columns = [ { title: t('问题标题'), - dataIndex: 'title', - key: 'title', + dataIndex: 'question', + key: 'question', render: (text) => (
{ }, { title: t('回答内容'), - dataIndex: 'content', - key: 'content', + dataIndex: 'answer', + key: 'answer', render: (text) => (
{ try { setLoading(true); const faqJson = JSON.stringify(faqList); - await updateOption('FAQ', faqJson); + await updateOption('console_setting.faq', faqJson); setHasChanges(false); } catch (error) { console.error('常见问答更新失败', error); @@ -137,8 +137,8 @@ const SettingsFAQ = ({ options, refresh }) => { const handleAddFaq = () => { setEditingFaq(null); setFaqForm({ - title: '', - content: '' + question: '', + answer: '' }); setShowFaqModal(true); }; @@ -146,8 +146,8 @@ const SettingsFAQ = ({ options, refresh }) => { const handleEditFaq = (faq) => { setEditingFaq(faq); setFaqForm({ - title: faq.title, - content: faq.content + question: faq.question, + answer: faq.answer }); setShowFaqModal(true); }; @@ -169,7 +169,7 @@ const SettingsFAQ = ({ options, refresh }) => { }; const handleSaveFaq = async () => { - if (!faqForm.title || !faqForm.content) { + if (!faqForm.question || !faqForm.answer) { showError('请填写完整的问答信息'); return; } @@ -226,10 +226,10 @@ const SettingsFAQ = ({ options, refresh }) => { }; useEffect(() => { - if (options.FAQ !== undefined) { - parseFAQ(options.FAQ); + if (options['console_setting.faq'] !== undefined) { + parseFAQ(options['console_setting.faq']); } - }, [options.FAQ]); + }, [options['console_setting.faq']]); const handleBatchDelete = () => { if (selectedRowKeys.length === 0) { @@ -372,21 +372,21 @@ const SettingsFAQ = ({ options, refresh }) => { >
setFaqForm({ ...faqForm, title: value })} + onChange={(value) => setFaqForm({ ...faqForm, question: value })} /> setFaqForm({ ...faqForm, content: value })} + onChange={(value) => setFaqForm({ ...faqForm, answer: value })} /> diff --git a/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js b/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js index 3a7b4896..58f8fedf 100644 --- a/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js +++ b/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js @@ -22,9 +22,9 @@ const SettingsUptimeKuma = ({ options, refresh }) => { const formApiRef = useRef(null); const initValues = useMemo(() => ({ - uptimeKumaUrl: options?.UptimeKumaUrl || '', - uptimeKumaSlug: options?.UptimeKumaSlug || '' - }), [options?.UptimeKumaUrl, options?.UptimeKumaSlug]); + uptimeKumaUrl: options?.['console_setting.uptime_kuma_url'] || '', + uptimeKumaSlug: options?.['console_setting.uptime_kuma_slug'] || '' + }), [options?.['console_setting.uptime_kuma_url'], options?.['console_setting.uptime_kuma_slug']]); useEffect(() => { if (formApiRef.current) { @@ -46,18 +46,18 @@ const SettingsUptimeKuma = ({ options, refresh }) => { const trimmedUrl = (uptimeKumaUrl || '').trim(); const trimmedSlug = (uptimeKumaSlug || '').trim(); - if (trimmedUrl === options?.UptimeKumaUrl && trimmedSlug === options?.UptimeKumaSlug) { + if (trimmedUrl === options?.['console_setting.uptime_kuma_url'] && trimmedSlug === options?.['console_setting.uptime_kuma_slug']) { showSuccess(t('无需保存,配置未变动')); return; } const [urlRes, slugRes] = await Promise.all([ - trimmedUrl === options?.UptimeKumaUrl ? Promise.resolve({ data: { success: true } }) : API.put('/api/option/', { - key: 'UptimeKumaUrl', + trimmedUrl === options?.['console_setting.uptime_kuma_url'] ? Promise.resolve({ data: { success: true } }) : API.put('/api/option/', { + key: 'console_setting.uptime_kuma_url', value: trimmedUrl }), - trimmedSlug === options?.UptimeKumaSlug ? Promise.resolve({ data: { success: true } }) : API.put('/api/option/', { - key: 'UptimeKumaSlug', + trimmedSlug === options?.['console_setting.uptime_kuma_slug'] ? Promise.resolve({ data: { success: true } }) : API.put('/api/option/', { + key: 'console_setting.uptime_kuma_slug', value: trimmedSlug }) ]);