完整示例
一个包含完整功能的示例项目,包括工具栏、导出、主题切换等。
项目管理看板
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>
)
}效果预览
相关链接
- React 使用示例 - React 基础示例
- Vue 使用示例 - Vue 基础示例
- 拖拽与调整 - 交互功能
- 导出功能 - 导出指南