111 lines
3.6 KiB
Vue
111 lines
3.6 KiB
Vue
<template>
|
|||
|
|
<div class="flex min-h-screen bg-slate-50">
|
||
|
|
<!-- Sidebar -->
|
||
|
|
<aside class="fixed inset-y-0 right-0 z-30 flex w-64 flex-col border-l border-slate-200 bg-white shadow-sm">
|
||
|
|
<div class="flex items-center gap-3 border-b border-slate-100 px-5 py-5">
|
||
|
|
<div class="flex h-10 w-10 items-center justify-center rounded-xl bg-brand text-lg font-bold text-white">
|
||
|
|
ن
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<p class="text-sm font-bold text-slate-900">استودیو نورو</p>
|
||
|
|
<p class="text-xs text-slate-500">پنل مدیریت CMS</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<nav class="flex-1 space-y-1 overflow-y-auto p-3">
|
||
|
|
<RouterLink
|
||
|
|
v-for="item in navItems"
|
||
|
|
:key="item.name"
|
||
|
|
:to="{ name: item.name }"
|
||
|
|
class="flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors"
|
||
|
|
:class="isActive(item.name)
|
||
|
|
? 'bg-brand/10 text-brand-700'
|
||
|
|
: 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'"
|
||
|
|
>
|
||
|
|
<span class="text-lg" aria-hidden="true">{{ item.icon }}</span>
|
||
|
|
{{ item.label }}
|
||
|
|
</RouterLink>
|
||
|
|
</nav>
|
||
|
|
|
||
|
|
<div class="border-t border-slate-100 p-4">
|
||
|
|
<p class="truncate text-xs text-slate-500">{{ userEmail }}</p>
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
class="mt-2 w-full rounded-lg px-3 py-2 text-right text-sm text-red-600 transition-colors hover:bg-red-50"
|
||
|
|
@click="logout"
|
||
|
|
>
|
||
|
|
خروج از حساب
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</aside>
|
||
|
|
|
||
|
|
<!-- Main -->
|
||
|
|
<div class="mr-64 flex min-h-screen flex-1 flex-col">
|
||
|
|
<header class="sticky top-0 z-20 border-b border-slate-200 bg-white/90 px-8 py-4 backdrop-blur">
|
||
|
|
<h1 class="text-lg font-semibold text-slate-800">{{ pageTitle }}</h1>
|
||
|
|
</header>
|
||
|
|
<main class="flex-1 p-8">
|
||
|
|
<RouterView />
|
||
|
|
</main>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { computed, onMounted, ref } from 'vue'
|
||
|
|
import { useRoute, useRouter } from 'vue-router'
|
||
|
|
import { api, clearToken } from '@/api/client'
|
||
|
|
|
||
|
|
const route = useRoute()
|
||
|
|
const router = useRouter()
|
||
|
|
const userEmail = ref('')
|
||
|
|
|
||
|
|
const navItems = [
|
||
|
|
{ name: 'dashboard', label: 'داشبورد', icon: '📊' },
|
||
|
|
{ name: 'site', label: 'تنظیمات سایت', icon: '⚙️' },
|
||
|
|
{ name: 'portfolio', label: 'نمونهکارها', icon: '🎨' },
|
||
|
|
{ name: 'services', label: 'خدمات', icon: '💼' },
|
||
|
|
{ name: 'clients', label: 'مشتریان', icon: '🤝' },
|
||
|
|
{ name: 'blog', label: 'وبلاگ', icon: '📝' },
|
||
|
|
{ name: 'media', label: 'رسانه', icon: '🖼️' },
|
||
|
|
]
|
||
|
|
|
||
|
|
const titles: Record<string, string> = {
|
||
|
|
dashboard: 'داشبورد',
|
||
|
|
site: 'تنظیمات سایت',
|
||
|
|
portfolio: 'نمونهکارها',
|
||
|
|
'portfolio-edit': 'ویرایش نمونهکار',
|
||
|
|
services: 'خدمات',
|
||
|
|
'service-edit': 'ویرایش خدمت',
|
||
|
|
clients: 'مشتریان',
|
||
|
|
blog: 'وبلاگ',
|
||
|
|
'blog-edit': 'ویرایش مطلب',
|
||
|
|
media: 'کتابخانه رسانه',
|
||
|
|
}
|
||
|
|
|
||
|
|
const pageTitle = computed(() => titles[String(route.name)] || 'پنل مدیریت')
|
||
|
|
|
||
|
|
function isActive(name: string) {
|
||
|
|
if (route.name === name) return true
|
||
|
|
if (name === 'portfolio' && route.name === 'portfolio-edit') return true
|
||
|
|
if (name === 'services' && route.name === 'service-edit') return true
|
||
|
|
if (name === 'blog' && route.name === 'blog-edit') return true
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
function logout() {
|
||
|
|
clearToken()
|
||
|
|
router.push({ name: 'login' })
|
||
|
|
}
|
||
|
|
|
||
|
|
onMounted(async () => {
|
||
|
|
try {
|
||
|
|
const { user } = await api.me()
|
||
|
|
userEmail.value = user.email
|
||
|
|
} catch {
|
||
|
|
clearToken()
|
||
|
|
router.push({ name: 'login' })
|
||
|
|
}
|
||
|
|
})
|
||
|
|
</script>
|