fix(gallery): resolve image zoom stretching and add rich viewer features
deploy-chabokan / deploy (push) Canceled after 0s

This commit is contained in:
2026-08-30 11:25:31 +03:30
parent 7c56d2e66a
commit 195d0300a0
48 changed files with 2448 additions and 2163 deletions
+23 -139
View File
@@ -96,7 +96,8 @@ const handleSeedDatabase = () => {
if (seedStatus.value.isSeeded) return;
confirm.require({
message: 'توجه: این عملیات داده‌های اولیه سیستم (کاربران پیش‌فرض، نقش‌ها، دسته‌بندی‌ها، ویژگی‌های تکمیلی و وضعیت‌های املاک) را در پایگاه داده ایجاد می‌کند. این عملیات فقط یک‌بار قابل اجراست و پس از انجام دکمه برای همیشه قفل خواهد شد. آیا ادامه می‌دهید؟',
message:
'توجه: این عملیات داده‌های اولیه سیستم (کاربران پیش‌فرض، نقش‌ها، دسته‌بندی‌ها، ویژگی‌های تکمیلی و وضعیت‌های املاک) را در پایگاه داده ایجاد می‌کند. این عملیات فقط یک‌بار قابل اجراست و پس از انجام دکمه برای همیشه قفل خواهد شد. آیا ادامه می‌دهید؟',
header: 'تایید مقداردهی اولیه پایگاه داده (Seed Database)',
icon: 'pi pi-exclamation-triangle',
rejectProps: { label: 'انصراف', severity: 'secondary', outlined: true },
@@ -237,9 +238,7 @@ const navigateToModule = (routePath) => {
</div>
<div>
<h2 class="text-2xl font-bold m-0 text-surface-900 dark:text-surface-0">تنظیمات موتور جستجو</h2>
<p class="text-surface-500 dark:text-surface-400 m-0 mt-1 text-sm">
انتخاب روش جستجو (MongoDB یا Typesense) برای هر بخش از سیستم به صورت مجزا
</p>
<p class="text-surface-500 dark:text-surface-400 m-0 mt-1 text-sm">انتخاب روش جستجو (MongoDB یا Typesense) برای هر بخش از سیستم به صورت مجزا</p>
</div>
</div>
@@ -267,33 +266,15 @@ const navigateToModule = (routePath) => {
<div class="flex items-start gap-4">
<div
class="w-14 h-14 rounded-2xl flex items-center justify-center text-2xl flex-shrink-0 transition-colors"
:class="seedStatus.isSeeded
? 'bg-surface-100 text-surface-500 dark:bg-surface-800 dark:text-surface-400'
: 'bg-indigo-100 text-indigo-600 dark:bg-indigo-950 dark:text-indigo-400 shadow-sm'"
:class="seedStatus.isSeeded ? 'bg-surface-100 text-surface-500 dark:bg-surface-800 dark:text-surface-400' : 'bg-indigo-100 text-indigo-600 dark:bg-indigo-950 dark:text-indigo-400 shadow-sm'"
>
<i :class="seedStatus.isSeeded ? 'pi pi-lock' : 'pi pi-database'"></i>
</div>
<div>
<div class="flex items-center gap-2.5 flex-wrap">
<h3 class="text-xl font-bold m-0 text-surface-900 dark:text-surface-0">
مقداردهی اولیه پایگاه داده (Seed Database)
</h3>
<Tag
v-if="seedStatus.isSeeded"
value="مقداردهی شده و قفل است"
severity="success"
icon="pi pi-lock"
rounded
class="px-2.5 text-xs font-bold"
/>
<Tag
v-else
value="آماده مقداردهی (یک‌بار مصرف)"
severity="warn"
icon="pi pi-exclamation-circle"
rounded
class="px-2.5 text-xs font-bold"
/>
<h3 class="text-xl font-bold m-0 text-surface-900 dark:text-surface-0">مقداردهی اولیه پایگاه داده (Seed Database)</h3>
<Tag v-if="seedStatus.isSeeded" value="مقداردهی شده و قفل است" severity="success" icon="pi pi-lock" rounded class="px-2.5 text-xs font-bold" />
<Tag v-else value="آماده مقداردهی (یک‌بار مصرف)" severity="warn" icon="pi pi-exclamation-circle" rounded class="px-2.5 text-xs font-bold" />
</div>
<p class="text-surface-500 dark:text-surface-400 m-0 mt-1.5 text-sm leading-relaxed max-w-2xl">
ایجاد خودکار کاربران اولیه، نقش‌ها و سطوح دسترسی، دسته‌بندی‌های املاک، ویژگی‌های تکمیلی و وضعیت‌های املاک در دیتابیس. این عملیات صرفاً یک‌بار قابل اجراست و پس از انجام دکمه برای همیشه قفل می‌شود.
@@ -306,25 +287,8 @@ const navigateToModule = (routePath) => {
</div>
<div class="flex items-center gap-3 flex-shrink-0">
<Button
v-if="!seedStatus.isSeeded"
label="اجرای مقداردهی اولیه داده‌ها"
icon="pi pi-database"
severity="primary"
rounded
:loading="isSeeding"
@click="handleSeedDatabase"
class="px-5 font-semibold"
/>
<Button
v-else
label="پایگاه داده مقداردهی شده است"
icon="pi pi-lock"
severity="secondary"
rounded
disabled
class="px-5 font-semibold opacity-70 cursor-not-allowed"
/>
<Button v-if="!seedStatus.isSeeded" label="اجرای مقداردهی اولیه داده‌ها" icon="pi pi-database" severity="primary" rounded :loading="isSeeding" @click="handleSeedDatabase" class="px-5 font-semibold" />
<Button v-else label="پایگاه داده مقداردهی شده است" icon="pi pi-lock" severity="secondary" rounded disabled class="px-5 font-semibold opacity-70 cursor-not-allowed" />
</div>
</div>
</div>
@@ -338,15 +302,8 @@ const navigateToModule = (routePath) => {
</div>
<div>
<div class="flex items-center gap-2.5 flex-wrap">
<h3 class="text-xl font-bold m-0 text-surface-900 dark:text-surface-0">
تنظیمات ذخیره خودکار (Auto-Save)
</h3>
<Tag
:value="autoSaveSettings.enabled ? 'فعال' : 'غیرفعال'"
:severity="autoSaveSettings.enabled ? 'success' : 'secondary'"
rounded
class="px-2.5 text-xs font-bold"
/>
<h3 class="text-xl font-bold m-0 text-surface-900 dark:text-surface-0">تنظیمات ذخیره خودکار (Auto-Save)</h3>
<Tag :value="autoSaveSettings.enabled ? 'فعال' : 'غیرفعال'" :severity="autoSaveSettings.enabled ? 'success' : 'secondary'" rounded class="px-2.5 text-xs font-bold" />
</div>
<p class="text-surface-500 dark:text-surface-400 m-0 mt-1.5 text-sm leading-relaxed max-w-2xl">
پیکربندی ذخیره پس‌زمینه خودکار فرم‌ها و املاک. در صورت فعال بودن، تغییرات شما بدون نیاز به فشردن دستی دکمه ذخیره، در بازه‌های مشخص ذخیره شده و اعلان آن در گوشه صفحه نمایش داده می‌شود.
@@ -362,26 +319,10 @@ const navigateToModule = (routePath) => {
<div class="flex items-center gap-2">
<span class="text-xs font-medium text-surface-600 dark:text-surface-300">بازه زمانی:</span>
<Dropdown
v-model="autoSaveSettings.intervalSeconds"
:options="autoSaveIntervalOptions"
optionLabel="label"
optionValue="value"
:disabled="!autoSaveSettings.enabled"
class="!rounded-xl text-xs w-44"
/>
<Dropdown v-model="autoSaveSettings.intervalSeconds" :options="autoSaveIntervalOptions" optionLabel="label" optionValue="value" :disabled="!autoSaveSettings.enabled" class="!rounded-xl text-xs w-44" />
</div>
<Button
label="ذخیره تنظیمات"
icon="pi pi-check"
severity="primary"
rounded
size="small"
:loading="autoSaveSaving"
@click="saveAutoSaveSettings"
class="px-4 font-semibold"
/>
<Button label="ذخیره تنظیمات" icon="pi pi-check" severity="primary" rounded size="small" :loading="autoSaveSaving" @click="saveAutoSaveSettings" class="px-4 font-semibold" />
</div>
</div>
</div>
@@ -389,35 +330,11 @@ const navigateToModule = (routePath) => {
<!-- ── Quick Actions Toolbar ── -->
<div class="card p-4 mb-6 flex flex-wrap items-center justify-between gap-4">
<div class="flex flex-wrap items-center gap-3">
<Button
label="تنظیم همه روی MongoDB"
icon="pi pi-database"
severity="success"
outlined
size="small"
:disabled="loading || saving"
@click="setAllToMethod('mongo')"
/>
<Button
label="تنظیم همه روی Typesense"
icon="pi pi-bolt"
severity="warn"
outlined
size="small"
:disabled="loading || saving"
@click="setAllToMethod('typesense')"
/>
<Button label="تنظیم همه روی MongoDB" icon="pi pi-database" severity="success" outlined size="small" :disabled="loading || saving" @click="setAllToMethod('mongo')" />
<Button label="تنظیم همه روی Typesense" icon="pi pi-bolt" severity="warn" outlined size="small" :disabled="loading || saving" @click="setAllToMethod('typesense')" />
</div>
<div class="flex items-center gap-2">
<Button
icon="pi pi-refresh"
severity="secondary"
text
rounded
:loading="loading"
v-tooltip.top="'بارگذاری مجدد'"
@click="loadSettings"
/>
<Button icon="pi pi-refresh" severity="secondary" text rounded :loading="loading" v-tooltip.top="'بارگذاری مجدد'" @click="loadSettings" />
</div>
</div>
@@ -435,11 +352,7 @@ const navigateToModule = (routePath) => {
v-for="item in settings"
:key="item.pageKey"
class="card p-6 rounded-2xl border transition-all duration-200 hover:shadow-md flex flex-col justify-between"
:class="[
item.searchMethod === 'mongo'
? 'border-emerald-200 dark:border-emerald-900 bg-emerald-50/10'
: 'border-amber-200 dark:border-amber-900 bg-amber-50/10'
]"
:class="[item.searchMethod === 'mongo' ? 'border-emerald-200 dark:border-emerald-900 bg-emerald-50/10' : 'border-amber-200 dark:border-amber-900 bg-amber-50/10']"
>
<div>
<!-- Card Top Header -->
@@ -447,11 +360,7 @@ const navigateToModule = (routePath) => {
<div class="flex items-center gap-3">
<div
class="w-10 h-10 rounded-xl flex items-center justify-center text-lg"
:class="[
item.searchMethod === 'mongo'
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300'
: 'bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-300'
]"
:class="[item.searchMethod === 'mongo' ? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300' : 'bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-300']"
>
<i :class="item.icon || 'pi pi-file'"></i>
</div>
@@ -462,11 +371,7 @@ const navigateToModule = (routePath) => {
</div>
<!-- Persistence Badge -->
<Tag
:value="item.isPersisted ? 'ذخیره در دیتابیس' : 'پیش‌فرض'"
:severity="item.isPersisted ? 'success' : 'secondary'"
class="text-xs"
/>
<Tag :value="item.isPersisted ? 'ذخیره در دیتابیس' : 'پیش‌فرض'" :severity="item.isPersisted ? 'success' : 'secondary'" class="text-xs" />
</div>
<!-- Description -->
@@ -489,15 +394,7 @@ const navigateToModule = (routePath) => {
<div class="pt-4 border-t border-surface-200 dark:border-surface-700 flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-2">
<span class="text-xs font-semibold text-surface-600 dark:text-surface-400">موتور جستجو:</span>
<SelectButton
:modelValue="item.searchMethod"
:options="methodOptions"
optionLabel="label"
optionValue="value"
:allowEmpty="false"
size="small"
@update:modelValue="(val) => updateMethod(item.pageKey, val)"
>
<SelectButton :modelValue="item.searchMethod" :options="methodOptions" optionLabel="label" optionValue="value" :allowEmpty="false" size="small" @update:modelValue="(val) => updateMethod(item.pageKey, val)">
<template #option="slotProps">
<div class="flex items-center gap-1.5 px-1 py-0.5 text-xs font-bold">
<i :class="slotProps.option.icon"></i>
@@ -507,16 +404,7 @@ const navigateToModule = (routePath) => {
</SelectButton>
</div>
<Button
v-if="item.routePath"
label="مشاهده صفحه"
icon="pi pi-arrow-left"
iconPos="right"
size="small"
severity="secondary"
text
@click="navigateToModule(item.routePath)"
/>
<Button v-if="item.routePath" label="مشاهده صفحه" icon="pi pi-arrow-left" iconPos="right" size="small" severity="secondary" text @click="navigateToModule(item.routePath)" />
</div>
</div>
</div>
@@ -533,18 +421,14 @@ const navigateToModule = (routePath) => {
<i class="pi pi-bolt"></i>
<span>موتور Typesense</span>
</div>
<p class="m-0">
موتور جستجوی ایندکس‌شده با سرعت فوق‌العاده بالا، تحمل خطای تایپی (Typo Tolerance) و سیستم Faceting پیشرفته. نیازمند فرآیند ایندکس‌سازی داده‌ها است.
</p>
<p class="m-0">موتور جستجوی ایندکس‌شده با سرعت فوق‌العاده بالا، تحمل خطای تایپی (Typo Tolerance) و سیستم Faceting پیشرفته. نیازمند فرآیند ایندکس‌سازی داده‌ها است.</p>
</div>
<div class="p-4 rounded-xl bg-surface-0 dark:bg-surface-900 border border-emerald-200 dark:border-emerald-900/50">
<div class="font-bold text-emerald-700 dark:text-emerald-400 flex items-center gap-2 mb-1">
<i class="pi pi-database"></i>
<span>موتور MongoDB</span>
</div>
<p class="m-0">
جستجوی زنده مستقیم روی پایگاه داده MongoDB با الگوهای عبارت منظم (Regex) و فیلترهای دقیق. بدون نیاز به ایندکس جداگانه و همیشه هماهنگ با آخرین تغییرات پایگاه داده.
</p>
<p class="m-0">جستجوی زنده مستقیم روی پایگاه داده MongoDB با الگوهای عبارت منظم (Regex) و فیلترهای دقیق. بدون نیاز به ایندکس جداگانه و همیشه هماهنگ با آخرین تغییرات پایگاه داده.</p>
</div>
</div>
</div>