Wochenplan-Editor

Ich schreibe, weil ich zum dritten Mal in einem Jahr auf diese Aufgabe stoße. Jedes Mal beginnt alles mit einer erstaunlich kreativen Lösung, die einfacher ist, und am Ende kommt es zu dem System, über das ich sprechen werde.

Ziel ist es, einen Wochenplan zu erstellen und zu pflegen, z. B. einen Stundenplan für die Schule oder einen Zeitplan für Ärzte und Beamte. Es gibt eine Reihe von Slots. Jeder Slot ist ein Platz im Wochenplan mit verschiedenen zusätzlichen Parametern wie Schranknummer, Name des Mitarbeiters. Es ist erforderlich, ein flexibles System mit einer vollständigen Historie aufzubauen, das Probleme lösen kann, z. B.: Erstellen eines anderen Zeitplans ab Beginn des Sommers, Ersetzen des Lehrers für die nächsten 3 Wochen, Verschieben des Zeitplans von Freitag auf Samstag aufgrund der Feiertage.

Ich werde darüber schreiben, worüber sie normalerweise stolpern und wie man es löst, ich werde das Problem des Malens des Streifens lösen, und dann werde ich Beispiele für ein einfaches Backend auf Node / Sequelize geben und mit einem einfachen Frontend auf Vue / Vuex / Vuetify / Nuxt enden, wo Sie alles mit einer Maus ziehen und sehen können Wie funktioniert es?

Die Codes werden auf github veröffentlicht , das hier bereitgestellt wird.



Granulare Änderungen


Es gibt einen Slot, der irgendwie in der Datenbank dargestellt wird. Muss bearbeitet werden. Sie müssen also ein Formular mit den Feldern und unter der Schaltfläche "Speichern" zeichnen. Schließlich ist normalerweise alles so angeordnet. In diesem Fall jedoch nicht. Betrachten Sie das Formular:


Beim Speichern werden alle Daten des Steckplatzes aktualisiert, der Verlauf geht verloren. Versuchen wir, ein solches Element hinzuzufügen:


Wieder von. Beispielsweise wurde am 4. Juni, am Montag, ein eintägiger Unterrichtstransfer vom ersten zum zweiten Büro aufgezeichnet. Dann kommt eine neue Forderung - ab dem 28. Mai beginnt der Unterricht immer um 20:00 Uhr statt um 19:00 Uhr. Wir öffnen das Formular, ändern die Uhrzeit, geben das Datum ab dem 28. und für immer an und ... alle Felder gehen zusammen mit der Schranknummer zum Server. Die vorübergehende Änderung am 4. Juni wird überschrieben. Mit diesem Formular kann nicht ermittelt werden, welche Felder in welchen Intervallen der Benutzer ändern möchte, da alle Felder überhaupt gesendet werden.

Die Idee ist, dass sich jede Regel unabhängig von den anderen mit einem eigenen Intervall ändert. Der Slot wird durch einen Satz eindimensionaler Parameter definiert. Jeder Parameter hat einen Änderungsverlauf, der durch einen Satz von Regeln definiert ist. Jede Regel enthält einen Wert, ein Start- und ein Enddatum. Da es sich um einen Wochenkalender handelt, reichen die Daten aus, um bis zu einer Woche JJJJWW anzugeben.


Es scheint, dass das Bearbeiten des Steckplatzes jetzt sehr kompliziert ist. Um mehrere Felder zu ändern, müssen Sie jedes Feld auswählen, das Formular öffnen, einen Wert und ein Intervall eingeben. In der Praxis hat es sich jedoch als selten erwiesen, mehrere Bereiche zu wechseln. Viel häufiger ist die Massenaktualisierung mehrerer Slots gleichzeitig. Um beispielsweise die krankheitsbedingte Abwesenheit eines Lehrers zu beseitigen, müssen Sie alle seine Blöcke auswählen, den Status der Personalzuweisung in den medizinischen Urlaub versetzen und dann einen Ersatzlehrer für dieselben Blöcke auswählen. Nur 2 Aktionen anstelle von n Aktionen für n Slots in dem Fall, als ob sie durch das traditionelle Formular angegeben würden. Auf dem StarBright.com- System, an dem ich gerade arbeite, sieht es folgendermaßen aus:


Die Aufgabe, Streifen zu malen


Stellen Sie sich einen Streifen vor, der aus Zellen besteht, die in verschiedenen Farben bemalt sind. Jede Zelle ist eine Woche, jede Farbe ist eine Bedeutung. Eine neue Farbe kommt und das Intervall, in dem sie angewendet werden soll, muss über dem, was ist, neu gestrichen werden. Auf Datenebene bedeutet dies, dass Sie vollständig überlappende Intervalle entfernen, die Intervalle teilweise überlappender Intervalle ändern, ein neues Intervall hinzufügen und benachbarte einfarbige Intervalle zu einem zusammenführen müssen. Das Endergebnis sollte aus Intervallen bestehen, die sich nicht überlappen.


Ergebnis: [{Löschen, ID: 2}, {Aktualisieren, ID: 1, Daten: {bis: 5}}, {Aktualisieren, ID: 3, Daten: {von: 16}}, {Einfügen, Daten: {von : 6 bis: 15, Wert: wed}}]

Dies ist eine einfache Aufgabe, aber es ist leicht, hier etwas zu ignorieren. Hier ist ein separates Repository mit einer Lösung und Tests. http://timeblock-rules.rag.lt - hier können Sie überprüfen, wie es funktioniert, und mit Shading spielen.

Backend


Die Regeln überschneiden sich nicht, daher reicht die einfachste Auswahl von Regeln aus, bei denen <=: Woche und (bis ist null oder> =: Woche) aus, um genau die erforderlichen Regeln für die angegebene Woche auszuwählen. Hier ist ein einfaches Beispiel-Backend für Node / Sequelize. Es verwendet den kombinierten Stil von c Versprechen und Async / Warten, über den Sie in einem anderen Artikel von mir lesen können.

Hier ist die Aktion, mit der die Regeln für die angegebene Woche ausgewählt werden:
routes.get('/timeblocks', async (req, res) => { try { ... validation ... await Rule .findAll({ where: { from: {$or: [{$lte: req.query.week}, null]}, to: {$or: [{$gte: req.query.week}, null]} } }) .then( sendSuccess(res, 'Calendar data extracted.'), throwError(500, 'sequelize error') ) } catch (error) { catchError(res, error) } }) 


Und hier ist PATCH, um den Regelsatz zu ändern:
 routes.patch('/timeblocks/:id(\\d+)', async (req, res) => { try { ... validation ... const initialRules = await Rule .findAll({ where: { timeblock_id: req.params.id, type: {$in: req.params.rules.map(rule => rule.type)} } }).catch(throwError(500, 'sequelize error')) const promises = [] req.params.rules.forEach(rule => { // This function defined in stripe coloring repo, https://github.com/Kasheftin/timeblock-rules/blob/master/src/fn/rules.js; const actions = processNewRule(rule, initialRules[rule.type] || []) actions.forEach(action => { if (action.type === 'delete') { promises.push(Rule.destroy({where: {id: action.id}})) } else if (action.type === 'update') { promises.push(Rule.update(action.data, {where: {id: action.id}})) } else if (action.type === 'insert') { promises.push(Rule.build({...action.data, timeblock_id: rule.timeblock_id, type: rule.type}).save()) } }) }) Promise.all(promises).then( result => sendSuccess(res, 'Timeblock rules updated.')() ) } catch (error) { catchError(res, error) } }) 


Dies ist der schwierigste ideologische Teil des Backends, der Rest ist noch einfacher.

Die Frage ist, wie man Slots entfernt. In diesem Fall wird der gesamte Verlauf gespeichert, nichts wird gelöscht. Es gibt ein Statusfeld, das geöffnet, vorübergehend geschlossen und geschlossen werden kann. Besucher sehen aktive Slots und sind vorübergehend inaktiv. In letzteren schreibt der Administrator normalerweise einen Kommentar darüber, warum keine Aktivität stattfindet. Im Laufe der Zeit gibt es viele geschlossene Slots. Um die Situation zu vereinfachen, ist es hilfreich, eine andere Eigenschaft wie ein Schuljahr einzuführen und beim Bearbeiten von Slots nur das aktuelle Schuljahr anzuzeigen.

Frontend


Der Code befindet sich in diesem Repository , es ist eine einfache einseitige Site auf nuxt. Eigentlich gibt es ein paar Probleme mit ssr (zum Beispiel analysiere ich im Detail, wie man Authentifizierung auf nuxt schreibt), aber einfache Anwendungen werden sehr schnell darauf geschrieben.

Hier ist der Code für eine einzelne Seite:
 export default { components: {...}, fetch ({app, route, redirect, store}) { if (!route.query.week) { const newRoute = app.router.resolve({query: {...route.query, week: moment().format('YYYYWW')}}, route) return redirect(newRoute.href) } return Promise.resolve() .then(() => store.dispatch('calendar/set', {week: route.query.week})) .then(() => store.dispatch('calendar/fetch')) }, computed: { week () { return this.$store.state.calendar.week } }, watch: { week (week) { this.$router.push({ query: { ...this.$route.query, week } }) this.$store.dispatch('calendar/fetch') } } } 


Die Abrufmethode funktioniert auf dem Server und dem Client, leitet zur aktuellen Woche um und fordert einen Kalender an. Wenn sich die Woche ändert, werden die Daten erneut angefordert.

Was tun mit überlappenden Slots? Die Antwort hängt von der Geschäftslogik ab. Beispielsweise benötigen Sie möglicherweise eine Serverüberprüfung, die Überlagerungen verhindert. In diesem Fall sind Überlagerungen zulässig, und um ein schönes Bild zu erhalten, werden solche Schlitze in halber Breite nebeneinander gezeichnet. Fügen Sie das Layout hinzu und erhalten Sie diesen Look:



Alles andere ist einfaches Javascript ohne besondere Ideen. Durch Drücken der Maus auf dem Block beginnt das Ziehen und Ablegen. Die Mausbewegungs- und Mouseup-Ereignisse werden am gesamten Fenster aufgehängt. Drag & Drop beginnt mit einer Verzögerung von 200 ms, um Drag & Drop zu unterscheiden. Die Parameter der Container, in denen der Tropfen verfolgt wird, werden im Voraus berechnet, da getBoundingClientRect für jede Mausbewegung zu schwer ist. Ich musste zwei Formulare erstellen - eines zum Erstellen (Festlegen aller Regeln gleichzeitig ab der aktuellen Woche), das andere für detaillierte Änderungen am Slot.

http://calendar.rag.lt - hier können Sie überprüfen, wie alles funktioniert.

Links zum Artikel


Source: https://habr.com/ru/post/de412665/


All Articles