Refactor database configuration and enhance server settings: Update database connection logic to utilize environment variables and improve error handling in database connection. Adjust server port configuration to prioritize BACKEND_PORT. Update HTML structure for better compatibility and add missing elements in various components.
This commit is contained in:
90
src/common/components/BreadcrumbsComponent.vue
Normal file
90
src/common/components/BreadcrumbsComponent.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<nav v-if="crumbs.length" class="breadcrumbs" aria-label="Brotkrumen">
|
||||
<ol>
|
||||
<li v-for="(c, idx) in crumbs" :key="c.to + '-' + idx">
|
||||
<router-link v-if="idx < crumbs.length - 1" :to="c.to">{{ c.label }}</router-link>
|
||||
<span v-else aria-current="page">{{ c.label }}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
function findLabel(menuItems, link) {
|
||||
for (const item of menuItems) {
|
||||
if (item?.link === link) {
|
||||
return item.pageTitle || item.name || '';
|
||||
}
|
||||
if (item?.submenu?.length) {
|
||||
const found = findLabel(item.submenu, link);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'BreadcrumbsComponent',
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
|
||||
const crumbs = computed(() => {
|
||||
const path = route.path || '/';
|
||||
const list = [{ label: 'Startseite', to: '/' }];
|
||||
|
||||
if (path === '/') return [];
|
||||
|
||||
const label = findLabel(store.state.menuData || [], path);
|
||||
list.push({ label: label || 'Seite', to: path });
|
||||
return list;
|
||||
});
|
||||
|
||||
return { crumbs };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.breadcrumbs {
|
||||
margin: 0 var(--space-3) var(--space-2) var(--space-3);
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.breadcrumbs ol {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.breadcrumbs li {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.breadcrumbs li:not(:last-child)::after {
|
||||
content: '›';
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.breadcrumbs a {
|
||||
color: var(--color-text-muted);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.breadcrumbs a:hover,
|
||||
.breadcrumbs a:focus-visible {
|
||||
color: var(--color-text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -34,14 +34,16 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.footer {
|
||||
background-color: #0b1735;
|
||||
background-color: var(--color-footer-bg);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 7px;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.left-links {
|
||||
@@ -52,17 +54,22 @@ export default {
|
||||
.right-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #fff;
|
||||
padding-right: 20px;
|
||||
color: var(--color-footer-link);
|
||||
padding-right: var(--space-5);
|
||||
text-decoration: none;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.footer a.login-link {
|
||||
color: #444;
|
||||
.footer a:hover,
|
||||
.footer a:focus-visible {
|
||||
color: var(--color-footer-link-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.footer a.logout-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -5,18 +5,21 @@
|
||||
<span class="reload-icon" @click="reloadMenu">⟳</span>
|
||||
</div>
|
||||
<NavbarComponent />
|
||||
<BreadcrumbsComponent />
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavbarComponent from './NavbarComponent.vue';
|
||||
import BreadcrumbsComponent from './BreadcrumbsComponent.vue';
|
||||
import { mapActions } from 'vuex';
|
||||
import router from '@/router';
|
||||
|
||||
export default {
|
||||
name: 'HeaderComponent',
|
||||
components: {
|
||||
NavbarComponent
|
||||
NavbarComponent,
|
||||
BreadcrumbsComponent,
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['loadMenuData']),
|
||||
@@ -42,7 +45,7 @@ header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
background-color: #ffffff;
|
||||
background-color: var(--color-bg-page);
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@@ -50,28 +53,30 @@ header {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: .3em .5em;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
text-shadow: 2px 2px 1px #e0bfff;
|
||||
padding-bottom: 4px;
|
||||
text-shadow: 1px 1px 0 var(--color-brand-tint);
|
||||
padding-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.reload-icon {
|
||||
font-size: 16px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
background-color: #e0bfff;
|
||||
color: white;
|
||||
padding: 5px;
|
||||
margin-left: var(--space-3);
|
||||
background-color: var(--color-brand-tint);
|
||||
color: var(--color-bg-page);
|
||||
padding: var(--space-1) 6px;
|
||||
border-radius: 50%;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.reload-icon:hover {
|
||||
color: #7a00d1;
|
||||
.reload-icon:hover,
|
||||
.reload-icon:focus-visible {
|
||||
color: var(--color-brand-primary-hover);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
<template>
|
||||
<nav class="navbar">
|
||||
<button class="menu-toggle" @click="toggleMenu">
|
||||
Menü
|
||||
<button
|
||||
class="menu-toggle"
|
||||
type="button"
|
||||
@click="toggleMenu"
|
||||
:aria-expanded="String(isMenuOpen)"
|
||||
aria-controls="main-menu"
|
||||
>
|
||||
<span class="menu-toggle__icon" aria-hidden="true">≡</span>
|
||||
<span>Menü</span>
|
||||
</button>
|
||||
<ul v-if="isMenuOpen || windowWidth > 768">
|
||||
<li class="ekhnlogo"><img src="/images/facettenkreuz.png" class="facettenkreuz" /></li>
|
||||
<li v-for="item in menu" :key="item.name" @click="toggleSubmenu(item.name)">
|
||||
<router-link :to="item.link" v-if="item.link" @click="closeMenu">
|
||||
<ul id="main-menu" v-if="isMenuOpen || windowWidth > 768">
|
||||
<li class="ekhnlogo">
|
||||
<img src="/images/facettenkreuz.png" class="facettenkreuz" alt="EKHN" />
|
||||
</li>
|
||||
<li v-for="item in menu" :key="item.name">
|
||||
<router-link :to="item.link" v-if="item.link" @click="closeMenu" class="nav-link">
|
||||
{{ item.name }}
|
||||
</router-link>
|
||||
<span v-if="!item.link" class="pointer">
|
||||
<span
|
||||
v-if="!item.link"
|
||||
class="nav-link pointer"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="toggleSubmenu(item.name)"
|
||||
@keydown.enter.prevent="toggleSubmenu(item.name)"
|
||||
@keydown.space.prevent="toggleSubmenu(item.name)"
|
||||
:aria-expanded="String(!!isSubmenuOpen[item.name])"
|
||||
>
|
||||
{{ item.name }}
|
||||
</span>
|
||||
<transition name="fade">
|
||||
@@ -38,16 +56,20 @@ export default {
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
|
||||
const menu = computed(() => {
|
||||
return store.state.menuData.filter(item => {
|
||||
return (store.state.menuData || []).map((item) => {
|
||||
const submenu = Array.isArray(item.submenu)
|
||||
? item.submenu.filter(
|
||||
(subitem) => subitem.showInMenu && (!subitem.requiresAuth || store.getters.isLoggedIn)
|
||||
)
|
||||
: item.submenu;
|
||||
return { ...item, submenu };
|
||||
}).filter(item => {
|
||||
if (!item.showInMenu) {
|
||||
return false;
|
||||
}
|
||||
if (item.requiresAuth && !store.getters.isLoggedIn) {
|
||||
return false;
|
||||
}
|
||||
if (item.submenu) {
|
||||
item.submenu = item.submenu.filter(subitem => subitem.showInMenu && (!subitem.requiresAuth || store.getters.isLoggedIn));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
@@ -102,25 +124,34 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.navbar {
|
||||
background-color: #9400ff;
|
||||
background-color: var(--color-brand-primary);
|
||||
overflow: visible;
|
||||
min-height: 31px;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
width: auto;
|
||||
margin: 0.1em 0.75em 9px 0.75em;
|
||||
box-shadow: 0 0 2px 5px #9400ff;
|
||||
width: calc(100% - (var(--space-3) * 2));
|
||||
margin: var(--space-1) var(--space-3) var(--space-3) var(--space-3);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 0 2px var(--color-brand-primary);
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
background-color: #9400ff;
|
||||
color: white;
|
||||
background-color: var(--color-brand-primary);
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 14px 20px;
|
||||
text-align: center;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
display: none;
|
||||
font-weight: bold;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.menu-toggle__icon {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.navbar ul {
|
||||
@@ -128,24 +159,39 @@ export default {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.navbar li {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-link,
|
||||
.navbar a,
|
||||
.navbar li>span {
|
||||
.navbar li > span {
|
||||
display: block;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 6px 20px;
|
||||
padding: 10px 14px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
border-radius: 3px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.navbar a:hover {
|
||||
background-color: #7a00d1;
|
||||
.navbar a:hover,
|
||||
.nav-link:hover {
|
||||
background-color: var(--color-brand-primary-hover);
|
||||
}
|
||||
|
||||
.navbar a:focus-visible,
|
||||
.menu-toggle:focus-visible {
|
||||
outline-color: #fff;
|
||||
}
|
||||
|
||||
.navbar :deep(.router-link-exact-active) {
|
||||
background-color: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
@@ -156,7 +202,7 @@ export default {
|
||||
|
||||
.dropdown-content {
|
||||
position: absolute;
|
||||
background-color: #9400ff;
|
||||
background-color: var(--color-brand-primary);
|
||||
min-width: 200px;
|
||||
z-index: 1;
|
||||
top: 100%;
|
||||
@@ -164,7 +210,9 @@ export default {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
|
||||
box-shadow: 2px 2px 4px #666;
|
||||
box-shadow: var(--shadow-dropdown);
|
||||
border-radius: 4px;
|
||||
padding: var(--space-1);
|
||||
}
|
||||
|
||||
.dropdown-content a {
|
||||
@@ -176,7 +224,7 @@ export default {
|
||||
}
|
||||
|
||||
.dropdown-content a:hover {
|
||||
background-color: #7a00d1;
|
||||
background-color: var(--color-brand-primary-hover);
|
||||
}
|
||||
|
||||
.navbar li:hover .dropdown-content {
|
||||
@@ -205,6 +253,8 @@ export default {
|
||||
|
||||
.navbar ul {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.navbar li {
|
||||
@@ -214,11 +264,12 @@ export default {
|
||||
.navbar a,
|
||||
.navbar li>span {
|
||||
text-align: left;
|
||||
padding: 14px 20px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
display: block;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
@@ -226,12 +277,12 @@ export default {
|
||||
box-shadow: none;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
display: none;
|
||||
padding-left: 1em;
|
||||
padding-left: var(--space-4);
|
||||
padding-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.navbar li:hover .dropdown-content {
|
||||
display: block;
|
||||
.navbar :deep(.router-link-exact-active) {
|
||||
background-color: rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,12 +291,16 @@ export default {
|
||||
}
|
||||
|
||||
.facettenkreuz {
|
||||
max-width:30px;
|
||||
max-height:30px;
|
||||
position: fixed;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: block;
|
||||
margin: 0 var(--space-2);
|
||||
}
|
||||
|
||||
.ekhnlogo {
|
||||
width: 32px;
|
||||
width: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user