首页 / 应用软件 / 一步步教你,集成在线考试与题库管理系统到WordPress

一步步教你,集成在线考试与题库管理系统到WordPress

一步步教你,集成在线考试与题库管理系统到WordPress,通过WordPress程序的代码二次开发实现常用互联网小工具功能

引言:为什么要在WordPress中集成在线考试系统?

在当今数字化教育与企业培训蓬勃发展的时代,在线考试与评估已成为教育机构、企业培训部门乃至知识付费创作者不可或缺的功能。WordPress作为全球最受欢迎的内容管理系统(CMS),其强大的可扩展性使其远不止是一个博客平台。通过代码二次开发,我们可以将专业的在线考试与题库管理系统无缝集成到WordPress中,从而打造一个功能全面、用户体验良好的学习与评估平台。

传统的独立考试系统往往存在与主网站风格不一、用户数据割裂、维护成本高等问题。而在WordPress中集成此类系统,则能实现品牌统一、数据集中管理、用户单点登录等诸多优势。更重要的是,通过深度集成,我们还能利用WordPress的生态,结合其他插件与功能,创造出更丰富的互联网小工具应用场景。

本文将详细解析如何通过代码二次开发,在WordPress中从零开始构建一个功能完善的在线考试与题库管理系统,并探讨如何扩展其作为常用互联网小工具的潜力。无论你是WordPress开发者、教育科技创业者,还是企业培训负责人,都能从中获得切实可行的技术方案与灵感。

第一章:项目规划与系统架构设计

1.1 需求分析与功能规划

在开始编码之前,明确的需求分析是成功的关键。一个完整的在线考试与题库管理系统应包含以下核心模块:

  1. 题库管理模块:支持单选题、多选题、判断题、填空题、问答题等多种题型;支持试题分类、标签、难度分级;支持试题导入导出(Word、Excel、JSON格式)。
  2. 试卷管理模块:支持手动组卷(按分类、难度随机抽题)和固定试卷;设置考试时间、及格分数、考试次数限制等参数。
  3. 考试执行模块:全屏考试防作弊、实时计时、自动保存答案、中途恢复功能。
  4. 成绩与统计分析模块:个人成绩报告、错题集、整体考试统计分析、成绩导出。
  5. 用户与权限管理:与WordPress用户系统集成,区分考生、教师、管理员角色。

1.2 技术选型与架构设计

考虑到系统的复杂性与WordPress的特性,我们采用分层架构:

  • 数据层:在WordPress默认数据库基础上,创建自定义数据表来存储试题、试卷、考试记录等结构化数据。利用WordPress的$wpdb类进行安全的数据操作。
  • 业务逻辑层:开发自定义插件,封装所有考试相关的业务逻辑。采用面向对象编程(OOP)设计,提高代码可维护性。
  • 表现层:结合WordPress主题模板和短代码(Shortcode)或Gutenberg块,实现前端界面的灵活嵌入。使用AJAX提升考试过程的交互体验。

数据库设计示例

  • wp_exam_questions:试题表(ID、题型、题干、选项、答案、解析、分类、难度、创建时间)
  • wp_exam_papers:试卷表(ID、试卷名称、组卷规则、设置参数)
  • wp_exam_records:考试记录表(ID、用户ID、试卷ID、开始时间、结束时间、得分、答案详情)
  • wp_exam_user_meta:用户扩展表(错题集、收藏试题等)

第二章:开发环境搭建与基础插件创建

2.1 本地开发环境配置

推荐使用Local by Flywheel或XAMPP搭建本地WordPress环境。确保环境支持PHP 7.4+、MySQL 5.6+。安装代码编辑器(如VS Code)并配置调试工具(如Xdebug)。

2.2 创建自定义插件骨架

wp-content/plugins/目录下创建新文件夹exam-system,并创建主插件文件exam-system.php

<?php
/**
 * Plugin Name: WordPress在线考试与题库管理系统
 * Description: 在WordPress中集成完整的在线考试与题库管理功能
 * Version: 1.0.0
 * Author: 你的名字
 */

// 防止直接访问
if (!defined('ABSPATH')) {
    exit;
}

// 定义插件常量
define('EXAM_SYSTEM_VERSION', '1.0.0');
define('EXAM_SYSTEM_PATH', plugin_dir_path(__FILE__));
define('EXAM_SYSTEM_URL', plugin_dir_url(__FILE__));

// 核心类加载
require_once EXAM_SYSTEM_PATH . 'includes/class-database.php';
require_once EXAM_SYSTEM_PATH . 'includes/class-question.php';
require_once EXAM_SYSTEM_PATH . 'includes/class-exam.php';
require_once EXAM_SYSTEM_PATH . 'includes/class-shortcodes.php';
require_once EXAM_SYSTEM_PATH . 'includes/class-admin.php';

// 初始化插件
class Exam_System_Plugin {
    
    private static $instance = null;
    
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    private function __construct() {
        $this->init_hooks();
    }
    
    private function init_hooks() {
        // 激活/停用钩子
        register_activation_hook(__FILE__, array('Exam_Database', 'create_tables'));
        register_deactivation_hook(__FILE__, array($this, 'deactivate'));
        
        // 初始化各模块
        add_action('plugins_loaded', array($this, 'init_modules'));
    }
    
    public function init_modules() {
        Exam_Database::init();
        Exam_Question::init();
        Exam_Manager::init();
        Exam_Shortcodes::init();
        
        if (is_admin()) {
            Exam_Admin::init();
        }
    }
    
    public function deactivate() {
        // 清理临时数据,但不删除考试记录
        flush_rewrite_rules();
    }
}

// 启动插件
Exam_System_Plugin::get_instance();

2.3 数据库表创建

includes/class-database.php中实现数据表的创建与升级:

class Exam_Database {
    
    public static function init() {
        // 数据库版本控制
        add_action('plugins_loaded', array(__CLASS__, 'check_db_version'));
    }
    
    public static function create_tables() {
        global $wpdb;
        $charset_collate = $wpdb->get_charset_collate();
        $table_prefix = $wpdb->prefix . 'exam_';
        
        // 试题表
        $questions_table = $table_prefix . 'questions';
        $sql1 = "CREATE TABLE IF NOT EXISTS $questions_table (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            question_type varchar(50) NOT NULL DEFAULT 'single_choice',
            title text NOT NULL,
            options longtext,
            correct_answer longtext NOT NULL,
            analysis text,
            category_id bigint(20) DEFAULT 0,
            difficulty tinyint(1) DEFAULT 1,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY category_id (category_id),
            KEY difficulty (difficulty)
        ) $charset_collate;";
        
        // 试卷表
        $papers_table = $table_prefix . 'papers';
        $sql2 = "CREATE TABLE IF NOT EXISTS $papers_table (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            paper_name varchar(255) NOT NULL,
            question_ids text,
            settings longtext,
            total_score int(11) DEFAULT 100,
            time_limit int(11) DEFAULT 60,
            status tinyint(1) DEFAULT 1,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id)
        ) $charset_collate;";
        
        // 考试记录表
        $records_table = $table_prefix . 'records';
        $sql3 = "CREATE TABLE IF NOT EXISTS $records_table (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            user_id bigint(20) NOT NULL,
            paper_id bigint(20) NOT NULL,
            start_time datetime,
            end_time datetime,
            score decimal(5,2) DEFAULT 0,
            answers longtext,
            is_passed tinyint(1) DEFAULT 0,
            PRIMARY KEY (id),
            KEY user_id (user_id),
            KEY paper_id (paper_id)
        ) $charset_collate;";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql1);
        dbDelta($sql2);
        dbDelta($sql3);
        
        // 存储数据库版本
        add_option('exam_system_db_version', '1.0.0');
    }
    
    public static function check_db_version() {
        if (get_option('exam_system_db_version') !== '1.0.0') {
            self::create_tables();
        }
    }
}

第三章:核心功能模块开发

3.1 题库管理模块实现

试题管理类 (includes/class-question.php):

class Exam_Question {
    
    private static $question_types = array(
        'single_choice' => '单选题',
        'multi_choice' => '多选题',
        'true_false' => '判断题',
        'fill_blank' => '填空题',
        'short_answer' => '简答题'
    );
    
    public static function init() {
        add_action('admin_menu', array(__CLASS__, 'add_admin_menu'));
        add_action('wp_ajax_save_question', array(__CLASS__, 'ajax_save_question'));
        add_action('wp_ajax_delete_question', array(__CLASS__, 'ajax_delete_question'));
    }
    
    // 添加管理菜单
    public static function add_admin_menu() {
        add_menu_page(
            '题库管理',
            '考试系统',
            'manage_options',
            'exam-system',
            array(__CLASS__, 'render_questions_page'),
            'dashicons-welcome-learn-more',
            30
        );
        
        add_submenu_page(
            'exam-system',
            '试题管理',
            '试题管理',
            'manage_options',
            'exam-questions',
            array(__CLASS__, 'render_questions_page')
        );
    }
    
    // 渲染试题管理页面
    public static function render_questions_page() {
        ?>
        <div class="wrap">
            <h1 class="wp-heading-inline">试题管理</h1>
            <button id="add-new-question" class="page-title-action">添加新试题</button>
            <hr class="wp-header-end">
            
            <!-- 试题列表表格 -->
            <table class="wp-list-table widefat fixed striped">
                <thead>
                    <tr>
                        <th width="5%">ID</th>
                        <th width="40%">试题内容</th>
                        <th width="10%">题型</th>
                        <th width="10%">难度</th>
                        <th width="15%">分类</th>
                        <th width="20%">操作</th>
                    </tr>
                </thead>
                <tbody id="questions-list">
                    <!-- 通过AJAX动态加载 -->
                </tbody>
            </table>
            
            <!-- 添加/编辑试题模态框 -->
            <div id="question-modal" class="modal" style="display:none;">
                <div class="modal-content">
                    <span class="close">&times;</span>
                    <h2>编辑试题</h2>
                    <form id="question-form">
                        <input type="hidden" id="question_id" name="question_id" value="0">
                        
                        <p>
                            <label for="question_type">题型:</label>
                            <select id="question_type" name="question_type">
                                <?php foreach(self::$question_types as $key => $label): ?>
                                <option value="<?php echo esc_attr($key); ?>"><?php echo esc_html($label); ?></option>
                                <?php endforeach; ?>
                            </select>
                        </p>
                        
                        <p>
                            <label for="question_title">题干:</label><br>
                            <textarea id="question_title" name="question_title" rows="3" cols="80" required></textarea>
                        </p>
                        
                        <div id="options-container">
                            <!-- 选项内容动态生成 -->
                        </div>
                        
                        <p>
                            <label for="correct_answer">正确答案:</label><br>
                            <textarea id="correct_answer" name="correct_answer" rows="2" cols="80" required></textarea>
                        </p>
                        
                        <p>
                            <label for="analysis">试题解析:</label><br>
                            <textarea id="analysis" name="analysis" rows="3" cols="80"></textarea>
                        </p>
                        
                        <p>
                            <button type="submit" class="button button-primary">保存试题</button>
                        </p>
                    </form>
                </div>
            </div>
        </div>
        
        <style>
            .modal {
                display: none;
                position: fixed;
                z-index: 1000;
                left: 0;
                top: 0;
                width: 100%;
                height: 100%;
                background-color: rgba(0,0,0,0.4);
            }
            .modal-content {
                background-color: #fefefe;
                margin: 5% auto;
                padding: 20px;
                border: 1px solid #888;
                width: 80%;
                max-width: 800px;
            }
            .close {
                color: #aaa;
                float: right;
                font-size: 28px;
                font-weight: bold;
                cursor: pointer;
            }
        </style>
        
        <script>
        jQuery(document).ready(function($) {
            // 加载试题列表
            function loadQuestions() {
                $.ajax({
                    url: ajaxurl,
                    type: 'POST',
                    data: {
                        action: 'get_questions',
                        nonce: '<?php echo wp_create_nonce('exam_nonce'); ?>'
                    },
                    success: function(response) {
                        $('#questions-list').html(response.data);
                    }
                });
            }
            
            // 打开模态框
            $('#add-new-question').click(function() {
                $('#question-modal').show();
                $('#question-form')[0].reset();
                $('#question_id').val('0');
            });
            
            // 关闭模态框
            $('.close').click(function() {
                $('#question-modal').hide();
            });
            
            // 保存试题
            $('#question-form').submit(function(e) {
                e.preventDefault();
                
                $.ajax({
                    url: ajaxurl,
                    type: 'POST',
                    data: {
                        action: 'save_question',
                        nonce: '<?php echo wp_create_nonce('exam_nonce'); ?>',
                        form_data: $(this).serialize()
                    },
                    success: function(response) {
                        if (response.success) {
                            alert('保存成功!');
                            $('#question-modal').hide();
                            loadQuestions();
                        } else {
                            alert('保存失败:' + response.data);
                        }
                    }
                });
            });
            
            // 初始加载
            loadQuestions();
        });
        </script>
        <?php
    }
    
    // AJAX保存试题
    public static function ajax_save_question() {
        check_ajax_referer('exam_nonce', 'nonce');
        
        global $wpdb;
        $table_name = $wpdb->prefix . 'exam_questions';
        
        parse_str($_POST['form_data'], $form_data);
        
        $data = array(
            'question_type' => sanitize_text_field($form_data['question_type']),
            'title' => wp_kses_post($form_data['question_title']),
            'correct_answer' => sanitize_textarea_field($form_data['correct_answer']),
            'analysis' => sanitize_textarea_field($form_data['analysis']),
            'updated_at' => current_time('mysql')
        );
        
        // 处理选项(针对选择题)
        if (in_array($form_data['question_type'], array('single_choice', 'multi_choice'))) {
            $options = array();
            if (isset($form_data['option'])) {
                foreach ($form_data['option'] as $key => $value) {
                    if (!empty(trim($value))) {
                        $options[$key] = sanitize_text_field($value);
                    }
                }
            }
            $data['options'] = maybe_serialize($options);
        }
        
        $question_id = intval($form_data['question_id']);
        
        if ($question_id > 0) {
            // 更新
            $result = $wpdb->update($table_name, $data, array('id' => $question_id));
        } else {
            // 新增
            $data['created_at'] = current_time('mysql');
            $result = $wpdb->insert($table_name, $data);
            $question_id = $wpdb->insert_id;
        }
        
        if ($result !== false) {
            wp_send_json_success('试题保存成功');
        } else {
            wp_send_json_error('保存失败:' . $wpdb->last_error);
        }
    }
}

3.2 试卷管理与组卷功能

试卷管理类 (includes/class-exam.php):

class Exam_Manager {
    
    public static function init() {
        add_action('admin_menu', array(__CLASS__, 'add_exam_menu'));
        add_action('wp_ajax_generate_paper', array(__CLASS__, 'ajax_generate_paper'));
        add_action('wp_ajax_start_exam', array(__CLASS__, 'ajax_start_exam'));
        add_action('wp_ajax_submit_exam', array(__CLASS__, 'ajax_submit_exam'));
        
        // 注册短代码
        add_shortcode('exam_list', array(__CLASS__, 'shortcode_exam_list'));
        add_shortcode('take_exam', array(__CLASS__, 'shortcode_take_exam'));
        add_shortcode('exam_results', array(__CLASS__, 'shortcode_exam_results'));
    }
    
    // 手动组卷功能
    public static function render_paper_generator() {
        global $wpdb;
        $categories = $wpdb->get_results("SELECT DISTINCT category_id FROM {$wpdb->prefix}exam_questions");
        
        ?>
        <div class="wrap">
            <h1>试卷生成器</h1>
            
            <form id="paper-generator-form">
                <table class="form-table">
                    <tr>
                        <th><label for="paper_name">试卷名称</label></th>

id="paper_name" name="paper_name" class="regular-text" required></td>

                </tr>
                <tr>
                    <th><label>组卷方式</label></th>
                    <td>
                        <label><input type="radio" name="generate_type" value="manual" checked> 手动选题</label>
                        <label><input type="radio" name="generate_type" value="auto"> 自动组卷</label>
                    </td>
                </tr>
                
                <tbody id="manual-selection">
                    <tr>
                        <th><label>选择试题</label></th>
                        <td>
                            <div style="max-height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 10px;">
                                <?php
                                $questions = $wpdb->get_results("SELECT id, title, question_type FROM {$wpdb->prefix}exam_questions ORDER BY id DESC");
                                foreach ($questions as $q) {
                                    $type_labels = Exam_Question::$question_types;
                                    $type = isset($type_labels[$q->question_type]) ? $type_labels[$q->question_type] : $q->question_type;
                                    echo '<label style="display: block; margin-bottom: 5px;">';
                                    echo '<input type="checkbox" name="selected_questions[]" value="' . $q->id . '"> ';
                                    echo wp_trim_words(strip_tags($q->title), 10) . ' <small>(' . $type . ')</small>';
                                    echo '</label>';
                                }
                                ?>
                            </div>
                        </td>
                    </tr>
                </tbody>
                
                <tbody id="auto-selection" style="display:none;">
                    <tr>
                        <th><label>按分类组卷</label></th>
                        <td>
                            <div id="category-rules">
                                <div class="rule-row">
                                    <select name="category_rules[0][category_id]" class="category-select">
                                        <option value="0">所有分类</option>
                                        <?php foreach($categories as $cat): ?>
                                        <option value="<?php echo $cat->category_id; ?>">分类 <?php echo $cat->category_id; ?></option>
                                        <?php endforeach; ?>
                                    </select>
                                    <select name="category_rules[0][question_type]">
                                        <option value="all">所有题型</option>
                                        <?php foreach(Exam_Question::$question_types as $key => $label): ?>
                                        <option value="<?php echo $key; ?>"><?php echo $label; ?></option>
                                        <?php endforeach; ?>
                                    </select>
                                    <input type="number" name="category_rules[0][count]" min="1" max="50" value="5" style="width: 60px;"> 题
                                    <select name="category_rules[0][difficulty]">
                                        <option value="0">所有难度</option>
                                        <option value="1">简单</option>
                                        <option value="2">中等</option>
                                        <option value="3">困难</option>
                                    </select>
                                    <button type="button" class="button remove-rule">删除</button>
                                </div>
                            </div>
                            <button type="button" id="add-rule" class="button">添加规则</button>
                        </td>
                    </tr>
                </tbody>
                
                <tr>
                    <th><label for="time_limit">考试时间(分钟)</label></th>
                    <td><input type="number" id="time_limit" name="time_limit" min="1" max="300" value="60"></td>
                </tr>
                <tr>
                    <th><label for="passing_score">及格分数</label></th>
                    <td><input type="number" id="passing_score" name="passing_score" min="0" max="100" value="60"></td>
                </tr>
                <tr>
                    <th><label for="attempts_allowed">考试次数限制</label></th>
                    <td><input type="number" id="attempts_allowed" name="attempts_allowed" min="0" value="0"> (0表示无限制)</td>
                </tr>
            </table>
            
            <p class="submit">
                <button type="submit" class="button button-primary">生成试卷</button>
            </p>
        </form>
        
        <div id="generated-paper" style="display:none; margin-top: 30px; padding: 20px; background: #f5f5f5; border: 1px solid #ddd;">
            <h3>试卷生成成功!</h3>
            <div id="paper-preview"></div>
            <p>
                <button id="save-paper" class="button button-primary">保存试卷</button>
                <button id="regenerate-paper" class="button">重新生成</button>
            </p>
        </div>
    </div>
    
    <script>
    jQuery(document).ready(function($) {
        // 切换组卷方式
        $('input[name="generate_type"]').change(function() {
            if ($(this).val() === 'manual') {
                $('#manual-selection').show();
                $('#auto-selection').hide();
            } else {
                $('#manual-selection').hide();
                $('#auto-selection').show();
            }
        });
        
        // 添加规则行
        var ruleIndex = 1;
        $('#add-rule').click(function() {
            var newRow = $('.rule-row:first').clone();
            newRow.find('select, input').each(function() {
                var name = $(this).attr('name');
                $(this).attr('name', name.replace('[0]', '[' + ruleIndex + ']'));
            });
            newRow.appendTo('#category-rules');
            ruleIndex++;
        });
        
        // 删除规则行
        $(document).on('click', '.remove-rule', function() {
            if ($('.rule-row').length > 1) {
                $(this).closest('.rule-row').remove();
            }
        });
        
        // 提交生成试卷
        $('#paper-generator-form').submit(function(e) {
            e.preventDefault();
            
            $.ajax({
                url: ajaxurl,
                type: 'POST',
                data: {
                    action: 'generate_paper',
                    nonce: '<?php echo wp_create_nonce("exam_nonce"); ?>',
                    form_data: $(this).serialize()
                },
                success: function(response) {
                    if (response.success) {
                        $('#paper-preview').html(response.data.preview);
                        $('#generated-paper').show().data('paper-data', response.data.paper_data);
                        $('html, body').animate({
                            scrollTop: $('#generated-paper').offset().top
                        }, 500);
                    } else {
                        alert('生成失败:' + response.data);
                    }
                }
            });
        });
        
        // 保存试卷
        $('#save-paper').click(function() {
            var paperData = $('#generated-paper').data('paper-data');
            paperData.paper_name = $('#paper_name').val();
            paperData.time_limit = $('#time_limit').val();
            paperData.passing_score = $('#passing_score').val();
            paperData.attempts_allowed = $('#attempts_allowed').val();
            
            $.ajax({
                url: ajaxurl,
                type: 'POST',
                data: {
                    action: 'save_paper',
                    nonce: '<?php echo wp_create_nonce("exam_nonce"); ?>',
                    paper_data: paperData
                },
                success: function(response) {
                    if (response.success) {
                        alert('试卷保存成功!');
                        window.location.href = '<?php echo admin_url("admin.php?page=exam-papers"); ?>';
                    } else {
                        alert('保存失败:' + response.data);
                    }
                }
            });
        });
    });
    </script>
    <?php
}

// AJAX生成试卷
public static function ajax_generate_paper() {
    check_ajax_referer('exam_nonce', 'nonce');
    
    global $wpdb;
    parse_str($_POST['form_data'], $form_data);
    
    $question_ids = array();
    
    if ($form_data['generate_type'] === 'manual') {
        // 手动选题
        if (!empty($form_data['selected_questions'])) {
            $question_ids = array_map('intval', $form_data['selected_questions']);
        }
    } else {
        // 自动组卷
        foreach ($form_data['category_rules'] as $rule) {
            $where = array('1=1');
            $params = array();
            
            if (!empty($rule['category_id']) && $rule['category_id'] != '0') {
                $where[] = 'category_id = %d';
                $params[] = intval($rule['category_id']);
            }
            
            if (!empty($rule['question_type']) && $rule['question_type'] != 'all') {
                $where[] = 'question_type = %s';
                $params[] = sanitize_text_field($rule['question_type']);
            }
            
            if (!empty($rule['difficulty']) && $rule['difficulty'] != '0') {
                $where[] = 'difficulty = %d';
                $params[] = intval($rule['difficulty']);
            }
            
            $sql = "SELECT id FROM {$wpdb->prefix}exam_questions WHERE " . implode(' AND ', $where) . " ORDER BY RAND() LIMIT %d";
            $params[] = intval($rule['count']);
            
            if (!empty($params)) {
                $results = $wpdb->get_col($wpdb->prepare($sql, $params));
                $question_ids = array_merge($question_ids, $results);
            }
        }
    }
    
    if (empty($question_ids)) {
        wp_send_json_error('没有选择任何试题');
    }
    
    // 生成预览
    $preview = '<h4>试卷包含 ' . count($question_ids) . ' 道试题:</h4><ol>';
    foreach ($question_ids as $qid) {
        $question = $wpdb->get_row($wpdb->prepare("SELECT title, question_type FROM {$wpdb->prefix}exam_questions WHERE id = %d", $qid));
        if ($question) {
            $type_labels = Exam_Question::$question_types;
            $type = isset($type_labels[$question->question_type]) ? $type_labels[$question->question_type] : $question->question_type;
            $preview .= '<li>' . wp_trim_words(strip_tags($question->title), 15) . ' <em>(' . $type . ')</em></li>';
        }
    }
    $preview .= '</ol>';
    
    wp_send_json_success(array(
        'preview' => $preview,
        'paper_data' => array(
            'question_ids' => $question_ids,
            'total_questions' => count($question_ids)
        )
    ));
}

}

第四章:前端考试界面与交互实现

4.1 考试短代码与页面模板

短代码类 (includes/class-shortcodes.php):

class Exam_Shortcodes {
    
    public static function init() {
        // 短代码已在Exam_Manager中注册
        add_action('wp_enqueue_scripts', array(__CLASS__, 'enqueue_frontend_scripts'));
    }
    
    public static function enqueue_frontend_scripts() {
        wp_enqueue_style('exam-frontend', EXAM_SYSTEM_URL . 'assets/css/frontend.css', array(), EXAM_SYSTEM_VERSION);
        wp_enqueue_script('exam-frontend', EXAM_SYSTEM_URL . 'assets/js/frontend.js', array('jquery'), EXAM_SYSTEM_VERSION, true);
        
        wp_localize_script('exam-frontend', 'exam_ajax', array(
            'ajax_url' => admin_url('admin-ajax.php'),
            'nonce' => wp_create_nonce('exam_frontend_nonce')
        ));
    }
    
    // 考试列表短代码
    public static function shortcode_exam_list($atts) {
        if (!is_user_logged_in()) {
            return '<div class="exam-notice">请先登录后参加考试</div>';
        }
        
        global $wpdb;
        $papers = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}exam_papers WHERE status = 1 ORDER BY id DESC");
        
        $output = '<div class="exam-list-container">';
        $output .= '<h2>可用考试</h2>';
        
        if (empty($papers)) {
            $output .= '<p>暂无可用考试</p>';
        } else {
            $output .= '<div class="exam-grid">';
            foreach ($papers as $paper) {
                $settings = maybe_unserialize($paper->settings);
                $attempts_allowed = isset($settings['attempts_allowed']) ? $settings['attempts_allowed'] : 0;
                
                // 检查已考次数
                $attempts = $wpdb->get_var($wpdb->prepare(
                    "SELECT COUNT(*) FROM {$wpdb->prefix}exam_records WHERE user_id = %d AND paper_id = %d",
                    get_current_user_id(),
                    $paper->id
                ));
                
                $can_take = ($attempts_allowed == 0 || $attempts < $attempts_allowed);
                
                $output .= '<div class="exam-card">';
                $output .= '<h3>' . esc_html($paper->paper_name) . '</h3>';
                $output .= '<ul class="exam-meta">';
                $output .= '<li>题数: ' . $paper->total_questions . '</li>';
                $output .= '<li>时间: ' . $paper->time_limit . '分钟</li>';
                $output .= '<li>及格分: ' . $paper->passing_score . '</li>';
                $output .= '<li>已考次数: ' . $attempts . ($attempts_allowed > 0 ? '/' . $attempts_allowed : '') . '</li>';
                $output .= '</ul>';
                
                if ($can_take) {
                    $output .= '<a href="?exam_id=' . $paper->id . '" class="button start-exam-btn">开始考试</a>';
                } else {
                    $output .= '<button class="button disabled" disabled>考试次数已用完</button>';
                }
                
                // 查看历史成绩
                $best_score = $wpdb->get_var($wpdb->prepare(
                    "SELECT MAX(score) FROM {$wpdb->prefix}exam_records WHERE user_id = %d AND paper_id = %d",
                    get_current_user_id(),
                    $paper->id
                ));
                
                if ($best_score !== null) {
                    $output .= '<div class="best-score">最佳成绩: ' . floatval($best_score) . '分</div>';
                }
                
                $output .= '</div>';
            }
            $output .= '</div>';
        }
        
        $output .= '</div>';
        
        return $output;
    }
    
    // 参加考试短代码
    public static function shortcode_take_exam($atts) {
        if (!is_user_logged_in()) {
            return '<div class="exam-notice">请先登录后参加考试</div>';
        }
        
        if (!isset($_GET['exam_id'])) {
            return '<div class="exam-notice">请选择要参加的考试</div>';
        }
        
        $paper_id = intval($_GET['exam_id']);
        $user_id = get_current_user_id();
        
        global $wpdb;
        
        // 检查考试次数限制
        $paper = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}exam_papers WHERE id = %d AND status = 1",
            $paper_id
        ));
        
        if (!$paper) {
            return '<div class="exam-error">考试不存在或已关闭</div>';
        }
        
        $settings = maybe_unserialize($paper->settings);
        $attempts_allowed = isset($settings['attempts_allowed']) ? $settings['attempts_allowed'] : 0;
        
        $attempts = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->prefix}exam_records WHERE user_id = %d AND paper_id = %d",
            $user_id,
            $paper_id
        ));
        
        if ($attempts_allowed > 0 && $attempts >= $attempts_allowed) {
            return '<div class="exam-error">您已达到该考试的最大尝试次数</div>';
        }
        
        // 检查是否有未完成的考试
        $unfinished = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}exam_records WHERE user_id = %d AND paper_id = %d AND end_time IS NULL",
            $user_id,
            $paper_id
        ));
        
        if ($unfinished) {
            // 恢复未完成的考试
            $record_id = $unfinished->id;
            $remaining_time = $paper->time_limit * 60 - (time() - strtotime($unfinished->start_time));
            $remaining_time = max(0, $remaining_time);
        } else {
            // 创建新的考试记录
            $wpdb->insert(
                $wpdb->prefix . 'exam_records',
                array(
                    'user_id' => $user_id,
                    'paper_id' => $paper_id,
                    'start_time' => current_time('mysql'),
                    'answers' => maybe_serialize(array())
                )
            );
            $record_id = $wpdb->insert_id;
            $remaining_time = $paper->time_limit * 60;
        }
        
        // 获取试题
        $question_ids = maybe_unserialize($paper->question_ids);
        if (empty($question_ids)) {
            return '<div class="exam-error">试卷试题配置错误</div>';
        }
        
        $questions = array();
        foreach ($question_ids as $qid) {
            $question = $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$wpdb->prefix}exam_questions WHERE id = %d",
                $qid
            ));
            if ($question) {
                $question->options = maybe_unserialize($question->options);
                $questions[] = $question;
            }
        }
        
        // 输出考试界面
本文来自网络,不代表柔性供应链服务中心立场,转载请注明出处:https://mall.org.cn/5126.html

EXCHANGES®作者

上一篇
下一篇

为您推荐

发表回复

联系我们

联系我们

18559313275

在线咨询: QQ交谈

邮箱: vip@exchanges.center

工作时间:周一至周五,9:00-17:30,节假日休息
返回顶部