feat: add search settings page in dashboard, sidebar menu, and MongoDB search integration
deploy-chabokan / deploy (push) Canceled after 0s
deploy-chabokan / deploy (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { SettingService } from '@/service/SettingService';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const toast = useToast();
|
||||
const confirm = useConfirm();
|
||||
const router = useRouter();
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const settings = ref([]);
|
||||
|
||||
const methodOptions = [
|
||||
{ label: 'Typesense', value: 'typesense', icon: 'pi pi-bolt', class: 'text-amber-500' },
|
||||
{ label: 'MongoDB', value: 'mongo', icon: 'pi pi-database', class: 'text-emerald-500' }
|
||||
];
|
||||
|
||||
onMounted(async () => {
|
||||
await loadSettings();
|
||||
});
|
||||
|
||||
const loadSettings = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await SettingService.getSearchSettings();
|
||||
settings.value = data;
|
||||
} catch (e) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'خطا',
|
||||
detail: 'خطا در بارگذاری تنظیمات جستجو',
|
||||
life: 3000
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const stats = computed(() => {
|
||||
const total = settings.value.length;
|
||||
const mongoCount = settings.value.filter((s) => s.searchMethod === 'mongo').length;
|
||||
const typesenseCount = settings.value.filter((s) => s.searchMethod === 'typesense').length;
|
||||
const persistedCount = settings.value.filter((s) => s.isPersisted).length;
|
||||
return { total, mongoCount, typesenseCount, persistedCount };
|
||||
});
|
||||
|
||||
const updateMethod = async (pageKey, newMethod) => {
|
||||
try {
|
||||
const updated = await SettingService.updateSearchSetting(pageKey, newMethod);
|
||||
const index = settings.value.findIndex((s) => s.pageKey === pageKey);
|
||||
if (index !== -1) {
|
||||
settings.value[index] = { ...settings.value[index], ...updated };
|
||||
}
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'بروزرسانی شد',
|
||||
detail: `روش جستجوی "${updated.title}" به ${newMethod === 'mongo' ? 'MongoDB' : 'Typesense'} تغییر یافت.`,
|
||||
life: 3000
|
||||
});
|
||||
} catch (e) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'خطا',
|
||||
detail: 'خطا در ذخیرهسازی تنظیمات در پایگاه داده',
|
||||
life: 3000
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const setAllToMethod = async (method) => {
|
||||
confirm.require({
|
||||
message: `آیا از تغییر روش جستجوی تمامی بخشها به ${method === 'mongo' ? 'MongoDB' : 'Typesense'} اطمینان دارید؟`,
|
||||
header: 'تایید تغییر همگانی',
|
||||
icon: 'pi pi-exclamation-triangle',
|
||||
rejectProps: { label: 'انصراف', severity: 'secondary', outlined: true },
|
||||
acceptProps: { label: 'تایید و اعمال', severity: method === 'mongo' ? 'success' : 'warn' },
|
||||
accept: async () => {
|
||||
saving.value = true;
|
||||
try {
|
||||
const payload = settings.value.map((s) => ({
|
||||
pageKey: s.pageKey,
|
||||
searchMethod: method
|
||||
}));
|
||||
const updatedList = await SettingService.bulkUpdateSearchSettings(payload);
|
||||
settings.value = updatedList;
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'موفقیتآمیز',
|
||||
detail: `تمامی بخشها با موفقیت به ${method === 'mongo' ? 'MongoDB' : 'Typesense'} تغییر یافتند.`,
|
||||
life: 4000
|
||||
});
|
||||
} catch (e) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'خطا',
|
||||
detail: 'خطا در بروزرسانی همگانی تنظیمات',
|
||||
life: 3000
|
||||
});
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const navigateToModule = (routePath) => {
|
||||
if (routePath) {
|
||||
router.push(routePath);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="search-settings-page">
|
||||
<Toast />
|
||||
<ConfirmDialog />
|
||||
|
||||
<!-- ── Header Banner ── -->
|
||||
<div class="card p-6 mb-6 shadow-sm border border-surface-200 dark:border-surface-700 bg-surface-0 dark:bg-surface-900 rounded-2xl">
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-14 h-14 rounded-2xl bg-primary-100 dark:bg-primary-950 flex items-center justify-center text-primary-600 dark:text-primary-400">
|
||||
<i class="pi pi-sliders-h text-2xl"></i>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Badges -->
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="flex items-center gap-2 px-3 py-2 rounded-xl bg-surface-100 dark:bg-surface-800 text-surface-700 dark:text-surface-300 text-xs font-semibold">
|
||||
<i class="pi pi-list text-primary-500"></i>
|
||||
<span>کل بخشها: {{ stats.total }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 px-3 py-2 rounded-xl bg-emerald-50 dark:bg-emerald-950 text-emerald-700 dark:text-emerald-300 text-xs font-semibold">
|
||||
<i class="pi pi-database text-emerald-500"></i>
|
||||
<span>MongoDB: {{ stats.mongoCount }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 px-3 py-2 rounded-xl bg-amber-50 dark:bg-amber-950 text-amber-700 dark:text-amber-300 text-xs font-semibold">
|
||||
<i class="pi pi-bolt text-amber-500"></i>
|
||||
<span>Typesense: {{ stats.typesenseCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── 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')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
icon="pi pi-refresh"
|
||||
severity="secondary"
|
||||
text
|
||||
rounded
|
||||
:loading="loading"
|
||||
v-tooltip.top="'بارگذاری مجدد'"
|
||||
@click="loadSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Settings Cards Grid ── -->
|
||||
<div v-if="loading" class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="i in 6" :key="i" class="card p-6">
|
||||
<Skeleton width="40%" height="1.5rem" class="mb-3" />
|
||||
<Skeleton width="80%" height="1rem" class="mb-4" />
|
||||
<Skeleton width="100%" height="2.5rem" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div
|
||||
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'
|
||||
]"
|
||||
>
|
||||
<div>
|
||||
<!-- Card Top Header -->
|
||||
<div class="flex items-start justify-between gap-3 mb-3">
|
||||
<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'
|
||||
]"
|
||||
>
|
||||
<i :class="item.icon || 'pi pi-file'"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-bold m-0 text-surface-900 dark:text-surface-0">{{ item.title }}</h3>
|
||||
<span class="text-xs text-surface-500 font-mono">{{ item.pageKey }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Persistence Badge -->
|
||||
<Tag
|
||||
:value="item.isPersisted ? 'ذخیره در دیتابیس' : 'پیشفرض'"
|
||||
:severity="item.isPersisted ? 'success' : 'secondary'"
|
||||
class="text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="text-sm text-surface-600 dark:text-surface-300 mb-4 leading-relaxed">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
|
||||
<!-- Endpoints Information -->
|
||||
<div class="p-3 mb-4 rounded-xl bg-surface-100 dark:bg-surface-800/80 text-xs space-y-1 font-mono">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-surface-500">اندپوینت فعال:</span>
|
||||
<span class="font-bold" :class="item.searchMethod === 'mongo' ? 'text-emerald-600 dark:text-emerald-400' : 'text-amber-600 dark:text-amber-400'">
|
||||
{{ item.searchMethod === 'mongo' ? item.endpointMongo : item.endpointTypesense }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Selector & Actions -->
|
||||
<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)"
|
||||
>
|
||||
<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>
|
||||
<span>{{ slotProps.option.label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</SelectButton>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- ── Information Guide ── -->
|
||||
<div class="card p-6 mt-8 rounded-2xl bg-surface-50 dark:bg-surface-800/50 border border-surface-200 dark:border-surface-700">
|
||||
<h4 class="text-base font-bold mb-3 flex items-center gap-2 text-surface-800 dark:text-surface-100">
|
||||
<i class="pi pi-info-circle text-primary-500"></i>
|
||||
<span>راهنمای روشهای جستجو</span>
|
||||
</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-xs leading-relaxed text-surface-600 dark:text-surface-300">
|
||||
<div class="p-4 rounded-xl bg-surface-0 dark:bg-surface-900 border border-amber-200 dark:border-amber-900/50">
|
||||
<div class="font-bold text-amber-700 dark:text-amber-400 flex items-center gap-2 mb-1">
|
||||
<i class="pi pi-bolt"></i>
|
||||
<span>موتور Typesense</span>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.search-settings-page {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(6px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user