feat: complete UI/UX improvements, vuesax icons, media server, and section management
This commit is contained in:
@@ -8,6 +8,7 @@ import VuesaxIcon from "@/components/VuesaxIcon.vue";
|
||||
import type { SectionResponse } from "@/api/types";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import { useZodForm } from "@/composables/useZodForm";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { jalali, SITE_URL } from "@/utils/format";
|
||||
import AboutForm from "@/sections/AboutForm.vue";
|
||||
import ContactForm from "@/sections/ContactForm.vue";
|
||||
@@ -37,6 +38,7 @@ const FORMS: Record<SectionKey, Component> = {
|
||||
footer: FooterForm,
|
||||
};
|
||||
|
||||
const auth = useAuthStore();
|
||||
const route = useRoute();
|
||||
const key = String(route.params.key);
|
||||
const valid = isSectionKey(key);
|
||||
@@ -55,7 +57,11 @@ const label = computed(() => (valid ? SECTION_LABELS[sectionKey] : null));
|
||||
async function load() {
|
||||
try {
|
||||
const res = await api<SectionResponse>(`/admin/sections/${sectionKey}`);
|
||||
reset(res.data as never);
|
||||
const data = res.data as any;
|
||||
if (sectionKey !== "settings" && data.enabled === undefined) {
|
||||
data.enabled = true;
|
||||
}
|
||||
reset(data as never);
|
||||
updatedAt.value = res.updatedAt;
|
||||
loaded.value = true;
|
||||
} catch (e) {
|
||||
@@ -79,6 +85,33 @@ async function save() {
|
||||
}
|
||||
}
|
||||
|
||||
const togglingVisibility = ref(false);
|
||||
|
||||
async function onVisibilityChange(val: any) {
|
||||
const nextVal = Boolean(val);
|
||||
(model.value as any).enabled = nextVal;
|
||||
togglingVisibility.value = true;
|
||||
try {
|
||||
const res = await api<{ key: string; enabled: boolean; updatedAt: string }>(
|
||||
`/admin/sections/${sectionKey}/visibility`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: { enabled: nextVal },
|
||||
},
|
||||
);
|
||||
(model.value as any).enabled = res.enabled !== false;
|
||||
updatedAt.value = res.updatedAt;
|
||||
void message.success(
|
||||
`وضعیت نمایش به ${nextVal ? "نمایش" : "عدم نمایش"} تغییر یافت`,
|
||||
);
|
||||
} catch (e) {
|
||||
(model.value as any).enabled = !nextVal;
|
||||
void message.error(errorMessage(e));
|
||||
} finally {
|
||||
togglingVisibility.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) void load();
|
||||
</script>
|
||||
|
||||
@@ -90,16 +123,81 @@ if (valid) void load();
|
||||
:subtitle="updatedAt ? `آخرین ذخیره: ${jalali(updatedAt)}` : label!.desc"
|
||||
back="/sections"
|
||||
>
|
||||
<a :href="SITE_URL" target="_blank" rel="noopener"><a-button>مشاهده سایت</a-button></a>
|
||||
<a-button type="primary" :loading="saving" :disabled="!loaded" @click="save">
|
||||
<div v-if="sectionKey !== 'settings'" class="max-lg:hidden flex items-center gap-2 me-2">
|
||||
<span class="text-sm font-medium text-gray-700">نمایش در سایت:</span>
|
||||
<a-switch
|
||||
:checked="(model as any)?.enabled !== false"
|
||||
:loading="togglingVisibility"
|
||||
checked-children="نمایش"
|
||||
un-checked-children="مخفی"
|
||||
@change="onVisibilityChange"
|
||||
/>
|
||||
</div>
|
||||
<a :href="SITE_URL" target="_blank" rel="noopener" class="max-lg:hidden"><a-button>مشاهده سایت</a-button></a>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="max-lg:hidden"
|
||||
:loading="saving"
|
||||
:disabled="!loaded || (sectionKey === 'settings' && !auth.isAdmin)"
|
||||
@click="save"
|
||||
>
|
||||
<template #icon><VuesaxIcon name="save-2" :size="16" /></template>
|
||||
ذخیره
|
||||
</a-button>
|
||||
</PageHeader>
|
||||
<a-spin :spinning="!loaded">
|
||||
<a-form v-if="loaded" layout="vertical" @submit.prevent="save">
|
||||
<a-alert
|
||||
v-if="loaded && sectionKey === 'settings' && !auth.isAdmin"
|
||||
type="error"
|
||||
show-icon
|
||||
message="فقط مدیر کل میتواند تنظیمات سایت را تغییر دهد."
|
||||
class="mb-4"
|
||||
/>
|
||||
<a-alert
|
||||
v-else-if="loaded && sectionKey !== 'settings' && (model as any).enabled === false"
|
||||
type="warning"
|
||||
show-icon
|
||||
message="این بخش در حال حاضر در سایت نمایش داده نمیشود (مخفی است)."
|
||||
class="mb-4"
|
||||
/>
|
||||
<a-form v-if="loaded" layout="vertical" class="pb-20 lg:pb-0" @submit.prevent="save">
|
||||
<component :is="FORMS[sectionKey]" v-model="model" />
|
||||
</a-form>
|
||||
</a-spin>
|
||||
|
||||
<!-- Mobile Sticky Bottom Action Bar -->
|
||||
<div class="lg:hidden fixed bottom-0 inset-x-0 z-30 border-t border-gray-200 bg-white/95 px-4 py-2.5 backdrop-blur-md shadow-lg flex items-center justify-between gap-3">
|
||||
<div v-if="sectionKey !== 'settings'" class="flex items-center gap-2">
|
||||
<a-switch
|
||||
:checked="(model as any)?.enabled !== false"
|
||||
:loading="togglingVisibility"
|
||||
size="small"
|
||||
@change="onVisibilityChange"
|
||||
/>
|
||||
<span class="text-xs font-medium text-gray-700">
|
||||
{{ (model as any)?.enabled !== false ? 'نمایش در سایت' : 'مخفی' }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-else class="text-xs text-gray-500 font-medium">تنظیمات پایه</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<a :href="SITE_URL" target="_blank" rel="noopener">
|
||||
<a-button size="middle" class="!px-3" title="مشاهده سایت">
|
||||
<template #icon><VuesaxIcon name="eye" :size="16" /></template>
|
||||
</a-button>
|
||||
</a>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="middle"
|
||||
class="!font-bold !px-5"
|
||||
:loading="saving"
|
||||
:disabled="!loaded || (sectionKey === 'settings' && !auth.isAdmin)"
|
||||
@click="save"
|
||||
>
|
||||
<template #icon><VuesaxIcon name="save-2" :size="16" /></template>
|
||||
ذخیره تغییرات
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user