首页 / 应用软件 / WordPress高级教程,开发集成在线问卷调查结果实时数据看板

WordPress高级教程,开发集成在线问卷调查结果实时数据看板

WordPress高级教程:开发集成在线问卷调查结果实时数据看板

引言:WordPress作为企业级应用开发平台

WordPress早已超越了简单的博客系统定位,如今已成为一个功能强大的内容管理系统(CMS)和应用程序框架。全球超过43%的网站使用WordPress构建,这得益于其灵活的可扩展性和庞大的开发者社区。在本教程中,我们将探索如何通过WordPress代码二次开发,实现一个专业级的在线问卷调查结果实时数据看板,展示如何将WordPress转变为功能丰富的互联网应用平台。

传统的问卷调查工具往往独立于企业网站存在,导致数据孤岛和用户体验割裂。通过将问卷调查与数据可视化看板直接集成到WordPress中,我们可以创建无缝的用户体验,同时利用WordPress的用户管理、权限控制和内容展示能力。本教程将引导您完成从需求分析到代码实现的完整过程,适合有一定WordPress开发经验的开发者。

第一部分:项目架构设计与技术选型

1.1 需求分析与功能规划

在开始编码之前,我们需要明确项目的核心需求:

  1. 问卷调查功能:支持多种题型(单选、多选、矩阵、评分等)
  2. 实时数据收集:用户提交问卷后立即更新数据存储
  3. 可视化看板:多种图表展示调查结果(柱状图、饼图、趋势图等)
  4. 权限管理:不同用户角色查看不同级别的数据
  5. 响应式设计:在桌面和移动设备上都能良好显示
  6. 数据导出:支持将结果导出为CSV、PDF等格式

1.2 技术栈选择

基于WordPress生态,我们选择以下技术方案:

  • 核心框架:WordPress 5.8+,使用自定义文章类型存储问卷和结果
  • 前端框架:Vue.js 3.0 + Chart.js,实现交互式数据可视化
  • 实时通信:REST API + WebSocket(可选,用于实时更新)
  • 数据库优化:自定义数据库表结构,提高查询效率
  • 缓存机制:Transients API + Object Cache,提升性能

1.3 系统架构设计

我们将采用分层架构设计:

表现层 (前端展示)
   ↓
业务逻辑层 (WordPress插件)
   ↓
数据访问层 (自定义数据库表+API)
   ↓
数据存储层 (MySQL + 缓存)

第二部分:创建WordPress问卷调查插件

2.1 插件基础结构

首先创建插件主文件 wp-survey-dashboard.php

<?php
/**
 * Plugin Name: WordPress问卷调查与数据看板
 * Plugin URI: https://yourwebsite.com/
 * Description: 集成在线问卷调查和实时数据看板的高级解决方案
 * Version: 1.0.0
 * Author: 您的名称
 * License: GPL v2 or later
 */

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

// 定义插件常量
define('WPSD_VERSION', '1.0.0');
define('WPSD_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WPSD_PLUGIN_URL', plugin_dir_url(__FILE__));

// 初始化插件
class WP_Survey_Dashboard {
    
    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($this, 'activate'));
        register_deactivation_hook(__FILE__, array($this, 'deactivate'));
        
        // 初始化
        add_action('init', array($this, 'init'));
        
        // 管理菜单
        add_action('admin_menu', array($this, 'add_admin_menu'));
        
        // 加载脚本和样式
        add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets'));
        add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
    }
    
    public function activate() {
        // 创建数据库表
        $this->create_database_tables();
        
        // 设置默认选项
        update_option('wpsd_version', WPSD_VERSION);
        
        // 刷新重写规则
        flush_rewrite_rules();
    }
    
    public function deactivate() {
        // 清理临时数据
        // 注意:不删除调查数据
        flush_rewrite_rules();
    }
    
    public function init() {
        // 注册自定义文章类型
        $this->register_post_types();
        
        // 注册短代码
        add_shortcode('survey_form', array($this, 'survey_form_shortcode'));
        add_shortcode('survey_dashboard', array($this, 'survey_dashboard_shortcode'));
        
        // 初始化REST API
        add_action('rest_api_init', array($this, 'register_rest_routes'));
    }
    
    // 其他方法将在后续部分实现
}

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

2.2 创建数据库表结构

activate 方法中调用的 create_database_tables 方法:

private function create_database_tables() {
    global $wpdb;
    
    $charset_collate = $wpdb->get_charset_collate();
    
    // 调查表
    $table_surveys = $wpdb->prefix . 'wpsd_surveys';
    $table_results = $wpdb->prefix . 'wpsd_results';
    $table_answers = $wpdb->prefix . 'wpsd_answers';
    
    // 创建调查表
    $sql_surveys = "CREATE TABLE IF NOT EXISTS $table_surveys (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        title VARCHAR(255) NOT NULL,
        description TEXT,
        settings LONGTEXT,
        status VARCHAR(20) DEFAULT 'draft',
        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 (status),
        KEY created_by (created_by)
    ) $charset_collate;";
    
    // 创建结果表
    $sql_results = "CREATE TABLE IF NOT EXISTS $table_results (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        survey_id BIGINT(20) UNSIGNED NOT NULL,
        user_id BIGINT(20) UNSIGNED,
        user_ip VARCHAR(45),
        user_agent TEXT,
        session_id VARCHAR(100),
        completed TINYINT(1) DEFAULT 0,
        started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        completed_at DATETIME,
        metadata LONGTEXT,
        PRIMARY KEY (id),
        KEY survey_id (survey_id),
        KEY user_id (user_id),
        KEY session_id (session_id),
        KEY completed (completed)
    ) $charset_collate;";
    
    // 创建答案表
    $sql_answers = "CREATE TABLE IF NOT EXISTS $table_answers (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        result_id BIGINT(20) UNSIGNED NOT NULL,
        question_id VARCHAR(100) NOT NULL,
        question_type VARCHAR(50) NOT NULL,
        question_text TEXT,
        answer_value LONGTEXT,
        answer_text TEXT,
        answered_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY result_id (result_id),
        KEY question_id (question_id),
        KEY question_type (question_type)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql_surveys);
    dbDelta($sql_results);
    dbDelta($sql_answers);
}

2.3 注册自定义文章类型和分类

扩展 init 方法中的 register_post_types

private function register_post_types() {
    // 注册调查自定义文章类型
    register_post_type('wpsd_survey',
        array(
            'labels' => array(
                'name' => __('问卷调查', 'wp-survey-dashboard'),
                'singular_name' => __('调查', 'wp-survey-dashboard'),
                'add_new' => __('添加新调查', 'wp-survey-dashboard'),
                'add_new_item' => __('添加新调查', 'wp-survey-dashboard'),
                'edit_item' => __('编辑调查', 'wp-survey-dashboard'),
                'new_item' => __('新调查', 'wp-survey-dashboard'),
                'view_item' => __('查看调查', 'wp-survey-dashboard'),
                'search_items' => __('搜索调查', 'wp-survey-dashboard'),
                'not_found' => __('未找到调查', 'wp-survey-dashboard'),
                'not_found_in_trash' => __('回收站中无调查', 'wp-survey-dashboard'),
            ),
            'public' => true,
            'has_archive' => true,
            'rewrite' => array('slug' => 'surveys'),
            'supports' => array('title', 'editor', 'author', 'revisions'),
            'menu_icon' => 'dashicons-chart-bar',
            'show_in_rest' => true,
            'rest_base' => 'wpsd_surveys',
            'capability_type' => 'post',
            'capabilities' => array(
                'create_posts' => 'create_wpsd_surveys',
            ),
            'map_meta_cap' => true,
        )
    );
    
    // 注册调查分类
    register_taxonomy('survey_category', 'wpsd_survey',
        array(
            'labels' => array(
                'name' => __('调查分类', 'wp-survey-dashboard'),
                'singular_name' => __('分类', 'wp-survey-dashboard'),
            ),
            'hierarchical' => true,
            'show_in_rest' => true,
            'show_admin_column' => true,
        )
    );
}

第三部分:构建问卷调查前端功能

3.1 创建问卷调查表单短代码

实现 survey_form_shortcode 方法:

public function survey_form_shortcode($atts) {
    // 提取短代码属性
    $atts = shortcode_atts(array(
        'id' => 0,
        'title' => 'yes',
        'description' => 'yes',
    ), $atts, 'survey_form');
    
    $survey_id = intval($atts['id']);
    
    if (!$survey_id) {
        return '<div class="wpsd-error">请指定有效的调查ID</div>';
    }
    
    // 获取调查数据
    global $wpdb;
    $table_surveys = $wpdb->prefix . 'wpsd_surveys';
    $survey = $wpdb->get_row($wpdb->prepare(
        "SELECT * FROM $table_surveys WHERE id = %d AND status = 'published'",
        $survey_id
    ));
    
    if (!$survey) {
        return '<div class="wpsd-error">调查不存在或未发布</div>';
    }
    
    // 解析调查设置
    $settings = json_decode($survey->settings, true);
    $questions = isset($settings['questions']) ? $settings['questions'] : array();
    
    // 生成唯一会话ID
    $session_id = $this->generate_session_id();
    
    // 输出调查表单
    ob_start();
    ?>
    <div class="wpsd-survey-container" data-survey-id="<?php echo esc_attr($survey_id); ?>">
        <div class="wpsd-survey-header">
            <?php if ($atts['title'] === 'yes') : ?>
                <h2 class="wpsd-survey-title"><?php echo esc_html($survey->title); ?></h2>
            <?php endif; ?>
            
            <?php if ($atts['description'] === 'yes' && !empty($survey->description)) : ?>
                <div class="wpsd-survey-description">
                    <?php echo wp_kses_post($survey->description); ?>
                </div>
            <?php endif; ?>
        </div>
        
        <form id="wpsd-survey-form-<?php echo esc_attr($survey_id); ?>" class="wpsd-survey-form" method="post">
            <input type="hidden" name="survey_id" value="<?php echo esc_attr($survey_id); ?>">
            <input type="hidden" name="session_id" value="<?php echo esc_attr($session_id); ?>">
            <input type="hidden" name="wpsd_nonce" value="<?php echo wp_create_nonce('wpsd_submit_survey'); ?>">
            
            <div class="wpsd-questions-container">
                <?php foreach ($questions as $index => $question) : ?>
                    <div class="wpsd-question" data-question-id="<?php echo esc_attr($question['id']); ?>" data-question-type="<?php echo esc_attr($question['type']); ?>">
                        <div class="wpsd-question-header">
                            <h3 class="wpsd-question-title">
                                <span class="wpsd-question-number"><?php echo $index + 1; ?>.</span>
                                <?php echo esc_html($question['title']); ?>
                                <?php if (isset($question['required']) && $question['required']) : ?>
                                    <span class="wpsd-required">*</span>
                                <?php endif; ?>
                            </h3>
                            
                            <?php if (!empty($question['description'])) : ?>
                                <div class="wpsd-question-description">
                                    <?php echo esc_html($question['description']); ?>
                                </div>
                            <?php endif; ?>
                        </div>
                        
                        <div class="wpsd-question-body">
                            <?php echo $this->render_question_field($question); ?>
                        </div>
                    </div>
                <?php endforeach; ?>
            </div>
            
            <div class="wpsd-form-footer">
                <button type="submit" class="wpsd-submit-button">
                    <?php _e('提交调查', 'wp-survey-dashboard'); ?>
                </button>
                <div class="wpsd-form-message"></div>
            </div>
        </form>
    </div>
    <?php
    
    return ob_get_clean();
}

private function render_question_field($question) {
    $field_html = '';
    $question_id = esc_attr($question['id']);
    $field_name = "answers[$question_id]";
    
    switch ($question['type']) {
        case 'radio':
        case 'checkbox':
            $field_html .= '<div class="wpsd-options-container">';
            foreach ($question['options'] as $option) {
                $option_id = esc_attr($option['id']);
                $input_id = "{$question_id}_{$option_id}";
                
                if ($question['type'] === 'radio') {
                    $field_html .= '<div class="wpsd-option">';
                    $field_html .= '<input type="radio" id="' . $input_id . '" name="' . $field_name . '" value="' . esc_attr($option['value']) . '">';
                    $field_html .= '<label for="' . $input_id . '">' . esc_html($option['label']) . '</label>';
                    $field_html .= '</div>';
                } else {
                    $field_html .= '<div class="wpsd-option">';
                    $field_html .= '<input type="checkbox" id="' . $input_id . '" name="' . $field_name . '[]" value="' . esc_attr($option['value']) . '">';
                    $field_html .= '<label for="' . $input_id . '">' . esc_html($option['label']) . '</label>';
                    $field_html .= '</div>';
                }
            }
            $field_html .= '</div>';
            break;
            
        case 'select':
            $field_html .= '<select name="' . $field_name . '" class="wpsd-select">';
            if (isset($question['placeholder'])) {
                $field_html .= '<option value="">' . esc_html($question['placeholder']) . '</option>';
            }
            foreach ($question['options'] as $option) {
                $field_html .= '<option value="' . esc_attr($option['value']) . '">' . esc_html($option['label']) . '</option>';
            }
            $field_html .= '</select>';
            break;
            
        case 'rating':
            $field_html .= '<div class="wpsd-rating-container" data-max-rating="' . esc_attr($question['max_rating']) . '">';
            for ($i = 1; $i <= $question['max_rating']; $i++) {
                $field_html .= '<div class="wpsd-rating-star" data-value="' . $i . '">';
                $field_html .= '<span class="wpsd-star-icon">★</span>';
                $field_html .= '</div>';
            }
            $field_html .= '<input type="hidden" name="' . $field_name . '" value="">';
            $field_html .= '</div>';
            break;
            
        case 'text':
        case 'textarea':
        case 'email':
            $input_type = $question['type'] === 'textarea' ? 'textarea' : 'text';
            $input_type_attr = $question['type'] === 'email' ? 'email' : 'text';
            
            if ($input_type === 'textarea') {
                $field_html .= '<textarea name="' . $field_name . '" class="wpsd-textarea"';
                if (isset($question['placeholder'])) {
                    $field_html .= ' placeholder="' . esc_attr($question['placeholder']) . '"';
                }
                if (isset($question['rows'])) {
                    $field_html .= ' rows="' . esc_attr($question['rows']) . '"';
                }
                $field_html .= '></textarea>';
            } else {
                $field_html .= '<input type="' . $input_type_attr . '" name="' . $field_name . '" class="wpsd-input"';
                if (isset($question['placeholder'])) {
                    $field_html .= ' placeholder="' . esc_attr($question['placeholder']) . '"';
                }
                if (isset($question['maxlength'])) {
                    $field_html .= ' maxlength="' . esc_attr($question['maxlength']) . '"';
                }
                $field_html .= '>';
            }
            break;
            
        case 'matrix':
            $field_html .= '<div class="wpsd-matrix-container">';
            $field_html .= '<table class="wpsd-matrix-table">';
            $field_html .= '<thead><tr><th></th>';
            foreach ($question['columns'] as $column) {
                $field_html .= '<th>' . esc_html($column['label']) . '</th>';
            }
            $field_html .= '</tr></thead>';
            $field_html .= '<tbody>';
            foreach ($question['rows'] as $row) {
                $field_html .= '<tr>';
                $field_html .= '<td class="wpsd-matrix-row-label">' . esc_html($row['label']) . '</td>';
                foreach ($question['columns'] as $column) {
                    $input_id = "{$question_id}_{$row['id']}_{$column['id']}";
                    $field_html .= '<td class="wpsd-matrix-cell">';
                    $field_html .= '<input type="radio" id="' . $input_id . '" name="answers[' . $question_id . '][' . $row['id'] . ']" value="' . esc_attr($column['value']) . '">';
                    $field_html .= '<label for="' . $input_id . '"></label>';
                    $field_html .= '</td>';
                }
                $field_html .= '</tr>';
            }
            $field_html .= '</tbody></table></div>';
            break;
            
        default:
            $field_html .= '<p>不支持的问题类型: ' . esc_html($question['type']) . '</p>';
    }
    
    return $field_html;
}

private function generate_session_id() {
    if (isset($_COOKIE['wpsd_session_id'])) {
        return sanitize_text_field($_COOKIE['wpsd_session_id']);
    }
    
    $session_id = wp_generate_uuid4();
    setcookie('wpsd_session_id', $session_id, time() + 3600 * 24 * 7, COOKIEPATH, COOKIE_DOMAIN);
    return $session_id;
}

3.2 前端脚本和样式

实现前端资源加载方法:

public function enqueue_frontend_assets() {
    // 只在需要时加载
    global $post;
    if (is_a($post, 'WP_Post') && (has_shortcode($post->post_content, 'survey_form') || has_shortcode($post->post_content, 'survey_dashboard'))) {
        
        // 加载Vue.js
        wp_enqueue_script('vue-js', 'https://cdn.jsdelivr.net/npm/vue@3.2.31/dist/vue.global.prod.js', array(), '3.2.31', true);
        
        // 加载Chart.js
        wp_enqueue_script('chart-js', 'https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js', array(), '3.7.1', true);
        
        // 加载插件主脚本
        wp_enqueue_script(
            'wpsd-frontend',
            WPSD_PLUGIN_URL . 'assets/js/frontend.js',
            array('jquery', 'vue-js', 'chart-js'),
            WPSD_VERSION,
            true
        );
        
        // 本地化脚本
        wp_localize_script('wpsd-frontend', 'wpsd_ajax', array(
            'ajax_url' => admin_url('admin-ajax.php'),
            'rest_url' => rest_url('wpsd/v1/'),
            'nonce' => wp_create_nonce('wpsd_ajax_nonce')
        ));
        
        // 加载样式
        wp_enqueue_style(
            'wpsd-frontend-style',
            WPSD_PLUGIN_URL . 'assets/css/frontend.css',
            array(),
            WPSD_VERSION
        );
    }
}

创建前端JavaScript文件 assets/js/frontend.js

(function($) {
    'use strict';
    
    // 问卷调查提交处理
    $(document).on('submit', '.wpsd-survey-form', function(e) {
        e.preventDefault();
        
        const $form = $(this);
        const $submitBtn = $form.find('.wpsd-submit-button');
        const $message = $form.find('.wpsd-form-message');
        const formData = new FormData(this);
        
        // 禁用提交按钮
        $submitBtn.prop('disabled', true).text('提交中...');
        $message.removeClass('wpsd-success wpsd-error').text('');
        
        // 验证必填字段
        const requiredFields = $form.find('.wpsd-required');
        let isValid = true;
        
        requiredFields.each(function() {
            const $question = $(this).closest('.wpsd-question');
            const questionType = $question.data('question-type');
            const questionId = $question.data('question-id');
            
            let isAnswered = false;
            
            if (questionType === 'radio' || questionType === 'select') {
                isAnswered = $form.find(`[name="answers[${questionId}]"]:checked`).length > 0 || 
                            $form.find(`[name="answers[${questionId}]"]`).val() !== '';
            } else if (questionType === 'checkbox') {
                isAnswered = $form.find(`[name="answers[${questionId}][]"]:checked`).length > 0;
            } else if (questionType === 'matrix') {
                const rowCount = $question.find('.wpsd-matrix-row-label').length;
                const answeredRows = $question.find('input[type="radio"]:checked').length;
                isAnswered = answeredRows === rowCount;
            } else {
                isAnswered = $form.find(`[name="answers[${questionId}]"]`).val().trim() !== '';
            }
            
            if (!isAnswered) {
                $question.addClass('wpsd-error');
                isValid = false;
            } else {
                $question.removeClass('wpsd-error');
            }
        });
        
        if (!isValid) {
            $message.addClass('wpsd-error').text('请填写所有必填问题');
            $submitBtn.prop('disabled', false).text('提交调查');
            return;
        }
        
        // 发送AJAX请求
        $.ajax({
            url: wpsd_ajax.ajax_url,
            type: 'POST',
            data: formData,
            processData: false,
            contentType: false,
            success: function(response) {
                if (response.success) {
                    $message.addClass('wpsd-success').text(response.data.message);
                    $form[0].reset();
                    
                    // 更新评分控件
                    $form.find('.wpsd-rating-container').each(function() {
                        $(this).find('.wpsd-rating-star').removeClass('active');
                        $(this).find('input[type="hidden"]').val('');
                    });
                    
                    // 显示感谢信息
                    setTimeout(function() {
                        $form.html(`
                            <div class="wpsd-thank-you">
                                <h3>感谢参与!</h3>
                                <p>您的反馈对我们非常重要。</p>
                                <p>您可以在调查结果页面查看实时统计数据。</p>
                            </div>
                        `);
                    }, 2000);
                } else {
                    $message.addClass('wpsd-error').text(response.data.message);
                    $submitBtn.prop('disabled', false).text('提交调查');
                }
            },
            error: function() {
                $message.addClass('wpsd-error').text('提交失败,请稍后重试');
                $submitBtn.prop('disabled', false).text('提交调查');
            }
        });
    });
    
    // 评分控件交互
    $(document).on('mouseenter', '.wpsd-rating-star', function() {
        const $container = $(this).closest('.wpsd-rating-container');
        const value = $(this).data('value');
        
        $container.find('.wpsd-rating-star').each(function() {
            if ($(this).data('value') <= value) {
                $(this).addClass('hover');
            } else {
                $(this).removeClass('hover');
            }
        });
    });
    
    $(document).on('mouseleave', '.wpsd-rating-container', function() {
        $(this).find('.wpsd-rating-star').removeClass('hover');
    });
    
    $(document).on('click', '.wpsd-rating-star', function() {
        const $container = $(this).closest('.wpsd-rating-container');
        const value = $(this).data('value');
        const $hiddenInput = $container.find('input[type="hidden"]');
        
        $container.find('.wpsd-rating-star').removeClass('active hover');
        $container.find('.wpsd-rating-star').each(function() {
            if ($(this).data('value') <= value) {
                $(this).addClass('active');
            }
        });
        
        $hiddenInput.val(value);
    });
    
    // 数据看板Vue应用
    if (typeof Vue !== 'undefined' && $('.wpsd-dashboard-container').length > 0) {
        const { createApp } = Vue;
        
        const DashboardApp = {
            data() {
                return {
                    surveyId: 0,
                    loading: true,
                    error: null,
                    surveyData: null,
                    chartData: {},
                    filters: {
                        dateRange: 'all',
                        startDate: '',
                        endDate: '',
                        questionFilter: 'all'
                    },
                    realTime: false,
                    refreshInterval: null
                };
            },
            
            mounted() {
                const container = document.querySelector('.wpsd-dashboard-container');
                this.surveyId = container.dataset.surveyId;
                this.realTime = container.dataset.realtime === 'true';
                
                this.loadSurveyData();
                
                if (this.realTime) {
                    this.startRealTimeUpdates();
                }
            },
            
            methods: {
                async loadSurveyData() {
                    this.loading = true;
                    this.error = null;
                    
                    try {
                        const response = await fetch(
                            `${wpsd_ajax.rest_url}survey/${this.surveyId}/results?` + 
                            new URLSearchParams(this.filters)
                        );
                        
                        if (!response.ok) {
                            throw new Error('获取数据失败');
                        }
                        
                        const data = await response.json();
                        this.surveyData = data;
                        this.prepareChartData();
                    } catch (err) {
                        this.error = err.message;
                        console.error('加载调查数据失败:', err);
                    } finally {
                        this.loading = false;
                    }
                },
                
                prepareChartData() {
                    if (!this.surveyData || !this.surveyData.questions) return;
                    
                    this.chartData = {};
                    
                    this.surveyData.questions.forEach(question => {
                        if (question.type === 'radio' || question.type === 'select' || question.type === 'checkbox') {
                            this.chartData[question.id] = this.createPieChartData(question);
                        } else if (question.type === 'rating') {
                            this.chartData[question.id] = this.createBarChartData(question);
                        } else if (question.type === 'matrix') {
                            this.chartData[question.id] = this.createMatrixChartData(question);
                        }
                    });
                    
                    // 渲染图表
                    this.$nextTick(() => {
                        this.renderCharts();
                    });
                },
                
                createPieChartData(question) {
                    const labels = [];
                    const data = [];
                    const backgroundColors = [];
                    
                    question.options.forEach((option, index) => {
                        labels.push(option.label);
                        data.push(option.count || 0);
                        backgroundColors.push(this.getColor(index));
                    });
                    
                    return {
                        type: 'pie',
                        data: {
                            labels: labels,
                            datasets: [{
                                data: data,
                                backgroundColor: backgroundColors,
                                borderWidth: 1
                            }]
                        },
                        options: {
                            responsive: true,
                            plugins: {
                                legend: {
                                    position: 'right'
                                },
                                title: {
                                    display: true,
                                    text: question.title
                                }
                            }
                        }
                    };
                },
                
                createBarChartData(question) {
                    const labels = [];
                    const data = [];
                    
                    for (let i = 1; i <= question.max_rating; i++) {
                        labels.push(`${i}星`);
                        const count = question.ratings ? (question.ratings[i] || 0) : 0;
                        data.push(count);
                    }
                    
                    return {
                        type: 'bar',
                        data: {
                            labels: labels,
                            datasets: [{
                                label: '评分分布',
                                data: data,
                                backgroundColor: 'rgba(54, 162, 235, 0.5)',
                                borderColor: 'rgba(54, 162, 235, 1)',
                                borderWidth: 1
                            }]
                        },
                        options: {
                            responsive: true,
                            scales: {
                                y: {
                                    beginAtZero: true,
                                    ticks: {
                                        stepSize: 1
                                    }
                                }
                            },
                            plugins: {
                                title: {
                                    display: true,
                                    text: question.title
                                }
                            }
                        }
                    };
                },
                
                createMatrixChartData(question) {
                    const labels = question.rows.map(row => row.label);
                    const datasets = [];
                    
                    question.columns.forEach((column, colIndex) => {
                        const data = question.rows.map(row => {
                            const cell = question.matrix_data?.find(
                                d => d.row_id === row.id && d.column_id === column.id
                            );
                            return cell ? cell.count : 0;
                        });
                        
                        datasets.push({
                            label: column.label,
                            data: data,
                            backgroundColor: this.getColor(colIndex, 0.5),
                            borderColor: this.getColor(colIndex, 1),
                            borderWidth: 1
                        });
                    });
                    
                    return {
                        type: 'bar',
                        data: {
                            labels: labels,
                            datasets: datasets
                        },
                        options: {
                            responsive: true,
                            scales: {
                                x: {
                                    stacked: true
                                },
                                y: {
                                    stacked: true,
                                    beginAtZero: true
                                }
                            },
                            plugins: {
                                title: {
                                    display: true,
                                    text: question.title
                                }
                            }
                        }
                    };
                },
                
                renderCharts() {
                    Object.keys(this.chartData).forEach(questionId => {
                        const canvas = document.getElementById(`chart-${questionId}`);
                        if (!canvas) return;
                        
                        const ctx = canvas.getContext('2d');
                        const chartConfig = this.chartData[questionId];
                        
                        // 销毁现有图表实例
                        if (canvas.chart) {
                            canvas.chart.destroy();
                        }
                        
                        // 创建新图表
                        canvas.chart = new Chart(ctx, chartConfig);
                    });
                },
                
                getColor(index, alpha = 1) {
                    const colors = [
                        `rgba(255, 99, 132, ${alpha})`,
                        `rgba(54, 162, 235, ${alpha})`,
                        `rgba(255, 206, 86, ${alpha})`,
                        `rgba(75, 192, 192, ${alpha})`,
                        `rgba(153, 102, 255, ${alpha})`,
                        `rgba(255, 159, 64, ${alpha})`,
                        `rgba(199, 199, 199, ${alpha})`,
                        `rgba(83, 102, 255, ${alpha})`,
                        `rgba(40, 159, 64, ${alpha})`,
                        `rgba(210, 199, 199, ${alpha})`
                    ];
                    
                    return colors[index % colors.length];
                },
                
                applyFilters() {
                    this.loadSurveyData();
                },
                
                resetFilters() {
                    this.filters = {
                        dateRange: 'all',
                        startDate: '',
                        endDate: '',
                        questionFilter: 'all'
                    };
                    this.loadSurveyData();
                },
                
                startRealTimeUpdates() {
                    // 每30秒更新一次数据
                    this.refreshInterval = setInterval(() => {
                        this.loadSurveyData();
                    }, 30000);
                },
                
                stopRealTimeUpdates() {
                    if (this.refreshInterval) {
                        clearInterval(this.refreshInterval);
                        this.refreshInterval = null;
                    }
                },
                
                exportData(format) {
                    const url = `${wpsd_ajax.rest_url}survey/${this.surveyId}/export?format=${format}`;
                    window.open(url, '_blank');
                }
            },
            
            beforeUnmount() {
                this.stopRealTimeUpdates();
            },
            
            template: `
                <div class="wpsd-dashboard">
                    <div v-if="loading" class="wpsd-loading">
                        加载中...
                    </div>
                    
                    <div v-else-if="error" class="wpsd-error">
                        {{ error }}
                    </div>
                    
                    <div v-else>
                        <!-- 控制面板 -->
                        <div class="wpsd-dashboard-controls">
                            <div class="wpsd-filters">
                                <div class="wpsd-filter-group">
                                    <label>时间范围:</label>
                                    <select v-model="filters.dateRange" @change="applyFilters">
                                        <option value="all">全部时间</option>
                                        <option value="today">今天</option>
                                        <option value="yesterday">昨天</option>
                                        <option value="week">本周</option>
                                        <option value="month">本月</option>
                                        <option value="custom">自定义</option>
                                    </select>
                                </div>
                                
                                <div v-if="filters.dateRange === 'custom'" class="wpsd-filter-group">
本文来自网络,不代表柔性供应链服务中心立场,转载请注明出处:https://mall.org.cn/5262.html

EXCHANGES®作者

上一篇
下一篇

为您推荐

发表回复

联系我们

联系我们

18559313275

在线咨询: QQ交谈

邮箱: vip@exchanges.center

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