|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
|
|
|
|
|
<head>
|
|
|
|
|
<meta name="description" content="Default Layout">
|
|
|
|
|
<title>VBase</title>
|
|
|
|
|
<style>
|
|
|
|
|
.layout-container {
|
|
|
|
|
display: flex;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
width: 100vw;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
background-color: var(--bg-color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-container {
|
|
|
|
|
height: 100%;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
border-right: 1px solid var(--border-color);
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
transition: width 0.3s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-content {
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
background-color: var(--bg-color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header {
|
|
|
|
|
height: 60px;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
border-bottom: 1px solid var(--border-color);
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 0 20px;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header-left {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header-right {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 15px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content-body {
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.logo {
|
|
|
|
|
height: 60px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
color: var(--color-primary);
|
|
|
|
|
border-bottom: 1px solid var(--border-color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.org-switcher {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 5px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
padding: 5px 10px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
transition: background 0.2s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.org-switcher:hover {
|
|
|
|
|
background-color: var(--bg-color-tertiary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-profile {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.breadcrumb {
|
|
|
|
|
color: var(--text-color-secondary);
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
|
|
|
|
|
<body>
|
|
|
|
|
<div class="layout-container">
|
|
|
|
|
<!-- Sidebar -->
|
|
|
|
|
<div class="sidebar-container" :style="{width: collapsed ? '64px' : '240px'}">
|
|
|
|
|
<v-sidebar :items="menuItems" :collapsed="collapsed" width="240px" collapsedWidth="64px">
|
|
|
|
|
<div vslot="header" class="logo">
|
|
|
|
|
<span v-if="!collapsed">VBase</span>
|
|
|
|
|
<span v-else>VB</span>
|
|
|
|
|
</div>
|
|
|
|
|
</v-sidebar>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Main Content -->
|
|
|
|
|
<div class="main-content">
|
|
|
|
|
<!-- Header -->
|
|
|
|
|
<header class="header">
|
|
|
|
|
<div class="header-left">
|
|
|
|
|
<div @click="toggleCollapse" style="cursor: pointer;">
|
|
|
|
|
<i class="fas" :class="collapsed ? 'fa-indent' : 'fa-outdent'"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="breadcrumb">
|
|
|
|
|
{{ currentRouteName }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="header-right">
|
|
|
|
|
<!-- Org Switcher -->
|
|
|
|
|
<div class="org-switcher" @click="openOrgSwitch" v-if="currentOrg">
|
|
|
|
|
<i class="fas fa-building"></i>
|
|
|
|
|
<span>{{ currentOrg.name }}</span>
|
|
|
|
|
<i class="fas fa-chevron-down" style="font-size: 12px;"></i>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<v-lang></v-lang>
|
|
|
|
|
|
|
|
|
|
<!-- User Profile -->
|
|
|
|
|
<div class="user-profile" @click="goToProfile">
|
|
|
|
|
<i class="fas fa-user-circle" style="font-size: 24px;"></i>
|
|
|
|
|
<span>{{ user ? user.nickname || user.username : 'User' }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div @click="logout" style="cursor: pointer; color: var(--color-danger);" title="Logout">
|
|
|
|
|
<i class="fas fa-sign-out-alt"></i>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<!-- Page Content -->
|
|
|
|
|
<vslot class="content-body">
|
|
|
|
|
</vslot>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</body>
|
|
|
|
|
<script setup>
|
|
|
|
|
collapsed = false;
|
|
|
|
|
user = $env.$vbase.user;
|
|
|
|
|
currentOrg = $env.$vbase.currentOrg;
|
|
|
|
|
|
|
|
|
|
// Define Menu Items
|
|
|
|
|
menuItems = [
|
|
|
|
|
{label: $t('nav.dashboard'), icon: "<i class='fas fa-tachometer-alt'></i>", path: "/"},
|
|
|
|
|
{label: $t('nav.org'), icon: "<i class='fas fa-sitemap'></i>", path: "/org"},
|
|
|
|
|
{label: $t('nav.profile'), icon: "<i class='fas fa-user'></i>", path: "/profile"},
|
|
|
|
|
// Admin only items would be filtered here ideally
|
|
|
|
|
{label: $t('nav.users'), icon: "<i class='fas fa-users-cog'></i>", path: "/users"},
|
|
|
|
|
{label: $t('nav.oauth'), icon: "<i class='fas fa-key'></i>", path: "/oauth/apps"},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
currentRouteName = "";
|
|
|
|
|
|
|
|
|
|
toggleCollapse = () => {
|
|
|
|
|
collapsed = !collapsed;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
logout = () => {
|
|
|
|
|
$env.$vbase.logout();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
goToProfile = () => {
|
|
|
|
|
$router.push('/profile');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
openOrgSwitch = () => {
|
|
|
|
|
// Simple alert for now, should be a modal or dropdown
|
|
|
|
|
// In vhtml we can use $message or navigate to org list
|
|
|
|
|
$router.push('/org');
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
<script>
|
|
|
|
|
$watch(() => $env.$route.path, () => {
|
|
|
|
|
// Update breadcrumb or active item
|
|
|
|
|
// v-sidebar handles active state via path matching usually
|
|
|
|
|
// We can update title based on route name
|
|
|
|
|
// For now just simple mapping or rely on $route
|
|
|
|
|
// $data.currentRouteName = $env.$route.name || $env.$route.path
|
|
|
|
|
// Simple implementation:
|
|
|
|
|
const path = $env.$route.path;
|
|
|
|
|
const item = $data.menuItems.find(i => i.path === path);
|
|
|
|
|
$data.currentRouteName = item ? item.label : path;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Watch global state for user/org changes
|
|
|
|
|
$watch(() => [$env.$vbase.user, $env.$i18n.locale], () => {
|
|
|
|
|
$data.user = $env.$vbase.user;
|
|
|
|
|
// Re-generate menu items when locale changes
|
|
|
|
|
$data.menuItems = [
|
|
|
|
|
{label: $t('nav.dashboard'), icon: "<i class='fas fa-tachometer-alt'></i>", path: "/"},
|
|
|
|
|
{label: $t('nav.org'), icon: "<i class='fas fa-sitemap'></i>", path: "/org"},
|
|
|
|
|
{label: $t('nav.profile'), icon: "<i class='fas fa-user'></i>", path: "/profile"},
|
|
|
|
|
// Admin only items would be filtered here ideally
|
|
|
|
|
{label: $t('nav.users'), icon: "<i class='fas fa-users-cog'></i>", path: "/users"},
|
|
|
|
|
{label: $t('nav.oauth'), icon: "<i class='fas fa-key'></i>", path: "/oauth/apps"},
|
|
|
|
|
];
|
|
|
|
|
});
|
|
|
|
|
$watch(() => $env.$vbase.currentOrg, () => {
|
|
|
|
|
$data.currentOrg = $env.$vbase.currentOrg;
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
</html>
|