文章目录[隐藏]
详细指南:在WordPress中开发内嵌式项目管理与甘特图工具
摘要
本文提供了一份完整的指南,介绍如何在WordPress平台中通过代码二次开发,实现内嵌式项目管理与甘特图工具。我们将从需求分析开始,逐步讲解数据库设计、功能模块开发、甘特图集成、用户界面设计以及性能优化等关键环节,帮助开发者将常用互联网小工具功能无缝集成到WordPress系统中。
目录
- 项目概述与需求分析
- WordPress开发环境搭建
- 数据库设计与数据模型
- 项目管理核心功能开发
- 甘特图集成与可视化
- 用户权限与团队协作
- 前端界面与用户体验优化
- 数据安全与性能优化
- 测试与部署指南
- 扩展与维护建议
1. 项目概述与需求分析
1.1 项目背景
随着远程工作和团队协作的普及,项目管理工具成为企业日常运营的重要组成部分。许多中小型企业使用WordPress作为其官方网站或内容管理系统,但缺乏集成的项目管理功能。通过开发内嵌式项目管理与甘特图工具,用户可以在熟悉的WordPress环境中管理项目,无需切换多个平台,提高工作效率。
1.2 功能需求
- 项目管理:创建、编辑、删除项目,设置项目基本信息
- 任务管理:任务创建、分配、优先级设置、状态跟踪
- 甘特图可视化:直观展示项目时间线、任务依赖关系
- 团队协作:用户角色分配、任务评论、文件附件
- 进度跟踪:完成百分比、里程碑标记、时间跟踪
- 报告与分析:项目进度报告、团队绩效统计
1.3 技术选型
- 核心框架:WordPress插件架构
- 前端技术:React/Vue.js(可选)、jQuery、HTML5、CSS3
- 图表库:DHTMLX Gantt、Frappe Gantt或自定义SVG实现
- 数据库:WordPress默认MySQL数据库
- 通信方式:REST API + AJAX
2. WordPress开发环境搭建
2.1 本地开发环境配置
# 使用Local by Flywheel或Docker配置WordPress环境
# 安装必要工具
npm install -g @wordpress/env
wp-env start
# 或使用传统方法
# 1. 安装XAMPP/MAMP/WAMP
# 2. 下载最新WordPress
# 3. 配置数据库
2.2 插件基础结构
创建插件主文件 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();
2.3 插件目录结构
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/ # 模板文件
3. 数据库设计与数据模型
3.1 自定义数据表设计
为了避免与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);
}
}
3.2 数据关系模型
- 一个项目包含多个任务
- 一个任务可以有子任务(通过parent_id实现层级结构)
- 一个项目可以有多个成员
- 一个用户可以参与多个项目
3.3 数据表优化考虑
- 添加适当的索引以提高查询性能
- 考虑大数据量下的分表策略
- 定期清理历史数据
4. 项目管理核心功能开发
4.1 项目CRUD操作
// 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));
}
}
4.2 任务管理功能
// 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)
);
}
}
}
4.3 REST API端点
// 注册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);
}
5. 甘特图集成与可视化
5.1 甘特图库选择与集成
5.1.1 选择适合的甘特图库
- DHTMLX Gantt:功能强大,商业使用需授权
- Frappe Gantt:开源免费,轻量级
- Gantt-elastic:基于SVG,响应式设计
- 自定义实现:完全控制,但开发成本高
5.2 使用Frappe Gantt实现
<!-- 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);
5.3 甘特图数据API端点
// 添加甘特图数据端点
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);
}
5.4 甘特图样式定制
/* 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;
}
6. 用户权限与团队协作
6.1 用户角色系统
// 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
));
}
}
6.2 团队协作功能
// 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,
