作为一个前端,我最近发现mzgantt的插件系统简直太强了!5分钟就能开发一个专业级插件
最近在做一个内部项目管理系统,产品经理突然要求给甘特图加一堆新功能:实时协作、高级统计、自定义报表...正当我头大的时候,发现了mzgantt的插件系统,简直像打开了新世界的大门!
今天就来手把手教你如何用mzgantt的插件系统,快速扩展甘特图功能,让你的项目体验直接起飞!
一、 为什么需要插件系统?
先看几个真实场景:
- 产品经理:"能不能加个任务统计面板?"
- 测试同学:"需要依赖关系可视化功能"
- 项目经理:"想要多人在线协作编辑"
- UI设计师:"需要支持暗色主题切换"
mzgantt的插件系统一招全搞定!来看看插件前后的对比:
Before(原生功能):
javascript
arduino
// 基础甘特图
const gantt = new Gantt('#ganttContainer');
// 只能使用内置的基础功能 😢
After(插件增强):
javascript
php
// 增强版甘特图
const gantt = new Gantt('#ganttContainer');
// 加载各种插件
gantt.use(TaskStatsPlugin); // 任务统计
gantt.use(DependencyLinesPlugin); // 依赖可视化
gantt.use(CollaborationPlugin); // 实时协作
gantt.use(ThemeManagerPlugin); // 主题管理
// 现在拥有超级强大的功能! 🚀
二、 5分钟开发你的第一个插件
2.1 基础插件结构
从一个简单的任务统计插件开始:
javascript
kotlin
// 任务统计插件 - 统计任务完成情况
class TaskStatsPlugin {
constructor(gantt, options = {}) {
this.gantt = gantt;
this.options = {
position: 'top-right', // 显示位置
autoUpdate: true, // 自动更新
refreshInterval: 3000, // 更新间隔
...options
};
this.statsElement = null;
this.stats = {
total: 0,
completed: 0,
overdue: 0,
inProgress: 0
};
}
// 插件安装方法
install() {
console.log('🔄 安装任务统计插件...');
this.createStatsPanel(); // 创建统计面板
this.setupEventListeners(); // 设置事件监听
this.updateStats(); // 初始更新数据
console.log('✅ 任务统计插件安装完成!');
}
// 创建统计面板
createStatsPanel() {
this.statsElement = document.createElement('div');
this.statsElement.className = 'task-stats-panel';
this.statsElement.style.cssText = `
position: absolute;
top: 10px;
right: 10px;
background: white;
padding: 12px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.15);
font-family: -apple-system, sans-serif;
z-index: 1000;
min-width: 150px;
`;
// 添加到甘特图容器
this.gantt.container.style.position = 'relative';
this.gantt.container.appendChild(this.statsElement);
}
// 设置事件监听
setupEventListeners() {
if (this.options.autoUpdate) {
// 监听数据变化
this.gantt.on('dataChange', this.updateStats.bind(this));
this.gantt.on('taskChange', this.updateStats.bind(this));
// 定时更新
this.interval = setInterval(() => {
this.updateStats();
}, this.options.refreshInterval);
}
}
// 更新统计数据
updateStats() {
const tasks = this.gantt.getTasks();
const now = new Date();
this.stats = {
total: tasks.length,
completed: tasks.filter(t => t.progress === 100).length,
overdue: tasks.filter(t =>
t.progress < 100 && new Date(t.end) < now
).length,
inProgress: tasks.filter(t =>
t.progress > 0 && t.progress < 100
).length
};
this.renderStats();
}
// 渲染统计面板
renderStats() {
if (!this.statsElement) return;
this.statsElement.innerHTML = `
<div style="font-weight: 600; margin-bottom: 8px; color: #333;">
📊 任务统计
</div>
<div style="font-size: 13px; line-height: 1.6;">
<div>📋 总计: ${this.stats.total}</div>
<div style="color: #52c41a;">✅ 已完成: ${this.stats.completed}</div>
<div style="color: #faad14;">🔄 进行中: ${this.stats.inProgress}</div>
<div style="color: #ff4d4f;">⏰ 已逾期: ${this.stats.overdue}</div>
</div>
`;
}
// 获取统计信息(公共API)
getStats() {
return { ...this.stats };
}
// 手动刷新(公共API)
refresh() {
this.updateStats();
}
// 插件卸载清理
uninstall() {
if (this.statsElement) {
this.statsElement.remove();
}
if (this.interval) {
clearInterval(this.interval);
}
console.log('🗑️ 任务统计插件已卸载');
}
}
2.2 使用插件
使用起来超级简单:
javascript
arduino
// 初始化甘特图
const gantt = new Gantt('#ganttContainer');
// 使用插件
const statsPlugin = new TaskStatsPlugin(gantt, {
position: 'top-right',
autoUpdate: true,
refreshInterval: 5000
});
// 安装插件
statsPlugin.install();
// 在任何地方都可以调用插件方法
console.log('当前统计:', statsPlugin.getStats());
// 手动刷新数据
statsPlugin.refresh();
三、 开发高级插件
3.1 依赖关系可视化插件
来点更酷的!实现任务依赖关系连线:
javascript
kotlin
// 依赖关系可视化插件
class DependencyLinesPlugin {
constructor(gantt, options = {}) {
this.gantt = gantt;
this.options = {
lineColor: '#1890ff',
lineWidth: 2,
arrowSize: 5,
showOnHover: true,
...options
};
this.canvas = null;
this.ctx = null;
this.dependencies = new Map();
}
install() {
console.log('🔄 安装依赖关系插件...');
this.createCanvas();
this.calculateDependencies();
this.setupEventListeners();
console.log('✅ 依赖关系插件安装完成!');
}
// 创建画布
createCanvas() {
this.canvas = document.createElement('canvas');
this.canvas.style.cssText = `
position: absolute;
top: 0;
left: 0;
pointer-events: none;
z-index: 500;
`;
this.ctx = this.canvas.getContext('2d');
this.gantt.container.appendChild(this.cavas);
// 响应式处理
this.resizeCanvas();
window.addEventListener('resize', () => this.resizeCanvas());
}
// 计算依赖关系
calculateDependencies() {
this.dependencies.clear();
const tasks = this.gantt.getTasks();
tasks.forEach(task => {
if (task.dependencies) {
const deps = Array.isArray(task.dependencies)
? task.dependencies
: [task.dependencies];
deps.forEach(depId => {
const sourceTask = tasks.find(t => t.id === depId);
if (sourceTask) {
this.dependencies.set(`${depId}-${task.id}`, {
source: sourceTask,
target: task
});
}
});
}
});
this.drawDependencies();
}
// 绘制依赖线
drawDependencies() {
if (!this.ctx) return;
// 清空画布
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// 绘制所有依赖线
this.dependencies.forEach(({ source, target }) => {
this.drawDependencyLine(source, target);
});
}
// 绘制单条依赖线
drawDependencyLine(source, target) {
const sourceEl = this.gantt.container.querySelector(
`[data-task-id="${source.id}"]`
);
const targetEl = this.gantt.container.querySelector(
`[data-task-id="${target.id}"]`
);
if (!sourceEl || !targetEl) return;
const sourceRect = sourceEl.getBoundingClientRect();
const targetRect = targetEl.getBoundingClientRect();
const containerRect = this.gantt.container.getBoundingClientRect();
const startX = sourceRect.right - containerRect.left;
const startY = sourceRect.top + sourceRect.height / 2 - containerRect.top;
const endX = targetRect.left - containerRect.left;
const endY = targetRect.top + targetRect.height / 2 - containerRect.top;
// 绘制连线
this.ctx.strokeStyle = this.options.lineColor;
this.ctx.lineWidth = this.options.lineWidth;
this.ctx.beginPath();
this.ctx.moveTo(startX, startY);
this.ctx.lineTo(endX, endY);
this.ctx.stroke();
// 绘制箭头
this.drawArrow(endX, endY, Math.atan2(endY - startY, endX - startX));
}
// 绘制箭头
drawArrow(x, y, angle) {
this.ctx.save();
this.ctx.translate(x, y);
this.ctx.rotate(angle);
this.ctx.fillStyle = this.options.lineColor;
this.ctx.beginPath();
this.ctx.moveTo(0, 0);
this.ctx.lineTo(-this.options.arrowSize, -this.options.arrowSize);
this.ctx.lineTo(-this.options.arrowSize, this.options.arrowSize);
this.ctx.closePath();
this.ctx.fill();
this.ctx.restore();
}
// 调整画布大小
resizeCanvas() {
if (!this.canvas) return;
const rect = this.gantt.container.getBoundingClientRect();
this.canvas.width = rect.width;
this.canvas.height = rect.height;
this.canvas.style.width = `${rect.width}px`;
this.canvas.style.height = `${rect.height}px`;
this.drawDependencies();
}
uninstall() {
if (this.canvas) {
this.canvas.remove();
}
console.log('🗑️ 依赖关系插件已卸载');
}
}
3.2 使用依赖插件
javascript
arduino
// 使用依赖关系插件
const dependencyPlugin = new DependencyLinesPlugin(gantt, {
lineColor: '#722ed1', // 紫色线条
lineWidth: 2,
arrowSize: 6,
showOnHover: true
});
dependencyPlugin.install();
// 数据示例(包含依赖关系)
const tasks = [
{
id: 'design',
name: 'UI设计',
start: '2023-11-01',
end: '2023-11-05',
progress: 100
},
{
id: 'develop',
name: '前端开发',
start: '2023-11-06',
end: '2023-11-15',
progress: 80,
dependencies: ['design'] // 依赖于UI设计
},
{
id: 'test',
name: '测试验收',
start: '2023-11-16',
end: '2023-11-20',
progress: 30,
dependencies: ['develop'] // 依赖于前端开发
}
];
gantt.load(tasks);
四、 插件开发最佳实践
4.1 错误处理机制
javascript
kotlin
// 健壮的插件错误处理
class RobustPlugin {
constructor(gantt, options) {
this.gantt = gantt;
this.options = options;
this.retryCount = 0;
this.maxRetries = 3;
}
install() {
try {
this.validateOptions();
this.setupPlugin();
this.startPlugin();
} catch (error) {
this.handleError(error);
}
}
// 验证配置
validateOptions() {
const required = ['apiKey', 'endpoint'];
const missing = required.filter(key => !this.options[key]);
if (missing.length > 0) {
throw new Error(`缺少必要配置: ${missing.join(', ')}`);
}
}
// 错误处理
handleError(error, context = '') {
console.error(`❌ 插件错误${context}:`, error);
// 重试机制
if (this.retryCount < this.maxRetries) {
this.retryCount++;
console.log(`🔄 第${this.retryCount}次重试...`);
setTimeout(() => this.install(), 1000 * this.retryCount);
} else {
this.showErrorToUser(error);
}
// 错误上报
this.reportError(error);
}
// 错误上报
reportError(error) {
// 可以上报到Sentry、监控系统等
fetch('/api/error-log', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
plugin: this.constructor.name,
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
})
}).catch(console.error);
}
}
4.2 性能优化技巧
javascript
javascript
// 高性能插件开发
class PerformancePlugin {
constructor() {
this.cache = new Map();
this.debounceTimers = new Map();
}
// 防抖处理
debounce(key, func, delay = 300) {
clearTimeout(this.debounceTimers.get(key));
this.debounceTimers.set(key, setTimeout(func, delay));
}
// 缓存处理
withCache(key, generator, ttl = 60000) {
const cached = this.cache.get(key);
const now = Date.now();
if (cached && now - cached.timestamp < ttl) {
return cached.value;
}
const value = generator();
this.cache.set(key, { value, timestamp: now });
return value;
}
// 批量处理
batchProcess(items, processor, batchSize = 10) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
results.push(...processor(batch));
}
return results;
}
// 内存清理
cleanupMemory() {
const now = Date.now();
for (const [key, value] of this.cache.entries()) {
if (now - value.timestamp > 300000) { // 5分钟清理
this.cache.delete(key);
}
}
}
}
五、 发布你的插件
5.1 创建插件包
bash
bash
# 创建插件项目
mkdir mzgantt-task-stats-plugin
cd mzgantt-task-stats-plugin
# 初始化package.json
npm init -y
# 安装开发依赖
npm install --save-dev webpack webpack-cli babel-loader @babel/core @babel/preset-env
5.2 package.json 配置
json
json
{
"name": "mzgantt-task-stats-plugin",
"version": "1.0.0",
"description": "任务统计插件 for mzgantt",
"main": "dist/index.js",
"keywords": [
"gantt",
"mzgantt",
"plugin",
"task-stats",
"productivity"
],
"author": "你的名字",
"license": "MIT",
"peerDependencies": {
"mzgantt": "^2.0.0"
},
"files": [
"dist",
"README.md"
],
"scripts": {
"build": "webpack --mode production",
"dev": "webpack --mode development --watch"
}
}
5.3 发布到npm
bash
bash
# 登录npm
npm login
# 构建项目
npm run build
# 发布
npm publish
# 或者发布测试版本
npm publish --tag beta
六、 实战案例:开发主题切换插件
javascript
kotlin
// 主题切换插件
class ThemeSwitcherPlugin {
constructor(gantt, options = {}) {
this.gantt = gantt;
this.options = {
themes: {
light: {
primary: '#1890ff',
background: '#ffffff',
text: '#262626'
},
dark: {
primary: '#177ddc',
background: '#141414',
text: '#f0f0f0'
}
},
defaultTheme: 'light',
...options
};
this.currentTheme = this.options.defaultTheme;
}
install() {
this.createThemeSelector();
this.applyTheme(this.currentTheme);
this.setupEventListeners();
}
createThemeSelector() {
this.selector = document.createElement('select');
this.selector.style.cssText = `
position: absolute;
top: 10px;
left: 10px;
z-index: 1000;
padding: 5px;
border-radius: 4px;
border: 1px solid #ddd;
`;
// 添加主题选项
Object.keys(this.options.themes).forEach(themeName => {
const option = document.createElement('option');
option.value = themeName;
option.textContent = themeName.charAt(0).toUpperCase() + themeName.slice(1);
this.selector.appendChild(option);
});
this.selector.value = this.currentTheme;
this.gantt.container.appendChild(this.selector);
}
applyTheme(themeName) {
const theme = this.options.themes[themeName];
if (!theme) return;
this.currentTheme = themeName;
// 应用CSS变量
const root = document.documentElement;
Object.entries(theme).forEach(([key, value]) => {
root.style.setProperty(`--gantt-${key}-color`, value);
});
// 触发主题变化事件
this.gantt.emit('themeChange', {
theme: themeName,
config: theme
});
}
setupEventListeners() {
this.selector.addEventListener('change', (e) => {
this.applyTheme(e.target.value);
});
}
// 公共API:获取当前主题
getCurrentTheme() {
return this.currentTheme;
}
// 公共API:切换主题
switchTheme(themeName) {
this.applyTheme(themeName);
this.selector.value = themeName;
}
}
七、 总结与建议
mzgantt插件开发的优势:
- ✅ 极其简单:几分钟就能开发一个功能完整的插件
- ✅ 强大灵活:可以扩展任何你想要的功能
- ✅ 生态丰富:已经有大量现成插件可以直接使用
- ✅ 性能优秀:完善的错误处理和性能优化机制
开发建议:
- 先设计后开发:明确插件功能和API设计
- 错误处理:一定要有完善的错误处理和重试机制
- 性能优化:使用防抖、缓存、批量处理等优化手段
- 文档完善:写好README和使用文档
- 测试充分:编写单元测试和集成测试