Skip to content

完整示例

一个包含完整功能的示例项目,包括工具栏、导出、主题切换等。

项目管理看板

tsx
import React, { useState, useRef } from "react"
import { EnhancedGanttChart } from "@agions/gantt-flow"
import "@agions/gantt-flow/style"
import type { Task, ViewMode, Dependency } from "@agions/gantt-flow"

const initialTasks: Task[] = [
  {
    id: "1",
    name: "需求分析",
    start: "2023-03-01",
    end: "2023-03-05",
    progress: 100,
    type: "task",
  },
  {
    id: "2",
    name: "系统设计",
    start: "2023-03-06",
    end: "2023-03-10",
    progress: 80,
    type: "task",
  },
  {
    id: "3",
    name: "详细设计",
    start: "2023-03-09",
    end: "2023-03-12",
    progress: 60,
    type: "task",
  },
  {
    id: "4",
    name: "后端开发",
    start: "2023-03-13",
    end: "2023-03-22",
    progress: 40,
    type: "task",
  },
  {
    id: "5",
    name: "前端开发",
    start: "2023-03-13",
    end: "2023-03-22",
    progress: 30,
    type: "task",
  },
  {
    id: "6",
    name: "集成测试",
    start: "2023-03-23",
    end: "2023-03-26",
    progress: 0,
    type: "task",
  },
  {
    id: "7",
    name: "正式发布",
    start: "2023-03-30",
    end: "2023-03-30",
    progress: 0,
    type: "milestone",
  },
]

const initialDependencies: Dependency[] = [
  { fromId: "1", toId: "2", type: "finish_to_start" },
  { fromId: "2", toId: "3", type: "finish_to_start" },
  { fromId: "3", toId: "4", type: "finish_to_start" },
  { fromId: "3", toId: "5", type: "finish_to_start" },
  { fromId: "4", toId: "6", type: "finish_to_start" },
  { fromId: "5", toId: "6", type: "finish_to_start" },
  { fromId: "6", toId: "7", type: "finish_to_start" },
]

function ProjectGantt() {
  const ganttRef = useRef(null)
  const [tasks, setTasks] = useState(initialTasks)
  const [dependencies] = useState(initialDependencies)
  const [viewMode, setViewMode] = useState<ViewMode>("week")
  const [theme, setTheme] = useState<"light" | "dark">("light")

  const handleViewChange = (mode: ViewMode) => {
    setViewMode(mode)
  }

  const handleThemeToggle = () => {
    setTheme(theme === "light" ? "dark" : "light")
  }

  const handleExportPNG = async () => {
    const dataUrl = await ganttRef.current?.exportAsPNG()
    if (dataUrl) {
      const link = document.createElement("a")
      link.href = dataUrl
      link.download = `gantt-${Date.now()}.png`
      link.click()
    }
  }

  const handleExportPDF = async () => {
    const blob = await ganttRef.current?.exportAsPDF()
    if (blob) {
      const url = URL.createObjectURL(blob)
      const link = document.createElement("a")
      link.href = url
      link.download = `gantt-${Date.now()}.pdf`
      link.click()
      URL.revokeObjectURL(url)
    }
  }

  const handleFitToScreen = () => {
    ganttRef.current?.fitToScreen()
  }

  const handleTaskClick = (task: Task) => {
    console.log("任务点击:", task.name)
  }

  const handleTaskDrag = (task: Task, e: MouseEvent, newStart: Date, newEnd: Date) => {
    setTasks(prev =>
      prev.map(t =>
        t.id === task.id ? { ...t, start: newStart, end: newEnd } : t
      )
    )
  }

  const handleProgressChange = (task: Task, progress: number) => {
    setTasks(prev =>
      prev.map(t => (t.id === task.id ? { ...t, progress } : t))
    )
  }

  // 计算统计数据
  const stats = {
    total: tasks.length,
    completed: tasks.filter(t => t.progress === 100).length,
    inProgress: tasks.filter(t => t.progress > 0 && t.progress < 100).length,
    totalProgress: Math.round(
      tasks.reduce((acc, t) => acc + (t.progress || 0), 0) / tasks.length
    ),
  }

  return (
    <div className={`gantt-project ${theme}`}>
      {/* 工具栏 */}
      <div className="toolbar">
        <div className="toolbar-left">
          <h2>📊 项目甘特图</h2>
          <div className="stats">
            <span className="stat">
              <span className="stat-value">{stats.total}</span>
              <span className="stat-label">个任务</span>
            </span>
            <span className="stat">
              <span className="stat-value completed">{stats.completed}</span>
              <span className="stat-label">已完成</span>
            </span>
            <span className="stat">
              <span className="stat-value in-progress">{stats.inProgress}</span>
              <span className="stat-label">进行中</span>
            </span>
            <span className="stat">
              <span className="stat-value">{stats.totalProgress}%</span>
              <span className="stat-label">总进度</span>
            </span>
          </div>
        </div>

        <div className="toolbar-right">
          {/* 视图切换 */}
          <div className="view-switcher">
            {(["day", "week", "month", "quarter"] as ViewMode[]).map((mode) => (
              <button
                key={mode}
                className={viewMode === mode ? "active" : ""}
                onClick={() => ganttRef.current?.setViewMode(mode)}
              >
                {mode === "day" && "日"}
                {mode === "week" && "周"}
                {mode === "month" && "月"}
                {mode === "quarter" && "季"}
              </button>
            ))}
          </div>

          {/* 操作按钮 */}
          <button onClick={handleFitToScreen} title="适应屏幕">
            🔍
          </button>
          <button onClick={handleExportPNG} title="导出 PNG">
            📷
          </button>
          <button onClick={handleExportPDF} title="导出 PDF">
            📄
          </button>
          <button onClick={handleThemeToggle} title="切换主题">
            {theme === "light" ? "🌙" : "☀️"}
          </button>
        </div>
      </div>

      {/* 甘特图 */}
      <div style={{ height: "calc(100vh - 160px)" }}>
        <EnhancedGanttChart
          ref={ganttRef}
          tasks={tasks}
          dependencies={dependencies}
          viewMode={viewMode}
          enableDragging={true}
          enableResizing={true}
          enableProgress={true}
          enableDependencies={true}
          showToday={true}
          showWeekends={true}
          enableCriticalPath={true}
          onViewChange={handleViewChange}
          onTaskClick={handleTaskClick}
          onTaskDrag={handleTaskDrag}
          onProgressChange={handleProgressChange}
          options={{
            theme,
            enableSnap: true,
            snapThreshold: 5,
          }}
        />
      </div>
    </div>
  )
}

export default ProjectGantt

样式

css
.gantt-project {
  padding: 20px;
  background: var(--vp-c-bg);
  color: var(--vp-c-text-1);
  transition: background-color 0.3s, color 0.3s;
}

.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 24px;
  background: var(--vp-c-bg-soft);
  border-radius: 12px;
  margin-bottom: 16px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}

.toolbar-left {
  display: flex;
  align-items: center;
  gap: 24px;
}

.toolbar-left h2 {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
}

.stats {
  display: flex;
  gap: 20px;
}

.stat {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.stat-value {
  font-size: 24px;
  font-weight: 700;
  color: var(--vp-c-brand-1);
}

.stat-value.completed {
  color: #10b981;
}

.stat-value.in-progress {
  color: #3b82f6;
}

.stat-label {
  font-size: 12px;
  color: var(--vp-c-text-2);
}

.toolbar-right {
  display: flex;
  align-items: center;
  gap: 8px;
}

.view-switcher {
  display: flex;
  background: var(--vp-c-bg);
  border-radius: 8px;
  padding: 4px;
  gap: 4px;
}

.view-switcher button {
  padding: 8px 16px;
  border: none;
  background: transparent;
  cursor: pointer;
  border-radius: 6px;
  font-size: 14px;
  font-weight: 500;
  color: var(--vp-c-text-2);
  transition: all 0.2s;
}

.view-switcher button:hover {
  background: var(--vp-c-bg-soft);
  color: var(--vp-c-text-1);
}

.view-switcher button.active {
  background: var(--vp-c-brand-1);
  color: white;
}

.toolbar-right > button {
  padding: 8px 12px;
  border: 1px solid var(--vp-c-border);
  background: var(--vp-c-bg);
  border-radius: 8px;
  cursor: pointer;
  font-size: 16px;
  transition: all 0.2s;
}

.toolbar-right > button:hover {
  background: var(--vp-c-bg-soft);
  border-color: var(--vp-c-brand-1);
}

/* 暗色主题适配 */
.dark .toolbar {
  background: rgba(30, 27, 75, 0.5);
}

.dark .stat-value {
  color: var(--vp-c-brand-2);
}

添加新任务示例

tsx
function AddTaskExample() {
  const ganttRef = useRef(null)
  const [tasks, setTasks] = useState(initialTasks)

  const handleAddTask = () => {
    const newTask: Task = {
      id: `task-${Date.now()}`,
      name: "新任务",
      start: new Date().toISOString().split("T")[0],
      end: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split("T")[0],
      progress: 0,
    }
    
    ganttRef.current?.addTask(newTask)
    setTasks([...tasks, newTask])
  }

  const handleDeleteTask = (taskId: string) => {
    ganttRef.current?.removeTask(taskId)
    setTasks(tasks.filter(t => t.id !== taskId))
  }

  return (
    <div>
      <div className="actions">
        <button onClick={handleAddTask}>添加任务</button>
      </div>
      <EnhancedGanttChart ref={ganttRef} tasks={tasks} />
    </div>
  )
}

撤销/重做示例

tsx
function UndoRedoExample() {
  const ganttRef = useRef(null)
  const [canUndo, setCanUndo] = useState(false)
  const [canRedo, setCanRedo] = useState(false)

  useEffect(() => {
    const gantt = ganttRef.current?.getInstanceCore()
    if (!gantt) return

    const updateState = () => {
      setCanUndo(gantt.canUndo())
      setCanRedo(gantt.canRedo())
    }

    gantt.on("history:change", updateState)
    updateState()

    return () => gantt.off("history:change", updateState)
  }, [])

  return (
    <div className="actions">
      <button
        onClick={() => ganttRef.current?.undo()}
        disabled={!canUndo}
      >
        撤销
      </button>
      <button
        onClick={() => ganttRef.current?.redo()}
        disabled={!canRedo}
      >
        重做
      </button>
    </div>
  )
}

导出面板示例

tsx
import { EnhancedGanttChart, ExportPanel } from "@agions/gantt-flow"
import "@agions/gantt-flow/style"

function ExportPanelExample() {
  return (
    <div style={{ height: "600px", position: "relative" }}>
      <EnhancedGanttChart tasks={tasks}>
        <ExportPanel
          position="top-right"
          showFormats={["png", "pdf", "excel"]}
        />
      </EnhancedGanttChart>
    </div>
  )
}

效果预览

查看完整在线演示 →

相关链接

基于 MIT 许可证发布