198 lines
7.5 KiB
Vue
198 lines
7.5 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">تصاویر Hero</h3>
|
||
<ImageField v-model="hero.darkImageUrl" label="پسزمینه تم تیره" />
|
||
<ImageField v-model="hero.lightImageUrl" label="پسزمینه تم روشن" />
|
||
</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="addMessenger">افزودن</button>
|
||
</div>
|
||
<div v-for="(messenger, i) in messengers" :key="messenger.id || i" class="space-y-4 rounded-xl border border-slate-100 p-4">
|
||
<div class="grid gap-4 sm:grid-cols-2">
|
||
<div>
|
||
<label class="label">نام</label>
|
||
<input v-model="messenger.name" class="input" />
|
||
</div>
|
||
<div>
|
||
<label class="label">لینک</label>
|
||
<input v-model="messenger.url" class="input" dir="ltr" />
|
||
</div>
|
||
</div>
|
||
<ImageField v-model="messenger.iconUrl" :label="`آیکون ${messenger.name || 'پیامرسان'}`" preview-class="h-12 w-12 object-contain p-1" />
|
||
<button type="button" class="text-sm text-red-500" @click="messengers.splice(i, 1)">حذف پیامرسان</button>
|
||
</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'
|
||
import ImageField from '@/components/ImageField.vue'
|
||
|
||
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[]>([])
|
||
const hero = reactive({ darkImageUrl: '', lightImageUrl: '' })
|
||
const messengers = ref<Array<{ id: string; name: string; url: string; iconUrl?: string }>>([])
|
||
|
||
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]
|
||
hero.darkImageUrl = siteData.hero?.darkImageUrl || ''
|
||
hero.lightImageUrl = siteData.hero?.lightImageUrl || ''
|
||
messengers.value = siteData.messengers?.length
|
||
? siteData.messengers.map((m) => ({ ...m }))
|
||
: []
|
||
} catch (e) {
|
||
error.value = e instanceof Error ? e.message : 'خطا در بارگذاری'
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
})
|
||
|
||
function addNavItem() {
|
||
navigation.value.push({ label: '', to: '' })
|
||
}
|
||
|
||
function addMessenger() {
|
||
messengers.value.push({
|
||
id: `messenger-${Date.now()}`,
|
||
name: '',
|
||
url: '',
|
||
iconUrl: '',
|
||
})
|
||
}
|
||
|
||
async function save() {
|
||
saving.value = true
|
||
message.value = ''
|
||
error.value = ''
|
||
try {
|
||
site.seo = { defaultTitle: seo.defaultTitle, defaultDescription: seo.defaultDescription }
|
||
site.hero = { darkImageUrl: hero.darkImageUrl, lightImageUrl: hero.lightImageUrl }
|
||
site.messengers = messengers.value
|
||
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>
|