Alles und nix
This commit is contained in:
parent
c374ef8074
commit
0c84fb7b6d
@ -1,120 +0,0 @@
|
||||
<template>
|
||||
<div class="component-container" :class="direction">
|
||||
<div class="drop-zone" :class="computedDropClass" @dragover.prevent @dragenter="onDragEnter" @drop="onDrop($event, index)"></div>
|
||||
|
||||
<component :is="resolvedComponent" v-bind="element" @update-items="updateSchema" />
|
||||
|
||||
<div class="drop-zone" :class="computedDropClass" @dragover.prevent @dragenter="onDragEnter" @drop="onDrop($event, index + 1)"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, defineAsyncComponent } from "vue";
|
||||
|
||||
const InputTextRenderer = defineAsyncComponent(() => import("@/components/formDesigner/formRenderer/InputTextRenderer.vue"));
|
||||
const InputNumberRenderer = defineAsyncComponent(() => import("@/components/formDesigner/formRenderer/InputNumberRenderer.vue"));
|
||||
const InputDateRenderer = defineAsyncComponent(() => import("@/components/formDesigner/formRenderer/InputDateRenderer.vue"));
|
||||
const LabelRenderer = defineAsyncComponent(() => import("@/components/formDesigner/formRenderer/LabelRenderer.vue"));
|
||||
const ButtonRenderer = defineAsyncComponent(() => import("@/components/formDesigner/formRenderer/ButtonRenderer.vue"));
|
||||
const FlexLayoutRenderer = defineAsyncComponent(() => import("@/components/formDesigner/formRenderer/FlexLayoutRenderer.vue"));
|
||||
|
||||
export default defineComponent({
|
||||
name: "ComponentRenderer",
|
||||
components: {
|
||||
InputTextRenderer,
|
||||
InputNumberRenderer,
|
||||
InputDateRenderer,
|
||||
LabelRenderer,
|
||||
ButtonRenderer,
|
||||
FlexLayoutRenderer, // Jetzt wird es erst geladen, wenn nötig
|
||||
},
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
resolvedComponent() {
|
||||
const componentMap = {
|
||||
FlexLayout: "FlexLayoutRenderer",
|
||||
Label: "LabelRenderer",
|
||||
InputText: "InputTextRenderer",
|
||||
InputNumber: "InputNumberRenderer",
|
||||
InputDate: "InputDateRenderer",
|
||||
Button: "ButtonRenderer",
|
||||
};
|
||||
return componentMap[this.element.type] || null;
|
||||
},
|
||||
computedDropClass() {
|
||||
return this.direction === "horizontal" ? "drop-zone-horizontal" : "drop-zone-vertical";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateSchema(newItems) {
|
||||
this.$emit("update-schema", newItems); // 🔥 Leitet die Änderung weiter
|
||||
},
|
||||
onDragEnter(event) {
|
||||
event.preventDefault();
|
||||
},
|
||||
onDrop(event, position) {
|
||||
event.preventDefault();
|
||||
const droppedType = event.dataTransfer.getData("text/plain");
|
||||
const newElement = this.createElementByType(droppedType);
|
||||
if (newElement) {
|
||||
this.$emit("insert-item", { position, newElement });
|
||||
}
|
||||
},
|
||||
createElementByType(type) {
|
||||
switch (type) {
|
||||
case "InputText":
|
||||
return { type: "InputText", label: "Neues Textfeld", placeholder: "Hier eingeben..." };
|
||||
case "InputNumber":
|
||||
return { type: "InputNumber", label: "Neue Zahl", placeholder: "0" };
|
||||
case "InputDate":
|
||||
return { type: "InputDate", label: "Neues Datum" };
|
||||
case "Button":
|
||||
return { type: "Button", label: "Klicken" };
|
||||
case "Label":
|
||||
return { type: "Label", value: "Neues Label" };
|
||||
case "FlexLayout":
|
||||
return { type: "FlexLayout", direction: "vertical", items: [] };
|
||||
default:
|
||||
console.warn("⚠ Unbekannter Typ:", type);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.component-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Drop-Zonen für horizontale Layouts */
|
||||
.drop-zone-horizontal {
|
||||
width: 10px;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 255, 0.3);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Drop-Zonen für vertikale Layouts */
|
||||
.drop-zone-vertical {
|
||||
height: 10px;
|
||||
width: 100%;
|
||||
background: rgba(0, 0, 255, 0.3);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@ -1,144 +0,0 @@
|
||||
<template>
|
||||
<div id="accordion-1" class="accordion" role="tablist">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" role="tab"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-1" aria-expanded="false" aria-controls="accordion-1 .item-1">Steuerelemente</button></h2>
|
||||
<div class="accordion-collapse collapse item-1" role="tabpanel" data-bs-parent="#accordion-1">
|
||||
<div class="accordion-body">
|
||||
<div class="row ms-0 me-0" draggable="true" @dragstart="startDrag($event, 'InputText')">
|
||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
||||
<svg class="bi bi-input-cursor-text pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5 2a.5.5 0 0 1 .5-.5c.862 0 1.573.287 2.06.566.174.099.321.198.44.286.119-.088.266-.187.44-.286A4.165 4.165 0 0 1 10.5 1.5a.5.5 0 0 1 0 1c-.638 0-1.177.213-1.564.434a3.49 3.49 0 0 0-.436.294V7.5H9a.5.5 0 0 1 0 1h-.5v4.272c.1.08.248.187.436.294.387.221.926.434 1.564.434a.5.5 0 0 1 0 1 4.165 4.165 0 0 1-2.06-.566A4.561 4.561 0 0 1 8 13.65a4.561 4.561 0 0 1-.44.285 4.165 4.165 0 0 1-2.06.566.5.5 0 0 1 0-1c.638 0 1.177-.213 1.564-.434.188-.107.335-.214.436-.294V8.5H7a.5.5 0 0 1 0-1h.5V3.228a3.49 3.49 0 0 0-.436-.294A3.166 3.166 0 0 0 5.5 2.5.5.5 0 0 1 5 2"
|
||||
></path>
|
||||
<path d="M10 5h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4v1h4a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-4zM6 5V4H2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4v-1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z"></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Textfeld</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mb-1 mt-1" />
|
||||
<div class="row ms-0 me-0" draggable="true" @dragstart="startDrag($event, 'InputNumber')">
|
||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
||||
<svg class="bi bi-0-square pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M7.988 12.158c-1.851 0-2.941-1.57-2.941-3.99V7.84c0-2.408 1.101-3.996 2.965-3.996 1.857 0 2.935 1.57 2.935 3.996v.328c0 2.408-1.101 3.99-2.959 3.99ZM8 4.951c-1.008 0-1.629 1.09-1.629 2.895v.31c0 1.81.627 2.895 1.629 2.895s1.623-1.09 1.623-2.895v-.31c0-1.8-.621-2.895-1.623-2.895Z"
|
||||
></path>
|
||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Nummernfeld</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mb-1 mt-1" />
|
||||
<div class="row ms-0 me-0" draggable="true" @dragstart="startDrag($event, 'InputDate')">
|
||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
||||
<svg class="bi bi-calendar3 pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M14 0H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2M1 3.857C1 3.384 1.448 3 2 3h12c.552 0 1 .384 1 .857v10.286c0 .473-.448.857-1 .857H2c-.552 0-1-.384-1-.857V3.857z"></path>
|
||||
<path
|
||||
d="M6.5 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Datumsfeld</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mb-1 mt-1" />
|
||||
<div class="row ms-0 me-0" draggable="true" @dragstart="startDrag($event, 'Button')">
|
||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
||||
<svg class="bi bi-menu-button pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M0 1.5A1.5 1.5 0 0 1 1.5 0h8A1.5 1.5 0 0 1 11 1.5v2A1.5 1.5 0 0 1 9.5 5h-8A1.5 1.5 0 0 1 0 3.5zM1.5 1a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 0-.5-.5z"></path>
|
||||
<path
|
||||
d="m7.823 2.823-.396-.396A.25.25 0 0 1 7.604 2h.792a.25.25 0 0 1 .177.427l-.396.396a.25.25 0 0 1-.354 0zM0 8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm1 3v2a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2zm14-1V8a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2zM2 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0 4a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Button</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" role="tab"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-2" aria-expanded="false" aria-controls="accordion-1 .item-2">Layoutelemente</button></h2>
|
||||
<div class="accordion-collapse collapse item-2" role="tabpanel" data-bs-parent="#accordion-1">
|
||||
<div class="accordion-body">
|
||||
<div class="row ms-0 me-0" draggable="true" @dragstart="startDrag($event, 'FlexLayout')">
|
||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
||||
<svg class="bi bi-grid-1x2 pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M6 1H1v14h5zm9 0h-5v5h5zm0 9v5h-5v-5zM0 1a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1zm9 0a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1zm1 8a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1z"></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Flexibles Layout</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mb-1 mt-1" />
|
||||
<div class="row ms-0 me-0" draggable="true" @dragstart="startDrag($event, 'Label')">
|
||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
||||
<svg class="bi bi-alphabet-uppercase pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M1.226 10.88H0l2.056-6.26h1.42l2.047 6.26h-1.29l-.48-1.61H1.707l-.48 1.61ZM2.76 5.818h-.054l-.75 2.532H3.51zm3.217 5.062V4.62h2.56c1.09 0 1.808.582 1.808 1.54 0 .762-.444 1.22-1.05 1.372v.055c.736.074 1.365.587 1.365 1.528 0 1.119-.89 1.766-2.133 1.766h-2.55ZM7.18 5.55v1.675h.8c.812 0 1.171-.308 1.171-.853 0-.51-.328-.822-.898-.822zm0 2.537V9.95h.903c.951 0 1.342-.312 1.342-.909 0-.591-.382-.954-1.095-.954H7.18Zm5.089-.711v.775c0 1.156.49 1.803 1.347 1.803.705 0 1.163-.454 1.212-1.096H16v.12C15.942 10.173 14.95 11 13.607 11c-1.648 0-2.573-1.073-2.573-2.849v-.78c0-1.775.934-2.871 2.573-2.871 1.347 0 2.34.849 2.393 2.087v.115h-1.172c-.05-.665-.516-1.156-1.212-1.156-.849 0-1.347.67-1.347 1.83Z"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Label</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" role="tab"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-3" aria-expanded="false" aria-controls="accordion-1 .item-3">Templates</button></h2>
|
||||
<div class="accordion-collapse collapse item-3" role="tabpanel" data-bs-parent="#accordion-1">
|
||||
<div class="accordion-body">
|
||||
<div class="row ms-0 me-0">
|
||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
||||
<svg class="bi bi-chevron-down pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Accordion</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mb-1 mt-1" />
|
||||
<div class="row ms-0 me-0">
|
||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
||||
<svg class="bi bi-floppy pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M11 2H9v3h2z"></path>
|
||||
<path
|
||||
d="M1.5 0h11.586a1.5 1.5 0 0 1 1.06.44l1.415 1.414A1.5 1.5 0 0 1 16 2.914V14.5a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 14.5v-13A1.5 1.5 0 0 1 1.5 0M1 1.5v13a.5.5 0 0 0 .5.5H2v-4.5A1.5 1.5 0 0 1 3.5 9h9a1.5 1.5 0 0 1 1.5 1.5V15h.5a.5.5 0 0 0 .5-.5V2.914a.5.5 0 0 0-.146-.353l-1.415-1.415A.5.5 0 0 0 13.086 1H13v4.5A1.5 1.5 0 0 1 11.5 7h-7A1.5 1.5 0 0 1 3 5.5V1H1.5a.5.5 0 0 0-.5.5m3 4a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 .5-.5V1H4zM3 15h10v-4.5a.5.5 0 0 0-.5-.5h-9a.5.5 0 0 0-.5.5z"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Gespeicherter Eintrag 1</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FormDesignerToolsComponent",
|
||||
// Verwendete Komponenten
|
||||
components: {},
|
||||
// Props für die Komponente
|
||||
props: {},
|
||||
|
||||
// Daten der Komponente
|
||||
data() {},
|
||||
|
||||
// Computed Properties
|
||||
computed: {},
|
||||
|
||||
// Methoden
|
||||
methods: {
|
||||
startDrag(event, type) {
|
||||
console.log("Dragging:", type);
|
||||
event.dataTransfer.setData("text/plain", type); // Speichere das Objekt als JSON
|
||||
},
|
||||
},
|
||||
|
||||
// Lifecycle-Hooks
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.accordion-button:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
@ -1,135 +0,0 @@
|
||||
<template>
|
||||
<div class="flex-layout" :class="computedDirection">
|
||||
<component-renderer v-for="(item, key) in items" :key="key" :element="item" :index="key" :direction="direction" @update-schema="updateChildItems(key, $event)" @insert-item="insertItem" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import ComponentRenderer from "@/components/formDesigner/ComponentRenderer.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "FlexLayoutRenderer",
|
||||
components: { ComponentRenderer },
|
||||
data() {
|
||||
return {
|
||||
dragOverActive: false,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
computedDirection() {
|
||||
return this.direction === "horizontal" ? "horizontal" : "vertical";
|
||||
},
|
||||
computedDropClass() {
|
||||
return this.direction === "horizontal" ? "drop-zone-horizontal" : "drop-zone-vertical";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateChildItems(index, newItems) {
|
||||
const updatedItems = [...this.items];
|
||||
updatedItems[index] = { ...updatedItems[index], items: newItems };
|
||||
this.$emit("update-items", updatedItems);
|
||||
},
|
||||
onDragOver(event) {
|
||||
event.preventDefault();
|
||||
this.dragOverActive = true;
|
||||
},
|
||||
onDragEnter(event) {
|
||||
event.preventDefault();
|
||||
},
|
||||
onDrop(event, position) {
|
||||
event.preventDefault();
|
||||
console.log(`📌 onDrop aufgerufen bei Position: ${position}`); // 🔥 Debugging-Log
|
||||
|
||||
const droppedType = event.dataTransfer.getData("text/plain");
|
||||
console.log(`📦 Gedropptes Element: ${droppedType}`); // 🔥 Zeigt an, was gedroppt wurde
|
||||
|
||||
const newElement = this.createElementByType(droppedType);
|
||||
if (newElement) {
|
||||
console.log(`✅ Neues Element erstellt:`, newElement);
|
||||
this.insertItem({ position, newElement });
|
||||
} else {
|
||||
console.warn("⚠ Kein gültiges Element erstellt!");
|
||||
}
|
||||
},
|
||||
insertItem({ position, newElement }) {
|
||||
console.log(`🔧 Inserting at position: ${position}`, newElement); // 🔥 Debugging-Log
|
||||
|
||||
const updatedItems = [...this.items];
|
||||
|
||||
// 🛠 Stelle sicher, dass `position` innerhalb der Liste bleibt
|
||||
if (position < 0) position = 0;
|
||||
if (position > updatedItems.length) position = updatedItems.length;
|
||||
|
||||
updatedItems.splice(position, 0, newElement); // 🔥 Element an richtiger Stelle einfügen
|
||||
console.log("🔄 Updated Items:", updatedItems); // 🔥 Zeigt die neue Liste in der Konsole
|
||||
|
||||
this.$emit("update-items", updatedItems); // 🔥 Änderungen an Parent weitergeben
|
||||
},
|
||||
createElementByType(type) {
|
||||
switch (type) {
|
||||
case "InputText":
|
||||
return { type: "InputText", label: "Neues Textfeld", placeholder: "Hier eingeben..." };
|
||||
case "InputNumber":
|
||||
return { type: "InputNumber", label: "Neue Zahl", placeholder: "0" };
|
||||
case "InputDate":
|
||||
return { type: "InputDate", label: "Neues Datum" };
|
||||
case "Button":
|
||||
return { type: "Button", label: "Klicken" };
|
||||
case "Label":
|
||||
return { type: "Label", value: "Neues Label" };
|
||||
case "FlexLayout":
|
||||
return { type: "FlexLayout", direction: "vertical", items: [] };
|
||||
default:
|
||||
console.warn("⚠ Unbekannter Typ:", type);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.flex-layout {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
/* Layouts richtig ausrichten */
|
||||
.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Drop-Zonen für horizontale Layouts (zwischen Spalten) */
|
||||
.drop-zone-horizontal {
|
||||
width: 8px; /* Dünne vertikale Linie für horizontale Anordnung */
|
||||
height: auto; /* ✅ Höhe passt sich dem Inhalt an */
|
||||
align-self: center; /* ✅ Zentriert die Drop-Zone vertikal */
|
||||
background: rgba(0, 0, 255, 0.3);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Drop-Zonen für vertikale Layouts (zwischen Reihen) */
|
||||
.drop-zone-vertical {
|
||||
height: 6px; /* Dünne horizontale Linie für vertikale Anordnung */
|
||||
width: 100%;
|
||||
background: rgba(0, 0, 255, 0.3);
|
||||
margin: 2px 0; /* ✅ Kleinere Abstände */
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@ -1,91 +0,0 @@
|
||||
<template>
|
||||
<div class="row ms-0 ps-0 mb-4" style="text-align: left">
|
||||
<div class="col-3 ps-0 pe-0 me-5" style="border: 1px solid rgba(39, 68, 93, 0.39); width: 100%">
|
||||
<div @click.stop="toggleTable" class="d-inline-flex justify-content-between align-items-center align-items-sm-center align-items-md-center align-items-lg-center align-items-xl-center align-items-xxl-center ps-3" style="width: 100%; height: 50px; text-align: left; background: #71bbb2">
|
||||
<div class="d-inline-flex align-items-xl-center">
|
||||
<h1 style="font-size: 22px; text-align: left; width: auto">{{ tableTitleProp }} ({{ viewProp?.length || 0 }})</h1>
|
||||
<svg class="pointer bi bi-plus-circle-fill ps-0 ms-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" style="font-size: 18px; color: var(--bs-primary) !important">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8.5 4.5a.5.5 0 0 0-1 0v3h-3a.5.5 0 0 0 0 1h3v3a.5.5 0 0 0 1 0v-3h3a.5.5 0 0 0 0-1h-3z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div style="text-align: right; width: 50px">
|
||||
<svg v-if="showTable" @click.stop="toggleTable" :class="tableIcon" class="pointer bi me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" style="font-size: 25px">
|
||||
<path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"></path>
|
||||
</svg>
|
||||
<svg v-if="!showTable" @click.stop="toggleTable" class="pointer bi bi-chevron-right me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" style="font-size: 25px">
|
||||
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showTable" class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 80%">Name</th>
|
||||
<th>Änderungsdatum</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="pro-row" @click="setActivePage('FormDesignerLayout', 'Ansicht (' + row.name + ')', {}, 'process')" v-for="row in viewProp" :key="row.name + '-' + row.changed">
|
||||
<td>{{ row.name }}</td>
|
||||
<td>{{ row.changed }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useUiStore } from "../../store/uiStore.js";
|
||||
export default {
|
||||
name: "ProcessTableComponent",
|
||||
// Verwendete Komponenten
|
||||
components: {},
|
||||
// Props für die Komponente
|
||||
props: {
|
||||
viewProp: {
|
||||
type: Array,
|
||||
},
|
||||
tableTitleProp: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
// Daten der Komponente
|
||||
data() {
|
||||
return {
|
||||
showTable: false,
|
||||
uiStore: useUiStore(),
|
||||
};
|
||||
},
|
||||
|
||||
// Computed Propertiesprocesses.data
|
||||
computed: {
|
||||
openComponents() {
|
||||
return this.uiStore.openComponents;
|
||||
},
|
||||
},
|
||||
|
||||
// Methoden
|
||||
methods: {
|
||||
toggleTable() {
|
||||
this.showTable = !this.showTable;
|
||||
},
|
||||
setActivePage(componentName, displayName, props, menuItem) {
|
||||
this.uiStore.setActivePage(componentName, displayName, props, menuItem);
|
||||
},
|
||||
},
|
||||
|
||||
// Lifecycle-Hooks
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pro-row:hover td {
|
||||
background-color: rgba(113, 187, 178, 0.3);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@ -1,14 +1,37 @@
|
||||
<template>
|
||||
<div style="width: 4.5rem; height: 100%; margin-right: 4.5rem; top: 0; background: var(--bs-primary); height: 100dvh">
|
||||
<div
|
||||
style="
|
||||
width: 4.5rem;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
background: var(--bs-primary);
|
||||
height: 100dvh;
|
||||
"
|
||||
class="me-2"
|
||||
>
|
||||
<ul class="nav nav-pills flex-column text-center nav-flush mb-auto">
|
||||
<li v-for="(item, index) in menuItems" :key="index" class="nav-item" @mouseover="showPopover($event)" @mouseleave="hidePopoverWithDelay" :class="{ active: isActive(item.menuItem) }" @click="setActivePage(item.componentName, item.text)">
|
||||
<li
|
||||
v-for="(item, index) in menuItems"
|
||||
:key="index"
|
||||
class="nav-item"
|
||||
@mouseover="showPopover($event)"
|
||||
@mouseleave="hidePopoverWithDelay"
|
||||
:class="{ active: isActive(item.menuItem) }"
|
||||
@click="setActivePage(item.componentName, item.text)"
|
||||
>
|
||||
<a class="nav-link py-3 border-bottom rounded-0" href="#">
|
||||
<Icon :icon="item.icon" :style="{ fontSize: '20px' }" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Menu-Popover-->
|
||||
<div v-if="popoverVisible" class="popover-box" :style="popoverStyle" @mouseenter="popoverVisible = true" @mouseleave="hidePopoverWithDelay">
|
||||
<div
|
||||
v-if="popoverVisible"
|
||||
class="popover-box"
|
||||
:style="popoverStyle"
|
||||
@mouseenter="popoverVisible = true"
|
||||
@mouseleave="hidePopoverWithDelay"
|
||||
>
|
||||
<p>{{ popoverText }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -31,12 +54,42 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
menuItems: [
|
||||
{ icon: "carbon:home", text: "Dashboard", componentName: "DevelopmentDashboardComponent", menuItem: "home" },
|
||||
{ icon: "carbon:ibm-process-mining", text: "Prozesse", componentName: "ProcessComponent", menuItem: "process" },
|
||||
{ icon: "carbon:user-multiple", text: "Benutzer", componentName: "UserComponent", menuItem: "users" },
|
||||
{ icon: "carbon:building", text: "Firmen", componentName: "CompanyComponent", menuItem: "company" },
|
||||
{ icon: "carbon:object-storage", text: "Objekte", componentName: "StorageComponent", menuItem: "storage" },
|
||||
{ icon: "carbon:enumeration-usage", text: "Enums", componentName: "StatisticsComponent", menuItem: "stats" },
|
||||
{
|
||||
icon: "carbon:home",
|
||||
text: "Dashboard",
|
||||
componentName: "DevelopmentDashboardComponent",
|
||||
menuItem: "home",
|
||||
},
|
||||
{
|
||||
icon: "carbon:ibm-process-mining",
|
||||
text: "Prozesse",
|
||||
componentName: "ProcessComponent",
|
||||
menuItem: "process",
|
||||
},
|
||||
{
|
||||
icon: "carbon:user-multiple",
|
||||
text: "Benutzer",
|
||||
componentName: "UserComponent",
|
||||
menuItem: "users",
|
||||
},
|
||||
{
|
||||
icon: "carbon:building",
|
||||
text: "Firmen",
|
||||
componentName: "CompanyComponent",
|
||||
menuItem: "company",
|
||||
},
|
||||
{
|
||||
icon: "carbon:object-storage",
|
||||
text: "Objekte",
|
||||
componentName: "StorageComponent",
|
||||
menuItem: "storage",
|
||||
},
|
||||
{
|
||||
icon: "carbon:enumeration-usage",
|
||||
text: "Enums",
|
||||
componentName: "StatisticsComponent",
|
||||
menuItem: "stats",
|
||||
},
|
||||
],
|
||||
uiStore: useUiStore(),
|
||||
popoverVisible: false,
|
||||
@ -48,7 +101,9 @@ export default {
|
||||
// Computed Properties
|
||||
computed: {
|
||||
activeMenuItem() {
|
||||
const activeComponent = this.uiStore.openComponents.find((comp) => comp.active);
|
||||
const activeComponent = this.uiStore.openComponents.find(
|
||||
(comp) => comp.active
|
||||
);
|
||||
return activeComponent ? activeComponent.menuItem : null;
|
||||
},
|
||||
},
|
||||
@ -66,7 +121,9 @@ export default {
|
||||
top: `${rect.top + window.scrollY + 10}px`, // Höhe relativ zur Seite
|
||||
left: `${rect.right + 10}px`, // 10px rechts vom Element
|
||||
};
|
||||
const index = [...triggerElement.parentElement.children].indexOf(triggerElement);
|
||||
const index = [...triggerElement.parentElement.children].indexOf(
|
||||
triggerElement
|
||||
);
|
||||
this.popoverText = this.menuItems[index]?.text || "Info";
|
||||
}
|
||||
this.popoverVisible = true;
|
||||
@ -110,7 +167,7 @@ export default {
|
||||
|
||||
.popover-box {
|
||||
position: absolute;
|
||||
background: var(--bs-primary);
|
||||
background: #142330;
|
||||
color: white;
|
||||
padding: 6px;
|
||||
border-radius: 5px;
|
||||
|
||||
@ -2,10 +2,13 @@
|
||||
<div>
|
||||
<slot name="topbar"> </slot>
|
||||
<div class="d-inline-flex">
|
||||
<slot name="sidebar">test</slot>
|
||||
<slot name="sidebar"></slot>
|
||||
<Suspense>
|
||||
<template #default>
|
||||
<component :is="activePageComponent" v-bind="currentProps"></component>
|
||||
<component
|
||||
:is="activePageComponent"
|
||||
v-bind="currentProps"
|
||||
></component>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<p>Lade Seite...</p>
|
||||
@ -35,18 +38,32 @@ export default {
|
||||
computed: {
|
||||
activePageComponent() {
|
||||
const components = {
|
||||
Dashboard: defineAsyncComponent(() => import("@/pages/DevelopmentDashboard.vue")),
|
||||
TestComponent: defineAsyncComponent(() => import("@/pages/TestComponent.vue")),
|
||||
ProcessOverviewComponent: defineAsyncComponent(() => import("@/pages/ProcessOverviewComponent.vue")),
|
||||
FormDesignerLayout: defineAsyncComponent(() => import("@/layouts/FormDesignerLayout.vue")),
|
||||
Dashboard: defineAsyncComponent(() =>
|
||||
import("@/pages/DevelopmentDashboard.vue")
|
||||
),
|
||||
TestComponent: defineAsyncComponent(() =>
|
||||
import("@/pages/TestComponent.vue")
|
||||
),
|
||||
ProcessOverviewComponent: defineAsyncComponent(() =>
|
||||
import("@/modules/process/pages/ProcessOverviewComponent.vue")
|
||||
),
|
||||
FormDesignerLayout: defineAsyncComponent(() =>
|
||||
import("@/modules/process/layouts/FormDesignerLayout.vue")
|
||||
),
|
||||
};
|
||||
|
||||
const activeComponent = this.uiStore.openComponents.find((comp) => comp.active);
|
||||
const activeComponent = this.uiStore.openComponents.find(
|
||||
(comp) => comp.active
|
||||
);
|
||||
|
||||
return components[activeComponent?.componentName] || components["Dashboard"];
|
||||
return (
|
||||
components[activeComponent?.componentName] || components["Dashboard"]
|
||||
);
|
||||
},
|
||||
currentProps() {
|
||||
const activeComponent = this.uiStore.openComponents.find((comp) => comp.active);
|
||||
const activeComponent = this.uiStore.openComponents.find(
|
||||
(comp) => comp.active
|
||||
);
|
||||
return activeComponent?.props || {};
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,272 +0,0 @@
|
||||
<template>
|
||||
<div class="container mt-2" style="width: 100vw; height: 100vh">
|
||||
<div style="height: 100%">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link active" role="tab" data-bs-toggle="tab" href="#tab-1">
|
||||
<svg class="bi bi-file-text pe-0 me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" style="font-size: 16px">
|
||||
<path d="M5 4a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm-.5 2.5A.5.5 0 0 1 5 6h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5M5 8a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1z"></path>
|
||||
<path d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1"></path>
|
||||
</svg>
|
||||
Verwaltung
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link" role="tab" data-bs-toggle="tab" href="#tab-2">
|
||||
<svg class="bi bi-card-heading pe-0 me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2z"></path>
|
||||
<path d="M3 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5m0-5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5z"></path>
|
||||
</svg>
|
||||
Editor
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link" role="tab" data-bs-toggle="tab" href="#tab-3">
|
||||
<svg class="bi bi-diagram-3 pe-0 me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M6 3.5A1.5 1.5 0 0 1 7.5 2h1A1.5 1.5 0 0 1 10 3.5v1A1.5 1.5 0 0 1 8.5 6v1H14a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 2 7h5.5V6A1.5 1.5 0 0 1 6 4.5zM8.5 5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5zM0 11.5A1.5 1.5 0 0 1 1.5 10h1A1.5 1.5 0 0 1 4 11.5v1A1.5 1.5 0 0 1 2.5 14h-1A1.5 1.5 0 0 1 0 12.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm4.5.5A1.5 1.5 0 0 1 7.5 10h1a1.5 1.5 0 0 1 1.5 1.5v1A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm4.5.5a1.5 1.5 0 0 1 1.5-1.5h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5z"
|
||||
></path>
|
||||
</svg>
|
||||
Objektstruktur
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link" role="tab" data-bs-toggle="tab" href="#tab-4">
|
||||
<svg class="bi bi-filetype-json pe-0 me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M14 4.5V11h-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5zM4.151 15.29a1.176 1.176 0 0 1-.111-.449h.764a.578.578 0 0 0 .255.384c.07.049.154.087.25.114.095.028.201.041.319.041.164 0 .301-.023.413-.07a.559.559 0 0 0 .255-.193.507.507 0 0 0 .084-.29.387.387 0 0 0-.152-.326c-.101-.08-.256-.144-.463-.193l-.618-.143a1.72 1.72 0 0 1-.539-.214 1.001 1.001 0 0 1-.352-.367 1.068 1.068 0 0 1-.123-.524c0-.244.064-.457.19-.639.128-.181.304-.322.528-.422.225-.1.484-.149.777-.149.304 0 .564.05.779.152.217.102.384.239.5.41.12.17.186.359.2.566h-.75a.56.56 0 0 0-.12-.258.624.624 0 0 0-.246-.181.923.923 0 0 0-.37-.068c-.216 0-.387.05-.512.152a.472.472 0 0 0-.185.384c0 .121.048.22.144.3a.97.97 0 0 0 .404.175l.621.143c.217.05.406.12.566.211a1 1 0 0 1 .375.358c.09.148.135.335.135.56 0 .247-.063.466-.188.656a1.216 1.216 0 0 1-.539.439c-.234.105-.52.158-.858.158-.254 0-.476-.03-.665-.09a1.404 1.404 0 0 1-.478-.252 1.13 1.13 0 0 1-.29-.375Zm-3.104-.033a1.32 1.32 0 0 1-.082-.466h.764a.576.576 0 0 0 .074.27.499.499 0 0 0 .454.246c.19 0 .33-.055.422-.164.091-.11.137-.265.137-.466v-2.745h.791v2.725c0 .44-.119.774-.357 1.005-.237.23-.565.345-.985.345a1.59 1.59 0 0 1-.568-.094 1.145 1.145 0 0 1-.407-.266 1.14 1.14 0 0 1-.243-.39Zm9.091-1.585v.522c0 .256-.039.47-.117.641a.862.862 0 0 1-.322.387.877.877 0 0 1-.47.126.883.883 0 0 1-.47-.126.87.87 0 0 1-.32-.387 1.55 1.55 0 0 1-.117-.641v-.522c0-.258.039-.471.117-.641a.87.87 0 0 1 .32-.387.868.868 0 0 1 .47-.129c.177 0 .333.043.47.129a.862.862 0 0 1 .322.387c.078.17.117.383.117.641m.803.519v-.513c0-.377-.069-.701-.205-.973a1.46 1.46 0 0 0-.59-.63c-.253-.146-.559-.22-.916-.22-.356 0-.662.074-.92.22a1.441 1.441 0 0 0-.589.628c-.137.271-.205.596-.205.975v.513c0 .375.068.699.205.973.137.271.333.48.589.626.258.145.564.217.92.217.357 0 .663-.072.917-.217.256-.146.452-.355.589-.626.136-.274.205-.598.205-.973Zm1.29-.935v2.675h-.746v-3.999h.662l1.752 2.66h.032v-2.66h.75v4h-.656l-1.761-2.676h-.032Z"
|
||||
></path>
|
||||
</svg>
|
||||
JSON
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" style="height: 100%">
|
||||
<div id="tab-1" class="tab-pane active" role="tabpanel">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-xl-flex justify-content-xl-center align-items-xl-center" style="width: 300px; height: 200px; text-align: center; background: #71bbb2">
|
||||
<h1 style="font-size: 22px">Menüauswahl</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="d-xl-flex justify-content-xl-center align-items-xl-center" style="width: 300px; height: 200px; text-align: center; background: #71bbb2">
|
||||
<h1 style="font-size: 22px">Zuordnung</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-2" class="tab-pane" role="tabpanel" style="height: 100%">
|
||||
<div class="split-container">
|
||||
<!-- Linker Bereich mit fixer Höhe oben -->
|
||||
<div class="panel left" :style="{ width: leftWidth + 'px' }">
|
||||
<div class="top-panel" :style="{ height: topHeight + 'px' }">
|
||||
<FormDesignerTreeViewComponent :nodes="formJSON"></FormDesignerTreeViewComponent>
|
||||
</div>
|
||||
<div class="horizontal-splitter" @mousedown="startHorizontalResize"></div>
|
||||
<div class="bottom-panel" :style="{ height: bottomHeight + 'px' }">
|
||||
<FormDesignerToolsComponent></FormDesignerToolsComponent>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vertikaler Splitter -->
|
||||
<div class="splitter" @mousedown="startVerticalResize"></div>
|
||||
|
||||
<!-- Mittlerer Bereich -->
|
||||
<div class="panel middle p-3" :style="{ width: middleWidth + 'px' }">
|
||||
<FormRendererComponent :schema="formJSON" @update-schema="addElementToForm" />
|
||||
<slot name="middle"></slot>
|
||||
</div>
|
||||
|
||||
<!-- Zweiter vertikaler Splitter -->
|
||||
<div class="splitter" @mousedown="startSecondVerticalResize"></div>
|
||||
|
||||
<!-- Rechter Bereich -->
|
||||
<div class="panel right" :style="{ width: rightWidth + 'px' }">
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-3" class="tab-pane" role="tabpanel">
|
||||
<p>Content for tab 3.</p>
|
||||
</div>
|
||||
<div id="tab-4" class="tab-pane" role="tabpanel">
|
||||
<textarea class="pro-textarea" v-model="jsonOutput" readonly></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from "vue";
|
||||
import FormDesignerToolsComponent from "@/components/formDesigner/FormDesignerToolsComponent.vue";
|
||||
import FormDesignerTreeViewComponent from "@/components/formDesigner/FormDesignerTreeViewComponent.vue";
|
||||
import FormRendererComponent from "@/components/formDesigner/FormRendererComponent.vue";
|
||||
import FormJSON from "@/dummyData/formularData.json";
|
||||
|
||||
export default {
|
||||
name: "FormDesignerLayout",
|
||||
// Verwendete Komponenten
|
||||
components: {
|
||||
FormDesignerToolsComponent,
|
||||
FormDesignerTreeViewComponent,
|
||||
FormRendererComponent,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
leftWidth: 400, // Feste Breite für den linken Bereich
|
||||
rightWidth: 200, // Feste Breite für den rechten Bereich
|
||||
middleWidth: window.innerWidth - 400, // Dynamisch: Restliche Breite
|
||||
topHeight: 150, // Fixierte Höhe für den oberen Bereich
|
||||
bottomHeight: window.innerHeight - 150, // Dynamisch: Resthöhe
|
||||
isResizingVertical: false,
|
||||
isResizingSecondVertical: false,
|
||||
isResizingHorizontal: false,
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
formJSON: reactive([...FormJSON]),
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("resize", this.updateSizes);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener("resize", this.updateSizes);
|
||||
},
|
||||
methods: {
|
||||
updateSizes() {
|
||||
this.middleWidth = window.innerWidth - this.leftWidth - this.rightWidth;
|
||||
this.bottomHeight = window.innerHeight - this.topHeight;
|
||||
},
|
||||
addElementToForm(newSchema) {
|
||||
console.log("💾 `update-schema` angekommen in FormDesignerLayout! 🔥", newSchema);
|
||||
this.formJSON = newSchema;
|
||||
},
|
||||
|
||||
// Vertikaler Splitter (zwischen links und Mitte)
|
||||
startVerticalResize(event) {
|
||||
this.isResizingVertical = true;
|
||||
this.startX = event.clientX;
|
||||
document.addEventListener("mousemove", this.resizeVertical);
|
||||
document.addEventListener("mouseup", this.stopResize);
|
||||
},
|
||||
resizeVertical(event) {
|
||||
if (!this.isResizingVertical) return;
|
||||
const delta = event.clientX - this.startX;
|
||||
const newLeftWidth = this.leftWidth + delta;
|
||||
if (newLeftWidth >= 100 && this.middleWidth - delta >= 100) {
|
||||
this.leftWidth = newLeftWidth;
|
||||
this.middleWidth = window.innerWidth - this.leftWidth - this.rightWidth;
|
||||
}
|
||||
this.startX = event.clientX;
|
||||
},
|
||||
|
||||
// Zweiter vertikaler Splitter (zwischen Mitte und Rechts)
|
||||
startSecondVerticalResize(event) {
|
||||
this.isResizingSecondVertical = true;
|
||||
this.startX = event.clientX;
|
||||
document.addEventListener("mousemove", this.resizeSecondVertical);
|
||||
document.addEventListener("mouseup", this.stopResize);
|
||||
},
|
||||
resizeSecondVertical(event) {
|
||||
if (!this.isResizingSecondVertical) return;
|
||||
const delta = event.clientX - this.startX;
|
||||
const newRightWidth = this.rightWidth - delta;
|
||||
if (newRightWidth >= 100 && this.middleWidth + delta >= 100) {
|
||||
this.rightWidth = newRightWidth;
|
||||
this.middleWidth = window.innerWidth - this.leftWidth - this.rightWidth;
|
||||
}
|
||||
this.startX = event.clientX;
|
||||
},
|
||||
|
||||
// Horizontaler Splitter für den linken Bereich
|
||||
startHorizontalResize(event) {
|
||||
this.isResizingHorizontal = true;
|
||||
this.startY = event.clientY;
|
||||
document.addEventListener("mousemove", this.resizeHorizontal);
|
||||
document.addEventListener("mouseup", this.stopResize);
|
||||
},
|
||||
resizeHorizontal(event) {
|
||||
if (!this.isResizingHorizontal) return;
|
||||
const delta = event.clientY - this.startY;
|
||||
const newTopHeight = this.topHeight + delta;
|
||||
const newBottomHeight = this.bottomHeight - delta;
|
||||
|
||||
if (newTopHeight >= 50 && newBottomHeight >= 50) {
|
||||
this.topHeight = newTopHeight;
|
||||
this.bottomHeight = newBottomHeight;
|
||||
}
|
||||
this.startY = event.clientY;
|
||||
},
|
||||
|
||||
stopResize() {
|
||||
this.isResizingVertical = false;
|
||||
this.isResizingSecondVertical = false;
|
||||
this.isResizingHorizontal = false;
|
||||
document.removeEventListener("mousemove", this.resizeVertical);
|
||||
document.removeEventListener("mousemove", this.resizeSecondVertical);
|
||||
document.removeEventListener("mousemove", this.resizeHorizontal);
|
||||
document.removeEventListener("mouseup", this.stopResize);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
jsonOutput() {
|
||||
return JSON.stringify(this.formJSON, null, 2); // ✅ JSON sauber formatieren
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.split-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Linker Bereich mit fixer Höhe oben */
|
||||
.left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top-panel {
|
||||
height: 150px; /* Feste Höhe */
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.bottom-panel {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Allgemeine Panel-Styling */
|
||||
.panel {
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Splitter für vertikale Trennung */
|
||||
.splitter {
|
||||
width: 6px;
|
||||
background: #27445d21;
|
||||
cursor: ew-resize;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Splitter für horizontale Trennung */
|
||||
.horizontal-splitter {
|
||||
height: 3px;
|
||||
background: #27445d21;
|
||||
cursor: ns-resize;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.pro-textarea {
|
||||
height: 100dvh;
|
||||
width: 100dvh;
|
||||
}
|
||||
</style>
|
||||
@ -5,8 +5,13 @@ import "bootstrap/dist/css/bootstrap.min.css";
|
||||
import "bootstrap/dist/js/bootstrap.bundle.min.js";
|
||||
import "@/assets/custom-bootstrap.css";
|
||||
|
||||
import { client } from "@/modules/shared/services/WebSocketService";
|
||||
|
||||
(async () => {
|
||||
await client.connect();
|
||||
|
||||
const app = createApp(App);
|
||||
const pinia = createPinia();
|
||||
|
||||
app.use(pinia);
|
||||
app.mount("#app");
|
||||
})();
|
||||
|
||||
@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div class="component-container" :class="direction">
|
||||
<component
|
||||
:is="resolvedComponent"
|
||||
v-bind="element.props"
|
||||
@update-items="updateSchema"
|
||||
@dragover.prevent="onDragOver($event)"
|
||||
@drop="onDrop($event, index)"
|
||||
@dragleave="onDragLeave()"
|
||||
:class="getDropClass"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, defineAsyncComponent } from "vue";
|
||||
|
||||
const InputTextRenderer = defineAsyncComponent(() =>
|
||||
import(
|
||||
"@/modules/process/components/formDesigner/renderer/InputTextRenderer.vue"
|
||||
)
|
||||
);
|
||||
const InputNumberRenderer = defineAsyncComponent(() =>
|
||||
import(
|
||||
"@/modules/process/components/formDesigner/renderer/InputNumberRenderer.vue"
|
||||
)
|
||||
);
|
||||
const InputDateRenderer = defineAsyncComponent(() =>
|
||||
import(
|
||||
"@/modules/process/components/formDesigner/renderer/InputDateRenderer.vue"
|
||||
)
|
||||
);
|
||||
const LabelRenderer = defineAsyncComponent(() =>
|
||||
import("@/modules/process/components/formDesigner/renderer/LabelRenderer.vue")
|
||||
);
|
||||
const ButtonRenderer = defineAsyncComponent(() =>
|
||||
import(
|
||||
"@/modules/process/components/formDesigner/renderer/ButtonRenderer.vue"
|
||||
)
|
||||
);
|
||||
const FlexLayoutRenderer = defineAsyncComponent(() =>
|
||||
import(
|
||||
"@/modules/process/components/formDesigner/renderer/FlexLayoutRenderer.vue"
|
||||
)
|
||||
);
|
||||
|
||||
import { InputTextBlock } from "@/modules/process/models/formDesigner/blocks/InputTextBlock";
|
||||
import { ButtonBlock } from "@/modules/process/models/formDesigner/blocks/ButtonBlock";
|
||||
import { LabelBlock } from "@/modules/process/models/formDesigner/blocks/LabelBlock";
|
||||
import { FlexLayoutBlock } from "@/modules/process/models/formDesigner/blocks/FlexLayoutBlock";
|
||||
import { InputDateBlock } from "@/modules/process/models/formDesigner/blocks/InputDateBlock";
|
||||
import { InputNumberBlock } from "@/modules/process/models/formDesigner/blocks/InputNumberBlock";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ComponentRenderer",
|
||||
components: {
|
||||
InputTextRenderer,
|
||||
InputNumberRenderer,
|
||||
InputDateRenderer,
|
||||
LabelRenderer,
|
||||
ButtonRenderer,
|
||||
FlexLayoutRenderer,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dropIndicator: null,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
resolvedComponent() {
|
||||
const componentMap = {
|
||||
FlexLayout: "FlexLayoutRenderer",
|
||||
Label: "LabelRenderer",
|
||||
InputText: "InputTextRenderer",
|
||||
InputNumber: "InputNumberRenderer",
|
||||
InputDate: "InputDateRenderer",
|
||||
Button: "ButtonRenderer",
|
||||
};
|
||||
return componentMap[this.element.type] || null;
|
||||
},
|
||||
getDropClass() {
|
||||
if (!this.dropIndicator || this.dropIndicator?.type !== this.element.type)
|
||||
return "";
|
||||
switch (this.dropIndicator.position) {
|
||||
case "above":
|
||||
return "drop-above";
|
||||
case "below":
|
||||
return "drop-below";
|
||||
case "right":
|
||||
return "drop-right";
|
||||
case "left":
|
||||
return "drop-left";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateSchema(newItems) {
|
||||
this.$emit("update-schema", newItems);
|
||||
},
|
||||
onDrop(event, index) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if (
|
||||
this.dropIndicator?.position === "right" ||
|
||||
this.dropIndicator?.position === "below"
|
||||
) {
|
||||
index = index + 1;
|
||||
}
|
||||
const droppedType = event.dataTransfer.getData("text/plain");
|
||||
const newElement = this.createElementByType(droppedType);
|
||||
this.dropIndicator = null;
|
||||
if (newElement) {
|
||||
this.$emit("insert-item", { index, newElement });
|
||||
}
|
||||
},
|
||||
onDragOver(event) {
|
||||
event.stopPropagation();
|
||||
const bounds = event.currentTarget.getBoundingClientRect();
|
||||
const offsetX = event.clientX - bounds.left;
|
||||
const offsetY = event.clientY - bounds.top;
|
||||
|
||||
const isHorizontal = this.direction === "horizontal";
|
||||
|
||||
const position = isHorizontal
|
||||
? offsetX < bounds.width / 2
|
||||
? "left"
|
||||
: "right"
|
||||
: offsetY < bounds.height / 2
|
||||
? "above"
|
||||
: "below";
|
||||
|
||||
const curElement = this.element;
|
||||
this.dropIndicator = {
|
||||
type: curElement.type,
|
||||
position,
|
||||
};
|
||||
},
|
||||
onDragLeave() {
|
||||
if (
|
||||
this.dropIndicator &&
|
||||
this.dropIndicator?.type === this.element.type
|
||||
) {
|
||||
this.dropIndicator = null;
|
||||
}
|
||||
},
|
||||
createElementByType(type) {
|
||||
switch (type) {
|
||||
case "InputText":
|
||||
return new InputTextBlock({
|
||||
label: "Neues Textfeld",
|
||||
placeholder: "Beispieltext",
|
||||
});
|
||||
case "InputNumber":
|
||||
return new InputNumberBlock({
|
||||
label: "Neue Nummer",
|
||||
placeholder: "Hier Nummer eingeben",
|
||||
});
|
||||
case "InputDate":
|
||||
return new InputDateBlock({
|
||||
label: "Neues Datum",
|
||||
placeholder: "Hier Datum eingeben",
|
||||
});
|
||||
case "Button":
|
||||
return new ButtonBlock({ label: "Neuer Button" });
|
||||
case "Label":
|
||||
return new LabelBlock({ label: "Neues Label" });
|
||||
case "FlexLayout":
|
||||
return new FlexLayoutBlock({
|
||||
items: [new LabelBlock({ label: "Erstes Element" })],
|
||||
});
|
||||
default:
|
||||
console.warn("⚠ Unbekannter Typ:", type);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.component-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.component-container .drop-above::before,
|
||||
.component-container .drop-below::after,
|
||||
.component-container .drop-left::before,
|
||||
.component-container .drop-right::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background: #42b983;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.component-container .drop-above::before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
}
|
||||
.component-container .drop-below::after {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.component-container .drop-left::before {
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
}
|
||||
.component-container .drop-right::after {
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<div class="mt-2">
|
||||
<div class="btn-group formBtn m-1" role="group" aria-label="Button group">
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
:class="elementClass"
|
||||
@click="setActive('element')"
|
||||
>
|
||||
Eingabe
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
:class="layoutClass"
|
||||
@click="setActive('layout')"
|
||||
>
|
||||
Layout
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
:class="templateClass"
|
||||
@click="setActive('template')"
|
||||
>
|
||||
Template
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2 formElements" v-if="elementActive">
|
||||
<div
|
||||
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||
draggable="true"
|
||||
@dragstart="startDrag($event, 'InputText')"
|
||||
>
|
||||
<div
|
||||
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||
>
|
||||
<svg
|
||||
class="bi bi-input-cursor-text pe-0 me-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5 2a.5.5 0 0 1 .5-.5c.862 0 1.573.287 2.06.566.174.099.321.198.44.286.119-.088.266-.187.44-.286A4.165 4.165 0 0 1 10.5 1.5a.5.5 0 0 1 0 1c-.638 0-1.177.213-1.564.434a3.49 3.49 0 0 0-.436.294V7.5H9a.5.5 0 0 1 0 1h-.5v4.272c.1.08.248.187.436.294.387.221.926.434 1.564.434a.5.5 0 0 1 0 1 4.165 4.165 0 0 1-2.06-.566A4.561 4.561 0 0 1 8 13.65a4.561 4.561 0 0 1-.44.285 4.165 4.165 0 0 1-2.06.566.5.5 0 0 1 0-1c.638 0 1.177-.213 1.564-.434.188-.107.335-.214.436-.294V8.5H7a.5.5 0 0 1 0-1h.5V3.228a3.49 3.49 0 0 0-.436-.294A3.166 3.166 0 0 0 5.5 2.5.5.5 0 0 1 5 2"
|
||||
></path>
|
||||
<path
|
||||
d="M10 5h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4v1h4a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-4zM6 5V4H2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4v-1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Textfeld</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mb-1 mt-1" />
|
||||
<div
|
||||
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||
draggable="true"
|
||||
@dragstart="startDrag($event, 'InputNumber')"
|
||||
>
|
||||
<div
|
||||
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||
>
|
||||
<svg
|
||||
class="bi bi-0-square pe-0 me-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M7.988 12.158c-1.851 0-2.941-1.57-2.941-3.99V7.84c0-2.408 1.101-3.996 2.965-3.996 1.857 0 2.935 1.57 2.935 3.996v.328c0 2.408-1.101 3.99-2.959 3.99ZM8 4.951c-1.008 0-1.629 1.09-1.629 2.895v.31c0 1.81.627 2.895 1.629 2.895s1.623-1.09 1.623-2.895v-.31c0-1.8-.621-2.895-1.623-2.895Z"
|
||||
></path>
|
||||
<path
|
||||
d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Nummernfeld</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mb-1 mt-1" />
|
||||
<div
|
||||
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||
draggable="true"
|
||||
@dragstart="startDrag($event, 'InputDate')"
|
||||
>
|
||||
<div
|
||||
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||
>
|
||||
<svg
|
||||
class="bi bi-calendar3 pe-0 me-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M14 0H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2M1 3.857C1 3.384 1.448 3 2 3h12c.552 0 1 .384 1 .857v10.286c0 .473-.448.857-1 .857H2c-.552 0-1-.384-1-.857V3.857z"
|
||||
></path>
|
||||
<path
|
||||
d="M6.5 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Datumsfeld</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mb-1 mt-1" />
|
||||
<div
|
||||
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||
draggable="true"
|
||||
@dragstart="startDrag($event, 'Button')"
|
||||
>
|
||||
<div
|
||||
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||
>
|
||||
<svg
|
||||
class="bi bi-menu-button pe-0 me-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M0 1.5A1.5 1.5 0 0 1 1.5 0h8A1.5 1.5 0 0 1 11 1.5v2A1.5 1.5 0 0 1 9.5 5h-8A1.5 1.5 0 0 1 0 3.5zM1.5 1a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 0-.5-.5z"
|
||||
></path>
|
||||
<path
|
||||
d="m7.823 2.823-.396-.396A.25.25 0 0 1 7.604 2h.792a.25.25 0 0 1 .177.427l-.396.396a.25.25 0 0 1-.354 0zM0 8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm1 3v2a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2zm14-1V8a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2zM2 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0 4a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Button</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 formElements" v-if="layoutActive">
|
||||
<div
|
||||
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||
draggable="true"
|
||||
@dragstart="startDrag($event, 'FlexLayout')"
|
||||
>
|
||||
<div
|
||||
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||
>
|
||||
<svg
|
||||
class="bi bi-grid-1x2 pe-0 me-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M6 1H1v14h5zm9 0h-5v5h5zm0 9v5h-5v-5zM0 1a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1zm9 0a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1zm1 8a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1z"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Flexibles Layout</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mb-1 mt-1" />
|
||||
<div
|
||||
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||
draggable="true"
|
||||
@dragstart="startDrag($event, 'Label')"
|
||||
>
|
||||
<div
|
||||
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||
>
|
||||
<svg
|
||||
class="bi bi-alphabet-uppercase pe-0 me-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M1.226 10.88H0l2.056-6.26h1.42l2.047 6.26h-1.29l-.48-1.61H1.707l-.48 1.61ZM2.76 5.818h-.054l-.75 2.532H3.51zm3.217 5.062V4.62h2.56c1.09 0 1.808.582 1.808 1.54 0 .762-.444 1.22-1.05 1.372v.055c.736.074 1.365.587 1.365 1.528 0 1.119-.89 1.766-2.133 1.766h-2.55ZM7.18 5.55v1.675h.8c.812 0 1.171-.308 1.171-.853 0-.51-.328-.822-.898-.822zm0 2.537V9.95h.903c.951 0 1.342-.312 1.342-.909 0-.591-.382-.954-1.095-.954H7.18Zm5.089-.711v.775c0 1.156.49 1.803 1.347 1.803.705 0 1.163-.454 1.212-1.096H16v.12C15.942 10.173 14.95 11 13.607 11c-1.648 0-2.573-1.073-2.573-2.849v-.78c0-1.775.934-2.871 2.573-2.871 1.347 0 2.34.849 2.393 2.087v.115h-1.172c-.05-.665-.516-1.156-1.212-1.156-.849 0-1.347.67-1.347 1.83Z"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Label</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 formElements" v-if="templateActive">
|
||||
<div
|
||||
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||
>
|
||||
<div
|
||||
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||
>
|
||||
<svg
|
||||
class="bi bi-chevron-down pe-0 me-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Accordion</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mb-1 mt-1" />
|
||||
<div
|
||||
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||
>
|
||||
<div
|
||||
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||
>
|
||||
<svg
|
||||
class="bi bi-floppy pe-0 me-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M11 2H9v3h2z"></path>
|
||||
<path
|
||||
d="M1.5 0h11.586a1.5 1.5 0 0 1 1.06.44l1.415 1.414A1.5 1.5 0 0 1 16 2.914V14.5a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 14.5v-13A1.5 1.5 0 0 1 1.5 0M1 1.5v13a.5.5 0 0 0 .5.5H2v-4.5A1.5 1.5 0 0 1 3.5 9h9a1.5 1.5 0 0 1 1.5 1.5V15h.5a.5.5 0 0 0 .5-.5V2.914a.5.5 0 0 0-.146-.353l-1.415-1.415A.5.5 0 0 0 13.086 1H13v4.5A1.5 1.5 0 0 1 11.5 7h-7A1.5 1.5 0 0 1 3 5.5V1H1.5a.5.5 0 0 0-.5.5m3 4a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 .5-.5V1H4zM3 15h10v-4.5a.5.5 0 0 0-.5-.5h-9a.5.5 0 0 0-.5.5z"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="mb-0" style="font-size: 14px">Gespeicherter Eintrag 1</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FormDesignerToolsComponent",
|
||||
// Verwendete Komponenten
|
||||
components: {},
|
||||
// Props für die Komponente
|
||||
props: {},
|
||||
|
||||
// Daten der Komponente
|
||||
data() {
|
||||
return {
|
||||
elementActive: true,
|
||||
layoutActive: false,
|
||||
templateActive: false,
|
||||
};
|
||||
},
|
||||
|
||||
// Computed Properties
|
||||
computed: {
|
||||
elementClass() {
|
||||
if (this.elementActive) {
|
||||
return "active";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
layoutClass() {
|
||||
if (this.layoutActive) {
|
||||
return "active";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
templateClass() {
|
||||
if (this.templateActive) {
|
||||
return "active";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
},
|
||||
|
||||
// Methoden
|
||||
methods: {
|
||||
startDrag(event, type) {
|
||||
console.log("Dragging:", type);
|
||||
event.dataTransfer.setData("text/plain", type); // Speichere das Objekt als JSON
|
||||
},
|
||||
setActive(type) {
|
||||
switch (type) {
|
||||
case "element":
|
||||
this.elementActive = true;
|
||||
this.layoutActive = false;
|
||||
this.templateActive = false;
|
||||
break;
|
||||
case "layout":
|
||||
this.layoutActive = true;
|
||||
this.elementActive = false;
|
||||
this.templateActive = false;
|
||||
break;
|
||||
case "template":
|
||||
this.templateActive = true;
|
||||
this.elementActive = false;
|
||||
this.layoutActive = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Lifecycle-Hooks
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.accordion-button:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.formBtn {
|
||||
width: 97%;
|
||||
border: white solid 1px;
|
||||
}
|
||||
|
||||
.formBtn button:not(:last-child) {
|
||||
border-right: white solid 1px;
|
||||
}
|
||||
|
||||
.formBtn button {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.formBtn button.active {
|
||||
color: var(--bs-primary);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.formBtn button:hover {
|
||||
background-color: #617789;
|
||||
}
|
||||
|
||||
.formElements .formItem {
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.formElements .formItem:hover {
|
||||
background-color: #617789;
|
||||
cursor: grab;
|
||||
}
|
||||
</style>
|
||||
@ -1,9 +1,13 @@
|
||||
<template>
|
||||
<FlexLayoutRenderer direction="vertical" :items="schema" @update-items="$emit('update-schema', $event)" />
|
||||
<FlexLayoutRenderer
|
||||
direction="vertical"
|
||||
:items="schema"
|
||||
@update-items="$emit('update-schema', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FlexLayoutRenderer from "@/components/formDesigner/formRenderer/FlexLayoutRenderer.vue";
|
||||
import FlexLayoutRenderer from "@/modules/process/components/formDesigner/renderer/FlexLayoutRenderer.vue";
|
||||
|
||||
export default {
|
||||
name: "FormRendererComponent",
|
||||
@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div class="flex-layout" :class="computedDirection">
|
||||
<component-renderer
|
||||
v-for="(item, key) in items"
|
||||
:key="key"
|
||||
:element="item"
|
||||
:index="key"
|
||||
:direction="direction"
|
||||
@update-schema="updateChildItems(key, $event)"
|
||||
@insert-item="insertItem"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import ComponentRenderer from "@/modules/process/components/formDesigner/ComponentRenderer.vue";
|
||||
|
||||
import { InputTextBlock } from "@/modules/process/models/formDesigner/blocks/InputTextBlock";
|
||||
import { ButtonBlock } from "@/modules/process/models/formDesigner/blocks/ButtonBlock";
|
||||
import { LabelBlock } from "@/modules/process/models/formDesigner/blocks/LabelBlock";
|
||||
import { FlexLayoutBlock } from "@/modules/process/models/formDesigner/blocks/FlexLayoutBlock";
|
||||
import { InputDateBlock } from "@/modules/process/models/formDesigner/blocks/InputDateBlock";
|
||||
import { InputNumberBlock } from "@/modules/process/models/formDesigner/blocks/InputNumberBlock";
|
||||
|
||||
export default defineComponent({
|
||||
name: "FlexLayoutRenderer",
|
||||
components: { ComponentRenderer },
|
||||
data() {
|
||||
return {
|
||||
dragOverActive: false,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
computedDirection() {
|
||||
return this.direction === "horizontal" ? "horizontal" : "vertical";
|
||||
},
|
||||
computedDropClass() {
|
||||
return this.direction === "horizontal"
|
||||
? "drop-zone-horizontal"
|
||||
: "drop-zone-vertical";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateChildItems(index, newItems) {
|
||||
const updatedItems = [...this.items];
|
||||
updatedItems[index] = { ...updatedItems[index], items: newItems };
|
||||
this.$emit("update-items", updatedItems);
|
||||
},
|
||||
insertItem({ index, newElement }) {
|
||||
const updatedItems = [...this.items];
|
||||
|
||||
// 🛠 Stelle sicher, dass `index` innerhalb der Liste bleibt
|
||||
if (index < 0) index = 0;
|
||||
if (index > updatedItems.length) index = updatedItems.length;
|
||||
|
||||
updatedItems.splice(index, 0, newElement);
|
||||
|
||||
this.$emit("update-items", updatedItems);
|
||||
},
|
||||
createElementByType(type) {
|
||||
switch (type) {
|
||||
case "InputText":
|
||||
return new InputTextBlock({
|
||||
label: "Neues Textfeld",
|
||||
placeholder: "Beispieltext",
|
||||
});
|
||||
case "InputNumber":
|
||||
return new InputNumberBlock({
|
||||
label: "Neue Nummer",
|
||||
placeholder: "Hier Nummer eingeben",
|
||||
});
|
||||
case "InputDate":
|
||||
return new InputDateBlock({
|
||||
label: "Neues Datum",
|
||||
placeholder: "Hier Datum eingeben",
|
||||
});
|
||||
case "Button":
|
||||
return new ButtonBlock({ label: "Neuer Button" });
|
||||
case "Label":
|
||||
return new LabelBlock({ label: "Neues Label" });
|
||||
case "FlexLayout":
|
||||
return new FlexLayoutBlock({
|
||||
items: [new LabelBlock({ label: "Erstes Element" })],
|
||||
});
|
||||
default:
|
||||
console.warn("⚠ Unbekannter Typ:", type);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.flex-layout {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
/* Layouts richtig ausrichten */
|
||||
.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Drop-Zonen für horizontale Layouts (zwischen Spalten) */
|
||||
.drop-zone-horizontal {
|
||||
width: 8px; /* Dünne vertikale Linie für horizontale Anordnung */
|
||||
height: auto; /* ✅ Höhe passt sich dem Inhalt an */
|
||||
align-self: center; /* ✅ Zentriert die Drop-Zone vertikal */
|
||||
background: rgba(0, 0, 255, 0.3);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Drop-Zonen für vertikale Layouts (zwischen Reihen) */
|
||||
.drop-zone-vertical {
|
||||
height: 6px; /* Dünne horizontale Linie für vertikale Anordnung */
|
||||
width: 100%;
|
||||
background: rgba(0, 0, 255, 0.3);
|
||||
margin: 2px 0; /* ✅ Kleinere Abstände */
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<label>{{ value }}</label>
|
||||
<label>{{ label }}</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "LabelRenderer",
|
||||
props: {
|
||||
value: String,
|
||||
label: String,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
149
src/modules/process/components/process/ProcessTableComponent.vue
Normal file
149
src/modules/process/components/process/ProcessTableComponent.vue
Normal file
@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div class="row ms-0 ps-0 mb-4" style="text-align: left">
|
||||
<div
|
||||
class="col-3 ps-0 pe-0 me-5"
|
||||
style="border: 1px solid rgba(39, 68, 93, 0.39); width: 100%"
|
||||
>
|
||||
<div
|
||||
@click.stop="toggleTable"
|
||||
class="d-inline-flex justify-content-between align-items-center align-items-sm-center align-items-md-center align-items-lg-center align-items-xl-center align-items-xxl-center ps-3"
|
||||
style="width: 100%; height: 50px; text-align: left; background: #71bbb2"
|
||||
>
|
||||
<div class="d-inline-flex align-items-xl-center">
|
||||
<h1 style="font-size: 22px; text-align: left; width: auto">
|
||||
{{ tableTitleProp }} ({{ viewProp?.length || 0 }})
|
||||
</h1>
|
||||
<svg
|
||||
class="pointer bi bi-plus-circle-fill ps-0 ms-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
style="font-size: 18px; color: var(--bs-primary) !important"
|
||||
>
|
||||
<path
|
||||
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8.5 4.5a.5.5 0 0 0-1 0v3h-3a.5.5 0 0 0 0 1h3v3a.5.5 0 0 0 1 0v-3h3a.5.5 0 0 0 0-1h-3z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div style="text-align: right; width: 50px">
|
||||
<svg
|
||||
v-if="showTable"
|
||||
@click.stop="toggleTable"
|
||||
:class="tableIcon"
|
||||
class="pointer bi me-2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
style="font-size: 25px"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"
|
||||
></path>
|
||||
</svg>
|
||||
<svg
|
||||
v-if="!showTable"
|
||||
@click.stop="toggleTable"
|
||||
class="pointer bi bi-chevron-right me-2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
style="font-size: 25px"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showTable" class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 80%">Name</th>
|
||||
<th>Änderungsdatum</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
class="pro-row"
|
||||
@click="
|
||||
setActivePage(
|
||||
'FormDesignerLayout',
|
||||
'Ansicht (' + row.name + ')',
|
||||
{},
|
||||
'process'
|
||||
)
|
||||
"
|
||||
v-for="row in viewProp"
|
||||
:key="row.name + '-' + row.changed"
|
||||
>
|
||||
<td>{{ row.name }}</td>
|
||||
<td>{{ row.changed }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useUiStore } from "../../../../store/uiStore.js";
|
||||
export default {
|
||||
name: "ProcessTableComponent",
|
||||
// Verwendete Komponenten
|
||||
components: {},
|
||||
// Props für die Komponente
|
||||
props: {
|
||||
viewProp: {
|
||||
type: Array,
|
||||
},
|
||||
tableTitleProp: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
// Daten der Komponente
|
||||
data() {
|
||||
return {
|
||||
showTable: false,
|
||||
uiStore: useUiStore(),
|
||||
};
|
||||
},
|
||||
|
||||
// Computed Propertiesprocesses.data
|
||||
computed: {
|
||||
openComponents() {
|
||||
return this.uiStore.openComponents;
|
||||
},
|
||||
},
|
||||
|
||||
// Methoden
|
||||
methods: {
|
||||
toggleTable() {
|
||||
this.showTable = !this.showTable;
|
||||
},
|
||||
setActivePage(componentName, displayName, props, menuItem) {
|
||||
this.uiStore.setActivePage(componentName, displayName, props, menuItem);
|
||||
},
|
||||
},
|
||||
|
||||
// Lifecycle-Hooks
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pro-row:hover td {
|
||||
background-color: rgba(113, 187, 178, 0.3);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
397
src/modules/process/layouts/FormDesignerLayout.vue
Normal file
397
src/modules/process/layouts/FormDesignerLayout.vue
Normal file
@ -0,0 +1,397 @@
|
||||
<template>
|
||||
<div class="container mt-2" style="width: 100vw; height: 100vh">
|
||||
<div style="height: 100%; width: calc(100vw - 120px)">
|
||||
<ul class="nav nav-tabs" style="justify-content: center" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<a
|
||||
class="nav-link active"
|
||||
role="tab"
|
||||
data-bs-toggle="tab"
|
||||
href="#tab-1"
|
||||
>
|
||||
<svg
|
||||
class="bi bi-file-text pe-0 me-2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
style="font-size: 16px"
|
||||
>
|
||||
<path
|
||||
d="M5 4a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm-.5 2.5A.5.5 0 0 1 5 6h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5M5 8a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1z"
|
||||
></path>
|
||||
<path
|
||||
d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1"
|
||||
></path>
|
||||
</svg>
|
||||
Verwaltung
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link" role="tab" data-bs-toggle="tab" href="#tab-2">
|
||||
<svg
|
||||
class="bi bi-card-heading pe-0 me-2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2z"
|
||||
></path>
|
||||
<path
|
||||
d="M3 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5m0-5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5z"
|
||||
></path>
|
||||
</svg>
|
||||
Editor
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" style="height: 100%">
|
||||
<div id="tab-1" class="tab-pane active" role="tabpanel">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div
|
||||
class="d-xl-flex justify-content-xl-center align-items-xl-center"
|
||||
style="
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
text-align: center;
|
||||
background: #71bbb2;
|
||||
"
|
||||
>
|
||||
<h1 style="font-size: 22px">Menüauswahl</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div
|
||||
class="d-xl-flex justify-content-xl-center align-items-xl-center"
|
||||
style="
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
text-align: center;
|
||||
background: #71bbb2;
|
||||
"
|
||||
>
|
||||
<h1 style="font-size: 22px">Zuordnung</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-2" class="tab-pane" role="tabpanel" style="height: 100%">
|
||||
<div class="split-container">
|
||||
<!-- Linker Bereich mit fixer Höhe oben -->
|
||||
<div
|
||||
class="panel left"
|
||||
style="background-color: var(--bs-primary); color: white"
|
||||
:style="{ width: leftWidth + 'px' }"
|
||||
>
|
||||
<ul class="nav nav-tabs d-flex formPanel" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<a
|
||||
class="nav-link active"
|
||||
role="tab"
|
||||
data-bs-toggle="tab"
|
||||
href="#tab-elements"
|
||||
>
|
||||
<svg
|
||||
class="bi bi-file-text pe-0 me-2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
style="font-size: 16px"
|
||||
>
|
||||
<path
|
||||
d="M5 4a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm-.5 2.5A.5.5 0 0 1 5 6h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5M5 8a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1z"
|
||||
></path>
|
||||
<path
|
||||
d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1"
|
||||
></path>
|
||||
</svg>
|
||||
Elemente
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a
|
||||
class="nav-link"
|
||||
role="tab"
|
||||
data-bs-toggle="tab"
|
||||
href="#tab-tree"
|
||||
>
|
||||
<svg
|
||||
class="bi bi-card-heading pe-0 me-2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2z"
|
||||
></path>
|
||||
<path
|
||||
d="M3 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5m0-5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5z"
|
||||
></path>
|
||||
</svg>
|
||||
Baum
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" style="height: 100%">
|
||||
<div id="tab-tree" class="tab-pane" role="tabpanel">
|
||||
<FormDesignerTreeViewComponent
|
||||
:nodes="formJSON"
|
||||
></FormDesignerTreeViewComponent>
|
||||
</div>
|
||||
<div id="tab-elements" class="tab-pane active" role="tabpanel">
|
||||
<FormDesignerToolsComponent></FormDesignerToolsComponent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vertikaler Splitter -->
|
||||
<div class="splitter" @mousedown="startVerticalResize"></div>
|
||||
|
||||
<!-- Mittlerer Bereich -->
|
||||
<div
|
||||
class="panel middle p-3"
|
||||
:style="{ width: middleWidth + 'px' }"
|
||||
>
|
||||
<FormRendererComponent
|
||||
:schema="schema"
|
||||
@update-schema="addElementToForm"
|
||||
/>
|
||||
<slot name="middle"></slot>
|
||||
</div>
|
||||
|
||||
<!-- Zweiter vertikaler Splitter -->
|
||||
<div class="splitter" @mousedown="startSecondVerticalResize"></div>
|
||||
|
||||
<!-- Rechter Bereich -->
|
||||
<div
|
||||
class="panel right"
|
||||
style="background-color: var(--bs-primary); color: white"
|
||||
:style="{ width: rightWidth + 'px' }"
|
||||
>
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormDesignerToolsComponent from "@/modules/process/components/formDesigner/FormDesignerToolsComponent.vue";
|
||||
import FormDesignerTreeViewComponent from "@/modules/process/components/formDesigner/FormDesignerTreeViewComponent.vue";
|
||||
import FormRendererComponent from "@/modules/process/components/formDesigner/FormRendererComponent.vue";
|
||||
|
||||
import { InputTextBlock } from "@/modules/process/models/formDesigner/blocks/InputTextBlock";
|
||||
import { ButtonBlock } from "@/modules/process/models/formDesigner/blocks/ButtonBlock";
|
||||
import { LabelBlock } from "@/modules/process/models/formDesigner/blocks/LabelBlock";
|
||||
import { FlexLayoutBlock } from "@/modules/process/models/formDesigner/blocks/FlexLayoutBlock";
|
||||
import { InputDateBlock } from "@/modules/process/models/formDesigner/blocks/InputDateBlock";
|
||||
import { InputNumberBlock } from "@/modules/process/models/formDesigner/blocks/InputNumberBlock";
|
||||
|
||||
export default {
|
||||
name: "FormDesignerLayout",
|
||||
// Verwendete Komponenten
|
||||
components: {
|
||||
FormDesignerToolsComponent,
|
||||
FormDesignerTreeViewComponent,
|
||||
FormRendererComponent,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
leftWidth: 400, // Feste Breite für den linken Bereich
|
||||
rightWidth: 200, // Feste Breite für den rechten Bereich
|
||||
middleWidth: window.innerWidth - 400, // Dynamisch: Restliche Breite
|
||||
topHeight: 150, // Fixierte Höhe für den oberen Bereich
|
||||
bottomHeight: window.innerHeight - 150, // Dynamisch: Resthöhe
|
||||
isResizingVertical: false,
|
||||
isResizingSecondVertical: false,
|
||||
isResizingHorizontal: false,
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
schema: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("resize", this.updateSizes);
|
||||
this.schema = [
|
||||
new InputTextBlock({ label: "Vorname", placeholder: "Dein Name" }),
|
||||
new ButtonBlock({ label: "Klick mich an" }),
|
||||
new LabelBlock({ label: "Ich bin ein Label" }),
|
||||
new FlexLayoutBlock({
|
||||
items: [
|
||||
new InputDateBlock({
|
||||
label: "Geburtstag",
|
||||
placeholder: "Datumswert",
|
||||
}),
|
||||
new InputNumberBlock({
|
||||
label: "Alter",
|
||||
placeholder: "Gib dein Alter ein",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
];
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener("resize", this.updateSizes);
|
||||
},
|
||||
methods: {
|
||||
updateSizes() {
|
||||
this.middleWidth = window.innerWidth - this.leftWidth - this.rightWidth;
|
||||
this.bottomHeight = window.innerHeight - this.topHeight;
|
||||
},
|
||||
addElementToForm(newSchema) {
|
||||
this.schema = newSchema;
|
||||
},
|
||||
// Vertikaler Splitter (zwischen links und Mitte)
|
||||
startVerticalResize(event) {
|
||||
this.isResizingVertical = true;
|
||||
this.startX = event.clientX;
|
||||
document.addEventListener("mousemove", this.resizeVertical);
|
||||
document.addEventListener("mouseup", this.stopResize);
|
||||
},
|
||||
resizeVertical(event) {
|
||||
if (!this.isResizingVertical) return;
|
||||
const delta = event.clientX - this.startX;
|
||||
const newLeftWidth = this.leftWidth + delta;
|
||||
if (newLeftWidth >= 100 && this.middleWidth - delta >= 100) {
|
||||
this.leftWidth = newLeftWidth;
|
||||
this.middleWidth = window.innerWidth - this.leftWidth - this.rightWidth;
|
||||
}
|
||||
this.startX = event.clientX;
|
||||
},
|
||||
|
||||
// Zweiter vertikaler Splitter (zwischen Mitte und Rechts)
|
||||
startSecondVerticalResize(event) {
|
||||
this.isResizingSecondVertical = true;
|
||||
this.startX = event.clientX;
|
||||
document.addEventListener("mousemove", this.resizeSecondVertical);
|
||||
document.addEventListener("mouseup", this.stopResize);
|
||||
},
|
||||
resizeSecondVertical(event) {
|
||||
if (!this.isResizingSecondVertical) return;
|
||||
const delta = event.clientX - this.startX;
|
||||
const newRightWidth = this.rightWidth - delta;
|
||||
if (newRightWidth >= 100 && this.middleWidth + delta >= 100) {
|
||||
this.rightWidth = newRightWidth;
|
||||
this.middleWidth = window.innerWidth - this.leftWidth - this.rightWidth;
|
||||
}
|
||||
this.startX = event.clientX;
|
||||
},
|
||||
|
||||
// Horizontaler Splitter für den linken Bereich
|
||||
startHorizontalResize(event) {
|
||||
this.isResizingHorizontal = true;
|
||||
this.startY = event.clientY;
|
||||
document.addEventListener("mousemove", this.resizeHorizontal);
|
||||
document.addEventListener("mouseup", this.stopResize);
|
||||
},
|
||||
resizeHorizontal(event) {
|
||||
if (!this.isResizingHorizontal) return;
|
||||
const delta = event.clientY - this.startY;
|
||||
const newTopHeight = this.topHeight + delta;
|
||||
const newBottomHeight = this.bottomHeight - delta;
|
||||
|
||||
if (newTopHeight >= 50 && newBottomHeight >= 50) {
|
||||
this.topHeight = newTopHeight;
|
||||
this.bottomHeight = newBottomHeight;
|
||||
}
|
||||
this.startY = event.clientY;
|
||||
},
|
||||
|
||||
stopResize() {
|
||||
this.isResizingVertical = false;
|
||||
this.isResizingSecondVertical = false;
|
||||
this.isResizingHorizontal = false;
|
||||
document.removeEventListener("mousemove", this.resizeVertical);
|
||||
document.removeEventListener("mousemove", this.resizeSecondVertical);
|
||||
document.removeEventListener("mousemove", this.resizeHorizontal);
|
||||
document.removeEventListener("mouseup", this.stopResize);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
jsonOutput() {
|
||||
return JSON.stringify(this.formJSON, null, 2); // ✅ JSON sauber formatieren
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.split-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Linker Bereich mit fixer Höhe oben */
|
||||
.left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top-panel {
|
||||
height: 150px; /* Feste Höhe */
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.bottom-panel {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Allgemeine Panel-Styling */
|
||||
.panel {
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Splitter für vertikale Trennung */
|
||||
.splitter {
|
||||
width: 6px;
|
||||
background: #27445d21;
|
||||
cursor: ew-resize;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Splitter für horizontale Trennung */
|
||||
.horizontal-splitter {
|
||||
height: 3px;
|
||||
background: #27445d21;
|
||||
cursor: ns-resize;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.pro-textarea {
|
||||
height: 100dvh;
|
||||
width: 100dvh;
|
||||
}
|
||||
|
||||
.formPanel a {
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.formPanel .nav-item {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.formPanel .nav-link:not(.active) {
|
||||
background-color: white;
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.formPanel .nav-link.active {
|
||||
background-color: var(--bs-primary);
|
||||
color: white !important;
|
||||
}
|
||||
</style>
|
||||
18
src/modules/process/models/formDesigner/FormBlock.js
Normal file
18
src/modules/process/models/formDesigner/FormBlock.js
Normal file
@ -0,0 +1,18 @@
|
||||
export class FormBlock {
|
||||
constructor(type, props = {}) {
|
||||
this.type = type;
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
type: this.type,
|
||||
...this.props,
|
||||
};
|
||||
}
|
||||
|
||||
// Optional: Logik für spätere Validierung
|
||||
validate(value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
27
src/modules/process/models/formDesigner/FormBlockFactory.js
Normal file
27
src/modules/process/models/formDesigner/FormBlockFactory.js
Normal file
@ -0,0 +1,27 @@
|
||||
import { ButtonBlock } from "../../../utils/formular/blocks/ButtonBlock";
|
||||
import { FlexLayoutBlock } from "../../../utils/formular/blocks/FlexLayoutBlock";
|
||||
import { InputDateBlock } from "../../../utils/formular/blocks/InputDateBlock";
|
||||
import { InputNumberBlock } from "../../../utils/formular/blocks/InputNumberBlock";
|
||||
import { InputTextBlock } from "../../../utils/formular/blocks/InputTextBlock";
|
||||
import { LabelBlock } from "../../../utils/formular/blocks/LabelBlock";
|
||||
|
||||
export class FormBlockFactory {
|
||||
static createBlock(data) {
|
||||
switch (data.type) {
|
||||
case "InputText":
|
||||
return new InputTextBlock(data);
|
||||
case "Label":
|
||||
return new LabelBlock(data);
|
||||
case "Button":
|
||||
return new ButtonBlock(data);
|
||||
case "FlexLayout":
|
||||
return new FlexLayoutBlock(data);
|
||||
case "InputDate":
|
||||
return new InputDateBlock(data);
|
||||
case "InputNumber":
|
||||
return new InputNumberBlock(data);
|
||||
default:
|
||||
throw new Error(`Unbekannter Baustein-Typ: ${data.type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { FormBlock } from "../FormBlock";
|
||||
|
||||
export class ButtonBlock extends FormBlock {
|
||||
constructor({ label = "Klick mich" } = {}) {
|
||||
super("Button", {
|
||||
label,
|
||||
});
|
||||
}
|
||||
|
||||
validate() {
|
||||
console.log("Validier ist noch nicht fertig");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import { FormBlock } from "../FormBlock";
|
||||
|
||||
export class FlexLayoutBlock extends FormBlock {
|
||||
constructor({ direction = "horizontal", items = [] } = {}) {
|
||||
super("FlexLayout", {
|
||||
direction,
|
||||
items,
|
||||
});
|
||||
}
|
||||
|
||||
validate() {
|
||||
console.log("Validier ist noch nicht fertig");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import { FormBlock } from "../FormBlock";
|
||||
|
||||
export class InputDateBlock extends FormBlock {
|
||||
constructor({ label = "", placeholder = "" } = {}) {
|
||||
super("InputDate", {
|
||||
label,
|
||||
placeholder,
|
||||
});
|
||||
}
|
||||
|
||||
validate() {
|
||||
console.log("Validier ist noch nicht fertig");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import { FormBlock } from "../FormBlock";
|
||||
|
||||
export class InputNumberBlock extends FormBlock {
|
||||
constructor({ label = "", placeholder = "" } = {}) {
|
||||
super("InputNumber", {
|
||||
label,
|
||||
placeholder,
|
||||
});
|
||||
}
|
||||
|
||||
validate() {
|
||||
console.log("Validier ist noch nicht fertig");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import { FormBlock } from "../FormBlock";
|
||||
|
||||
export class InputTextBlock extends FormBlock {
|
||||
constructor({ label = "", placeholder = "" } = {}) {
|
||||
super("InputText", {
|
||||
label,
|
||||
placeholder,
|
||||
});
|
||||
}
|
||||
|
||||
validate() {
|
||||
console.log("Validier ist noch nicht fertig");
|
||||
}
|
||||
}
|
||||
13
src/modules/process/models/formDesigner/blocks/LabelBlock.js
Normal file
13
src/modules/process/models/formDesigner/blocks/LabelBlock.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { FormBlock } from "../FormBlock";
|
||||
|
||||
export class LabelBlock extends FormBlock {
|
||||
constructor({ label = "Ich bin ein Label" } = {}) {
|
||||
super("Label", {
|
||||
label,
|
||||
});
|
||||
}
|
||||
|
||||
validate() {
|
||||
console.log("Validier ist noch nicht fertig");
|
||||
}
|
||||
}
|
||||
15
src/modules/process/models/process/Process.js
Normal file
15
src/modules/process/models/process/Process.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { BaseModel } from "@/modules/shared/models/Basemodel";
|
||||
|
||||
export class Process extends BaseModel {
|
||||
constructor({ id, name, description } = {}) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.topic = "system_process";
|
||||
}
|
||||
|
||||
getPublicFields() {
|
||||
return ["id", "name", "description"];
|
||||
}
|
||||
}
|
||||
76
src/modules/process/pages/ProcessOverviewComponent.vue
Normal file
76
src/modules/process/pages/ProcessOverviewComponent.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="container mt-2" style="width: 100vw; height: 100vh">
|
||||
<h1 class="mb-3">{{ processObject.name }}</h1>
|
||||
<label class="form-label mb-0" style="font-size: 14px">Beschreibung</label>
|
||||
<textarea
|
||||
class="form-control-sm mb-3 pb-2 ms-0 me-0"
|
||||
wrap="hard"
|
||||
style="width: 100%"
|
||||
v-model="processObject.description"
|
||||
></textarea>
|
||||
<ProcessTableComponent
|
||||
:viewProp="objectData"
|
||||
:tableTitleProp="'Ansichten'"
|
||||
></ProcessTableComponent>
|
||||
<ProcessTableComponent
|
||||
:viewProp="processObject.workflows"
|
||||
:tableTitleProp="'Workflows'"
|
||||
></ProcessTableComponent>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProcessTableComponent from "@/modules/process/components/process/ProcessTableComponent.vue";
|
||||
import { getObject } from "@/utils/getObject.js";
|
||||
//import { getSpecificObject } from "@/utils/getSpecificObject";
|
||||
export default {
|
||||
name: "ProcessOverviewComponent",
|
||||
// Verwendete Komponenten
|
||||
components: {
|
||||
ProcessTableComponent,
|
||||
},
|
||||
// Props für die Komponente
|
||||
props: {
|
||||
object: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
objectId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
// Daten der Komponente
|
||||
data() {
|
||||
return {
|
||||
processObject: {},
|
||||
objectData: {},
|
||||
};
|
||||
},
|
||||
|
||||
// Computed Propertiesprocesses.data
|
||||
computed: {},
|
||||
|
||||
// Methoden
|
||||
methods: {},
|
||||
|
||||
// Lifecycle-Hooks
|
||||
mounted() {
|
||||
this.processObject = { ...this.object };
|
||||
console.log(this.processObject);
|
||||
this.wsHandler = getObject(
|
||||
"system_processView",
|
||||
["name", "description"],
|
||||
(id, updated) => {
|
||||
this.objectData = { ...this.objectData, [id]: updated };
|
||||
}
|
||||
);
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.specificObjectHandler?.unsubscribe();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
74
src/modules/shared/models/Basemodel.js
Normal file
74
src/modules/shared/models/Basemodel.js
Normal file
@ -0,0 +1,74 @@
|
||||
import socketService from "@/modules/shared/services/WebSocketService";
|
||||
import { reactive } from "vue";
|
||||
|
||||
export class BaseModel {
|
||||
constructor() {
|
||||
this._activeSubscriptions = new Map();
|
||||
}
|
||||
|
||||
makeReactive() {
|
||||
return reactive(this);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this.getPublicFields().reduce((obj, key) => {
|
||||
obj[key] = this[key];
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
subscribe(fields = [], callback = null) {
|
||||
if (!this.topic || !this.id) {
|
||||
console.warn("BaseModel.subscribe(): 'topic' und 'id' fehlen.");
|
||||
return;
|
||||
}
|
||||
|
||||
fields.forEach((field) => {
|
||||
const filter = { _id: this.id };
|
||||
const key = `${field}`;
|
||||
|
||||
const handler = (value) => {
|
||||
const newValue = Array.isArray(value)
|
||||
? value[0]?.transientValue
|
||||
: value?.transientValue ?? value;
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this, field)) {
|
||||
// Feld existiert bereits -> Vue kann es tracken
|
||||
this[field] = newValue;
|
||||
} else {
|
||||
// 🧠 Feld existiert noch nicht -> tricksen:
|
||||
Object.assign(this, { [field]: newValue });
|
||||
}
|
||||
|
||||
if (typeof callback === "function") {
|
||||
callback(field, newValue);
|
||||
}
|
||||
};
|
||||
|
||||
this._activeSubscriptions.set(key, { field, filter, handler });
|
||||
socketService.subscribe(this.topic, field, filter, handler);
|
||||
});
|
||||
}
|
||||
|
||||
unsubscribe(fields = []) {
|
||||
if (!this.topic || !this.id) return;
|
||||
|
||||
fields.forEach((field) => {
|
||||
const key = `${field}`;
|
||||
const sub = this._activeSubscriptions.get(key);
|
||||
if (sub) {
|
||||
socketService.unsubscribe(
|
||||
this.topic,
|
||||
sub.field,
|
||||
sub.filter,
|
||||
sub.handler
|
||||
);
|
||||
this._activeSubscriptions.delete(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
unsubscribeAll() {
|
||||
this.unsubscribe([...this._activeSubscriptions.keys()]);
|
||||
}
|
||||
}
|
||||
53
src/modules/shared/services/SubscriptionManager.js
Normal file
53
src/modules/shared/services/SubscriptionManager.js
Normal file
@ -0,0 +1,53 @@
|
||||
function generateKey(subject, field, filter = {}) {
|
||||
return `${subject}/${field}/${JSON.stringify(filter)}`;
|
||||
}
|
||||
|
||||
export class SubscriptionManager {
|
||||
constructor(socketClient) {
|
||||
this.socketClient = socketClient;
|
||||
this.subscribers = {}; // { key: [callback1, callback2, ...] }
|
||||
|
||||
this.socketClient.onEvent(this._handleMessage.bind(this));
|
||||
}
|
||||
|
||||
_handleMessage(event) {
|
||||
if (event.type !== "message") return;
|
||||
|
||||
const { subject, field, filter = {}, value } = event.payload;
|
||||
const key = generateKey(subject, field, filter);
|
||||
|
||||
if (this.subscribers[key]) {
|
||||
this.subscribers[key].forEach((cb) => {
|
||||
if (typeof cb === "function") {
|
||||
cb(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
subscribe(subject, field, filter = {}, callback) {
|
||||
const key = generateKey(subject, field, filter);
|
||||
|
||||
if (!this.subscribers[key]) {
|
||||
this.subscribers[key] = [];
|
||||
console.log({ action: "subscribe", subject, field, filter });
|
||||
this.socketClient.send({ action: "subscribe", subject, field, filter });
|
||||
}
|
||||
|
||||
this.subscribers[key].push(callback);
|
||||
}
|
||||
|
||||
unsubscribe(subject, field, filter = {}, callback) {
|
||||
const key = generateKey(subject, field, filter);
|
||||
const callbacks = this.subscribers[key];
|
||||
|
||||
if (!callbacks) return;
|
||||
|
||||
this.subscribers[key] = callbacks.filter((cb) => cb !== callback);
|
||||
|
||||
if (this.subscribers[key].length === 0) {
|
||||
this.socketClient.send({ action: "unsubscribe", subject, field, filter });
|
||||
delete this.subscribers[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/modules/shared/services/WebSocketClient.js
Normal file
61
src/modules/shared/services/WebSocketClient.js
Normal file
@ -0,0 +1,61 @@
|
||||
const VUE_APP_WS_URL = process.env.VUE_APP_WS_URL || "ws://localhost";
|
||||
|
||||
export class WebSocketClient {
|
||||
constructor(url = VUE_APP_WS_URL) {
|
||||
this.url = url;
|
||||
this.socket = null;
|
||||
this.handlers = new Set();
|
||||
}
|
||||
|
||||
connect() {
|
||||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.socket = new WebSocket(this.url);
|
||||
|
||||
this.socket.onopen = () => {
|
||||
console.log("WebSocket verbunden.");
|
||||
this.handlers.forEach((h) => h({ type: "open" }));
|
||||
resolve(); // ✅ Promise wird hier erfüllt
|
||||
};
|
||||
|
||||
this.socket.onmessage = (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
this.handlers.forEach((h) =>
|
||||
h({ type: "message", payload: message })
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("WebSocket-Parsing-Fehler:", err);
|
||||
}
|
||||
};
|
||||
|
||||
this.socket.onclose = () => {
|
||||
console.warn("WebSocket getrennt. Reconnect in 2s...");
|
||||
//setTimeout(() => this.connect(), 2000); // <- ruft sich selbst erneut auf
|
||||
};
|
||||
|
||||
this.socket.onerror = (err) => {
|
||||
console.error("WebSocket-Fehler:", err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
send(data) {
|
||||
if (this.socket?.readyState === WebSocket.OPEN) {
|
||||
this.socket.send(JSON.stringify(data));
|
||||
} else {
|
||||
console.warn("WebSocket nicht bereit:", data);
|
||||
}
|
||||
}
|
||||
|
||||
onEvent(callback) {
|
||||
this.handlers.add(callback);
|
||||
}
|
||||
|
||||
removeEvent(callback) {
|
||||
this.handlers.delete(callback);
|
||||
}
|
||||
}
|
||||
7
src/modules/shared/services/WebSocketService.js
Normal file
7
src/modules/shared/services/WebSocketService.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { WebSocketClient } from "./WebSocketClient";
|
||||
import { SubscriptionManager } from "./SubscriptionManager";
|
||||
|
||||
export const client = new WebSocketClient();
|
||||
const subscriptionManager = new SubscriptionManager(client);
|
||||
|
||||
export default subscriptionManager;
|
||||
@ -1,32 +1,17 @@
|
||||
<template>
|
||||
<div class="container mt-2" style="width: 100vw; height: 100vh">
|
||||
<h1 class="mb-3">
|
||||
processes
|
||||
<svg
|
||||
class="bi bi-plus-circle-fill ps-0 ms-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
style="font-size: 20px; color: var(--bs-primary) !important"
|
||||
>
|
||||
<path
|
||||
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8.5 4.5a.5.5 0 0 0-1 0v3h-3a.5.5 0 0 0 0 1h3v3a.5.5 0 0 0 1 0v-3h3a.5.5 0 0 0 0-1h-3z"
|
||||
></path>
|
||||
</svg>
|
||||
</h1>
|
||||
<h1 class="mb-3">Prozesse</h1>
|
||||
<div class="row" style="text-align: left">
|
||||
<div
|
||||
v-for="process in processes"
|
||||
:key="process.id"
|
||||
v-for="(object, objectId) in objectData"
|
||||
:key="objectId"
|
||||
class="col-3 ps-0 pe-0 me-5 pointer"
|
||||
style="border: 1px solid rgba(39, 68, 93, 0.39); min-height: 130px"
|
||||
@click="
|
||||
setActivePage(
|
||||
'ProcessOverviewComponent',
|
||||
processData[process.id]?.name || 'Unknown',
|
||||
{ process },
|
||||
object.name || 'Unknown',
|
||||
{ object: object, objectId: objectId },
|
||||
'process'
|
||||
)
|
||||
"
|
||||
@ -41,11 +26,11 @@
|
||||
"
|
||||
>
|
||||
<h1 style="font-size: 22px">
|
||||
{{ processData[process.id]?.name || "Loading..." }}
|
||||
{{ object.name || "Loading..." }}
|
||||
</h1>
|
||||
</div>
|
||||
<p class="ms-2 pe-2 pt-2 pb-2" style="font-size: 14px">
|
||||
{{ process.description }}
|
||||
{{ object.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -53,17 +38,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useUiStore } from "../store/uiStore.js";
|
||||
import WebSocketService from "@/services/WebSocketService";
|
||||
import { useUiStore } from "@/store/uiStore.js";
|
||||
//import { getObject } from "@/utils/getObject.js";
|
||||
import socketService from "@/modules/shared/services/WebSocketService";
|
||||
import { Process } from "@/modules/process/models/process/Process";
|
||||
|
||||
export default {
|
||||
name: "DevelopmentDashboard",
|
||||
data() {
|
||||
return {
|
||||
processes: [],
|
||||
processData: {},
|
||||
callbacks: {}, // field-wise callbacks per processId
|
||||
fields: ["name"], // fields to subscribe
|
||||
objectData: [],
|
||||
uiStore: useUiStore(),
|
||||
};
|
||||
},
|
||||
@ -72,66 +56,26 @@ export default {
|
||||
setActivePage(componentName, displayName, props, menuItem) {
|
||||
this.uiStore.setActivePage(componentName, displayName, props, menuItem);
|
||||
},
|
||||
|
||||
handleNewProcessId(processId) {
|
||||
if (this.processes.some((p) => p.id === processId)) return;
|
||||
|
||||
this.processes.push({ id: processId });
|
||||
|
||||
if (!this.processData[processId]) {
|
||||
this.processData[processId] = {};
|
||||
}
|
||||
|
||||
if (!this.callbacks[processId]) {
|
||||
this.callbacks[processId] = {};
|
||||
}
|
||||
|
||||
this.fields.forEach((field) => {
|
||||
const callback = (value) => {
|
||||
this.handleUpdate(processId, field, value);
|
||||
};
|
||||
this.callbacks[processId][field] = callback;
|
||||
|
||||
WebSocketService.subscribe(
|
||||
"process",
|
||||
field,
|
||||
{ id: processId },
|
||||
callback
|
||||
);
|
||||
});
|
||||
console.log(this.processes);
|
||||
},
|
||||
|
||||
handleUpdate(processId, field, value) {
|
||||
if (!this.processData[processId]) {
|
||||
this.processData[processId] = {};
|
||||
}
|
||||
this.processData[processId][field] = value;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this._idCallback = (value) => {
|
||||
const ids = [...new Set(Array.isArray(value) ? value : [value])];
|
||||
ids.forEach((id) => this.handleNewProcessId(id));
|
||||
};
|
||||
WebSocketService.subscribe("process", "_id", {}, this._idCallback);
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
Object.entries(this.callbacks).forEach(([processId, fieldMap]) => {
|
||||
Object.entries(fieldMap).forEach(([field, callback]) => {
|
||||
WebSocketService.unsubscribe(
|
||||
"process",
|
||||
field,
|
||||
{ id: processId },
|
||||
callback
|
||||
);
|
||||
socketService.subscribe("system_process", "_id", {}, (val) => {
|
||||
val.forEach((element) => {
|
||||
let curProcess = new Process({ id: element }).makeReactive();
|
||||
curProcess.subscribe(["name", "description"]);
|
||||
this.objectData.push(curProcess);
|
||||
});
|
||||
});
|
||||
|
||||
WebSocketService.unsubscribe("process", "_id", {}, this._idCallback);
|
||||
/*this.wsHandler = getObject(
|
||||
"system_process",
|
||||
["name", "description"],
|
||||
(id, updated) => {
|
||||
this.objectData = { ...this.objectData, [id]: updated };
|
||||
}
|
||||
);*/
|
||||
},
|
||||
|
||||
beforeUnmount() {},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
<template>
|
||||
<div class="container mt-2" style="width: 100vw; height: 100vh">
|
||||
<h1 class="mb-3">{{ processObject.name }}</h1>
|
||||
<label class="form-label mb-0" style="font-size: 14px">Bemerkung</label>
|
||||
<textarea class="form-control-sm mb-3 pb-2 ms-0 me-0" wrap="hard" style="width: 100%" v-model="processObject.description"></textarea>
|
||||
<ProcessTableComponent :viewProp="processObject.views" :tableTitleProp="'Ansichten'"></ProcessTableComponent>
|
||||
<ProcessTableComponent :viewProp="processObject.workflows" :tableTitleProp="'Workflows'"></ProcessTableComponent>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import processes from "@/dummyData/processes.json";
|
||||
import ProcessTableComponent from "@/components/process/ProcessTableComponent.vue";
|
||||
export default {
|
||||
name: "ProcessOverviewComponent",
|
||||
// Verwendete Komponenten
|
||||
components: {
|
||||
ProcessTableComponent,
|
||||
},
|
||||
// Props für die Komponente
|
||||
props: {
|
||||
process: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
// Daten der Komponente
|
||||
data() {
|
||||
return {
|
||||
processObject: {},
|
||||
};
|
||||
},
|
||||
|
||||
// Computed Propertiesprocesses.data
|
||||
computed: {},
|
||||
|
||||
// Methoden
|
||||
methods: {
|
||||
getProcess(processId) {
|
||||
if (processId) {
|
||||
let currentProcess = processes.processes.find((processEntry) => processEntry.id === processId);
|
||||
if (currentProcess) {
|
||||
this.processObject = currentProcess;
|
||||
} else {
|
||||
console.error(`Prozess mit ID ${processId} nicht gefunden.`);
|
||||
}
|
||||
} else {
|
||||
console.error("Kein gültiger Prozess-ID übergeben.");
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Lifecycle-Hooks
|
||||
mounted() {
|
||||
if (this.process && this.process.id) {
|
||||
this.getProcess(this.process.id);
|
||||
} else {
|
||||
console.error("Prozess ist undefiniert oder hat keine ID.");
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
66
src/utils/getObject.js.js
Normal file
66
src/utils/getObject.js.js
Normal file
@ -0,0 +1,66 @@
|
||||
import WebSocketService from "@/services/WebSocketService";
|
||||
|
||||
/**
|
||||
* Synchronisiert Objekte per WebSocket über ID + Felder
|
||||
* @param {string} subject
|
||||
* @param {string[]} fields
|
||||
* @param {(id: string, data: Object) => void} [onUpdate]
|
||||
* @returns {{ dataMap: Object, unsubscribe: Function }}
|
||||
*/
|
||||
export function getObject(subject, fields = [], onUpdate) {
|
||||
const dataMap = {};
|
||||
const callbacks = {};
|
||||
let idCallback = null;
|
||||
|
||||
idCallback = (value) => {
|
||||
const ids = [...new Set(Array.isArray(value) ? value : [value])];
|
||||
|
||||
ids.forEach((id) => {
|
||||
if (dataMap[id]) return;
|
||||
|
||||
dataMap[id] = {};
|
||||
callbacks[id] = {};
|
||||
|
||||
fields.forEach((field) => {
|
||||
const callback = (val) => {
|
||||
let safeValue;
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
if (val.length === 1) {
|
||||
// Nur ein Wert → direkt den Wert extrahieren
|
||||
safeValue = val[0]?.transientValue;
|
||||
} else {
|
||||
// Mehrere Werte → Liste aus transientValue extrahieren
|
||||
safeValue = val.map((entry) => entry.transientValue);
|
||||
}
|
||||
} else {
|
||||
safeValue = undefined; // oder throw error / fallback
|
||||
}
|
||||
|
||||
dataMap[id][field] = safeValue;
|
||||
|
||||
if (typeof onUpdate === "function") {
|
||||
onUpdate(id, { ...dataMap[id] });
|
||||
}
|
||||
};
|
||||
|
||||
callbacks[id][field] = callback;
|
||||
WebSocketService.subscribe(subject, field, { _id: id }, callback);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
WebSocketService.subscribe(subject, "_id", {}, idCallback);
|
||||
|
||||
return {
|
||||
dataMap,
|
||||
unsubscribe: () => {
|
||||
Object.entries(callbacks).forEach(([id, fieldMap]) => {
|
||||
Object.entries(fieldMap).forEach(([field, cb]) => {
|
||||
WebSocketService.unsubscribe(subject, field, { _id: id }, cb);
|
||||
});
|
||||
});
|
||||
WebSocketService.unsubscribe(subject, "_id", {}, idCallback);
|
||||
},
|
||||
};
|
||||
}
|
||||
47
src/utils/getSpecificObject.js
Normal file
47
src/utils/getSpecificObject.js
Normal file
@ -0,0 +1,47 @@
|
||||
import WebSocketService from "@/services/WebSocketService";
|
||||
|
||||
/**
|
||||
* Holt ein spezifisches Objekt per WebSocket anhand eines Filters
|
||||
* @param {string} subject
|
||||
* @param {Object} filter
|
||||
* @param {string[]} fields
|
||||
* @param {(data: Object) => void} [onUpdate]
|
||||
* @returns {{ object: Object, unsubscribe: Function }}
|
||||
*/
|
||||
export function getSpecificObject(subject, filter = {}, fields = [], onUpdate) {
|
||||
const object = {};
|
||||
const callbacks = {};
|
||||
|
||||
fields.forEach((field) => {
|
||||
const callback = (val) => {
|
||||
let safeValue;
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
safeValue =
|
||||
val.length === 1
|
||||
? val[0]?.transientValue
|
||||
: val.map((v) => v.transientValue);
|
||||
} else {
|
||||
safeValue = undefined;
|
||||
}
|
||||
|
||||
object[field] = safeValue;
|
||||
|
||||
if (typeof onUpdate === "function") {
|
||||
onUpdate({ ...object });
|
||||
}
|
||||
};
|
||||
|
||||
callbacks[field] = callback;
|
||||
WebSocketService.subscribe(subject, field, filter, callback);
|
||||
});
|
||||
|
||||
return {
|
||||
object,
|
||||
unsubscribe: () => {
|
||||
Object.entries(callbacks).forEach(([field, cb]) => {
|
||||
WebSocketService.unsubscribe(subject, field, filter, cb);
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user