fixed:自定义主题应用所有人

This commit is contained in:
djteang 2025-09-22 17:20:02 +08:00
parent c462e1c2b5
commit 3457a7c565
5 changed files with 153 additions and 278 deletions

View File

@ -15,31 +15,21 @@
// 应用自定义CSS
if (css) {
let customStyleEl = document.getElementById('init-theme-css');
let customStyleEl = document.getElementById('custom-theme-css');
if (!customStyleEl) {
customStyleEl = document.createElement('style');
customStyleEl.id = 'init-theme-css';
customStyleEl.id = 'custom-theme-css';
document.head.appendChild(customStyleEl);
}
customStyleEl.textContent = css;
}
}
// 从localStorage获取保存的主题
const savedTheme = localStorage.getItem('app-theme');
const savedCustomCSS = localStorage.getItem('app-custom-css') || '';
// 应用默认主题避免闪烁等待GlobalThemeLoader加载全站配置
applyTheme('default', '');
console.log('主题初始化完成,等待加载全站配置');
// 立即应用已保存的主题(如果有)
if (savedTheme) {
applyTheme(savedTheme, savedCustomCSS);
console.log('主题已初始化(本地设置):', savedTheme);
} else {
// 没有用户设置时,先应用默认主题
applyTheme('default', '');
console.log('主题已初始化(默认)');
}
// 注意GlobalThemeLoader会在React组件挂载后进一步处理全站配置
// 注意GlobalThemeLoader会在React组件挂载后加载并应用全站主题配置
} catch (error) {
console.error('主题初始化失败:', error);
}

View File

@ -23,7 +23,6 @@ import { DoubanItem } from '@/lib/types';
import CapsuleSwitch from '@/components/CapsuleSwitch';
import ContinueWatching from '@/components/ContinueWatching';
import PageLayout from '@/components/PageLayout';
import { useThemeInit } from '@/hooks/useTheme';
import ScrollableRow from '@/components/ScrollableRow';
import { useSite } from '@/components/SiteProvider';
import VideoCard from '@/components/VideoCard';
@ -516,8 +515,6 @@ function HomeClient() {
}
export default function Home() {
// 初始化主题
useThemeInit();
return (
<Suspense>
<HomeClient />

View File

@ -14,59 +14,19 @@ const GlobalThemeLoader = () => {
console.log('获取到全站主题配置:', result);
if (result.success && result.data) {
const { defaultTheme, customCSS, allowUserCustomization } = result.data;
const { defaultTheme, customCSS } = result.data;
// 检查用户是否有自定义设置
const userTheme = localStorage.getItem('app-theme');
const userCustomCSS = localStorage.getItem('app-custom-css') || '';
console.log('加载全站主题配置:', { defaultTheme, customCSS });
console.log('当前用户设置:', { userTheme, userCustomCSS });
console.log('全站配置:', { defaultTheme, customCSS, allowUserCustomization });
// 如果不允许用户自定义,强制应用全局配置
if (!allowUserCustomization) {
localStorage.setItem('app-theme', defaultTheme);
localStorage.setItem('app-custom-css', customCSS);
applyTheme(defaultTheme, customCSS);
console.log('强制应用全站配置:', defaultTheme);
return;
}
// 智能决定使用哪个主题
let finalTheme = defaultTheme;
let finalCSS = customCSS;
// 检查是否需要强制应用全站主题
// 如果localStorage中存储的主题与全站默认不同说明可能是过期的设置需要更新
const shouldForceGlobalTheme = !userTheme || userTheme !== defaultTheme;
if (shouldForceGlobalTheme) {
// 强制应用全站默认配置
finalTheme = defaultTheme;
finalCSS = customCSS;
localStorage.setItem('app-theme', defaultTheme);
if (customCSS) {
localStorage.setItem('app-custom-css', customCSS);
} else {
localStorage.removeItem('app-custom-css');
}
console.log('强制应用全站默认主题:', defaultTheme, '(替换过期设置:', userTheme, ')');
} else {
// 用户设置与全站默认一致,使用现有设置
finalTheme = userTheme;
finalCSS = userCustomCSS || customCSS;
console.log('保持一致的主题设置:', userTheme);
}
// 应用最终主题
applyTheme(finalTheme, finalCSS);
// 直接应用全站配置
applyTheme(defaultTheme, customCSS);
console.log('已应用全站主题:', defaultTheme);
}
} catch (error) {
console.error('加载全局主题配置失败:', error);
// 失败时使用本地设置或默认设置
const savedTheme = localStorage.getItem('app-theme') || 'default';
const savedCustomCSS = localStorage.getItem('app-custom-css') || '';
applyTheme(savedTheme, savedCustomCSS);
console.error('加载全站主题配置失败:', error);
// 失败时使用默认设置
applyTheme('default', '');
console.log('加载配置失败,使用默认主题');
}
};
@ -83,10 +43,10 @@ const GlobalThemeLoader = () => {
}
// 应用自定义CSS
let customStyleEl = document.getElementById('global-theme-css');
let customStyleEl = document.getElementById('custom-theme-css');
if (!customStyleEl) {
customStyleEl = document.createElement('style');
customStyleEl.id = 'global-theme-css';
customStyleEl.id = 'custom-theme-css';
document.head.appendChild(customStyleEl);
}
customStyleEl.textContent = css;

View File

@ -274,7 +274,6 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
customCSS: string;
allowUserCustomization: boolean;
} | null>(null);
const [isGlobalMode, setIsGlobalMode] = useState(false);
const isAdmin = role === 'admin' || role === 'owner';
@ -338,23 +337,23 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
// 加载全局配置
const globalConfig = await loadGlobalThemeConfig();
if (isGlobalMode && globalConfig) {
if (globalConfig) {
// 使用全局配置
setCurrentTheme(globalConfig.defaultTheme);
setCustomCSS(globalConfig.customCSS);
applyTheme(globalConfig.defaultTheme, globalConfig.customCSS);
} else {
// 使用本地配置
const savedTheme = localStorage.getItem('app-theme') || globalConfig?.defaultTheme || 'default';
const savedCustomCSS = localStorage.getItem('app-custom-css') || '';
setCurrentTheme(savedTheme);
setCustomCSS(savedCustomCSS);
applyTheme(savedTheme, savedCustomCSS);
// 如果没有全局配置,使用默认值
const defaultTheme = 'default';
const defaultCSS = '';
setCurrentTheme(defaultTheme);
setCustomCSS(defaultCSS);
applyTheme(defaultTheme, defaultCSS);
}
};
initTheme();
}, [isGlobalMode]);
}, []);
// 应用主题
const applyTheme = (themeId: string, css: string = '') => {
@ -383,7 +382,7 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
setCurrentTheme(themeId);
applyTheme(themeId, customCSS);
if (isGlobalMode && isAdmin) {
if (isAdmin) {
// 保存到全局配置
const success = await saveGlobalThemeConfig({
defaultTheme: themeId,
@ -399,15 +398,12 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
allowUserCustomization: globalThemeConfig?.allowUserCustomization ?? true,
});
}
} else {
// 保存到本地
localStorage.setItem('app-theme', themeId);
}
const theme = themes.find(t => t.id === themeId);
showAlert({
type: 'success',
title: isGlobalMode ? '全局主题已设置' : '主题已切换',
title: '全站主题已设置',
message: `已切换到${theme?.name}`,
timer: 2000
});
@ -432,7 +428,7 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
try {
applyTheme(currentTheme, customCSS);
if (isGlobalMode && isAdmin) {
if (isAdmin) {
// 保存到全局配置
const success = await saveGlobalThemeConfig({
defaultTheme: currentTheme,
@ -449,12 +445,10 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
});
}
} else {
// 保存到本地
localStorage.setItem('app-custom-css', customCSS);
showAlert({
type: 'success',
title: '自定义样式已应用',
message: '您的自定义CSS已生效',
type: 'warning',
title: '权限不足',
message: '仅管理员可以设置全站主题',
timer: 2000
});
}
@ -469,14 +463,28 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
};
// 重置自定义CSS
const handleCustomCSSReset = () => {
const handleCustomCSSReset = async () => {
setCustomCSS('');
applyTheme(currentTheme, '');
localStorage.removeItem('app-custom-css');
if (isAdmin) {
// 保存到全局配置
await saveGlobalThemeConfig({
defaultTheme: currentTheme,
customCSS: '',
allowUserCustomization: globalThemeConfig?.allowUserCustomization ?? true,
});
setGlobalThemeConfig({
defaultTheme: currentTheme,
customCSS: '',
allowUserCustomization: globalThemeConfig?.allowUserCustomization ?? true,
});
}
showAlert({
type: 'success',
title: '自定义样式已重置',
title: '全站自定义样式已重置',
timer: 2000
});
};
@ -495,68 +503,33 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
return (
<div className="space-y-6">
{/* 管理员控制面板 */}
{isAdmin && (
{isAdmin && globalThemeConfig && (
<div className="bg-theme-surface border border-theme-border rounded-lg p-4">
<h3 className="text-lg font-semibold text-theme-text mb-4 flex items-center gap-2">
<Palette className="h-5 w-5" />
</h3>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<label className="text-sm font-medium text-theme-text"></label>
<p className="text-xs text-theme-text-secondary mt-1">
</p>
<div className="p-3 bg-theme-accent/5 border border-theme-accent/20 rounded-lg">
<div className="text-sm text-theme-text">
<strong></strong>
</div>
<div className="flex items-center gap-3">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="configMode"
checked={!isGlobalMode}
onChange={() => setIsGlobalMode(false)}
className="w-4 h-4 text-theme-accent"
/>
<span className="text-sm text-theme-text"></span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="configMode"
checked={isGlobalMode}
onChange={() => setIsGlobalMode(true)}
className="w-4 h-4 text-theme-accent"
/>
<span className="text-sm text-theme-text"></span>
</label>
<div className="text-xs text-theme-text-secondary mt-1">
: {themes.find(t => t.id === globalThemeConfig.defaultTheme)?.name || globalThemeConfig.defaultTheme}
{globalThemeConfig.customCSS && ' | 包含自定义CSS'}
{!globalThemeConfig.allowUserCustomization && ' | 禁止用户自定义'}
</div>
</div>
{globalThemeConfig && (
<div className="p-3 bg-theme-accent/5 border border-theme-accent/20 rounded-lg">
<div className="text-sm text-theme-text">
<strong></strong>
</div>
<div className="text-xs text-theme-text-secondary mt-1">
: {themes.find(t => t.id === globalThemeConfig.defaultTheme)?.name || globalThemeConfig.defaultTheme}
{globalThemeConfig.customCSS && ' | 包含自定义CSS'}
{!globalThemeConfig.allowUserCustomization && ' | 禁止用户自定义'}
</div>
<div className="p-3 bg-blue-50 border border-blue-200 rounded-lg dark:bg-blue-900/20 dark:border-blue-700">
<div className="flex items-center gap-2 text-blue-800 dark:text-blue-200">
<span className="text-sm font-medium"> </span>
</div>
)}
{isGlobalMode && (
<div className="p-3 bg-yellow-50 border border-yellow-200 rounded-lg dark:bg-yellow-900/20 dark:border-yellow-700">
<div className="flex items-center gap-2 text-yellow-800 dark:text-yellow-200">
<span className="text-sm font-medium"> </span>
</div>
<p className="text-xs text-yellow-700 dark:text-yellow-300 mt-1">
</p>
</div>
)}
<p className="text-xs text-blue-700 dark:text-blue-300 mt-1">
</p>
</div>
</div>
</div>
)}
@ -565,18 +538,18 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
<div>
<h3 className="text-lg font-semibold text-theme-text mb-4 flex items-center gap-2">
<Palette className="h-5 w-5" />
{isGlobalMode && isAdmin ? '全站默认主题' : '主题选择'}
</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{themes.map((theme) => (
<div
key={theme.id}
className={`relative p-4 border-2 rounded-xl cursor-pointer transition-all ${currentTheme === theme.id
className={`relative p-4 border-2 rounded-xl transition-all ${currentTheme === theme.id
? 'border-theme-accent bg-theme-accent/5'
: 'border-theme-border bg-theme-surface hover:border-theme-accent/50'
}`}
onClick={() => handleThemeChange(theme.id)}
: 'border-theme-border bg-theme-surface'
} ${isAdmin ? 'cursor-pointer hover:border-theme-accent/50' : 'cursor-not-allowed opacity-60'}`}
onClick={() => isAdmin && handleThemeChange(theme.id)}
>
{/* 主题预览 */}
<div className="flex items-center justify-between mb-3">
@ -589,11 +562,11 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
<button
onClick={(e) => {
e.stopPropagation();
handleThemePreview(theme.id);
if (isAdmin) handleThemePreview(theme.id);
}}
className="p-1 text-theme-text-secondary hover:text-theme-accent transition-colors"
title="预览主题"
disabled={previewMode}
className={`p-1 transition-colors ${isAdmin ? 'text-theme-text-secondary hover:text-theme-accent' : 'text-theme-text-secondary opacity-50 cursor-not-allowed'}`}
title={isAdmin ? "预览主题" : "仅管理员可预览"}
disabled={previewMode || !isAdmin}
>
<Eye className="h-4 w-4" />
</button>
@ -621,18 +594,35 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-theme-text flex items-center gap-2">
<Palette className="h-5 w-5" />
</h3>
<button
onClick={() => setShowCustomEditor(!showCustomEditor)}
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-theme-surface border border-theme-border rounded-lg hover:bg-theme-accent/5 transition-colors"
>
{showCustomEditor ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
{showCustomEditor ? '收起编辑器' : '展开编辑器'}
</button>
{isAdmin ? (
<button
onClick={() => setShowCustomEditor(!showCustomEditor)}
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-theme-surface border border-theme-border rounded-lg hover:bg-theme-accent/5 transition-colors"
>
{showCustomEditor ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
{showCustomEditor ? '收起编辑器' : '展开编辑器'}
</button>
) : (
<div className="text-sm text-theme-text-secondary">
</div>
)}
</div>
{showCustomEditor && (
{!isAdmin && (
<div className="p-4 bg-yellow-50 border border-yellow-200 rounded-lg dark:bg-yellow-900/20 dark:border-yellow-700 mb-4">
<div className="flex items-center gap-2 text-yellow-800 dark:text-yellow-200">
<span className="text-sm font-medium"> </span>
</div>
<p className="text-xs text-yellow-700 dark:text-yellow-300 mt-1">
</p>
</div>
)}
{isAdmin && showCustomEditor && (
<div className="space-y-4">
<div className="text-sm text-theme-text-secondary bg-theme-surface p-3 rounded-lg border border-theme-border">
<p className="mb-2">💡 <strong>使</strong></p>
@ -685,47 +675,49 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
</div>
{/* CSS 模板库 */}
<div className="bg-theme-surface border border-theme-border rounded-lg p-4">
<h4 className="font-medium text-theme-text mb-3 flex items-center gap-2">
<Palette className="h-4 w-4" />
🎨
</h4>
<p className="text-sm text-theme-text-secondary mb-4"></p>
{isAdmin && (
<div className="bg-theme-surface border border-theme-border rounded-lg p-4">
<h4 className="font-medium text-theme-text mb-3 flex items-center gap-2">
<Palette className="h-4 w-4" />
🎨
</h4>
<p className="text-sm text-theme-text-secondary mb-4"></p>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{cssTemplates.map((template) => (
<div key={template.id} className="p-3 border border-theme-border rounded-lg hover:bg-theme-accent/5 transition-colors group">
<div className="flex items-center justify-between mb-2">
<h5 className="text-sm font-medium text-theme-text">{template.name}</h5>
<button
onClick={() => handleApplyTemplate(template.css, template.name)}
className="text-xs px-2 py-1 bg-theme-accent text-white rounded hover:opacity-90 transition-opacity opacity-0 group-hover:opacity-100"
>
</button>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{cssTemplates.map((template) => (
<div key={template.id} className="p-3 border border-theme-border rounded-lg hover:bg-theme-accent/5 transition-colors group">
<div className="flex items-center justify-between mb-2">
<h5 className="text-sm font-medium text-theme-text">{template.name}</h5>
<button
onClick={() => handleApplyTemplate(template.css, template.name)}
className="text-xs px-2 py-1 bg-theme-accent text-white rounded hover:opacity-90 transition-opacity opacity-0 group-hover:opacity-100"
>
</button>
</div>
<p className="text-xs text-theme-text-secondary mb-2">{template.description}</p>
<div className="text-xs bg-theme-bg rounded p-2 max-h-16 overflow-y-auto">
<code className="whitespace-pre-wrap text-theme-text-secondary">{template.preview}</code>
</div>
</div>
<p className="text-xs text-theme-text-secondary mb-2">{template.description}</p>
<div className="text-xs bg-theme-bg rounded p-2 max-h-16 overflow-y-auto">
<code className="whitespace-pre-wrap text-theme-text-secondary">{template.preview}</code>
</div>
</div>
))}
</div>
))}
</div>
<div className="mt-4 p-3 bg-theme-accent/5 border border-theme-accent/20 rounded-lg">
<p className="text-xs text-theme-text-secondary">
<strong>💡 使</strong> "应用"CSS编辑器"应用样式"
</p>
<div className="mt-4 p-3 bg-theme-accent/5 border border-theme-accent/20 rounded-lg">
<p className="text-xs text-theme-text-secondary">
<strong>💡 使</strong> "应用"CSS编辑器"应用样式"
</p>
</div>
</div>
</div>
)}
{/* 使用说明 */}
<div className="bg-theme-surface border border-theme-border rounded-lg p-4">
<h4 className="font-medium text-theme-text mb-2">📖 </h4>
<h4 className="font-medium text-theme-text mb-2">📖 </h4>
<div className="text-sm text-theme-text-secondary space-y-2">
<p><strong></strong></p>
<p><strong>CSS</strong>CSS变量或直接样式实现个性化定制</p>
<p><strong></strong>使</p>
<p><strong></strong>{isAdmin ? '选择预设主题即可一键切换全站整体风格' : '由管理员设置的全站预设主题'}</p>
{isAdmin && <p><strong>CSS</strong>CSS变量或直接样式实现全站个性化定制</p>}
{isAdmin && <p><strong></strong>使</p>}
<p><strong></strong></p>
<ul className="text-xs space-y-1 ml-4 mt-1">
<li> <code className="bg-theme-bg px-1 rounded">--color-theme-bg</code> - </li>
@ -734,12 +726,16 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
<li> <code className="bg-theme-bg px-1 rounded">--color-theme-text</code> - </li>
<li> <code className="bg-theme-bg px-1 rounded">--color-theme-border</code> - </li>
</ul>
<p><strong></strong></p>
<ul className="text-xs space-y-1 ml-4 mt-1">
<li> <code className="bg-theme-bg px-1 rounded">{`body { background: linear-gradient(...); }`}</code></li>
<li> 使Tailwind<code className="bg-theme-bg px-1 rounded">{`.my-class { @apply bg-red-500; }`}</code></li>
<li> </li>
</ul>
{isAdmin && (
<>
<p><strong></strong></p>
<ul className="text-xs space-y-1 ml-4 mt-1">
<li> <code className="bg-theme-bg px-1 rounded">{`body { background: linear-gradient(...); }`}</code></li>
<li> 使Tailwind<code className="bg-theme-bg px-1 rounded">{`.my-class { @apply bg-red-500; }`}</code></li>
<li> </li>
</ul>
</>
)}
</div>
</div>
</div>

View File

@ -1,87 +1,19 @@
// 全局主题Hook - 用于在任何组件中初始化和使用主题
import { useEffect } from 'react';
// 全局主题Hook - 已弃用,主题现在由 GlobalThemeLoader 统一管理
// 保留此文件是为了向后兼容性,但不再使用
export const useThemeInit = () => {
useEffect(() => {
// 确保在客户端环境中执行
if (typeof window === 'undefined') return;
try {
// 从localStorage获取保存的主题
const savedTheme = localStorage.getItem('app-theme') || 'default';
const savedCustomCSS = localStorage.getItem('app-custom-css') || '';
// 立即应用主题到HTML元素
const html = document.documentElement;
// 移除所有主题属性
html.removeAttribute('data-theme');
// 应用保存的主题
if (savedTheme !== 'default') {
html.setAttribute('data-theme', savedTheme);
}
// 应用自定义CSS
if (savedCustomCSS) {
let customStyleEl = document.getElementById('custom-theme-css');
if (!customStyleEl) {
customStyleEl = document.createElement('style');
customStyleEl.id = 'custom-theme-css';
document.head.appendChild(customStyleEl);
}
customStyleEl.textContent = savedCustomCSS;
}
console.log(`主题已初始化: ${savedTheme}`);
} catch (error) {
console.error('主题初始化失败:', error);
}
}, []);
// 不再执行任何操作,主题由 GlobalThemeLoader 处理
console.warn('useThemeInit is deprecated. Theme is now managed by GlobalThemeLoader.');
};
export const useTheme = () => {
const applyTheme = (themeId: string, css: string = '') => {
if (typeof window === 'undefined') return;
const html = document.documentElement;
// 移除所有主题class
html.removeAttribute('data-theme');
// 应用新主题
if (themeId !== 'default') {
html.setAttribute('data-theme', themeId);
}
// 应用自定义CSS
let customStyleEl = document.getElementById('custom-theme-css');
if (!customStyleEl) {
customStyleEl = document.createElement('style');
customStyleEl.id = 'custom-theme-css';
document.head.appendChild(customStyleEl);
}
customStyleEl.textContent = css;
// 保存到localStorage
localStorage.setItem('app-theme', themeId);
localStorage.setItem('app-custom-css', css);
};
const getCurrentTheme = () => {
if (typeof window === 'undefined') return 'default';
return localStorage.getItem('app-theme') || 'default';
};
const getCurrentCustomCSS = () => {
if (typeof window === 'undefined') return '';
return localStorage.getItem('app-custom-css') || '';
};
// 已弃用:主题现在由 GlobalThemeLoader 和 ThemeManager 统一管理
console.warn('useTheme is deprecated. Use ThemeManager component for theme management.');
return {
applyTheme,
getCurrentTheme,
getCurrentCustomCSS
applyTheme: () => console.warn('applyTheme is deprecated'),
getCurrentTheme: () => 'default',
getCurrentCustomCSS: () => ''
};
};