文章目录
-
- 本文提供了一份完整的指南,介绍如何在WordPress平台中通过代码二次开发,实现内嵌式项目管理与甘特图工具。我们将从需求分析开始,逐步讲解数据库设计、功能模块开发、甘特图集成、用户界面设计以及性能优化等关键环节,帮助开发者将常用互联网小工具功能无缝集成到WordPress系统中。
- 项目概述与需求分析 WordPress开发环境搭建 数据库设计与数据模型 项目管理核心功能开发 甘特图集成与可视化 用户权限与团队协作 前端界面与用户体验优化 数据安全与性能优化 测试与部署指南 扩展与维护建议
-
- 随着远程工作和团队协作的普及,项目管理工具成为企业日常运营的重要组成部分。许多中小型企业使用WordPress作为其官方网站或内容管理系统,但缺乏集成的项目管理功能。通过开发内嵌式项目管理与甘特图工具,用户可以在熟悉的WordPress环境中管理项目,无需切换多个平台,提高工作效率。
- 项目管理:创建、编辑、删除项目,设置项目基本信息 任务管理:任务创建、分配、优先级设置、状态跟踪 甘特图可视化:直观展示项目时间线、任务依赖关系 团队协作:用户角色分配、任务评论、文件附件 进度跟踪:完成百分比、里程碑标记、时间跟踪 报告与分析:项目进度报告、团队绩效统计
- 核心框架:WordPress插件架构 前端技术:React/Vue.js(可选)、jQuery、HTML5、CSS3 图表库:DHTMLX Gantt、Frappe Gantt或自定义SVG实现 数据库:WordPress默认MySQL数据库 通信方式:REST API + AJAX
-
- # 使用Local by Flywheel或Docker配置WordPress环境 # 安装必要工具 npm install -g @wordpress/env wp-env start # 或使用传统方法 # 1. 安装XAMPP/MAMP/WAMP # 2. 下载最新WordPress # 3. 配置数据库
- 创建插件主文件 project-management-gantt.php: <?php /** * Plugin Name: 项目管理与甘特图工具 * Plugin URI: https://yourwebsite.com/ * Description: 在WordPress中集成项目管理与甘特图功能 * Version: 1.0.0 * Author: 开发者名称 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('PMG_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('PMG_PLUGIN_URL', plugin_dir_url(__FILE__)); define('PMG_VERSION', '1.0.0'); // 初始化插件 require_once PMG_PLUGIN_DIR . 'includes/class-pmg-init.php'; PMG_Init::register();
- project-management-gantt/ ├── project-management-gantt.php # 主插件文件 ├── includes/ # 核心类文件 │ ├── class-pmg-init.php # 初始化类 │ ├── class-pmg-database.php # 数据库处理 │ ├── class-pmg-projects.php # 项目管理类 │ ├── class-pmg-tasks.php # 任务管理类 │ └── class-pmg-gantt.php # 甘特图处理类 ├── admin/ # 后台管理文件 │ ├── css/ # 管理端CSS │ ├── js/ # 管理端JavaScript │ └── views/ # 管理端视图 ├── public/ # 前端文件 │ ├── css/ # 前端CSS │ ├── js/ # 前端JavaScript │ └── views/ # 前端视图 ├── assets/ # 静态资源 │ ├── lib/ # 第三方库 │ └── images/ # 图片资源 └── templates/ # 模板文件
-
- 为了避免与WordPress核心表冲突,我们创建独立的数据表: // includes/class-pmg-database.php class PMG_Database { public static function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 项目表 $projects_table = $wpdb->prefix . 'pmg_projects'; $projects_sql = "CREATE TABLE IF NOT EXISTS $projects_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, name varchar(255) NOT NULL, description text, status varchar(50) DEFAULT 'active', start_date date, end_date date, progress tinyint(3) DEFAULT 0, created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; // 任务表 $tasks_table = $wpdb->prefix . 'pmg_tasks'; $tasks_sql = "CREATE TABLE IF NOT EXISTS $tasks_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, project_id mediumint(9) NOT NULL, parent_id mediumint(9) DEFAULT 0, title varchar(255) NOT NULL, description text, start_date date, end_date date, duration int(11) DEFAULT 1, progress tinyint(3) DEFAULT 0, priority varchar(20) DEFAULT 'medium', status varchar(50) DEFAULT 'pending', assigned_to bigint(20), sort_order int(11) DEFAULT 0, dependencies text, created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY project_id (project_id) ) $charset_collate;"; // 项目成员表 $members_table = $wpdb->prefix . 'pmg_project_members'; $members_sql = "CREATE TABLE IF NOT EXISTS $members_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, project_id mediumint(9) NOT NULL, user_id bigint(20) NOT NULL, role varchar(50) DEFAULT 'member', joined_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY project_user (project_id, user_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($projects_sql); dbDelta($tasks_sql); dbDelta($members_sql); } }
- 一个项目包含多个任务 一个任务可以有子任务(通过parent_id实现层级结构) 一个项目可以有多个成员 一个用户可以参与多个项目
- 添加适当的索引以提高查询性能 考虑大数据量下的分表策略 定期清理历史数据
-
- // includes/class-pmg-projects.php class PMG_Projects { // 创建新项目 public static function create_project($data) { global $wpdb; $table = $wpdb->prefix . 'pmg_projects'; $defaults = array( 'name' => '', 'description' => '', 'status' => 'active', 'start_date' => current_time('mysql'), 'end_date' => null, 'progress' => 0, 'created_by' => get_current_user_id() ); $data = wp_parse_args($data, $defaults); $wpdb->insert($table, $data); return $wpdb->insert_id; } // 获取项目列表 public static function get_projects($args = array()) { global $wpdb; $table = $wpdb->prefix . 'pmg_projects'; $defaults = array( 'status' => 'active', 'user_id' => 0, 'per_page' => 10, 'page' => 1 ); $args = wp_parse_args($args, $defaults); $where = "WHERE status = '" . esc_sql($args['status']) . "'"; if ($args['user_id'] > 0) { $members_table = $wpdb->prefix . 'pmg_project_members'; $where .= " AND id IN (SELECT project_id FROM $members_table WHERE user_id = " . intval($args['user_id']) . ")"; } $offset = ($args['page'] - 1) * $args['per_page']; $query = "SELECT * FROM $table $where ORDER BY created_at DESC LIMIT %d OFFSET %d"; return $wpdb->get_results($wpdb->prepare($query, $args['per_page'], $offset)); } // 更新项目 public static function update_project($project_id, $data) { global $wpdb; $table = $wpdb->prefix . 'pmg_projects'; return $wpdb->update($table, $data, array('id' => $project_id)); } // 删除项目 public static function delete_project($project_id) { global $wpdb; $table = $wpdb->prefix . 'pmg_projects'; // 同时删除相关任务和成员 $tasks_table = $wpdb->prefix . 'pmg_tasks'; $members_table = $wpdb->prefix . 'pmg_project_members'; $wpdb->delete($tasks_table, array('project_id' => $project_id)); $wpdb->delete($members_table, array('project_id' => $project_id)); return $wpdb->delete($table, array('id' => $project_id)); } }
- // includes/class-pmg-tasks.php class PMG_Tasks { // 创建任务 public static function create_task($data) { global $wpdb; $table = $wpdb->prefix . 'pmg_tasks'; $defaults = array( 'project_id' => 0, 'parent_id' => 0, 'title' => '', 'description' => '', 'start_date' => current_time('mysql'), 'end_date' => null, 'duration' => 1, 'progress' => 0, 'priority' => 'medium', 'status' => 'pending', 'assigned_to' => null, 'sort_order' => 0, 'dependencies' => '', 'created_by' => get_current_user_id() ); $data = wp_parse_args($data, $defaults); // 自动计算结束日期 if (empty($data['end_date']) && !empty($data['start_date']) && $data['duration'] > 0) { $start_date = new DateTime($data['start_date']); $start_date->modify('+' . ($data['duration'] - 1) . ' days'); $data['end_date'] = $start_date->format('Y-m-d'); } $wpdb->insert($table, $data); return $wpdb->insert_id; } // 获取项目任务树 public static function get_project_tasks($project_id, $flat = false) { global $wpdb; $table = $wpdb->prefix . 'pmg_tasks'; $tasks = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table WHERE project_id = %d ORDER BY parent_id, sort_order ASC", $project_id )); if ($flat) { return $tasks; } // 构建层级结构 return self::build_task_tree($tasks); } // 构建任务树 private static function build_task_tree($tasks, $parent_id = 0) { $tree = array(); foreach ($tasks as $task) { if ($task->parent_id == $parent_id) { $children = self::build_task_tree($tasks, $task->id); if ($children) { $task->children = $children; } $tree[] = $task; } } return $tree; } // 更新任务进度 public static function update_task_progress($task_id, $progress) { global $wpdb; $table = $wpdb->prefix . 'pmg_tasks'; $result = $wpdb->update($table, array('progress' => $progress, 'updated_at' => current_time('mysql')), array('id' => $task_id) ); // 更新父任务和项目进度 if ($result) { self::update_parent_progress($task_id); } return $result; } // 递归更新父任务进度 private static function update_parent_progress($task_id) { global $wpdb; $table = $wpdb->prefix . 'pmg_tasks'; $task = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE id = %d", $task_id)); if ($task && $task->parent_id > 0) { // 计算父任务下所有子任务的平均进度 $children = $wpdb->get_results($wpdb->prepare( "SELECT progress FROM $table WHERE parent_id = %d", $task->parent_id )); if ($children) { $total_progress = 0; foreach ($children as $child) { $total_progress += $child->progress; } $avg_progress = round($total_progress / count($children)); $wpdb->update($table, array('progress' => $avg_progress), array('id' => $task->parent_id) ); // 递归更新 self::update_parent_progress($task->parent_id); } } // 更新项目进度 if ($task) { self::update_project_progress($task->project_id); } } // 更新项目进度 private static function update_project_progress($project_id) { global $wpdb; $tasks_table = $wpdb->prefix . 'pmg_tasks'; $projects_table = $wpdb->prefix . 'pmg_projects'; // 计算项目下所有根任务的平均进度 $root_tasks = $wpdb->get_results($wpdb->prepare( "SELECT progress FROM $tasks_table WHERE project_id = %d AND parent_id = 0", $project_id )); if ($root_tasks) { $total_progress = 0; foreach ($root_tasks as $task) { $total_progress += $task->progress; } $avg_progress = round($total_progress / count($root_tasks)); $wpdb->update($projects_table, array('progress' => $avg_progress), array('id' => $project_id) ); } } }
- // 注册REST API路由 add_action('rest_api_init', function() { // 项目相关端点 register_rest_route('pmg/v1', '/projects', array( array( 'methods' => 'GET', 'callback' => 'pmg_rest_get_projects', 'permission_callback' => function() { return current_user_can('read'); } ), array( 'methods' => 'POST', 'callback' => 'pmg_rest_create_project', 'permission_callback' => function() { return current_user_can('edit_posts'); } ) )); // 任务相关端点 register_rest_route('pmg/v1', '/projects/(?P<project_id>d+)/tasks', array( 'methods' => 'GET', 'callback' => 'pmg_rest_get_tasks', 'permission_callback' => function($request) { return pmg_check_project_access($request['project_id']); } )); register_rest_route('pmg/v1', '/tasks/(?P<task_id>d+)', array( array( 'methods' => 'PUT', 'callback' => 'pmg_rest_update_task', 'permission_callback' => function($request) { return pmg_check_task_access($request['task_id']); } ), array( 'methods' => 'DELETE', 'callback' => 'pmg_rest_delete_task', 'permission_callback' => function($request) { return current_user_can('delete_posts'); } ) )); }); // REST API回调函数示例 function pmg_rest_get_projects(WP_REST_Request $request) { $args = array( 'status' => $request->get_param('status') ?: 'active', 'user_id' => get_current_user_id(), 'page' => $request->get_param('page') ?: 1 ); $projects = PMG_Projects::get_projects($args); return new WP_REST_Response($projects, 200); } function pmg_rest_create_project(WP_REST_Request $request) { $data = $request->get_json_params(); $project_id = PMG_Projects::create_project($data); if ($project_id) { // 自动将创建者添加为项目管理员 PMG_Projects::add_project_member($project_id, get_current_user_id(), 'admin'); return new WP_REST_Response(array( 'id' => $project_id, 'message' => '项目创建成功' ), 201); } return new WP_REST_Response(array( 'error' => '项目创建失败' ), 500); }
-
-
- DHTMLX Gantt:功能强大,商业使用需授权 Frappe Gantt:开源免费,轻量级 Gantt-elastic:基于SVG,响应式设计 自定义实现:完全控制,但开发成本高
- <!-- public/views/gantt-view.php --> <div class="pmg-gantt-container"> <div class="pmg-gantt-toolbar"> <button class="pmg-btn pmg-btn-zoom-in">放大</button> <button class="pmg-btn pmg-btn-zoom-out">缩小</button> <button class="pmg-btn pmg-btn-today">今天</button> <select class="pmg-select-view"> <option value="Day">日视图</option> <option value="Week">周视图</option> <option value="Month">月视图</option> </select> </div> <div id="pmg-gantt-chart"></div> </div> // public/js/gantt-chart.js (function($) { 'use strict'; class PMG_GanttChart { constructor(containerId, projectId) { this.container = document.getElementById(containerId); this.projectId = projectId; this.gantt = null; this.init(); } init() { // 加载Frappe Gantt库 this.loadGanttLibrary().then(() => { this.setupGantt(); this.loadProjectData(); this.bindEvents(); }); } loadGanttLibrary() { return new Promise((resolve) => { if (typeof Gantt !== 'undefined') { resolve(); return; } // 动态加载CSS和JS const cssLink = document.createElement('link'); cssLink.rel = 'stylesheet'; cssLink.href = PMG_Gantt.pluginUrl + 'assets/lib/frappe-gantt/frappe-gantt.css'; document.head.appendChild(cssLink); const script = document.createElement('script'); script.src = PMG_Gantt.pluginUrl + 'assets/lib/frappe-gantt/frappe-gantt.min.js'; script.onload = resolve; document.head.appendChild(script); }); } setupGantt() { this.gantt = new Gantt(this.container, [], { header_height: 50, column_width: 30, step: 24, view_modes: ['Day', 'Week', 'Month'], bar_height: 20, bar_corner_radius: 3, arrow_curve: 5, padding: 18, view_mode: 'Week', date_format: 'YYYY-MM-DD', custom_popup_html: null, on_click: (task) => this.onTaskClick(task), on_date_change: (task, start, end) => this.onDateChange(task, start, end), on_progress_change: (task, progress) => this.onProgressChange(task, progress), on_view_change: (mode) => this.onViewChange(mode) }); } loadProjectData() { $.ajax({ url: PMG_Gantt.restUrl + 'pmg/v1/projects/' + this.projectId + '/gantt-data', method: 'GET', beforeSend: (xhr) => { xhr.setRequestHeader('X-WP-Nonce', PMG_Gantt.nonce); }, success: (response) => { this.transformAndLoadData(response); }, error: (error) => { console.error('加载甘特图数据失败:', error); } }); } transformAndLoadData(data) { // 转换数据为甘特图所需格式 const ganttTasks = data.tasks.map(task => ({ id: task.id.toString(), name: task.title, start: task.start_date, end: task.end_date, progress: task.progress, dependencies: task.dependencies ? task.dependencies.split(',') : [], custom_class: task.priority + '-priority' })); this.gantt.refresh(ganttTasks); } onTaskClick(task) { // 打开任务详情模态框 this.openTaskModal(task.id); } onDateChange(task, start, end) { // 更新任务日期 $.ajax({ url: PMG_Gantt.restUrl + 'pmg/v1/tasks/' + task.id, method: 'PUT', beforeSend: (xhr) => { xhr.setRequestHeader('X-WP-Nonce', PMG_Gantt.nonce); }, data: JSON.stringify({ start_date: start, end_date: end }), contentType: 'application/json', success: () => { console.log('任务日期更新成功'); }, error: (error) => { console.error('更新失败:', error); // 恢复原始日期 this.loadProjectData(); } }); } onProgressChange(task, progress) { // 更新任务进度 $.ajax({ url: PMG_Gantt.restUrl + 'pmg/v1/tasks/' + task.id + '/progress', method: 'PUT', beforeSend: (xhr) => { xhr.setRequestHeader('X-WP-Nonce', PMG_Gantt.nonce); }, data: JSON.stringify({ progress: progress }), contentType: 'application/json', success: () => { console.log('任务进度更新成功'); } }); } bindEvents() { // 工具栏事件绑定 $('.pmg-btn-zoom-in').on('click', () => this.zoomIn()); $('.pmg-btn-zoom-out').on('click', () => this.zoomOut()); $('.pmg-btn-today').on('click', () => this.scrollToToday()); $('.pmg-select-view').on('change', (e) => this.changeView(e.target.value)); } zoomIn() { const currentWidth = this.gantt.options.column_width; this.gantt.change_view_mode({ ...this.gantt.options, column_width: Math.min(currentWidth + 10, 100) }); } zoomOut() { const currentWidth = this.gantt.options.column_width; this.gantt.change_view_mode({ ...this.gantt.options, column_width: Math.max(currentWidth - 10, 10) }); } scrollToToday() { const today = new Date(); this.gantt.scroll_to(today); } changeView(mode) { this.gantt.change_view_mode(mode); } openTaskModal(taskId) { // 实现任务详情模态框 console.log('打开任务详情:', taskId); } } // 初始化甘特图 $(document).ready(function() { if ($('#pmg-gantt-chart').length) { const projectId = $('#pmg-gantt-chart').data('project-id'); window.pmgGantt = new PMG_GanttChart('pmg-gantt-chart', projectId); } }); })(jQuery);
- // 添加甘特图数据端点 add_action('rest_api_init', function() { register_rest_route('pmg/v1', '/projects/(?P<project_id>d+)/gantt-data', array( 'methods' => 'GET', 'callback' => 'pmg_rest_get_gantt_data', 'permission_callback' => function($request) { return pmg_check_project_access($request['project_id']); } )); }); function pmg_rest_get_gantt_data(WP_REST_Request $request) { $project_id = $request->get_param('project_id'); // 获取项目任务 $tasks = PMG_Tasks::get_project_tasks($project_id, true); // 格式化任务依赖关系 $formatted_tasks = array(); foreach ($tasks as $task) { $formatted_task = array( 'id' => $task->id, 'title' => $task->title, 'start_date' => $task->start_date, 'end_date' => $task->end_date, 'progress' => $task->progress, 'priority' => $task->priority, 'dependencies' => $task->dependencies ); $formatted_tasks[] = $formatted_task; } // 获取项目信息 $project = PMG_Projects::get_project($project_id); return new WP_REST_Response(array( 'project' => $project, 'tasks' => $formatted_tasks ), 200); }
- /* public/css/gantt-styles.css */ .pmg-gantt-container { background: #fff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); overflow: hidden; margin: 20px 0; } .pmg-gantt-toolbar { padding: 15px; background: #f8f9fa; border-bottom: 1px solid #e9ecef; display: flex; gap: 10px; align-items: center; } .pmg-btn { padding: 8px 16px; background: #007cba; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background 0.3s; } .pmg-btn:hover { background: #005a87; } .pmg-select-view { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; background: white; } /* 甘特图任务样式 */ .gantt .bar { rx: 4; ry: 4; } .gantt .bar-wrapper { cursor: pointer; } .gantt .bar-progress { fill: #4CAF50; } /* 优先级颜色 */ .high-priority .bar { fill: #ff6b6b; } .medium-priority .bar { fill: #4d96ff; } .low-priority .bar { fill: #6bcf7f; } /* 里程碑样式 */ .milestone .bar { fill: #ffd166; width: 10px; rx: 10; ry: 10; } /* 依赖线样式 */ .gantt .arrow { stroke: #666; stroke-width: 2; fill: none; }
-
-
- // includes/class-pmg-permissions.php class PMG_Permissions { // 定义项目角色 const ROLES = array( 'admin' => array( 'name' => '管理员', 'capabilities' => array( 'edit_project', 'delete_project', 'manage_members', 'create_tasks', 'edit_all_tasks', 'delete_tasks', 'assign_tasks' ) ), 'manager' => array( 'name' => '经理', 'capabilities' => array( 'edit_project', 'create_tasks', 'edit_all_tasks', 'assign_tasks' ) ), 'member' => array( 'name' => '成员', 'capabilities' => array( 'view_project', 'create_tasks', 'edit_own_tasks' ) ), 'viewer' => array( 'name' => '观察者', 'capabilities' => array( 'view_project' ) ) ); // 检查用户权限 public static function user_can($user_id, $project_id, $capability) { $user_role = self::get_user_role($user_id, $project_id); if (!$user_role) { return false; } // 管理员拥有所有权限 if ($user_role === 'admin') { return true; } // 检查角色权限 if (isset(self::ROLES[$user_role])) { return in_array($capability, self::ROLES[$user_role]['capabilities']); } return false; } // 获取用户在项目中的角色 public static function get_user_role($user_id, $project_id) { global $wpdb; $table = $wpdb->prefix . 'pmg_project_members'; $role = $wpdb->get_var($wpdb->prepare( "SELECT role FROM $table WHERE user_id = %d AND project_id = %d", $user_id, $project_id )); return $role ?: false; } // 添加项目成员 public static function add_project_member($project_id, $user_id, $role = 'member') { global $wpdb; $table = $wpdb->prefix . 'pmg_project_members'; // 检查是否已是成员 $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table WHERE project_id = %d AND user_id = %d", $project_id, $user_id )); if ($existing) { return $wpdb->update($table, array('role' => $role), array('id' => $existing) ); } return $wpdb->insert($table, array( 'project_id' => $project_id, 'user_id' => $user_id, 'role' => $role )); } // 获取项目成员列表 public static function get_project_members($project_id) { global $wpdb; $members_table = $wpdb->prefix . 'pmg_project_members'; $users_table = $wpdb->users; return $wpdb->get_results($wpdb->prepare( "SELECT m.*, u.display_name, u.user_email FROM $members_table m LEFT JOIN $users_table u ON m.user_id = u.ID WHERE m.project_id = %d ORDER BY m.joined_at ASC", $project_id )); } }
- // includes/class-pmg-collaboration.php class PMG_Collaboration { // 添加任务评论 public static function add_task_comment($task_id, $user_id, $content) { global $wpdb; $table = $wpdb->prefix . 'pmg_task_comments'; $comment_id = $wpdb->insert($table, array( 'task_id' => $task_id, 'user_id' => $user_id, 'content' => wp_kses_post($content), 'created_at' => current_time('mysql') )); if ($comment_id) { // 发送通知 self::notify_task_comment($task_id, $user_id, $content); } return $comment_id; } // 获取任务评论 public static function get_task_comments($task_id) { global $wpdb; $comments_table = $wpdb->prefix . 'pmg_task_comments'; $users_table = $wpdb->users; return $wpdb->get_results($wpdb->prepare( "SELECT c.*, u.display_name, u.user_email FROM $comments_table c LEFT JOIN $users_table u ON c.user_id = u.ID WHERE c.task_id = %d ORDER BY c.created_at ASC", $task_id )); } // 添加文件附件 public static function add_task_attachment($task_id, $user_id, $file_data) { // 使用WordPress媒体库上传文件 require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/media.php'); require_once(ABSPATH . 'wp-admin/includes/image.php'); $upload = wp_handle_upload($file_data, array('test_form' => false)); if (isset($upload['error'])) { return new WP_Error('upload_error', $upload['error']); } // 创建附件记录 global $wpdb; $table = $wpdb->prefix . 'pmg_task_attachments'; $attachment_id = $wpdb->insert($table, array( 'task_id' => $task_id,
本文提供了一份完整的指南,介绍如何在WordPress平台中通过代码二次开发,实现内嵌式项目管理与甘特图工具。我们将从需求分析开始,逐步讲解数据库设计、功能模块开发、甘特图集成、用户界面设计以及性能优化等关键环节,帮助开发者将常用互联网小工具功能无缝集成到WordPress系统中。
- 项目概述与需求分析
- WordPress开发环境搭建
- 数据库设计与数据模型
- 项目管理核心功能开发
- 甘特图集成与可视化
- 用户权限与团队协作
- 前端界面与用户体验优化
- 数据安全与性能优化
- 测试与部署指南
- 扩展与维护建议
随着远程工作和团队协作的普及,项目管理工具成为企业日常运营的重要组成部分。许多中小型企业使用WordPress作为其官方网站或内容管理系统,但缺乏集成的项目管理功能。通过开发内嵌式项目管理与甘特图工具,用户可以在熟悉的WordPress环境中管理项目,无需切换多个平台,提高工作效率。
- 项目管理:创建、编辑、删除项目,设置项目基本信息
- 任务管理:任务创建、分配、优先级设置、状态跟踪
- 甘特图可视化:直观展示项目时间线、任务依赖关系
- 团队协作:用户角色分配、任务评论、文件附件
- 进度跟踪:完成百分比、里程碑标记、时间跟踪
- 报告与分析:项目进度报告、团队绩效统计
- 核心框架:WordPress插件架构
- 前端技术:React/Vue.js(可选)、jQuery、HTML5、CSS3
- 图表库:DHTMLX Gantt、Frappe Gantt或自定义SVG实现
- 数据库:WordPress默认MySQL数据库
- 通信方式:REST API + AJAX
# 使用Local by Flywheel或Docker配置WordPress环境
# 安装必要工具
npm install -g @wordpress/env
wp-env start
# 或使用传统方法
# 1. 安装XAMPP/MAMP/WAMP
# 2. 下载最新WordPress
# 3. 配置数据库
# 使用Local by Flywheel或Docker配置WordPress环境
# 安装必要工具
npm install -g @wordpress/env
wp-env start
# 或使用传统方法
# 1. 安装XAMPP/MAMP/WAMP
# 2. 下载最新WordPress
# 3. 配置数据库
创建插件主文件 project-management-gantt.php:
<?php
/**
* Plugin Name: 项目管理与甘特图工具
* Plugin URI: https://yourwebsite.com/
* Description: 在WordPress中集成项目管理与甘特图功能
* Version: 1.0.0
* Author: 开发者名称
* License: GPL v2 or later
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('PMG_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('PMG_PLUGIN_URL', plugin_dir_url(__FILE__));
define('PMG_VERSION', '1.0.0');
// 初始化插件
require_once PMG_PLUGIN_DIR . 'includes/class-pmg-init.php';
PMG_Init::register();
project-management-gantt/
├── project-management-gantt.php # 主插件文件
├── includes/ # 核心类文件
│ ├── class-pmg-init.php # 初始化类
│ ├── class-pmg-database.php # 数据库处理
│ ├── class-pmg-projects.php # 项目管理类
│ ├── class-pmg-tasks.php # 任务管理类
│ └── class-pmg-gantt.php # 甘特图处理类
├── admin/ # 后台管理文件
│ ├── css/ # 管理端CSS
│ ├── js/ # 管理端JavaScript
│ └── views/ # 管理端视图
├── public/ # 前端文件
│ ├── css/ # 前端CSS
│ ├── js/ # 前端JavaScript
│ └── views/ # 前端视图
├── assets/ # 静态资源
│ ├── lib/ # 第三方库
│ └── images/ # 图片资源
└── templates/ # 模板文件
project-management-gantt/
├── project-management-gantt.php # 主插件文件
├── includes/ # 核心类文件
│ ├── class-pmg-init.php # 初始化类
│ ├── class-pmg-database.php # 数据库处理
│ ├── class-pmg-projects.php # 项目管理类
│ ├── class-pmg-tasks.php # 任务管理类
│ └── class-pmg-gantt.php # 甘特图处理类
├── admin/ # 后台管理文件
│ ├── css/ # 管理端CSS
│ ├── js/ # 管理端JavaScript
│ └── views/ # 管理端视图
├── public/ # 前端文件
│ ├── css/ # 前端CSS
│ ├── js/ # 前端JavaScript
│ └── views/ # 前端视图
├── assets/ # 静态资源
│ ├── lib/ # 第三方库
│ └── images/ # 图片资源
└── templates/ # 模板文件
为了避免与WordPress核心表冲突,我们创建独立的数据表:
// includes/class-pmg-database.php
class PMG_Database {
public static function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// 项目表
$projects_table = $wpdb->prefix . 'pmg_projects';
$projects_sql = "CREATE TABLE IF NOT EXISTS $projects_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
description text,
status varchar(50) DEFAULT 'active',
start_date date,
end_date date,
progress tinyint(3) DEFAULT 0,
created_by bigint(20) NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
// 任务表
$tasks_table = $wpdb->prefix . 'pmg_tasks';
$tasks_sql = "CREATE TABLE IF NOT EXISTS $tasks_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
project_id mediumint(9) NOT NULL,
parent_id mediumint(9) DEFAULT 0,
title varchar(255) NOT NULL,
description text,
start_date date,
end_date date,
duration int(11) DEFAULT 1,
progress tinyint(3) DEFAULT 0,
priority varchar(20) DEFAULT 'medium',
status varchar(50) DEFAULT 'pending',
assigned_to bigint(20),
sort_order int(11) DEFAULT 0,
dependencies text,
created_by bigint(20) NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY project_id (project_id)
) $charset_collate;";
// 项目成员表
$members_table = $wpdb->prefix . 'pmg_project_members';
$members_sql = "CREATE TABLE IF NOT EXISTS $members_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
project_id mediumint(9) NOT NULL,
user_id bigint(20) NOT NULL,
role varchar(50) DEFAULT 'member',
joined_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY project_user (project_id, user_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($projects_sql);
dbDelta($tasks_sql);
dbDelta($members_sql);
}
}
- 一个项目包含多个任务
- 一个任务可以有子任务(通过parent_id实现层级结构)
- 一个项目可以有多个成员
- 一个用户可以参与多个项目
- 添加适当的索引以提高查询性能
- 考虑大数据量下的分表策略
- 定期清理历史数据
// includes/class-pmg-projects.php
class PMG_Projects {
// 创建新项目
public static function create_project($data) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_projects';
$defaults = array(
'name' => '',
'description' => '',
'status' => 'active',
'start_date' => current_time('mysql'),
'end_date' => null,
'progress' => 0,
'created_by' => get_current_user_id()
);
$data = wp_parse_args($data, $defaults);
$wpdb->insert($table, $data);
return $wpdb->insert_id;
}
// 获取项目列表
public static function get_projects($args = array()) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_projects';
$defaults = array(
'status' => 'active',
'user_id' => 0,
'per_page' => 10,
'page' => 1
);
$args = wp_parse_args($args, $defaults);
$where = "WHERE status = '" . esc_sql($args['status']) . "'";
if ($args['user_id'] > 0) {
$members_table = $wpdb->prefix . 'pmg_project_members';
$where .= " AND id IN (SELECT project_id FROM $members_table WHERE user_id = " . intval($args['user_id']) . ")";
}
$offset = ($args['page'] - 1) * $args['per_page'];
$query = "SELECT * FROM $table $where ORDER BY created_at DESC LIMIT %d OFFSET %d";
return $wpdb->get_results($wpdb->prepare($query, $args['per_page'], $offset));
}
// 更新项目
public static function update_project($project_id, $data) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_projects';
return $wpdb->update($table, $data, array('id' => $project_id));
}
// 删除项目
public static function delete_project($project_id) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_projects';
// 同时删除相关任务和成员
$tasks_table = $wpdb->prefix . 'pmg_tasks';
$members_table = $wpdb->prefix . 'pmg_project_members';
$wpdb->delete($tasks_table, array('project_id' => $project_id));
$wpdb->delete($members_table, array('project_id' => $project_id));
return $wpdb->delete($table, array('id' => $project_id));
}
}
// includes/class-pmg-projects.php
class PMG_Projects {
// 创建新项目
public static function create_project($data) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_projects';
$defaults = array(
'name' => '',
'description' => '',
'status' => 'active',
'start_date' => current_time('mysql'),
'end_date' => null,
'progress' => 0,
'created_by' => get_current_user_id()
);
$data = wp_parse_args($data, $defaults);
$wpdb->insert($table, $data);
return $wpdb->insert_id;
}
// 获取项目列表
public static function get_projects($args = array()) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_projects';
$defaults = array(
'status' => 'active',
'user_id' => 0,
'per_page' => 10,
'page' => 1
);
$args = wp_parse_args($args, $defaults);
$where = "WHERE status = '" . esc_sql($args['status']) . "'";
if ($args['user_id'] > 0) {
$members_table = $wpdb->prefix . 'pmg_project_members';
$where .= " AND id IN (SELECT project_id FROM $members_table WHERE user_id = " . intval($args['user_id']) . ")";
}
$offset = ($args['page'] - 1) * $args['per_page'];
$query = "SELECT * FROM $table $where ORDER BY created_at DESC LIMIT %d OFFSET %d";
return $wpdb->get_results($wpdb->prepare($query, $args['per_page'], $offset));
}
// 更新项目
public static function update_project($project_id, $data) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_projects';
return $wpdb->update($table, $data, array('id' => $project_id));
}
// 删除项目
public static function delete_project($project_id) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_projects';
// 同时删除相关任务和成员
$tasks_table = $wpdb->prefix . 'pmg_tasks';
$members_table = $wpdb->prefix . 'pmg_project_members';
$wpdb->delete($tasks_table, array('project_id' => $project_id));
$wpdb->delete($members_table, array('project_id' => $project_id));
return $wpdb->delete($table, array('id' => $project_id));
}
}
// includes/class-pmg-tasks.php
class PMG_Tasks {
// 创建任务
public static function create_task($data) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_tasks';
$defaults = array(
'project_id' => 0,
'parent_id' => 0,
'title' => '',
'description' => '',
'start_date' => current_time('mysql'),
'end_date' => null,
'duration' => 1,
'progress' => 0,
'priority' => 'medium',
'status' => 'pending',
'assigned_to' => null,
'sort_order' => 0,
'dependencies' => '',
'created_by' => get_current_user_id()
);
$data = wp_parse_args($data, $defaults);
// 自动计算结束日期
if (empty($data['end_date']) && !empty($data['start_date']) && $data['duration'] > 0) {
$start_date = new DateTime($data['start_date']);
$start_date->modify('+' . ($data['duration'] - 1) . ' days');
$data['end_date'] = $start_date->format('Y-m-d');
}
$wpdb->insert($table, $data);
return $wpdb->insert_id;
}
// 获取项目任务树
public static function get_project_tasks($project_id, $flat = false) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_tasks';
$tasks = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $table WHERE project_id = %d ORDER BY parent_id, sort_order ASC",
$project_id
));
if ($flat) {
return $tasks;
}
// 构建层级结构
return self::build_task_tree($tasks);
}
// 构建任务树
private static function build_task_tree($tasks, $parent_id = 0) {
$tree = array();
foreach ($tasks as $task) {
if ($task->parent_id == $parent_id) {
$children = self::build_task_tree($tasks, $task->id);
if ($children) {
$task->children = $children;
}
$tree[] = $task;
}
}
return $tree;
}
// 更新任务进度
public static function update_task_progress($task_id, $progress) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_tasks';
$result = $wpdb->update($table,
array('progress' => $progress, 'updated_at' => current_time('mysql')),
array('id' => $task_id)
);
// 更新父任务和项目进度
if ($result) {
self::update_parent_progress($task_id);
}
return $result;
}
// 递归更新父任务进度
private static function update_parent_progress($task_id) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_tasks';
$task = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE id = %d", $task_id));
if ($task && $task->parent_id > 0) {
// 计算父任务下所有子任务的平均进度
$children = $wpdb->get_results($wpdb->prepare(
"SELECT progress FROM $table WHERE parent_id = %d",
$task->parent_id
));
if ($children) {
$total_progress = 0;
foreach ($children as $child) {
$total_progress += $child->progress;
}
$avg_progress = round($total_progress / count($children));
$wpdb->update($table,
array('progress' => $avg_progress),
array('id' => $task->parent_id)
);
// 递归更新
self::update_parent_progress($task->parent_id);
}
}
// 更新项目进度
if ($task) {
self::update_project_progress($task->project_id);
}
}
// 更新项目进度
private static function update_project_progress($project_id) {
global $wpdb;
$tasks_table = $wpdb->prefix . 'pmg_tasks';
$projects_table = $wpdb->prefix . 'pmg_projects';
// 计算项目下所有根任务的平均进度
$root_tasks = $wpdb->get_results($wpdb->prepare(
"SELECT progress FROM $tasks_table WHERE project_id = %d AND parent_id = 0",
$project_id
));
if ($root_tasks) {
$total_progress = 0;
foreach ($root_tasks as $task) {
$total_progress += $task->progress;
}
$avg_progress = round($total_progress / count($root_tasks));
$wpdb->update($projects_table,
array('progress' => $avg_progress),
array('id' => $project_id)
);
}
}
}
// includes/class-pmg-tasks.php
class PMG_Tasks {
// 创建任务
public static function create_task($data) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_tasks';
$defaults = array(
'project_id' => 0,
'parent_id' => 0,
'title' => '',
'description' => '',
'start_date' => current_time('mysql'),
'end_date' => null,
'duration' => 1,
'progress' => 0,
'priority' => 'medium',
'status' => 'pending',
'assigned_to' => null,
'sort_order' => 0,
'dependencies' => '',
'created_by' => get_current_user_id()
);
$data = wp_parse_args($data, $defaults);
// 自动计算结束日期
if (empty($data['end_date']) && !empty($data['start_date']) && $data['duration'] > 0) {
$start_date = new DateTime($data['start_date']);
$start_date->modify('+' . ($data['duration'] - 1) . ' days');
$data['end_date'] = $start_date->format('Y-m-d');
}
$wpdb->insert($table, $data);
return $wpdb->insert_id;
}
// 获取项目任务树
public static function get_project_tasks($project_id, $flat = false) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_tasks';
$tasks = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $table WHERE project_id = %d ORDER BY parent_id, sort_order ASC",
$project_id
));
if ($flat) {
return $tasks;
}
// 构建层级结构
return self::build_task_tree($tasks);
}
// 构建任务树
private static function build_task_tree($tasks, $parent_id = 0) {
$tree = array();
foreach ($tasks as $task) {
if ($task->parent_id == $parent_id) {
$children = self::build_task_tree($tasks, $task->id);
if ($children) {
$task->children = $children;
}
$tree[] = $task;
}
}
return $tree;
}
// 更新任务进度
public static function update_task_progress($task_id, $progress) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_tasks';
$result = $wpdb->update($table,
array('progress' => $progress, 'updated_at' => current_time('mysql')),
array('id' => $task_id)
);
// 更新父任务和项目进度
if ($result) {
self::update_parent_progress($task_id);
}
return $result;
}
// 递归更新父任务进度
private static function update_parent_progress($task_id) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_tasks';
$task = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE id = %d", $task_id));
if ($task && $task->parent_id > 0) {
// 计算父任务下所有子任务的平均进度
$children = $wpdb->get_results($wpdb->prepare(
"SELECT progress FROM $table WHERE parent_id = %d",
$task->parent_id
));
if ($children) {
$total_progress = 0;
foreach ($children as $child) {
$total_progress += $child->progress;
}
$avg_progress = round($total_progress / count($children));
$wpdb->update($table,
array('progress' => $avg_progress),
array('id' => $task->parent_id)
);
// 递归更新
self::update_parent_progress($task->parent_id);
}
}
// 更新项目进度
if ($task) {
self::update_project_progress($task->project_id);
}
}
// 更新项目进度
private static function update_project_progress($project_id) {
global $wpdb;
$tasks_table = $wpdb->prefix . 'pmg_tasks';
$projects_table = $wpdb->prefix . 'pmg_projects';
// 计算项目下所有根任务的平均进度
$root_tasks = $wpdb->get_results($wpdb->prepare(
"SELECT progress FROM $tasks_table WHERE project_id = %d AND parent_id = 0",
$project_id
));
if ($root_tasks) {
$total_progress = 0;
foreach ($root_tasks as $task) {
$total_progress += $task->progress;
}
$avg_progress = round($total_progress / count($root_tasks));
$wpdb->update($projects_table,
array('progress' => $avg_progress),
array('id' => $project_id)
);
}
}
}
// 注册REST API路由
add_action('rest_api_init', function() {
// 项目相关端点
register_rest_route('pmg/v1', '/projects', array(
array(
'methods' => 'GET',
'callback' => 'pmg_rest_get_projects',
'permission_callback' => function() {
return current_user_can('read');
}
),
array(
'methods' => 'POST',
'callback' => 'pmg_rest_create_project',
'permission_callback' => function() {
return current_user_can('edit_posts');
}
)
));
// 任务相关端点
register_rest_route('pmg/v1', '/projects/(?P<project_id>d+)/tasks', array(
'methods' => 'GET',
'callback' => 'pmg_rest_get_tasks',
'permission_callback' => function($request) {
return pmg_check_project_access($request['project_id']);
}
));
register_rest_route('pmg/v1', '/tasks/(?P<task_id>d+)', array(
array(
'methods' => 'PUT',
'callback' => 'pmg_rest_update_task',
'permission_callback' => function($request) {
return pmg_check_task_access($request['task_id']);
}
),
array(
'methods' => 'DELETE',
'callback' => 'pmg_rest_delete_task',
'permission_callback' => function($request) {
return current_user_can('delete_posts');
}
)
));
});
// REST API回调函数示例
function pmg_rest_get_projects(WP_REST_Request $request) {
$args = array(
'status' => $request->get_param('status') ?: 'active',
'user_id' => get_current_user_id(),
'page' => $request->get_param('page') ?: 1
);
$projects = PMG_Projects::get_projects($args);
return new WP_REST_Response($projects, 200);
}
function pmg_rest_create_project(WP_REST_Request $request) {
$data = $request->get_json_params();
$project_id = PMG_Projects::create_project($data);
if ($project_id) {
// 自动将创建者添加为项目管理员
PMG_Projects::add_project_member($project_id, get_current_user_id(), 'admin');
return new WP_REST_Response(array(
'id' => $project_id,
'message' => '项目创建成功'
), 201);
}
return new WP_REST_Response(array(
'error' => '项目创建失败'
), 500);
}
// 注册REST API路由
add_action('rest_api_init', function() {
// 项目相关端点
register_rest_route('pmg/v1', '/projects', array(
array(
'methods' => 'GET',
'callback' => 'pmg_rest_get_projects',
'permission_callback' => function() {
return current_user_can('read');
}
),
array(
'methods' => 'POST',
'callback' => 'pmg_rest_create_project',
'permission_callback' => function() {
return current_user_can('edit_posts');
}
)
));
// 任务相关端点
register_rest_route('pmg/v1', '/projects/(?P<project_id>d+)/tasks', array(
'methods' => 'GET',
'callback' => 'pmg_rest_get_tasks',
'permission_callback' => function($request) {
return pmg_check_project_access($request['project_id']);
}
));
register_rest_route('pmg/v1', '/tasks/(?P<task_id>d+)', array(
array(
'methods' => 'PUT',
'callback' => 'pmg_rest_update_task',
'permission_callback' => function($request) {
return pmg_check_task_access($request['task_id']);
}
),
array(
'methods' => 'DELETE',
'callback' => 'pmg_rest_delete_task',
'permission_callback' => function($request) {
return current_user_can('delete_posts');
}
)
));
});
// REST API回调函数示例
function pmg_rest_get_projects(WP_REST_Request $request) {
$args = array(
'status' => $request->get_param('status') ?: 'active',
'user_id' => get_current_user_id(),
'page' => $request->get_param('page') ?: 1
);
$projects = PMG_Projects::get_projects($args);
return new WP_REST_Response($projects, 200);
}
function pmg_rest_create_project(WP_REST_Request $request) {
$data = $request->get_json_params();
$project_id = PMG_Projects::create_project($data);
if ($project_id) {
// 自动将创建者添加为项目管理员
PMG_Projects::add_project_member($project_id, get_current_user_id(), 'admin');
return new WP_REST_Response(array(
'id' => $project_id,
'message' => '项目创建成功'
), 201);
}
return new WP_REST_Response(array(
'error' => '项目创建失败'
), 500);
}
- DHTMLX Gantt:功能强大,商业使用需授权
- Frappe Gantt:开源免费,轻量级
- Gantt-elastic:基于SVG,响应式设计
- 自定义实现:完全控制,但开发成本高
<!-- public/views/gantt-view.php -->
<div class="pmg-gantt-container">
<div class="pmg-gantt-toolbar">
<button class="pmg-btn pmg-btn-zoom-in">放大</button>
<button class="pmg-btn pmg-btn-zoom-out">缩小</button>
<button class="pmg-btn pmg-btn-today">今天</button>
<select class="pmg-select-view">
<option value="Day">日视图</option>
<option value="Week">周视图</option>
<option value="Month">月视图</option>
</select>
</div>
<div id="pmg-gantt-chart"></div>
</div>
// public/js/gantt-chart.js
(function($) {
'use strict';
class PMG_GanttChart {
constructor(containerId, projectId) {
this.container = document.getElementById(containerId);
this.projectId = projectId;
this.gantt = null;
this.init();
}
init() {
// 加载Frappe Gantt库
this.loadGanttLibrary().then(() => {
this.setupGantt();
this.loadProjectData();
this.bindEvents();
});
}
loadGanttLibrary() {
return new Promise((resolve) => {
if (typeof Gantt !== 'undefined') {
resolve();
return;
}
// 动态加载CSS和JS
const cssLink = document.createElement('link');
cssLink.rel = 'stylesheet';
cssLink.href = PMG_Gantt.pluginUrl + 'assets/lib/frappe-gantt/frappe-gantt.css';
document.head.appendChild(cssLink);
const script = document.createElement('script');
script.src = PMG_Gantt.pluginUrl + 'assets/lib/frappe-gantt/frappe-gantt.min.js';
script.onload = resolve;
document.head.appendChild(script);
});
}
setupGantt() {
this.gantt = new Gantt(this.container, [], {
header_height: 50,
column_width: 30,
step: 24,
view_modes: ['Day', 'Week', 'Month'],
bar_height: 20,
bar_corner_radius: 3,
arrow_curve: 5,
padding: 18,
view_mode: 'Week',
date_format: 'YYYY-MM-DD',
custom_popup_html: null,
on_click: (task) => this.onTaskClick(task),
on_date_change: (task, start, end) => this.onDateChange(task, start, end),
on_progress_change: (task, progress) => this.onProgressChange(task, progress),
on_view_change: (mode) => this.onViewChange(mode)
});
}
loadProjectData() {
$.ajax({
url: PMG_Gantt.restUrl + 'pmg/v1/projects/' + this.projectId + '/gantt-data',
method: 'GET',
beforeSend: (xhr) => {
xhr.setRequestHeader('X-WP-Nonce', PMG_Gantt.nonce);
},
success: (response) => {
this.transformAndLoadData(response);
},
error: (error) => {
console.error('加载甘特图数据失败:', error);
}
});
}
transformAndLoadData(data) {
// 转换数据为甘特图所需格式
const ganttTasks = data.tasks.map(task => ({
id: task.id.toString(),
name: task.title,
start: task.start_date,
end: task.end_date,
progress: task.progress,
dependencies: task.dependencies ? task.dependencies.split(',') : [],
custom_class: task.priority + '-priority'
}));
this.gantt.refresh(ganttTasks);
}
onTaskClick(task) {
// 打开任务详情模态框
this.openTaskModal(task.id);
}
onDateChange(task, start, end) {
// 更新任务日期
$.ajax({
url: PMG_Gantt.restUrl + 'pmg/v1/tasks/' + task.id,
method: 'PUT',
beforeSend: (xhr) => {
xhr.setRequestHeader('X-WP-Nonce', PMG_Gantt.nonce);
},
data: JSON.stringify({
start_date: start,
end_date: end
}),
contentType: 'application/json',
success: () => {
console.log('任务日期更新成功');
},
error: (error) => {
console.error('更新失败:', error);
// 恢复原始日期
this.loadProjectData();
}
});
}
onProgressChange(task, progress) {
// 更新任务进度
$.ajax({
url: PMG_Gantt.restUrl + 'pmg/v1/tasks/' + task.id + '/progress',
method: 'PUT',
beforeSend: (xhr) => {
xhr.setRequestHeader('X-WP-Nonce', PMG_Gantt.nonce);
},
data: JSON.stringify({
progress: progress
}),
contentType: 'application/json',
success: () => {
console.log('任务进度更新成功');
}
});
}
bindEvents() {
// 工具栏事件绑定
$('.pmg-btn-zoom-in').on('click', () => this.zoomIn());
$('.pmg-btn-zoom-out').on('click', () => this.zoomOut());
$('.pmg-btn-today').on('click', () => this.scrollToToday());
$('.pmg-select-view').on('change', (e) => this.changeView(e.target.value));
}
zoomIn() {
const currentWidth = this.gantt.options.column_width;
this.gantt.change_view_mode({
...this.gantt.options,
column_width: Math.min(currentWidth + 10, 100)
});
}
zoomOut() {
const currentWidth = this.gantt.options.column_width;
this.gantt.change_view_mode({
...this.gantt.options,
column_width: Math.max(currentWidth - 10, 10)
});
}
scrollToToday() {
const today = new Date();
this.gantt.scroll_to(today);
}
changeView(mode) {
this.gantt.change_view_mode(mode);
}
openTaskModal(taskId) {
// 实现任务详情模态框
console.log('打开任务详情:', taskId);
}
}
// 初始化甘特图
$(document).ready(function() {
if ($('#pmg-gantt-chart').length) {
const projectId = $('#pmg-gantt-chart').data('project-id');
window.pmgGantt = new PMG_GanttChart('pmg-gantt-chart', projectId);
}
});
})(jQuery);
<!-- public/views/gantt-view.php -->
<div class="pmg-gantt-container">
<div class="pmg-gantt-toolbar">
<button class="pmg-btn pmg-btn-zoom-in">放大</button>
<button class="pmg-btn pmg-btn-zoom-out">缩小</button>
<button class="pmg-btn pmg-btn-today">今天</button>
<select class="pmg-select-view">
<option value="Day">日视图</option>
<option value="Week">周视图</option>
<option value="Month">月视图</option>
</select>
</div>
<div id="pmg-gantt-chart"></div>
</div>
// public/js/gantt-chart.js
(function($) {
'use strict';
class PMG_GanttChart {
constructor(containerId, projectId) {
this.container = document.getElementById(containerId);
this.projectId = projectId;
this.gantt = null;
this.init();
}
init() {
// 加载Frappe Gantt库
this.loadGanttLibrary().then(() => {
this.setupGantt();
this.loadProjectData();
this.bindEvents();
});
}
loadGanttLibrary() {
return new Promise((resolve) => {
if (typeof Gantt !== 'undefined') {
resolve();
return;
}
// 动态加载CSS和JS
const cssLink = document.createElement('link');
cssLink.rel = 'stylesheet';
cssLink.href = PMG_Gantt.pluginUrl + 'assets/lib/frappe-gantt/frappe-gantt.css';
document.head.appendChild(cssLink);
const script = document.createElement('script');
script.src = PMG_Gantt.pluginUrl + 'assets/lib/frappe-gantt/frappe-gantt.min.js';
script.onload = resolve;
document.head.appendChild(script);
});
}
setupGantt() {
this.gantt = new Gantt(this.container, [], {
header_height: 50,
column_width: 30,
step: 24,
view_modes: ['Day', 'Week', 'Month'],
bar_height: 20,
bar_corner_radius: 3,
arrow_curve: 5,
padding: 18,
view_mode: 'Week',
date_format: 'YYYY-MM-DD',
custom_popup_html: null,
on_click: (task) => this.onTaskClick(task),
on_date_change: (task, start, end) => this.onDateChange(task, start, end),
on_progress_change: (task, progress) => this.onProgressChange(task, progress),
on_view_change: (mode) => this.onViewChange(mode)
});
}
loadProjectData() {
$.ajax({
url: PMG_Gantt.restUrl + 'pmg/v1/projects/' + this.projectId + '/gantt-data',
method: 'GET',
beforeSend: (xhr) => {
xhr.setRequestHeader('X-WP-Nonce', PMG_Gantt.nonce);
},
success: (response) => {
this.transformAndLoadData(response);
},
error: (error) => {
console.error('加载甘特图数据失败:', error);
}
});
}
transformAndLoadData(data) {
// 转换数据为甘特图所需格式
const ganttTasks = data.tasks.map(task => ({
id: task.id.toString(),
name: task.title,
start: task.start_date,
end: task.end_date,
progress: task.progress,
dependencies: task.dependencies ? task.dependencies.split(',') : [],
custom_class: task.priority + '-priority'
}));
this.gantt.refresh(ganttTasks);
}
onTaskClick(task) {
// 打开任务详情模态框
this.openTaskModal(task.id);
}
onDateChange(task, start, end) {
// 更新任务日期
$.ajax({
url: PMG_Gantt.restUrl + 'pmg/v1/tasks/' + task.id,
method: 'PUT',
beforeSend: (xhr) => {
xhr.setRequestHeader('X-WP-Nonce', PMG_Gantt.nonce);
},
data: JSON.stringify({
start_date: start,
end_date: end
}),
contentType: 'application/json',
success: () => {
console.log('任务日期更新成功');
},
error: (error) => {
console.error('更新失败:', error);
// 恢复原始日期
this.loadProjectData();
}
});
}
onProgressChange(task, progress) {
// 更新任务进度
$.ajax({
url: PMG_Gantt.restUrl + 'pmg/v1/tasks/' + task.id + '/progress',
method: 'PUT',
beforeSend: (xhr) => {
xhr.setRequestHeader('X-WP-Nonce', PMG_Gantt.nonce);
},
data: JSON.stringify({
progress: progress
}),
contentType: 'application/json',
success: () => {
console.log('任务进度更新成功');
}
});
}
bindEvents() {
// 工具栏事件绑定
$('.pmg-btn-zoom-in').on('click', () => this.zoomIn());
$('.pmg-btn-zoom-out').on('click', () => this.zoomOut());
$('.pmg-btn-today').on('click', () => this.scrollToToday());
$('.pmg-select-view').on('change', (e) => this.changeView(e.target.value));
}
zoomIn() {
const currentWidth = this.gantt.options.column_width;
this.gantt.change_view_mode({
...this.gantt.options,
column_width: Math.min(currentWidth + 10, 100)
});
}
zoomOut() {
const currentWidth = this.gantt.options.column_width;
this.gantt.change_view_mode({
...this.gantt.options,
column_width: Math.max(currentWidth - 10, 10)
});
}
scrollToToday() {
const today = new Date();
this.gantt.scroll_to(today);
}
changeView(mode) {
this.gantt.change_view_mode(mode);
}
openTaskModal(taskId) {
// 实现任务详情模态框
console.log('打开任务详情:', taskId);
}
}
// 初始化甘特图
$(document).ready(function() {
if ($('#pmg-gantt-chart').length) {
const projectId = $('#pmg-gantt-chart').data('project-id');
window.pmgGantt = new PMG_GanttChart('pmg-gantt-chart', projectId);
}
});
})(jQuery);
// 添加甘特图数据端点
add_action('rest_api_init', function() {
register_rest_route('pmg/v1', '/projects/(?P<project_id>d+)/gantt-data', array(
'methods' => 'GET',
'callback' => 'pmg_rest_get_gantt_data',
'permission_callback' => function($request) {
return pmg_check_project_access($request['project_id']);
}
));
});
function pmg_rest_get_gantt_data(WP_REST_Request $request) {
$project_id = $request->get_param('project_id');
// 获取项目任务
$tasks = PMG_Tasks::get_project_tasks($project_id, true);
// 格式化任务依赖关系
$formatted_tasks = array();
foreach ($tasks as $task) {
$formatted_task = array(
'id' => $task->id,
'title' => $task->title,
'start_date' => $task->start_date,
'end_date' => $task->end_date,
'progress' => $task->progress,
'priority' => $task->priority,
'dependencies' => $task->dependencies
);
$formatted_tasks[] = $formatted_task;
}
// 获取项目信息
$project = PMG_Projects::get_project($project_id);
return new WP_REST_Response(array(
'project' => $project,
'tasks' => $formatted_tasks
), 200);
}
// 添加甘特图数据端点
add_action('rest_api_init', function() {
register_rest_route('pmg/v1', '/projects/(?P<project_id>d+)/gantt-data', array(
'methods' => 'GET',
'callback' => 'pmg_rest_get_gantt_data',
'permission_callback' => function($request) {
return pmg_check_project_access($request['project_id']);
}
));
});
function pmg_rest_get_gantt_data(WP_REST_Request $request) {
$project_id = $request->get_param('project_id');
// 获取项目任务
$tasks = PMG_Tasks::get_project_tasks($project_id, true);
// 格式化任务依赖关系
$formatted_tasks = array();
foreach ($tasks as $task) {
$formatted_task = array(
'id' => $task->id,
'title' => $task->title,
'start_date' => $task->start_date,
'end_date' => $task->end_date,
'progress' => $task->progress,
'priority' => $task->priority,
'dependencies' => $task->dependencies
);
$formatted_tasks[] = $formatted_task;
}
// 获取项目信息
$project = PMG_Projects::get_project($project_id);
return new WP_REST_Response(array(
'project' => $project,
'tasks' => $formatted_tasks
), 200);
}
/* public/css/gantt-styles.css */
.pmg-gantt-container {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
margin: 20px 0;
}
.pmg-gantt-toolbar {
padding: 15px;
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
display: flex;
gap: 10px;
align-items: center;
}
.pmg-btn {
padding: 8px 16px;
background: #007cba;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
.pmg-btn:hover {
background: #005a87;
}
.pmg-select-view {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
}
/* 甘特图任务样式 */
.gantt .bar {
rx: 4;
ry: 4;
}
.gantt .bar-wrapper {
cursor: pointer;
}
.gantt .bar-progress {
fill: #4CAF50;
}
/* 优先级颜色 */
.high-priority .bar {
fill: #ff6b6b;
}
.medium-priority .bar {
fill: #4d96ff;
}
.low-priority .bar {
fill: #6bcf7f;
}
/* 里程碑样式 */
.milestone .bar {
fill: #ffd166;
width: 10px;
rx: 10;
ry: 10;
}
/* 依赖线样式 */
.gantt .arrow {
stroke: #666;
stroke-width: 2;
fill: none;
}
/* public/css/gantt-styles.css */
.pmg-gantt-container {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
margin: 20px 0;
}
.pmg-gantt-toolbar {
padding: 15px;
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
display: flex;
gap: 10px;
align-items: center;
}
.pmg-btn {
padding: 8px 16px;
background: #007cba;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
.pmg-btn:hover {
background: #005a87;
}
.pmg-select-view {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
}
/* 甘特图任务样式 */
.gantt .bar {
rx: 4;
ry: 4;
}
.gantt .bar-wrapper {
cursor: pointer;
}
.gantt .bar-progress {
fill: #4CAF50;
}
/* 优先级颜色 */
.high-priority .bar {
fill: #ff6b6b;
}
.medium-priority .bar {
fill: #4d96ff;
}
.low-priority .bar {
fill: #6bcf7f;
}
/* 里程碑样式 */
.milestone .bar {
fill: #ffd166;
width: 10px;
rx: 10;
ry: 10;
}
/* 依赖线样式 */
.gantt .arrow {
stroke: #666;
stroke-width: 2;
fill: none;
}
// includes/class-pmg-permissions.php
class PMG_Permissions {
// 定义项目角色
const ROLES = array(
'admin' => array(
'name' => '管理员',
'capabilities' => array(
'edit_project',
'delete_project',
'manage_members',
'create_tasks',
'edit_all_tasks',
'delete_tasks',
'assign_tasks'
)
),
'manager' => array(
'name' => '经理',
'capabilities' => array(
'edit_project',
'create_tasks',
'edit_all_tasks',
'assign_tasks'
)
),
'member' => array(
'name' => '成员',
'capabilities' => array(
'view_project',
'create_tasks',
'edit_own_tasks'
)
),
'viewer' => array(
'name' => '观察者',
'capabilities' => array(
'view_project'
)
)
);
// 检查用户权限
public static function user_can($user_id, $project_id, $capability) {
$user_role = self::get_user_role($user_id, $project_id);
if (!$user_role) {
return false;
}
// 管理员拥有所有权限
if ($user_role === 'admin') {
return true;
}
// 检查角色权限
if (isset(self::ROLES[$user_role])) {
return in_array($capability, self::ROLES[$user_role]['capabilities']);
}
return false;
}
// 获取用户在项目中的角色
public static function get_user_role($user_id, $project_id) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_project_members';
$role = $wpdb->get_var($wpdb->prepare(
"SELECT role FROM $table WHERE user_id = %d AND project_id = %d",
$user_id, $project_id
));
return $role ?: false;
}
// 添加项目成员
public static function add_project_member($project_id, $user_id, $role = 'member') {
global $wpdb;
$table = $wpdb->prefix . 'pmg_project_members';
// 检查是否已是成员
$existing = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM $table WHERE project_id = %d AND user_id = %d",
$project_id, $user_id
));
if ($existing) {
return $wpdb->update($table,
array('role' => $role),
array('id' => $existing)
);
}
return $wpdb->insert($table, array(
'project_id' => $project_id,
'user_id' => $user_id,
'role' => $role
));
}
// 获取项目成员列表
public static function get_project_members($project_id) {
global $wpdb;
$members_table = $wpdb->prefix . 'pmg_project_members';
$users_table = $wpdb->users;
return $wpdb->get_results($wpdb->prepare(
"SELECT m.*, u.display_name, u.user_email
FROM $members_table m
LEFT JOIN $users_table u ON m.user_id = u.ID
WHERE m.project_id = %d
ORDER BY m.joined_at ASC",
$project_id
));
}
}
// includes/class-pmg-permissions.php
class PMG_Permissions {
// 定义项目角色
const ROLES = array(
'admin' => array(
'name' => '管理员',
'capabilities' => array(
'edit_project',
'delete_project',
'manage_members',
'create_tasks',
'edit_all_tasks',
'delete_tasks',
'assign_tasks'
)
),
'manager' => array(
'name' => '经理',
'capabilities' => array(
'edit_project',
'create_tasks',
'edit_all_tasks',
'assign_tasks'
)
),
'member' => array(
'name' => '成员',
'capabilities' => array(
'view_project',
'create_tasks',
'edit_own_tasks'
)
),
'viewer' => array(
'name' => '观察者',
'capabilities' => array(
'view_project'
)
)
);
// 检查用户权限
public static function user_can($user_id, $project_id, $capability) {
$user_role = self::get_user_role($user_id, $project_id);
if (!$user_role) {
return false;
}
// 管理员拥有所有权限
if ($user_role === 'admin') {
return true;
}
// 检查角色权限
if (isset(self::ROLES[$user_role])) {
return in_array($capability, self::ROLES[$user_role]['capabilities']);
}
return false;
}
// 获取用户在项目中的角色
public static function get_user_role($user_id, $project_id) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_project_members';
$role = $wpdb->get_var($wpdb->prepare(
"SELECT role FROM $table WHERE user_id = %d AND project_id = %d",
$user_id, $project_id
));
return $role ?: false;
}
// 添加项目成员
public static function add_project_member($project_id, $user_id, $role = 'member') {
global $wpdb;
$table = $wpdb->prefix . 'pmg_project_members';
// 检查是否已是成员
$existing = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM $table WHERE project_id = %d AND user_id = %d",
$project_id, $user_id
));
if ($existing) {
return $wpdb->update($table,
array('role' => $role),
array('id' => $existing)
);
}
return $wpdb->insert($table, array(
'project_id' => $project_id,
'user_id' => $user_id,
'role' => $role
));
}
// 获取项目成员列表
public static function get_project_members($project_id) {
global $wpdb;
$members_table = $wpdb->prefix . 'pmg_project_members';
$users_table = $wpdb->users;
return $wpdb->get_results($wpdb->prepare(
"SELECT m.*, u.display_name, u.user_email
FROM $members_table m
LEFT JOIN $users_table u ON m.user_id = u.ID
WHERE m.project_id = %d
ORDER BY m.joined_at ASC",
$project_id
));
}
}
// includes/class-pmg-collaboration.php
class PMG_Collaboration {
// 添加任务评论
public static function add_task_comment($task_id, $user_id, $content) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_task_comments';
$comment_id = $wpdb->insert($table, array(
'task_id' => $task_id,
'user_id' => $user_id,
'content' => wp_kses_post($content),
'created_at' => current_time('mysql')
));
if ($comment_id) {
// 发送通知
self::notify_task_comment($task_id, $user_id, $content);
}
return $comment_id;
}
// 获取任务评论
public static function get_task_comments($task_id) {
global $wpdb;
$comments_table = $wpdb->prefix . 'pmg_task_comments';
$users_table = $wpdb->users;
return $wpdb->get_results($wpdb->prepare(
"SELECT c.*, u.display_name, u.user_email
FROM $comments_table c
LEFT JOIN $users_table u ON c.user_id = u.ID
WHERE c.task_id = %d
ORDER BY c.created_at ASC",
$task_id
));
}
// 添加文件附件
public static function add_task_attachment($task_id, $user_id, $file_data) {
// 使用WordPress媒体库上传文件
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/media.php');
require_once(ABSPATH . 'wp-admin/includes/image.php');
$upload = wp_handle_upload($file_data, array('test_form' => false));
if (isset($upload['error'])) {
return new WP_Error('upload_error', $upload['error']);
}
// 创建附件记录
global $wpdb;
$table = $wpdb->prefix . 'pmg_task_attachments';
$attachment_id = $wpdb->insert($table, array(
'task_id' => $task_id,
// includes/class-pmg-collaboration.php
class PMG_Collaboration {
// 添加任务评论
public static function add_task_comment($task_id, $user_id, $content) {
global $wpdb;
$table = $wpdb->prefix . 'pmg_task_comments';
$comment_id = $wpdb->insert($table, array(
'task_id' => $task_id,
'user_id' => $user_id,
'content' => wp_kses_post($content),
'created_at' => current_time('mysql')
));
if ($comment_id) {
// 发送通知
self::notify_task_comment($task_id, $user_id, $content);
}
return $comment_id;
}
// 获取任务评论
public static function get_task_comments($task_id) {
global $wpdb;
$comments_table = $wpdb->prefix . 'pmg_task_comments';
$users_table = $wpdb->users;
return $wpdb->get_results($wpdb->prepare(
"SELECT c.*, u.display_name, u.user_email
FROM $comments_table c
LEFT JOIN $users_table u ON c.user_id = u.ID
WHERE c.task_id = %d
ORDER BY c.created_at ASC",
$task_id
));
}
// 添加文件附件
public static function add_task_attachment($task_id, $user_id, $file_data) {
// 使用WordPress媒体库上传文件
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/media.php');
require_once(ABSPATH . 'wp-admin/includes/image.php');
$upload = wp_handle_upload($file_data, array('test_form' => false));
if (isset($upload['error'])) {
return new WP_Error('upload_error', $upload['error']);
}
// 创建附件记录
global $wpdb;
$table = $wpdb->prefix . 'pmg_task_attachments';
$attachment_id = $wpdb->insert($table, array(
'task_id' => $task_id,


