✨ 添加主题切换功能:支持暗色/亮色玻璃主题
功能说明: - 管理员可在系统设置中配置全局默认主题 - 普通用户可在设置页面选择:跟随全局/暗色/亮色 - 分享页面自动继承分享者的主题偏好 - 主题设置实时保存,刷新后保持 技术实现: - 后端:数据库添加theme_preference字段,新增主题API - 前端:CSS变量实现主题切换,localStorage缓存避免闪烁 - 分享页:加载时获取分享者主题设置 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
103
frontend/app.js
103
frontend/app.js
@@ -250,7 +250,12 @@ createApp({
|
||||
// SFTP空间使用统计
|
||||
sftpUsage: null, // { totalSize, totalSizeFormatted, fileCount, dirCount }
|
||||
sftpUsageLoading: false,
|
||||
sftpUsageError: null
|
||||
sftpUsageError: null,
|
||||
|
||||
// 主题设置
|
||||
currentTheme: 'dark', // 当前生效的主题: 'dark' 或 'light'
|
||||
globalTheme: 'dark', // 全局默认主题(管理员设置)
|
||||
userThemePreference: null // 用户主题偏好: 'dark', 'light', 或 null(跟随全局)
|
||||
};
|
||||
},
|
||||
|
||||
@@ -281,6 +286,91 @@ createApp({
|
||||
},
|
||||
|
||||
methods: {
|
||||
// ========== 主题管理 ==========
|
||||
// 初始化主题
|
||||
initTheme() {
|
||||
// 先从localStorage读取,避免页面闪烁
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme) {
|
||||
this.applyTheme(savedTheme);
|
||||
}
|
||||
},
|
||||
|
||||
// 加载用户主题设置(登录后调用)
|
||||
async loadUserTheme() {
|
||||
try {
|
||||
const res = await axios.get(`${this.apiBase}/api/user/theme`, {
|
||||
headers: { Authorization: `Bearer ${this.token}` }
|
||||
});
|
||||
if (res.data.success) {
|
||||
this.globalTheme = res.data.theme.global;
|
||||
this.userThemePreference = res.data.theme.user;
|
||||
this.currentTheme = res.data.theme.effective;
|
||||
this.applyTheme(this.currentTheme);
|
||||
localStorage.setItem('theme', this.currentTheme);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载主题设置失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 应用主题到DOM
|
||||
applyTheme(theme) {
|
||||
this.currentTheme = theme;
|
||||
if (theme === 'light') {
|
||||
document.body.classList.add('light-theme');
|
||||
} else {
|
||||
document.body.classList.remove('light-theme');
|
||||
}
|
||||
},
|
||||
|
||||
// 切换用户主题偏好
|
||||
async setUserTheme(theme) {
|
||||
try {
|
||||
const res = await axios.post(`${this.apiBase}/api/user/theme`,
|
||||
{ theme },
|
||||
{ headers: { Authorization: `Bearer ${this.token}` }}
|
||||
);
|
||||
if (res.data.success) {
|
||||
this.userThemePreference = res.data.theme.user;
|
||||
this.currentTheme = res.data.theme.effective;
|
||||
this.applyTheme(this.currentTheme);
|
||||
localStorage.setItem('theme', this.currentTheme);
|
||||
this.showToast('success', '主题已更新', theme === null ? '已设为跟随全局' : (theme === 'dark' ? '已切换到暗色主题' : '已切换到亮色主题'));
|
||||
}
|
||||
} catch (error) {
|
||||
this.showToast('error', '主题更新失败', error.response?.data?.message || '请稍后重试');
|
||||
}
|
||||
},
|
||||
|
||||
// 获取主题显示文本
|
||||
getThemeText(theme) {
|
||||
if (theme === null) return '跟随全局';
|
||||
return theme === 'dark' ? '暗色主题' : '亮色主题';
|
||||
},
|
||||
|
||||
// 设置全局主题(管理员)
|
||||
async setGlobalTheme(theme) {
|
||||
try {
|
||||
const res = await axios.post(`${this.apiBase}/api/admin/settings`,
|
||||
{ global_theme: theme },
|
||||
{ headers: { Authorization: `Bearer ${this.token}` }}
|
||||
);
|
||||
if (res.data.success) {
|
||||
this.globalTheme = theme;
|
||||
// 如果用户没有设置个人偏好,则跟随全局
|
||||
if (this.userThemePreference === null) {
|
||||
this.currentTheme = theme;
|
||||
this.applyTheme(theme);
|
||||
localStorage.setItem('theme', theme);
|
||||
}
|
||||
this.showToast('success', '全局主题已更新', theme === 'dark' ? '默认暗色主题' : '默认亮色主题');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showToast('error', '设置失败', error.response?.data?.message || '请稍后重试');
|
||||
}
|
||||
},
|
||||
|
||||
// 提取URL中的token(兼容缺少 ? 的场景)
|
||||
getTokenFromUrl(key) {
|
||||
const currentHref = window.location.href;
|
||||
@@ -438,6 +528,8 @@ handleDragLeave(e) {
|
||||
|
||||
// 启动定期检查用户配置
|
||||
this.startProfileSync();
|
||||
// 加载用户主题设置
|
||||
this.loadUserTheme();
|
||||
// 管理员直接跳转到管理后台
|
||||
if (this.user.is_admin) {
|
||||
this.currentView = 'admin';
|
||||
@@ -878,6 +970,8 @@ handleDragLeave(e) {
|
||||
|
||||
// 启动定期检查用户配置
|
||||
this.startProfileSync();
|
||||
// 加载用户主题设置
|
||||
this.loadUserTheme();
|
||||
|
||||
// 读取上次停留的视图(需合法才生效)
|
||||
const savedView = localStorage.getItem('lastView');
|
||||
@@ -2207,6 +2301,10 @@ handleDragLeave(e) {
|
||||
if (response.data.success) {
|
||||
const settings = response.data.settings;
|
||||
this.systemSettings.maxUploadSizeMB = Math.round(settings.max_upload_size / (1024 * 1024));
|
||||
// 加载全局主题设置
|
||||
if (settings.global_theme) {
|
||||
this.globalTheme = settings.global_theme;
|
||||
}
|
||||
if (settings.smtp) {
|
||||
this.systemSettings.smtp.host = settings.smtp.host || '';
|
||||
this.systemSettings.smtp.port = settings.smtp.port || 465;
|
||||
@@ -2584,6 +2682,9 @@ handleDragLeave(e) {
|
||||
// 初始化调试模式状态
|
||||
this.debugMode = localStorage.getItem('debugMode') === 'true';
|
||||
|
||||
// 初始化主题(从localStorage加载,避免闪烁)
|
||||
this.initTheme();
|
||||
|
||||
// 处理URL中的验证/重置token(兼容缺少?的旧链接)
|
||||
const verifyToken = this.getTokenFromUrl('verifyToken');
|
||||
const resetToken = this.getTokenFromUrl('resetToken');
|
||||
|
||||
Reference in New Issue
Block a user