152 lines
5.4 KiB
Vue
152 lines
5.4 KiB
Vue
<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>
|