Files
admin/src/views/SiteSettingsView.vue
T

198 lines
7.5 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>