本文目录导读:

在PHP项目中实现用户主题偏好,通常有几种常见方案,下面介绍几种由简单到完善的实现方式。
基础方案:Cookie + Session(适合简单场景)
后端处理逻辑
<?php
class ThemeManager {
// 可用的主题列表
private static $themes = ['light', 'dark', 'blue', 'green'];
// 从请求中获取主题
public static function getUserTheme() {
// 1. 优先检查Session
if (isset($_SESSION['theme']) && in_array($_SESSION['theme'], self::$themes)) {
return $_SESSION['theme'];
}
// 2. 其次检查Cookie
if (isset($_COOKIE['user_theme']) && in_array($_COOKIE['user_theme'], self::$themes)) {
$_SESSION['theme'] = $_COOKIE['user_theme'];
return $_COOKIE['user_theme'];
}
// 3. 返回默认主题
return 'light';
}
// 设置用户主题
public static function setUserTheme($theme) {
if (!in_array($theme, self::$themes)) {
throw new InvalidArgumentException('无效的主题');
}
// 存储到Session
$_SESSION['theme'] = $theme;
// 存储到Cookie(有效期30天)
setcookie('user_theme', $theme, time() + 86400 * 30, '/');
}
}
前端HTML模板
<!-- 在应用入口或布局文件中应用 -->
<!DOCTYPE html>
<html data-theme="<?php echo ThemeManager::getUserTheme(); ?>">
<head>
<link rel="stylesheet" href="/css/theme-<?php echo ThemeManager::getUserTheme(); ?>.css">
</head>
<body>
<!-- 主题切换表单 -->
<form method="post" action="/theme/switch">
<select name="theme">
<option value="light" <?php echo ThemeManager::getUserTheme() === 'light' ? 'selected' : ''; ?>>浅色</option>
<option value="dark" <?php echo ThemeManager::getUserTheme() === 'dark' ? 'selected' : ''; ?>>深色</option>
</select>
<button type="submit">切换主题</button>
</form>
</body>
</html>
数据库持久化方案(适合用户系统)
数据库设计
CREATE TABLE user_preferences (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL UNIQUE,
theme VARCHAR(50) DEFAULT 'light',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
完整服务类
<?php
class UserPreferenceService {
private $db;
public function __construct(PDO $db) {
$this->db = $db;
}
// 获取用户主题偏好
public function getUserTheme($userId) {
// 优先从数据库获取
$stmt = $this->db->prepare(
"SELECT theme FROM user_preferences WHERE user_id = ?"
);
$stmt->execute([$userId]);
$preference = $stmt->fetch(PDO::FETCH_ASSOC);
if ($preference) {
return $preference['theme'];
}
// 没有偏好则创建默认记录
$this->createDefaultPreference($userId);
return 'light';
}
// 更新用户主题偏好
public function updateUserTheme($userId, $theme) {
$stmt = $this->db->prepare(
"INSERT INTO user_preferences (user_id, theme)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE theme = VALUES(theme)"
);
return $stmt->execute([$userId, $theme]);
}
private function createDefaultPreference($userId) {
$stmt = $this->db->prepare(
"INSERT IGNORE INTO user_preferences (user_id, theme) VALUES (?, 'light')"
);
$stmt->execute([$userId]);
}
}
现代化的API方案(适合前后端分离)
API接口设计
<?php
// routes/api.php
Route::post('/api/user/preferences/theme', 'Api\UserPreferenceController@updateTheme');
Route::get('/api/user/preferences/theme', 'Api\UserPreferenceController@getTheme');
Controller实现
<?php
namespace App\Http\Controllers\Api;
use App\Models\UserPreference;
use Illuminate\Http\Request;
class UserPreferenceController extends Controller
{
public function getTheme(Request $request) {
$userId = $request->user()->id;
$preference = UserPreference::firstOrCreate(
['user_id' => $userId],
['theme' => 'light']
);
return response()->json([
'theme' => $preference->theme
]);
}
public function updateTheme(Request $request) {
$validated = $request->validate([
'theme' => 'required|in:light,dark,auto'
]);
$userId = $request->user()->id;
UserPreference::updateOrCreate(
['user_id' => $userId],
['theme' => $validated['theme']]
);
return response()->json(['message' => '主题更新成功']);
}
}
前端JavaScript集成
// theme-switcher.js
class ThemeManager {
constructor() {
this.apiEndpoint = '/api/user/preferences/theme';
this.init();
}
async init() {
try {
const response = await fetch(this.apiEndpoint);
const data = await response.json();
this.applyTheme(data.theme);
} catch (error) {
// API失败时使用本地存储降级
this.applyTheme(localStorage.getItem('theme') || 'light');
}
}
async switchTheme(theme) {
try {
await fetch(this.apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ theme })
});
this.applyTheme(theme);
localStorage.setItem('theme', theme);
} catch (error) {
console.error('切换主题失败:', error);
}
}
applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
// 更新meta theme-color
const metaTheme = document.querySelector('meta[name="theme-color"]');
if (metaTheme) {
const colors = {
'light': '#ffffff',
'dark': '#1a1a2e',
'blue': '#0056b3'
};
metaTheme.content = colors[theme] || colors.light;
}
}
}
// 初始化
const themeManager = new ThemeManager();
// 使用示例
document.querySelectorAll('.theme-switcher').forEach(button => {
button.addEventListener('click', () => {
const theme = button.dataset.theme;
themeManager.switchTheme(theme);
});
});
完整的CSS方案(配合CSS变量)
/* themes.css */
:root {
/* 默认浅色主题 */
--bg-primary: #ffffff;
--text-primary: #333333;
--accent-color: #007bff;
--border-color: #dee2e6;
}
[data-theme="dark"] {
--bg-primary: #1a1a2e;
--text-primary: #e0e0e0;
--accent-color: #66b0ff;
--border-color: #404040;
}
[data-theme="blue"] {
--bg-primary: #f0f8ff;
--text-primary: #003366;
--accent-color: #0056b3;
--border-color: #b8d4f0;
}
/* 应用变量 */
body {
background-color: var(--bg-primary);
color: var(--text-primary);
border-color: var(--border-color);
}
.btn-primary {
background-color: var(--accent-color);
border: 1px solid var(--border-color);
}
最佳实践建议
-
分级存储策略:根据用户状态决定存储优先级
- 未登录:Cookie/LocalStorage
- 已登录:数据库 + 缓存
-
缓存优化:使用Redis缓存热点用户的主题偏好
-
渐进增强:先加载默认主题,再通过JavaScript异步获取用户偏好
-
性能考虑:
- 将主题相关的CSS单独打包
- 使用CSS变量减少重复代码
- 考虑使用
<link rel="preload">预加载常用主题
-
无障碍支持:
<meta name="color-scheme" content="<?php echo $theme; ?>">
选择合适的方案取决于你的应用场景、用户数量和性能要求,对于大部分中小型项目,数据库 + Session/Cookie的组合方案就足够使用了。