Merge branch 'Baghi/conflict' of https://git2.tavasi.ir/Baghi/conflict-nuxt-4 into main
This commit is contained in:
commit
82d71d0ede
|
|
@ -1,4 +1,3 @@
|
||||||
<!-- app.vue -->
|
|
||||||
<template>
|
<template>
|
||||||
<UApp>
|
<UApp>
|
||||||
<NuxtLayout>
|
<NuxtLayout>
|
||||||
|
|
@ -11,7 +10,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useHead } from "#imports";
|
import { useHead } from "#imports";
|
||||||
import { onMounted } from "vue";
|
import { onMounted } from "vue";
|
||||||
import { composSystemTheme } from "~/composables/composSystemTheme";
|
import { composSystemTheme } from "@/composables/composSystemTheme";
|
||||||
// تنظیم تم سیستم
|
// تنظیم تم سیستم
|
||||||
useHead({
|
useHead({
|
||||||
script: [
|
script: [
|
||||||
|
|
|
||||||
972
app/components/auto-import/AutoComplation.vue
Normal file
972
app/components/auto-import/AutoComplation.vue
Normal file
|
|
@ -0,0 +1,972 @@
|
||||||
|
<template>
|
||||||
|
<div class="relative w-full max-w-xl mx-auto">
|
||||||
|
<!-- Main Search Container -->
|
||||||
|
<div class="relative">
|
||||||
|
<!-- Input Field -->
|
||||||
|
<div class="relative">
|
||||||
|
<div
|
||||||
|
class="relative bg-white dark:bg-dark-primary border border-gray-300 dark:border-dark-primary-700 rounded-lg transition-all duration-200"
|
||||||
|
>
|
||||||
|
<!-- Input Row -->
|
||||||
|
<div class="flex items-center">
|
||||||
|
<!-- Left Side: Icons + Filter Dropdown -->
|
||||||
|
<div class="flex items-center space-x-2 space-x-reverse ml-3">
|
||||||
|
<!-- Search Icon -->
|
||||||
|
<!-- <UIcon
|
||||||
|
name="i-heroicons-magnifying-glass"
|
||||||
|
class="w-5 h-5 text-gray-400 dark:text-gray-500 transition-colors duration-200"
|
||||||
|
:class="{
|
||||||
|
'text-blue-500 dark:text-blue-400': isFocused || searchQuery,
|
||||||
|
}"
|
||||||
|
/> -->
|
||||||
|
|
||||||
|
<!-- Filter Dropdown Trigger -->
|
||||||
|
<div class="relative">
|
||||||
|
<!-- Trigger Button با آیکون -->
|
||||||
|
<button
|
||||||
|
@click.stop="toggleFilterDropdown"
|
||||||
|
class="flex items-center gap-1 px-3 py-1.5 text-sm text-primary-700 hover:text-primary-900 hover:bg-primary-50 rounded-lg transition-all duration-200"
|
||||||
|
>
|
||||||
|
{{ selectedFilterLabel }}
|
||||||
|
<!-- آیکون Chevron با انیمیشن -->
|
||||||
|
<svg
|
||||||
|
class="w-4 h-4 transition-transform duration-200"
|
||||||
|
:class="{ 'rotate-180': filterDropdownOpen }"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M19 9l-7 7-7-7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Dropdown Menu -->
|
||||||
|
<Transition
|
||||||
|
enter-active-class="transition ease-out duration-150"
|
||||||
|
enter-from-class="opacity-0 -translate-y-1"
|
||||||
|
enter-to-class="opacity-100 translate-y-0"
|
||||||
|
leave-active-class="transition ease-in duration-100"
|
||||||
|
leave-from-class="opacity-100 translate-y-0"
|
||||||
|
leave-to-class="opacity-0 -translate-y-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="filterDropdownOpen"
|
||||||
|
class="absolute top-full left-0 mt-1.5 w-36 bg-white border border-primary-200 rounded-lg shadow-sm z-50"
|
||||||
|
>
|
||||||
|
<div class="py-1.5">
|
||||||
|
<button
|
||||||
|
v-for="filter in quickFilters"
|
||||||
|
:key="filter.value"
|
||||||
|
@click="selectFilterFromDropdown(filter)"
|
||||||
|
class="w-full px-4 py-2 text-sm text-primary-700 hover:bg-primary-50 hover:text-primary-900 text-right transition-colors duration-150"
|
||||||
|
:class="{
|
||||||
|
'text-blue-600': selectedFilter === filter.value,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ filter.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
<!-- Selected Filter Badge -->
|
||||||
|
<!-- <div
|
||||||
|
v-if="selectedFilter !== 'all'"
|
||||||
|
class="flex items-center space-x-1 space-x-reverse px-2 py-1 bg-blue-50 dark:bg-blue-900/30 rounded-md animate-fade-in"
|
||||||
|
>
|
||||||
|
<UIcon
|
||||||
|
:name="selectedFilterIcon"
|
||||||
|
class="w-3 h-3 text-blue-500 dark:text-blue-400"
|
||||||
|
/>
|
||||||
|
<span class="text-xs text-blue-600 dark:text-blue-300">{{
|
||||||
|
selectedFilterLabel
|
||||||
|
}}</span>
|
||||||
|
<button
|
||||||
|
@click.stop="clearFilter"
|
||||||
|
class="text-blue-400 dark:text-blue-300 hover:text-blue-600 dark:hover:text-blue-100"
|
||||||
|
>
|
||||||
|
<UIcon name="i-heroicons-x-mark" class="w-3 h-3" />
|
||||||
|
</button>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Input -->
|
||||||
|
<input
|
||||||
|
ref="inputRef"
|
||||||
|
v-model="searchQuery"
|
||||||
|
:placeholder="isFocused && !searchQuery ? placeholder : ''"
|
||||||
|
type="text"
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
|
class="flex-1 bg-transparent text-dark-light dark:text-white-light placeholder:text-dark-light dark:placeholder:text-white-light focus:outline-none text-sm w-full"
|
||||||
|
@focus="handleFocus"
|
||||||
|
@blur="handleBlur"
|
||||||
|
@input="handleInput"
|
||||||
|
@keydown="handleKeyDown"
|
||||||
|
@keyup.enter="handleEnter"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Right Side Actions -->
|
||||||
|
<div class="flex items-center space-x-2 space-x-reverse">
|
||||||
|
<!-- <div v-if="loading" class="flex items-center justify-center">
|
||||||
|
<div
|
||||||
|
class="w-4 h-4 border-2 border-gray-300 dark:border-dark-primary-600 border-t-blue-500 dark:border-t-blue-400 rounded-full animate-spin"
|
||||||
|
></div>
|
||||||
|
</div> -->
|
||||||
|
<button
|
||||||
|
v-if="searchQuery"
|
||||||
|
@click="clearSearch"
|
||||||
|
class="flex items-center justify-center w-6 h-6 rounded-full hover:bg-gray-100 dark:hover:bg-dark-primary-800 transition-colors duration-200"
|
||||||
|
aria-label="پاک کردن"
|
||||||
|
>
|
||||||
|
<UIcon
|
||||||
|
name="i-heroicons-x-mark"
|
||||||
|
class="w-3 h-3 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:disabled="loading"
|
||||||
|
@click="handleSearch"
|
||||||
|
class="cursor-pointer flex items-center justify-center h-10 p-2 bg-primary text-white rounded-l-lg hover:bg-primary-400 dark:hover:bg-dark-primary-700 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 active:scale-95"
|
||||||
|
aria-label="جستجو"
|
||||||
|
>
|
||||||
|
جستجو
|
||||||
|
<!-- <UIcon name="i-heroicons-arrow-left" class="w-4 h-4" /> -->
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Character Counter -->
|
||||||
|
<!-- <div v-if="searchQuery" class="absolute left-3 -bottom-5">
|
||||||
|
<span class="text-xs text-gray-400 dark:text-gray-500 font-mono">
|
||||||
|
{{ searchQuery.length }}/{{ resolvedProps.value.maxChars || 100 }}
|
||||||
|
</span>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results Dropdown -->
|
||||||
|
<Transition
|
||||||
|
enter-active-class="transition-all duration-200 ease-out"
|
||||||
|
enter-from-class="opacity-0 transform -translate-y-2"
|
||||||
|
enter-to-class="opacity-100 transform translate-y-0"
|
||||||
|
leave-active-class="transition-all duration-150 ease-in"
|
||||||
|
leave-from-class="opacity-100 transform translate-y-0"
|
||||||
|
leave-to-class="opacity-0 transform -translate-y-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="hasVisibleResults"
|
||||||
|
class="absolute z-50 w-full mt-2 bg-white dark:bg-dark-primary border border-gray-200 dark:border-dark-primary-700 rounded-lg shadow-lg backdrop-blur-sm bg-opacity-95 dark:bg-opacity-95 overflow-hidden"
|
||||||
|
>
|
||||||
|
<!-- Header -->
|
||||||
|
<div
|
||||||
|
class="px-4 py-3 bg-gray-100 dark:bg-dark-primary-800 border-b border-gray-100 dark:border-dark-primary-800"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center space-x-2 space-x-reverse">
|
||||||
|
<UIcon
|
||||||
|
:name="
|
||||||
|
showAutocompleteList
|
||||||
|
? 'i-heroicons-sparkles'
|
||||||
|
: 'i-heroicons-clock'
|
||||||
|
"
|
||||||
|
class="w-4 h-4 ml-1"
|
||||||
|
:class="
|
||||||
|
showAutocompleteList ? 'text-purple-500' : 'text-blue-500'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
{{ showAutocompleteList ? "پیشنهادات" : "تاریخچه" }}
|
||||||
|
<!-- <span
|
||||||
|
v-if="selectedFilter !== 'all'"
|
||||||
|
class="mr-2 inline-flex items-center px-2 py-0.5 rounded text-xs bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-300"
|
||||||
|
>
|
||||||
|
<UIcon :name="selectedFilterIcon" class="w-3 h-3 ml-1" />
|
||||||
|
{{ selectedFilterLabel }}
|
||||||
|
</span> -->
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-gray-500 dark:text-gray-400"
|
||||||
|
>{{ totalResults }} مورد</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results List -->
|
||||||
|
<div class="max-h-80 overflow-y-auto custom-scrollbar">
|
||||||
|
<!-- Autocomplete -->
|
||||||
|
<div v-if="showAutocompleteList">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in autocompleteResults"
|
||||||
|
:key="`autocomplete-${item.id || index}`"
|
||||||
|
class="px-4 py-3 hover:bg-gray-50 dark:hover:bg-dark-primary-800 cursor-pointer border-b border-gray-100 dark:border-dark-primary-800 last:border-b-0 transition-colors duration-150"
|
||||||
|
:class="{
|
||||||
|
'bg-blue-50 dark:bg-blue-900/20':
|
||||||
|
selectedIndex === index && activeSection === 'autocomplete',
|
||||||
|
}"
|
||||||
|
@click="handleAutocompleteSelect(item)"
|
||||||
|
@mouseenter="
|
||||||
|
hoverIndex = index;
|
||||||
|
activeSection = 'autocomplete';
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div
|
||||||
|
class="flex items-center space-x-2 space-x-reverse mb-1"
|
||||||
|
>
|
||||||
|
<UIcon
|
||||||
|
:name="item.icon || 'i-heroicons-magnifying-glass'"
|
||||||
|
class="w-4 h-4 text-gray-400 dark:text-gray-500 flex-shrink-0"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="text-sm font-medium text-gray-800 dark:text-gray-200 truncate"
|
||||||
|
>
|
||||||
|
{{ item.label || item.value || item }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
v-if="item.description"
|
||||||
|
class="text-xs text-gray-500 dark:text-gray-400 line-clamp-2"
|
||||||
|
>
|
||||||
|
{{ item.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<UIcon
|
||||||
|
name="i-heroicons-chevron-left"
|
||||||
|
class="w-4 h-4 text-gray-300 dark:text-gray-600 flex-shrink-0"
|
||||||
|
:class="{
|
||||||
|
'text-blue-500 dark:text-blue-400':
|
||||||
|
selectedIndex === index &&
|
||||||
|
activeSection === 'autocomplete',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- History -->
|
||||||
|
<div v-if="showHistoryList">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in historyResults"
|
||||||
|
:key="`history-${item.id || index}`"
|
||||||
|
class="group px-4 py-3 hover:bg-gray-50 dark:hover:bg-dark-primary-800 cursor-pointer border-b border-gray-100 dark:border-dark-primary-800 last:border-b-0 transition-colors duration-150"
|
||||||
|
:class="{
|
||||||
|
'bg-blue-50 dark:bg-blue-900/20':
|
||||||
|
selectedIndex === autocompleteResults.length + index &&
|
||||||
|
activeSection === 'history',
|
||||||
|
}"
|
||||||
|
@click="handleHistorySelect(item)"
|
||||||
|
@mouseenter="
|
||||||
|
hoverIndex = autocompleteResults.length + index;
|
||||||
|
activeSection = 'history';
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div
|
||||||
|
class="flex items-center space-x-3 space-x-reverse flex-1 min-w-0"
|
||||||
|
>
|
||||||
|
<div class="relative ml-2">
|
||||||
|
<div
|
||||||
|
class="w-8 h-8 bg-gray-100 dark:bg-dark-primary-800 rounded-lg flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<UIcon
|
||||||
|
name="i-heroicons-clock"
|
||||||
|
class="w-4 h-4 text-gray-400 dark:text-gray-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="item.count && item.count > 1"
|
||||||
|
class="absolute -top-1 -right-1 w-4 h-4 bg-blue-500 dark:bg-blue-400 rounded-full flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<span class="text-[9px] text-white font-medium">{{
|
||||||
|
item.count
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p
|
||||||
|
class="text-sm text-gray-800 dark:text-gray-200 truncate"
|
||||||
|
>
|
||||||
|
{{ item.label || item.value || item }}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
v-if="item.timestamp"
|
||||||
|
class="text-xs text-gray-500 dark:text-gray-400 mt-0.5"
|
||||||
|
>
|
||||||
|
{{ formatTimeAgo(item.timestamp) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
@click.stop="removeHistoryItem(item, index)"
|
||||||
|
class="opacity-0 group-hover:opacity-100 p-1 text-gray-400 dark:text-gray-500 hover:text-red-500 dark:hover:text-red-400 transition-all duration-200"
|
||||||
|
>
|
||||||
|
<UIcon name="i-heroicons-trash" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Empty History -->
|
||||||
|
<div
|
||||||
|
v-if="historyResults.length === 0"
|
||||||
|
class="px-4 py-8 text-center"
|
||||||
|
>
|
||||||
|
<UIcon
|
||||||
|
name="i-heroicons-clock"
|
||||||
|
class="w-8 h-8 text-gray-300 dark:text-gray-600 mx-auto mb-3"
|
||||||
|
/>
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
تاریخچه خالی است
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<div v-if="showEmptyState" class="px-4 py-8 text-center">
|
||||||
|
<UIcon
|
||||||
|
name="i-heroicons-magnifying-glass"
|
||||||
|
class="w-8 h-8 text-gray-300 dark:text-gray-600 mx-auto mb-3"
|
||||||
|
/>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">
|
||||||
|
نتیجهای یافت نشد
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-500">
|
||||||
|
عبارت جستجو را تغییر دهید
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Clear All History -->
|
||||||
|
<div
|
||||||
|
v-if="historyResults.length > 0"
|
||||||
|
class="px-4 py-3 bg-gray-100 dark:bg-dark-primary-800 border-t border-gray-100 dark:border-dark-primary-800"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
@click="clearAllHistory"
|
||||||
|
class="w-full cursor-pointer flex items-center justify-center space-x-2 space-x-reverse text-sm text-gray-500 dark:text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors duration-200"
|
||||||
|
>
|
||||||
|
<UIcon name="i-heroicons-trash" class="w-4 h-4 ml-1" />
|
||||||
|
<span>پاک کردن تاریخچه</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Footer -->
|
||||||
|
<!-- <div
|
||||||
|
v-if="
|
||||||
|
hasVisibleResults && (showAutocompleteList || showHistoryList)
|
||||||
|
"
|
||||||
|
class="px-4 py-2 border-t border-gray-100 dark:border-dark-primary-800 bg-gray-50 dark:bg-dark-primary-800/50"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400"
|
||||||
|
>
|
||||||
|
<div class="flex items-center space-x-4 space-x-reverse">
|
||||||
|
<span class="flex items-center space-x-1 space-x-reverse">
|
||||||
|
<UIcon name="i-heroicons-arrow-up" class="w-3 h-3" />
|
||||||
|
<UIcon name="i-heroicons-arrow-down" class="w-3 h-3" />
|
||||||
|
<span>ناوبری</span>
|
||||||
|
</span>
|
||||||
|
<span class="flex items-center space-x-1 space-x-reverse">
|
||||||
|
<UIcon name="i-heroicons-enter" class="w-3 h-3" />
|
||||||
|
<span>انتخاب</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span>↵ Enter برای جستجو</span>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recent Searches (خارج از دراپداون) -->
|
||||||
|
<!-- <div
|
||||||
|
v-if="
|
||||||
|
showRecentSearches &&
|
||||||
|
historyResults.length > 0 &&
|
||||||
|
!searchQuery &&
|
||||||
|
!isFocused
|
||||||
|
"
|
||||||
|
class="mt-4 animate-fade-in"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex items-center space-x-2 space-x-reverse overflow-x-auto py-2"
|
||||||
|
>
|
||||||
|
<span class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap"
|
||||||
|
>اخیراً:</span
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-for="(item, index) in recentSearches"
|
||||||
|
:key="index"
|
||||||
|
@click="selectRecentSearch(item)"
|
||||||
|
class="flex-shrink-0 px-3 py-1 text-xs bg-gray-100 dark:bg-dark-primary-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-dark-primary-700 rounded-full transition-colors duration-200 truncate max-w-xs"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
autoComplationSchema: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
|
||||||
|
placeholder: { type: String, default: "جستجو در هزاران محتوا" },
|
||||||
|
autocompleteUrl: { type: String, required: true },
|
||||||
|
historyUrl: { type: String, required: true },
|
||||||
|
showSearchButton: { type: Boolean, default: true },
|
||||||
|
debounceTime: { type: Number, default: 500 },
|
||||||
|
maxHistoryItems: { type: Number, default: 10 },
|
||||||
|
minCharsForAutocomplete: { type: Number, default: 2 },
|
||||||
|
maxChars: { type: Number, default: 100 },
|
||||||
|
filters: { type: Array, default: () => [] },
|
||||||
|
});
|
||||||
|
|
||||||
|
const resolvedProps = computed(() => ({
|
||||||
|
placeholder: props.autoComplationSchema.placeholder ?? props.placeholder,
|
||||||
|
|
||||||
|
autocompleteUrl:
|
||||||
|
props.autoComplationSchema.autocompleteUrl ?? props.autocompleteUrl,
|
||||||
|
|
||||||
|
historyUrl: props.autoComplationSchema.historyUrl ?? props.historyUrl,
|
||||||
|
|
||||||
|
showSearchButton:
|
||||||
|
props.autoComplationSchema.showSearchButton ?? props.showSearchButton,
|
||||||
|
|
||||||
|
debounceTime: props.autoComplationSchema.debounceTime ?? props.debounceTime,
|
||||||
|
|
||||||
|
maxHistoryItems:
|
||||||
|
props.autoComplationSchema.maxHistoryItems ?? props.maxHistoryItems,
|
||||||
|
|
||||||
|
minCharsForAutocomplete:
|
||||||
|
props.autoComplationSchema.minCharsForAutocomplete ??
|
||||||
|
props.minCharsForAutocomplete,
|
||||||
|
|
||||||
|
maxChars: props.autoComplationSchema.maxChars ?? props.maxChars,
|
||||||
|
|
||||||
|
filters: props.autoComplationSchema.filters ?? props.filters,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// const emit = defineEmits([
|
||||||
|
// "search",
|
||||||
|
// "autocomplete-select",
|
||||||
|
// "history-select",
|
||||||
|
// "history-clear",
|
||||||
|
// "history-remove",
|
||||||
|
// "focus",
|
||||||
|
// "blur",
|
||||||
|
// "clear",
|
||||||
|
// "filter-selected",
|
||||||
|
// "complete-search",
|
||||||
|
// ]);
|
||||||
|
const emit = defineEmits(["auto-complation-handler"]);
|
||||||
|
|
||||||
|
// States
|
||||||
|
const searchQuery = ref("");
|
||||||
|
const autocompleteResults = ref([]);
|
||||||
|
const historyResults = ref([]);
|
||||||
|
const recentSearches = ref([]);
|
||||||
|
const showResults = ref(false);
|
||||||
|
const selectedIndex = ref(-1);
|
||||||
|
const hoverIndex = ref(-1);
|
||||||
|
const activeSection = ref(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
const isFocused = ref(false);
|
||||||
|
const inputRef = ref(null);
|
||||||
|
const selectedFilter = ref("all");
|
||||||
|
const filterDropdownOpen = ref(false);
|
||||||
|
const showRecentSearches = ref(true);
|
||||||
|
|
||||||
|
const quickFilters = computed(() => [
|
||||||
|
// { label: "همه", value: "all", icon: "i-heroicons-globe-alt" },
|
||||||
|
...(resolvedProps.value.filters || []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// LocalStorage Key
|
||||||
|
const LOCAL_STORAGE_KEY = "searchHistory";
|
||||||
|
|
||||||
|
// لود از localStorage
|
||||||
|
const loadHistoryFromLocal = () => {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(LOCAL_STORAGE_KEY);
|
||||||
|
if (stored) {
|
||||||
|
const parsed = JSON.parse(stored);
|
||||||
|
historyResults.value = parsed.slice(
|
||||||
|
0,
|
||||||
|
resolvedProps.value.maxHistoryItems,
|
||||||
|
);
|
||||||
|
recentSearches.value = historyResults.value
|
||||||
|
.slice(0, 5)
|
||||||
|
.map((i) => i.label);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("خطا در لود تاریخچه از localStorage:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ذخیره در localStorage
|
||||||
|
const saveHistoryToLocal = () => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(
|
||||||
|
LOCAL_STORAGE_KEY,
|
||||||
|
JSON.stringify(historyResults.value),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("خطا در ذخیره تاریخچه:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Computed
|
||||||
|
const hasVisibleResults = computed(
|
||||||
|
() =>
|
||||||
|
showResults.value &&
|
||||||
|
(showAutocompleteList.value ||
|
||||||
|
showHistoryList.value ||
|
||||||
|
showEmptyState.value),
|
||||||
|
);
|
||||||
|
const showAutocompleteList = computed(
|
||||||
|
() =>
|
||||||
|
showResults.value &&
|
||||||
|
autocompleteResults.value.length > 0 &&
|
||||||
|
searchQuery.value.length >= resolvedProps.value.minCharsForAutocomplete,
|
||||||
|
);
|
||||||
|
const showHistoryList = computed(
|
||||||
|
() => showResults.value && searchQuery.value.length === 0,
|
||||||
|
);
|
||||||
|
const showEmptyState = computed(
|
||||||
|
() =>
|
||||||
|
showResults.value &&
|
||||||
|
!showAutocompleteList.value &&
|
||||||
|
!showHistoryList.value &&
|
||||||
|
searchQuery.value.length >= resolvedProps.value.minCharsForAutocomplete,
|
||||||
|
);
|
||||||
|
const totalResults = computed(
|
||||||
|
() =>
|
||||||
|
(showAutocompleteList.value ? autocompleteResults.value.length : 0) +
|
||||||
|
(showHistoryList.value ? historyResults.value.length : 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedFilterIcon = computed(
|
||||||
|
() =>
|
||||||
|
quickFilters.value.find((f) => f.value === selectedFilter.value)?.icon ||
|
||||||
|
"i-heroicons-funnel",
|
||||||
|
);
|
||||||
|
const selectedFilterLabel = computed(
|
||||||
|
() =>
|
||||||
|
quickFilters.value.find((f) => f.value === selectedFilter.value)?.label ||
|
||||||
|
"فیلتر",
|
||||||
|
);
|
||||||
|
|
||||||
|
// فرمت زمان
|
||||||
|
const formatTimeAgo = (timestamp) => {
|
||||||
|
if (!timestamp) return "اخیراً";
|
||||||
|
const now = new Date();
|
||||||
|
const past = new Date(timestamp);
|
||||||
|
const diffMs = now - past;
|
||||||
|
const diffMins = Math.floor(diffMs / 60000);
|
||||||
|
const diffHours = Math.floor(diffMs / 3600000);
|
||||||
|
const diffDays = Math.floor(diffMs / 86400000);
|
||||||
|
if (diffMins < 1) return "همین الان";
|
||||||
|
if (diffMins < 60) return `${diffMins} دقیقه پیش`;
|
||||||
|
if (diffHours < 24) return `${diffHours} ساعت پیش`;
|
||||||
|
if (diffDays < 7) return `${diffDays} روز پیش`;
|
||||||
|
return past.toLocaleDateString("fa-IR");
|
||||||
|
};
|
||||||
|
|
||||||
|
const { $http: httpService } = useNuxtApp();
|
||||||
|
|
||||||
|
// Autocomplete
|
||||||
|
const fetchAutocomplete = async () => {
|
||||||
|
if (
|
||||||
|
!searchQuery.value ||
|
||||||
|
searchQuery.value.length < resolvedProps.value.minCharsForAutocomplete
|
||||||
|
) {
|
||||||
|
autocompleteResults.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
let url = `${resolvedProps.value.autocompleteUrl}/q=${searchQuery.value}`;
|
||||||
|
if (selectedFilter.value !== "all") url += `&filter=${selectedFilter.value}`;
|
||||||
|
try {
|
||||||
|
console.log("url", url);
|
||||||
|
|
||||||
|
const response = await httpService.getRequest(url);
|
||||||
|
const hits = response?.hits?.hits || [];
|
||||||
|
autocompleteResults.value = hits
|
||||||
|
.map((hit) => ({
|
||||||
|
id: hit._id,
|
||||||
|
label: hit._source?.title || hit._source?.name || "بدون عنوان",
|
||||||
|
value: hit._source?.title || hit._source?.name,
|
||||||
|
description: hit._source?.description || hit._source?.subtitle || "",
|
||||||
|
icon:
|
||||||
|
hit._source?.type === "product"
|
||||||
|
? "i-heroicons-shopping-bag"
|
||||||
|
: hit._source?.type === "article"
|
||||||
|
? "i-heroicons-newspaper"
|
||||||
|
: "i-heroicons-document-text",
|
||||||
|
}))
|
||||||
|
.slice(0, 6);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("خطا در پیشنهادات:", err);
|
||||||
|
autocompleteResults.value = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// History (اولویت با localStorage)
|
||||||
|
const fetchHistory = async () => {
|
||||||
|
loadHistoryFromLocal();
|
||||||
|
// اگر بخوای از سرور هم بگیری، این بخش رو فعال کن
|
||||||
|
// if (historyResults.value.length === 0) { ... }
|
||||||
|
};
|
||||||
|
|
||||||
|
// اضافه کردن به تاریخچه + ذخیره
|
||||||
|
const addToHistory = (item) => {
|
||||||
|
const term =
|
||||||
|
typeof item === "string" ? item : item.value || item.label || item;
|
||||||
|
if (!term) return;
|
||||||
|
const existingIndex = historyResults.value.findIndex((h) => h.value === term);
|
||||||
|
if (existingIndex > -1) {
|
||||||
|
const existing = historyResults.value[existingIndex];
|
||||||
|
existing.count = (existing.count || 0) + 1;
|
||||||
|
existing.timestamp = new Date().toISOString();
|
||||||
|
historyResults.value.splice(existingIndex, 1);
|
||||||
|
historyResults.value.unshift(existing);
|
||||||
|
} else {
|
||||||
|
historyResults.value.unshift({
|
||||||
|
id: Date.now(),
|
||||||
|
label: term,
|
||||||
|
value: term,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
count: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (historyResults.value.length > resolvedProps.value.maxHistoryItems) {
|
||||||
|
historyResults.value = historyResults.value.slice(
|
||||||
|
0,
|
||||||
|
resolvedProps.value.maxHistoryItems,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
recentSearches.value = historyResults.value.slice(0, 5).map((i) => i.label);
|
||||||
|
saveHistoryToLocal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeHistoryItem = (item, index) => {
|
||||||
|
historyResults.value.splice(index, 1);
|
||||||
|
recentSearches.value = recentSearches.value.filter((t) => t !== item.label);
|
||||||
|
saveHistoryToLocal();
|
||||||
|
// emit("history-remove", item);
|
||||||
|
emit("auto-complation-handler", { action: "history-remove", item });
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearAllHistory = () => {
|
||||||
|
historyResults.value = [];
|
||||||
|
recentSearches.value = [];
|
||||||
|
localStorage.removeItem(LOCAL_STORAGE_KEY);
|
||||||
|
// emit("history-clear");
|
||||||
|
emit("auto-complation-handler", { action: "history-clear", item: null });
|
||||||
|
};
|
||||||
|
|
||||||
|
// UI Functions
|
||||||
|
const toggleFilterDropdown = () =>
|
||||||
|
(filterDropdownOpen.value = !filterDropdownOpen.value);
|
||||||
|
|
||||||
|
const selectFilterFromDropdown = (filter) => {
|
||||||
|
selectedFilter.value = filter.value;
|
||||||
|
|
||||||
|
// emit("filter-selected", filter);
|
||||||
|
emit("auto-complation-handler", { action: "filter-selected", item: filter });
|
||||||
|
filterDropdownOpen.value = false;
|
||||||
|
if (searchQuery.value) fetchAutocomplete();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearFilter = () => {
|
||||||
|
selectedFilter.value = "all";
|
||||||
|
// emit("filter-selected", {
|
||||||
|
// label: "همه",
|
||||||
|
// value: "all",
|
||||||
|
// icon: "i-heroicons-globe-alt",
|
||||||
|
// });
|
||||||
|
emit("auto-complation-handler", {
|
||||||
|
action: "filter-selected",
|
||||||
|
item: {
|
||||||
|
label: "همه",
|
||||||
|
value: "all",
|
||||||
|
icon: "i-heroicons-globe-alt",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (searchQuery.value) fetchAutocomplete();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSearch = () => {
|
||||||
|
searchQuery.value = "";
|
||||||
|
autocompleteResults.value = [];
|
||||||
|
// emit("clear");
|
||||||
|
emit("auto-complation-handler", { action: "clear", item: null });
|
||||||
|
};
|
||||||
|
const handleSearch = () => {
|
||||||
|
// اگر تایمر debounce فعال بود، لغوش کن
|
||||||
|
if (searchTimer.value) {
|
||||||
|
clearTimeout(searchTimer.value);
|
||||||
|
searchTimer.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = searchQuery.value.trim();
|
||||||
|
|
||||||
|
// حتی اگر query خالی باشه، ایونت رو بفرست (جستجوی خالی = نمایش همه)
|
||||||
|
addToHistory(query || ""); // اختیاری: میتونی تاریخچه خالی اضافه نکنی
|
||||||
|
|
||||||
|
showResults.value = false;
|
||||||
|
|
||||||
|
// ارسال ایونت complete-search حتی اگر query خالی باشه
|
||||||
|
emit("auto-complation-handler", {
|
||||||
|
action: "complete-search",
|
||||||
|
item: query || null, // یا "" یا null — بسته به چیزی که والد انتظار داره
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// const handleSearch = () => {
|
||||||
|
// // اگر تایمر فعال بود، لغوش کن (چون کاربر دستی جستجو کرد)
|
||||||
|
// if (searchTimer.value) {
|
||||||
|
// clearTimeout(searchTimer.value);
|
||||||
|
// searchTimer.value = null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!searchQuery.value.trim()) return;
|
||||||
|
|
||||||
|
// addToHistory(searchQuery.value.trim());
|
||||||
|
// showResults.value = false;
|
||||||
|
// // emit("search", searchQuery.value.trim());
|
||||||
|
// // emit("complete-search", searchQuery.value.trim());
|
||||||
|
|
||||||
|
// // emit("auto-complation-handler", { action: "search", item: searchQuery.value.trim() });
|
||||||
|
// emit("auto-complation-handler", { action: "complete-search", item: searchQuery.value.trim() });
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleAutocompleteSelect = (item) => {
|
||||||
|
searchQuery.value = item.value || item.label;
|
||||||
|
addToHistory(item);
|
||||||
|
showResults.value = false;
|
||||||
|
inputRef.value?.blur();
|
||||||
|
// emit("autocomplete-select", item);
|
||||||
|
// emit("search", searchQuery.value);
|
||||||
|
|
||||||
|
emit("auto-complation-handler", { action: "autocomplete-select", item });
|
||||||
|
// emit("auto-complation-handler", { action: "search", item: searchQuery.value });
|
||||||
|
emit("auto-complation-handler", {
|
||||||
|
action: "complete-search",
|
||||||
|
item: searchQuery.value.trim(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHistorySelect = (item) => {
|
||||||
|
// console.log("item ==> ", item);
|
||||||
|
searchQuery.value = item.value || item.label;
|
||||||
|
showResults.value = false;
|
||||||
|
inputRef.value?.focus();
|
||||||
|
// emit("history-select", item);
|
||||||
|
emit("auto-complation-handler", { action: "history-select", item });
|
||||||
|
emit("auto-complation-handler", {
|
||||||
|
action: "complete-search",
|
||||||
|
item: searchQuery.value.trim(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectRecentSearch = (term) => {
|
||||||
|
searchQuery.value = term;
|
||||||
|
handleSearch();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. اصلاح handleFocus برای باز کردن دراپداون
|
||||||
|
const handleFocus = () => {
|
||||||
|
isFocused.value = true;
|
||||||
|
showResults.value = true; // همیشه باز باشه
|
||||||
|
selectedIndex.value = -1;
|
||||||
|
activeSection.value = null;
|
||||||
|
// emit("focus");
|
||||||
|
emit("auto-complation-handler", { action: "focus", item: null });
|
||||||
|
|
||||||
|
if (historyResults.value.length === 0) {
|
||||||
|
fetchHistory();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
isFocused.value = false;
|
||||||
|
showResults.value = false;
|
||||||
|
selectedIndex.value = -1;
|
||||||
|
hoverIndex.value = -1;
|
||||||
|
activeSection.value = null;
|
||||||
|
filterDropdownOpen.value = false;
|
||||||
|
// emit("blur");
|
||||||
|
emit("auto-complation-handler", { action: "blur", item: null });
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchTimer = ref(null);
|
||||||
|
|
||||||
|
// زمان debounce به ثانیه (۳ ثانیه = 3000 میلیثانیه)
|
||||||
|
const DEBOUNCE_TIME = computed(() => resolvedProps.value.debounceTime);
|
||||||
|
|
||||||
|
const handleInput = () => {
|
||||||
|
// همیشه پیشنهادات رو سریع بگیر (autocomplete)
|
||||||
|
fetchAutocomplete();
|
||||||
|
|
||||||
|
// ریست تایمر قبلی
|
||||||
|
if (searchTimer.value) {
|
||||||
|
clearTimeout(searchTimer.value);
|
||||||
|
}
|
||||||
|
// تنظیم تایمر جدید برای جستجوی نهایی
|
||||||
|
searchTimer.value = setTimeout(() => {
|
||||||
|
if (
|
||||||
|
searchQuery.value.trim() &&
|
||||||
|
searchQuery.value.length >= resolvedProps.value.minCharsForAutocomplete
|
||||||
|
) {
|
||||||
|
// جستجوی نهایی بعد از ۳ ثانیه توقف تایپ
|
||||||
|
addToHistory(searchQuery.value.trim());
|
||||||
|
// emit("search", searchQuery.value.trim());
|
||||||
|
// emit("complete-search", searchQuery.value.trim());
|
||||||
|
// emit("auto-complation-handler", { action: "search", item: searchQuery.value.trim() });
|
||||||
|
emit("auto-complation-handler", {
|
||||||
|
action: "complete-search",
|
||||||
|
item: searchQuery.value.trim(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, DEBOUNCE_TIME.value);
|
||||||
|
};
|
||||||
|
// 4. اصلاح handleKeyDown (نسخه بهبود یافته)
|
||||||
|
const handleKeyDown = (event) => {
|
||||||
|
if (!hasVisibleResults.value) return;
|
||||||
|
|
||||||
|
const autocompleteCount = showAutocompleteList.value
|
||||||
|
? autocompleteResults.value.length
|
||||||
|
: 0;
|
||||||
|
const historyCount = showHistoryList.value ? historyResults.value.length : 0;
|
||||||
|
const totalItems = autocompleteCount + historyCount;
|
||||||
|
|
||||||
|
if (totalItems === 0) return;
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case "ArrowDown":
|
||||||
|
event.preventDefault();
|
||||||
|
if (selectedIndex.value < totalItems - 1) {
|
||||||
|
selectedIndex.value++;
|
||||||
|
} else {
|
||||||
|
selectedIndex.value = 0; // چرخشی
|
||||||
|
}
|
||||||
|
updateActiveSection(autocompleteCount);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ArrowUp":
|
||||||
|
event.preventDefault();
|
||||||
|
if (selectedIndex.value <= 0) {
|
||||||
|
selectedIndex.value = totalItems - 1;
|
||||||
|
} else {
|
||||||
|
selectedIndex.value--;
|
||||||
|
}
|
||||||
|
updateActiveSection(autocompleteCount);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Enter":
|
||||||
|
event.preventDefault();
|
||||||
|
if (selectedIndex.value >= 0) {
|
||||||
|
if (activeSection.value === "autocomplete") {
|
||||||
|
handleAutocompleteSelect(
|
||||||
|
autocompleteResults.value[selectedIndex.value],
|
||||||
|
);
|
||||||
|
} else if (activeSection.value === "history") {
|
||||||
|
const historyIndex = selectedIndex.value - autocompleteCount;
|
||||||
|
handleHistorySelect(historyResults.value[historyIndex]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Escape":
|
||||||
|
showResults.value = false;
|
||||||
|
selectedIndex.value = -1;
|
||||||
|
activeSection.value = null;
|
||||||
|
inputRef.value?.blur();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateActiveSection = (autocompleteCount) => {
|
||||||
|
activeSection.value =
|
||||||
|
selectedIndex.value < autocompleteCount ? "autocomplete" : "history";
|
||||||
|
}; // قابل گسترش برای ناوبری کیبورد
|
||||||
|
const handleEnter = () => {
|
||||||
|
handleSearch(); // جستجوی فوری با Enter
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mount
|
||||||
|
const closeDropdown = () => (filterDropdownOpen.value = false);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener("click", closeDropdown);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener("click", closeDropdown);
|
||||||
|
});
|
||||||
|
watch(autocompleteResults, (val) => {
|
||||||
|
if (val.length > 0 && isFocused.value) {
|
||||||
|
showResults.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
watch(searchQuery, (newVal) => {
|
||||||
|
// همیشه دراپداون باز باشه وقتی فوکوس داریم یا چیزی تایپ شده
|
||||||
|
if (isFocused.value) {
|
||||||
|
showResults.value = true;
|
||||||
|
}
|
||||||
|
// ریست ناوبری کیبورد
|
||||||
|
selectedIndex.value = -1;
|
||||||
|
activeSection.value = null;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.animate-fade-in {
|
||||||
|
animation: fadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-track {
|
||||||
|
background: rgba(243, 244, 246, 0.5);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(156, 163, 175, 0.5);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.dark .custom-scrollbar::-webkit-scrollbar-track {
|
||||||
|
background: rgba(31, 41, 55, 0.5);
|
||||||
|
}
|
||||||
|
.dark .custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(75, 85, 99, 0.5);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="col-span-3 lg:col-span-2 xl:col-span-3 hidden lg:block">
|
<div class="col-span-3 lg:col-span-2 xl:col-span-3 hidden lg:block">
|
||||||
<template v-if="headerSchema.breadcrumb">
|
<template v-if="headerSchema.breadcrumb">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
:breadcrumbData="[]"
|
:breadcrumbData="defaultSidebar.topMenu"
|
||||||
:tabs="tabs"
|
:tabs="tabs"
|
||||||
:activeTabId="activeTabModel"
|
:activeTabId="activeTabModel"
|
||||||
/>
|
/>
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<template v-if="headerSchema.logo">
|
<template v-if="headerSchema.logo">
|
||||||
<nuxt-link :to="{ name: 'DashboardBasePage' }">
|
<nuxt-link :to="{ name: 'DashboardBasePage' }">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<img :src="useSystemTheme.logo.value" alt="" class="h-9 w-9" />
|
<img :src="logoSrc" alt="" class="h-9 w-9" />
|
||||||
<div v-if="useSystemTheme.currentTheme.value" class="flex flex-col">
|
<div v-if="useSystemTheme.currentTheme.value" class="flex flex-col">
|
||||||
<span class="font-bold text-gray-900 dark:text-light-primary">
|
<span class="font-bold text-gray-900 dark:text-light-primary">
|
||||||
{{ useSystemTheme.currentTheme.value.title || "" }}
|
{{ useSystemTheme.currentTheme.value.title || "" }}
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-3 lg:col-span-2 xl:col-span-3 lg:hidden">
|
<div class="col-span-3 lg:col-span-2 xl:col-span-3 lg:hidden">
|
||||||
<button
|
<button
|
||||||
@click="toggleSidebarMenu"
|
@click="commonStore.toggleSidebar"
|
||||||
class="flex items-center justify-center w-8 h-8 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-dark-primary-800 transition-colors duration-200"
|
class="flex cursor-pointer items-center justify-center w-8 h-8 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-dark-primary-800 transition-colors duration-200"
|
||||||
aria-label="باز کردن منو"
|
aria-label="باز کردن منو"
|
||||||
>
|
>
|
||||||
<UIcon name="i-heroicons-bars-3" class="w-5 h-5" />
|
<UIcon name="i-heroicons-bars-3" class="w-5 h-5" />
|
||||||
|
|
@ -123,6 +123,7 @@
|
||||||
import { ref, computed, onMounted } from "vue";
|
import { ref, computed, onMounted } from "vue";
|
||||||
import headerItems from "@/json/header/header.json";
|
import headerItems from "@/json/header/header.json";
|
||||||
import { composSystemTheme } from "@/composables/composSystemTheme";
|
import { composSystemTheme } from "@/composables/composSystemTheme";
|
||||||
|
import defaultSidebar from "@/json/sidebar/dashboard.json";
|
||||||
import { useCommonStore } from "@/stores/commonStore";
|
import { useCommonStore } from "@/stores/commonStore";
|
||||||
import { useAuthStore } from "@/stores/authStore";
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
@ -144,7 +145,7 @@ const emit = defineEmits([
|
||||||
"tab-change",
|
"tab-change",
|
||||||
"user-menu-select",
|
"user-menu-select",
|
||||||
]);
|
]);
|
||||||
|
const logoSrc = ref("");
|
||||||
/* ---------------- ACTIVE TAB ---------------- */
|
/* ---------------- ACTIVE TAB ---------------- */
|
||||||
const activeTabModel = computed({
|
const activeTabModel = computed({
|
||||||
get: () => props.activeTab,
|
get: () => props.activeTab,
|
||||||
|
|
@ -197,9 +198,6 @@ const userMenuItems = computed(() =>
|
||||||
|
|
||||||
// const userAvatar = "https://api.dicebear.com/7.x/avataaars/svg?seed=admin";
|
// const userAvatar = "https://api.dicebear.com/7.x/avataaars/svg?seed=admin";
|
||||||
const isClient = ref(false);
|
const isClient = ref(false);
|
||||||
onMounted(() => {
|
|
||||||
isClient.value = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
const userAvatar = computed(() => {
|
const userAvatar = computed(() => {
|
||||||
if (!process.client) return null;
|
if (!process.client) return null;
|
||||||
|
|
@ -237,7 +235,14 @@ const userInitial = computed(() => {
|
||||||
return "؟";
|
return "؟";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
function toggleSidebarMenu() {
|
// function toggleSidebarMenu() {
|
||||||
commonStore.isSidebarOpen();
|
// commonStore.isSidebarOpen();
|
||||||
}
|
// }
|
||||||
|
onMounted(() => {
|
||||||
|
isClient.value = true;
|
||||||
|
useSystemTheme.applyTheme();
|
||||||
|
setTimeout(() => {
|
||||||
|
logoSrc.value = useSystemTheme.logo.value;
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -4,41 +4,44 @@
|
||||||
side="left"
|
side="left"
|
||||||
:collapsed="collapsed"
|
:collapsed="collapsed"
|
||||||
collapsible
|
collapsible
|
||||||
:min-size="5"
|
|
||||||
:default-size="10"
|
|
||||||
:max-size="15"
|
|
||||||
:ui="sidebarUI"
|
:ui="sidebarUI"
|
||||||
class="sidebar-gradient"
|
class="sidebar-gradient"
|
||||||
|
:style="{ width: collapsed ? '2%' : '8%' }"
|
||||||
dir="rtl"
|
dir="rtl"
|
||||||
@update:collapsed="onCollapse"
|
@update:collapsed="onCollapse"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<nuxt-link :to="{ name: 'DashboardBasePage' }">
|
<nuxt-link
|
||||||
<div class="flex items-center gap-3">
|
:to="{ name: 'DashboardBasePage' }"
|
||||||
<img :src="useSystemTheme.logo.value" alt="" class="h-9 w-9" />
|
class="w-full flex justify-center items-center"
|
||||||
<div
|
|
||||||
v-if="useSystemTheme.currentTheme.value && !collapsed"
|
|
||||||
class="flex flex-col"
|
|
||||||
>
|
>
|
||||||
<span class="font-bold text-gray-900 dark:text-light-primary">
|
<div class="flex items-center gap-3">
|
||||||
{{ useSystemTheme.currentTheme.value.title || "" }}
|
<img :src="logoSrc" alt="" class="h-9 w-9" />
|
||||||
</span>
|
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
{{ useSystemTheme.currentTheme.value.subTitle || "" }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- <div
|
||||||
|
v-if="useSystemTheme?.currentTheme?.value && !collapsed"
|
||||||
|
class="flex flex-col"
|
||||||
|
>
|
||||||
|
<span class="font-bold text-gray-900 dark:text-light-primary">
|
||||||
|
{{ useSystemTheme?.currentTheme?.value?.title || "" }}
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{ useSystemTheme?.currentTheme?.value?.subTitle || "" }}
|
||||||
|
</span>
|
||||||
|
</div> -->
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="">
|
<div class="">
|
||||||
|
|
||||||
<UNavigationMenu
|
<UNavigationMenu
|
||||||
dir="rtl"
|
dir="rtl"
|
||||||
:collapsed="collapsed"
|
|
||||||
:items="getSideBarSchema()?.topMenu || []"
|
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
|
:items="getSideBarSchema()?.topMenu || []"
|
||||||
|
:collapsed="collapsed"
|
||||||
:ui="navigationUI"
|
:ui="navigationUI"
|
||||||
|
class="w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -50,21 +53,36 @@
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
:ui="navigationUI"
|
:ui="navigationUI"
|
||||||
/>
|
/>
|
||||||
</div>
|
<UTooltip
|
||||||
</template>
|
arrow
|
||||||
|
dir="rtl"
|
||||||
<template #footer="{ collapsed }">
|
:text="user?.first_name + ' ' + user?.last_name"
|
||||||
<div>
|
>
|
||||||
<UButton
|
<UButton
|
||||||
:avatar="{ src: '' }"
|
:avatar="
|
||||||
:label="collapsed ? undefined : 'مدیرفنی سامانه'"
|
user?.avatar
|
||||||
|
? { src: user?.avatar }
|
||||||
|
: {
|
||||||
|
label:
|
||||||
|
(user?.first_name?.[0] || '') +
|
||||||
|
(user?.last_name?.[0] || ''),
|
||||||
|
}
|
||||||
|
"
|
||||||
|
:label="
|
||||||
|
collapsed ? '' : user?.first_name + ' ' + user?.last_name || ''
|
||||||
|
"
|
||||||
color="neutral"
|
color="neutral"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
:block="collapsed"
|
:block="collapsed"
|
||||||
/>
|
/></UTooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
<!-- دکمههای باز/بسته کردن سایدبار -->
|
<!-- دکمههای باز/بسته کردن سایدبار -->
|
||||||
<div class="flex justify-center items-center gap-2 mt-2">
|
<div class="flex justify-center items-center gap-2 mt-4">
|
||||||
<!-- دکمه باز کردن -->
|
<!-- دکمه باز کردن -->
|
||||||
<UButton
|
<UButton
|
||||||
v-if="collapsed"
|
v-if="collapsed"
|
||||||
|
|
@ -89,12 +107,85 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</UDashboardSidebar>
|
</UDashboardSidebar>
|
||||||
|
<USlideover
|
||||||
|
v-model:open="sidebarOpenComputed"
|
||||||
|
side="right"
|
||||||
|
overlay
|
||||||
|
class="lg:hidden"
|
||||||
|
:dismissible="true"
|
||||||
|
dir="rtl"
|
||||||
|
:title="
|
||||||
|
useSystemTheme?.currentTheme?.value?.title +
|
||||||
|
' - ' +
|
||||||
|
useSystemTheme?.currentTheme?.value?.subTitle || ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<div class="p-4 flex flex-col h-full">
|
||||||
|
<UNavigationMenu
|
||||||
|
orientation="vertical"
|
||||||
|
:items="filteredSidebar.topMenu"
|
||||||
|
@click="onMenuSelect"
|
||||||
|
dir="rtl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="mt-auto">
|
||||||
|
<UNavigationMenu
|
||||||
|
orientation="vertical"
|
||||||
|
:items="filteredSidebar.bottomMenu"
|
||||||
|
@click="onMenuSelect"
|
||||||
|
dir="rtl"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer="{ collapsed }">
|
||||||
|
<div>
|
||||||
|
<UButton
|
||||||
|
:avatar="
|
||||||
|
user?.avatar
|
||||||
|
? { src: user?.avatar }
|
||||||
|
: {
|
||||||
|
label:
|
||||||
|
(user?.first_name?.[0] || '') +
|
||||||
|
(user?.last_name?.[0] || ''),
|
||||||
|
}
|
||||||
|
"
|
||||||
|
:label="
|
||||||
|
collapsed ? '' : user?.first_name + ' ' + user?.last_name || ''
|
||||||
|
"
|
||||||
|
color="neutral"
|
||||||
|
variant="ghost"
|
||||||
|
class="w-full"
|
||||||
|
:block="collapsed"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</USlideover>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref} from "vue";
|
import { ref, onMounted } from "vue";
|
||||||
import { composSystemTheme } from "@/composables/composSystemTheme";
|
import { composSystemTheme } from "@/composables/composSystemTheme";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { useCommonStore } from "@/stores/commonStore";
|
||||||
|
|
||||||
|
const commonStore = useCommonStore();
|
||||||
|
const { sidebarOpen } = storeToRefs(commonStore);
|
||||||
|
|
||||||
|
// proxy امن برای v-model:open
|
||||||
|
const sidebarOpenComputed = computed({
|
||||||
|
get: () => sidebarOpen.value,
|
||||||
|
set: (v) => (commonStore.sidebarOpen = v),
|
||||||
|
});
|
||||||
|
const filteredSidebar = computed(
|
||||||
|
() => props.sidebarItems || { topMenu: [], bottomMenu: [] },
|
||||||
|
);
|
||||||
|
|
||||||
|
function onMenuSelect() {
|
||||||
|
// وقتی کاربر آیتم منو رو زد، اسلایداور رو ببند
|
||||||
|
commonStore.closeSidebar();
|
||||||
|
}
|
||||||
const useSystemTheme = composSystemTheme();
|
const useSystemTheme = composSystemTheme();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
@ -110,27 +201,35 @@ const props = defineProps({
|
||||||
|
|
||||||
const emit = defineEmits(["update:collapsed"]);
|
const emit = defineEmits(["update:collapsed"]);
|
||||||
|
|
||||||
|
const logoSrc = ref("");
|
||||||
const collapsed = ref(false);
|
const collapsed = ref(false);
|
||||||
|
const user = ref({ first_name: "", last_name: "", avatar: "" });
|
||||||
const onCollapse = (value) => {
|
const onCollapse = (value) => {
|
||||||
collapsed.value = value;
|
collapsed.value = value;
|
||||||
emit("update:collapsed", value);
|
emit("update:collapsed", value);
|
||||||
};
|
};
|
||||||
|
function getSideBarSchema() {
|
||||||
function getSideBarSchema(){
|
|
||||||
|
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const IS_DEVLOP_MODE = config.public.IS_DEVLOP_MODE || 1;
|
const NUXT_PUBLIC_IS_DEVLOP_MODE = Number(
|
||||||
|
config.public?.NUXT_PUBLIC_IS_DEVLOP_MODE ?? 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
const filterMenu = (menu) => {
|
||||||
|
if (NUXT_PUBLIC_IS_DEVLOP_MODE === 1) return menu || [];
|
||||||
|
return (
|
||||||
|
menu?.filter(
|
||||||
|
(el) => el.develop === undefined || Number(el.develop) === 0,
|
||||||
|
) || []
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
let result = {}
|
const topMenuFiltered = filterMenu(props.sidebarItems?.topMenu);
|
||||||
|
const bottomMenuFiltered = filterMenu(props.sidebarItems?.bottomMenu);
|
||||||
|
|
||||||
result.topMenu = props.sidebarItems?.topMenu.filter((el) => (!el.develop || el.develop == IS_DEVLOP_MODE) )
|
return {
|
||||||
result.bottomMenu = props.sidebarItems?.bottomMenu.filter((el) => (!el.develop || el.develop == IS_DEVLOP_MODE) )
|
topMenu: topMenuFiltered,
|
||||||
|
bottomMenu: bottomMenuFiltered,
|
||||||
// console.log("SideBar IS_DEVLOP_MODE ", IS_DEVLOP_MODE, result);
|
};
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// تابع برای باز/بسته کردن نرم سایدبار
|
// تابع برای باز/بسته کردن نرم سایدبار
|
||||||
|
|
@ -140,8 +239,6 @@ const toggleSidebar = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const sidebarUI = {
|
const sidebarUI = {
|
||||||
width: "w-72",
|
|
||||||
collapsed: { width: "w-16" },
|
|
||||||
wrapper:
|
wrapper:
|
||||||
"z-30 h-[calc(100vh-64px)] top-16 rounded-r-2xl border-r border-gray-200/50 dark:border-dark-primary-800/50 backdrop-blur-sm transition-all duration-300",
|
"z-30 h-[calc(100vh-64px)] top-16 rounded-r-2xl border-r border-gray-200/50 dark:border-dark-primary-800/50 backdrop-blur-sm transition-all duration-300",
|
||||||
base: "backdrop-blur-sm transition-all duration-300 ease-out",
|
base: "backdrop-blur-sm transition-all duration-300 ease-out",
|
||||||
|
|
@ -158,6 +255,21 @@ const navigationUI = {
|
||||||
"bg-gradient-to-r from-blue-100 to-blue-50/30 dark:from-blue-900/30 dark:to-blue-900/10 text-blue-600 dark:text-blue-400 font-semibold shadow-sm",
|
"bg-gradient-to-r from-blue-100 to-blue-50/30 dark:from-blue-900/30 dark:to-blue-900/10 text-blue-600 dark:text-blue-400 font-semibold shadow-sm",
|
||||||
icon: { base: "transition-transform duration-300", active: "scale-110" },
|
icon: { base: "transition-transform duration-300", active: "scale-110" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
useSystemTheme.applyTheme();
|
||||||
|
setTimeout(() => {
|
||||||
|
logoSrc.value = useSystemTheme.logo.value;
|
||||||
|
}, 300);
|
||||||
|
const stored = localStorage.getItem("user");
|
||||||
|
if (stored) {
|
||||||
|
try {
|
||||||
|
user.value = JSON.parse(stored);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("خطا در خواندن کاربر از localStorage:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,124 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- تولبار ادیتور -->
|
<!-- تولبار ادیتور -->
|
||||||
<div v-if="editor" class="editor-toolbar">
|
<div class="editor-toolbar" v-if="editor">
|
||||||
<div class="toolbar-group">
|
<div
|
||||||
<button
|
class="toolbar-group"
|
||||||
@click="editor.chain().focus().toggleBold().run()"
|
v-for="(group, gIndex) in toolbarGroups"
|
||||||
:class="{ 'is-active': editor.isActive('bold') }"
|
:key="gIndex"
|
||||||
title="بولد"
|
|
||||||
>
|
>
|
||||||
<span class="toolbar-icon">𝐁</span>
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
@click="editor.chain().focus().toggleItalic().run()"
|
v-for="(btn, index) in group"
|
||||||
:class="{ 'is-active': editor.isActive('italic') }"
|
:key="index"
|
||||||
title="ایتالیک"
|
@click="btn.action(editor)"
|
||||||
|
:class="{ 'is-active': btn.isActive ? btn.isActive(editor) : false }"
|
||||||
|
:disabled="btn.disabled ? !btn.disabled(editor) : false"
|
||||||
|
:title="btn.title"
|
||||||
>
|
>
|
||||||
<span class="toolbar-icon">𝐼</span>
|
<span class="toolbar-icon">{{ btn.icon }}</span>
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().toggleUnderline().run()"
|
|
||||||
:class="{ 'is-active': editor.isActive('underline') }"
|
|
||||||
title="زیرخط"
|
|
||||||
>
|
|
||||||
<span class="toolbar-icon">𝑈</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().toggleStrike().run()"
|
|
||||||
:class="{ 'is-active': editor.isActive('strike') }"
|
|
||||||
title="خط خورده"
|
|
||||||
>
|
|
||||||
<span class="toolbar-icon">̶S̶</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="toolbar-group">
|
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
|
|
||||||
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
|
|
||||||
title="عنوان ۱"
|
|
||||||
>
|
|
||||||
H1
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
|
|
||||||
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
|
|
||||||
title="عنوان ۲"
|
|
||||||
>
|
|
||||||
H2
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
|
|
||||||
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
|
|
||||||
title="عنوان ۳"
|
|
||||||
>
|
|
||||||
H3
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="toolbar-group">
|
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().toggleBulletList().run()"
|
|
||||||
:class="{ 'is-active': editor.isActive('bulletList') }"
|
|
||||||
title="لیست بولتدار"
|
|
||||||
>
|
|
||||||
<span class="toolbar-icon">•</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().toggleOrderedList().run()"
|
|
||||||
:class="{ 'is-active': editor.isActive('orderedList') }"
|
|
||||||
title="لیست شمارهدار"
|
|
||||||
>
|
|
||||||
<span class="toolbar-icon">1.</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().toggleBlockquote().run()"
|
|
||||||
:class="{ 'is-active': editor.isActive('blockquote') }"
|
|
||||||
title="نقل قول"
|
|
||||||
>
|
|
||||||
<span class="toolbar-icon">"</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().toggleCodeBlock().run()"
|
|
||||||
:class="{ 'is-active': editor.isActive('codeBlock') }"
|
|
||||||
title="کد"
|
|
||||||
>
|
|
||||||
<span class="toolbar-icon">{ }</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="toolbar-group">
|
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().setDetails().run()"
|
|
||||||
:disabled="!editor.can().setDetails()"
|
|
||||||
title="افزودن جزییات"
|
|
||||||
>
|
|
||||||
<span class="toolbar-icon">📋</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().unsetDetails().run()"
|
|
||||||
:disabled="!editor.can().unsetDetails()"
|
|
||||||
title="حذف جزییات"
|
|
||||||
>
|
|
||||||
<span class="toolbar-icon">🗑️</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="toolbar-group">
|
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().undo().run()"
|
|
||||||
:disabled="!editor.can().undo()"
|
|
||||||
title="بازگشت"
|
|
||||||
>
|
|
||||||
<span class="toolbar-icon">↩</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().redo().run()"
|
|
||||||
:disabled="!editor.can().redo()"
|
|
||||||
title="جلو"
|
|
||||||
>
|
|
||||||
<span class="toolbar-icon">↪</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -155,9 +52,119 @@ function isList(text) {
|
||||||
(line) =>
|
(line) =>
|
||||||
line.trim().startsWith("*") ||
|
line.trim().startsWith("*") ||
|
||||||
line.trim().startsWith("-") ||
|
line.trim().startsWith("-") ||
|
||||||
/^\d+\./.test(line.trim())
|
/^\d+\./.test(line.trim()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const toolbarGroups = [
|
||||||
|
// گروه فرمت متن
|
||||||
|
[
|
||||||
|
{
|
||||||
|
title: "بولد",
|
||||||
|
icon: "𝐁",
|
||||||
|
action: (editor) => editor.chain().focus().toggleBold().run(),
|
||||||
|
isActive: (editor) => editor.isActive("bold"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "ایتالیک",
|
||||||
|
icon: "𝐼",
|
||||||
|
action: (editor) => editor.chain().focus().toggleItalic().run(),
|
||||||
|
isActive: (editor) => editor.isActive("italic"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "زیرخط",
|
||||||
|
icon: "𝑈",
|
||||||
|
action: (editor) => editor.chain().focus().toggleUnderline().run(),
|
||||||
|
isActive: (editor) => editor.isActive("underline"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "خط خورده",
|
||||||
|
icon: "̶S̶",
|
||||||
|
action: (editor) => editor.chain().focus().toggleStrike().run(),
|
||||||
|
isActive: (editor) => editor.isActive("strike"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// گروه هدینگها
|
||||||
|
[
|
||||||
|
{
|
||||||
|
title: "عنوان ۱",
|
||||||
|
icon: "H1",
|
||||||
|
action: (editor) =>
|
||||||
|
editor.chain().focus().toggleHeading({ level: 1 }).run(),
|
||||||
|
isActive: (editor) => editor.isActive("heading", { level: 1 }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "عنوان ۲",
|
||||||
|
icon: "H2",
|
||||||
|
action: (editor) =>
|
||||||
|
editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
||||||
|
isActive: (editor) => editor.isActive("heading", { level: 2 }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "عنوان ۳",
|
||||||
|
icon: "H3",
|
||||||
|
action: (editor) =>
|
||||||
|
editor.chain().focus().toggleHeading({ level: 3 }).run(),
|
||||||
|
isActive: (editor) => editor.isActive("heading", { level: 3 }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// گروه لیست و نقل قول
|
||||||
|
[
|
||||||
|
{
|
||||||
|
title: "لیست بولتدار",
|
||||||
|
icon: "•",
|
||||||
|
action: (editor) => editor.chain().focus().toggleBulletList().run(),
|
||||||
|
isActive: (editor) => editor.isActive("bulletList"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "لیست شمارهدار",
|
||||||
|
icon: "1.",
|
||||||
|
action: (editor) => editor.chain().focus().toggleOrderedList().run(),
|
||||||
|
isActive: (editor) => editor.isActive("orderedList"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "نقل قول",
|
||||||
|
icon: '"',
|
||||||
|
action: (editor) => editor.chain().focus().toggleBlockquote().run(),
|
||||||
|
isActive: (editor) => editor.isActive("blockquote"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "کد",
|
||||||
|
icon: "{ }",
|
||||||
|
action: (editor) => editor.chain().focus().toggleCodeBlock().run(),
|
||||||
|
isActive: (editor) => editor.isActive("codeBlock"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// گروه جزییات
|
||||||
|
[
|
||||||
|
{
|
||||||
|
title: "افزودن جزییات",
|
||||||
|
icon: "📋",
|
||||||
|
action: (editor) => editor.chain().focus().setDetails().run(),
|
||||||
|
disabled: (editor) => !editor.can().setDetails(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "حذف جزییات",
|
||||||
|
icon: "🗑️",
|
||||||
|
action: (editor) => editor.chain().focus().unsetDetails().run(),
|
||||||
|
disabled: (editor) => !editor.can().unsetDetails(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// گروه Undo/Redo
|
||||||
|
[
|
||||||
|
{
|
||||||
|
title: "بازگشت",
|
||||||
|
icon: "↩",
|
||||||
|
action: (editor) => editor.chain().focus().undo().run(),
|
||||||
|
disabled: (editor) => !editor.can().undo(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "جلو",
|
||||||
|
icon: "↪",
|
||||||
|
action: (editor) => editor.chain().focus().redo().run(),
|
||||||
|
disabled: (editor) => !editor.can().redo(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
function formatListToHtml(text) {
|
function formatListToHtml(text) {
|
||||||
const lines = text.split("\n").filter((line) => line.trim() !== "");
|
const lines = text.split("\n").filter((line) => line.trim() !== "");
|
||||||
|
|
@ -247,7 +254,7 @@ function generateDetailsHtml(item) {
|
||||||
|
|
||||||
html += `${escapeHtml(item.title) || "بدون عنوان"}`;
|
html += `${escapeHtml(item.title) || "بدون عنوان"}`;
|
||||||
if (item.link_url) {
|
if (item.link_url) {
|
||||||
html += `<a href="${item.link_url}">${item.link_label}</a>`;
|
html += `<a class="link-url" href="${item.link_url}">${item.link_label}</a>`;
|
||||||
}
|
}
|
||||||
html += "</summary>";
|
html += "</summary>";
|
||||||
|
|
||||||
|
|
@ -290,7 +297,7 @@ watch(
|
||||||
loadFromJson();
|
loadFromJson();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{ deep: true, immediate: true }
|
{ deep: true, immediate: true },
|
||||||
);
|
);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
editor.value = new Editor({
|
editor.value = new Editor({
|
||||||
|
|
@ -335,8 +342,8 @@ onBeforeUnmount(() => {
|
||||||
/* فقط متغیرهای غیررنگ (سایه، انحنا، RGB اصلی) – رنگها در main.css تعریف شدهاند */
|
/* فقط متغیرهای غیررنگ (سایه، انحنا، RGB اصلی) – رنگها در main.css تعریف شدهاند */
|
||||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||||
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
--shadow-md:
|
||||||
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
--radius: 0.75rem;
|
--radius: 0.75rem;
|
||||||
--radius-sm: 0.5rem;
|
--radius-sm: 0.5rem;
|
||||||
--radius-lg: 1rem;
|
--radius-lg: 1rem;
|
||||||
|
|
@ -358,7 +365,7 @@ onBeforeUnmount(() => {
|
||||||
border: 1px solid var(--color-primary-200);
|
border: 1px solid var(--color-primary-200);
|
||||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||||
margin: 2rem auto 0;
|
margin: 2rem auto 0;
|
||||||
max-width: 900px;
|
max-width: 1200px;
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -414,7 +421,7 @@ onBeforeUnmount(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-content {
|
.editor-content {
|
||||||
max-width: 900px;
|
max-width: 1200px;
|
||||||
margin: 0 auto 2rem;
|
margin: 0 auto 2rem;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
background: var(--color-white-normal);
|
background: var(--color-white-normal);
|
||||||
|
|
@ -426,8 +433,14 @@ onBeforeUnmount(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiptap {
|
.tiptap {
|
||||||
font-family: "Vazir", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
font-family:
|
||||||
Roboto, sans-serif;
|
"Vazir",
|
||||||
|
"Inter",
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
"Segoe UI",
|
||||||
|
Roboto,
|
||||||
|
sans-serif;
|
||||||
color: var(--color-dark-primary-700);
|
color: var(--color-dark-primary-700);
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
|
|
||||||
|
|
@ -546,14 +559,15 @@ onBeforeUnmount(() => {
|
||||||
padding: 1.25rem 1.25rem 1.25rem 3.5rem;
|
padding: 1.25rem 1.25rem 1.25rem 3.5rem;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
to right,
|
to right,
|
||||||
var(--color-dark-primary-50),
|
var(--color-primary-100),
|
||||||
var(--color-white-normal)
|
var(--color-white-normal)
|
||||||
);
|
);
|
||||||
|
color: var(--color-primary-700);
|
||||||
border-radius: var(--radius) var(--radius) 0 0;
|
border-radius: var(--radius) var(--radius) 0 0;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
color: var(--color-dark-primary-800);
|
color: var(--color-dark-primary-800);
|
||||||
cursor: pointer;
|
// cursor: pointer;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
@ -561,15 +575,6 @@ onBeforeUnmount(() => {
|
||||||
&::-webkit-details-marker {
|
&::-webkit-details-marker {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: linear-gradient(
|
|
||||||
to right,
|
|
||||||
var(--color-primary-100),
|
|
||||||
var(--color-white-normal)
|
|
||||||
);
|
|
||||||
color: var(--color-primary-700);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
|
|
@ -634,6 +639,10 @@ onBeforeUnmount(() => {
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
border-right: 2px dashed var(--color-primary-100);
|
border-right: 2px dashed var(--color-primary-100);
|
||||||
}
|
}
|
||||||
|
.link-url {
|
||||||
|
color: blue;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideDown {
|
@keyframes slideDown {
|
||||||
|
|
|
||||||
|
|
@ -24,36 +24,34 @@ const props = defineProps({
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
pagination: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const emit = defineEmits(["conflict-details"]);
|
const emit = defineEmits(["conflict-details"]);
|
||||||
const pagination = ref({
|
|
||||||
total: 0,
|
|
||||||
page: 1,
|
|
||||||
limit: 10,
|
|
||||||
});
|
|
||||||
const myContentSchema = computed(() => {
|
const myContentSchema = computed(() => {
|
||||||
const baseSchema = JSON.parse(JSON.stringify(myContentJson));
|
const baseSchema = JSON.parse(JSON.stringify(myContentJson));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...baseSchema,
|
...baseSchema,
|
||||||
items: props.listConflicts?.hits ?? [],
|
items: props.listConflicts?.hits ?? [],
|
||||||
pagination: {
|
pagination: props.pagination,
|
||||||
total: props.listConflicts?.total?.value ?? 0,
|
|
||||||
page: pagination.value.page,
|
|
||||||
limit: pagination.value.limit,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const headerTools = computed(() => [
|
const headerTools = ref([
|
||||||
|
[
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
|
{ key: "label", label: "کد:", style: "code-title" },
|
||||||
{
|
{
|
||||||
type: "dropdown",
|
type: "dropdown",
|
||||||
key: "dropdown",
|
key: "dropdown",
|
||||||
name: "refine_codes",
|
name: "refine_codes",
|
||||||
dropdownSchema: {
|
dropdownSchema: {
|
||||||
width: "18em",
|
width: "18em",
|
||||||
modelValue: null, // ✅ اینو بذار
|
modelValue: null,
|
||||||
optionAttribute: "title",
|
optionAttribute: "title",
|
||||||
valueAttribute: "value",
|
valueAttribute: "value",
|
||||||
searchable: false,
|
searchable: false,
|
||||||
|
|
@ -61,6 +59,110 @@ const headerTools = computed(() => [
|
||||||
items: refineCodes,
|
items: refineCodes,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{ key: "label", label: "نوع رابطه:", style: "code-title" },
|
||||||
|
{
|
||||||
|
type: "dropdown",
|
||||||
|
key: "dropdown",
|
||||||
|
name: "refine_codes",
|
||||||
|
dropdownSchema: {
|
||||||
|
width: "10em",
|
||||||
|
modelValue: null,
|
||||||
|
optionAttribute: "title",
|
||||||
|
valueAttribute: "value",
|
||||||
|
searchable: false,
|
||||||
|
placeholder: "انتخاب کنید",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "تعارض",
|
||||||
|
value: "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "تینت",
|
||||||
|
value: "2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "ییی",
|
||||||
|
value: "3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "ییی",
|
||||||
|
value: "4",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ key: "label", label: "وضعیت اعتبار:", style: "code-title" },
|
||||||
|
{
|
||||||
|
type: "dropdown",
|
||||||
|
key: "dropdown",
|
||||||
|
name: "refine_codes",
|
||||||
|
dropdownSchema: {
|
||||||
|
width: "10em",
|
||||||
|
modelValue: null,
|
||||||
|
optionAttribute: "title",
|
||||||
|
valueAttribute: "value",
|
||||||
|
searchable: false,
|
||||||
|
placeholder: "انتخاب کنید",
|
||||||
|
placeholder: "انتخاب کنید",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "تعارض",
|
||||||
|
value: "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "تینت",
|
||||||
|
value: "2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "ییی",
|
||||||
|
value: "3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "ییی",
|
||||||
|
value: "4",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
key: "autoComplation",
|
||||||
|
placeholder: "جستجوی ...",
|
||||||
|
debounceTime: 500,
|
||||||
|
// autocompleteUrl: "/repo/monir/complation/sanad",
|
||||||
|
minCharsForAutocomplete: 3,
|
||||||
|
maxHistoryItems: 20,
|
||||||
|
showSearchButton: false,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
label: "همه اجزاء",
|
||||||
|
value: "all",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "عنوان جلسه",
|
||||||
|
value: "title",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "عنوان دوره",
|
||||||
|
value: "branch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: " فقط فهرست",
|
||||||
|
value: "mindex",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: " فقط کدها",
|
||||||
|
value: "codes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: " دوره",
|
||||||
|
value: "advance",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "prevNext",
|
key: "prevNext",
|
||||||
name: "entityNavigator",
|
name: "entityNavigator",
|
||||||
|
|
@ -69,9 +171,17 @@ const headerTools = computed(() => [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function headerToolsAction({ action, data }) {
|
function headerToolsAction({ action, data }) {
|
||||||
|
if (action == "auto-complation") {
|
||||||
|
if (data.action == "complete-search") {
|
||||||
|
// console.log("data ==> ", data);
|
||||||
|
|
||||||
|
emit("my-header-tools-search", data.item);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (action === "prev-click") {
|
if (action === "prev-click") {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,6 +191,8 @@ function headerToolsAction({ action, data }) {
|
||||||
function myContentAction({ action, payload }) {
|
function myContentAction({ action, payload }) {
|
||||||
if (action === "conflict_Details") {
|
if (action === "conflict_Details") {
|
||||||
emit("conflict-details", payload);
|
emit("conflict-details", payload);
|
||||||
|
} else {
|
||||||
|
emit("my-content-action", { action, payload });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,6 @@
|
||||||
"icon": "i-lucide-list",
|
"icon": "i-lucide-list",
|
||||||
"active": true
|
"active": true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "RuleEdit",
|
|
||||||
"label": "احکام",
|
|
||||||
"key": "RuleEdit",
|
|
||||||
"icon": "i-lucide-scroll-text"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "RelationEdit",
|
"id": "RelationEdit",
|
||||||
"key": "RelationEdit",
|
"key": "RelationEdit",
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,12 @@
|
||||||
v-if="currentComponent"
|
v-if="currentComponent"
|
||||||
:activeTabKey="activeTabKey"
|
:activeTabKey="activeTabKey"
|
||||||
:listConflicts="listConflicts"
|
:listConflicts="listConflicts"
|
||||||
|
:conflictSelected="conflictSelected"
|
||||||
|
:pagination="pagination"
|
||||||
:conflict_id="conflictSelected?._id"
|
:conflict_id="conflictSelected?._id"
|
||||||
@conflict-details="conflictDetails"
|
@conflict-details="conflictDetails"
|
||||||
|
@my-content-action="myContentAction"
|
||||||
|
@my-header-tools-search="myHeaderToolsSearch"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -128,9 +132,15 @@ const getListConflict = async (textSearch = "", filterExtended = "") => {
|
||||||
request.url,
|
request.url,
|
||||||
request.payload_full,
|
request.payload_full,
|
||||||
);
|
);
|
||||||
console.log("res", res);
|
// console.log("res", res);
|
||||||
|
|
||||||
listConflicts.value = res.hits;
|
listConflicts.value = res.hits;
|
||||||
|
pagination.value.total = res.hits.total.value;
|
||||||
|
// reactiveSampleData.value.pagination = {
|
||||||
|
// total: res.hits.total.value,
|
||||||
|
// page: pagination.value.page,
|
||||||
|
// limit: pagination.value.limit,
|
||||||
|
// };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("خطا در دریافت داده:", err);
|
console.error("خطا در دریافت داده:", err);
|
||||||
}
|
}
|
||||||
|
|
@ -138,10 +148,28 @@ const getListConflict = async (textSearch = "", filterExtended = "") => {
|
||||||
function conflictDetails(event) {
|
function conflictDetails(event) {
|
||||||
conflictSelected.value = event.item;
|
conflictSelected.value = event.item;
|
||||||
activeTabKey.value = "RelationEdit";
|
activeTabKey.value = "RelationEdit";
|
||||||
console.log("event", event);
|
// console.log("event", event);
|
||||||
}
|
}
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getListConflict();
|
getListConflict();
|
||||||
activeTabKey.value = headerTabs.value[0]?.id || "";
|
activeTabKey.value = headerTabs.value[0]?.id || "";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function myContentAction({ action, payload }) {
|
||||||
|
// console.log("payload ==> ", payload);
|
||||||
|
// console.log("action ==> ", action);
|
||||||
|
if (action === "pagination") {
|
||||||
|
const { page, limit } = payload;
|
||||||
|
pagination.value.page = page;
|
||||||
|
pagination.value.limit = limit;
|
||||||
|
// console.log("pagination.value ==> ", pagination.value);
|
||||||
|
|
||||||
|
getListConflict();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function myHeaderToolsSearch(textSearch) {
|
||||||
|
console.log("dataqqqq ==> ", textSearch);
|
||||||
|
getListConflict(textSearch);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user