This commit ensures that the Satzung content is loaded as a string, enhancing reliability. Additionally, it refines the HTML conversion function by improving the handling of line breaks, merging related lines, and removing empty paragraphs. These changes enhance the overall quality and readability of the generated HTML content.
297 lines
8.6 KiB
Vue
297 lines
8.6 KiB
Vue
<template>
|
|
<div class="min-h-full py-16 bg-gray-50">
|
|
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h1 class="text-3xl sm:text-4xl font-display font-bold text-gray-900">
|
|
Satzung verwalten
|
|
</h1>
|
|
</div>
|
|
|
|
<!-- 1. PDF-Upload -->
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
|
|
<h2 class="text-xl font-semibold mb-4">
|
|
PDF-Upload
|
|
</h2>
|
|
|
|
<form
|
|
enctype="multipart/form-data"
|
|
class="space-y-4"
|
|
@submit.prevent="uploadPdf"
|
|
>
|
|
<div>
|
|
<label
|
|
for="pdf-file"
|
|
class="block text-sm font-medium text-gray-700 mb-2"
|
|
>
|
|
Neue Satzung hochladen (PDF)
|
|
</label>
|
|
<input
|
|
id="pdf-file"
|
|
ref="fileInput"
|
|
type="file"
|
|
accept=".pdf"
|
|
class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-primary-50 file:text-primary-700 hover:file:bg-primary-100"
|
|
@change="handleFileSelect"
|
|
>
|
|
<p class="mt-1 text-sm text-gray-500">
|
|
Nur PDF-Dateien bis 10MB sind erlaubt
|
|
</p>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
:disabled="!selectedFile || uploading"
|
|
class="inline-flex items-center px-4 py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
<svg
|
|
v-if="uploading"
|
|
class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<circle
|
|
class="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
stroke-width="4"
|
|
/>
|
|
<path
|
|
class="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
{{ uploading ? 'Wird hochgeladen...' : 'PDF hochladen' }}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- 2. Aktuelle PDF-Information -->
|
|
<div
|
|
v-if="currentPdfUrl"
|
|
class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6"
|
|
>
|
|
<h2 class="text-xl font-semibold mb-4">
|
|
Aktuelle Satzung (PDF)
|
|
</h2>
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
|
<div>
|
|
<p class="text-gray-600">
|
|
PDF-Datei verfügbar
|
|
</p>
|
|
<a
|
|
:href="currentPdfUrl"
|
|
target="_blank"
|
|
class="text-primary-600 hover:text-primary-700 font-medium"
|
|
>
|
|
Satzung anzeigen →
|
|
</a>
|
|
</div>
|
|
<div class="text-sm text-gray-500">
|
|
Zuletzt aktualisiert: {{ lastUpdated }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 3. Textfassung (WYSIWYG) -->
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h2 class="text-xl font-semibold">
|
|
Textfassung für die Website
|
|
</h2>
|
|
<button
|
|
type="button"
|
|
class="inline-flex items-center px-4 py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed text-sm"
|
|
:disabled="savingText"
|
|
@click="saveText"
|
|
>
|
|
<svg
|
|
v-if="savingText"
|
|
class="animate-spin -ml-1 mr-2 h-4 w-4 text-white"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<circle
|
|
class="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
stroke-width="4"
|
|
/>
|
|
<path
|
|
class="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
{{ savingText ? 'Speichert...' : 'Text speichern' }}
|
|
</button>
|
|
</div>
|
|
<p class="text-sm text-gray-500 mb-4">
|
|
Diese HTML-Fassung wird auf der Seite „Verein → Satzung“ angezeigt. Die PDF-Version bleibt die rechtlich verbindliche Fassung.
|
|
</p>
|
|
|
|
<RichTextEditor
|
|
v-model="satzungContent"
|
|
label="Satzung (HTML-Version)"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
v-if="message"
|
|
class="mt-4 p-4 rounded-lg"
|
|
:class="messageType === 'success' ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'"
|
|
>
|
|
{{ message }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from 'vue'
|
|
import RichTextEditor from '~/components/RichTextEditor.vue'
|
|
|
|
definePageMeta({
|
|
middleware: 'auth',
|
|
})
|
|
|
|
useHead({ title: 'CMS: Satzung' })
|
|
|
|
const fileInput = ref(null)
|
|
const selectedFile = ref(null)
|
|
const uploading = ref(false)
|
|
const currentPdfUrl = ref('')
|
|
const lastUpdated = ref('')
|
|
const message = ref('')
|
|
const messageType = ref('')
|
|
const satzungContent = ref('')
|
|
const savingText = ref(false)
|
|
|
|
async function loadCurrentSatzung() {
|
|
try {
|
|
const data = await $fetch('/api/config')
|
|
const satzung = data?.seiten?.satzung
|
|
if (satzung?.pdfUrl) {
|
|
currentPdfUrl.value = satzung.pdfUrl
|
|
// Einfache Zeitstempel-Simulation
|
|
lastUpdated.value = new Date().toLocaleDateString('de-DE')
|
|
}
|
|
if (satzung?.content) {
|
|
// Stelle sicher, dass der Inhalt als String geladen wird
|
|
const content = typeof satzung.content === 'string' ? satzung.content : String(satzung.content || '')
|
|
satzungContent.value = content
|
|
} else {
|
|
satzungContent.value = ''
|
|
}
|
|
} catch (e) {
|
|
console.error('Fehler beim Laden der aktuellen Satzung:', e)
|
|
satzungContent.value = ''
|
|
}
|
|
}
|
|
|
|
function handleFileSelect(event) {
|
|
const file = event.target.files[0]
|
|
if (file) {
|
|
if (file.type !== 'application/pdf') {
|
|
message.value = 'Bitte wählen Sie eine PDF-Datei aus'
|
|
messageType.value = 'error'
|
|
return
|
|
}
|
|
if (file.size > 10 * 1024 * 1024) {
|
|
message.value = 'Die Datei ist zu groß (max. 10MB)'
|
|
messageType.value = 'error'
|
|
return
|
|
}
|
|
selectedFile.value = file
|
|
message.value = ''
|
|
}
|
|
}
|
|
|
|
async function uploadPdf() {
|
|
if (!selectedFile.value) return
|
|
|
|
uploading.value = true
|
|
message.value = ''
|
|
|
|
try {
|
|
const formData = new FormData()
|
|
formData.append('pdf', selectedFile.value)
|
|
|
|
const result = await $fetch('/api/cms/satzung-upload', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
|
|
message.value = result.message
|
|
messageType.value = 'success'
|
|
|
|
// Aktuelle Satzung neu laden
|
|
await loadCurrentSatzung()
|
|
|
|
// Formular zurücksetzen
|
|
selectedFile.value = null
|
|
if (fileInput.value) fileInput.value.value = ''
|
|
|
|
} catch (error) {
|
|
message.value = error.data?.message || 'Fehler beim Hochladen der PDF-Datei'
|
|
messageType.value = 'error'
|
|
} finally {
|
|
uploading.value = false
|
|
}
|
|
}
|
|
|
|
async function saveText() {
|
|
savingText.value = true
|
|
message.value = ''
|
|
try {
|
|
const current = await $fetch('/api/config')
|
|
const currentSeiten = current.seiten || {}
|
|
const currentSatzung = currentSeiten.satzung || {}
|
|
|
|
const updated = {
|
|
...current,
|
|
seiten: {
|
|
...currentSeiten,
|
|
satzung: {
|
|
...currentSatzung,
|
|
content: satzungContent.value || ''
|
|
}
|
|
}
|
|
}
|
|
|
|
await $fetch('/api/config', {
|
|
method: 'PUT',
|
|
body: updated
|
|
})
|
|
|
|
message.value = 'Satzungstext erfolgreich gespeichert'
|
|
messageType.value = 'success'
|
|
|
|
try {
|
|
window.showSuccessModal && window.showSuccessModal('Erfolg', 'Satzungstext erfolgreich gespeichert.')
|
|
} catch {
|
|
// Modal optional
|
|
}
|
|
} catch (error) {
|
|
const errMsg = error?.data?.message || 'Fehler beim Speichern des Satzungstextes'
|
|
message.value = errMsg
|
|
messageType.value = 'error'
|
|
try {
|
|
window.showErrorModal && window.showErrorModal('Fehler', errMsg)
|
|
} catch {
|
|
// optional
|
|
}
|
|
} finally {
|
|
savingText.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(loadCurrentSatzung)
|
|
</script>
|