Написание движка для интерактивного обучения

This commit is contained in:
brusnitsyn
2026-07-15 00:17:17 +09:00
parent 082f2b8b01
commit e48d38f3a2
18 changed files with 1160 additions and 352 deletions

View File

@@ -1,40 +1,139 @@
import {defineStore} from "pinia";
import {computed, ref} from "vue";
import {tours} from "../config/tours.js";
import { defineStore } from "pinia";
import { router } from "@inertiajs/vue3";
import { route } from "ziggy-js";
export const useOnboardingStore = defineStore('onboarding', () => {
const activeTourId = ref(null)
const activeStepIndex = ref(0)
const isActive = ref(false)
import tours from "@/Tours";
const currentTourSteps = computed(() => {
if (!activeTourId.value) return []
return tours[activeTourId.value]?.steps || []
})
export const useOnboardingStore = defineStore("onboarding", {
state: () => ({
active: false,
currentTourId: null,
stepIndex: 0,
waitingEvent: null,
}),
const startTour = (tourId, startFrom = 0) => {
activeTourId.value = tourId
activeStepIndex.value = startFrom
isActive.value = true
}
getters: {
tour(state) {
return tours[state.currentTourId] ?? null;
},
currentStep() {
return this.tour?.steps[this.stepIndex] ?? null;
},
isFirst() {
return this.stepIndex === 0;
},
isLast() {
return this.stepIndex >= this.tour.steps.length - 1;
}
},
const setStep = (index) => {
activeStepIndex.value = index
}
actions: {
start(tourId) {
const finish = () => {
activeTourId.value = null
activeStepIndex.value = 0
isActive.value = false
}
if (!tours[tourId]) {
console.warn(`Tour "${tourId}" not found`);
return;
}
return {
activeTourId,
activeStepIndex,
isActive,
currentTourSteps,
startTour,
finish,
setStep
this.currentTourId = tourId;
this.stepIndex = 0;
this.active = true;
},
finish() {
this.active = false;
this.currentTourId = null;
this.stepIndex = 0;
this.waitingEvent = null;
},
async next() {
if (!this.active)
return;
const step = this.currentStep;
if (step?.afterLeave)
await step.afterLeave();
if (this.isLast) {
this.finish();
return;
}
this.stepIndex++;
await this.openStep();
},
async previous() {
if (!this.active)
return;
const step = this.currentStep;
if (step?.afterLeave)
await step.afterLeave();
if (this.isFirst)
return;
this.stepIndex--;
await this.openStep();
},
async goTo(index) {
if (
index < 0 ||
index >= this.tour.steps.length
) {
return;
}
this.stepIndex = index;
await this.openStep();
},
async openStep() {
const step = this.currentStep;
if (!step)
return;
/*
* Выполнить beforeEnter
*/
if (step.beforeEnter)
await step.beforeEnter();
/*
* Переход между страницами
*/
if (
step.route &&
step.route !== route().current()
) {
router.visit(route(step.route));
return;
}
},
emit(event) {
const step = this.currentStep;
if (!step)
return;
if (
step.nextEvent &&
step.nextEvent === event
) {
this.next();
}
}
}
})