Initial commit: Admin panel

This commit is contained in:
Noro Studio
2026-06-30 00:50:13 +03:30
commit 79491cd8ef
36 changed files with 6448 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
<template>
<div>
<div class="mb-6 flex items-center justify-between">
<h2 class="page-title">تنظیمات سایت</h2>
<button type="button" class="btn-primary" :disabled="saving" @click="save">
{{ saving ? 'در حال ذخیره...' : 'ذخیره تغییرات' }}
</button>
</div>
<p v-if="message" class="mb-4 rounded-lg bg-green-50 px-4 py-2 text-sm text-green-700">{{ message }}</p>
<p v-if="error" class="mb-4 rounded-lg bg-red-50 px-4 py-2 text-sm text-red-600">{{ error }}</p>
<div v-if="loading" class="text-slate-500">در حال بارگذاری...</div>
<div v-else class="space-y-6">
<section class="card space-y-4">
<h3 class="font-semibold text-slate-800">اطلاعات عمومی</h3>
<div class="grid gap-4 sm:grid-cols-2">
<div>
<label class="label">نام فارسی</label>
<input v-model="site.name" class="input" />
</div>
<div>
<label class="label">نام انگلیسی</label>
<input v-model="site.nameEn" class="input" dir="ltr" />
</div>
<div class="sm:col-span-2">
<label class="label">شعار</label>
<input v-model="site.tagline" class="input" />
</div>
<div class="sm:col-span-2">
<label class="label">توضیحات</label>
<textarea v-model="site.description" class="input min-h-[100px]" rows="4" />
</div>
</div>
</section>
<section class="card space-y-4">
<h3 class="font-semibold text-slate-800">تماس</h3>
<div class="grid gap-4 sm:grid-cols-2">
<div>
<label class="label">ایمیل</label>
<input v-model="site.email" type="email" class="input" dir="ltr" />
</div>
<div>
<label class="label">وب‌سایت</label>
<input v-model="site.website" class="input" dir="ltr" />
</div>
<div>
<label class="label">تلفن</label>
<input v-model="site.phone" class="input" />
</div>
<div>
<label class="label">موقعیت</label>
<input v-model="site.location" class="input" />
</div>
</div>
</section>
<section class="card space-y-4">
<h3 class="font-semibold text-slate-800">سئو پیش‌فرض</h3>
<div class="grid gap-4">
<div>
<label class="label">عنوان پیش‌فرض</label>
<input v-model="seo.defaultTitle" class="input" />
</div>
<div>
<label class="label">توضیحات پیش‌فرض</label>
<textarea v-model="seo.defaultDescription" class="input min-h-[80px]" rows="3" />
</div>
</div>
</section>
<section class="card space-y-4">
<div class="flex items-center justify-between">
<h3 class="font-semibold text-slate-800">منوی ناوبری</h3>
<button type="button" class="btn-secondary text-xs" @click="addNavItem">افزودن آیتم</button>
</div>
<div v-for="(item, i) in navigation" :key="i" class="flex gap-2">
<input v-model="item.label" class="input flex-1" placeholder="برچسب" />
<input v-model="item.to" class="input flex-1" placeholder="/path" dir="ltr" />
<button type="button" class="btn-danger px-3" @click="navigation.splice(i, 1)">حذف</button>
</div>
<button type="button" class="btn-secondary" :disabled="savingNav" @click="saveNavigation">
{{ savingNav ? '...' : 'ذخیره منو' }}
</button>
</section>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { api, type NavItem, type SiteSettings } from '@/api/client'
const loading = ref(true)
const saving = ref(false)
const savingNav = ref(false)
const message = ref('')
const error = ref('')
const site = reactive<SiteSettings>({})
const seo = reactive({ defaultTitle: '', defaultDescription: '' })
const navigation = ref<NavItem[]>([])
onMounted(async () => {
try {
const [siteData, nav] = await Promise.all([api.getSite(), api.getNavigation()])
Object.assign(site, siteData)
seo.defaultTitle = siteData.seo?.defaultTitle || ''
seo.defaultDescription = siteData.seo?.defaultDescription || ''
navigation.value = [...nav]
} catch (e) {
error.value = e instanceof Error ? e.message : 'خطا در بارگذاری'
} finally {
loading.value = false
}
})
function addNavItem() {
navigation.value.push({ label: '', to: '' })
}
async function save() {
saving.value = true
message.value = ''
error.value = ''
try {
site.seo = { defaultTitle: seo.defaultTitle, defaultDescription: seo.defaultDescription }
await api.putSite({ ...site })
message.value = 'تنظیمات سایت ذخیره شد.'
} catch (e) {
error.value = e instanceof Error ? e.message : 'خطا در ذخیره'
} finally {
saving.value = false
}
}
async function saveNavigation() {
savingNav.value = true
error.value = ''
try {
await api.putNavigation(navigation.value)
message.value = 'منوی ناوبری ذخیره شد.'
} catch (e) {
error.value = e instanceof Error ? e.message : 'خطا در ذخیره منو'
} finally {
savingNav.value = false
}
}
</script>