0
0
Fork 0

Route acls and added LeftMenu component

This commit is contained in:
Joan Sanchez 2022-03-15 10:33:28 +01:00
parent 49b5ed8003
commit 760c89aff9
19 changed files with 213 additions and 232 deletions

View File

@ -0,0 +1,63 @@
<script lang="ts" setup>
import { ref, Ref } from 'vue';
import { RouteRecordRaw } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useRole } from 'src/composables/useRole';
import routes from 'src/router/routes';
const { t } = useI18n();
const { hasAny } = useRole();
const mainRoute = routes.find(route => route.path === '/');
const modules: RouteRecordRaw[] = mainRoute && mainRoute.children || []
interface MenuItem {
path: string;
title?: string;
icon?: string;
roles: string[];
}
interface ItemMeta {
title?: string;
icon?: string;
roles?: string[];
}
const items: Ref<MenuItem[]> = ref([]);
for (const module of modules) {
const item: MenuItem = {
path: module.path,
roles: []
};
if (module.meta) {
const meta: ItemMeta = module.meta;
item.title = meta.title;
item.icon = meta.icon;
item.roles = meta.roles || [];
}
items.value.push(item);
}
</script>
<template>
<q-list padding>
<template v-for="route in items" :key="route.title">
<q-item
clickable
v-ripple
:to="{ path: route.path }"
v-if="!route.roles.length || hasAny(route.roles)"
active-class="text-orange"
>
<q-item-section avatar :if="route.icon">
<q-icon :name="route.icon" />
</q-item-section>
<q-item-section>{{ t(`pages.${route.title}`) }}</q-item-section>
</q-item>
</template>
</q-list>
</template>

View File

@ -1,12 +0,0 @@
<template>
<q-btn data-cy="button" label="test emit" color="positive" rounded icon="edit" @click="$emit('test')" />
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'QuasarButton',
emits: ['test'],
});
</script>

View File

@ -1,65 +0,0 @@
<template>
<!-- notice dialogRef here -->
<q-dialog ref="dialogRef" @hide="onDialogHide" data-cy="dialog">
<q-card class="q-dialog-plugin">
<q-card-section>{{ message }}</q-card-section>
<!-- buttons example -->
<q-card-actions align="right">
<q-btn data-cy="ok-button" color="primary" label="OK" @click="onOKClick" />
<q-btn color="primary" label="Cancel" @click="onCancelClick" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script>
import { useDialogPluginComponent } from 'quasar';
import { defineComponent } from 'vue';
export default defineComponent({
name: 'QuasarDialog',
props: {
message: {
type: String,
required: true,
},
},
// REQUIRED; need to specify some events that your
// component will emit through useDialogPluginComponent()
emits: useDialogPluginComponent.emits,
setup() {
// REQUIRED; must be called inside of setup()
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent();
// dialogRef - Vue ref to be applied to QDialog
// onDialogHide - Function to be used as handler for @hide on QDialog
// onDialogOK - Function to call to settle dialog with "ok" outcome
// example: onDialogOK() - no payload
// example: onDialogOK({ /*.../* }) - with payload
// onDialogCancel - Function to call to settle dialog with "cancel" outcome
return {
// This is REQUIRED;
// Need to inject these (from useDialogPluginComponent() call)
// into the vue scope for the vue html template
dialogRef,
onDialogHide,
// other methods that we used in our vue html template;
// these are part of our example (so not required)
onOKClick() {
// on OK, it is REQUIRED to
// call onDialogOK (with optional payload)
onDialogOK();
// or with payload: onDialogOK({ ... })
// ...and it will also hide the dialog automatically
},
// we can passthrough onDialogCancel directly
onCancelClick: onDialogCancel,
};
},
});
</script>

View File

@ -1,33 +0,0 @@
<template>
<q-drawer
v-model="showDrawer"
show-if-above
:width="200"
:breakpoint="700"
elevated
data-cy="drawer"
class="bg-primary text-white"
>
<q-scroll-area class="fit">
<div class="q-pa-sm">
<div v-for="n in 50" :key="n">Drawer {{ n }} / 50</div>
</div>
<q-btn data-cy="button">Am I on screen?</q-btn>
</q-scroll-area>
</q-drawer>
</template>
<script>
import { ref, defineComponent } from 'vue';
export default defineComponent({
name: 'QuasarDrawer',
setup() {
const showDrawer = ref(true);
return {
showDrawer,
};
},
});
</script>

View File

@ -1,21 +0,0 @@
<template>
<q-page-sticky position="bottom-right" :offset="[18, 18]">
<q-btn data-cy="button" rounded color="accent" icon="arrow_forward">
{{ title }}
</q-btn>
</q-page-sticky>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'QuasarPageSticky',
props: {
title: {
type: String,
required: true,
},
},
});
</script>

View File

@ -1,21 +0,0 @@
<template>
<q-btn color="primary" data-cy="button">
Button
<q-tooltip v-model="showTooltip" data-cy="tooltip" class="bg-red" :offset="[10, 10]"> Here I am! </q-tooltip>
</q-btn>
</template>
<script>
import { ref, defineComponent } from 'vue';
export default defineComponent({
name: 'QuasarTooltip',
setup() {
const showTooltip = ref(true);
return {
showTooltip,
};
},
});
</script>

View File

@ -11,7 +11,12 @@
true-value="en"
v-model="locale"
/>
<q-toggle v-model="darkMode" checked-icon="dark_mode" color="orange" unchecked-icon="light_mode" />
<q-toggle
v-model="darkMode"
checked-icon="dark_mode"
color="orange"
unchecked-icon="light_mode"
/>
<q-btn color="orange" outline size="sm" label="Settings" icon="settings" />
</div>
@ -31,7 +36,15 @@
</div>
<div class="text-subtitle3 text-grey-7 q-mb-xs">@{{ user.username }}</div>
<q-btn color="orange" flat label="Log Out" size="sm" icon="logout" @click="logout()" v-close-popup />
<q-btn
color="orange"
flat
label="Log Out"
size="sm"
icon="logout"
@click="logout()"
v-close-popup
/>
</div>
</div>
</q-menu>
@ -65,6 +78,15 @@ const darkMode = computed({
const user = state.getUser();
const token = session.getToken();
interface Role {
name: string
}
interface RoleList {
roleId: number;
role: Role
}
onMounted(() => {
axios
.get('/api/accounts/acl')
@ -74,6 +96,13 @@ onMounted(() => {
username: data.user.name,
nickname: data.user.nickname,
});
if (data.roles) {
const roleList: RoleList[] = data.roles;
const roles: string[] = roleList.map(item => item.role.name);
state.setRoles(roles);
}
})
.catch(() => {
quasar.notify({

View File

@ -1,11 +1,13 @@
/* import store from '@/store';
import { ComputedRef } from 'vue';
import { useState } from './useState';
export function useRole() {
function hasAny(roles: string[]): boolean {
const roleStore: string[] = store.state.roles;
const { getRoles } = useState();
const roleStore: ComputedRef<string[]> = getRoles();
for (const role of roles) {
if (roleStore.indexOf(role) !== -1) return true;
if (roleStore.value.indexOf(role) !== -1) return true;
}
return false;
@ -15,4 +17,4 @@ export function useRole() {
hasAny,
};
}
*/

View File

@ -31,5 +31,7 @@ export default {
dashboard: 'Dashboard',
customers: 'Customers',
list: 'List',
basicData: 'Basic Data',
tickets: 'Tickets',
},
};

View File

@ -12,51 +12,7 @@
:breakpoint="500"
>
<q-scroll-area class="fit text-grey-8">
<q-list padding>
<q-item clickable v-ripple :to="{ path: '/dashboard' }" active-class="text-orange">
<q-item-section avatar>
<q-icon name="dashboard" />
</q-item-section>
<q-item-section>Dashboard</q-item-section>
</q-item>
<q-item clickable v-ripple :to="{ path: '/customer' }" active-class="text-orange">
<q-item-section avatar>
<q-icon name="people" />
</q-item-section>
<q-item-section>Customers</q-item-section>
</q-item>
<q-item clickable v-ripple :to="{ path: '/ticket' }" active-class="text-orange">
<q-item-section avatar>
<q-icon name="vn:ticket" />
</q-item-section>
<q-item-section>Tickets</q-item-section>
</q-item>
<q-item clickable v-ripple>
<q-item-section avatar>
<q-icon name="receipt" />
</q-item-section>
<q-item-section>Invoice Out</q-item-section>
</q-item>
<q-item clickable v-ripple>
<q-item-section avatar>
<q-icon name="shopping_cart" />
</q-item-section>
<q-item-section>Catalog</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-ripple>
<q-item-section avatar>
<q-icon name="drafts" />
</q-item-section>
<q-item-section>Drafts</q-item-section>
</q-item>
</q-list>
<LeftMenu />
</q-scroll-area>
</q-drawer>
<q-page-container>
@ -68,6 +24,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
import Navbar from 'src/components/Navbar.vue';
import LeftMenu from 'src/components/LeftMenu.vue';
const drawer = ref(false);
const miniState = ref(true);

View File

@ -0,0 +1,3 @@
<template>
<q-card>asd</q-card>
</template>

View File

@ -9,7 +9,7 @@
</div>
<div class="col">
<q-card>asd</q-card>
<router-view></router-view>
</div>
</div>
</template>

View File

View File

@ -0,0 +1,3 @@
<template>
<router-view></router-view>
</template>

View File

@ -3,7 +3,10 @@ import { createMemoryHistory, createRouter, createWebHashHistory, createWebHisto
import routes from './routes';
import { i18n } from 'src/boot/i18n';
import { useSession } from 'src/composables/useSession';
import { useRole } from 'src/composables/useRole';
const session = useSession();
const role = useRole();
/*
* If not building with SSR mode, you can
* directly export the Router instantiation;
@ -13,6 +16,12 @@ const session = useSession();
* with the Router instance.
*/
interface ItemMeta {
title?: string;
icon?: string;
roles?: string[];
}
export default route(function (/* { store, ssrContext } */) {
const createHistory = process.env.SERVER
? createMemoryHistory
@ -36,7 +45,20 @@ export default route(function (/* { store, ssrContext } */) {
if (!isLoggedIn && to.name !== 'Login') {
next({ path: '/login', query: { redirect: to.fullPath } });
} else {
next();
const pathRoutes = to.matched;
const droles = pathRoutes.every(route => {
const meta = route.meta as ItemMeta;
if (meta.roles)
return role.hasAny(meta.roles)
return true;
});
if (droles) {
next();
} else {
next({ path: '/' });
}
}
});
@ -48,20 +70,31 @@ export default route(function (/* { store, ssrContext } */) {
const { t } = i18n.global;
let title = '';
if (to.matched && to.matched.length > 2) {
const parent = to.matched[1];
if (parent) {
const parentMeta: Meta = parent.meta;
if (parentMeta && parentMeta.title) {
title += t(`pages.${parentMeta.title}`);
}
}
//const childTitle: string = childMeta.title;
const childMeta: Meta = to.meta;
if (childMeta && childMeta.title) {
if (title != '') title += ' - ';
if (title != '') title += ': ';
title += t(`pages.${childMeta.title}`);
const childTitle = t(`pages.${childMeta.title}`);
if (to.params && to.params.id) {
const idParam = to.params.id;
if (idParam instanceof Array) {
title += `${idParam[0]} - ${childTitle}`
} else {
title += `${idParam} - ${childTitle}`;
}
} else {
title += t(`pages.${childMeta.title}`);
}
}
document.title = title;

View File

@ -0,0 +1,34 @@
export default {
path: '/customer',
name: 'Customer',
meta: {
title: 'customers',
icon: 'vn:client'
},
component: () => import('src/pages/Customer/Customer.vue'),
redirect: { path: '/customer/list' },
children: [
{
path: '/customer/list',
meta: {
title: 'list'
},
component: () => import('src/pages/Customer/List.vue'),
},
{
path: '/customer/:id',
component: () => import('src/pages/Customer/Card/Card.vue'),
redirect: { name: 'BasicData' },
children: [
{
path: 'basic-data',
name: 'BasicData',
meta: {
title: 'basicData'
},
component: () => import('src/pages/Customer/Card/BasicData.vue'),
}
]
},
],
};

View File

@ -0,0 +1,18 @@
export default {
path: '/ticket',
name: 'Ticket',
meta: {
title: 'tickets',
icon: 'vn:ticket',
},
component: () => import('src/pages/Ticket/Ticket.vue'),
redirect: { path: '/ticket/list'},
children: [
{
path: 'list',
name: 'List',
meta: { title: 'list' },
component: () => import('src/pages/Ticket/List.vue'),
}
],
};

View File

@ -1,4 +1,6 @@
import { RouteRecordRaw } from 'vue-router';
import customer from './modules/customer';
import ticket from './modules/ticket';
const routes: RouteRecordRaw[] = [
{
@ -16,34 +18,17 @@ const routes: RouteRecordRaw[] = [
{
path: '/dashboard',
name: 'Dashboard',
meta: { title: 'dashboard' },
meta: { title: 'dashboard', icon: 'dashboard' },
component: () => import('../pages/Dashboard/Dashboard.vue'),
},
{
path: '/customer',
name: 'Customer',
meta: { title: 'customers' },
component: () => import('../pages/Customer/Customer.vue'),
redirect: { name: 'List' },
children: [
{
path: 'list',
name: 'List',
meta: { title: 'list' },
component: () => import('../pages/Customer/List.vue'),
},
{
path: ':id',
name: 'Card',
component: () => import('../pages/Customer/Card/Card.vue'),
},
],
},
{
/* {
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('../pages/NotFound.vue'),
},
}, */
// Module routes
customer,
ticket,
],
},
];

4
src/types/route.ts Normal file
View File

@ -0,0 +1,4 @@
export interface Route {
path: string;
component: any;
}