Vue Gantt + Pinia Tutorial
Dieses Tutorial zeigt eine store-gesteuerte Vue Gantt-Integration mit Pinia. Es folgt derselben Architektur wie die öffentlichen Vue-Beispiele: Der Store besitzt tasks und links, und Wrapper-Callbacks übertragen Diagrammbearbeitungen zurück in den Store.
Prerequisites
- Vue 3-Projekt
- Pinia installiert (oder Berechtigung, es hinzuzufügen)
- Vue Gantt-Paket installiert
- Grundlegendes Verständnis von Datenbindung und Grundlagen der Zustandsverwaltung
1. Pinia installieren und registrieren
Wenn Pinia noch nicht installiert ist:
npm install pinia
Registrieren Sie Pinia in src/main.ts:
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
createApp(App).use(createPinia()).mount("#app");
2. Vue Gantt installieren
Installieren Sie Vue Gantt wie in der Vue Gantt-Installationsanleitung beschrieben.
In diesem Tutorial verwenden wir das Evaluierungspaket:
npm install @dhtmlx/trial-vue-gantt
oder
yarn add @dhtmlx/trial-vue-gantt
Wenn Sie bereits das Professional-Paket verwenden, ersetzen Sie @dhtmlx/trial-vue-gantt durch @dhx/vue-gantt in den Befehlen und Importen.
3. Demo-Daten hinzufügen
Erstellen Sie src/demoData.ts:
import type { SerializedLink, SerializedTask } from "@dhtmlx/trial-vue-gantt";
export const tasks: SerializedTask[] = [
{
id: 1,
text: "Office itinerancy",
type: "project",
start_date: new Date(2026, 0, 5),
duration: 10,
progress: 0.4,
open: true,
parent: 0
},
{
id: 2,
text: "Planning",
start_date: new Date(2026, 0, 5),
duration: 4,
progress: 0.6,
parent: 1
}
];
export const links: SerializedLink[] = [{ id: 1, source: 1, target: 2, type: "0" }];
4. Einen Basis-Gantt-Store erstellen
Erstellen Sie src/stores/ganttStore.ts:
import { defineStore } from "pinia";
import type { BatchChanges, SerializedLink, SerializedTask } from "@dhtmlx/trial-vue-gantt";
import { links, tasks } from "../demoData";
type ZoomLevel = "day" | "month" | "year";
const zoomLevels = [
{
name: "day",
scale_height: 27,
min_column_width: 80,
scales: [{ unit: "day", step: 1, format: "%d %M" }]
},
{
name: "month",
scale_height: 50,
min_column_width: 120,
scales: [
{ unit: "month", format: "%F, %Y" },
{ unit: "week", format: "Week #%W" }
]
},
{
name: "year",
scale_height: 50,
min_column_width: 36,
scales: [{ unit: "year", step: 1, format: "%Y" }]
}
];
function applyBatchChanges(tasks: SerializedTask[], links: SerializedLink[], changes: BatchChanges) {
let nextTasks = [...tasks];
let nextLinks = [...links];
for (const change of changes.tasks || []) {
if (change.action === "create") nextTasks.push(change.data as SerializedTask);
if (change.action === "update") {
nextTasks = nextTasks.map(t => String(t.id) === String(change.id) ? change.data as SerializedTask : t);
}
if (change.action === "delete") {
nextTasks = nextTasks.filter(t => String(t.id) !== String(change.id));
}
}
for (const change of changes.links || []) {
if (change.action === "create") nextLinks.push(change.data as SerializedLink);
if (change.action === "update") {
nextLinks = nextLinks.map(l => String(l.id) === String(change.id) ? change.data as SerializedLink : l);
}
if (change.action === "delete") {
nextLinks = nextLinks.filter(l => String(l.id) !== String(change.id));
}
}
return { tasks: nextTasks, links: nextLinks };
}
export const useGanttStore = defineStore("gantt", {
state: () => ({
tasks: tasks,
links: links,
zoomLevel: "day" as ZoomLevel
}),
getters: {
config: state => ({
zoom: {
current: state.zoomLevel,
levels: zoomLevels
}
})
},
actions: {
setZoom(level: ZoomLevel) {
this.zoomLevel = level;
},
applyBatch(changes: BatchChanges) {
const next = applyBatchChanges(this.tasks, this.links, changes);
this.tasks = next.tasks;
this.links = next.links;
}
}
});
Dieser Store bildet eine einzige Quelle der Wahrheit:
tasksundlinkssind kanonische Datenconfigist abgeleiteter ZustandapplyBatchist der Einstiegspunkt des Wrapper-Callbacks
5. Store-Zustand an VueGantt binden
Erstellen Sie src/components/GanttChart.vue:
<script setup lang="ts">
import { storeToRefs } from "pinia";
import VueGantt, { type BatchChanges } from "@dhtmlx/trial-vue-gantt";
import "@dhtmlx/trial-vue-gantt/dist/vue-gantt.css";
import { useGanttStore } from "../stores/ganttStore";
const store = useGanttStore();
const { tasks, links, config, zoomLevel } = storeToRefs(store);
const data = {
batchSave: (changes: BatchChanges) => store.applyBatch(changes)
};
const setZoom = (level: "day" | "month" | "year") => {
store.setZoom(level);
};
</script>
<template>
<section>
<div style={{display: 'flex', gap: '8px', marginBottom: '10px'}}>
<button type="button" :class="{ active: zoomLevel === 'day' }" @click="setZoom('day')">Day</button>
<button type="button" :class="{ active: zoomLevel === 'month' }" @click="setZoom('month')">Month</button>
<button type="button" :class="{ active: zoomLevel === 'year' }" @click="setZoom('year')">Year</button>
</div>
<div style={{height: '80vh'}}>
<VueGantt :tasks="tasks" :links="links" :config="config" :data="data" />
</div>
</section>
</template>
Dies ist die Kernverkabelung des Wrappers:
- store-Werte -> Wrapper-Props
batchSave-> Store-Aktion- Store-Aktion -> neuer Zustand -> Wrapper-Props erneut
6. Gantt in der App-Hülle rendern
Ersetzen Sie src/App.vue:
<script setup lang="ts">
import GanttChart from "./components/GanttChart.vue";
</script>
<template>
<div :style="{ height: '100%', width: '100%' }">
<GanttChart />
</div>
</template>
7. Den Datenfluss verifizieren
Verwenden Sie diesen Ablauf für stabile Updates:
- Store stellt
tasks,linksund abgeleitetenconfigbereit. VueGanttrendert aus Props.- Benutzerbearbeitungen im Diagramm lösen
data.batchSaveaus. - Store-Aktion (
applyBatch) führt die Änderungen zusammen. - Aktualisierter Zustand fließt zurück in
VueGantt.
Mischen Sie dies nicht mit direkten Instanz-Mutationen, es sei denn, Sie aktualisieren auch den Store.