Added Worship page rendering
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
const { Worship } = require('../models');
|
const { Worship, EventPlace } = require('../models');
|
||||||
|
const { Op } = require('sequelize'); // Importieren Sie die Operatoren von Sequelize
|
||||||
|
|
||||||
exports.getAllWorships = async (req, res) => {
|
exports.getAllWorships = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -14,6 +15,7 @@ exports.createWorship = async (req, res) => {
|
|||||||
const worship = await Worship.create(req.body);
|
const worship = await Worship.create(req.body);
|
||||||
res.status(201).json(worship);
|
res.status(201).json(worship);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
res.status(500).json({ message: 'Fehler beim Erstellen des Gottesdienstes' });
|
res.status(500).json({ message: 'Fehler beim Erstellen des Gottesdienstes' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -45,3 +47,31 @@ exports.deleteWorship = async (req, res) => {
|
|||||||
res.status(500).json({ message: 'Fehler beim Löschen des Gottesdienstes' });
|
res.status(500).json({ message: 'Fehler beim Löschen des Gottesdienstes' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.getFilteredWorships = async (req, res) => {
|
||||||
|
const { location, orderBy } = req.query;
|
||||||
|
const where = {};
|
||||||
|
|
||||||
|
if (location && location !== '-1') {
|
||||||
|
where.eventPlaceId = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
where.date = {
|
||||||
|
[Op.gte]: new Date(), // Only include events from today onwards
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const worships = await Worship.findAll({
|
||||||
|
where,
|
||||||
|
include: {
|
||||||
|
model: EventPlace,
|
||||||
|
as: 'eventPlace',
|
||||||
|
},
|
||||||
|
order: [orderBy.split(' ')],
|
||||||
|
});
|
||||||
|
res.status(200).json(worships);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.status(500).json({ message: 'Fehler beim Abrufen der gefilterten Gottesdienste' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
16
migrations/20240617095455-add-day-name-to-worship.js
Normal file
16
migrations/20240617095455-add-day-name-to-worship.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/** @type {import('sequelize-cli').Migration} */
|
||||||
|
module.exports = {
|
||||||
|
up: async (queryInterface, Sequelize) => {
|
||||||
|
await queryInterface.addColumn('worships', 'day_name', {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
default: ''
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: async (queryInterface, Sequelize) => {
|
||||||
|
await queryInterface.removeColumn('worships', 'day_name');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -34,7 +34,7 @@ module.exports = (sequelize) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0
|
defaultValue: 0
|
||||||
},
|
},
|
||||||
page_title: { // Neuer Eintrag
|
page_title: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: true
|
allowNull: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ module.exports = (sequelize) => {
|
|||||||
introLine: {
|
introLine: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: true
|
allowNull: true
|
||||||
|
},
|
||||||
|
dayName: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
defaultValue: '',
|
||||||
|
allowNull: false
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
tableName: 'worships',
|
tableName: 'worships',
|
||||||
|
|||||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -28,6 +28,7 @@
|
|||||||
"core-js": "^3.8.3",
|
"core-js": "^3.8.3",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"crypto": "^1.0.1",
|
"crypto": "^1.0.1",
|
||||||
|
"date-fns": "^3.6.0",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
@@ -5708,6 +5709,15 @@
|
|||||||
"node": ">=0.12"
|
"node": ">=0.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debounce": {
|
"node_modules/debounce": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"core-js": "^3.8.3",
|
"core-js": "^3.8.3",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"crypto": "^1.0.1",
|
"crypto": "^1.0.1",
|
||||||
|
"date-fns": "^3.6.0",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { getAllWorships, createWorship, updateWorship, deleteWorship } = require('../controllers/worshipController');
|
const { getAllWorships, createWorship, updateWorship, deleteWorship, getFilteredWorships } = require('../controllers/worshipController');
|
||||||
const authMiddleware = require('../middleware/authMiddleware');
|
const authMiddleware = require('../middleware/authMiddleware');
|
||||||
|
|
||||||
router.get('/', authMiddleware, getAllWorships);
|
router.get('/', getAllWorships);
|
||||||
router.post('/', authMiddleware, createWorship);
|
router.post('/', authMiddleware, createWorship);
|
||||||
router.put('/:id', authMiddleware, updateWorship);
|
router.put('/:id', authMiddleware, updateWorship);
|
||||||
router.delete('/:id', authMiddleware, deleteWorship);
|
router.delete('/:id', authMiddleware, deleteWorship);
|
||||||
|
router.get('/filtered', getFilteredWorships);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,73 +1,74 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1>{{ title }}</h1>
|
<h1>{{ title }}</h1>
|
||||||
<div v-html="renderedContent"></div>
|
<RenderContentComponent :content="content" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from '@/axios';
|
import axios from '@/axios';
|
||||||
import { mapState, mapGetters } from 'vuex';
|
import { mapState, mapGetters } from 'vuex';
|
||||||
import { render } from '@/utils/render';
|
import RenderContentComponent from '@/components/RenderContentComponent.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ContentComponent',
|
name: 'ContentComponent',
|
||||||
|
components: {
|
||||||
|
RenderContentComponent
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
link: {
|
link: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
content: '',
|
content: '',
|
||||||
title: '',
|
title: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['menuData']),
|
...mapState(['menuData']),
|
||||||
...mapGetters(['getMenuData']),
|
...mapGetters(['getMenuData']),
|
||||||
renderedContent() {
|
|
||||||
return render(this.content);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
link: {
|
link: {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler(newLink) {
|
handler(newLink) {
|
||||||
this.fetchContent(newLink);
|
this.fetchContent(newLink);
|
||||||
this.setTitle(newLink);
|
this.setTitle(newLink);
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchContent(link) {
|
async fetchContent(link) {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`/page-content?link=${link}`);
|
const response = await axios.get(`/page-content?link=${link}`);
|
||||||
this.content = response.data.content;
|
this.content = response.data.content;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Abrufen des Inhalts:', error);
|
console.error('Fehler beim Abrufen des Inhalts:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setTitle(link) {
|
||||||
|
const findTitle = (menuItems, link) => {
|
||||||
|
for (const item of menuItems) {
|
||||||
|
if (item.link === link) {
|
||||||
|
return item.pageTitle || item.name;
|
||||||
}
|
}
|
||||||
},
|
if (item.submenu && item.submenu.length > 0) {
|
||||||
setTitle(link) {
|
const found = findTitle(item.submenu, link);
|
||||||
const findTitle = (menuItems, link) => {
|
if (found) {
|
||||||
for (const item of menuItems) {
|
return `${found}`;
|
||||||
if (item.link === link) {
|
}
|
||||||
return item.pageTitle || item.name;
|
}
|
||||||
}
|
}
|
||||||
if (item.submenu && item.submenu.length > 0) {
|
return '';
|
||||||
const found = findTitle(item.submenu, link);
|
};
|
||||||
if (found) {
|
this.title = findTitle(this.menuData, link);
|
||||||
return `${found}`;
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
this.title = findTitle(this.menuData, link);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
||||||
74
src/components/RenderContentComponent.vue
Normal file
74
src/components/RenderContentComponent.vue
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<component :is="dynamicComponent" v-bind="componentProps"></component>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { createApp, h } from 'vue';
|
||||||
|
import WorshipRender from './WorshipRender.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RenderContentComponent',
|
||||||
|
props: {
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dynamicComponent: null,
|
||||||
|
componentProps: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
content: {
|
||||||
|
immediate: true,
|
||||||
|
handler(newContent) {
|
||||||
|
this.renderContent(newContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
renderContent(content) {
|
||||||
|
const worshipsPattern = /{{ worshipslist:(.*?) }}/g;
|
||||||
|
content.replace(worshipsPattern, (match, config) => {
|
||||||
|
const props = this.parseConfig(config);
|
||||||
|
this.dynamicComponent = WorshipRender;
|
||||||
|
this.componentProps = props;
|
||||||
|
return '<div id="worship-render-placeholder"></div>';
|
||||||
|
});
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.dynamicComponent) {
|
||||||
|
const placeholder = document.getElementById('worship-render-placeholder');
|
||||||
|
if (placeholder) {
|
||||||
|
const app = createApp({
|
||||||
|
render() {
|
||||||
|
return h(this.dynamicComponent, this.componentProps);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
app.mount(placeholder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
parseConfig(configString) {
|
||||||
|
const config = {};
|
||||||
|
const configArray = configString.split(',');
|
||||||
|
configArray.forEach(item => {
|
||||||
|
const [key, value] = item.split('=');
|
||||||
|
if (key && value !== undefined) {
|
||||||
|
config[key.trim()] = isNaN(value) ? value.trim() : Number(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Add styles if needed */
|
||||||
|
</style>
|
||||||
|
|
||||||
94
src/components/WorshipRender.vue
Normal file
94
src/components/WorshipRender.vue
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<table v-if="worships.length" class="worships">
|
||||||
|
<tr v-for="worship in worships" :key="worship.id" :style="worship.eventPlace && worship.eventPlace.backgroundColor ? `background-color:${worship.eventPlace.backgroundColor}` : ''">
|
||||||
|
<td>
|
||||||
|
<div>{{ formatDate(worship.date) }}</div>
|
||||||
|
<div>{{ worship.dayName }}</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div v-if="worship.neighborInvitation" class="neighborhood-invitation">Einladung zum Gottesdienst im Nachbarschaftsraum:</div>
|
||||||
|
<h3>
|
||||||
|
<span :class="worship.highlightTime ? 'highlight-time' : ''">{{ formatTime(worship.time) }}</span> -
|
||||||
|
{{ !worship.neighborInvitation ? worship.title : '' }}
|
||||||
|
</h3>
|
||||||
|
<div v-if="worship.organizer">Gestaltung: {{ worship.organizer }}</div>
|
||||||
|
<div v-if="worship.collection">Kollekte: {{ worship.collection }}</div>
|
||||||
|
<div v-if="worship.eventPlace.id && worship.eventPlace.id">
|
||||||
|
Adresse: {{ worship.eventPlace.name }}, {{ worship.eventPlace.street }}, {{ worship.eventPlace.city }}
|
||||||
|
</div>
|
||||||
|
<div v-if="worship.selfInformation" class="selfinformation">Bitte informieren Sie sich auch auf den Internetseiten dieser Gemeinde!</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p v-else>Keine Gottesdienste verfügbar.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from '@/axios';
|
||||||
|
import { formatTime, formatDate } from '@/utils/strings';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WorshipRender',
|
||||||
|
props: {
|
||||||
|
location: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
type: String,
|
||||||
|
default: 'date ASC'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
worships: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
await this.fetchWorships();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formatTime,
|
||||||
|
formatDate,
|
||||||
|
async fetchWorships() {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/worships/filtered', {
|
||||||
|
params: {
|
||||||
|
location: this.location,
|
||||||
|
orderBy: this.orderBy
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.worships = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Abrufen der Gottesdienste:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
table.worships {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
table.worships td {
|
||||||
|
border: 1px solid #000;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
table.worships td div{
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
.highlight-time {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.neighborhood-invitation {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #0020e0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="admin-menu-management">
|
|
||||||
<h2>Menüverwaltung</h2>
|
|
||||||
<div>
|
|
||||||
<h3>Neuen Menüpunkt hinzufügen</h3>
|
|
||||||
<form @submit.prevent="addMenuItem">
|
|
||||||
<label for="name">Name:</label>
|
|
||||||
<input type="text" v-model="newItem.name" required />
|
|
||||||
|
|
||||||
<label for="link">Link:</label>
|
|
||||||
<input type="text" v-model="newItem.link" required />
|
|
||||||
|
|
||||||
<label for="component">Component:</label>
|
|
||||||
<input type="text" v-model="newItem.component" required />
|
|
||||||
|
|
||||||
<label for="showInMenu">Im Menü anzeigen:</label>
|
|
||||||
<input type="checkbox" v-model="newItem.showInMenu" />
|
|
||||||
|
|
||||||
<label for="requiresAuth">Authentifizierung erforderlich:</label>
|
|
||||||
<input type="checkbox" v-model="newItem.requiresAuth" />
|
|
||||||
|
|
||||||
<label for="image">Bild:</label>
|
|
||||||
<input type="text" v-model="newItem.image" />
|
|
||||||
|
|
||||||
<label for="parentId">Eltern-Menüpunkt:</label>
|
|
||||||
<select v-model="newItem.parentId">
|
|
||||||
<option :value="null">Kein Eltern-Menüpunkt</option>
|
|
||||||
<option v-for="item in menuItems" :key="item.id" :value="item.id">
|
|
||||||
{{ item.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<button type="submit">Hinzufügen</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3>Menüpunkte bearbeiten</h3>
|
|
||||||
<ul>
|
|
||||||
<li v-for="item in menuItems" :key="item.id">
|
|
||||||
{{ item.name }}
|
|
||||||
<button @click="editMenuItem(item)">Bearbeiten</button>
|
|
||||||
<button @click="deleteMenuItem(item.id)">Löschen</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="editingItem">
|
|
||||||
<h3>Menüpunkt bearbeiten</h3>
|
|
||||||
<form @submit.prevent="updateMenuItem">
|
|
||||||
<label for="editName">Name:</label>
|
|
||||||
<input type="text" v-model="editingItem.name" required />
|
|
||||||
|
|
||||||
<label for="editLink">Link:</label>
|
|
||||||
<input type="text" v-model="editingItem.link" required />
|
|
||||||
|
|
||||||
<label for="editComponent">Component:</label>
|
|
||||||
<input type="text" v-model="editingItem.component" required />
|
|
||||||
|
|
||||||
<label for="editShowInMenu">Im Menü anzeigen:</label>
|
|
||||||
<input type="checkbox" v-model="editingItem.showInMenu" />
|
|
||||||
|
|
||||||
<label for="editRequiresAuth">Authentifizierung erforderlich:</label>
|
|
||||||
<input type="checkbox" v-model="editingItem.requiresAuth" />
|
|
||||||
|
|
||||||
<label for="editImage">Bild:</label>
|
|
||||||
<input type="text" v-model="editingItem.image" />
|
|
||||||
|
|
||||||
<label for="editParentId">Eltern-Menüpunkt:</label>
|
|
||||||
<select v-model="editingItem.parentId">
|
|
||||||
<option :value="null">Kein Eltern-Menüpunkt</option>
|
|
||||||
<option v-for="item in menuItems" :key="item.id" :value="item.id">
|
|
||||||
{{ item.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<button type="submit">Aktualisieren</button>
|
|
||||||
<button @click="cancelEdit">Abbrechen</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import { mapState, mapActions } from 'vuex';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'AdminMenuManagement',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
newItem: {
|
|
||||||
name: '',
|
|
||||||
link: '',
|
|
||||||
component: '',
|
|
||||||
showInMenu: false,
|
|
||||||
requiresAuth: false,
|
|
||||||
image: '',
|
|
||||||
parentId: null
|
|
||||||
},
|
|
||||||
editingItem: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(['menuData']),
|
|
||||||
menuItems() {
|
|
||||||
return this.menuData;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions(['loadMenuData']),
|
|
||||||
async addMenuItem() {
|
|
||||||
this.menuData.push(this.newItem);
|
|
||||||
await this.saveMenuData();
|
|
||||||
this.resetNewItem();
|
|
||||||
},
|
|
||||||
editMenuItem(item) {
|
|
||||||
this.editingItem = { ...item };
|
|
||||||
},
|
|
||||||
async updateMenuItem() {
|
|
||||||
const index = this.menuData.findIndex(item => item.id === this.editingItem.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
this.menuData.splice(index, 1, this.editingItem);
|
|
||||||
await this.saveMenuData();
|
|
||||||
this.cancelEdit();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async deleteMenuItem(id) {
|
|
||||||
this.menuData = this.menuData.filter(item => item.id !== id);
|
|
||||||
await this.saveMenuData();
|
|
||||||
},
|
|
||||||
cancelEdit() {
|
|
||||||
this.editingItem = null;
|
|
||||||
},
|
|
||||||
resetNewItem() {
|
|
||||||
this.newItem = {
|
|
||||||
name: '',
|
|
||||||
link: '',
|
|
||||||
component: '',
|
|
||||||
showInMenu: false,
|
|
||||||
requiresAuth: false,
|
|
||||||
image: '',
|
|
||||||
parentId: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async saveMenuData() {
|
|
||||||
try {
|
|
||||||
await fetch('/menu-data', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(this.menuData)
|
|
||||||
});
|
|
||||||
await this.loadMenuData();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Fehler beim Speichern der Menü-Daten:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.loadMenuData();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style scoped>
|
|
||||||
.admin-menu-management {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form label {
|
|
||||||
display: block;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form input,
|
|
||||||
form select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 5px;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin-top: 10px;
|
|
||||||
padding: 5px 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin: 10px 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
li button {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted, watch } from 'vue';
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
import axios from '../../axios';
|
import axios from '../../axios';
|
||||||
import { EditorContent, useEditor } from '@tiptap/vue-3';
|
import { EditorContent, useEditor } from '@tiptap/vue-3';
|
||||||
@@ -97,7 +97,7 @@ export default {
|
|||||||
],
|
],
|
||||||
content: '',
|
content: '',
|
||||||
onUpdate: ({ editor }) => {
|
onUpdate: ({ editor }) => {
|
||||||
store.commit('UPDATE_PAGE_CONTENT', editor.getHTML());
|
store.commit('SET_PAGE_CONTENT', editor.getHTML());
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,6 +118,7 @@ export default {
|
|||||||
const allPages = [];
|
const allPages = [];
|
||||||
flattenPages(data, allPages);
|
flattenPages(data, allPages);
|
||||||
pages.value = allPages.sort((a, b) => a.name.localeCompare(b.name));
|
pages.value = allPages.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
store.commit('setMenuData', data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Abrufen der Seiten:', error);
|
console.error('Fehler beim Abrufen der Seiten:', error);
|
||||||
}
|
}
|
||||||
@@ -127,7 +128,6 @@ export default {
|
|||||||
try {
|
try {
|
||||||
await store.dispatch('loadPageContent', selectedPage.value);
|
await store.dispatch('loadPageContent', selectedPage.value);
|
||||||
const content = store.getters.pageContent;
|
const content = store.getters.pageContent;
|
||||||
|
|
||||||
const setEditorContent = () => {
|
const setEditorContent = () => {
|
||||||
if (editor.value && editor.value.commands) {
|
if (editor.value && editor.value.commands) {
|
||||||
editor.value.commands.setContent(content, false);
|
editor.value.commands.setContent(content, false);
|
||||||
@@ -135,7 +135,6 @@ export default {
|
|||||||
setTimeout(setEditorContent, 100);
|
setTimeout(setEditorContent, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setEditorContent();
|
setEditorContent();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Laden des Seiteninhalts:', error);
|
console.error('Fehler beim Laden des Seiteninhalts:', error);
|
||||||
@@ -167,6 +166,15 @@ export default {
|
|||||||
return pages.value;
|
return pages.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(selectedPage, (newPage) => {
|
||||||
|
store.dispatch('setSelectedPage', newPage);
|
||||||
|
const page = pages.value.find(page => page.link === newPage);
|
||||||
|
if (page) {
|
||||||
|
store.dispatch('setPageTitle', page.name);
|
||||||
|
}
|
||||||
|
loadPageContent();
|
||||||
|
});
|
||||||
|
|
||||||
const openWorshipDialog = () => {
|
const openWorshipDialog = () => {
|
||||||
worshipDialog.value.openWorshipDialog();
|
worshipDialog.value.openWorshipDialog();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,272 +1,280 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="menu-management">
|
<div class="menu-management">
|
||||||
<h1>Menüverwaltung</h1>
|
<h1>Menüverwaltung</h1>
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
<button @click="addMenuItem">Hauptmenü hinzufügen</button>
|
<button @click="addMenuItem">Hauptmenü hinzufügen</button>
|
||||||
<button @click="saveMenuData">Speichern</button>
|
<button @click="saveMenuData">Speichern</button>
|
||||||
</div>
|
|
||||||
<div v-if="selectedMenuItem" class="edit-form">
|
|
||||||
<h2>Menüpunkt bearbeiten</h2>
|
|
||||||
<form @submit.prevent="saveMenuData">
|
|
||||||
<label for="name">Name</label>
|
|
||||||
<input id="name" v-model="selectedMenuItem.name" placeholder="Name" />
|
|
||||||
|
|
||||||
<label for="link">Link</label>
|
|
||||||
<input id="link" v-model="selectedMenuItem.link" placeholder="Link" />
|
|
||||||
|
|
||||||
<label for="order-id">Order ID</label>
|
|
||||||
<input id="order-id" v-model.number="selectedMenuItem.orderId" placeholder="Order ID" type="number" class="order-id" />
|
|
||||||
|
|
||||||
<div class="checkbox-container">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" v-model="selectedMenuItem.showInMenu" />
|
|
||||||
Im Menü anzeigen
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" v-model="selectedMenuItem.requiresAuth" />
|
|
||||||
Authentifizierung erforderlich
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label for="parent-id">Elternelement</label>
|
|
||||||
<select id="parent-id" v-model="selectedMenuItem.parent_id">
|
|
||||||
<option value="">Ohne Elternelement</option>
|
|
||||||
<option v-for="item in flattenedMenuData" :key="item.id" :value="item.id">
|
|
||||||
<span v-html="getIndentedName(item)"></span>
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="tree-view">
|
|
||||||
<ul>
|
|
||||||
<li v-for="menuItem in sortedMenuData" :key="menuItem.id">
|
|
||||||
<div class="menu-item">
|
|
||||||
<span @click="selectMenuItem(menuItem)">
|
|
||||||
{{ menuItem.name }} (ID: {{ menuItem.orderId }})
|
|
||||||
</span>
|
|
||||||
<div class="action-buttons">
|
|
||||||
<button @click="addSubmenu(menuItem)" class="action-button">Untermenü hinzufügen</button>
|
|
||||||
<button @click="removeMenuItem(menuItem)" class="action-button">Löschen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul v-if="menuItem.submenu.length">
|
|
||||||
<li v-for="submenuItem in sortedSubmenu(menuItem)" :key="submenuItem.id">
|
|
||||||
<div class="menu-item">
|
|
||||||
<span @click="selectMenuItem(submenuItem)">
|
|
||||||
{{ submenuItem.name }} (ID: {{ submenuItem.orderId }})
|
|
||||||
</span>
|
|
||||||
<div class="action-buttons">
|
|
||||||
<button @click="addSubmenu(menuItem)" class="action-button">Untermenü hinzufügen</button>
|
|
||||||
<button @click="removeSubmenu(menuItem, submenuItem)" class="action-button">Löschen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<div v-if="selectedMenuItem" class="edit-form">
|
||||||
|
<h2>Menüpunkt bearbeiten</h2>
|
||||||
<script>
|
<form @submit.prevent="saveMenuData">
|
||||||
import { ref, onMounted, computed } from 'vue';
|
<label for="name">Name</label>
|
||||||
import axios from '../../axios';
|
<input id="name" v-model="selectedMenuItem.name" placeholder="Name" />
|
||||||
|
|
||||||
export default {
|
<label for="link">Link</label>
|
||||||
name: 'MenuManagement',
|
<input id="link" v-model="selectedMenuItem.link" placeholder="Link" />
|
||||||
setup() {
|
|
||||||
const menuData = ref([]);
|
<label for="page-title">Seitenname</label>
|
||||||
const selectedMenuItem = ref(null);
|
<input id="page-title" v-model="selectedMenuItem.pageTitle" placeholder="Seitenname" />
|
||||||
|
|
||||||
const fetchMenuData = async () => {
|
<label for="order-id">Order ID</label>
|
||||||
try {
|
<input id="order-id" v-model.number="selectedMenuItem.orderId" placeholder="Order ID" type="number"
|
||||||
const response = await axios.get('/menu-data');
|
class="order-id" />
|
||||||
menuData.value = response.data;
|
|
||||||
} catch (error) {
|
<div class="checkbox-container">
|
||||||
console.error('Fehler beim Abrufen der Menü-Daten:', error);
|
<label>
|
||||||
}
|
<input type="checkbox" v-model="selectedMenuItem.showInMenu" />
|
||||||
};
|
Im Menü anzeigen
|
||||||
|
</label>
|
||||||
const saveMenuData = async () => {
|
<label>
|
||||||
try {
|
<input type="checkbox" v-model="selectedMenuItem.requiresAuth" />
|
||||||
const flatMenuData = flattenMenuData(menuData.value);
|
Authentifizierung erforderlich
|
||||||
await axios.post('/menu-data', flatMenuData);
|
</label>
|
||||||
alert('Menü-Daten erfolgreich gespeichert');
|
</div>
|
||||||
} catch (error) {
|
|
||||||
console.error('Fehler beim Speichern der Menü-Daten:', error);
|
<label for="parent-id">Elternelement</label>
|
||||||
}
|
<select id="parent-id" v-model="selectedMenuItem.parent_id">
|
||||||
};
|
<option value="">Ohne Elternelement</option>
|
||||||
|
<option v-for="item in flattenedMenuData" :key="item.id" :value="item.id">
|
||||||
const flattenMenuData = (data, parentId = null) => {
|
<span v-html="getIndentedName(item)"></span>
|
||||||
return data.reduce((acc, item) => {
|
</option>
|
||||||
const newItem = { ...item, parent_id: parentId };
|
</select>
|
||||||
const { submenu, ...rest } = newItem;
|
|
||||||
acc.push(rest);
|
<label for="component">Vue-Komponente</label>
|
||||||
if (submenu && submenu.length) {
|
<input id="component" v-model="selectedMenuItem.component" placeholder="Vue-Komponente" />
|
||||||
acc.push(...flattenMenuData(submenu, newItem.id));
|
</form>
|
||||||
}
|
</div>
|
||||||
return acc;
|
<div class="tree-view">
|
||||||
}, []);
|
<ul>
|
||||||
};
|
<li v-for="menuItem in sortedMenuData" :key="menuItem.id">
|
||||||
|
<div class="menu-item">
|
||||||
const addMenuItem = () => {
|
<span @click="selectMenuItem(menuItem)">
|
||||||
const newItem = {
|
{{ menuItem.name }} (ID: {{ menuItem.orderId }})
|
||||||
name: '',
|
</span>
|
||||||
link: '',
|
<div class="action-buttons">
|
||||||
component: '',
|
<button @click="addSubmenu(menuItem)" class="action-button">Untermenü hinzufügen</button>
|
||||||
showInMenu: true,
|
<button @click="removeMenuItem(menuItem)" class="action-button">Löschen</button>
|
||||||
requiresAuth: false,
|
</div>
|
||||||
orderId: 0,
|
</div>
|
||||||
submenu: [],
|
<ul v-if="menuItem.submenu.length">
|
||||||
parent_id: null,
|
<li v-for="submenuItem in sortedSubmenu(menuItem)" :key="submenuItem.id">
|
||||||
};
|
<div class="menu-item">
|
||||||
menuData.value.push(newItem);
|
<span @click="selectMenuItem(submenuItem)">
|
||||||
selectMenuItem(newItem);
|
{{ submenuItem.name }} (ID: {{ submenuItem.orderId }})
|
||||||
};
|
</span>
|
||||||
|
<div class="action-buttons">
|
||||||
const addSubmenu = (menuItem) => {
|
<button @click="addSubmenu(menuItem)" class="action-button">Untermenü hinzufügen</button>
|
||||||
const newSubItem = {
|
<button @click="removeSubmenu(menuItem, submenuItem)" class="action-button">Löschen</button>
|
||||||
name: '',
|
</div>
|
||||||
link: '',
|
</div>
|
||||||
component: '',
|
</li>
|
||||||
showInMenu: true,
|
</ul>
|
||||||
requiresAuth: false,
|
</li>
|
||||||
orderId: 0,
|
</ul>
|
||||||
parent_id: menuItem.id,
|
</div>
|
||||||
};
|
</div>
|
||||||
menuItem.submenu.push(newSubItem);
|
</template>
|
||||||
selectMenuItem(newSubItem);
|
|
||||||
};
|
<script>
|
||||||
|
import { ref, onMounted, computed } from 'vue';
|
||||||
const removeMenuItem = (menuItem) => {
|
import axios from '../../axios';
|
||||||
const index = menuData.value.indexOf(menuItem);
|
|
||||||
if (index > -1) {
|
export default {
|
||||||
menuData.value.splice(index, 1);
|
name: 'MenuManagement',
|
||||||
}
|
setup() {
|
||||||
selectedMenuItem.value = null;
|
const menuData = ref([]);
|
||||||
};
|
const selectedMenuItem = ref(null);
|
||||||
|
|
||||||
const removeSubmenu = (menuItem, submenuItem) => {
|
const fetchMenuData = async () => {
|
||||||
const index = menuItem.submenu.indexOf(submenuItem);
|
try {
|
||||||
if (index > -1) {
|
const response = await axios.get('/menu-data');
|
||||||
menuItem.submenu.splice(index, 1);
|
menuData.value = response.data;
|
||||||
}
|
} catch (error) {
|
||||||
selectedMenuItem.value = null;
|
console.error('Fehler beim Abrufen der Menü-Daten:', error);
|
||||||
};
|
}
|
||||||
|
|
||||||
const selectMenuItem = (menuItem) => {
|
|
||||||
selectedMenuItem.value = menuItem;
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortedMenuData = computed(() => {
|
|
||||||
return [...menuData.value].sort((a, b) => a.orderId - b.orderId);
|
|
||||||
});
|
|
||||||
|
|
||||||
const sortedSubmenu = (menuItem) => {
|
|
||||||
return menuItem.submenu.slice().sort((a, b) => a.orderId - b.orderId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getIndentedName = (item) => {
|
|
||||||
return ' '.repeat(item.indent * 2) + item.name;
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(fetchMenuData);
|
|
||||||
|
|
||||||
return {
|
|
||||||
menuData,
|
|
||||||
sortedMenuData,
|
|
||||||
sortedSubmenu,
|
|
||||||
selectedMenuItem,
|
|
||||||
fetchMenuData,
|
|
||||||
saveMenuData,
|
|
||||||
addMenuItem,
|
|
||||||
addSubmenu,
|
|
||||||
removeMenuItem,
|
|
||||||
removeSubmenu,
|
|
||||||
selectMenuItem,
|
|
||||||
getIndentedName,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
|
||||||
|
const saveMenuData = async () => {
|
||||||
<style scoped>
|
try {
|
||||||
.menu-management {
|
const flatMenuData = flattenMenuData(menuData.value);
|
||||||
width: 100%;
|
await axios.post('/menu-data', flatMenuData);
|
||||||
margin: auto;
|
alert('Menü-Daten erfolgreich gespeichert');
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Speichern der Menü-Daten:', error);
|
||||||
.button-container {
|
}
|
||||||
display: inline-flex;
|
};
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 20px;
|
const flattenMenuData = (data, parentId = null) => {
|
||||||
}
|
return data.reduce((acc, item) => {
|
||||||
|
const newItem = { ...item, parent_id: parentId, page_title: item.pageTitle };
|
||||||
.tree-view {
|
const { submenu, ...rest } = newItem;
|
||||||
margin-top: 20px;
|
acc.push(rest);
|
||||||
}
|
if (submenu && submenu.length) {
|
||||||
|
acc.push(...flattenMenuData(submenu, newItem.id));
|
||||||
.tree-view ul {
|
}
|
||||||
list-style-type: none;
|
return acc;
|
||||||
padding: 0;
|
}, []);
|
||||||
}
|
};
|
||||||
|
|
||||||
.tree-view li {
|
const addMenuItem = () => {
|
||||||
margin-bottom: 5px;
|
const newItem = {
|
||||||
padding-left: 20px;
|
name: '',
|
||||||
}
|
link: '',
|
||||||
|
component: '',
|
||||||
.tree-view .menu-item {
|
pageTitle: '',
|
||||||
display: inline-flex;
|
showInMenu: true,
|
||||||
width: 100%;
|
requiresAuth: false,
|
||||||
justify-content: space-between;
|
orderId: 0,
|
||||||
align-items: center;
|
submenu: [],
|
||||||
}
|
parent_id: null,
|
||||||
|
};
|
||||||
.tree-view span {
|
menuData.value.push(newItem);
|
||||||
cursor: pointer;
|
selectMenuItem(newItem);
|
||||||
color: black;
|
};
|
||||||
}
|
|
||||||
|
const addSubmenu = (menuItem) => {
|
||||||
.tree-view button {
|
const newSubItem = {
|
||||||
border: none;
|
name: '',
|
||||||
height: 1.6em;
|
link: '',
|
||||||
padding: 0 0.5em;
|
component: '',
|
||||||
margin: 1px;
|
pageTitle: '',
|
||||||
border-radius: 5px;
|
showInMenu: true,
|
||||||
}
|
requiresAuth: false,
|
||||||
|
orderId: 0,
|
||||||
.tree-view span:hover {
|
parent_id: menuItem.id,
|
||||||
text-decoration: underline;
|
};
|
||||||
}
|
menuItem.submenu.push(newSubItem);
|
||||||
|
selectMenuItem(newSubItem);
|
||||||
.edit-form {
|
};
|
||||||
margin-top: 20px;
|
|
||||||
}
|
const removeMenuItem = (menuItem) => {
|
||||||
|
const index = menuData.value.indexOf(menuItem);
|
||||||
.edit-form label {
|
if (index > -1) {
|
||||||
display: block;
|
menuData.value.splice(index, 1);
|
||||||
margin-bottom: 5px;
|
}
|
||||||
font-weight: bold;
|
selectedMenuItem.value = null;
|
||||||
}
|
};
|
||||||
|
|
||||||
.edit-form input:not([type="checkbox"]) {
|
const removeSubmenu = (menuItem, submenuItem) => {
|
||||||
display: block;
|
const index = menuItem.submenu.indexOf(submenuItem);
|
||||||
margin-bottom: 10px;
|
if (index > -1) {
|
||||||
}
|
menuItem.submenu.splice(index, 1);
|
||||||
|
}
|
||||||
.edit-form .checkbox-container {
|
selectedMenuItem.value = null;
|
||||||
display: flex;
|
};
|
||||||
flex-direction: column;
|
|
||||||
margin-right: 10px;
|
const selectMenuItem = (menuItem) => {
|
||||||
}
|
selectedMenuItem.value = menuItem;
|
||||||
|
};
|
||||||
.edit-form .order-id {
|
|
||||||
width: 50px;
|
const sortedMenuData = computed(() => {
|
||||||
}
|
return [...menuData.value].sort((a, b) => a.orderId - b.orderId);
|
||||||
|
});
|
||||||
.edit-form button {
|
|
||||||
margin-top: 5px;
|
const sortedSubmenu = (menuItem) => {
|
||||||
}
|
return menuItem.submenu.slice().sort((a, b) => a.orderId - b.orderId);
|
||||||
</style>
|
};
|
||||||
|
|
||||||
|
const getIndentedName = (item) => {
|
||||||
|
return ' '.repeat(item.indent * 2) + item.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(fetchMenuData);
|
||||||
|
|
||||||
|
return {
|
||||||
|
menuData,
|
||||||
|
sortedMenuData,
|
||||||
|
sortedSubmenu,
|
||||||
|
selectedMenuItem,
|
||||||
|
fetchMenuData,
|
||||||
|
saveMenuData,
|
||||||
|
addMenuItem,
|
||||||
|
addSubmenu,
|
||||||
|
removeMenuItem,
|
||||||
|
removeSubmenu,
|
||||||
|
selectMenuItem,
|
||||||
|
getIndentedName,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.menu-management {
|
||||||
|
width: 100%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view li {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view .menu-item {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view span {
|
||||||
|
cursor: pointer;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view button {
|
||||||
|
border: none;
|
||||||
|
height: 1.6em;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
margin: 1px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view span:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form input:not([type="checkbox"]) {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form .checkbox-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form .order-id {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form button {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,26 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="previewinfo">Dies ist eine Vorschau.</div>
|
<div class="previewinfo">Dies ist eine Vorschau.</div>
|
||||||
<div v-html="renderedContent"></div>
|
<h1>{{ title }}</h1>
|
||||||
|
<RenderContentComponent :content="content" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue';
|
import { computed, watchEffect } from 'vue';
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
import { render } from '@/utils/render';
|
import RenderContentComponent from '@/components/RenderContentComponent.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PagePreview',
|
name: 'PagePreview',
|
||||||
|
components: {
|
||||||
|
RenderContentComponent
|
||||||
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const content = computed(() => store.state.pageContent);
|
const content = computed(() => store.state.pageContent);
|
||||||
const renderedContent = computed(() => render(content.value));
|
const selectedPage = computed(() => store.state.selectedPage);
|
||||||
|
const menuData = computed(() => store.state.menuData);
|
||||||
|
const title = computed(() => store.state.pageTitle);
|
||||||
|
|
||||||
|
const setTitle = (link) => {
|
||||||
|
const findTitle = (menuItems, link) => {
|
||||||
|
for (const item of menuItems) {
|
||||||
|
if (item.link === link) {
|
||||||
|
return item.pageTitle || item.name;
|
||||||
|
}
|
||||||
|
if (item.submenu && item.submenu.length > 0) {
|
||||||
|
const found = findTitle(item.submenu, link);
|
||||||
|
if (found) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
store.dispatch('setPageTitle', findTitle(menuData.value, link));
|
||||||
|
};
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
setTitle(selectedPage.value);
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
renderedContent,
|
content,
|
||||||
|
title
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
<label for="date">Datum:</label>
|
<label for="date">Datum:</label>
|
||||||
<input type="date" id="date" v-model="worshipData.date" required>
|
<input type="date" id="date" v-model="worshipData.date" required>
|
||||||
|
|
||||||
|
<label for="dayName">Name des Tags:</label>
|
||||||
|
<input type="text" id="dayName" v-model="worshipData.dayName" required>
|
||||||
|
|
||||||
<label for="time">Uhrzeit:</label>
|
<label for="time">Uhrzeit:</label>
|
||||||
<input type="time" id="time" v-model="worshipData.time" required>
|
<input type="time" id="time" v-model="worshipData.time" required>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createStore } from 'vuex';
|
import { createStore } from 'vuex';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import router from '../router'; // Importieren des Routers
|
import router from '../router';
|
||||||
|
|
||||||
let user = [];
|
let user = [];
|
||||||
try {
|
try {
|
||||||
@@ -16,6 +16,8 @@ export default createStore({
|
|||||||
token: localStorage.getItem('token') || '',
|
token: localStorage.getItem('token') || '',
|
||||||
menuData: [],
|
menuData: [],
|
||||||
pageContent: '',
|
pageContent: '',
|
||||||
|
pageTitle: '',
|
||||||
|
selectedPage: '',
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setLogin(state, { user, token }) {
|
setLogin(state, { user, token }) {
|
||||||
@@ -44,6 +46,12 @@ export default createStore({
|
|||||||
UPDATE_PAGE_CONTENT(state, content) {
|
UPDATE_PAGE_CONTENT(state, content) {
|
||||||
state.pageContent = content;
|
state.pageContent = content;
|
||||||
},
|
},
|
||||||
|
setPageTitle(state, title) {
|
||||||
|
state.pageTitle = title;
|
||||||
|
},
|
||||||
|
setSelectedPage(state, page) {
|
||||||
|
state.selectedPage = page;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async loadMenuData({ commit }) {
|
async loadMenuData({ commit }) {
|
||||||
@@ -77,6 +85,12 @@ export default createStore({
|
|||||||
console.error('Fehler beim Speichern des Seiteninhalts:', error);
|
console.error('Fehler beim Speichern des Seiteninhalts:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setPageTitle({ commit }, title) {
|
||||||
|
commit('setPageTitle', title);
|
||||||
|
},
|
||||||
|
setSelectedPage({ commit }, page) {
|
||||||
|
commit('setSelectedPage', page);
|
||||||
|
},
|
||||||
login({ commit }, { user, token }) {
|
login({ commit }, { user, token }) {
|
||||||
commit('setLogin', { user, token });
|
commit('setLogin', { user, token });
|
||||||
},
|
},
|
||||||
@@ -89,5 +103,7 @@ export default createStore({
|
|||||||
user: state => state.user,
|
user: state => state.user,
|
||||||
menuData: state => state.menuData,
|
menuData: state => state.menuData,
|
||||||
pageContent: state => state.pageContent,
|
pageContent: state => state.pageContent,
|
||||||
|
pageTitle: state => state.pageTitle,
|
||||||
|
selectedPage: state => state.selectedPage,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
export function render(content) {
|
|
||||||
console.log('do render', content);
|
|
||||||
const worshipsPattern = /{{ worshipslist:(.*?) }}/g;
|
|
||||||
const renderedContent = content.replace(worshipsPattern, (match, config) => {
|
|
||||||
return renderWorships(config);
|
|
||||||
});
|
|
||||||
|
|
||||||
return renderedContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderWorships(config) {
|
|
||||||
console.log('render worships', config);
|
|
||||||
return `<div class="worships-list">Worships at location ${location}</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user