文章目录[隐藏]
WordPress高级教程:开发集成在线问卷调研与可视化数据报告生成器
引言:WordPress作为企业级应用开发平台
WordPress早已超越了简单的博客系统范畴,成为功能强大的内容管理系统(CMS)和应用程序开发平台。全球超过43%的网站基于WordPress构建,其强大的插件架构和灵活的代码结构使其成为开发各种互联网小工具的理想选择。本教程将深入探讨如何通过WordPress代码二次开发,实现一个集在线问卷调研与可视化数据报告生成器于一体的高级功能模块。
在当今数据驱动的商业环境中,在线问卷调研和数据可视化分析已成为企业决策、市场研究和用户反馈收集的重要工具。通过将这些功能集成到WordPress网站中,我们可以为用户提供无缝的体验,同时利用WordPress强大的用户管理、权限控制和内容展示能力。
第一部分:项目架构设计与技术选型
1.1 系统需求分析
在开始开发之前,我们需要明确系统的核心需求:
- 问卷创建与管理:支持多种题型(单选、多选、文本输入、评分等)
- 问卷发布与收集:可通过短代码、小工具或独立页面嵌入
- 响应数据存储:高效存储和检索大量问卷响应数据
- 数据可视化:自动生成图表和报告
- 权限控制:基于WordPress角色系统的访问控制
- 数据导出:支持CSV、Excel和PDF格式导出
1.2 技术架构设计
我们将采用分层架构设计:
表现层 (Presentation Layer)
├── WordPress前端界面
├── 管理后台界面
└── 可视化报告界面
业务逻辑层 (Business Logic Layer)
├── 问卷管理模块
├── 响应处理模块
├── 数据分析模块
└── 报告生成模块
数据访问层 (Data Access Layer)
├── WordPress自定义表
├── 选项API (Options API)
└── 文件系统(图表缓存)
基础服务层 (Infrastructure Layer)
├── WordPress核心API
├── 图表库(Chart.js或ECharts)
└── PDF生成库
1.3 技术栈选择
- 核心框架:WordPress 5.8+
- 前端图表库:Chart.js 3.x(轻量级且功能强大)
- PDF生成:TCPDF或Dompdf
- 数据导出:PhpSpreadsheet(用于Excel导出)
- AJAX处理:WordPress REST API + jQuery/Axios
- 数据库:MySQL 5.6+(与WordPress兼容)
第二部分:数据库设计与自定义表创建
2.1 自定义表设计
虽然WordPress提供了Posts和Post Meta表,但为了问卷数据的性能优化和规范化,我们将创建自定义表:
-- 问卷主表
CREATE TABLE wp_surveys (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
description TEXT,
status ENUM('draft', 'published', 'closed') DEFAULT 'draft',
settings LONGTEXT, -- JSON格式存储配置
created_by BIGINT(20) UNSIGNED,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY status_idx (status),
KEY created_by_idx (created_by)
);
-- 问卷问题表
CREATE TABLE wp_survey_questions (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
survey_id BIGINT(20) UNSIGNED NOT NULL,
question_text TEXT NOT NULL,
question_type ENUM('single_choice', 'multiple_choice', 'text', 'rating', 'likert') NOT NULL,
options LONGTEXT, -- JSON格式存储选项
is_required BOOLEAN DEFAULT FALSE,
sort_order INT(11) DEFAULT 0,
settings LONGTEXT,
PRIMARY KEY (id),
KEY survey_id_idx (survey_id),
FOREIGN KEY (survey_id) REFERENCES wp_surveys(id) ON DELETE CASCADE
);
-- 问卷响应表
CREATE TABLE wp_survey_responses (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
survey_id BIGINT(20) UNSIGNED NOT NULL,
respondent_ip VARCHAR(45),
respondent_user_id BIGINT(20) UNSIGNED,
started_at DATETIME,
completed_at DATETIME,
time_spent INT(11), -- 单位:秒
metadata LONGTEXT,
PRIMARY KEY (id),
KEY survey_id_idx (survey_id),
KEY respondent_user_id_idx (respondent_user_id),
FOREIGN KEY (survey_id) REFERENCES wp_surveys(id) ON DELETE CASCADE
);
-- 响应答案表
CREATE TABLE wp_survey_answers (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
response_id BIGINT(20) UNSIGNED NOT NULL,
question_id BIGINT(20) UNSIGNED NOT NULL,
answer_text TEXT,
answer_value TEXT,
PRIMARY KEY (id),
KEY response_id_idx (response_id),
KEY question_id_idx (question_id),
FOREIGN KEY (response_id) REFERENCES wp_survey_responses(id) ON DELETE CASCADE,
FOREIGN KEY (question_id) REFERENCES wp_survey_questions(id) ON DELETE CASCADE
);
2.2 数据库操作类实现
创建数据库操作类,封装所有数据库交互逻辑:
<?php
/**
* 问卷系统数据库操作类
*/
class Survey_DB_Manager {
private static $instance = null;
private $wpdb;
private $charset_collate;
private $table_prefix;
// 表名常量
const TABLE_SURVEYS = 'surveys';
const TABLE_QUESTIONS = 'survey_questions';
const TABLE_RESPONSES = 'survey_responses';
const TABLE_ANSWERS = 'survey_answers';
private function __construct() {
global $wpdb;
$this->wpdb = $wpdb;
$this->table_prefix = $wpdb->prefix . 'survey_';
$this->charset_collate = $wpdb->get_charset_collate();
}
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* 创建或更新数据库表
*/
public function create_tables() {
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
$sql = array();
// 创建问卷主表
$sql[] = "CREATE TABLE {$this->table_prefix}surveys (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
description TEXT,
status ENUM('draft', 'published', 'closed') DEFAULT 'draft',
settings LONGTEXT,
created_by BIGINT(20) UNSIGNED,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY status_idx (status),
KEY created_by_idx (created_by)
) {$this->charset_collate};";
// 创建其他表的SQL语句...
// 执行所有SQL
foreach ($sql as $query) {
dbDelta($query);
}
// 更新数据库版本
update_option('survey_db_version', '1.0.0');
}
/**
* 获取问卷列表
*/
public function get_surveys($args = array()) {
$defaults = array(
'status' => 'published',
'per_page' => 10,
'page' => 1,
'orderby' => 'created_at',
'order' => 'DESC'
);
$args = wp_parse_args($args, $defaults);
$offset = ($args['page'] - 1) * $args['per_page'];
$where = array('1=1');
$prepare_values = array();
if (!empty($args['status'])) {
$where[] = 'status = %s';
$prepare_values[] = $args['status'];
}
if (!empty($args['created_by'])) {
$where[] = 'created_by = %d';
$prepare_values[] = $args['created_by'];
}
$where_clause = implode(' AND ', $where);
$query = "SELECT * FROM {$this->table_prefix}surveys
WHERE {$where_clause}
ORDER BY {$args['orderby']} {$args['order']}
LIMIT %d OFFSET %d";
$prepare_values[] = $args['per_page'];
$prepare_values[] = $offset;
return $this->wpdb->get_results(
$this->wpdb->prepare($query, $prepare_values)
);
}
/**
* 保存问卷响应
*/
public function save_response($survey_id, $answers, $user_id = null) {
$this->wpdb->query('START TRANSACTION');
try {
// 插入响应记录
$response_data = array(
'survey_id' => $survey_id,
'respondent_user_id' => $user_id,
'respondent_ip' => $this->get_client_ip(),
'started_at' => current_time('mysql'),
'completed_at' => current_time('mysql'),
'metadata' => json_encode(array(
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'referer' => $_SERVER['HTTP_REFERER'] ?? ''
))
);
$this->wpdb->insert(
$this->table_prefix . 'responses',
$response_data
);
$response_id = $this->wpdb->insert_id;
// 保存每个问题的答案
foreach ($answers as $question_id => $answer) {
$answer_data = array(
'response_id' => $response_id,
'question_id' => $question_id,
'answer_text' => is_array($answer) ? json_encode($answer) : $answer,
'answer_value' => is_array($answer) ? implode(',', $answer) : $answer
);
$this->wpdb->insert(
$this->table_prefix . 'answers',
$answer_data
);
}
$this->wpdb->query('COMMIT');
return $response_id;
} catch (Exception $e) {
$this->wpdb->query('ROLLBACK');
error_log('保存问卷响应失败: ' . $e->getMessage());
return false;
}
}
/**
* 获取客户端IP地址
*/
private function get_client_ip() {
$ip_keys = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR');
foreach ($ip_keys as $key) {
if (array_key_exists($key, $_SERVER) === true) {
foreach (explode(',', $_SERVER[$key]) as $ip) {
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
return $ip;
}
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
/**
* 获取问卷统计数据
*/
public function get_survey_stats($survey_id) {
$stats = array();
// 获取响应总数
$stats['total_responses'] = (int) $this->wpdb->get_var(
$this->wpdb->prepare(
"SELECT COUNT(*) FROM {$this->table_prefix}responses WHERE survey_id = %d",
$survey_id
)
);
// 获取最近7天的响应趋势
$stats['response_trend'] = $this->wpdb->get_results(
$this->wpdb->prepare(
"SELECT DATE(completed_at) as date, COUNT(*) as count
FROM {$this->table_prefix}responses
WHERE survey_id = %d AND completed_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY DATE(completed_at)
ORDER BY date ASC",
$survey_id
)
);
return $stats;
}
}
?>
第三部分:问卷系统核心功能开发
3.1 问卷创建与管理模块
创建问卷管理类,处理问卷的CRUD操作:
<?php
/**
* 问卷管理类
*/
class Survey_Manager {
private $db;
public function __construct() {
$this->db = Survey_DB_Manager::get_instance();
}
/**
* 创建新问卷
*/
public function create_survey($data) {
$defaults = array(
'title' => '新问卷',
'description' => '',
'status' => 'draft',
'settings' => array(
'allow_multiple_responses' => false,
'require_login' => false,
'show_progress' => true,
'thank_you_message' => '感谢您的参与!'
)
);
$data = wp_parse_args($data, $defaults);
// 验证数据
if (empty($data['title'])) {
return new WP_Error('empty_title', '问卷标题不能为空');
}
// 获取当前用户ID
$current_user_id = get_current_user_id();
$survey_data = array(
'title' => sanitize_text_field($data['title']),
'description' => wp_kses_post($data['description']),
'status' => in_array($data['status'], array('draft', 'published', 'closed')) ? $data['status'] : 'draft',
'settings' => json_encode($data['settings']),
'created_by' => $current_user_id
);
// 插入数据库
global $wpdb;
$table_name = $wpdb->prefix . 'survey_surveys';
$result = $wpdb->insert($table_name, $survey_data);
if ($result === false) {
return new WP_Error('db_error', '创建问卷失败');
}
$survey_id = $wpdb->insert_id;
// 记录操作日志
$this->log_activity($survey_id, 'create', '创建问卷');
return $survey_id;
}
/**
* 添加问题到问卷
*/
public function add_question($survey_id, $question_data) {
// 验证问卷是否存在
if (!$this->survey_exists($survey_id)) {
return new WP_Error('not_found', '问卷不存在');
}
$defaults = array(
'question_text' => '',
'question_type' => 'single_choice',
'options' => array(),
'is_required' => false,
'settings' => array()
);
$question_data = wp_parse_args($question_data, $defaults);
// 根据问题类型验证选项
$validation_result = $this->validate_question($question_data);
if (is_wp_error($validation_result)) {
return $validation_result;
}
// 获取排序值
$sort_order = $this->get_next_sort_order($survey_id);
$question = array(
'survey_id' => $survey_id,
'question_text' => sanitize_text_field($question_data['question_text']),
'question_type' => $question_data['question_type'],
'options' => json_encode($question_data['options']),
'is_required' => (bool) $question_data['is_required'],
'sort_order' => $sort_order,
'settings' => json_encode($question_data['settings'])
);
global $wpdb;
$table_name = $wpdb->prefix . 'survey_questions';
$result = $wpdb->insert($table_name, $question);
if ($result === false) {
return new WP_Error('db_error', '添加问题失败');
}
$question_id = $wpdb->insert_id;
// 记录操作日志
$this->log_activity($survey_id, 'add_question', '添加问题: ' . $question_data['question_text']);
return $question_id;
}
/**
* 验证问题数据
*/
private function validate_question($question_data) {
if (empty($question_data['question_text'])) {
return new WP_Error('empty_question', '问题内容不能为空');
}
$allowed_types = array('single_choice', 'multiple_choice', 'text', 'rating', 'likert');
if (!in_array($question_data['question_type'], $allowed_types)) {
return new WP_Error('invalid_type', '无效的问题类型');
}
// 对于选择题,验证选项
if (in_array($question_data['question_type'], array('single_choice', 'multiple_choice'))) {
if (empty($question_data['options']) || !is_array($question_data['options'])) {
return new WP_Error('empty_options', '选择题必须提供选项');
}
// 验证每个选项
foreach ($question_data['options'] as $option) {
if (empty(trim($option['text']))) {
return new WP_Error('empty_option_text', '选项文本不能为空');
}
}
}
return true;
}
/**
* 获取下一个排序值
*/
private function get_next_sort_order($survey_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'survey_questions';
$max_sort = $wpdb->get_var(
$wpdb->prepare(
第三部分:问卷系统核心功能开发(续)
3.2 问卷前端展示与提交处理
创建问卷前端渲染和提交处理类:
<?php
/**
* 问卷前端展示类
*/
class Survey_Frontend {
private $db;
private $survey_manager;
public function __construct() {
$this->db = Survey_DB_Manager::get_instance();
$this->survey_manager = new Survey_Manager();
add_shortcode('survey', array($this, 'render_survey_shortcode'));
add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_scripts'));
add_action('wp_ajax_submit_survey', array($this, 'handle_survey_submission'));
add_action('wp_ajax_nopriv_submit_survey', array($this, 'handle_survey_submission'));
}
/**
* 注册前端脚本和样式
*/
public function enqueue_frontend_scripts() {
// 问卷样式
wp_enqueue_style(
'survey-frontend-style',
plugin_dir_url(__FILE__) . 'assets/css/survey-frontend.css',
array(),
'1.0.0'
);
// 问卷脚本
wp_enqueue_script(
'survey-frontend-script',
plugin_dir_url(__FILE__) . 'assets/js/survey-frontend.js',
array('jquery'),
'1.0.0',
true
);
// 本地化脚本
wp_localize_script('survey-frontend-script', 'survey_ajax', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('survey_nonce')
));
}
/**
* 渲染问卷短代码
*/
public function render_survey_shortcode($atts) {
$atts = shortcode_atts(array(
'id' => 0,
'title' => true,
'description' => true
), $atts, 'survey');
$survey_id = intval($atts['id']);
if (!$survey_id) {
return '<div class="survey-error">请指定问卷ID</div>';
}
// 获取问卷数据
$survey = $this->get_survey($survey_id);
if (!$survey) {
return '<div class="survey-error">问卷不存在或已被删除</div>';
}
// 检查问卷状态
if ($survey->status !== 'published') {
return '<div class="survey-error">该问卷当前不可用</div>';
}
// 检查用户权限
$settings = json_decode($survey->settings, true);
if ($settings['require_login'] && !is_user_logged_in()) {
return '<div class="survey-login-required">请先登录以参与此问卷</div>';
}
// 检查是否允许重复提交
if (!$settings['allow_multiple_responses'] && $this->has_user_responded($survey_id)) {
return '<div class="survey-already-responded">您已经参与过此问卷,感谢您的参与!</div>';
}
// 获取问卷问题
$questions = $this->get_survey_questions($survey_id);
// 渲染问卷HTML
ob_start();
?>
<div class="survey-container" data-survey-id="<?php echo esc_attr($survey_id); ?>">
<?php if ($atts['title']): ?>
<div class="survey-header">
<h2 class="survey-title"><?php echo esc_html($survey->title); ?></h2>
<?php if ($atts['description'] && !empty($survey->description)): ?>
<div class="survey-description">
<?php echo wp_kses_post($survey->description); ?>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<form id="survey-form-<?php echo esc_attr($survey_id); ?>" class="survey-form">
<div class="survey-questions">
<?php foreach ($questions as $index => $question): ?>
<div class="survey-question" data-question-id="<?php echo esc_attr($question->id); ?>" data-question-type="<?php echo esc_attr($question->question_type); ?>">
<div class="question-header">
<h3 class="question-title">
<?php echo ($index + 1) . '. ' . esc_html($question->question_text); ?>
<?php if ($question->is_required): ?>
<span class="required-indicator">*</span>
<?php endif; ?>
</h3>
</div>
<div class="question-body">
<?php echo $this->render_question_field($question); ?>
</div>
<?php if ($settings['show_progress']): ?>
<div class="question-progress">
<div class="progress-bar">
<div class="progress-fill" style="width: <?php echo (($index + 1) / count($questions)) * 100; ?>%"></div>
</div>
<div class="progress-text">
问题 <?php echo $index + 1; ?> / <?php echo count($questions); ?>
</div>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<div class="survey-footer">
<div class="form-actions">
<button type="submit" class="survey-submit-btn">
<span class="btn-text">提交问卷</span>
<span class="spinner" style="display: none;"></span>
</button>
<button type="button" class="survey-clear-btn">清除答案</button>
</div>
<div class="required-notice">
<span class="required-indicator">*</span> 表示必填问题
</div>
</div>
<div class="survey-messages" style="display: none;"></div>
</form>
<div class="survey-thank-you" style="display: none;">
<div class="thank-you-content">
<?php echo wp_kses_post($settings['thank_you_message']); ?>
</div>
</div>
</div>
<?php
return ob_get_clean();
}
/**
* 渲染问题字段
*/
private function render_question_field($question) {
$options = json_decode($question->options, true);
$html = '';
switch ($question->question_type) {
case 'single_choice':
$html .= '<div class="single-choice-options">';
foreach ($options as $option) {
$html .= sprintf(
'<label class="option-label"><input type="radio" name="question_%s" value="%s" %s> %s</label>',
esc_attr($question->id),
esc_attr($option['value']),
$question->is_required ? 'required' : '',
esc_html($option['text'])
);
}
$html .= '</div>';
break;
case 'multiple_choice':
$html .= '<div class="multiple-choice-options">';
foreach ($options as $option) {
$html .= sprintf(
'<label class="option-label"><input type="checkbox" name="question_%s[]" value="%s"> %s</label>',
esc_attr($question->id),
esc_attr($option['value']),
esc_html($option['text'])
);
}
$html .= '</div>';
break;
case 'text':
$html .= sprintf(
'<textarea name="question_%s" rows="4" %s placeholder="请输入您的回答..."></textarea>',
esc_attr($question->id),
$question->is_required ? 'required' : ''
);
break;
case 'rating':
$max_rating = isset($options['max_rating']) ? intval($options['max_rating']) : 5;
$html .= '<div class="rating-options">';
for ($i = 1; $i <= $max_rating; $i++) {
$html .= sprintf(
'<label class="rating-label"><input type="radio" name="question_%s" value="%d" %s> %d</label>',
esc_attr($question->id),
$i,
$question->is_required ? 'required' : '',
$i
);
}
$html .= '</div>';
break;
case 'likert':
$html .= '<table class="likert-table">';
$html .= '<thead><tr><th></th>';
foreach ($options['scale'] as $scale_item) {
$html .= '<th>' . esc_html($scale_item) . '</th>';
}
$html .= '</tr></thead><tbody>';
foreach ($options['items'] as $item) {
$html .= '<tr>';
$html .= '<td class="likert-item">' . esc_html($item) . '</td>';
foreach ($options['scale'] as $index => $scale_item) {
$html .= sprintf(
'<td><input type="radio" name="question_%s[%s]" value="%d"></td>',
esc_attr($question->id),
esc_attr($item),
$index
);
}
$html .= '</tr>';
}
$html .= '</tbody></table>';
break;
}
return $html;
}
/**
* 处理问卷提交
*/
public function handle_survey_submission() {
// 验证nonce
if (!check_ajax_referer('survey_nonce', 'nonce', false)) {
wp_die(json_encode(array(
'success' => false,
'message' => '安全验证失败'
)));
}
// 获取提交数据
$survey_id = intval($_POST['survey_id']);
$answers = isset($_POST['answers']) ? $_POST['answers'] : array();
// 验证问卷
$survey = $this->get_survey($survey_id);
if (!$survey || $survey->status !== 'published') {
wp_die(json_encode(array(
'success' => false,
'message' => '问卷不存在或已关闭'
)));
}
// 验证必填问题
$questions = $this->get_survey_questions($survey_id);
$errors = array();
foreach ($questions as $question) {
if ($question->is_required) {
$question_id = $question->id;
if (!isset($answers[$question_id]) || empty($answers[$question_id])) {
$errors[] = sprintf('问题 "%s" 是必填项', $question->question_text);
}
}
}
if (!empty($errors)) {
wp_die(json_encode(array(
'success' => false,
'message' => implode('<br>', $errors)
)));
}
// 获取当前用户ID
$user_id = is_user_logged_in() ? get_current_user_id() : null;
// 保存响应
$response_id = $this->db->save_response($survey_id, $answers, $user_id);
if ($response_id) {
// 发送成功响应
wp_die(json_encode(array(
'success' => true,
'message' => '问卷提交成功',
'response_id' => $response_id
)));
} else {
wp_die(json_encode(array(
'success' => false,
'message' => '提交失败,请稍后重试'
)));
}
}
/**
* 检查用户是否已经参与过问卷
*/
private function has_user_responded($survey_id) {
if (!is_user_logged_in()) {
return false;
}
$user_id = get_current_user_id();
global $wpdb;
$table_name = $wpdb->prefix . 'survey_responses';
$count = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $table_name WHERE survey_id = %d AND respondent_user_id = %d",
$survey_id,
$user_id
));
return $count > 0;
}
/**
* 获取问卷数据
*/
private function get_survey($survey_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'survey_surveys';
return $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE id = %d",
$survey_id
));
}
/**
* 获取问卷问题
*/
private function get_survey_questions($survey_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'survey_questions';
return $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $table_name WHERE survey_id = %d ORDER BY sort_order ASC",
$survey_id
));
}
}
?>
3.3 前端JavaScript交互
创建前端JavaScript文件处理表单交互:
// assets/js/survey-frontend.js
(function($) {
'use strict';
// 问卷表单处理
class SurveyForm {
constructor(formElement) {
this.form = formElement;
this.surveyId = formElement.closest('.survey-container').dataset.surveyId;
this.submitBtn = formElement.querySelector('.survey-submit-btn');
this.clearBtn = formElement.querySelector('.survey-clear-btn');
this.messagesContainer = formElement.querySelector('.survey-messages');
this.thankYouContainer = formElement.closest('.survey-container').querySelector('.survey-thank-you');
this.init();
}
init() {
// 绑定事件
this.form.addEventListener('submit', this.handleSubmit.bind(this));
if (this.clearBtn) {
this.clearBtn.addEventListener('click', this.handleClear.bind(this));
}
// 初始化验证
this.initValidation();
// 初始化进度跟踪
this.initProgressTracking();
}
initValidation() {
// 实时验证
const inputs = this.form.querySelectorAll('input, textarea, select');
inputs.forEach(input => {
input.addEventListener('blur', this.validateField.bind(this));
input.addEventListener('change', this.validateField.bind(this));
});
}
initProgressTracking() {
const questions = this.form.querySelectorAll('.survey-question');
if (questions.length === 0) return;
// 跟踪已回答的问题
const updateProgress = () => {
let answeredCount = 0;
questions.forEach(question => {
const inputs = question.querySelectorAll('input, textarea, select');
let isAnswered = false;
inputs.forEach(input => {
if (input.type === 'checkbox' || input.type === 'radio') {
if (input.checked) isAnswered = true;
} else if (input.type === 'text' || input.tagName === 'TEXTAREA' || input.tagName === 'SELECT') {
if (input.value.trim() !== '') isAnswered = true;
}
});
if (isAnswered) answeredCount++;
});
// 更新进度条
const progressFill = this.form.querySelector('.progress-fill');
if (progressFill) {
const progress = (answeredCount / questions.length) * 100;
progressFill.style.width = progress + '%';
}
};
// 监听所有输入变化
this.form.addEventListener('input', updateProgress);
this.form.addEventListener('change', updateProgress);
// 初始更新
updateProgress();
}
validateField(event) {
const field = event.target;
const question = field.closest('.survey-question');
if (!question) return;
const isRequired = question.querySelector('.required-indicator') !== null;
if (isRequired) {
let isValid = true;
const questionId = question.dataset.questionId;
const questionType = question.dataset.questionType;
switch (questionType) {
case 'single_choice':
const radios = question.querySelectorAll('input[type="radio"]');
isValid = Array.from(radios).some(radio => radio.checked);
break;
case 'multiple_choice':
const checkboxes = question.querySelectorAll('input[type="checkbox"]:checked');
isValid = checkboxes.length > 0;
break;
case 'text':
isValid = field.value.trim() !== '';
break;
case 'rating':
const ratingRadios = question.querySelectorAll('input[type="radio"]');
isValid = Array.from(ratingRadios).some(radio => radio.checked);
break;
}
if (!isValid) {
question.classList.add('has-error');
} else {
question.classList.remove('has-error');
}
}
}
collectFormData() {
const formData = new FormData();
formData.append('action', 'submit_survey');
formData.append('nonce', survey_ajax.nonce);
formData.append('survey_id', this.surveyId);
const answers = {};
const questions = this.form.querySelectorAll('.survey-question');
questions.forEach(question => {
const questionId = question.dataset.questionId;
const questionType = question.dataset.questionType;
switch (questionType) {
case 'single_choice':
const selectedRadio = question.querySelector('input[type="radio"]:checked');
if (selectedRadio) {
answers[questionId] = selectedRadio.value;
}
break;
case 'multiple_choice':
const selectedCheckboxes = question.querySelectorAll('input[type="checkbox"]:checked');
const checkboxValues = Array.from(selectedCheckboxes).map(cb => cb.value);
answers[questionId] = checkboxValues;
break;
case 'text':
const textarea = question.querySelector('textarea');
if (textarea) {
answers[questionId] = textarea.value.trim();
}
break;
