首页 / 应用软件 / WordPress集成教程,连接项目管理软件并展示项目状态

WordPress集成教程,连接项目管理软件并展示项目状态

WordPress集成教程:连接项目管理软件并展示项目状态

引言:WordPress的无限可能性

在当今数字化时代,企业网站已不再仅仅是展示公司信息的静态页面,而是逐渐演变为功能丰富的业务平台。WordPress作为全球最受欢迎的内容管理系统,其真正的力量不仅在于创建博客或简单网站,更在于通过代码二次开发实现各种复杂功能。本教程将深入探讨如何将WordPress与项目管理软件集成,实时展示项目状态,并实现一系列常用互联网小工具功能。

传统的企业网站往往与内部业务系统脱节,导致信息更新滞后、数据不一致等问题。通过将WordPress与项目管理工具(如Jira、Asana、Trello、Monday.com等)集成,我们可以创建一个动态的、实时更新的项目状态展示平台,让客户、团队成员和利益相关者随时了解项目进展。

第一部分:准备工作与环境搭建

1.1 选择合适的WordPress环境

在开始集成之前,确保你的WordPress环境满足以下要求:

  • WordPress 5.0或更高版本
  • PHP 7.4或更高版本(推荐PHP 8.0+)
  • 支持HTTPS的域名
  • 适当的服务器资源(至少1GB RAM,建议2GB以上)

1.2 安装必要插件

虽然本教程主要关注代码开发,但一些基础插件能极大提高开发效率:

  1. Advanced Custom Fields (ACF) - 用于创建自定义字段和管理数据
  2. Custom Post Type UI - 简化自定义文章类型的创建
  3. Query Monitor - 调试工具,监控数据库查询和性能
  4. WP REST API Controller - 管理REST API端点

1.3 设置子主题

为避免主题更新覆盖自定义代码,强烈建议创建子主题:

  1. wp-content/themes/目录下创建新文件夹,如my-custom-theme
  2. 创建style.css文件,添加主题信息:

    /*
    Theme Name: My Custom Theme
    Template: parent-theme-folder-name
    Version: 1.0
    */
  3. 创建functions.php文件,用于添加自定义功能

第二部分:连接项目管理软件API

2.1 选择项目管理软件并获取API凭证

不同的项目管理软件提供不同的API接口。以Jira为例:

  1. 登录Jira管理员账户
  2. 进入"设置" > "系统" > "API令牌"
  3. 创建新令牌并妥善保存
  4. 获取你的Jira实例URL(如https://yourcompany.atlassian.net

2.2 创建API连接类

在WordPress中创建专门的类来处理API通信:

<?php
/**
 * 项目管理软件API连接类
 */
class ProjectManagementAPI {
    
    private $api_url;
    private $api_token;
    private $username;
    
    public function __construct($url, $username, $token) {
        $this->api_url = $url;
        $this->username = $username;
        $this->api_token = $token;
    }
    
    /**
     * 发送API请求
     */
    private function make_request($endpoint, $method = 'GET', $data = []) {
        $url = $this->api_url . $endpoint;
        
        $args = [
            'method'  => $method,
            'headers' => [
                'Authorization' => 'Basic ' . base64_encode($this->username . ':' . $this->api_token),
                'Content-Type'  => 'application/json',
                'Accept'        => 'application/json'
            ],
            'timeout' => 30
        ];
        
        if (!empty($data)) {
            $args['body'] = json_encode($data);
        }
        
        $response = wp_remote_request($url, $args);
        
        if (is_wp_error($response)) {
            return [
                'success' => false,
                'error'   => $response->get_error_message()
            ];
        }
        
        $body = wp_remote_retrieve_body($response);
        $status_code = wp_remote_retrieve_response_code($response);
        
        return [
            'success'    => $status_code >= 200 && $status_code < 300,
            'status'     => $status_code,
            'data'       => json_decode($body, true),
            'raw_body'   => $body
        ];
    }
    
    /**
     * 获取项目列表
     */
    public function get_projects() {
        $endpoint = '/rest/api/3/project';
        return $this->make_request($endpoint);
    }
    
    /**
     * 获取特定项目的问题/任务
     */
    public function get_project_issues($project_key, $max_results = 50) {
        $endpoint = '/rest/api/3/search';
        
        $jql = "project = " . $project_key;
        
        $data = [
            'jql'        => $jql,
            'maxResults' => $max_results,
            'fields'     => ['summary', 'status', 'assignee', 'created', 'updated']
        ];
        
        return $this->make_request($endpoint, 'POST', $data);
    }
    
    /**
     * 获取项目状态概览
     */
    public function get_project_status($project_key) {
        $issues_response = $this->get_project_issues($project_key, 100);
        
        if (!$issues_response['success']) {
            return $issues_response;
        }
        
        $issues = $issues_response['data']['issues'] ?? [];
        $status_summary = [
            'total' => 0,
            'by_status' => [],
            'by_assignee' => []
        ];
        
        foreach ($issues as $issue) {
            $status_summary['total']++;
            
            // 按状态统计
            $status_name = $issue['fields']['status']['name'] ?? '未知';
            if (!isset($status_summary['by_status'][$status_name])) {
                $status_summary['by_status'][$status_name] = 0;
            }
            $status_summary['by_status'][$status_name]++;
            
            // 按负责人统计
            $assignee_name = $issue['fields']['assignee']['displayName'] ?? '未分配';
            if (!isset($status_summary['by_assignee'][$assignee_name])) {
                $status_summary['by_assignee'][$assignee_name] = 0;
            }
            $status_summary['by_assignee'][$assignee_name]++;
        }
        
        return [
            'success' => true,
            'data'    => $status_summary
        ];
    }
}
?>

2.3 安全存储API凭证

永远不要在代码中硬编码API凭证。使用WordPress选项API安全存储:

<?php
/**
 * 保存API设置
 */
function save_pm_api_settings() {
    if (isset($_POST['pm_api_nonce']) && wp_verify_nonce($_POST['pm_api_nonce'], 'save_pm_api_settings')) {
        $api_settings = [
            'api_url'   => sanitize_text_field($_POST['api_url']),
            'username'  => sanitize_text_field($_POST['username']),
            'api_token' => $_POST['api_token'] // 注意:令牌需要特殊处理
        ];
        
        // 加密存储令牌
        if (!empty($api_settings['api_token'])) {
            require_once(ABSPATH . 'wp-includes/class-phpass.php');
            $hasher = new PasswordHash(8, true);
            $api_settings['api_token'] = $hasher->HashPassword($api_settings['api_token']);
        } else {
            // 如果令牌为空,保留现有令牌
            $existing_settings = get_option('pm_api_settings', []);
            if (isset($existing_settings['api_token'])) {
                $api_settings['api_token'] = $existing_settings['api_token'];
            }
        }
        
        update_option('pm_api_settings', $api_settings);
        
        wp_redirect(add_query_arg('settings-updated', 'true', wp_get_referer()));
        exit;
    }
}
add_action('admin_post_save_pm_api_settings', 'save_pm_api_settings');

/**
 * 获取API设置
 */
function get_pm_api_settings() {
    $settings = get_option('pm_api_settings', []);
    
    // 解密令牌(在实际使用时)
    if (isset($settings['api_token']) && !empty($settings['api_token'])) {
        // 注意:实际解密逻辑需要根据加密方式实现
        // 这里只是示例结构
    }
    
    return $settings;
}
?>

第三部分:在WordPress中展示项目状态

3.1 创建自定义文章类型和分类

为了更好地组织项目数据,我们创建自定义文章类型:

<?php
/**
 * 注册项目自定义文章类型
 */
function register_project_post_type() {
    $labels = [
        '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 = [
        'labels'             => $labels,
        'public'             => true,
        'publicly_queryable' => true,
        'show_ui'            => true,
        'show_in_menu'       => true,
        'query_var'          => true,
        'rewrite'            => ['slug' => 'project'],
        'capability_type'    => 'post',
        'has_archive'        => true,
        'hierarchical'       => false,
        'menu_position'      => 20,
        'menu_icon'          => 'dashicons-portfolio',
        'supports'           => ['title', 'editor', 'thumbnail', 'excerpt'],
        'show_in_rest'       => true, // 启用Gutenberg编辑器和REST API
    ];
    
    register_post_type('project', $args);
    
    // 注册项目分类
    register_taxonomy(
        'project_category',
        'project',
        [
            'label' => '项目分类',
            'rewrite' => ['slug' => 'project-category'],
            'hierarchical' => true,
            'show_in_rest' => true
        ]
    );
}
add_action('init', 'register_project_post_type');
?>

3.2 使用Advanced Custom Fields添加项目元数据

通过ACF添加项目与外部项目管理软件的关联字段:

<?php
/**
 * 添加项目元字段
 */
function add_project_meta_fields() {
    if (function_exists('acf_add_local_field_group')) {
        acf_add_local_field_group([
            'key' => 'group_project_meta',
            'title' => '项目信息',
            'fields' => [
                [
                    'key' => 'field_project_external_id',
                    'label' => '外部项目ID',
                    'name' => 'project_external_id',
                    'type' => 'text',
                    'instructions' => '在项目管理软件中的项目标识符',
                    'required' => 0,
                ],
                [
                    'key' => 'field_project_key',
                    'label' => '项目键',
                    'name' => 'project_key',
                    'type' => 'text',
                    'instructions' => '项目键(如Jira中的项目键)',
                    'required' => 0,
                ],
                [
                    'key' => 'field_project_status',
                    'label' => '项目状态',
                    'name' => 'project_status',
                    'type' => 'select',
                    'choices' => [
                        'planning' => '规划中',
                        'active' => '进行中',
                        'on_hold' => '暂停',
                        'completed' => '已完成',
                        'cancelled' => '已取消'
                    ],
                    'default_value' => 'planning',
                ],
                [
                    'key' => 'field_project_start_date',
                    'label' => '开始日期',
                    'name' => 'project_start_date',
                    'type' => 'date_picker',
                ],
                [
                    'key' => 'field_project_end_date',
                    'label' => '结束日期',
                    'name' => 'project_end_date',
                    'type' => 'date_picker',
                ],
                [
                    'key' => 'field_project_progress',
                    'label' => '进度',
                    'name' => 'project_progress',
                    'type' => 'range',
                    'instructions' => '项目完成百分比',
                    'min' => 0,
                    'max' => 100,
                    'step' => 5,
                    'default_value' => 0,
                ]
            ],
            'location' => [
                [
                    [
                        'param' => 'post_type',
                        'operator' => '==',
                        'value' => 'project',
                    ],
                ],
            ],
        ]);
    }
}
add_action('acf/init', 'add_project_meta_fields');
?>

3.3 创建项目状态展示短代码

创建短代码以便在文章或页面中插入项目状态:

<?php
/**
 * 项目状态展示短代码
 */
function project_status_shortcode($atts) {
    // 解析短代码属性
    $atts = shortcode_atts([
        'project_id' => '', // WordPress项目ID
        'project_key' => '', // 外部项目键
        'show_tasks' => 'true', // 是否显示任务
        'max_tasks' => 10, // 最大任务显示数量
        'refresh' => 30, // 自动刷新时间(秒),0表示不自动刷新
    ], $atts);
    
    // 获取项目信息
    $project_data = [];
    
    if (!empty($atts['project_id'])) {
        $project_post = get_post($atts['project_id']);
        if ($project_post && $project_post->post_type === 'project') {
            $project_data['title'] = $project_post->post_title;
            $project_data['description'] = $project_post->post_excerpt;
            $project_data['progress'] = get_field('project_progress', $atts['project_id']);
            $project_data['status'] = get_field('project_status', $atts['project_id']);
            $project_key = get_field('project_key', $atts['project_id']);
        }
    }
    
    // 如果直接提供了project_key,使用它
    if (!empty($atts['project_key'])) {
        $project_key = $atts['project_key'];
    }
    
    // 如果没有项目键,返回错误
    if (empty($project_key)) {
        return '<div class="project-status-error">未指定项目</div>';
    }
    
    // 获取API设置
    $api_settings = get_pm_api_settings();
    
    // 初始化API连接
    $api = new ProjectManagementAPI(
        $api_settings['api_url'] ?? '',
        $api_settings['username'] ?? '',
        $api_settings['api_token'] ?? ''
    );
    
    // 获取项目状态
    $status_response = $api->get_project_status($project_key);
    
    // 准备输出
    ob_start();
    
    // 添加自动刷新脚本
    if ($atts['refresh'] > 0) {
        ?>
        <script>
        document.addEventListener('DOMContentLoaded', function() {
            setTimeout(function() {
                location.reload();
            }, <?php echo $atts['refresh'] * 1000; ?>);
        });
        </script>
        <?php
    }
    
    // 输出项目状态
    ?>
    <div class="project-status-container" data-project-key="<?php echo esc_attr($project_key); ?>">
        <div class="project-header">
            <h3 class="project-title"><?php echo esc_html($project_data['title'] ?? '项目状态'); ?></h3>
            <?php if (!empty($project_data['description'])): ?>
                <p class="project-description"><?php echo esc_html($project_data['description']); ?></p>
            <?php endif; ?>
        </div>
        
        <?php if ($status_response['success']): 
            $status_data = $status_response['data'];
        ?>
            <div class="project-stats">
                <div class="stat-card total-tasks">
                    <div class="stat-value"><?php echo $status_data['total']; ?></div>
                    <div class="stat-label">总任务数</div>
                </div>
                
                <?php foreach ($status_data['by_status'] as $status_name => $count): ?>
                <div class="stat-card status-<?php echo sanitize_title($status_name); ?>">
                    <div class="stat-value"><?php echo $count; ?></div>
                    <div class="stat-label"><?php echo esc_html($status_name); ?></div>
                </div>
                <?php endforeach; ?>
            </div>
            
            <?php if ($atts['show_tasks'] === 'true'): 
                $tasks_response = $api->get_project_issues($project_key, $atts['max_tasks']);
                if ($tasks_response['success'] && !empty($tasks_response['data']['issues'])):
            ?>
                <div class="project-tasks">
                    <h4>最近任务</h4>
                    <table class="tasks-table">
                        <thead>
                            <tr>
                                <th>任务</th>
                                <th>状态</th>
                                <th>负责人</th>
                                <th>更新时间</th>
                            </tr>
                        </thead>
                        <tbody>
                            <?php foreach ($tasks_response['data']['issues'] as $issue): 
                                $fields = $issue['fields'] ?? [];
                            ?>
                            <tr>
                                <td>
                                    <a href="#" class="task-link" data-task-key="<?php echo esc_attr($issue['key']); ?>">
                                        <?php echo esc_html($fields['summary'] ?? '无标题'); ?>
                                    </a>
                                </td>
                                <td>
                                    <span class="task-status status-<?php echo sanitize_title($fields['status']['name'] ?? '未知'); ?>">
                                        <?php echo esc_html($fields['status']['name'] ?? '未知'); ?>
                                    </span>
                                </td>

displayName'] ?? '未分配'); ?></td>

                            <td><?php 
                                $updated = $fields['updated'] ?? '';
                                if (!empty($updated)) {
                                    echo date('Y-m-d H:i', strtotime($updated));
                                } else {
                                    echo '未知';
                                }
                            ?></td>
                        </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
        <?php endif; endif; ?>
        
        <?php if (!empty($status_data['by_assignee'])): ?>
        <div class="assignee-distribution">
            <h4>任务分配情况</h4>
            <div class="assignee-chart">
                <?php foreach ($status_data['by_assignee'] as $assignee => $count): 
                    $percentage = $status_data['total'] > 0 ? ($count / $status_data['total']) * 100 : 0;
                ?>
                <div class="assignee-item">
                    <div class="assignee-name"><?php echo esc_html($assignee); ?></div>
                    <div class="assignee-bar">
                        <div class="assignee-bar-fill" style="width: <?php echo $percentage; ?>%"></div>
                    </div>
                    <div class="assignee-count"><?php echo $count; ?> 任务</div>
                </div>
                <?php endforeach; ?>
            </div>
        </div>
        <?php endif; ?>
        
    <?php else: ?>
        <div class="project-status-error">
            <p>无法获取项目状态数据</p>
            <?php if (current_user_can('manage_options')): ?>
                <p class="error-detail">错误: <?php echo esc_html($status_response['error'] ?? '未知错误'); ?></p>
            <?php endif; ?>
        </div>
    <?php endif; ?>
    
    <div class="project-status-footer">
        <p class="update-time">最后更新: <?php echo current_time('Y-m-d H:i:s'); ?></p>
        <?php if ($atts['refresh'] > 0): ?>
            <p class="auto-refresh">自动刷新: 每 <?php echo $atts['refresh']; ?> 秒</p>
        <?php endif; ?>
    </div>
</div>

<style>
.project-status-container {
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    padding: 20px;
    margin: 20px 0;
    background: #fff;
    box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}

.project-header {
    margin-bottom: 25px;
    padding-bottom: 15px;
    border-bottom: 2px solid #f0f0f0;
}

.project-title {
    margin: 0 0 10px 0;
    color: #333;
}

.project-description {
    margin: 0;
    color: #666;
    font-size: 14px;
}

.project-stats {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 15px;
    margin-bottom: 25px;
}

.stat-card {
    background: #f8f9fa;
    border-radius: 6px;
    padding: 15px;
    text-align: center;
    border-left: 4px solid #0073aa;
}

.stat-card.total-tasks {
    border-left-color: #0073aa;
}

.stat-card.status-已完成 {
    border-left-color: #46b450;
}

.stat-card.status-进行中 {
    border-left-color: #00a0d2;
}

.stat-card.status-待处理 {
    border-left-color: #ffb900;
}

.stat-value {
    font-size: 24px;
    font-weight: bold;
    color: #333;
    margin-bottom: 5px;
}

.stat-label {
    font-size: 12px;
    color: #666;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.project-tasks {
    margin-bottom: 25px;
}

.project-tasks h4 {
    margin-bottom: 15px;
    color: #444;
}

.tasks-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
}

.tasks-table th {
    background: #f8f9fa;
    padding: 12px 15px;
    text-align: left;
    font-weight: 600;
    color: #555;
    border-bottom: 2px solid #e0e0e0;
}

.tasks-table td {
    padding: 12px 15px;
    border-bottom: 1px solid #eee;
}

.tasks-table tr:hover {
    background: #f9f9f9;
}

.task-link {
    color: #0073aa;
    text-decoration: none;
}

.task-link:hover {
    text-decoration: underline;
}

.task-status {
    display: inline-block;
    padding: 3px 8px;
    border-radius: 12px;
    font-size: 12px;
    font-weight: 500;
}

.status-已完成 {
    background: #d1f0d9;
    color: #1e7c1e;
}

.status-进行中 {
    background: #d1e8f0;
    color: #0a6a8c;
}

.status-待处理 {
    background: #fff0d1;
    color: #b36b00;
}

.assignee-distribution {
    margin-bottom: 20px;
}

.assignee-distribution h4 {
    margin-bottom: 15px;
    color: #444;
}

.assignee-item {
    display: flex;
    align-items: center;
    margin-bottom: 10px;
}

.assignee-name {
    width: 150px;
    font-size: 14px;
    color: #555;
}

.assignee-bar {
    flex-grow: 1;
    height: 20px;
    background: #f0f0f0;
    border-radius: 10px;
    overflow: hidden;
    margin: 0 15px;
}

.assignee-bar-fill {
    height: 100%;
    background: linear-gradient(90deg, #0073aa, #00a0d2);
    border-radius: 10px;
    transition: width 0.5s ease;
}

.assignee-count {
    width: 80px;
    text-align: right;
    font-size: 14px;
    color: #666;
}

.project-status-footer {
    margin-top: 20px;
    padding-top: 15px;
    border-top: 1px solid #eee;
    font-size: 12px;
    color: #888;
    display: flex;
    justify-content: space-between;
}

.project-status-error {
    padding: 20px;
    background: #f8d7da;
    border: 1px solid #f5c6cb;
    border-radius: 4px;
    color: #721c24;
}

.error-detail {
    font-size: 12px;
    margin-top: 10px;
    color: #856404;
}

@media (max-width: 768px) {
    .project-stats {
        grid-template-columns: repeat(2, 1fr);
    }
    
    .assignee-item {
        flex-direction: column;
        align-items: flex-start;
    }
    
    .assignee-name {
        width: 100%;
        margin-bottom: 5px;
    }
    
    .assignee-bar {
        width: 100%;
        margin: 5px 0;
    }
    
    .assignee-count {
        width: 100%;
        text-align: left;
    }
    
    .tasks-table {
        display: block;
        overflow-x: auto;
    }
}
</style>
<?php

return ob_get_clean();

}
add_shortcode('project_status', 'project_status_shortcode');
?>


## 第四部分:实现常用互联网小工具功能

### 4.1 实时数据仪表板小工具

创建一个WordPress小工具,显示多个项目的实时状态:

<?php
/**

  • 项目状态仪表板小工具
    */

class ProjectDashboardWidget extends WP_Widget {


public function __construct() {
    parent::__construct(
        'project_dashboard_widget',
        '项目状态仪表板',
        ['description' => '显示多个项目的实时状态']
    );
}

public function widget($args, $instance) {
    echo $args['before_widget'];
    
    $title = apply_filters('widget_title', $instance['title']);
    if (!empty($title)) {
        echo $args['before_title'] . $title . $args['after_title'];
    }
    
    // 获取配置的项目键
    $project_keys = !empty($instance['project_keys']) ? 
        explode(',', $instance['project_keys']) : [];
    
    if (empty($project_keys)) {
        echo '<p>请配置要显示的项目</p>';
        echo $args['after_widget'];
        return;
    }
    
    // 获取API设置
    $api_settings = get_pm_api_settings();
    $api = new ProjectManagementAPI(
        $api_settings['api_url'] ?? '',
        $api_settings['username'] ?? '',
        $api_settings['api_token'] ?? ''
    );
    
    echo '<div class="project-dashboard-widget">';
    
    foreach ($project_keys as $project_key) {
        $project_key = trim($project_key);
        if (empty($project_key)) continue;
        
        $status_response = $api->get_project_status($project_key);
        
        echo '<div class="dashboard-project-item">';
        echo '<h4>' . esc_html($project_key) . '</h4>';
        
        if ($status_response['success']) {
            $status_data = $status_response['data'];
            
            // 计算完成率
            $completed = $status_data['by_status']['已完成'] ?? 0;
            $completion_rate = $status_data['total'] > 0 ? 
                round(($completed / $status_data['total']) * 100) : 0;
            
            echo '<div class="completion-bar">';
            echo '<div class="completion-fill" style="width: ' . $completion_rate . '%"></div>';
            echo '</div>';
            
            echo '<div class="project-metrics">';
            echo '<span class="metric">总任务: ' . $status_data['total'] . '</span>';
            echo '<span class="metric">完成率: ' . $completion_rate . '%</span>';
            echo '</div>';
        } else {
            echo '<p class="error">无法获取数据</p>';
        }
        
        echo '</div>';
    }
    
    echo '</div>';
    
    // 添加样式
    ?>
    <style>
    .project-dashboard-widget {
        font-size: 14px;
    }
    
    .dashboard-project-item {
        margin-bottom: 15px;
        padding: 10px;
        background: #f8f9fa;
        border-radius: 4px;
        border-left: 3px solid #0073aa;
    }
    
    .dashboard-project-item h4 {
        margin: 0 0 8px 0;
        font-size: 14px;
        color: #333;
    }
    
    .completion-bar {
        height: 6px;
        background: #e0e0e0;
        border-radius: 3px;
        overflow: hidden;
        margin-bottom: 8px;
    }
    
    .completion-fill {
        height: 100%;
        background: linear-gradient(90deg, #0073aa, #00a0d2);
        border-radius: 3px;
        transition: width 0.5s ease;
    }
    
    .project-metrics {
        display: flex;
        justify-content: space-between;
        font-size: 12px;
        color: #666;
    }
    
    .metric {
        display: inline-block;
    }
    
    .error {
        color: #dc3232;
        font-size: 12px;
        margin: 0;
    }
    </style>
    <?php
    
    echo $args['after_widget'];
}

public function form($instance) {
    $title = $instance['title'] ?? '项目状态';
    $project_keys = $instance['project_keys'] ?? '';
    ?>
    <p>
        <label for="<?php echo $this->get_field_id('title'); ?>">标题:</label>
        <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" 
               name="<?php echo $this->get_field_name('title'); ?>" 
               type="text" value="<?php echo esc_attr($title); ?>">
    </p>
    <p>
        <label for="<?php echo $this->get_field_id('project_keys'); ?>">项目键(用逗号分隔):</label>
        <input class="widefat" id="<?php echo $this->get_field_id('project_keys'); ?>" 
               name="<?php echo $this->get_field_name('project_keys'); ?>" 
               type="text" value="<?php echo esc_attr($project_keys); ?>">
        <small>例如: PROJ1, PROJ2, PROJ3</small>
    </p>
    <?php
}

public function update($new_instance, $old_instance) {
    $instance = [];
    $instance['title'] = !empty($new_instance['title']) ? 
        sanitize_text_field($new_instance['title']) : '';
    $instance['project_keys'] = !empty($new_instance['project_keys']) ? 
        sanitize_text_field($new_instance['project_keys']) : '';
    return $instance;
}

}

// 注册小工具
function register_project_dashboard_widget() {

register_widget('ProjectDashboardWidget');

}
add_action('widgets_init', 'register_project_dashboard_widget');
?>


### 4.2 项目时间线小工具

创建一个可视化项目时间线的小工具:

<?php
/**

  • 项目时间线小工具
    */

function project_timeline_shortcode($atts) {

$atts = shortcode_atts([
    'project_id' => '',
    'height' => '400px',
    'show_milestones' => 'true',
], $atts);

if (empty($atts['project_id'])) {
    return '<p>请指定项目ID</p>';
}

// 获取项目信息
$project_post = get_post($atts['project_id']);
if (!$project_post || $project_post->post_type !== 'project') {
    return '<p>项目不存在</p>';
}

// 获取项目时间线数据
$timeline_data = get_project_timeline_data($atts['project_id']);

ob_start();
?>
<div class="project-timeline-container" style="height: <?php echo esc_attr($atts['height']); ?>">
    <div class="timeline-header">
        <h3><?php echo esc_html($project_post->post_title); ?> 时间线</h3>
    </div>
    <div class="timeline-wrapper">
        <div class="timeline">
            <?php foreach ($timeline_data as $item): ?>
            <div class="timeline-item <?php echo esc_attr($item['type']); ?>" 
                 data-date="<?php echo esc_attr($item['date']); ?>">
                <div class="timeline-marker"></div>
                <div class="timeline-content">
                    <div class="timeline-date"><?php echo esc_html($item['display_date']); ?></div>
                    <h4 class="timeline-title"><?php echo esc_html($item['title']); ?></h4>
                    <?php if (!empty($item['description'])): ?>
                        <p class="timeline-description"><?php echo esc_html($item['description']); ?></p>
                    <?php endif; ?>
                    <?php if (!empty($item['status'])): ?>
                        <span class="timeline-status status-<?php echo esc_attr($item['status']); ?>">
                            <?php echo esc_html($item['status']); ?>
                        </span>
                    <?php endif; ?>
                </div>
            </div>
            <?php endforeach; ?>
        </div>
    </div>
</div>

<style>
.project-timeline-container {
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    overflow: hidden;
    background: #fff;
}

.timeline-header {
    padding: 15px 20px;
    background: #f8f9fa;
    border-bottom: 1px solid #e0e0e0;
}

.timeline-header h3 {
    margin: 0;
    font-size: 18px;
    color: #333;
}

.timeline-wrapper {
    padding: 20px;
    height: calc(100% - 60px);
    overflow-y: auto;
}

.timeline {
    position: relative;
    padding-left: 30px;
}

.timeline::before {
    content: '';
    position: absolute;
    left: 10px;
    top: 0;
    bottom: 0;
    width: 2px;
    background: #0073aa;
}

.timeline-item {
    position: relative;
    margin-bottom: 25px;
}

.timeline-marker {
    position: absolute;
    left: -25px;
    top: 5px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: #fff;
    border: 3px solid #0073aa;
    z-index: 1;
}

.timeline-item.milestone .timeline-marker {
    border-color:
本文来自网络,不代表柔性供应链服务中心立场,转载请注明出处:https://mall.org.cn/5102.html

EXCHANGES®作者

上一篇
下一篇

为您推荐

发表回复

联系我们

联系我们

18559313275

在线咨询: QQ交谈

邮箱: vip@exchanges.center

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