Skip to content

主题开发指南

主题开发指南将帮助您为 Yuan-ICP 系统创建自定义主题,包括主题结构、开发流程、最佳实践和示例代码。

📋 主题系统概览

Yuan-ICP 采用模块化的主题架构,支持:

  • 多主题切换:在多个主题之间快速切换
  • 响应式设计:支持各种设备尺寸
  • 模板引擎:基于PHP的模板系统
  • 资源管理:CSS、JavaScript、图片等资源管理
  • 配置系统:主题级别的配置选项

主题系统架构

themes/
├── default/                 # 默认主题
│   ├── theme.json          # 主题配置文件
│   ├── css/                # 样式文件
│   ├── js/                 # JavaScript文件
│   ├── images/             # 图片资源
│   └── templates/          # 模板文件
└── custom/                  # 自定义主题
    ├── theme.json          # 主题配置
    ├── css/                # 样式文件
    ├── js/                 # 脚本文件
    └── templates/          # 模板文件

🎨 创建新主题

1. 创建主题目录

首先在 themes/ 目录下创建新的主题文件夹:

bash
mkdir themes/my-theme
cd themes/my-theme
mkdir css js images templates

2. 创建主题配置文件

创建 theme.json 文件:

json
{
    "name": "我的主题",
    "version": "1.0.0",
    "description": "一个自定义的Yuan-ICP主题",
    "author": "您的姓名",
    "license": "MIT",
    "requires": {
        "system_version": "1.0.0"
    },
    "supports": {
        "responsive": true,
        "dark_mode": false,
        "customization": true
    },
    "templates": [
        "header",
        "footer",
        "home",
        "apply",
        "query",
        "result"
    ],
    "assets": {
        "css": [
            "css/main.css",
            "css/responsive.css"
        ],
        "js": [
            "js/main.js"
        ]
    }
}

3. 主题目录结构

my-theme/
├── theme.json              # 主题配置文件
├── css/                    # 样式文件目录
│   ├── main.css           # 主样式文件
│   ├── components.css     # 组件样式
│   └── responsive.css     # 响应式样式
├── js/                     # JavaScript文件目录
│   ├── main.js            # 主脚本文件
│   └── utils.js           # 工具函数
├── images/                 # 图片资源目录
│   ├── logo.png           # 主题logo
│   └── icons/             # 图标文件
└── templates/              # 模板文件目录
    ├── header.php          # 页面头部
    ├── footer.php          # 页面底部
    ├── home.php            # 首页模板
    ├── apply.php           # 申请页面
    ├── query.php           # 查询页面
    └── result.php          # 结果页面

📝 模板开发

模板系统基础

Yuan-ICP 使用基于PHP的模板系统,支持以下功能:

  • 数据传递:从控制器传递数据到模板
  • 条件渲染:根据数据状态显示不同内容
  • 循环渲染:渲染列表数据
  • 模板继承:基础模板和子模板

基础模板结构

页面头部模板 (header.php)

php
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?= htmlspecialchars($data['page_title'] ?? 'Yuan-ICP') ?></title>
    
    <!-- 主题CSS文件 -->
    <?php foreach ($this->getThemeAssets('css') as $css): ?>
        <link rel="stylesheet" href="<?= $css ?>">
    <?php endforeach; ?>
    
    <!-- 系统CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.4/css/all.min.css">
    
    <!-- 自定义样式 -->
    <style>
        :root {
            --primary-color: #007bff;
            --secondary-color: #6c757d;
            --success-color: #28a745;
            --danger-color: #dc3545;
            --warning-color: #ffc107;
            --info-color: #17a2b8;
        }
    </style>
</head>
<body>
    <!-- 导航栏 -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
        <div class="container">
            <a class="navbar-brand" href="/">
                <img src="<?= $this->getThemeAsset('images/logo.png') ?>" alt="Logo" height="30">
                <?= htmlspecialchars($data['config']['site_name'] ?? 'Yuan-ICP') ?>
            </a>
            
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                <span class="navbar-toggler-icon"></span>
            </button>
            
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item">
                        <a class="nav-link" href="/">首页</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="/apply.php">申请备案</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="/query.php">查询备案</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="/announcements.php">公告</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>
    
    <!-- 主要内容容器 -->
    <main class="main-content">
php
    </main>
    
    <!-- 页脚 -->
    <footer class="footer bg-dark text-light py-4 mt-5">
        <div class="container">
            <div class="row">
                <div class="col-md-6">
                    <h5><?= htmlspecialchars($data['config']['site_name'] ?? 'Yuan-ICP') ?></h5>
                    <p class="text-muted">专业的虚拟ICP备案管理系统</p>
                </div>
                <div class="col-md-6 text-md-end">
                    <p class="text-muted">
                        &copy; <?= date('Y') ?> <?= htmlspecialchars($data['config']['site_name'] ?? 'Yuan-ICP') ?>. 
                        保留所有权利。
                    </p>
                    <p class="text-muted">
                        <a href="https://github.com/bbb-lsy07/Yuan-ICP" class="text-light">
                            <i class="fab fa-github"></i> GitHub
                        </a>
                    </p>
                </div>
            </div>
        </div>
    </footer>
    
    <!-- 主题JavaScript文件 -->
    <?php foreach ($this->getThemeAssets('js') as $js): ?>
        <script src="<?= $js ?>"></script>
    <?php endforeach; ?>
    
    <!-- 系统JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    
    <!-- 自定义脚本 -->
    <script>
        // 主题特定的JavaScript代码
        document.addEventListener('DOMContentLoaded', function() {
            // 初始化主题功能
            initTheme();
        });
        
        function initTheme() {
            // 主题初始化逻辑
            console.log('主题已初始化');
        }
    </script>
</body>
</html>

首页模板 (home.php)

php
<!-- 英雄区域 -->
<section class="hero bg-primary text-white py-5">
    <div class="container text-center">
        <h1 class="display-4 mb-4">欢迎使用 Yuan-ICP</h1>
        <p class="lead mb-4">专业的虚拟ICP备案管理系统,为您提供完整的备案服务体验</p>
        <div class="hero-buttons">
            <a href="/apply.php" class="btn btn-light btn-lg me-3">
                <i class="fas fa-file-alt me-2"></i>申请备案
            </a>
            <a href="/query.php" class="btn btn-outline-light btn-lg">
                <i class="fas fa-search me-2"></i>查询备案
            </a>
        </div>
    </div>
</section>

<!-- 统计信息 -->
<section class="stats py-5">
    <div class="container">
        <div class="row text-center">
            <div class="col-md-3 mb-4">
                <div class="stat-card">
                    <i class="fas fa-file-alt fa-3x text-primary mb-3"></i>
                    <h3><?= number_format($data['stats']['total'] ?? 0) ?></h3>
                    <p class="text-muted">总申请数</p>
                </div>
            </div>
            <div class="col-md-3 mb-4">
                <div class="stat-card">
                    <i class="fas fa-clock fa-3x text-warning mb-3"></i>
                    <h3><?= number_format($data['stats']['pending'] ?? 0) ?></h3>
                    <p class="text-muted">待审核</p>
                </div>
            </div>
            <div class="col-md-3 mb-4">
                <div class="stat-card">
                    <i class="fas fa-check-circle fa-3x text-success mb-3"></i>
                    <h3><?= number_format($data['stats']['approved'] ?? 0) ?></h3>
                    <p class="text-muted">已通过</p>
                </div>
            </div>
            <div class="col-md-3 mb-4">
                <div class="stat-card">
                    <i class="fas fa-times-circle fa-3x text-danger mb-3"></i>
                    <h3><?= number_format($data['stats']['rejected'] ?? 0) ?></h3>
                    <p class="text-muted">已驳回</p>
                </div>
            </div>
        </div>
    </div>
</section>

<!-- 公告列表 -->
<?php if (!empty($data['announcements'])): ?>
<section class="announcements py-5 bg-light">
    <div class="container">
        <h2 class="text-center mb-5">最新公告</h2>
        <div class="row">
            <?php foreach (array_slice($data['announcements'], 0, 6) as $announcement): ?>
            <div class="col-md-6 col-lg-4 mb-4">
                <div class="announcement-card card h-100">
                    <div class="card-body">
                        <?php if ($announcement['is_pinned']): ?>
                        <span class="badge bg-warning mb-2">
                            <i class="fas fa-thumbtack"></i> 置顶
                        </span>
                        <?php endif; ?>
                        <h5 class="card-title"><?= htmlspecialchars($announcement['title']) ?></h5>
                        <p class="card-text text-muted">
                            <?= htmlspecialchars(substr($announcement['content'], 0, 100)) ?>...
                        </p>
                        <small class="text-muted">
                            <i class="fas fa-calendar me-1"></i>
                            <?= date('Y-m-d', strtotime($announcement['created_at'])) ?>
                        </small>
                    </div>
                </div>
            </div>
            <?php endforeach; ?>
        </div>
        <div class="text-center mt-4">
            <a href="/announcements.php" class="btn btn-outline-primary">
                查看所有公告 <i class="fas fa-arrow-right ms-1"></i>
            </a>
        </div>
    </div>
</section>
<?php endif; ?>

<!-- 功能特色 -->
<section class="features py-5">
    <div class="container">
        <h2 class="text-center mb-5">系统特色</h2>
        <div class="row">
            <div class="col-md-4 mb-4">
                <div class="feature-card text-center">
                    <i class="fas fa-shield-alt fa-3x text-primary mb-3"></i>
                    <h4>安全可靠</h4>
                    <p class="text-muted">内置安全机制,保护您的数据安全</p>
                </div>
            </div>
            <div class="col-md-4 mb-4">
                <div class="feature-card text-center">
                    <i class="fas fa-mobile-alt fa-3x text-success mb-3"></i>
                    <h4>响应式设计</h4>
                    <p class="text-muted">完美支持各种设备,提供一致的用户体验</p>
                </div>
            </div>
            <div class="col-md-4 mb-4">
                <div class="feature-card text-center">
                    <i class="fas fa-cogs fa-3x text-info mb-3"></i>
                    <h4>高度可定制</h4>
                    <p class="text-muted">支持主题切换和插件扩展</p>
                </div>
            </div>
        </div>
    </div>
</section>

🎨 CSS样式开发

主样式文件 (css/main.css)

css
/* 主题变量定义 */
:root {
    --primary-color: #007bff;
    --secondary-color: #6c757d;
    --success-color: #28a745;
    --danger-color: #dc3545;
    --warning-color: #ffc107;
    --info-color: #17a2b8;
    --light-color: #f8f9fa;
    --dark-color: #343a40;
    
    --font-family-sans-serif: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    --font-family-monospace: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
    
    --border-radius: 0.375rem;
    --box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
    --transition: all 0.15s ease-in-out;
}

/* 全局样式 */
body {
    font-family: var(--font-family-sans-serif);
    line-height: 1.6;
    color: var(--dark-color);
}

/* 导航栏样式 */
.navbar-brand img {
    transition: var(--transition);
}

.navbar-brand:hover img {
    transform: scale(1.05);
}

/* 英雄区域样式 */
.hero {
    background: linear-gradient(135deg, var(--primary-color) 0%, #0056b3 100%);
    position: relative;
    overflow: hidden;
}

.hero::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: url('../images/hero-bg.svg') center/cover;
    opacity: 0.1;
}

.hero .container {
    position: relative;
    z-index: 1;
}

.hero-buttons .btn {
    padding: 0.75rem 2rem;
    font-weight: 600;
    border-radius: var(--border-radius);
    transition: var(--transition);
}

.hero-buttons .btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}

/* 统计卡片样式 */
.stat-card {
    padding: 2rem 1rem;
    background: white;
    border-radius: var(--border-radius);
    box-shadow: var(--box-shadow);
    transition: var(--transition);
}

.stat-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}

.stat-card h3 {
    color: var(--primary-color);
    font-weight: 700;
    margin-bottom: 0.5rem;
}

/* 公告卡片样式 */
.announcement-card {
    border: none;
    box-shadow: var(--box-shadow);
    transition: var(--transition);
}

.announcement-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}

.announcement-card .card-title {
    color: var(--dark-color);
    font-weight: 600;
}

/* 功能特色样式 */
.feature-card {
    padding: 2rem 1rem;
}

.feature-card i {
    color: var(--primary-color);
    margin-bottom: 1rem;
}

.feature-card h4 {
    color: var(--dark-color);
    font-weight: 600;
    margin-bottom: 1rem;
}

/* 页脚样式 */
.footer {
    background: linear-gradient(135deg, var(--dark-color) 0%, #495057 100%);
}

.footer a {
    text-decoration: none;
    transition: var(--transition);
}

.footer a:hover {
    opacity: 0.8;
}

/* 按钮样式增强 */
.btn {
    border-radius: var(--border-radius);
    transition: var(--transition);
    font-weight: 500;
}

.btn-primary {
    background-color: var(--primary-color);
    border-color: var(--primary-color);
}

.btn-primary:hover {
    background-color: #0056b3;
    border-color: #0056b3;
    transform: translateY(-1px);
}

/* 表单样式 */
.form-control {
    border-radius: var(--border-radius);
    border: 1px solid #ced4da;
    transition: var(--transition);
}

.form-control:focus {
    border-color: var(--primary-color);
    box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}

/* 卡片样式 */
.card {
    border: none;
    border-radius: var(--border-radius);
    box-shadow: var(--box-shadow);
    transition: var(--transition);
}

.card:hover {
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}

响应式样式 (css/responsive.css)

css
/* 移动端优化 */
@media (max-width: 768px) {
    .hero h1 {
        font-size: 2rem;
    }
    
    .hero .lead {
        font-size: 1rem;
    }
    
    .hero-buttons .btn {
        display: block;
        width: 100%;
        margin-bottom: 1rem;
    }
    
    .stat-card {
        margin-bottom: 1rem;
    }
    
    .announcement-card {
        margin-bottom: 1rem;
    }
}

/* 平板端优化 */
@media (min-width: 769px) and (max-width: 1024px) {
    .hero h1 {
        font-size: 2.5rem;
    }
    
    .hero-buttons .btn {
        margin-bottom: 0.5rem;
    }
}

/* 大屏幕优化 */
@media (min-width: 1025px) {
    .hero {
        padding: 6rem 0;
    }
    
    .hero h1 {
        font-size: 3.5rem;
    }
    
    .hero .lead {
        font-size: 1.25rem;
    }
    
    .stat-card {
        padding: 3rem 2rem;
    }
}

/* 超小屏幕优化 */
@media (max-width: 576px) {
    .container {
        padding-left: 15px;
        padding-right: 15px;
    }
    
    .navbar-brand img {
        height: 25px;
    }
    
    .hero {
        padding: 3rem 0;
    }
    
    .hero h1 {
        font-size: 1.75rem;
    }
    
    .hero .lead {
        font-size: 0.9rem;
    }
}

/* 横屏模式优化 */
@media (orientation: landscape) and (max-height: 500px) {
    .hero {
        padding: 2rem 0;
    }
    
    .hero h1 {
        font-size: 2rem;
        margin-bottom: 1rem;
    }
    
    .hero .lead {
        margin-bottom: 1rem;
    }
}

/* 高分辨率屏幕优化 */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
    .hero::before {
        background-image: url('../images/hero-bg@2x.svg');
    }
    
    .navbar-brand img {
        image-rendering: -webkit-optimize-contrast;
        image-rendering: crisp-edges;
    }
}

/* 打印样式 */
@media print {
    .navbar,
    .hero,
    .footer,
    .btn {
        display: none !important;
    }
    
    .main-content {
        margin: 0 !important;
        padding: 0 !important;
    }
    
    .card {
        border: 1px solid #000 !important;
        box-shadow: none !important;
    }
}

🔧 JavaScript开发

主脚本文件 (js/main.js)

javascript
/**
 * Yuan-ICP 主题主脚本文件
 */

// 主题命名空间
window.YuanICPTheme = {
    // 配置选项
    config: {
        debug: false,
        animationDuration: 300,
        scrollOffset: 100
    },
    
    // 初始化函数
    init: function() {
        this.initComponents();
        this.bindEvents();
        this.initAnimations();
        
        if (this.config.debug) {
            console.log('Yuan-ICP 主题已初始化');
        }
    },
    
    // 初始化组件
    initComponents: function() {
        this.initTooltips();
        this.initPopovers();
        this.initScrollSpy();
        this.initBackToTop();
    },
    
    // 绑定事件
    bindEvents: function() {
        this.bindScrollEvents();
        this.bindFormEvents();
        this.bindNavigationEvents();
    },
    
    // 初始化动画
    initAnimations: function() {
        this.initScrollAnimations();
        this.initHoverEffects();
    },
    
    // 初始化工具提示
    initTooltips: function() {
        const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
        tooltipTriggerList.map(function (tooltipTriggerEl) {
            return new bootstrap.Tooltip(tooltipTriggerEl);
        });
    },
    
    // 初始化弹出框
    initPopovers: function() {
        const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
        popoverTriggerList.map(function (popoverTriggerEl) {
            return new bootstrap.Popover(popoverTriggerEl);
        });
    },
    
    // 初始化滚动监听
    initScrollSpy: function() {
        const scrollSpyElement = document.querySelector('[data-bs-spy="scroll"]');
        if (scrollSpyElement) {
            new bootstrap.ScrollSpy(scrollSpyElement);
        }
    },
    
    // 初始化返回顶部按钮
    initBackToTop: function() {
        const backToTopBtn = document.createElement('button');
        backToTopBtn.innerHTML = '<i class="fas fa-arrow-up"></i>';
        backToTopBtn.className = 'btn btn-primary back-to-top';
        backToTopBtn.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 1000;
            display: none;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
        `;
        
        document.body.appendChild(backToTopBtn);
        
        // 显示/隐藏返回顶部按钮
        window.addEventListener('scroll', () => {
            if (window.pageYOffset > this.config.scrollOffset) {
                backToTopBtn.style.display = 'block';
            } else {
                backToTopBtn.style.display = 'none';
            }
        });
        
        // 返回顶部功能
        backToTopBtn.addEventListener('click', () => {
            window.scrollTo({
                top: 0,
                behavior: 'smooth'
            });
        });
    },
    
    // 绑定滚动事件
    bindScrollEvents: function() {
        let ticking = false;
        
        function updateScroll() {
            // 滚动时的处理逻辑
            ticking = false;
        }
        
        function requestTick() {
            if (!ticking) {
                requestAnimationFrame(updateScroll);
                ticking = true;
            }
        }
        
        window.addEventListener('scroll', requestTick);
    },
    
    // 绑定表单事件
    bindFormEvents: function() {
        const forms = document.querySelectorAll('form');
        
        forms.forEach(form => {
            form.addEventListener('submit', this.handleFormSubmit.bind(this));
            form.addEventListener('input', this.handleFormInput.bind(this));
        });
    },
    
    // 处理表单提交
    handleFormSubmit: function(event) {
        const form = event.target;
        const submitBtn = form.querySelector('button[type="submit"]');
        
        if (submitBtn) {
            submitBtn.disabled = true;
            submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 提交中...';
        }
        
        // 表单验证逻辑
        if (!this.validateForm(form)) {
            event.preventDefault();
            if (submitBtn) {
                submitBtn.disabled = false;
                submitBtn.innerHTML = '提交';
            }
        }
    },
    
    // 处理表单输入
    handleFormInput: function(event) {
        const input = event.target;
        const formGroup = input.closest('.form-group');
        
        if (formGroup) {
            this.validateField(input);
        }
    },
    
    // 表单验证
    validateForm: function(form) {
        let isValid = true;
        const inputs = form.querySelectorAll('input[required], textarea[required], select[required]');
        
        inputs.forEach(input => {
            if (!this.validateField(input)) {
                isValid = false;
            }
        });
        
        return isValid;
    },
    
    // 字段验证
    validateField: function(field) {
        const value = field.value.trim();
        const formGroup = field.closest('.form-group');
        const errorElement = formGroup.querySelector('.invalid-feedback');
        
        // 移除之前的验证状态
        field.classList.remove('is-valid', 'is-invalid');
        if (errorElement) {
            errorElement.remove();
        }
        
        // 验证逻辑
        let isValid = true;
        let errorMessage = '';
        
        if (field.hasAttribute('required') && !value) {
            isValid = false;
            errorMessage = '此字段为必填项';
        } else if (field.type === 'email' && value && !this.isValidEmail(value)) {
            isValid = false;
            errorMessage = '请输入有效的邮箱地址';
        } else if (field.type === 'url' && value && !this.isValidUrl(value)) {
            isValid = false;
            errorMessage = '请输入有效的URL地址';
        }
        
        // 应用验证结果
        if (isValid) {
            field.classList.add('is-valid');
        } else {
            field.classList.add('is-invalid');
            this.showFieldError(formGroup, errorMessage);
        }
        
        return isValid;
    },
    
    // 显示字段错误
    showFieldError: function(formGroup, message) {
        const errorElement = document.createElement('div');
        errorElement.className = 'invalid-feedback';
        errorElement.textContent = message;
        formGroup.appendChild(errorElement);
    },
    
    // 邮箱验证
    isValidEmail: function(email) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    },
    
    // URL验证
    isValidUrl: function(url) {
        try {
            new URL(url);
            return true;
        } catch {
            return false;
        }
    },
    
    // 绑定导航事件
    bindNavigationEvents: function() {
        const navLinks = document.querySelectorAll('.navbar-nav .nav-link');
        
        navLinks.forEach(link => {
            link.addEventListener('click', this.handleNavClick.bind(this));
        });
    },
    
    // 处理导航点击
    handleNavClick: function(event) {
        const link = event.target;
        const href = link.getAttribute('href');
        
        // 平滑滚动到锚点
        if (href.startsWith('#')) {
            event.preventDefault();
            const target = document.querySelector(href);
            if (target) {
                target.scrollIntoView({
                    behavior: 'smooth',
                    block: 'start'
                });
            }
        }
    },
    
    // 初始化滚动动画
    initScrollAnimations: function() {
        const observerOptions = {
            threshold: 0.1,
            rootMargin: '0px 0px -50px 0px'
        };
        
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    entry.target.classList.add('animate-in');
                }
            });
        }, observerOptions);
        
        const animatedElements = document.querySelectorAll('.animate-on-scroll');
        animatedElements.forEach(el => observer.observe(el));
    },
    
    // 初始化悬停效果
    initHoverEffects: function() {
        const hoverElements = document.querySelectorAll('.hover-effect');
        
        hoverElements.forEach(element => {
            element.addEventListener('mouseenter', function() {
                this.style.transform = 'scale(1.05)';
            });
            
            element.addEventListener('mouseleave', function() {
                this.style.transform = 'scale(1)';
            });
        });
    },
    
    // 工具函数
    utils: {
        // 防抖函数
        debounce: function(func, wait) {
            let timeout;
            return function executedFunction(...args) {
                const later = () => {
                    clearTimeout(timeout);
                    func(...args);
                };
                clearTimeout(timeout);
                timeout = setTimeout(later, wait);
            };
        },
        
        // 节流函数
        throttle: function(func, limit) {
            let inThrottle;
            return function() {
                const args = arguments;
                const context = this;
                if (!inThrottle) {
                    func.apply(context, args);
                    inThrottle = true;
                    setTimeout(() => inThrottle = false, limit);
                }
            };
        },
        
        // 格式化日期
        formatDate: function(date) {
            return new Intl.DateTimeFormat('zh-CN', {
                year: 'numeric',
                month: 'long',
                day: 'numeric'
            }).format(new Date(date));
        },
        
        // 格式化数字
        formatNumber: function(num) {
            return new Intl.NumberFormat('zh-CN').format(num);
        }
    }
};

// 页面加载完成后初始化主题
document.addEventListener('DOMContentLoaded', function() {
    YuanICPTheme.init();
});

// 导出主题对象(用于模块化开发)
if (typeof module !== 'undefined' && module.exports) {
    module.exports = YuanICPTheme;
}

🎯 主题配置系统

主题配置选项

php
// 主题配置示例
$theme_config = [
    'colors' => [
        'primary' => '#007bff',
        'secondary' => '#6c757d',
        'success' => '#28a745',
        'danger' => '#dc3545',
        'warning' => '#ffc107',
        'info' => '#17a2b8'
    ],
    'typography' => [
        'font_family' => 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif',
        'font_size_base' => '16px',
        'line_height_base' => '1.6'
    ],
    'layout' => [
        'container_max_width' => '1200px',
        'sidebar_width' => '250px',
        'header_height' => '70px'
    ],
    'features' => [
        'dark_mode' => false,
        'animations' => true,
        'responsive' => true
    ]
];

动态配置加载

php
// 从数据库加载主题配置
function loadThemeConfig($theme_name) {
    $db = db();
    $stmt = $db->prepare("
        SELECT config_key, config_value 
        FROM system_config 
        WHERE config_key LIKE 'theme_{$theme_name}_%'
    ");
    $stmt->execute();
    
    $config = [];
    while ($row = $stmt->fetch()) {
        $key = str_replace("theme_{$theme_name}_", '', $row['config_key']);
        $config[$key] = $row['config_value'];
    }
    
    return $config;
}

🚀 主题开发最佳实践

1. 设计原则

  • 一致性:保持整个主题的设计风格一致
  • 可用性:确保用户界面易于使用
  • 可访问性:支持不同用户的需求
  • 性能:优化加载速度和响应时间

2. 代码规范

  • 命名规范:使用有意义的类名和ID
  • 注释规范:为复杂代码添加注释
  • 文件组织:合理组织CSS和JavaScript文件
  • 版本控制:使用语义化版本号

3. 性能优化

  • CSS优化:合并和压缩CSS文件
  • JavaScript优化:异步加载非关键脚本
  • 图片优化:使用适当的图片格式和大小
  • 缓存策略:合理设置缓存头

4. 兼容性考虑

  • 浏览器支持:支持主流浏览器的最新版本
  • 响应式设计:确保在各种设备上的显示效果
  • 渐进增强:基础功能在所有环境下可用

🔧 主题测试和调试

测试环境

bash
# 创建测试环境
mkdir theme-test
cd theme-test

# 复制主题文件
cp -r ../themes/my-theme ./

# 启动本地服务器
php -S localhost:8000

调试工具

javascript
// 启用调试模式
YuanICPTheme.config.debug = true;

// 控制台日志
console.log('主题状态:', YuanICPTheme);

// 性能监控
console.time('主题初始化');
YuanICPTheme.init();
console.timeEnd('主题初始化');

常见问题排查

  1. 样式不生效:检查CSS文件路径和语法
  2. JavaScript错误:查看浏览器控制台错误信息
  3. 响应式问题:测试不同屏幕尺寸
  4. 性能问题:使用浏览器开发者工具分析

📚 相关文档

🎯 总结

通过本指南,您可以:

  1. 创建自定义主题:按照规范创建完整的主题
  2. 优化用户体验:提供美观、易用的界面
  3. 扩展系统功能:通过主题系统扩展功能
  4. 维护主题代码:遵循最佳实践,便于维护

记住,好的主题不仅仅是美观,更重要的是提供良好的用户体验和功能完整性。通过不断测试和优化,您可以创建出优秀的Yuan-ICP主题。

基于 MIT 协议发布