init: чистый старт Laravel + Vuexy
This commit is contained in:
45
resources/ts/layouts/blank.vue
Normal file
45
resources/ts/layouts/blank.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script lang="ts" setup>
|
||||
const { injectSkinClasses } = useSkins()
|
||||
|
||||
// ℹ️ This will inject classes in body tag for accurate styling
|
||||
injectSkinClasses()
|
||||
|
||||
// SECTION: Loading Indicator
|
||||
const isFallbackStateActive = ref(false)
|
||||
const refLoadingIndicator = ref<any>(null)
|
||||
|
||||
// watching if the fallback state is active and the refLoadingIndicator component is available
|
||||
watch([isFallbackStateActive, refLoadingIndicator], () => {
|
||||
if (isFallbackStateActive.value && refLoadingIndicator.value)
|
||||
refLoadingIndicator.value.fallbackHandle()
|
||||
|
||||
if (!isFallbackStateActive.value && refLoadingIndicator.value)
|
||||
refLoadingIndicator.value.resolveHandle()
|
||||
}, { immediate: true })
|
||||
// !SECTION
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLoadingIndicator ref="refLoadingIndicator" />
|
||||
|
||||
<div
|
||||
class="layout-wrapper layout-blank"
|
||||
data-allow-mismatch
|
||||
>
|
||||
<RouterView #="{Component}">
|
||||
<Suspense
|
||||
:timeout="0"
|
||||
@fallback="isFallbackStateActive = true"
|
||||
@resolve="isFallbackStateActive = false"
|
||||
>
|
||||
<Component :is="Component" />
|
||||
</Suspense>
|
||||
</RouterView>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.layout-wrapper.layout-blank {
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
<script lang="ts" setup>
|
||||
import navItems from '@/navigation/horizontal'
|
||||
|
||||
import { themeConfig } from '@themeConfig'
|
||||
|
||||
// Components
|
||||
import Footer from '@/layouts/components/Footer.vue'
|
||||
import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue'
|
||||
import UserProfile from '@/layouts/components/UserProfile.vue'
|
||||
import NavBarI18n from '@core/components/I18n.vue'
|
||||
import { HorizontalNavLayout } from '@layouts'
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HorizontalNavLayout :nav-items="navItems">
|
||||
<!-- 👉 navbar -->
|
||||
<template #navbar>
|
||||
<RouterLink
|
||||
to="/"
|
||||
class="app-logo d-flex align-center gap-x-3"
|
||||
>
|
||||
<VNodeRenderer :nodes="themeConfig.app.logo" />
|
||||
|
||||
<h1 class="app-title font-weight-bold leading-normal text-xl text-capitalize">
|
||||
{{ themeConfig.app.title }}
|
||||
</h1>
|
||||
</RouterLink>
|
||||
<VSpacer />
|
||||
|
||||
<NavBarI18n
|
||||
v-if="themeConfig.app.i18n.enable && themeConfig.app.i18n.langConfig?.length"
|
||||
:languages="themeConfig.app.i18n.langConfig"
|
||||
/>
|
||||
|
||||
<NavbarThemeSwitcher class="me-2" />
|
||||
<UserProfile />
|
||||
</template>
|
||||
|
||||
<!-- 👉 Pages -->
|
||||
<slot />
|
||||
|
||||
<!-- 👉 Footer -->
|
||||
<template #footer>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<!-- 👉 Customizer -->
|
||||
<!-- <TheCustomizer /> -->
|
||||
</HorizontalNavLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,54 @@
|
||||
<script lang="ts" setup>
|
||||
import navItems from '@/navigation/vertical'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
|
||||
// Components
|
||||
import Footer from '@/layouts/components/Footer.vue'
|
||||
import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue'
|
||||
import UserProfile from '@/layouts/components/UserProfile.vue'
|
||||
import NavBarI18n from '@core/components/I18n.vue'
|
||||
|
||||
// @layouts plugin
|
||||
import { VerticalNavLayout } from '@layouts'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VerticalNavLayout :nav-items="navItems">
|
||||
<!-- 👉 navbar -->
|
||||
<template #navbar="{ toggleVerticalOverlayNavActive }">
|
||||
<div class="d-flex h-100 align-center">
|
||||
<IconBtn
|
||||
id="vertical-nav-toggle-btn"
|
||||
class="ms-n3 d-lg-none"
|
||||
@click="toggleVerticalOverlayNavActive(true)"
|
||||
>
|
||||
<VIcon
|
||||
size="26"
|
||||
icon="tabler-menu-2"
|
||||
/>
|
||||
</IconBtn>
|
||||
|
||||
<NavbarThemeSwitcher />
|
||||
|
||||
<VSpacer />
|
||||
|
||||
<NavBarI18n
|
||||
v-if="themeConfig.app.i18n.enable && themeConfig.app.i18n.langConfig?.length"
|
||||
:languages="themeConfig.app.i18n.langConfig"
|
||||
/>
|
||||
<UserProfile />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 👉 Pages -->
|
||||
<slot />
|
||||
|
||||
<!-- 👉 Footer -->
|
||||
<template #footer>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<!-- 👉 Customizer -->
|
||||
<!-- <TheCustomizer /> -->
|
||||
</VerticalNavLayout>
|
||||
</template>
|
||||
41
resources/ts/layouts/components/Footer.vue
Normal file
41
resources/ts/layouts/components/Footer.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="h-100 d-flex align-center justify-md-space-between justify-center">
|
||||
<!-- 👉 Footer: left content -->
|
||||
<span class="d-flex align-center text-medium-emphasis">
|
||||
©
|
||||
{{ new Date().getFullYear() }}
|
||||
Made With
|
||||
<VIcon
|
||||
icon="tabler-heart-filled"
|
||||
color="error"
|
||||
size="1.25rem"
|
||||
class="mx-1"
|
||||
/>
|
||||
By <a
|
||||
href="https://pixinvent.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary ms-1"
|
||||
>Pixinvent</a>
|
||||
</span>
|
||||
<!-- 👉 Footer: right content -->
|
||||
<span class="d-md-flex gap-x-4 text-primary d-none">
|
||||
<a
|
||||
href="https://themeforest.net/licenses/standard"
|
||||
target="noopener noreferrer"
|
||||
>License</a>
|
||||
<a
|
||||
href="https://1.envato.market/pixinvent_portfolio"
|
||||
target="noopener noreferrer"
|
||||
>More Themes</a>
|
||||
<a
|
||||
href="https://demos.pixinvent.com/vuexy-vuejs-admin-template/documentation/guide/laravel-integration/folder-structure.html"
|
||||
target="noopener noreferrer"
|
||||
>Documentation</a>
|
||||
<a
|
||||
href="https://pixinvent.ticksy.com/"
|
||||
target="noopener noreferrer"
|
||||
>Support</a>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
92
resources/ts/layouts/components/NavBarNotifications.vue
Normal file
92
resources/ts/layouts/components/NavBarNotifications.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Notification } from '@layouts/types'
|
||||
|
||||
import avatar3 from '@images/avatars/avatar-3.png'
|
||||
import avatar4 from '@images/avatars/avatar-4.png'
|
||||
import avatar5 from '@images/avatars/avatar-5.png'
|
||||
import paypal from '@images/cards/paypal-rounded.png'
|
||||
|
||||
const notifications = ref<Notification[]>([
|
||||
{
|
||||
id: 1,
|
||||
img: avatar4,
|
||||
title: 'Congratulation Flora! 🎉',
|
||||
subtitle: 'Won the monthly best seller badge',
|
||||
time: 'Today',
|
||||
isSeen: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: 'Tom Holland',
|
||||
title: 'New user registered.',
|
||||
subtitle: '5 hours ago',
|
||||
time: 'Yesterday',
|
||||
isSeen: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
img: avatar5,
|
||||
title: 'New message received 👋🏻',
|
||||
subtitle: 'You have 10 unread messages',
|
||||
time: '11 Aug',
|
||||
isSeen: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
img: paypal,
|
||||
title: 'PayPal',
|
||||
subtitle: 'Received Payment',
|
||||
time: '25 May',
|
||||
isSeen: false,
|
||||
color: 'error',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
img: avatar3,
|
||||
title: 'Received Order 📦',
|
||||
subtitle: 'New order received from john',
|
||||
time: '19 Mar',
|
||||
isSeen: true,
|
||||
},
|
||||
])
|
||||
|
||||
const removeNotification = (notificationId: number) => {
|
||||
notifications.value.forEach((item, index) => {
|
||||
if (notificationId === item.id)
|
||||
notifications.value.splice(index, 1)
|
||||
})
|
||||
}
|
||||
|
||||
const markRead = (notificationId: number[]) => {
|
||||
notifications.value.forEach(item => {
|
||||
notificationId.forEach(id => {
|
||||
if (id === item.id)
|
||||
item.isSeen = true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const markUnRead = (notificationId: number[]) => {
|
||||
notifications.value.forEach(item => {
|
||||
notificationId.forEach(id => {
|
||||
if (id === item.id)
|
||||
item.isSeen = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handleNotificationClick = (notification: Notification) => {
|
||||
if (!notification.isSeen)
|
||||
markRead([notification.id])
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Notifications
|
||||
:notifications="notifications"
|
||||
@remove="removeNotification"
|
||||
@read="markRead"
|
||||
@unread="markUnRead"
|
||||
@click:notification="handleNotificationClick"
|
||||
/>
|
||||
</template>
|
||||
266
resources/ts/layouts/components/NavSearchBar.vue
Normal file
266
resources/ts/layouts/components/NavSearchBar.vue
Normal file
@@ -0,0 +1,266 @@
|
||||
<script setup lang="ts">
|
||||
import Shepherd from 'shepherd.js'
|
||||
import { withQuery } from 'ufo'
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
import type { SearchResults } from '@db/app-bar-search/types'
|
||||
import { useConfigStore } from '@core/stores/config'
|
||||
|
||||
interface Suggestion {
|
||||
icon: string
|
||||
title: string
|
||||
url: RouteLocationRaw
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const configStore = useConfigStore()
|
||||
|
||||
interface SuggestionGroup {
|
||||
title: string
|
||||
content: Suggestion[]
|
||||
}
|
||||
|
||||
// 👉 Is App Search Bar Visible
|
||||
const isAppSearchBarVisible = ref(false)
|
||||
const isLoading = ref(false)
|
||||
|
||||
// 👉 Default suggestions
|
||||
|
||||
const suggestionGroups: SuggestionGroup[] = [
|
||||
{
|
||||
title: 'Popular Searches',
|
||||
content: [
|
||||
{ icon: 'tabler-chart-bar', title: 'Analytics', url: { name: 'dashboards-analytics' } },
|
||||
{ icon: 'tabler-chart-donut-3', title: 'CRM', url: { name: 'dashboards-crm' } },
|
||||
{ icon: 'tabler-shopping-cart', title: 'eCommerce', url: { name: 'dashboards-ecommerce' } },
|
||||
{ icon: 'tabler-truck', title: 'Logistics', url: { name: 'dashboards-logistics' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Apps & Pages',
|
||||
content: [
|
||||
{ icon: 'tabler-calendar', title: 'Calendar', url: { name: 'apps-calendar' } },
|
||||
{ icon: 'tabler-lock', title: 'Roles & Permissions', url: { name: 'apps-roles' } },
|
||||
{ icon: 'tabler-settings', title: 'Account Settings', url: { name: 'pages-account-settings-tab', params: { tab: 'account' } } },
|
||||
{ icon: 'tabler-copy', title: 'Dialog Examples', url: { name: 'pages-dialog-examples' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'User Interface',
|
||||
content: [
|
||||
{ icon: 'tabler-typography', title: 'Typography', url: { name: 'pages-typography' } },
|
||||
{ icon: 'tabler-menu-2', title: 'Accordion', url: { name: 'components-expansion-panel' } },
|
||||
{ icon: 'tabler-info-triangle', title: 'Alert', url: { name: 'components-alert' } },
|
||||
{ icon: 'tabler-checkbox', title: 'Cards', url: { name: 'pages-cards-card-basic' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Forms & Tables',
|
||||
content: [
|
||||
{ icon: 'tabler-circle-dot', title: 'Radio', url: { name: 'forms-radio' } },
|
||||
{ icon: 'tabler-file-invoice', title: 'Form Layouts', url: { name: 'forms-form-layouts' } },
|
||||
{ icon: 'tabler-table', title: 'Table', url: { name: 'tables-data-table' } },
|
||||
{ icon: 'tabler-edit', title: 'Editor', url: { name: 'forms-editors' } },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
// 👉 No Data suggestion
|
||||
const noDataSuggestions: Suggestion[] = [
|
||||
{
|
||||
title: 'Analytics',
|
||||
icon: 'tabler-chart-bar',
|
||||
url: { name: 'dashboards-analytics' },
|
||||
},
|
||||
{
|
||||
title: 'CRM',
|
||||
icon: 'tabler-chart-donut-3',
|
||||
url: { name: 'dashboards-crm' },
|
||||
},
|
||||
{
|
||||
title: 'eCommerce',
|
||||
icon: 'tabler-shopping-cart',
|
||||
url: { name: 'dashboards-ecommerce' },
|
||||
},
|
||||
]
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
const router = useRouter()
|
||||
const searchResult = ref<SearchResults[]>([])
|
||||
|
||||
const fetchResults = async () => {
|
||||
isLoading.value = true
|
||||
|
||||
const { data } = await useApi<any>(withQuery('/app-bar/search', { q: searchQuery.value }))
|
||||
|
||||
searchResult.value = data.value
|
||||
|
||||
// ℹ️ simulate loading: we have used setTimeout for better user experience your can remove it
|
||||
setTimeout(() => {
|
||||
isLoading.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
watch(searchQuery, fetchResults)
|
||||
|
||||
const closeSearchBar = () => {
|
||||
isAppSearchBarVisible.value = false
|
||||
searchQuery.value = ''
|
||||
}
|
||||
|
||||
// 👉 redirect the selected page
|
||||
const redirectToSuggestedPage = (selected: Suggestion) => {
|
||||
router.push(selected.url as string)
|
||||
closeSearchBar()
|
||||
}
|
||||
|
||||
const LazyAppBarSearch = defineAsyncComponent(() => import('@core/components/AppBarSearch.vue'))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="d-flex align-center cursor-pointer"
|
||||
v-bind="$attrs"
|
||||
style="user-select: none;"
|
||||
@click="isAppSearchBarVisible = !isAppSearchBarVisible"
|
||||
>
|
||||
<!-- 👉 Search Trigger button -->
|
||||
<!-- close active tour while opening search bar using icon -->
|
||||
<IconBtn @click="Shepherd.activeTour?.cancel()">
|
||||
<VIcon icon="tabler-search" />
|
||||
</IconBtn>
|
||||
|
||||
<span
|
||||
v-if="configStore.appContentLayoutNav === 'vertical'"
|
||||
class="d-none d-md-flex align-center text-disabled ms-2"
|
||||
@click="Shepherd.activeTour?.cancel()"
|
||||
>
|
||||
<span class="me-2">Search</span>
|
||||
<span class="meta-key">⌘K</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 👉 App Bar Search -->
|
||||
<LazyAppBarSearch
|
||||
v-model:is-dialog-visible="isAppSearchBarVisible"
|
||||
:search-results="searchResult"
|
||||
:is-loading="isLoading"
|
||||
@search="searchQuery = $event"
|
||||
>
|
||||
<!-- suggestion -->
|
||||
<template #suggestions>
|
||||
<VCardText class="app-bar-search-suggestions pa-12">
|
||||
<VRow v-if="suggestionGroups">
|
||||
<VCol
|
||||
v-for="suggestion in suggestionGroups"
|
||||
:key="suggestion.title"
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<p
|
||||
class="custom-letter-spacing text-disabled text-uppercase py-2 px-4 mb-0"
|
||||
style="font-size: 0.75rem; line-height: 0.875rem;"
|
||||
>
|
||||
{{ suggestion.title }}
|
||||
</p>
|
||||
<VList class="card-list">
|
||||
<VListItem
|
||||
v-for="item in suggestion.content"
|
||||
:key="item.title"
|
||||
class="app-bar-search-suggestion mx-4 mt-2"
|
||||
@click="redirectToSuggestedPage(item)"
|
||||
>
|
||||
<VListItemTitle>{{ item.title }}</VListItemTitle>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
:icon="item.icon"
|
||||
size="20"
|
||||
class="me-n1"
|
||||
/>
|
||||
</template>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</template>
|
||||
|
||||
<!-- no data suggestion -->
|
||||
<template #noDataSuggestion>
|
||||
<div class="mt-9">
|
||||
<span class="d-flex justify-center text-disabled mb-2">Try searching for</span>
|
||||
<h6
|
||||
v-for="suggestion in noDataSuggestions"
|
||||
:key="suggestion.title"
|
||||
class="app-bar-search-suggestion text-h6 font-weight-regular cursor-pointer py-2 px-4"
|
||||
@click="redirectToSuggestedPage(suggestion)"
|
||||
>
|
||||
<VIcon
|
||||
size="20"
|
||||
:icon="suggestion.icon"
|
||||
class="me-2"
|
||||
/>
|
||||
<span>{{ suggestion.title }}</span>
|
||||
</h6>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- search result -->
|
||||
<template #searchResult="{ item }">
|
||||
<VListSubheader class="text-disabled custom-letter-spacing font-weight-regular ps-4">
|
||||
{{ item.title }}
|
||||
</VListSubheader>
|
||||
<VListItem
|
||||
v-for="list in item.children"
|
||||
:key="list.title"
|
||||
:to="list.url"
|
||||
@click="closeSearchBar"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
size="20"
|
||||
:icon="list.icon"
|
||||
class="me-n1"
|
||||
/>
|
||||
</template>
|
||||
<template #append>
|
||||
<VIcon
|
||||
size="20"
|
||||
icon="tabler-corner-down-left"
|
||||
class="enter-icon flip-in-rtl"
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle>
|
||||
{{ list.title }}
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
</template>
|
||||
</LazyAppBarSearch>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@styles/variables/vuetify.scss";
|
||||
|
||||
.meta-key {
|
||||
border: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||
border-radius: 6px;
|
||||
block-size: 1.5625rem;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.3125rem;
|
||||
padding-block: 0.125rem;
|
||||
padding-inline: 0.25rem;
|
||||
}
|
||||
|
||||
.app-bar-search-dialog {
|
||||
.custom-letter-spacing {
|
||||
letter-spacing: 0.8px;
|
||||
}
|
||||
|
||||
.card-list {
|
||||
--v-card-list-gap: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
45
resources/ts/layouts/components/NavbarShortcuts.vue
Normal file
45
resources/ts/layouts/components/NavbarShortcuts.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
const shortcuts = [
|
||||
{
|
||||
icon: 'tabler-calendar',
|
||||
title: 'Calendar',
|
||||
subtitle: 'Appointments',
|
||||
to: { name: 'apps-calendar' },
|
||||
},
|
||||
{
|
||||
icon: 'tabler-file-dollar',
|
||||
title: 'Invoice App',
|
||||
subtitle: 'Manage Accounts',
|
||||
to: { name: 'apps-invoice-list' },
|
||||
},
|
||||
{
|
||||
icon: 'tabler-user',
|
||||
title: 'Users',
|
||||
subtitle: 'Manage Users',
|
||||
to: { name: 'apps-user-list' },
|
||||
},
|
||||
{
|
||||
icon: 'tabler-users',
|
||||
title: 'Role Management',
|
||||
subtitle: 'Permission',
|
||||
to: { name: 'apps-roles' },
|
||||
},
|
||||
{
|
||||
icon: 'tabler-device-desktop-analytics',
|
||||
title: 'Dashboard',
|
||||
subtitle: 'Dashboard Analytics',
|
||||
to: { name: 'dashboards-analytics' },
|
||||
},
|
||||
{
|
||||
icon: 'tabler-settings',
|
||||
title: 'Settings',
|
||||
subtitle: 'Account Settings',
|
||||
to: { name: 'pages-account-settings-tab', params: { tab: 'account' } },
|
||||
},
|
||||
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Shortcuts :shortcuts="shortcuts" />
|
||||
</template>
|
||||
22
resources/ts/layouts/components/NavbarThemeSwitcher.vue
Normal file
22
resources/ts/layouts/components/NavbarThemeSwitcher.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import type { ThemeSwitcherTheme } from '@layouts/types'
|
||||
|
||||
const themes: ThemeSwitcherTheme[] = [
|
||||
{
|
||||
name: 'light',
|
||||
icon: 'tabler-sun-high',
|
||||
},
|
||||
{
|
||||
name: 'dark',
|
||||
icon: 'tabler-moon-stars',
|
||||
},
|
||||
{
|
||||
name: 'system',
|
||||
icon: 'tabler-device-desktop-analytics',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ThemeSwitcher :themes="themes" />
|
||||
</template>
|
||||
130
resources/ts/layouts/components/UserProfile.vue
Normal file
130
resources/ts/layouts/components/UserProfile.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<script setup lang="ts">
|
||||
import avatar1 from '@images/avatars/avatar-1.png'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VBadge
|
||||
dot
|
||||
location="bottom right"
|
||||
offset-x="3"
|
||||
offset-y="3"
|
||||
bordered
|
||||
color="success"
|
||||
>
|
||||
<VAvatar
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
>
|
||||
<VImg :src="avatar1" />
|
||||
|
||||
<!-- SECTION Menu -->
|
||||
<VMenu
|
||||
activator="parent"
|
||||
width="230"
|
||||
location="bottom end"
|
||||
offset="14px"
|
||||
>
|
||||
<VList>
|
||||
<!-- 👉 User Avatar & Name -->
|
||||
<VListItem>
|
||||
<template #prepend>
|
||||
<VListItemAction start>
|
||||
<VBadge
|
||||
dot
|
||||
location="bottom right"
|
||||
offset-x="3"
|
||||
offset-y="3"
|
||||
color="success"
|
||||
>
|
||||
<VAvatar
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
>
|
||||
<VImg :src="avatar1" />
|
||||
</VAvatar>
|
||||
</VBadge>
|
||||
</VListItemAction>
|
||||
</template>
|
||||
|
||||
<VListItemTitle class="font-weight-semibold">
|
||||
John Doe
|
||||
</VListItemTitle>
|
||||
<VListItemSubtitle>Admin</VListItemSubtitle>
|
||||
</VListItem>
|
||||
|
||||
<VDivider class="my-2" />
|
||||
|
||||
<!-- 👉 Profile -->
|
||||
<VListItem link>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="tabler-user"
|
||||
size="22"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<VListItemTitle>Profile</VListItemTitle>
|
||||
</VListItem>
|
||||
|
||||
<!-- 👉 Settings -->
|
||||
<VListItem link>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="tabler-settings"
|
||||
size="22"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<VListItemTitle>Settings</VListItemTitle>
|
||||
</VListItem>
|
||||
|
||||
<!-- 👉 Pricing -->
|
||||
<VListItem link>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="tabler-currency-dollar"
|
||||
size="22"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<VListItemTitle>Pricing</VListItemTitle>
|
||||
</VListItem>
|
||||
|
||||
<!-- 👉 FAQ -->
|
||||
<VListItem link>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="tabler-help"
|
||||
size="22"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<VListItemTitle>FAQ</VListItemTitle>
|
||||
</VListItem>
|
||||
|
||||
<!-- Divider -->
|
||||
<VDivider class="my-2" />
|
||||
|
||||
<!-- 👉 Logout -->
|
||||
<VListItem to="/login">
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="tabler-logout"
|
||||
size="22"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<VListItemTitle>Logout</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
<!-- !SECTION -->
|
||||
</VAvatar>
|
||||
</VBadge>
|
||||
</template>
|
||||
56
resources/ts/layouts/default.vue
Normal file
56
resources/ts/layouts/default.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<script lang="ts" setup>
|
||||
import { useConfigStore } from '@core/stores/config'
|
||||
import { AppContentLayoutNav } from '@layouts/enums'
|
||||
import { switchToVerticalNavOnLtOverlayNavBreakpoint } from '@layouts/utils'
|
||||
|
||||
const DefaultLayoutWithHorizontalNav = defineAsyncComponent(() => import('./components/DefaultLayoutWithHorizontalNav.vue'))
|
||||
const DefaultLayoutWithVerticalNav = defineAsyncComponent(() => import('./components/DefaultLayoutWithVerticalNav.vue'))
|
||||
|
||||
const configStore = useConfigStore()
|
||||
|
||||
// ℹ️ This will switch to vertical nav when define breakpoint is reached when in horizontal nav layout
|
||||
// Remove below composable usage if you are not using horizontal nav layout in your app
|
||||
switchToVerticalNavOnLtOverlayNavBreakpoint()
|
||||
|
||||
const { layoutAttrs, injectSkinClasses } = useSkins()
|
||||
|
||||
injectSkinClasses()
|
||||
|
||||
// SECTION: Loading Indicator
|
||||
const isFallbackStateActive = ref(false)
|
||||
const refLoadingIndicator = ref<any>(null)
|
||||
|
||||
// watching if the fallback state is active and the refLoadingIndicator component is available
|
||||
watch([isFallbackStateActive, refLoadingIndicator], () => {
|
||||
if (isFallbackStateActive.value && refLoadingIndicator.value)
|
||||
refLoadingIndicator.value.fallbackHandle()
|
||||
|
||||
if (!isFallbackStateActive.value && refLoadingIndicator.value)
|
||||
refLoadingIndicator.value.resolveHandle()
|
||||
}, { immediate: true })
|
||||
// !SECTION
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Component
|
||||
v-bind="layoutAttrs"
|
||||
:is="configStore.appContentLayoutNav === AppContentLayoutNav.Vertical ? DefaultLayoutWithVerticalNav : DefaultLayoutWithHorizontalNav"
|
||||
>
|
||||
<AppLoadingIndicator ref="refLoadingIndicator" />
|
||||
|
||||
<RouterView v-slot="{ Component }">
|
||||
<Suspense
|
||||
:timeout="0"
|
||||
@fallback="isFallbackStateActive = true"
|
||||
@resolve="isFallbackStateActive = false"
|
||||
>
|
||||
<Component :is="Component" />
|
||||
</Suspense>
|
||||
</RouterView>
|
||||
</Component>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
// As we are using `layouts` plugin we need its styles to be imported
|
||||
@use "@layouts/styles/default-layout";
|
||||
</style>
|
||||
Reference in New Issue
Block a user