怎样在PHP项目中实现用户主题偏好?

wen java案例 2

本文目录导读:

怎样在PHP项目中实现用户主题偏好?

  1. 基础方案:Cookie + Session(适合简单场景)
  2. 数据库持久化方案(适合用户系统)
  3. 现代化的API方案(适合前后端分离)
  4. 完整的CSS方案(配合CSS变量)
  5. 最佳实践建议

在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);
}

最佳实践建议

  1. 分级存储策略:根据用户状态决定存储优先级

    • 未登录:Cookie/LocalStorage
    • 已登录:数据库 + 缓存
  2. 缓存优化:使用Redis缓存热点用户的主题偏好

  3. 渐进增强:先加载默认主题,再通过JavaScript异步获取用户偏好

  4. 性能考虑

    • 将主题相关的CSS单独打包
    • 使用CSS变量减少重复代码
    • 考虑使用<link rel="preload">预加载常用主题
  5. 无障碍支持

    <meta name="color-scheme" content="<?php echo $theme; ?>">

选择合适的方案取决于你的应用场景、用户数量和性能要求,对于大部分中小型项目,数据库 + Session/Cookie的组合方案就足够使用了。

抱歉,评论功能暂时关闭!