首页 / 应用软件 / 一步步实现,在WordPress中添加实时数据大屏与可视化图表

一步步实现,在WordPress中添加实时数据大屏与可视化图表

一步步实现:在WordPress中添加实时数据大屏与可视化图表

引言:为什么WordPress需要实时数据可视化功能

在当今数据驱动的互联网时代,实时数据可视化已成为网站运营和内容展示的重要组成部分。WordPress作为全球最流行的内容管理系统,虽然拥有强大的内容管理能力,但在原生功能上却缺乏专业的数据可视化工具。许多网站运营者、电商店主和内容创作者都需要在自己的WordPress站点上展示实时数据,如网站流量统计、销售数据、用户行为分析等。

传统上,用户可能需要依赖第三方服务或插件来实现这些功能,但这往往带来数据安全、加载速度和定制灵活性等问题。通过WordPress代码二次开发,我们可以创建完全可控、高度定制化的实时数据大屏和可视化图表,不仅能提升网站的专业形象,还能为访客提供更有价值的数据洞察。

本文将详细介绍如何通过代码开发,在WordPress中实现实时数据大屏与可视化图表功能,涵盖从环境准备到最终部署的完整流程。

第一章:开发环境准备与项目规划

1.1 开发环境搭建

在开始开发之前,我们需要准备合适的开发环境。建议使用本地开发环境,如Local by Flywheel、XAMPP或MAMP,这样可以避免对生产站点造成影响。

首先,确保你的开发环境满足以下要求:

  • PHP 7.4或更高版本
  • MySQL 5.6或更高版本
  • WordPress 5.8或更高版本
  • 支持HTTPS(用于某些API调用)

接下来,创建一个专门用于开发的WordPress主题或插件目录。考虑到功能的独立性,我们建议将其开发为一个独立插件,这样可以在不同主题间保持功能一致性。

<?php
/**
 * Plugin Name: 实时数据大屏与可视化工具
 * Plugin URI: https://yourwebsite.com/
 * Description: 为WordPress添加实时数据大屏和可视化图表功能
 * Version: 1.0.0
 * Author: Your Name
 * License: GPL v2 or later
 */

1.2 项目架构设计

良好的项目架构是成功开发的关键。我们建议采用模块化设计,将功能分解为以下几个核心模块:

  1. 数据获取模块:负责从各种数据源收集数据
  2. 数据处理模块:对原始数据进行清洗、转换和聚合
  3. 图表渲染模块:使用JavaScript库生成可视化图表
  4. 前端展示模块:创建数据大屏的界面布局
  5. 管理后台模块:提供配置选项和管理界面

目录结构建议如下:

real-time-dashboard/
├── includes/
│   ├── class-data-fetcher.php
│   ├── class-data-processor.php
│   ├── class-chart-renderer.php
│   └── class-admin-settings.php
├── assets/
│   ├── css/
│   ├── js/
│   └── images/
├── templates/
│   ├── dashboard-template.php
│   └── widget-template.php
└── real-time-dashboard.php

1.3 技术选型与工具准备

对于数据可视化,我们需要选择合适的JavaScript图表库。以下是几个优秀的选择:

  1. Chart.js:轻量级、易于使用,适合基础图表
  2. ECharts:功能强大、图表类型丰富,由百度开发
  3. D3.js:最灵活、功能最强大,但学习曲线较陡
  4. ApexCharts:现代、交互式图表库

考虑到易用性和功能平衡,本文选择ECharts作为主要图表库。同时,我们还需要以下工具:

  • 用于实时数据更新的WebSocket或Server-Sent Events
  • 用于数据缓存的Transients API或Redis
  • 用于REST API开发WordPress的REST API功能

第二章:数据获取与处理模块开发

2.1 创建数据获取类

数据获取是实时数据大屏的基础。我们需要创建一个专门的数据获取类,负责从不同数据源收集信息。

<?php
class RealTime_Data_Fetcher {
    
    private $data_sources;
    
    public function __construct() {
        $this->data_sources = array(
            'website_traffic' => array(
                'name' => '网站流量',
                'type' => 'internal',
                'callback' => array($this, 'fetch_website_traffic')
            ),
            'woocommerce_sales' => array(
                'name' => '销售数据',
                'type' => 'woocommerce',
                'callback' => array($this, 'fetch_woocommerce_sales')
            ),
            'external_api' => array(
                'name' => '外部API数据',
                'type' => 'external',
                'callback' => array($this, 'fetch_external_data')
            )
        );
    }
    
    /**
     * 获取网站流量数据
     */
    public function fetch_website_traffic($time_range = 'today') {
        global $wpdb;
        
        $data = array();
        $current_time = current_time('timestamp');
        
        switch($time_range) {
            case 'today':
                $start_time = strtotime('today midnight');
                break;
            case 'week':
                $start_time = strtotime('-7 days');
                break;
            case 'month':
                $start_time = strtotime('-30 days');
                break;
            default:
                $start_time = strtotime('-24 hours');
        }
        
        // 查询页面访问数据
        $query = $wpdb->prepare(
            "SELECT COUNT(*) as count, DATE_FORMAT(visit_time, '%%H:00') as hour 
             FROM {$wpdb->prefix}page_visits 
             WHERE visit_time >= %s 
             GROUP BY HOUR(visit_time) 
             ORDER BY visit_time",
            date('Y-m-d H:i:s', $start_time)
        );
        
        $results = $wpdb->get_results($query);
        
        foreach($results as $row) {
            $data[] = array(
                'time' => $row->hour,
                'visits' => (int)$row->count
            );
        }
        
        return $data;
    }
    
    /**
     * 获取WooCommerce销售数据
     */
    public function fetch_woocommerce_sales($time_range = 'today') {
        if (!class_exists('WooCommerce')) {
            return array();
        }
        
        $data = array();
        
        // 根据时间范围获取订单数据
        $args = array(
            'status' => array('completed', 'processing'),
            'date_created' => '>' . (time() - 24*60*60), // 最近24小时
            'limit' => -1
        );
        
        $orders = wc_get_orders($args);
        
        $hourly_sales = array();
        foreach($orders as $order) {
            $order_time = $order->get_date_created()->getTimestamp();
            $hour = date('H:00', $order_time);
            $total = $order->get_total();
            
            if (!isset($hourly_sales[$hour])) {
                $hourly_sales[$hour] = 0;
            }
            
            $hourly_sales[$hour] += $total;
        }
        
        ksort($hourly_sales);
        
        foreach($hourly_sales as $hour => $sales) {
            $data[] = array(
                'time' => $hour,
                'sales' => $sales
            );
        }
        
        return $data;
    }
    
    /**
     * 获取所有数据源的数据
     */
    public function fetch_all_data() {
        $all_data = array();
        
        foreach($this->data_sources as $key => $source) {
            if (is_callable($source['callback'])) {
                $all_data[$key] = call_user_func($source['callback']);
            }
        }
        
        return $all_data;
    }
}

2.2 实现数据缓存机制

为了避免频繁查询数据库,我们需要实现数据缓存机制。WordPress提供了Transients API,可以方便地实现临时数据存储。

class RealTime_Data_Processor {
    
    private $cache_expiration = 300; // 5分钟缓存
    
    /**
     * 处理并缓存数据
     */
    public function process_and_cache_data($data_key, $raw_data) {
        // 数据处理逻辑
        $processed_data = $this->process_data($raw_data);
        
        // 设置缓存
        set_transient(
            'rtd_' . $data_key, 
            $processed_data, 
            $this->cache_expiration
        );
        
        return $processed_data;
    }
    
    /**
     * 数据处理逻辑
     */
    private function process_data($raw_data) {
        $processed = array();
        
        // 数据清洗和转换
        foreach($raw_data as $item) {
            // 移除空值
            $cleaned_item = array_filter($item, function($value) {
                return $value !== null && $value !== '';
            });
            
            // 数据格式化
            if (!empty($cleaned_item)) {
                $processed[] = $this->format_data($cleaned_item);
            }
        }
        
        // 数据聚合(如果需要)
        if (count($processed) > 100) {
            $processed = $this->aggregate_data($processed);
        }
        
        return $processed;
    }
    
    /**
     * 数据格式化
     */
    private function format_data($data) {
        // 根据数据类型进行格式化
        foreach($data as $key => $value) {
            if (is_numeric($value)) {
                $data[$key] = floatval($value);
            } elseif (is_string($value)) {
                $data[$key] = sanitize_text_field($value);
            }
        }
        
        return $data;
    }
}

2.3 创建REST API端点

为了前端能够获取数据,我们需要创建REST API端点。

class RealTime_Dashboard_API {
    
    public function __construct() {
        add_action('rest_api_init', array($this, 'register_routes'));
    }
    
    public function register_routes() {
        // 获取所有数据
        register_rest_route('real-time-dashboard/v1', '/data', array(
            'methods' => 'GET',
            'callback' => array($this, 'get_all_data'),
            'permission_callback' => array($this, 'check_permissions')
        ));
        
        // 获取特定数据源的数据
        register_rest_route('real-time-dashboard/v1', '/data/(?P<source>[a-zA-Z0-9_-]+)', array(
            'methods' => 'GET',
            'callback' => array($this, 'get_source_data'),
            'permission_callback' => array($this, 'check_permissions')
        ));
    }
    
    public function get_all_data($request) {
        $fetcher = new RealTime_Data_Fetcher();
        $processor = new RealTime_Data_Processor();
        
        $all_data = array();
        $sources = array('website_traffic', 'woocommerce_sales');
        
        foreach($sources as $source) {
            $raw_data = $fetcher->fetch_data_by_source($source);
            $processed_data = $processor->process_and_cache_data($source, $raw_data);
            $all_data[$source] = $processed_data;
        }
        
        return rest_ensure_response($all_data);
    }
    
    public function check_permissions($request) {
        // 检查用户权限,可以根据需要调整
        return current_user_can('edit_posts');
    }
}

第三章:前端可视化界面开发

3.1 创建数据大屏基础布局

首先,我们需要创建一个响应式的大屏布局,确保在不同设备上都能良好显示。

<!-- templates/dashboard-template.php -->
<div class="real-time-dashboard-container">
    <header class="dashboard-header">
        <h1>实时数据大屏</h1>
        <div class="dashboard-controls">
            <select id="timeRangeSelect" class="dashboard-select">
                <option value="realtime">实时</option>
                <option value="today">今日</option>
                <option value="week">本周</option>
                <option value="month">本月</option>
            </select>
            <button id="refreshBtn" class="dashboard-button">
                <span class="refresh-icon">↻</span> 刷新
            </button>
            <button id="fullscreenBtn" class="dashboard-button">
                <span class="fullscreen-icon">⛶</span> 全屏
            </button>
        </div>
    </header>
    
    <div class="dashboard-grid">
        <!-- 图表容器将通过JavaScript动态生成 -->
        <div class="grid-item" data-chart-type="line" data-source="website_traffic">
            <div class="chart-header">
                <h3>网站实时访问量</h3>
                <div class="chart-stats">
                    <span class="current-value">0</span>
                    <span class="change-indicator"></span>
                </div>
            </div>
            <div class="chart-container" id="trafficChart"></div>
        </div>
        
        <div class="grid-item" data-chart-type="bar" data-source="woocommerce_sales">
            <div class="chart-header">
                <h3>实时销售数据</h3>
                <div class="chart-stats">
                    <span class="current-value">¥0</span>
                    <span class="change-indicator"></span>
                </div>
            </div>
            <div class="chart-container" id="salesChart"></div>
        </div>
        
        <!-- 更多图表容器... -->
    </div>
    
    <div class="dashboard-footer">
        <div class="last-updated">
            最后更新: <span id="lastUpdatedTime">--:--:--</span>
        </div>
        <div class="data-source-info">
            数据每5分钟自动更新
        </div>
    </div>
</div>

3.2 引入ECharts并初始化图表

接下来,我们需要引入ECharts库并创建图表初始化函数。

// assets/js/dashboard-main.js
(function($) {
    'use strict';
    
    class RealTimeDashboard {
        constructor() {
            this.charts = {};
            this.dataCache = {};
            this.updateInterval = 300000; // 5分钟
            this.isFullscreen = false;
            this.timeRange = 'realtime';
            
            this.init();
        }
        
        init() {
            this.loadECharts();
            this.initCharts();
            this.bindEvents();
            this.startAutoUpdate();
            this.fetchInitialData();
        }
        
        loadECharts() {
            // 动态加载ECharts库
            if (typeof echarts === 'undefined') {
                const script = document.createElement('script');
                script.src = 'https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js';
                script.onload = () => this.onEChartsLoaded();
                document.head.appendChild(script);
            } else {
                this.onEChartsLoaded();
            }
        }
        
        onEChartsLoaded() {
            console.log('ECharts loaded successfully');
            this.echarts = echarts;
            this.initCharts();
        }
        
        initCharts() {
            if (!this.echarts) return;
            
            // 初始化所有图表容器
            $('.chart-container').each((index, container) => {
                const chartId = $(container).attr('id');
                const chartType = $(container).closest('.grid-item').data('chart-type');
                const dataSource = $(container).closest('.grid-item').data('source');
                
                this.charts[chartId] = this.echarts.init(container);
                this.setChartOption(chartId, chartType, dataSource);
            });
            
            // 监听窗口大小变化,重新调整图表大小
            $(window).on('resize', () => {
                this.resizeCharts();
            });
        }
        
        setChartOption(chartId, chartType, dataSource) {
            const baseOption = {
                tooltip: {
                    trigger: 'axis',
                    formatter: function(params) {
                        let result = `${params[0].axisValue}<br/>`;
                        params.forEach(param => {
                            result += `${param.marker} ${param.seriesName}: ${param.value}<br/>`;
                        });
                        return result;
                    }
                },
                grid: {
                    left: '3%',
                    right: '4%',
                    bottom: '3%',
                    top: '15%',
                    containLabel: true
                },
                xAxis: {
                    type: 'category',
                    boundaryGap: false,
                    data: []
                },
                yAxis: {
                    type: 'value'
                },
                series: []
            };
            
            // 根据图表类型设置不同选项
            switch(chartType) {
                case 'line':
                    baseOption.series.push({
                        name: '数据',
                        type: 'line',
                        smooth: true,
                        data: [],
                        itemStyle: {
                            color: '#5470c6'
                        },
                        areaStyle: {
                            color: new this.echarts.graphic.LinearGradient(0, 0, 0, 1, [
                                { offset: 0, color: 'rgba(84, 112, 198, 0.5)' },
                                { offset: 1, color: 'rgba(84, 112, 198, 0.1)' }
                            ])
                        }
                    });
                    break;
                    
                case 'bar':
                    baseOption.series.push({
                        name: '数据',
                        type: 'bar',
                        data: [],
                        itemStyle: {
                            color: new this.echarts.graphic.LinearGradient(0, 0, 0, 1, [
                                { offset: 0, color: '#83bff6' },
                                { offset: 0.5, color: '#188df0' },
                                { offset: 1, color: '#188df0' }
                            ])
                        }
                    });
                    break;
                    
                // 可以添加更多图表类型
            }
            
            this.charts[chartId].setOption(baseOption);
        }
        
        fetchInitialData() {
            this.fetchData();
        }
        
        fetchData() {
            const self = this;
            
            $.ajax({
                url: '/wp-json/real-time-dashboard/v1/data',
                method: 'GET',
                dataType: 'json',
                beforeSend: function(xhr) {
                    xhr.setRequestHeader('X-WP-Nonce', realTimeDashboard.nonce);
                },
                success: function(response) {
                    self.processData(response);
                    self.updateLastUpdatedTime();
                },
                error: function(xhr, status, error) {
                    console.error('数据获取失败:', error);
                    self.showErrorMessage('数据加载失败,请稍后重试');
                }
            });
        }
        
        processData(data) {
            // 处理网站流量数据
            if (data.website_traffic && this.charts.trafficChart) {
                this.updateChartData('trafficChart', data.website_traffic, '访问量');
                this.updateStats('trafficChart', data.website_traffic);
            }
            
            // 处理销售数据
            if (data.woocommerce_sales && this.charts.salesChart) {
                this.updateChartData('salesChart', data.woocommerce_sales, '销售额');
                this.updateStats('salesChart', data.woocommerce_sales);
            }
            
            // 可以添加更多数据处理逻辑
        }
        
        updateChartData(chartId, data, seriesName) {
            const chart = this.charts[chartId];
            if (!chart) return;
            
            const xData = [];
            const yData = [];
            
            data.forEach(item => {
                xData.push(item.time || item.date);
                yData.push(item.visits || item.sales || item.value);
            });
            
            const option = chart.getOption();
            option.xAxis[0].data = xData;
            option.series[0].data = yData;
            option.series[0].name = seriesName;
            
            chart.setOption(option);
        }
        
        updateStats(chartId, data) {
            if (!data || data.length === 0) return;
            
            const currentValue = data[data.length - 1].visits || 
                                data[data.length - 1].sales || 
                                data[data.length - 1].value;
            const previousValue = data.length > 1 ? 
                                 data[data.length - 2].visits || 
                                 data[data.length - 2].sales || 
                                 data[data.length - 2].value : 0;
            
            const change = previousValue > 0 ? 
                          ((currentValue - previousValue) / previousValue * 100).toFixed(1) : 0;
            
            const $gridItem = $(`#${chartId}`).closest('.grid-item');
            const $currentValue = $gridItem.find('.current-value');
            const $changeIndicator = $gridItem.find('.change-indicator');
            
            // 更新当前值
            $currentValue.text(this.formatValue(currentValue, chartId));
            
            // 更新变化指示器
            if (change > 0) {
                $changeIndicator.html(`<span class="increase">↑ ${change}%</span>`);
                $changeIndicator.removeClass('decrease').addClass('increase');
            } else if (change < 0) {
                $changeIndicator.html(`<span class="decrease">↓ ${Math.abs(change)}%</span>`);
                $changeIndicator.removeClass('increase').addClass('decrease');
            } else {
                $changeIndicator.html('<span class="neutral">→ 0%</span>');
                $changeIndicator.removeClass('increase decrease');
            }
        }
        
        formatValue(value, chartId) {
            if (chartId === 'salesChart') {
                return '¥' + Number(value).toLocaleString('zh-CN', {
                    minimumFractionDigits: 2,
                    maximumFractionDigits: 2
                });
            }
            return Number(value).toLocaleString('zh-CN');
        }
        
        bindEvents() {
            const self = this;
            
            // 刷新按钮
            $('#refreshBtn').on('click', function() {
                self.fetchData();
                $(this).addClass('refreshing');
                setTimeout(() => {
                    $(this).removeClass('refreshing');
                }, 1000);
            });
            
            // 时间范围选择
            $('#timeRangeSelect').on('change', function() {
                self.timeRange = $(this).val();
                self.fetchData();
            });
            
            // 全屏按钮
            $('#fullscreenBtn').on('click', function() {
                self.toggleFullscreen();
            });
            
            // 键盘快捷键
            $(document).on('keydown', function(e) {
                if (e.key === 'F11' || (e.key === 'f' && e.ctrlKey)) {
                    e.preventDefault();
                    self.toggleFullscreen();
                } else if (e.key === 'r' && e.ctrlKey) {
                    e.preventDefault();
                    self.fetchData();
                }
            });
        }
        
        toggleFullscreen() {
            const container = $('.real-time-dashboard-container')[0];
            
            if (!this.isFullscreen) {
                if (container.requestFullscreen) {
                    container.requestFullscreen();
                } else if (container.webkitRequestFullscreen) {
                    container.webkitRequestFullscreen();
                } else if (container.msRequestFullscreen) {
                    container.msRequestFullscreen();
                }
                this.isFullscreen = true;
                $('#fullscreenBtn').html('<span class="exit-fullscreen-icon">⛶</span> 退出全屏');
            } else {
                if (document.exitFullscreen) {
                    document.exitFullscreen();
                } else if (document.webkitExitFullscreen) {
                    document.webkitExitFullscreen();
                } else if (document.msExitFullscreen) {
                    document.msExitFullscreen();
                }
                this.isFullscreen = false;
                $('#fullscreenBtn').html('<span class="fullscreen-icon">⛶</span> 全屏');
            }
        }
        
        resizeCharts() {
            Object.values(this.charts).forEach(chart => {
                chart.resize();
            });
        }
        
        startAutoUpdate() {
            setInterval(() => {
                this.fetchData();
            }, this.updateInterval);
        }
        
        updateLastUpdatedTime() {
            const now = new Date();
            const timeString = now.toLocaleTimeString('zh-CN', {
                hour12: false,
                hour: '2-digit',
                minute: '2-digit',
                second: '2-digit'
            });
            $('#lastUpdatedTime').text(timeString);
        }
        
        showErrorMessage(message) {
            // 显示错误提示
            const $errorDiv = $('<div class="dashboard-error">')
                .text(message)
                .hide()
                .appendTo('.dashboard-header');
            
            $errorDiv.fadeIn(300);
            setTimeout(() => {
                $errorDiv.fadeOut(300, function() {
                    $(this).remove();
                });
            }, 5000);
        }
    }
    
    // 初始化仪表板
    $(document).ready(function() {
        if ($('.real-time-dashboard-container').length) {
            window.realTimeDashboardApp = new RealTimeDashboard();
        }
    });
    
})(jQuery);

3.3 添加CSS样式

/* assets/css/dashboard-style.css */
.real-time-dashboard-container {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
    color: #ffffff;
    min-height: 100vh;
    padding: 20px;
    border-radius: 12px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}

.dashboard-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 30px;
    padding-bottom: 20px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.dashboard-header h1 {
    margin: 0;
    font-size: 28px;
    font-weight: 600;
    background: linear-gradient(90deg, #4cc9f0, #4361ee);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
}

.dashboard-controls {
    display: flex;
    gap: 12px;
    align-items: center;
}

.dashboard-select {
    background: rgba(255, 255, 255, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.2);
    color: white;
    padding: 8px 16px;
    border-radius: 6px;
    font-size: 14px;
    cursor: pointer;
    transition: all 0.3s ease;
}

.dashboard-select:hover {
    background: rgba(255, 255, 255, 0.15);
    border-color: rgba(255, 255, 255, 0.3);
}

.dashboard-select:focus {
    outline: none;
    box-shadow: 0 0 0 2px rgba(76, 201, 240, 0.3);
}

.dashboard-button {
    background: linear-gradient(135deg, #4361ee 0%, #3a0ca3 100%);
    border: none;
    color: white;
    padding: 8px 16px;
    border-radius: 6px;
    font-size: 14px;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 6px;
    transition: all 0.3s ease;
}

.dashboard-button:hover {
    transform: translateY(-2px);
    box-shadow: 0 5px 15px rgba(67, 97, 238, 0.4);
}

.dashboard-button:active {
    transform: translateY(0);
}

.dashboard-button.refreshing .refresh-icon {
    animation: spin 1s linear infinite;
}

@keyframes spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

.dashboard-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
    gap: 20px;
    margin-bottom: 30px;
}

.grid-item {
    background: rgba(255, 255, 255, 0.05);
    border-radius: 10px;
    padding: 20px;
    backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.1);
    transition: all 0.3s ease;
}

.grid-item:hover {
    transform: translateY(-5px);
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
    border-color: rgba(76, 201, 240, 0.3);
}

.chart-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
}

.chart-header h3 {
    margin: 0;
    font-size: 18px;
    font-weight: 500;
    color: #e0e0e0;
}

.chart-stats {
    display: flex;
    align-items: center;
    gap: 10px;
}

.current-value {
    font-size: 24px;
    font-weight: 600;
    color: #4cc9f0;
}

.change-indicator {
    font-size: 14px;
    padding: 4px 8px;
    border-radius: 4px;
}

.change-indicator.increase {
    background: rgba(76, 175, 80, 0.2);
    color: #4caf50;
}

.change-indicator.decrease {
    background: rgba(244, 67, 54, 0.2);
    color: #f44336;
}

.change-indicator.neutral {
    background: rgba(158, 158, 158, 0.2);
    color: #9e9e9e;
}

.chart-container {
    width: 100%;
    height: 300px;
}

.dashboard-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-top: 20px;
    border-top: 1px solid rgba(255, 255, 255, 0.1);
    font-size: 14px;
    color: #b0b0b0;
}

.last-updated {
    display: flex;
    align-items: center;
    gap: 8px;
}

#lastUpdatedTime {
    color: #4cc9f0;
    font-weight: 500;
}

.data-source-info {
    display: flex;
    align-items: center;
    gap: 8px;
}

.data-source-info::before {
    content: "⏱️";
    font-size: 12px;
}

/* 全屏模式样式 */
:fullscreen .real-time-dashboard-container {
    padding: 40px;
    border-radius: 0;
}

:fullscreen .dashboard-grid {
    grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
    gap: 30px;
}

:fullscreen .chart-container {
    height: 400px;
}

/* 响应式设计 */
@media (max-width: 1200px) {
    .dashboard-grid {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 768px) {
    .dashboard-header {
        flex-direction: column;
        gap: 15px;
        align-items: flex-start;
    }
    
    .dashboard-controls {
        width: 100%;
        justify-content: space-between;
    }
    
    .dashboard-grid {
        grid-template-columns: 1fr;
    }
    
    .grid-item {
        min-width: 100%;
    }
}

/* 加载动画 */
.chart-loading {
    position: relative;
}

.chart-loading::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 40px;
    height: 40px;
    margin: -20px 0 0 -20px;
    border: 3px solid rgba(76, 201, 240, 0.3);
    border-top-color: #4cc9f0;
    border-radius: 50%;
    animation: spin 1s linear infinite;
}

/* 错误提示 */
.dashboard-error {
    position: fixed;
    top: 20px;
    right: 20px;
    background: #f44336;
    color: white;
    padding: 12px 20px;
    border-radius: 6px;
    box-shadow: 0 4px 12px rgba(244, 67, 54, 0.3);
    z-index: 1000;
    animation: slideIn 0.3s ease;
}

@keyframes slideIn {
    from {
        transform: translateX(100%);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

第四章:管理后台与配置界面

4.1 创建管理菜单和设置页面

<?php
class RealTime_Dashboard_Admin {
    
    private $settings_page;
    
    public function __construct() {
        add_action('admin_menu', array($this, 'add_admin_menu'));
        add_action('admin_init', array($this, 'register_settings'));
        add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
    }
    
    public function add_admin_menu() {
        $this->settings_page = add_menu_page(
            '实时数据大屏设置',
            '数据大屏',
            'manage_options',
            'real-time-dashboard',
            array($this, 'render_settings_page'),
            'dashicons-chart-area',
            30
        );
        
        // 添加子菜单
        add_submenu_page(
            'real-time-dashboard',
            '数据源配置',
            '数据源配置',
            'manage_options',
            'real-time-dashboard-sources',
            array($this, 'render_sources_page')
        );
        
        add_submenu_page(
            'real-time-dashboard',
            '图表设置',
            '图表设置',
            'manage_options',
            'real-time-dashboard-charts',
            array($this, 'render_charts_page')
        );
    }
    
    public function render_settings_page() {
        ?>
        <div class="wrap real-time-dashboard-admin">
            <h1><?php echo esc_html(get_admin_page_title()); ?></h1>
            
            <div class="dashboard-admin-header">
                <div class="admin-stats">
                    <div class="stat-card">
                        <h3>数据源状态</h3>
                        <div class="stat-value" id="dataSourcesStatus">检查中...</div>
                    </div>
                    <div class="stat-card">
                        <h3>最后更新</h3>
                        <div class="stat-value" id="lastDataUpdate">--:--:--</div>
                    </div>
                    <div class="stat-card">
                        <h3>缓存状态</h3>
                        <div class="stat-value" id="cacheStatus">正常</div>
                    </div>
                </div>
            </div>
            
            <form method="post" action="options.php">
                <?php
                settings_fields('real_time_dashboard_settings');
                do_settings_sections('real_time_dashboard_settings');
                submit_button('保存设置');
                ?>
            </form>
            
            <div class="admin-actions">
                <h2>快速操作</h2>
                <div class="action-buttons">
                    <button type="button" class="button button-primary" id="clearCacheBtn">
                        清除缓存
                    </button>
                    <button type="button" class="button button-secondary" id="testDataBtn">
                        测试数据源
                    </button>
                    <button type="button" class="button button-secondary" id="exportConfigBtn">
                        导出配置
                    </button>
                    <a href="<?php echo home_url('/real-time-dashboard/'); ?>" 
                       class="button button-secondary" target="_blank">
                        查看大屏
                    </a>
                </div>
            </div>
        </div>
        <?php
    }
    
    public function register_settings() {
        // 注册基本设置
        register_setting(
            'real_time_dashboard_settings',
            'rtd_general_settings',
本文来自网络,不代表柔性供应链服务中心立场,转载请注明出处:https://mall.org.cn/5114.html

EXCHANGES®作者

上一篇
下一篇

为您推荐

发表回复

联系我们

联系我们

18559313275

在线咨询: QQ交谈

邮箱: vip@exchanges.center

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