forked from verdnatura/salix-front
Route acls and added LeftMenu component
This commit is contained in:
parent
49b5ed8003
commit
760c89aff9
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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({
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
|
|
|
@ -31,5 +31,7 @@ export default {
|
|||
dashboard: 'Dashboard',
|
||||
customers: 'Customers',
|
||||
list: 'List',
|
||||
basicData: 'Basic Data',
|
||||
tickets: 'Tickets',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
<q-card>asd</q-card>
|
||||
</template>
|
|
@ -9,7 +9,7 @@
|
|||
</div>
|
||||
|
||||
<div class="col">
|
||||
<q-card>asd</q-card>
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
|
@ -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 {
|
||||
|
||||
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,21 +70,32 @@ 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 += ': ';
|
||||
|
||||
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;
|
||||
});
|
||||
|
|
|
@ -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'),
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
};
|
|
@ -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'),
|
||||
}
|
||||
],
|
||||
};
|
|
@ -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,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export interface Route {
|
||||
path: string;
|
||||
component: any;
|
||||
}
|
Loading…
Reference in New Issue