feat: replace gear dropdown with direct settings link
Remove the gear dropdown menu from AppHeader; cog icon is now a plain router-link to /settings. Admin-only /admin/users and /admin/logs routes are replaced with redirects to /settings. Mobile menu admin links removed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import { useTheme } from "@/composables/useTheme";
|
import { useTheme } from "@/composables/useTheme";
|
||||||
import { useShortcuts } from "@/composables/useShortcuts";
|
import { useShortcuts } from "@/composables/useShortcuts";
|
||||||
@@ -17,8 +17,6 @@ const route = useRoute();
|
|||||||
const isChatActive = computed(() => route.path.startsWith("/chat"));
|
const isChatActive = computed(() => route.path.startsWith("/chat"));
|
||||||
|
|
||||||
const mobileMenuOpen = ref(false);
|
const mobileMenuOpen = ref(false);
|
||||||
const gearOpen = ref(false);
|
|
||||||
const gearMenuRef = ref<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
const statusShortLabel = computed(() => {
|
const statusShortLabel = computed(() => {
|
||||||
if (chatStore.ollamaStatus === "unavailable") return "Offline";
|
if (chatStore.ollamaStatus === "unavailable") return "Offline";
|
||||||
@@ -51,16 +49,6 @@ function toggleMobileMenu() {
|
|||||||
mobileMenuOpen.value = !mobileMenuOpen.value;
|
mobileMenuOpen.value = !mobileMenuOpen.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleGear() {
|
|
||||||
gearOpen.value = !gearOpen.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClickOutside(e: MouseEvent) {
|
|
||||||
if (gearMenuRef.value && !gearMenuRef.value.contains(e.target as Node)) {
|
|
||||||
gearOpen.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleLogout() {
|
async function handleLogout() {
|
||||||
await authStore.logout();
|
await authStore.logout();
|
||||||
mobileMenuOpen.value = false;
|
mobileMenuOpen.value = false;
|
||||||
@@ -69,11 +57,7 @@ async function handleLogout() {
|
|||||||
|
|
||||||
router.afterEach(() => {
|
router.afterEach(() => {
|
||||||
mobileMenuOpen.value = false;
|
mobileMenuOpen.value = false;
|
||||||
gearOpen.value = false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => document.addEventListener("click", handleClickOutside));
|
|
||||||
onUnmounted(() => document.removeEventListener("click", handleClickOutside));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -107,20 +91,13 @@ onUnmounted(() => document.removeEventListener("click", handleClickOutside));
|
|||||||
{{ theme === "dark" ? "\u2600" : "\u263E" }}
|
{{ theme === "dark" ? "\u2600" : "\u263E" }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Gear dropdown -->
|
<!-- Settings link -->
|
||||||
<div class="gear-menu" ref="gearMenuRef">
|
<router-link to="/settings" class="btn-icon" aria-label="Settings" title="Settings">
|
||||||
<button class="btn-icon btn-gear" @click.stop="toggleGear" :class="{ active: gearOpen }" title="Settings">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<circle cx="12" cy="12" r="3"/>
|
<circle cx="12" cy="12" r="3"/>
|
||||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</router-link>
|
||||||
<div v-if="gearOpen" class="gear-dropdown">
|
|
||||||
<router-link to="/settings" class="gear-item">Settings</router-link>
|
|
||||||
<router-link v-if="authStore.isAdmin" to="/admin/users" class="gear-item">Users</router-link>
|
|
||||||
<router-link v-if="authStore.isAdmin" to="/admin/logs" class="gear-item">Logs</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<span class="username">{{ authStore.user?.username }}</span>
|
<span class="username">{{ authStore.user?.username }}</span>
|
||||||
@@ -146,8 +123,6 @@ onUnmounted(() => document.removeEventListener("click", handleClickOutside));
|
|||||||
<router-link to="/graph" class="nav-link">Graph</router-link>
|
<router-link to="/graph" class="nav-link">Graph</router-link>
|
||||||
<div class="mobile-divider"></div>
|
<div class="mobile-divider"></div>
|
||||||
<router-link to="/settings" class="nav-link">Settings</router-link>
|
<router-link to="/settings" class="nav-link">Settings</router-link>
|
||||||
<router-link v-if="authStore.isAdmin" to="/admin/users" class="nav-link">Users</router-link>
|
|
||||||
<router-link v-if="authStore.isAdmin" to="/admin/logs" class="nav-link">Logs</router-link>
|
|
||||||
<div class="mobile-divider"></div>
|
<div class="mobile-divider"></div>
|
||||||
<div class="mobile-actions">
|
<div class="mobile-actions">
|
||||||
<span class="status-indicator" :class="statusClass" :title="statusLabel">
|
<span class="status-indicator" :class="statusClass" :title="statusLabel">
|
||||||
@@ -281,40 +256,6 @@ onUnmounted(() => document.removeEventListener("click", handleClickOutside));
|
|||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Gear dropdown */
|
|
||||||
.gear-menu {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.gear-dropdown {
|
|
||||||
position: absolute;
|
|
||||||
top: calc(100% + 6px);
|
|
||||||
right: 0;
|
|
||||||
background: var(--color-bg-card);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
box-shadow: 0 4px 16px var(--color-shadow, rgba(0,0,0,0.15));
|
|
||||||
min-width: 140px;
|
|
||||||
z-index: 200;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.gear-item {
|
|
||||||
padding: 0.55rem 0.9rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
text-decoration: none;
|
|
||||||
transition: background 0.12s, color 0.12s;
|
|
||||||
}
|
|
||||||
.gear-item:hover {
|
|
||||||
background: var(--color-bg-secondary);
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
.gear-item.router-link-active {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* User info */
|
/* User info */
|
||||||
.user-info {
|
.user-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -110,16 +110,8 @@ const router = createRouter({
|
|||||||
name: "settings",
|
name: "settings",
|
||||||
component: () => import("@/views/SettingsView.vue"),
|
component: () => import("@/views/SettingsView.vue"),
|
||||||
},
|
},
|
||||||
{
|
{ path: "/admin/users", redirect: "/settings" },
|
||||||
path: "/admin/users",
|
{ path: "/admin/logs", redirect: "/settings" },
|
||||||
name: "admin-users",
|
|
||||||
component: () => import("@/views/UserManagementView.vue"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/admin/logs",
|
|
||||||
name: "admin-logs",
|
|
||||||
component: () => import("@/views/LogsView.vue"),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user