文章目录
-
- WordPress作为全球最流行的内容管理系统,早已超越了简单的博客平台范畴。凭借其强大的插件架构、丰富的API接口和庞大的开发者社区,WordPress已经演变为一个功能完善的企业级应用开发平台。在许多人的印象中,WordPress可能仍是一个"博客工具",但实际上,全球超过43%的网站都运行在WordPress上,其中不乏复杂的企业级应用。 本教程将深入探讨如何通过WordPress的代码二次开发,实现一个功能完整的在线考试测评与自动组卷评分系统。这个系统不仅能够满足教育机构、企业培训部门的在线测评需求,还能展示WordPress作为应用开发平台的强大扩展能力。我们将从系统设计、数据库结构、核心功能实现到前端展示,全方位解析开发过程。
-
- 在线考试测评系统需要满足以下核心功能: 试题库管理(支持多种题型:单选、多选、判断、填空、简答等) 智能组卷策略(随机组卷、按知识点组卷、难度系数组卷) 在线考试与计时功能 自动评分与人工阅卷结合 成绩统计与分析报表 用户权限与考试管理
- 选择WordPress作为开发基础有以下几个显著优势: 用户管理系统完善:WordPress自带的用户角色和权限管理系统可以快速扩展为考试系统的用户体系 主题模板机制:利用WordPress主题系统可以快速构建一致的前端界面 插件架构:通过自定义插件的方式实现功能模块,便于维护和升级 丰富的API:REST API、AJAX、短代码等机制便于前后端交互 安全机制成熟:WordPress经过多年发展,拥有完善的安全防护机制 SEO友好:天生具备良好的搜索引擎优化基础
- 后端:PHP 7.4+,WordPress 5.6+,MySQL 5.6+ 前端:HTML5,CSS3,JavaScript (ES6+),jQuery(兼容性考虑) 关键WordPress特性:自定义文章类型(CPT)、自定义分类法、元数据、短代码、REST API 辅助工具:Chart.js(数据可视化),Select2(下拉框增强),Bootstrap 5(响应式布局)
-
- 虽然WordPress本身使用wp_posts和wp_postmeta作为主要数据存储结构,但对于复杂的考试系统,我们需要创建专门的数据表来保证性能和数据一致性。 -- 试题表扩展(基于wp_posts的自定义文章类型) -- 使用post_type = 'exam_question'标识试题 -- 自定义试题元数据表 CREATE TABLE wp_exam_question_meta ( meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT, question_id bigint(20) unsigned NOT NULL, meta_key varchar(255) DEFAULT NULL, meta_value longtext, PRIMARY KEY (meta_id), KEY question_id (question_id), KEY meta_key (meta_key(191)) ); -- 试卷表 CREATE TABLE wp_exam_papers ( paper_id bigint(20) unsigned NOT NULL AUTO_INCREMENT, paper_title varchar(255) NOT NULL, paper_description text, paper_type varchar(50) DEFAULT 'random', -- random/fixed/manual total_score int(11) DEFAULT 100, time_limit int(11) DEFAULT 60, -- 分钟 created_by bigint(20) unsigned NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (paper_id) ); -- 试卷-试题关联表 CREATE TABLE wp_exam_paper_questions ( relation_id bigint(20) unsigned NOT NULL AUTO_INCREMENT, paper_id bigint(20) unsigned NOT NULL, question_id bigint(20) unsigned NOT NULL, question_order int(11) DEFAULT 0, question_score decimal(5,2) DEFAULT 0, PRIMARY KEY (relation_id), KEY paper_id (paper_id), KEY question_id (question_id) ); -- 考试记录表 CREATE TABLE wp_exam_records ( record_id bigint(20) unsigned NOT NULL AUTO_INCREMENT, user_id bigint(20) unsigned NOT NULL, paper_id bigint(20) unsigned NOT NULL, start_time datetime DEFAULT NULL, end_time datetime DEFAULT NULL, submit_time datetime DEFAULT NULL, total_score decimal(5,2) DEFAULT 0, auto_score decimal(5,2) DEFAULT 0, manual_score decimal(5,2) DEFAULT 0, status varchar(20) DEFAULT 'in_progress', -- in_progress/completed/reviewing ip_address varchar(45) DEFAULT NULL, user_answers longtext, -- 存储用户答案的JSON PRIMARY KEY (record_id), KEY user_id (user_id), KEY paper_id (paper_id), KEY status (status) );
- 试题作为系统的核心数据,我们可以使用WordPress的自定义文章类型(CPT)来管理: // 注册试题自定义文章类型 function register_exam_question_post_type() { $labels = array( 'name' => '试题', 'singular_name' => '试题', 'menu_name' => '试题库', 'add_new' => '添加试题', 'add_new_item' => '添加新试题', 'edit_item' => '编辑试题', 'new_item' => '新试题', 'view_item' => '查看试题', 'search_items' => '搜索试题', 'not_found' => '未找到试题', 'not_found_in_trash' => '回收站中无试题' ); $args = array( 'labels' => $labels, 'public' => false, 'publicly_queryable' => false, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => false, 'capability_type' => 'post', 'has_archive' => false, 'hierarchical' => false, 'menu_position' => 30, 'menu_icon' => 'dashicons-welcome-learn-more', 'supports' => array('title', 'editor', 'author'), 'show_in_rest' => true, // 启用REST API支持 ); register_post_type('exam_question', $args); } add_action('init', 'register_exam_question_post_type');
- 利用WordPress的自定义分类法为试题添加分类和标签: // 注册试题分类法 function register_exam_taxonomies() { // 试题分类(如:数学、语文、英语) register_taxonomy( 'question_category', 'exam_question', array( 'labels' => array( 'name' => '试题分类', 'singular_name' => '试题分类', 'search_items' => '搜索分类', 'all_items' => '全部分类', 'edit_item' => '编辑分类', 'update_item' => '更新分类', 'add_new_item' => '添加新分类', 'new_item_name' => '新分类名称', 'menu_name' => '试题分类' ), 'hierarchical' => true, 'show_ui' => true, 'show_admin_column' => true, 'query_var' => true, 'rewrite' => false, 'show_in_rest' => true, ) ); // 试题难度标签 register_taxonomy( 'question_difficulty', 'exam_question', array( 'labels' => array( 'name' => '难度级别', 'singular_name' => '难度级别', 'search_items' => '搜索难度', 'all_items' => '全部难度', 'edit_item' => '编辑难度', 'update_item' => '更新难度', 'add_new_item' => '添加新难度', 'new_item_name' => '新难度名称', 'menu_name' => '难度级别' ), 'hierarchical' => false, 'show_ui' => true, 'show_admin_column' => true, 'query_var' => true, 'rewrite' => false, 'show_in_rest' => true, ) ); } add_action('init', 'register_exam_taxonomies');
-
- 在WordPress后台创建试题管理界面,支持多种题型: // 为试题添加元数据框 function add_exam_question_meta_boxes() { add_meta_box( 'question-type-meta', '试题类型与选项', 'render_question_type_meta_box', 'exam_question', 'normal', 'high' ); add_meta_box( 'question-answer-meta', '正确答案与解析', 'render_question_answer_meta_box', 'exam_question', 'normal', 'high' ); } add_action('add_meta_boxes', 'add_exam_question_meta_boxes'); // 渲染试题类型元数据框 function render_question_type_meta_box($post) { wp_nonce_field('save_question_meta', 'question_meta_nonce'); $question_type = get_post_meta($post->ID, '_question_type', true); $question_options = get_post_meta($post->ID, '_question_options', true); // 解析选项(如果是选择题) $options = !empty($question_options) ? json_decode($question_options, true) : array(); ?> <div class="question-meta-container"> <div class="form-group"> <label for="question_type">试题类型:</label> <select name="question_type" id="question_type" class="widefat"> <option value="single_choice" <?php selected($question_type, 'single_choice'); ?>>单选题</option> <option value="multiple_choice" <?php selected($question_type, 'multiple_choice'); ?>>多选题</option> <option value="true_false" <?php selected($question_type, 'true_false'); ?>>判断题</option> <option value="fill_blank" <?php selected($question_type, 'fill_blank'); ?>>填空题</option> <option value="short_answer" <?php selected($question_type, 'short_answer'); ?>>简答题</option> <option value="essay" <?php selected($question_type, 'essay'); ?>>论述题</option> </select> </div> <div id="choice-options-container" style="display: <?php echo in_array($question_type, array('single_choice', 'multiple_choice')) ? 'block' : 'none'; ?>;"> <h4>选项设置</h4> <div id="choice-options-list"> <?php if (!empty($options)): ?> <?php foreach ($options as $key => $option): ?> <div class="option-item"> <input type="text" name="question_options[]" value="<?php echo esc_attr($option); ?>" class="widefat" placeholder="选项内容"> <button type="button" class="button remove-option">删除</button> </div> <?php endforeach; ?> <?php else: ?> <div class="option-item"> <input type="text" name="question_options[]" value="" class="widefat" placeholder="选项内容"> <button type="button" class="button remove-option">删除</button> </div> <div class="option-item"> <input type="text" name="question_options[]" value="" class="widefat" placeholder="选项内容"> <button type="button" class="button remove-option">删除</button> </div> <?php endif; ?> </div> <button type="button" id="add-option" class="button">添加选项</button> </div> <div class="form-group"> <label for="question_score">默认分值:</label> <input type="number" name="question_score" id="question_score" value="<?php echo esc_attr(get_post_meta($post->ID, '_question_score', true) ?: 5); ?>" min="0" step="0.5" class="small-text"> 分 </div> </div> <script> jQuery(document).ready(function($) { // 切换试题类型时显示/隐藏选项设置 $('#question_type').change(function() { var type = $(this).val(); if (type === 'single_choice' || type === 'multiple_choice') { $('#choice-options-container').show(); } else { $('#choice-options-container').hide(); } }); // 添加选项 $('#add-option').click(function() { var newOption = '<div class="option-item">' + '<input type="text" name="question_options[]" value="" class="widefat" placeholder="选项内容">' + '<button type="button" class="button remove-option">删除</button>' + '</div>'; $('#choice-options-list').append(newOption); }); // 删除选项 $(document).on('click', '.remove-option', function() { if ($('#choice-options-list .option-item').length > 1) { $(this).closest('.option-item').remove(); } else { alert('至少需要保留一个选项'); } }); }); </script> <?php }
- 智能组卷是考试系统的核心功能,这里实现一个基于分类和难度的随机组卷算法: class ExamPaperGenerator { /** * 生成随机试卷 * * @param array $params 组卷参数 * @return int|false 试卷ID或false */ public static function generateRandomPaper($params) { global $wpdb; $defaults = array( 'title' => '随机试卷', 'description' => '', 'total_questions' => 50, 'categories' => array(), 'difficulties' => array('easy', 'medium', 'hard'), 'difficulty_distribution' => array('easy' => 0.3, 'medium' => 0.5, 'hard' => 0.2), 'question_types' => array('single_choice', 'multiple_choice', 'true_false'), 'time_limit' => 60, 'created_by' => get_current_user_id(), ); $params = wp_parse_args($params, $defaults); // 开始事务 $wpdb->query('START TRANSACTION'); try { // 创建试卷记录 $paper_data = array( 'paper_title' => sanitize_text_field($params['title']), 'paper_description' => sanitize_textarea_field($params['description']), 'paper_type' => 'random', 'total_score' => $params['total_questions'] * 2, // 假设每题2分 'time_limit' => intval($params['time_limit']), 'created_by' => intval($params['created_by']), 'created_at' => current_time('mysql'), ); $wpdb->insert($wpdb->prefix . 'exam_papers', $paper_data); $paper_id = $wpdb->insert_id; if (!$paper_id) { throw new Exception('创建试卷失败'); } // 获取符合条件的试题 $questions = self::getQuestionsByCriteria($params); if (count($questions) < $params['total_questions']) { throw new Exception('试题数量不足,无法生成试卷'); } // 随机选择试题 shuffle($questions); $selected_questions = array_slice($questions, 0, $params['total_questions']); // 关联试题到试卷 $order = 1; foreach ($selected_questions as $question) { $relation_data = array( 'paper_id' => $paper_id, 'question_id' => $question->ID, 'question_order' => $order, 'question_score' => get_post_meta($question->ID, '_question_score', true) ?: 2, ); $wpdb->insert($wpdb->prefix . 'exam_paper_questions', $relation_data); $order++; } // 提交事务 $wpdb->query('COMMIT'); return $paper_id; } catch (Exception $e) { // 回滚事务 $wpdb->query('ROLLBACK'); error_log('生成试卷失败: ' . $e->getMessage()); return false; } } /** * 根据条件获取试题 */ private static function getQuestionsByCriteria($params) { $args = array( 'post_type' => 'exam_question', 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', ); // 添加分类筛选 if (!empty($params['categories'])) { $args['tax_query'][] = array( 'taxonomy' => 'question_category', 'field' => 'term_id', 'terms' => $params['categories'], 'operator' => 'IN', ); } // 添加难度筛选 if (!empty($params['difficulties'])) { $args['tax_query'][] = array( 'taxonomy' => 'question_difficulty', 'field' => 'slug', 'terms' => $params['difficulties'], 'operator' => 'IN', ); } // 添加题型筛选 if (!empty($params['question_types'])) { $args['meta_query'][] = array( 'key' => '_question_type', 'value' => $params['question_types'], 'compare' => 'IN', ); } // 确保tax_query关系正确 if (isset($args['tax_query']) && count($args['tax_query']) > 1) { $args['tax_query']['relation'] = 'AND'; } $query = new WP_Query($args); return $query->posts; } /** * 按知识点比例组卷 */ public static function generatePaperByKnowledgePoints($params) { // 实现按知识点比例分配试题的逻辑 // 这里可以扩展为更复杂的组卷策略 } } ### 3.3 试卷管理后台界面 创建试卷管理后台界面,支持手动组卷和自动组卷: // 添加试卷管理菜单function add_exam_paper_admin_menu() { add_menu_page( '试卷管理', '考试系统', 'manage_options', 'exam-system', 'render_exam_dashboard', 'dashicons-clipboard', 30 ); add_submenu_page( 'exam-system', '试卷管理', '试卷管理', 'manage_options', 'exam-papers', 'render_exam_papers_page' ); add_submenu_page( 'exam-system', '组卷工具', '智能组卷', 'manage_options', 'exam-generator', 'render_paper_generator_page' ); add_submenu_page( 'exam-system', '考试记录', '考试记录', 'manage_options', 'exam-records', 'render_exam_records_page' ); add_submenu_page( 'exam-system', '成绩统计', '成绩统计', 'manage_options', 'exam-statistics', 'render_exam_statistics_page' ); }add_action('admin_menu', 'add_exam_paper_admin_menu'); // 渲染智能组卷页面function render_paper_generator_page() { // 获取所有试题分类 $categories = get_terms(array( 'taxonomy' => 'question_category', 'hide_empty' => false, )); // 获取所有难度级别 $difficulties = get_terms(array( 'taxonomy' => 'question_difficulty', 'hide_empty' => false, )); ?> <div class="wrap"> <h1>智能组卷工具</h1> <div class="card"> <h2>随机组卷</h2> <form id="random-paper-form" method="post"> <?php wp_nonce_field('generate_random_paper', 'paper_nonce'); ?> <table class="form-table"> <tr> <th scope="row"><label for="paper_title">试卷标题</label></th> <td> <input type="text" name="paper_title" id="paper_title" class="regular-text" value="随机试卷 <?php echo date('Y-m-d'); ?>" required> </td> </tr> <tr> <th scope="row"><label for="paper_description">试卷描述</label></th> <td> <textarea name="paper_description" id="paper_description" class="large-text" rows="3"></textarea> </td> </tr> <tr> <th scope="row"><label for="total_questions">试题数量</label></th> <td> <input type="number" name="total_questions" id="total_questions" min="10" max="200" value="50" required> </td> </tr> <tr> <th scope="row"><label>试题分类</label></th> <td> <div style="max-height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 10px;"> <?php foreach ($categories as $category): ?> <label style="display: block; margin-bottom: 5px;"> <input type="checkbox" name="categories[]" value="<?php echo $category->term_id; ?>"> <?php echo $category->name; ?> (<?php echo $category->count; ?>) </label> <?php endforeach; ?> </div> <p class="description">不选择任何分类表示从所有分类中选题</p> </td> </tr> <tr> <th scope="row"><label>难度分布</label></th> <td> <div id="difficulty-distribution"> <?php foreach ($difficulties as $difficulty): ?> <div style="margin-bottom: 10px;"> <label style="display: inline-block; width: 100px;"> <?php echo $difficulty->name; ?>: </label> <input type="number" name="difficulty_<?php echo $difficulty->slug; ?>" min="0" max="100" value="30" style="width: 60px;"> % </div> <?php endforeach; ?> </div> </td> </tr> <tr> <th scope="row"><label>试题类型</label></th> <td> <label style="margin-right: 15px;"> <input type="checkbox" name="question_types[]" value="single_choice" checked> 单选题 </label> <label style="margin-right: 15px;"> <input type="checkbox" name="question_types[]" value="multiple_choice" checked> 多选题 </label> <label style="margin-right: 15px;"> <input type="checkbox" name="question_types[]" value="true_false"> 判断题 </label> <label style="margin-right: 15px;"> <input type="checkbox" name="question_types[]" value="fill_blank"> 填空题 </label> </td> </tr> <tr> <th scope="row"><label for="time_limit">考试时间(分钟)</label></th> <td> <input type="number" name="time_limit" id="time_limit" min="10" max="300" value="60" required> </td> </tr> </table> <p class="submit"> <button type="submit" name="generate_paper" class="button button-primary">生成试卷</button> </p> </form> </div> <?php // 处理表单提交 if (isset($_POST['generate_paper']) && check_admin_referer('generate_random_paper', 'paper_nonce')) { $params = array( 'title' => sanitize_text_field($_POST['paper_title']), 'description' => sanitize_textarea_field($_POST['paper_description']), 'total_questions' => intval($_POST['total_questions']), 'categories' => isset($_POST['categories']) ? array_map('intval', $_POST['categories']) : array(), 'time_limit' => intval($_POST['time_limit']), 'question_types' => isset($_POST['question_types']) ? array_map('sanitize_text_field', $_POST['question_types']) : array(), ); $paper_id = ExamPaperGenerator::generateRandomPaper($params); if ($paper_id) { echo '<div class="notice notice-success"><p>试卷生成成功!<a href="' . admin_url('admin.php?page=exam-papers&action=edit&paper_id=' . $paper_id) . '">查看试卷</a></p></div>'; } else { echo '<div class="notice notice-error"><p>试卷生成失败,请检查试题库是否足够</p></div>'; } } ?> </div> <script> jQuery(document).ready(function($) { // 验证难度分布总和为100% $('#random-paper-form').submit(function(e) { var total = 0; $('#difficulty-distribution input[type="number"]').each(function() { total += parseInt($(this).val()) || 0; }); if (total !== 100) { alert('难度分布总和必须为100%,当前总和为:' + total + '%'); e.preventDefault(); return false; } }); }); </script> <?php } ## 第四章:在线考试功能实现 ### 4.1 考试前端界面与交互 创建考试前端界面,使用AJAX实现无刷新答题: // 创建考试短代码function exam_paper_shortcode($atts) { $atts = shortcode_atts(array( 'id' => 0, 'preview' => false, ), $atts, 'exam_paper'); $paper_id = intval($atts['id']); if (!$paper_id) { return '<div class="exam-error">请指定试卷ID</div>'; } // 检查用户权限 if (!is_user_logged_in()) { return '<div class="exam-error">请先登录后再参加考试</div>'; } // 获取试卷信息 global $wpdb; $paper = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}exam_papers WHERE paper_id = %d", $paper_id )); if (!$paper) { return '<div class="exam-error">试卷不存在</div>'; } // 检查是否已有进行中的考试 $user_id = get_current_user_id(); $existing_record = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}exam_records WHERE user_id = %d AND paper_id = %d AND status = 'in_progress'", $user_id, $paper_id )); // 如果是预览模式或管理员 $is_preview = $atts['preview'] || current_user_can('manage_options'); if (!$existing_record && !$is_preview) { // 创建新的考试记录 $record_data = array( 'user_id' => $user_id, 'paper_id' => $paper_id, 'start_time' => current_time('mysql'), 'status' => 'in_progress', 'ip_address' => $_SERVER['REMOTE_ADDR'], ); $wpdb->insert($wpdb->prefix . 'exam_records', $record_data); $record_id = $wpdb->insert_id; } else { $record_id = $existing_record ? $existing_record->record_id : 0; } // 获取试卷试题 $questions = $wpdb->get_results($wpdb->prepare( "SELECT q.*, pq.question_order, pq.question_score FROM {$wpdb->prefix}exam_paper_questions pq JOIN {$wpdb->prefix}posts q ON pq.question_id = q.ID WHERE pq.paper_id = %d AND q.post_status = 'publish' ORDER BY pq.question_order ASC", $paper_id )); if (empty($questions)) { return '<div class="exam-error">试卷中没有试题</div>'; } // 获取用户已有的答案(如果是继续考试) $user_answers = array(); if ($record_id && $existing_record && !empty($existing_record->user_answers)) { $user_answers = json_decode($existing_record->user_answers, true); } // 生成考试界面HTML ob_start(); ?> <div class="exam-container" data-paper-id="<?php echo $paper_id; ?>" data-record-id="<?php echo $record_id; ?>" data-time-limit="<?php echo $paper->time_limit; ?>" data-is-preview="<?php echo $is_preview ? '1' : '0'; ?>"> <div class="exam-header"> <h1 class="exam-title"><?php echo esc_html($paper->paper_title); ?></h1> <div class="exam-info"> <div class="exam-timer"> <span class="timer-label">剩余时间:</span> <span class="timer-display"><?php echo $paper->time_limit; ?>:00</span> </div> <div class="exam-progress"> <span class="progress-label">答题进度:</span> <span class="progress-display">0/<?php echo count($questions); ?></span> </div> </div> </div> <div class="exam-navigation"> <div class="question-nav"> <?php foreach ($questions as $index => $question): ?> <button type="button" class="nav-btn" data-question-index="<?php echo $index; ?>"> <?php echo $index + 1; ?> </button> <?php endforeach; ?> </div> <div class="nav-controls"> <button type="button" id="prev-question" class="button">上一题</button> <button type="button" id="next-question" class="button">下一题</button> <button type="button" id="submit-exam" class="button button-primary">提交试卷</button> </div> </div> <div class="exam-questions"> <?php foreach ($questions as $index => $question): $question_type = get_post_meta($question->ID, '_question_type', true); $question_options = get_post_meta($question->ID, '_question_options', true); $options = !empty($question_options) ? json_decode($question_options, true) : array(); $user_answer = isset($user_answers[$question->ID]) ? $user_answers[$question->ID] : ''; ?> <div class="question-item" data-question-id="<?php echo $question->ID; ?>" data-question-index="<?php echo $index; ?>" style="<?php echo $index > 0 ? 'display:none;' : ''; ?>"> <div class="question-header"> <h3 class="question-title"> 第<?php echo $index + 1; ?>题 <span class="question-score">(<?php echo $question->question_score; ?>分)</span> </h3> <div class="question-type"><?php echo get_question_type_name($question_type); ?></div> </div> <div class="question-content"> <?php echo apply_filters('the_content', $question->post_content); ?> </div> <div class="question-answer"> <?php switch ($question_type): case 'single_choice': ?> <div class="choice-options"> <?php foreach ($options as $key => $option): ?> <label class="choice-option"> <input type="radio" name="answer_<?php echo $question->ID; ?>" value="<?php echo chr(65 + $key); ?>" <?php checked($user_answer, chr(65 + $key)); ?>> <span class="option-label"><?php echo chr(65 + $key); ?>.</span> <span class="option-text"><?php echo esc_html($option); ?></span> </label> <?php endforeach; ?> </div> <?php break; case 'multiple_choice': ?> <div class="choice-options"> <?php foreach ($options as $key => $option): $user_answers_array = !empty($user_answer) ? explode(',', $user_answer) : array(); ?> <label class="choice-option"> <input type="checkbox" name="answer_<?php echo $question->ID; ?>[]" value="<?php echo chr(65 + $key); ?>" <?php checked(in_array(chr(65 + $key), $user_answers_array)); ?>> <span class="option-label"><?php echo chr(65 + $key); ?>.</span> <span class="option-text"><?php echo esc_html($option); ?></span> </label> <?php endforeach; ?> </div> <?php break; case 'true_false': ?> <div class="true-false-options"> <label class="true-false-option"> <input type="radio" name="answer_<?php echo $question->ID; ?>" value="true" <?php checked($user_answer, 'true'); ?>> <span>正确</span> </label> <label class="true-false-option"> <input type="radio" name="answer_<?php echo $question->ID; ?>" value="false" <?php checked($user_answer, 'false'); ?>> <span>错误</span> </label> </div> <?php break; case 'fill_blank': ?> <div class="fill-blank-answer"> <input type="text" name="answer_<?php echo $question->ID; ?>" value="<?php echo esc_attr($user_answer); ?>" class="widefat" placeholder="请输入答案"> </div> <?php break; case 'short_answer': case 'essay': ?> <div class="essay-answer"> <textarea name="answer_<?php echo $question->ID; ?>" class="widefat" rows="6" placeholder="请输入您的答案"><?php echo esc_textarea($user_answer); ?></textarea> </div> <?php break; endswitch; ?> </div> <div class="question-actions"> <button type="button" class="button mark-question" data-marked="0"> <span class="mark-text">标记此题</
WordPress作为全球最流行的内容管理系统,早已超越了简单的博客平台范畴。凭借其强大的插件架构、丰富的API接口和庞大的开发者社区,WordPress已经演变为一个功能完善的企业级应用开发平台。在许多人的印象中,WordPress可能仍是一个"博客工具",但实际上,全球超过43%的网站都运行在WordPress上,其中不乏复杂的企业级应用。
本教程将深入探讨如何通过WordPress的代码二次开发,实现一个功能完整的在线考试测评与自动组卷评分系统。这个系统不仅能够满足教育机构、企业培训部门的在线测评需求,还能展示WordPress作为应用开发平台的强大扩展能力。我们将从系统设计、数据库结构、核心功能实现到前端展示,全方位解析开发过程。
在线考试测评系统需要满足以下核心功能:
- 试题库管理(支持多种题型:单选、多选、判断、填空、简答等)
- 智能组卷策略(随机组卷、按知识点组卷、难度系数组卷)
- 在线考试与计时功能
- 自动评分与人工阅卷结合
- 成绩统计与分析报表
- 用户权限与考试管理
选择WordPress作为开发基础有以下几个显著优势:
- 用户管理系统完善:WordPress自带的用户角色和权限管理系统可以快速扩展为考试系统的用户体系
- 主题模板机制:利用WordPress主题系统可以快速构建一致的前端界面
- 插件架构:通过自定义插件的方式实现功能模块,便于维护和升级
- 丰富的API:REST API、AJAX、短代码等机制便于前后端交互
- 安全机制成熟:WordPress经过多年发展,拥有完善的安全防护机制
- SEO友好:天生具备良好的搜索引擎优化基础
- 后端:PHP 7.4+,WordPress 5.6+,MySQL 5.6+
- 前端:HTML5,CSS3,JavaScript (ES6+),jQuery(兼容性考虑)
- 关键WordPress特性:自定义文章类型(CPT)、自定义分类法、元数据、短代码、REST API
- 辅助工具:Chart.js(数据可视化),Select2(下拉框增强),Bootstrap 5(响应式布局)
虽然WordPress本身使用wp_posts和wp_postmeta作为主要数据存储结构,但对于复杂的考试系统,我们需要创建专门的数据表来保证性能和数据一致性。
-- 试题表扩展(基于wp_posts的自定义文章类型)
-- 使用post_type = 'exam_question'标识试题
-- 自定义试题元数据表
CREATE TABLE wp_exam_question_meta (
meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
question_id bigint(20) unsigned NOT NULL,
meta_key varchar(255) DEFAULT NULL,
meta_value longtext,
PRIMARY KEY (meta_id),
KEY question_id (question_id),
KEY meta_key (meta_key(191))
);
-- 试卷表
CREATE TABLE wp_exam_papers (
paper_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
paper_title varchar(255) NOT NULL,
paper_description text,
paper_type varchar(50) DEFAULT 'random', -- random/fixed/manual
total_score int(11) DEFAULT 100,
time_limit int(11) DEFAULT 60, -- 分钟
created_by bigint(20) unsigned NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (paper_id)
);
-- 试卷-试题关联表
CREATE TABLE wp_exam_paper_questions (
relation_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
paper_id bigint(20) unsigned NOT NULL,
question_id bigint(20) unsigned NOT NULL,
question_order int(11) DEFAULT 0,
question_score decimal(5,2) DEFAULT 0,
PRIMARY KEY (relation_id),
KEY paper_id (paper_id),
KEY question_id (question_id)
);
-- 考试记录表
CREATE TABLE wp_exam_records (
record_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
user_id bigint(20) unsigned NOT NULL,
paper_id bigint(20) unsigned NOT NULL,
start_time datetime DEFAULT NULL,
end_time datetime DEFAULT NULL,
submit_time datetime DEFAULT NULL,
total_score decimal(5,2) DEFAULT 0,
auto_score decimal(5,2) DEFAULT 0,
manual_score decimal(5,2) DEFAULT 0,
status varchar(20) DEFAULT 'in_progress', -- in_progress/completed/reviewing
ip_address varchar(45) DEFAULT NULL,
user_answers longtext, -- 存储用户答案的JSON
PRIMARY KEY (record_id),
KEY user_id (user_id),
KEY paper_id (paper_id),
KEY status (status)
);
试题作为系统的核心数据,我们可以使用WordPress的自定义文章类型(CPT)来管理:
// 注册试题自定义文章类型
function register_exam_question_post_type() {
$labels = array(
'name' => '试题',
'singular_name' => '试题',
'menu_name' => '试题库',
'add_new' => '添加试题',
'add_new_item' => '添加新试题',
'edit_item' => '编辑试题',
'new_item' => '新试题',
'view_item' => '查看试题',
'search_items' => '搜索试题',
'not_found' => '未找到试题',
'not_found_in_trash' => '回收站中无试题'
);
$args = array(
'labels' => $labels,
'public' => false,
'publicly_queryable' => false,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => false,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => false,
'menu_position' => 30,
'menu_icon' => 'dashicons-welcome-learn-more',
'supports' => array('title', 'editor', 'author'),
'show_in_rest' => true, // 启用REST API支持
);
register_post_type('exam_question', $args);
}
add_action('init', 'register_exam_question_post_type');
利用WordPress的自定义分类法为试题添加分类和标签:
// 注册试题分类法
function register_exam_taxonomies() {
// 试题分类(如:数学、语文、英语)
register_taxonomy(
'question_category',
'exam_question',
array(
'labels' => array(
'name' => '试题分类',
'singular_name' => '试题分类',
'search_items' => '搜索分类',
'all_items' => '全部分类',
'edit_item' => '编辑分类',
'update_item' => '更新分类',
'add_new_item' => '添加新分类',
'new_item_name' => '新分类名称',
'menu_name' => '试题分类'
),
'hierarchical' => true,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => false,
'show_in_rest' => true,
)
);
// 试题难度标签
register_taxonomy(
'question_difficulty',
'exam_question',
array(
'labels' => array(
'name' => '难度级别',
'singular_name' => '难度级别',
'search_items' => '搜索难度',
'all_items' => '全部难度',
'edit_item' => '编辑难度',
'update_item' => '更新难度',
'add_new_item' => '添加新难度',
'new_item_name' => '新难度名称',
'menu_name' => '难度级别'
),
'hierarchical' => false,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => false,
'show_in_rest' => true,
)
);
}
add_action('init', 'register_exam_taxonomies');
在WordPress后台创建试题管理界面,支持多种题型:
// 为试题添加元数据框
function add_exam_question_meta_boxes() {
add_meta_box(
'question-type-meta',
'试题类型与选项',
'render_question_type_meta_box',
'exam_question',
'normal',
'high'
);
add_meta_box(
'question-answer-meta',
'正确答案与解析',
'render_question_answer_meta_box',
'exam_question',
'normal',
'high'
);
}
add_action('add_meta_boxes', 'add_exam_question_meta_boxes');
// 渲染试题类型元数据框
function render_question_type_meta_box($post) {
wp_nonce_field('save_question_meta', 'question_meta_nonce');
$question_type = get_post_meta($post->ID, '_question_type', true);
$question_options = get_post_meta($post->ID, '_question_options', true);
// 解析选项(如果是选择题)
$options = !empty($question_options) ? json_decode($question_options, true) : array();
?>
<div class="question-meta-container">
<div class="form-group">
<label for="question_type">试题类型:</label>
<select name="question_type" id="question_type" class="widefat">
<option value="single_choice" <?php selected($question_type, 'single_choice'); ?>>单选题</option>
<option value="multiple_choice" <?php selected($question_type, 'multiple_choice'); ?>>多选题</option>
<option value="true_false" <?php selected($question_type, 'true_false'); ?>>判断题</option>
<option value="fill_blank" <?php selected($question_type, 'fill_blank'); ?>>填空题</option>
<option value="short_answer" <?php selected($question_type, 'short_answer'); ?>>简答题</option>
<option value="essay" <?php selected($question_type, 'essay'); ?>>论述题</option>
</select>
</div>
<div id="choice-options-container" style="display: <?php echo in_array($question_type, array('single_choice', 'multiple_choice')) ? 'block' : 'none'; ?>;">
<h4>选项设置</h4>
<div id="choice-options-list">
<?php if (!empty($options)): ?>
<?php foreach ($options as $key => $option): ?>
<div class="option-item">
<input type="text" name="question_options[]" value="<?php echo esc_attr($option); ?>" class="widefat" placeholder="选项内容">
<button type="button" class="button remove-option">删除</button>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="option-item">
<input type="text" name="question_options[]" value="" class="widefat" placeholder="选项内容">
<button type="button" class="button remove-option">删除</button>
</div>
<div class="option-item">
<input type="text" name="question_options[]" value="" class="widefat" placeholder="选项内容">
<button type="button" class="button remove-option">删除</button>
</div>
<?php endif; ?>
</div>
<button type="button" id="add-option" class="button">添加选项</button>
</div>
<div class="form-group">
<label for="question_score">默认分值:</label>
<input type="number" name="question_score" id="question_score"
value="<?php echo esc_attr(get_post_meta($post->ID, '_question_score', true) ?: 5); ?>"
min="0" step="0.5" class="small-text"> 分
</div>
</div>
<script>
jQuery(document).ready(function($) {
// 切换试题类型时显示/隐藏选项设置
$('#question_type').change(function() {
var type = $(this).val();
if (type === 'single_choice' || type === 'multiple_choice') {
$('#choice-options-container').show();
} else {
$('#choice-options-container').hide();
}
});
// 添加选项
$('#add-option').click(function() {
var newOption = '<div class="option-item">' +
'<input type="text" name="question_options[]" value="" class="widefat" placeholder="选项内容">' +
'<button type="button" class="button remove-option">删除</button>' +
'</div>';
$('#choice-options-list').append(newOption);
});
// 删除选项
$(document).on('click', '.remove-option', function() {
if ($('#choice-options-list .option-item').length > 1) {
$(this).closest('.option-item').remove();
} else {
alert('至少需要保留一个选项');
}
});
});
</script>
<?php
}
智能组卷是考试系统的核心功能,这里实现一个基于分类和难度的随机组卷算法:
class ExamPaperGenerator {
/**
* 生成随机试卷
*
* @param array $params 组卷参数
* @return int|false 试卷ID或false
*/
public static function generateRandomPaper($params) {
global $wpdb;
$defaults = array(
'title' => '随机试卷',
'description' => '',
'total_questions' => 50,
'categories' => array(),
'difficulties' => array('easy', 'medium', 'hard'),
'difficulty_distribution' => array('easy' => 0.3, 'medium' => 0.5, 'hard' => 0.2),
'question_types' => array('single_choice', 'multiple_choice', 'true_false'),
'time_limit' => 60,
'created_by' => get_current_user_id(),
);
$params = wp_parse_args($params, $defaults);
// 开始事务
$wpdb->query('START TRANSACTION');
try {
// 创建试卷记录
$paper_data = array(
'paper_title' => sanitize_text_field($params['title']),
'paper_description' => sanitize_textarea_field($params['description']),
'paper_type' => 'random',
'total_score' => $params['total_questions'] * 2, // 假设每题2分
'time_limit' => intval($params['time_limit']),
'created_by' => intval($params['created_by']),
'created_at' => current_time('mysql'),
);
$wpdb->insert($wpdb->prefix . 'exam_papers', $paper_data);
$paper_id = $wpdb->insert_id;
if (!$paper_id) {
throw new Exception('创建试卷失败');
}
// 获取符合条件的试题
$questions = self::getQuestionsByCriteria($params);
if (count($questions) < $params['total_questions']) {
throw new Exception('试题数量不足,无法生成试卷');
}
// 随机选择试题
shuffle($questions);
$selected_questions = array_slice($questions, 0, $params['total_questions']);
// 关联试题到试卷
$order = 1;
foreach ($selected_questions as $question) {
$relation_data = array(
'paper_id' => $paper_id,
'question_id' => $question->ID,
'question_order' => $order,
'question_score' => get_post_meta($question->ID, '_question_score', true) ?: 2,
);
$wpdb->insert($wpdb->prefix . 'exam_paper_questions', $relation_data);
$order++;
}
// 提交事务
$wpdb->query('COMMIT');
return $paper_id;
} catch (Exception $e) {
// 回滚事务
$wpdb->query('ROLLBACK');
error_log('生成试卷失败: ' . $e->getMessage());
return false;
}
}
/**
* 根据条件获取试题
*/
private static function getQuestionsByCriteria($params) {
$args = array(
'post_type' => 'exam_question',
'post_status' => 'publish',
'posts_per_page' => -1,
'fields' => 'ids',
);
// 添加分类筛选
if (!empty($params['categories'])) {
$args['tax_query'][] = array(
'taxonomy' => 'question_category',
'field' => 'term_id',
'terms' => $params['categories'],
'operator' => 'IN',
);
}
// 添加难度筛选
if (!empty($params['difficulties'])) {
$args['tax_query'][] = array(
'taxonomy' => 'question_difficulty',
'field' => 'slug',
'terms' => $params['difficulties'],
'operator' => 'IN',
);
}
// 添加题型筛选
if (!empty($params['question_types'])) {
$args['meta_query'][] = array(
'key' => '_question_type',
'value' => $params['question_types'],
'compare' => 'IN',
);
}
// 确保tax_query关系正确
if (isset($args['tax_query']) && count($args['tax_query']) > 1) {
$args['tax_query']['relation'] = 'AND';
}
$query = new WP_Query($args);
return $query->posts;
}
/**
* 按知识点比例组卷
*/
public static function generatePaperByKnowledgePoints($params) {
// 实现按知识点比例分配试题的逻辑
// 这里可以扩展为更复杂的组卷策略
}
}
### 3.3 试卷管理后台界面
创建试卷管理后台界面,支持手动组卷和自动组卷:
// 添加试卷管理菜单
function add_exam_paper_admin_menu() {
add_menu_page(
'试卷管理',
'考试系统',
'manage_options',
'exam-system',
'render_exam_dashboard',
'dashicons-clipboard',
30
);
add_submenu_page(
'exam-system',
'试卷管理',
'试卷管理',
'manage_options',
'exam-papers',
'render_exam_papers_page'
);
add_submenu_page(
'exam-system',
'组卷工具',
'智能组卷',
'manage_options',
'exam-generator',
'render_paper_generator_page'
);
add_submenu_page(
'exam-system',
'考试记录',
'考试记录',
'manage_options',
'exam-records',
'render_exam_records_page'
);
add_submenu_page(
'exam-system',
'成绩统计',
'成绩统计',
'manage_options',
'exam-statistics',
'render_exam_statistics_page'
);
}
add_action('admin_menu', 'add_exam_paper_admin_menu');
// 渲染智能组卷页面
function render_paper_generator_page() {
// 获取所有试题分类
$categories = get_terms(array(
'taxonomy' => 'question_category',
'hide_empty' => false,
));
// 获取所有难度级别
$difficulties = get_terms(array(
'taxonomy' => 'question_difficulty',
'hide_empty' => false,
));
?>
<div class="wrap">
<h1>智能组卷工具</h1>
<div class="card">
<h2>随机组卷</h2>
<form id="random-paper-form" method="post">
<?php wp_nonce_field('generate_random_paper', 'paper_nonce'); ?>
<table class="form-table">
<tr>
<th scope="row"><label for="paper_title">试卷标题</label></th>
<td>
<input type="text" name="paper_title" id="paper_title"
class="regular-text" value="随机试卷 <?php echo date('Y-m-d'); ?>" required>
</td>
</tr>
<tr>
<th scope="row"><label for="paper_description">试卷描述</label></th>
<td>
<textarea name="paper_description" id="paper_description"
class="large-text" rows="3"></textarea>
</td>
</tr>
<tr>
<th scope="row"><label for="total_questions">试题数量</label></th>
<td>
<input type="number" name="total_questions" id="total_questions"
min="10" max="200" value="50" required>
</td>
</tr>
<tr>
<th scope="row"><label>试题分类</label></th>
<td>
<div style="max-height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 10px;">
<?php foreach ($categories as $category): ?>
<label style="display: block; margin-bottom: 5px;">
<input type="checkbox" name="categories[]" value="<?php echo $category->term_id; ?>">
<?php echo $category->name; ?> (<?php echo $category->count; ?>)
</label>
<?php endforeach; ?>
</div>
<p class="description">不选择任何分类表示从所有分类中选题</p>
</td>
</tr>
<tr>
<th scope="row"><label>难度分布</label></th>
<td>
<div id="difficulty-distribution">
<?php foreach ($difficulties as $difficulty): ?>
<div style="margin-bottom: 10px;">
<label style="display: inline-block; width: 100px;">
<?php echo $difficulty->name; ?>:
</label>
<input type="number" name="difficulty_<?php echo $difficulty->slug; ?>"
min="0" max="100" value="30" style="width: 60px;"> %
</div>
<?php endforeach; ?>
</div>
</td>
</tr>
<tr>
<th scope="row"><label>试题类型</label></th>
<td>
<label style="margin-right: 15px;">
<input type="checkbox" name="question_types[]" value="single_choice" checked> 单选题
</label>
<label style="margin-right: 15px;">
<input type="checkbox" name="question_types[]" value="multiple_choice" checked> 多选题
</label>
<label style="margin-right: 15px;">
<input type="checkbox" name="question_types[]" value="true_false"> 判断题
</label>
<label style="margin-right: 15px;">
<input type="checkbox" name="question_types[]" value="fill_blank"> 填空题
</label>
</td>
</tr>
<tr>
<th scope="row"><label for="time_limit">考试时间(分钟)</label></th>
<td>
<input type="number" name="time_limit" id="time_limit"
min="10" max="300" value="60" required>
</td>
</tr>
</table>
<p class="submit">
<button type="submit" name="generate_paper" class="button button-primary">生成试卷</button>
</p>
</form>
</div>
<?php
// 处理表单提交
if (isset($_POST['generate_paper']) && check_admin_referer('generate_random_paper', 'paper_nonce')) {
$params = array(
'title' => sanitize_text_field($_POST['paper_title']),
'description' => sanitize_textarea_field($_POST['paper_description']),
'total_questions' => intval($_POST['total_questions']),
'categories' => isset($_POST['categories']) ? array_map('intval', $_POST['categories']) : array(),
'time_limit' => intval($_POST['time_limit']),
'question_types' => isset($_POST['question_types']) ? array_map('sanitize_text_field', $_POST['question_types']) : array(),
);
$paper_id = ExamPaperGenerator::generateRandomPaper($params);
if ($paper_id) {
echo '<div class="notice notice-success"><p>试卷生成成功!<a href="' . admin_url('admin.php?page=exam-papers&action=edit&paper_id=' . $paper_id) . '">查看试卷</a></p></div>';
} else {
echo '<div class="notice notice-error"><p>试卷生成失败,请检查试题库是否足够</p></div>';
}
}
?>
</div>
<script>
jQuery(document).ready(function($) {
// 验证难度分布总和为100%
$('#random-paper-form').submit(function(e) {
var total = 0;
$('#difficulty-distribution input[type="number"]').each(function() {
total += parseInt($(this).val()) || 0;
});
if (total !== 100) {
alert('难度分布总和必须为100%,当前总和为:' + total + '%');
e.preventDefault();
return false;
}
});
});
</script>
<?php
}
## 第四章:在线考试功能实现
### 4.1 考试前端界面与交互
创建考试前端界面,使用AJAX实现无刷新答题:
// 创建考试短代码
function exam_paper_shortcode($atts) {
$atts = shortcode_atts(array(
'id' => 0,
'preview' => false,
), $atts, 'exam_paper');
$paper_id = intval($atts['id']);
if (!$paper_id) {
return '<div class="exam-error">请指定试卷ID</div>';
}
// 检查用户权限
if (!is_user_logged_in()) {
return '<div class="exam-error">请先登录后再参加考试</div>';
}
// 获取试卷信息
global $wpdb;
$paper = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}exam_papers WHERE paper_id = %d",
$paper_id
));
if (!$paper) {
return '<div class="exam-error">试卷不存在</div>';
}
// 检查是否已有进行中的考试
$user_id = get_current_user_id();
$existing_record = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}exam_records
WHERE user_id = %d AND paper_id = %d AND status = 'in_progress'",
$user_id, $paper_id
));
// 如果是预览模式或管理员
$is_preview = $atts['preview'] || current_user_can('manage_options');
if (!$existing_record && !$is_preview) {
// 创建新的考试记录
$record_data = array(
'user_id' => $user_id,
'paper_id' => $paper_id,
'start_time' => current_time('mysql'),
'status' => 'in_progress',
'ip_address' => $_SERVER['REMOTE_ADDR'],
);
$wpdb->insert($wpdb->prefix . 'exam_records', $record_data);
$record_id = $wpdb->insert_id;
} else {
$record_id = $existing_record ? $existing_record->record_id : 0;
}
// 获取试卷试题
$questions = $wpdb->get_results($wpdb->prepare(
"SELECT q.*, pq.question_order, pq.question_score
FROM {$wpdb->prefix}exam_paper_questions pq
JOIN {$wpdb->prefix}posts q ON pq.question_id = q.ID
WHERE pq.paper_id = %d AND q.post_status = 'publish'
ORDER BY pq.question_order ASC",
$paper_id
));
if (empty($questions)) {
return '<div class="exam-error">试卷中没有试题</div>';
}
// 获取用户已有的答案(如果是继续考试)
$user_answers = array();
if ($record_id && $existing_record && !empty($existing_record->user_answers)) {
$user_answers = json_decode($existing_record->user_answers, true);
}
// 生成考试界面HTML
ob_start();
?>
<div class="exam-container" data-paper-id="<?php echo $paper_id; ?>"
data-record-id="<?php echo $record_id; ?>"
data-time-limit="<?php echo $paper->time_limit; ?>"
data-is-preview="<?php echo $is_preview ? '1' : '0'; ?>">
<div class="exam-header">
<h1 class="exam-title"><?php echo esc_html($paper->paper_title); ?></h1>
<div class="exam-info">
<div class="exam-timer">
<span class="timer-label">剩余时间:</span>
<span class="timer-display"><?php echo $paper->time_limit; ?>:00</span>
</div>
<div class="exam-progress">
<span class="progress-label">答题进度:</span>
<span class="progress-display">0/<?php echo count($questions); ?></span>
</div>
</div>
</div>
<div class="exam-navigation">
<div class="question-nav">
<?php foreach ($questions as $index => $question): ?>
<button type="button" class="nav-btn" data-question-index="<?php echo $index; ?>">
<?php echo $index + 1; ?>
</button>
<?php endforeach; ?>
</div>
<div class="nav-controls">
<button type="button" id="prev-question" class="button">上一题</button>
<button type="button" id="next-question" class="button">下一题</button>
<button type="button" id="submit-exam" class="button button-primary">提交试卷</button>
</div>
</div>
<div class="exam-questions">
<?php foreach ($questions as $index => $question):
$question_type = get_post_meta($question->ID, '_question_type', true);
$question_options = get_post_meta($question->ID, '_question_options', true);
$options = !empty($question_options) ? json_decode($question_options, true) : array();
$user_answer = isset($user_answers[$question->ID]) ? $user_answers[$question->ID] : '';
?>
<div class="question-item" data-question-id="<?php echo $question->ID; ?>"
data-question-index="<?php echo $index; ?>"
style="<?php echo $index > 0 ? 'display:none;' : ''; ?>">
<div class="question-header">
<h3 class="question-title">
第<?php echo $index + 1; ?>题
<span class="question-score">(<?php echo $question->question_score; ?>分)</span>
</h3>
<div class="question-type"><?php echo get_question_type_name($question_type); ?></div>
</div>
<div class="question-content">
<?php echo apply_filters('the_content', $question->post_content); ?>
</div>
<div class="question-answer">
<?php switch ($question_type):
case 'single_choice': ?>
<div class="choice-options">
<?php foreach ($options as $key => $option): ?>
<label class="choice-option">
<input type="radio" name="answer_<?php echo $question->ID; ?>"
value="<?php echo chr(65 + $key); ?>"
<?php checked($user_answer, chr(65 + $key)); ?>>
<span class="option-label"><?php echo chr(65 + $key); ?>.</span>
<span class="option-text"><?php echo esc_html($option); ?></span>
</label>
<?php endforeach; ?>
</div>
<?php break;
case 'multiple_choice': ?>
<div class="choice-options">
<?php foreach ($options as $key => $option):
$user_answers_array = !empty($user_answer) ? explode(',', $user_answer) : array();
?>
<label class="choice-option">
<input type="checkbox" name="answer_<?php echo $question->ID; ?>[]"
value="<?php echo chr(65 + $key); ?>"
<?php checked(in_array(chr(65 + $key), $user_answers_array)); ?>>
<span class="option-label"><?php echo chr(65 + $key); ?>.</span>
<span class="option-text"><?php echo esc_html($option); ?></span>
</label>
<?php endforeach; ?>
</div>
<?php break;
case 'true_false': ?>
<div class="true-false-options">
<label class="true-false-option">
<input type="radio" name="answer_<?php echo $question->ID; ?>"
value="true" <?php checked($user_answer, 'true'); ?>>
<span>正确</span>
</label>
<label class="true-false-option">
<input type="radio" name="answer_<?php echo $question->ID; ?>"
value="false" <?php checked($user_answer, 'false'); ?>>
<span>错误</span>
</label>
</div>
<?php break;
case 'fill_blank': ?>
<div class="fill-blank-answer">
<input type="text" name="answer_<?php echo $question->ID; ?>"
value="<?php echo esc_attr($user_answer); ?>"
class="widefat" placeholder="请输入答案">
</div>
<?php break;
case 'short_answer':
case 'essay': ?>
<div class="essay-answer">
<textarea name="answer_<?php echo $question->ID; ?>"
class="widefat" rows="6"
placeholder="请输入您的答案"><?php echo esc_textarea($user_answer); ?></textarea>
</div>
<?php break;
endswitch; ?>
</div>
<div class="question-actions">
<button type="button" class="button mark-question" data-marked="0">
<span class="mark-text">标记此题</


