feat: 外观设置后端持久化 + 保存按钮 + 跨浏览器同步

后端:
- User 模型新增 Preferences JSON 字段 (theme + login_style)
- 新增 GET/PUT /api/v1/user/preferences (auth) 个人偏好读写
- 新增 GET /api/v1/public/login-style (public) 登录页风格公开端点
- SQLite users 表手动添加 preferences 列

前端:
- AppearanceSettings 从后端加载偏好 + 保存按钮 + dirty 状态提示
- LoginPage 先从后端公开接口获取 login_style (跨浏览器一致)
- useLogin 登录成功后从后端加载偏好并应用主题
- 偏好同时存 localStorage(即时) + 后端DB(跨设备)
This commit is contained in:
logan committed 2026-05-26 22:09:47 +08:00
1 parent 8469df359f
commit 9055cc77ec
7 files changed
+369 -119

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
package handler
import (
"encoding/json"
"net/http"
"sql-platform/internal/middleware"
"sql-platform/internal/model"
"github.com/gin-gonic/gin"
)
// GET /api/v1/user/preferences
func GetUserPreferences(c *gin.Context) {
userID, _, _ := middleware.GetCurrentUser(c)
var user model.User
if err := model.DB.First(&user, userID).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"detail": "User not found"})
return
}
prefs := map[string]string{}
if user.Preferences != nil && *user.Preferences != "" {
json.Unmarshal([]byte(*user.Preferences), &prefs)
}
c.JSON(http.StatusOK, prefs)
}
// PUT /api/v1/user/preferences
func UpdateUserPreferences(c *gin.Context) {
userID, _, _ := middleware.GetCurrentUser(c)
var prefs map[string]string
if err := c.ShouldBindJSON(&prefs); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
return
}
data, _ := json.Marshal(prefs)
str := string(data)
result := model.DB.Model(&model.User{}).Where("id = ?", userID).Update("preferences", str)
if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": result.Error.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Preferences updated", "preferences": prefs})
}
// GET /api/v1/public/login-style (no auth required)
func GetPublicLoginStyle(c *gin.Context) {
// Check system settings first
var setting model.SystemSetting
style := "split" // default
if model.DB.Where("`key` = ?", "login_style").First(&setting).Error == nil {
if setting.Value != "" {
style = setting.Value
}
}
// Also return theme default
theme := "dark"
if model.DB.Where("`key` = ?", "default_theme").First(&setting).Error == nil {
if setting.Value != "" {
theme = setting.Value
}
}
c.JSON(http.StatusOK, gin.H{"login_style": style, "theme": theme})
}
+1
View File
@@ -18,6 +18,7 @@ type User struct {
HashedPassword string `gorm:"size:200;not null" json:"-"`
Role string `gorm:"size:20;not null" json:"role"` // admin, dba, developer, auditor
IsActive *bool `gorm:"default:true" json:"is_active"`
Preferences *string `gorm:"type:text" json:"preferences"` // JSON: {"theme":"dark","login_style":"split"}
LastLogin *time.Time `json:"last_login"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`