Implement efficient article search functionality with AND/OR mode support. Update backend to include searchArticles service and enhance MongoDB indexing. Modify frontend to allow users to select search mode and improve search input layout.

This commit is contained in:
Omar Sánchez Pizarro
2026-01-20 19:08:27 +01:00
parent 16ec8dc2fa
commit deb3dc2b31
4 changed files with 292 additions and 66 deletions

View File

@@ -90,9 +90,9 @@ export default {
return response.data;
},
async searchArticles(query) {
async searchArticles(query, mode = 'AND') {
const response = await api.get('/articles/search', {
params: { q: query },
params: { q: query, mode: mode },
});
return response.data;
},

View File

@@ -20,27 +20,44 @@
<MagnifyingGlassIcon class="w-4 h-4 text-gray-500 dark:text-gray-400" />
Búsqueda
</label>
<div class="relative">
<input
v-model="searchQuery"
type="text"
placeholder="Buscar artículos por título, descripción, precio, localidad..."
class="input pr-10 pl-10"
@input="searchQuery = $event.target.value"
/>
<MagnifyingGlassIcon class="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<span v-if="searching" class="absolute right-3 top-1/2 transform -translate-y-1/2">
<div class="inline-block animate-spin rounded-full h-4 w-4 border-b-2 border-primary-600"></div>
</span>
<button
v-else-if="searchQuery"
@click="searchQuery = ''"
class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
title="Limpiar búsqueda"
>
<XMarkIcon class="w-5 h-5" />
</button>
<div class="flex gap-2">
<div class="relative flex-1">
<input
v-model="searchQuery"
type="text"
placeholder="Buscar artículos por título, descripción, precio, localidad..."
class="input pr-10 pl-10"
@input="searchQuery = $event.target.value"
/>
<MagnifyingGlassIcon class="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<span v-if="searching" class="absolute right-3 top-1/2 transform -translate-y-1/2">
<div class="inline-block animate-spin rounded-full h-4 w-4 border-b-2 border-primary-600"></div>
</span>
<button
v-else-if="searchQuery"
@click="searchQuery = ''"
class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
title="Limpiar búsqueda"
>
<XMarkIcon class="w-5 h-5" />
</button>
</div>
<div class="flex items-center gap-2">
<label class="text-xs text-gray-600 dark:text-gray-400 whitespace-nowrap">Modo:</label>
<select
v-model="searchMode"
class="input text-sm w-24"
title="AND: todas las palabras deben estar presentes | OR: al menos una palabra debe estar presente"
>
<option value="AND">AND</option>
<option value="OR">OR</option>
</select>
</div>
</div>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
<span v-if="searchMode === 'AND'">Todas las palabras deben estar presentes</span>
<span v-else>Al menos una palabra debe estar presente</span>
</p>
</div>
<!-- Separador -->
@@ -252,6 +269,7 @@ const selectedPlatform = ref('');
const selectedUsername = ref('');
const selectedWorker = ref('');
const searchQuery = ref('');
const searchMode = ref('AND'); // 'AND' o 'OR'
const autoPollInterval = ref(null);
const searchTimeout = ref(null);
const POLL_INTERVAL = 30000; // 30 segundos
@@ -397,7 +415,7 @@ async function searchArticles(query) {
searching.value = true;
try {
const data = await api.searchArticles(query);
const data = await api.searchArticles(query, searchMode.value);
let filtered = data.articles || [];
@@ -421,8 +439,8 @@ async function searchArticles(query) {
}
}
// Watch para buscar cuando cambie searchQuery (con debounce)
watch(searchQuery, (newQuery) => {
// Watch para buscar cuando cambie searchQuery o searchMode (con debounce)
watch([searchQuery, searchMode], ([newQuery]) => {
// Limpiar timeout anterior
if (searchTimeout.value) {
clearTimeout(searchTimeout.value);