routing inside url
This commit is contained in:
parent
2d0fb30558
commit
6247c385c3
15 changed files with 278 additions and 203 deletions
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<footer
|
<footer
|
||||||
v-if="authCheck && routeName == 'admin'"
|
v-if="authCheck && routeName.includes('admin')"
|
||||||
class="md:hidden flex flex-row h-16 justify-center md:justify-normal p-1 bg-white"
|
class="md:hidden flex flex-row h-16 justify-center md:justify-normal p-1 bg-white"
|
||||||
>
|
>
|
||||||
<div class="w-full flex flex-row gap-2 h-full align-middle">
|
<div class="w-full flex flex-row gap-2 h-full align-middle">
|
||||||
|
@ -23,7 +23,7 @@ export default defineComponent({
|
||||||
...mapState(useAuthStore, ["authCheck"]),
|
...mapState(useAuthStore, ["authCheck"]),
|
||||||
...mapState(useNavigationStore, ["topLevel"]),
|
...mapState(useNavigationStore, ["topLevel"]),
|
||||||
routeName() {
|
routeName() {
|
||||||
return this.$route.name;
|
return typeof this.$route.name == "string" ? this.$route.name : "";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<h1 v-if="false" class="font-bold text-3xl w-fit whitespace-nowrap">Mitgliederverwaltung</h1>
|
<h1 v-if="false" class="font-bold text-3xl w-fit whitespace-nowrap">Mitgliederverwaltung</h1>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<div class="flex flex-row gap-2 items-center">
|
<div class="flex flex-row gap-2 items-center">
|
||||||
<div v-if="authCheck && routeName == 'admin'" class="hidden md:flex flex-row gap-2 h-full align-middle">
|
<div v-if="authCheck && routeName.includes('admin')" class="hidden md:flex flex-row gap-2 h-full align-middle">
|
||||||
<TopLevelLink v-for="item in topLevel" :key="item.key" :link="item" />
|
<TopLevelLink v-for="item in topLevel" :key="item.key" :link="item" />
|
||||||
</div>
|
</div>
|
||||||
<UserMenu v-if="authCheck" />
|
<UserMenu v-if="authCheck" />
|
||||||
|
@ -29,7 +29,7 @@ export default defineComponent({
|
||||||
...mapState(useAuthStore, ["authCheck"]),
|
...mapState(useAuthStore, ["authCheck"]),
|
||||||
...mapState(useNavigationStore, ["topLevel"]),
|
...mapState(useNavigationStore, ["topLevel"]),
|
||||||
routeName() {
|
routeName() {
|
||||||
return this.$route.name;
|
return typeof this.$route.name == "string" ? this.$route.name : "";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<RouterLink v-if="link" v-slot="{ isExactActive }" :to="{ name: `admin-${activeNavigation}-${link.key}` }">
|
||||||
v-if="link"
|
<p
|
||||||
class="cursor-pointer w-full px-2 py-3"
|
class="cursor-pointer w-full px-2 py-3"
|
||||||
:class="
|
:class="
|
||||||
activeLink?.key == link.key
|
isExactActive ? 'rounded-r-lg bg-red-200 border-l-4 border-l-primary' : 'pl-3 hover:bg-red-200 rounded-lg'
|
||||||
? 'rounded-r-lg bg-red-200 border-l-4 border-l-primary'
|
"
|
||||||
: 'pl-3 hover:bg-red-200 rounded-lg'
|
>
|
||||||
"
|
{{ link.title }}
|
||||||
@click="setLink(link.key)"
|
</p>
|
||||||
>
|
</RouterLink>
|
||||||
{{ link.title }}
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -20,6 +18,7 @@ import { useNavigationStore, type navigationLinkModel } from "@/stores/admin/nav
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, type PropType } from "vue";
|
import { defineComponent, type PropType } from "vue";
|
||||||
|
import { RouterLink } from "vue-router";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
link: {
|
link: {
|
||||||
|
@ -28,11 +27,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useNavigationStore, ["activeLink"]),
|
...mapState(useNavigationStore, ["activeNavigation"]),
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions(useNavigationStore, ["setLink"]),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@/stores/contest/viewManager
|
|
||||||
|
|
|
@ -1,25 +1,30 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<RouterLink
|
||||||
v-if="link"
|
v-if="link"
|
||||||
class="cursor-pointer w-full flex flex-col md:flex-row items-center md:gap-2 justify-center p-1 md:rounded-full md:px-3 font-medium text-center text-base self-center"
|
:to="{ name: `admin-${link.key}-${!disableSubLink ? link.levelDefault : 'default'}` }"
|
||||||
:class="
|
class="cursor-pointer w-full flex items-center justify-center self-center"
|
||||||
activeNavigation == link.key
|
|
||||||
? 'text-primary md:bg-primary md:text-white'
|
|
||||||
: 'text-gray-700 hover:text-accent md:hover:bg-accent md:hover:text-white'
|
|
||||||
"
|
|
||||||
@click="setTopLevel(link.key, disableSubLink)"
|
|
||||||
>
|
>
|
||||||
{{ link.title }}
|
<p
|
||||||
</div>
|
class="cursor-pointer w-full flex flex-col md:flex-row items-center md:gap-2 justify-center p-1 md:rounded-full md:px-3 font-medium text-center text-base self-center"
|
||||||
|
:class="
|
||||||
|
activeNavigation == link.key
|
||||||
|
? 'text-primary md:bg-primary md:text-white'
|
||||||
|
: 'text-gray-700 hover:text-accent md:hover:bg-accent md:hover:text-white'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ link.title }}
|
||||||
|
</p>
|
||||||
|
</RouterLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { mapState, mapActions } from "pinia";
|
|
||||||
import { useNavigationStore, type topLevelNavigationModel } from "@/stores/admin/navigation";
|
import { useNavigationStore, type topLevelNavigationModel } from "@/stores/admin/navigation";
|
||||||
|
import { mapState } from "pinia";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, type PropType } from "vue";
|
import { defineComponent, type PropType } from "vue";
|
||||||
|
import { RouterLink } from "vue-router";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
link: {
|
link: {
|
||||||
|
@ -34,8 +39,5 @@ export default defineComponent({
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useNavigationStore, ["activeNavigation"]),
|
...mapState(useNavigationStore, ["activeNavigation"]),
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
...mapActions(useNavigationStore, ["setTopLevel"]),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,16 +2,18 @@
|
||||||
<div class="flex flex-col h-fit w-full border border-primary rounded-md">
|
<div class="flex flex-col h-fit w-full border border-primary rounded-md">
|
||||||
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
|
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
|
||||||
<p>{{ role.role }} <small v-if="role.permissions?.isAdmin">(Admin)</small></p>
|
<p>{{ role.role }} <small v-if="role.permissions?.isAdmin">(Admin)</small></p>
|
||||||
<PencilIcon class="w-5 h-5 p-1 box-content cursor-pointer" />
|
<PencilIcon class="w-5 h-5 p-1 box-content cursor-pointer" @click="openUpdateModal" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent, type PropType } from "vue";
|
import { defineComponent, defineAsyncComponent, markRaw, type PropType } from "vue";
|
||||||
import { mapState, mapActions } from "pinia";
|
import { mapState, mapActions } from "pinia";
|
||||||
import { PencilIcon } from "@heroicons/vue/outline";
|
import { PencilIcon } from "@heroicons/vue/outline";
|
||||||
import type { RoleViewModel } from "@/viewmodels/admin/role.models";
|
import type { RoleViewModel } from "@/viewmodels/admin/role.models";
|
||||||
|
import { useModalStore } from "@/stores/modal";
|
||||||
|
import { useNavigationStore } from "@/stores/admin/navigation";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -23,5 +25,14 @@ export default defineComponent({
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
mounted() {},
|
mounted() {},
|
||||||
|
methods: {
|
||||||
|
...mapActions(useModalStore, ["openModal"]),
|
||||||
|
openUpdateModal() {
|
||||||
|
this.openModal(
|
||||||
|
markRaw(defineAsyncComponent(() => import("@/components/admin/user/role/UpdateRoleModal.vue"))),
|
||||||
|
this.role.id
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
54
src/components/admin/user/role/UpdateRoleModal.vue
Normal file
54
src/components/admin/user/role/UpdateRoleModal.vue
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col items-center gap-2 w-full max-w-6xl h-1/2 max-h-3/4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<p class="text-xl font-medium">Rolle bearbeiten</p>
|
||||||
|
</div>
|
||||||
|
<form class="flex flex-col gap-4 py-2 w-full max-w-xl" @submit.prevent="">
|
||||||
|
<div>
|
||||||
|
<label for="role">Rollenbezeichnung</label>
|
||||||
|
<input type="text" id="role" required />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row gap-2">
|
||||||
|
<button primary type="submit" :disabled="createStatus == 'loading' || createStatus?.status == 'success'">
|
||||||
|
speichern
|
||||||
|
</button>
|
||||||
|
<Spinner v-if="createStatus == 'loading'" class="my-auto" />
|
||||||
|
<SuccessCheckmark v-else-if="createStatus?.status == 'success'" />
|
||||||
|
<FailureXMark v-else-if="createStatus?.status == 'failed'" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="flex flex-row self-end mt-auto">
|
||||||
|
<div class="flex flex-row gap-4 py-2">
|
||||||
|
<button primary-outline @click="closeModal">schließen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import { mapState, mapActions } from "pinia";
|
||||||
|
import { useModalStore } from "@/stores/modal";
|
||||||
|
import Spinner from "@/components/Spinner.vue";
|
||||||
|
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
||||||
|
import FailureXMark from "@/components/FailureXMark.vue";
|
||||||
|
import { useRoleStore } from "@/stores/admin/role";
|
||||||
|
import Permission from "../../Permission.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default defineComponent({
|
||||||
|
mounted() {
|
||||||
|
this.fetchRoleById(this.data);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(useRoleStore, ["createStatus", "role"]),
|
||||||
|
...mapState(useModalStore, ["data"]),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(useModalStore, ["closeModal"]),
|
||||||
|
...mapActions(useRoleStore, ["fetchRoleById"]),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -6,7 +6,7 @@
|
||||||
>
|
>
|
||||||
<slot name="sidebar"></slot>
|
<slot name="sidebar"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-w-full grow flex-col gap-4" :class="defaultRoute && defaultSidebar ? 'hidden md:flex' : 'flex'">
|
<div class="max-w-full grow flex-col gap-2" :class="defaultRoute && defaultSidebar ? 'hidden md:flex' : 'flex'">
|
||||||
<slot name="main"></slot>
|
<slot name="main"></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,7 +29,7 @@ export default defineComponent({
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useNavigationStore, ["activeLink"]),
|
...mapState(useNavigationStore, ["activeLink"]),
|
||||||
defaultRoute() {
|
defaultRoute() {
|
||||||
return this.activeLink == null;
|
return ((this.$route?.name as string) ?? "").includes("-default");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
24
src/router/adminGuard.ts
Normal file
24
src/router/adminGuard.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import NProgress from "nprogress";
|
||||||
|
import { useAbilityStore } from "../stores/ability";
|
||||||
|
import { useNavigationStore } from "../stores/admin/navigation";
|
||||||
|
|
||||||
|
export async function abilityAndNavUpdate(to: any, from: any, next: any) {
|
||||||
|
NProgress.start();
|
||||||
|
const ability = useAbilityStore();
|
||||||
|
const navigation = useNavigationStore();
|
||||||
|
|
||||||
|
let type = to.meta.type;
|
||||||
|
let section = to.meta.section;
|
||||||
|
let module = to.meta.module;
|
||||||
|
|
||||||
|
navigation.activeNavigation = to.name.split("-")[1];
|
||||||
|
navigation.activeLink = to.name.split("-")[2];
|
||||||
|
|
||||||
|
if (ability.can(type, section, module)) {
|
||||||
|
NProgress.done();
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
NProgress.done();
|
||||||
|
next(false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import Login from "../views/Login.vue";
|
||||||
import { isAuthenticated } from "./authGuards";
|
import { isAuthenticated } from "./authGuards";
|
||||||
import { loadAccountData } from "./accountGuard";
|
import { loadAccountData } from "./accountGuard";
|
||||||
import { isSetup } from "./setupGuard";
|
import { isSetup } from "./setupGuard";
|
||||||
|
import { abilityAndNavUpdate } from "./adminGuard";
|
||||||
|
import type { PermissionType, PermissionSection, PermissionModule } from "../types/permissionTypes";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
@ -41,6 +43,110 @@ const router = createRouter({
|
||||||
name: "admin",
|
name: "admin",
|
||||||
component: () => import("../views/admin/View.vue"),
|
component: () => import("../views/admin/View.vue"),
|
||||||
beforeEnter: [isAuthenticated],
|
beforeEnter: [isAuthenticated],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
name: "admin-default",
|
||||||
|
component: () => import("../views/RouterView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "club",
|
||||||
|
name: "admin-club",
|
||||||
|
component: () => import("../views/RouterView.vue"),
|
||||||
|
meta: { type: "read", section: "club" },
|
||||||
|
beforeEnter: [abilityAndNavUpdate],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
name: "admin-club-default",
|
||||||
|
component: () => import("../views/admin/ViewSelect.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "members",
|
||||||
|
name: "admin-club-members",
|
||||||
|
component: () => import("../views/admin/members/Overview.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "calendar",
|
||||||
|
name: "admin-club-calendar",
|
||||||
|
component: () => import("../views/admin/members/Overview.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "newsletter",
|
||||||
|
name: "admin-club-newsletter",
|
||||||
|
component: () => import("../views/admin/members/Overview.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "protocol",
|
||||||
|
name: "admin-club-protocol",
|
||||||
|
component: () => import("../views/admin/members/Overview.vue"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "settings",
|
||||||
|
name: "admin-settings",
|
||||||
|
component: () => import("../views/RouterView.vue"),
|
||||||
|
meta: { type: "read", section: "settings" },
|
||||||
|
beforeEnter: [abilityAndNavUpdate],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
name: "admin-settings-default",
|
||||||
|
component: () => import("../views/admin/ViewSelect.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "qualification",
|
||||||
|
name: "admin-settings-qualification",
|
||||||
|
component: () => import("../views/admin/members/Overview.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "award",
|
||||||
|
name: "admin-settings-award",
|
||||||
|
component: () => import("../views/admin/members/Overview.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "executive-position",
|
||||||
|
name: "admin-settings-executive_position",
|
||||||
|
component: () => import("../views/admin/members/Overview.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "communication",
|
||||||
|
name: "admin-settings-communication",
|
||||||
|
component: () => import("../views/admin/members/Overview.vue"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "user",
|
||||||
|
name: "admin-user",
|
||||||
|
component: () => import("../views/RouterView.vue"),
|
||||||
|
meta: { type: "read", section: "user" },
|
||||||
|
beforeEnter: [abilityAndNavUpdate],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
name: "admin-user-default",
|
||||||
|
component: () => import("../views/admin/ViewSelect.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "user",
|
||||||
|
name: "admin-user-user",
|
||||||
|
component: () => import("../views/admin/user/User.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "role",
|
||||||
|
name: "admin-user-role",
|
||||||
|
component: () => import("../views/admin/user/Role.vue"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ":pathMatch(.*)*",
|
||||||
|
name: "admin-404",
|
||||||
|
component: () => import("../views/notFound.vue"),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/nopermissions",
|
path: "/nopermissions",
|
||||||
|
@ -56,3 +162,11 @@ const router = createRouter({
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
|
declare module "vue-router" {
|
||||||
|
interface RouteMeta {
|
||||||
|
type?: PermissionType | "admin";
|
||||||
|
section?: PermissionSection;
|
||||||
|
module?: PermissionModule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { shallowRef, defineAsyncComponent } from "vue";
|
|
||||||
import { useAbilityStore } from "../ability";
|
import { useAbilityStore } from "../ability";
|
||||||
|
import router from "../../router";
|
||||||
|
|
||||||
export interface navigationModel {
|
export interface navigationModel {
|
||||||
club: navigationSplitModel;
|
club: navigationSplitModel;
|
||||||
|
@ -26,17 +26,15 @@ export interface topLevelNavigationModel {
|
||||||
export interface navigationLinkModel {
|
export interface navigationLinkModel {
|
||||||
key: string;
|
key: string;
|
||||||
title: string;
|
title: string;
|
||||||
component: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNavigationStore = defineStore("navigation", {
|
export const useNavigationStore = defineStore("navigation", {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
activeNavigation: "club" as topLevelNavigationType,
|
activeNavigation: "club" as topLevelNavigationType,
|
||||||
activeLink: null as null | navigationLinkModel,
|
activeLink: null as null | string,
|
||||||
topLevel: [] as Array<topLevelNavigationModel>,
|
topLevel: [] as Array<topLevelNavigationModel>,
|
||||||
navigation: {} as navigationModel,
|
navigation: {} as navigationModel,
|
||||||
componentOverwrite: null as null | any,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
|
@ -45,42 +43,10 @@ export const useNavigationStore = defineStore("navigation", {
|
||||||
(state.topLevel.find((elem) => elem.key == state.activeNavigation) ?? {}) as topLevelNavigationModel,
|
(state.topLevel.find((elem) => elem.key == state.activeNavigation) ?? {}) as topLevelNavigationModel,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setTopLevel(key: topLevelNavigationType, disableSubLink: boolean = true) {
|
|
||||||
let level = this.topLevel.find((e) => e.key == key) ?? null;
|
|
||||||
if (!level) {
|
|
||||||
this.activeNavigation = "club";
|
|
||||||
if (!disableSubLink) this.setLink(this.topLevel.find((e) => e.key == "club")?.levelDefault ?? null);
|
|
||||||
else this.setLink(null);
|
|
||||||
} else {
|
|
||||||
this.activeNavigation = level.key;
|
|
||||||
if (!disableSubLink) this.setLink(level.levelDefault);
|
|
||||||
else this.setLink(null);
|
|
||||||
}
|
|
||||||
this.resetComponentOverwrite();
|
|
||||||
},
|
|
||||||
setLink(key: string | null) {
|
|
||||||
let nav = this.navigation[this.activeNavigation];
|
|
||||||
if (!nav) {
|
|
||||||
this.activeLink = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let links = [...Object.values(nav.main), ...Object.values(nav.top ?? {})];
|
|
||||||
this.activeLink = links.find((e) => e.key == key) ?? null;
|
|
||||||
this.resetComponentOverwrite();
|
|
||||||
},
|
|
||||||
setTopLevelNav(topLeveLinks: Array<topLevelNavigationModel>) {
|
|
||||||
this.topLevel = topLeveLinks;
|
|
||||||
},
|
|
||||||
setComponentOverwrite(component: any) {
|
|
||||||
this.componentOverwrite = component;
|
|
||||||
},
|
|
||||||
resetComponentOverwrite() {
|
|
||||||
this.componentOverwrite = null;
|
|
||||||
},
|
|
||||||
resetNavigation() {
|
resetNavigation() {
|
||||||
this.$reset();
|
this.$reset();
|
||||||
},
|
},
|
||||||
updateTopLevel() {
|
updateTopLevel(first: boolean = false) {
|
||||||
const abilityStore = useAbilityStore();
|
const abilityStore = useAbilityStore();
|
||||||
this.topLevel = [
|
this.topLevel = [
|
||||||
...(abilityStore.canSection("read", "club")
|
...(abilityStore.canSection("read", "club")
|
||||||
|
@ -88,7 +54,7 @@ export const useNavigationStore = defineStore("navigation", {
|
||||||
{
|
{
|
||||||
key: "club",
|
key: "club",
|
||||||
title: "Verein",
|
title: "Verein",
|
||||||
levelDefault: "#members",
|
levelDefault: "members",
|
||||||
} as topLevelNavigationModel,
|
} as topLevelNavigationModel,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
@ -97,7 +63,7 @@ export const useNavigationStore = defineStore("navigation", {
|
||||||
{
|
{
|
||||||
key: "settings",
|
key: "settings",
|
||||||
title: "Einstellungen",
|
title: "Einstellungen",
|
||||||
levelDefault: "#qualification",
|
levelDefault: "qualification",
|
||||||
} as topLevelNavigationModel,
|
} as topLevelNavigationModel,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
@ -106,124 +72,51 @@ export const useNavigationStore = defineStore("navigation", {
|
||||||
{
|
{
|
||||||
key: "user",
|
key: "user",
|
||||||
title: "Benutzer",
|
title: "Benutzer",
|
||||||
levelDefault: "#user",
|
levelDefault: "user",
|
||||||
} as topLevelNavigationModel,
|
} as topLevelNavigationModel,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
];
|
];
|
||||||
if (this.topLevel.findIndex((e) => e.key == this.activeNavigation) == -1)
|
if (this.topLevel.findIndex((e) => e.key == this.activeNavigation) == -1 && !first)
|
||||||
this.activeNavigation = this.topLevel[0]?.key ?? "club";
|
router.push({ name: `admin-${this.topLevel[0]?.key ?? "club"}-default` });
|
||||||
},
|
},
|
||||||
updateNavigation() {
|
updateNavigation(first: boolean = false) {
|
||||||
const abilityStore = useAbilityStore();
|
const abilityStore = useAbilityStore();
|
||||||
this.navigation = {
|
this.navigation = {
|
||||||
club: {
|
club: {
|
||||||
mainTitle: "Verein",
|
mainTitle: "Verein",
|
||||||
main: [
|
main: [
|
||||||
...(abilityStore.can("read", "club", "members")
|
...(abilityStore.can("read", "club", "members") ? [{ key: "members", title: "Mitglieder" }] : []),
|
||||||
? [
|
...(abilityStore.can("read", "club", "calendar") ? [{ key: "calendar", title: "Termine" }] : []),
|
||||||
{
|
...(abilityStore.can("read", "club", "newsletter") ? [{ key: "newsletter", title: "Newsletter" }] : []),
|
||||||
key: "#members",
|
...(abilityStore.can("read", "club", "protocoll") ? [{ key: "protocol", title: "Protokolle" }] : []),
|
||||||
title: "Mitglieder",
|
|
||||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...(abilityStore.can("read", "club", "calendar")
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
key: "#calendar",
|
|
||||||
title: "Termine",
|
|
||||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...(abilityStore.can("read", "club", "newsletter")
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
key: "#newsletter",
|
|
||||||
title: "Newsletter",
|
|
||||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...(abilityStore.can("read", "club", "protocoll")
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
key: "#protocol",
|
|
||||||
title: "Protokolle",
|
|
||||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
mainTitle: "Einstellungen",
|
mainTitle: "Einstellungen",
|
||||||
main: [
|
main: [
|
||||||
...(abilityStore.can("read", "settings", "qualification")
|
...(abilityStore.can("read", "settings", "qualification")
|
||||||
? [
|
? [{ key: "qualification", title: "Qualifikationen" }]
|
||||||
{
|
|
||||||
key: "#qualification",
|
|
||||||
title: "Qualifikationen",
|
|
||||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...(abilityStore.can("read", "settings", "award")
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
key: "#award",
|
|
||||||
title: "Auszeichnungen",
|
|
||||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
: []),
|
||||||
|
...(abilityStore.can("read", "settings", "award") ? [{ key: "award", title: "Auszeichnungen" }] : []),
|
||||||
...(abilityStore.can("read", "settings", "executive_position")
|
...(abilityStore.can("read", "settings", "executive_position")
|
||||||
? [
|
? [{ key: "executive_position", title: "Vereinsämter" }]
|
||||||
{
|
|
||||||
key: "#executive_position",
|
|
||||||
title: "Vereinsämter",
|
|
||||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
: []),
|
||||||
...(abilityStore.can("read", "settings", "communication")
|
...(abilityStore.can("read", "settings", "communication")
|
||||||
? [
|
? [{ key: "communication", title: "Mitgliederdaten" }]
|
||||||
{
|
|
||||||
key: "#communication",
|
|
||||||
title: "Mitgliederdaten",
|
|
||||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
: []),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
mainTitle: "Benutzer",
|
mainTitle: "Benutzer",
|
||||||
main: [
|
main: [
|
||||||
...(abilityStore.can("read", "user", "user")
|
...(abilityStore.can("read", "user", "user") ? [{ key: "user", title: "Benutzer" }] : []),
|
||||||
? [
|
...(abilityStore.can("read", "user", "role") ? [{ key: "role", title: "Rollen" }] : []),
|
||||||
{
|
|
||||||
key: "#user",
|
|
||||||
title: "Benutzer",
|
|
||||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/user/User.vue"))),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...(abilityStore.can("read", "user", "role")
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
key: "#role",
|
|
||||||
title: "Rollen",
|
|
||||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/user/Role.vue"))),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
} as navigationModel;
|
} as navigationModel;
|
||||||
if (this.topLevel.findIndex((e) => e.key == this.activeLink?.key) == -1) this.setLink(null);
|
if (this.topLevel.findIndex((e) => e.key == this.activeLink) == -1 && !first)
|
||||||
|
router.push({ name: `admin-${this.activeNavigation}-default` });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,7 +25,7 @@ export const useRoleStore = defineStore("role", {
|
||||||
this.loadingAll = "failed";
|
this.loadingAll = "failed";
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fetchRolesById(id: number) {
|
fetchRoleById(id: number) {
|
||||||
this.role = null;
|
this.role = null;
|
||||||
this.loadingSingle = "loading";
|
this.loadingSingle = "loading";
|
||||||
http
|
http
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const useUserStore = defineStore("user", {
|
||||||
this.loadingAll = "failed";
|
this.loadingAll = "failed";
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fetchUsersById(id: number) {
|
fetchUserById(id: number) {
|
||||||
this.user = null;
|
this.user = null;
|
||||||
this.loadingSingle = "loading";
|
this.loadingSingle = "loading";
|
||||||
http
|
http
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="!defaultRoute && showBack" class="flex md:hidden flex-row items-baseline">
|
<div v-if="!defaultRoute && showBack" class="flex md:hidden flex-row items-baseline">
|
||||||
<p v-if="!defaultRoute && showBack" class="text-primary" @click="setLink(null)">zur Übersicht</p>
|
<RouterLink v-if="!defaultRoute && showBack" :to="{ name: `${rootRoute}-default` }" class="mid:hidden text-primary">
|
||||||
|
zur Übersicht
|
||||||
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<slot v-if="headerInsert" name="headerInsert"></slot>
|
<slot v-if="headerInsert" name="headerInsert"></slot>
|
||||||
<div
|
<div
|
||||||
|
@ -37,7 +39,10 @@ export default defineComponent({
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useNavigationStore, ["activeLink"]),
|
...mapState(useNavigationStore, ["activeLink"]),
|
||||||
defaultRoute() {
|
defaultRoute() {
|
||||||
return this.activeLink == null;
|
return ((this.$route?.name as string) ?? "").includes("-default");
|
||||||
|
},
|
||||||
|
rootRoute() {
|
||||||
|
return ((this.$route?.name as string) ?? "").split("-")[0];
|
||||||
},
|
},
|
||||||
diffMain() {
|
diffMain() {
|
||||||
return this.$slots.diffMain;
|
return this.$slots.diffMain;
|
||||||
|
@ -49,8 +54,5 @@ export default defineComponent({
|
||||||
return window.matchMedia("(min-width: 800px)").matches;
|
return window.matchMedia("(min-width: 800px)").matches;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
...mapActions(useNavigationStore, ["setLink"]),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
</SidebarTemplate>
|
</SidebarTemplate>
|
||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<component v-if="display" :is="displayed" />
|
<RouterView />
|
||||||
<div v-else class="w-full h-full bg-white rounded-lg"></div>
|
|
||||||
</template>
|
</template>
|
||||||
</SidebarLayout>
|
</SidebarLayout>
|
||||||
</template>
|
</template>
|
||||||
|
@ -29,6 +28,7 @@ import SidebarLayout from "@/layouts/Sidebar.vue";
|
||||||
import SidebarTemplate from "@/templates/Sidebar.vue";
|
import SidebarTemplate from "@/templates/Sidebar.vue";
|
||||||
import RoutingLink from "@/components/admin/RoutingLink.vue";
|
import RoutingLink from "@/components/admin/RoutingLink.vue";
|
||||||
import { useAbilityStore } from "../../stores/ability";
|
import { useAbilityStore } from "../../stores/ability";
|
||||||
|
import RouterView from "../RouterView.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -40,44 +40,21 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useNavigationStore, [
|
...mapState(useNavigationStore, ["activeNavigationObject", "activeTopLevelObject", "activeLink"]),
|
||||||
"activeNavigationObject",
|
|
||||||
"activeTopLevelObject",
|
|
||||||
"activeLink",
|
|
||||||
"componentOverwrite",
|
|
||||||
]),
|
|
||||||
display(): boolean {
|
|
||||||
return this.activeLink?.component || this.componentOverwrite;
|
|
||||||
},
|
|
||||||
displayed() {
|
|
||||||
if (this.componentOverwrite != null) {
|
|
||||||
return this.componentOverwrite;
|
|
||||||
} else {
|
|
||||||
return this.activeLink?.component;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
useAbilityStore().$subscribe(() => {
|
useAbilityStore().$subscribe(() => {
|
||||||
this.updateTopLevel();
|
this.updateTopLevel();
|
||||||
this.updateNavigation();
|
this.updateNavigation();
|
||||||
});
|
});
|
||||||
this.updateTopLevel();
|
this.updateTopLevel(true);
|
||||||
this.updateNavigation();
|
this.updateNavigation(true);
|
||||||
|
|
||||||
this.setLink(this.activeTopLevelObject.levelDefault);
|
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.resetNavigation();
|
this.resetNavigation();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useNavigationStore, [
|
...mapActions(useNavigationStore, ["resetNavigation", "updateTopLevel", "updateNavigation"]),
|
||||||
"setLink",
|
|
||||||
"resetNavigation",
|
|
||||||
"setTopLevel",
|
|
||||||
"updateTopLevel",
|
|
||||||
"updateNavigation",
|
|
||||||
]),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
3
src/views/admin/ViewSelect.vue
Normal file
3
src/views/admin/ViewSelect.vue
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<div class="w-full h-full bg-white rounded-md flex items-center justify-center">bitte auswählen</div>
|
||||||
|
</template>
|
Loading…
Reference in a new issue