All checks were successful
continuous-integration/drone/push Build is passing
168 lines
4.8 KiB
Vue
168 lines
4.8 KiB
Vue
<template>
|
||
<div class="w-full sm:w-4/5 dark:text-neutral-300">
|
||
<div
|
||
class="bg-white flex flex-col lg:flex-row justify-between items-center p-3 lg:p-5 dark:bg-gray-800 gap-3 lg:gap-4"
|
||
>
|
||
<div class="flex flex-row w-full lg:w-auto">
|
||
<div class="relative w-2/3">
|
||
<img
|
||
v-if="isDark"
|
||
src="https://img.icons8.com/?size=100&id=WwWusvLMTFd7&format=png&color=000000"
|
||
class="absolute top-1/2 -translate-y-1/2 left-3 h-5"
|
||
alt=""
|
||
/>
|
||
<img
|
||
v-else
|
||
src="https://img.icons8.com/?size=100&id=zR5EBMqZTIBz&format=png&color=000000"
|
||
class="absolute top-1/2 -translate-y-1/2 left-3 h-5"
|
||
alt=""
|
||
/>
|
||
<input
|
||
v-model="searchQuery"
|
||
type="text"
|
||
placeholder="Поиск..."
|
||
class="w-full h-12 dark:bg-gray-900 border-slate-100 shadow rounded-xl p-3 pl-11"
|
||
/>
|
||
</div>
|
||
<select
|
||
v-model="activeCategory"
|
||
@change="onFilterChange"
|
||
class="w-1/3 h-12 dark:bg-gray-900 border-slate-100 shadow rounded-xl p-3 ml-2"
|
||
>
|
||
<option value="all">Все</option>
|
||
<option
|
||
v-for="category in categories"
|
||
:key="category"
|
||
:value="category"
|
||
>
|
||
{{ category }}
|
||
</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div
|
||
class="flex flex-row w-full lg:w-auto gap-2 p-2 bg-gray-50 dark:bg-gray-700 rounded-xl border border-gray-200 dark:border-gray-600"
|
||
>
|
||
<input
|
||
v-model="newSourceUrl"
|
||
type="text"
|
||
placeholder="https://example.com"
|
||
class="flex-1 h-12 dark:bg-gray-900 border-slate-100 shadow rounded-xl p-3 min-w-0"
|
||
/>
|
||
|
||
<select
|
||
v-model="newSourceCategory"
|
||
class="w-28 h-12 dark:bg-gray-900 border-slate-100 shadow rounded-xl p-3 min-w-30"
|
||
>
|
||
<option value="" disabled>Категория</option>
|
||
<option
|
||
v-for="category in categories"
|
||
:key="category"
|
||
:value="category"
|
||
>
|
||
{{ category }}
|
||
</option>
|
||
</select>
|
||
|
||
<button
|
||
type="button"
|
||
@click="addSource"
|
||
class="w-20 h-12 bg-green-600 text-white px-2 rounded-xl shadow hover:bg-green-700 cursor-pointer whitespace-nowrap"
|
||
>
|
||
+ Add
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="p-4 grid gap-2 grid-cols-1 xl:grid-cols-2 2xl:grid-cols-3">
|
||
<div
|
||
v-for="source in filteredSources"
|
||
:key="source.url"
|
||
class="mb-4 hover:-translate-y-2 hover:shadow-2xl transition"
|
||
>
|
||
<SourceCard
|
||
:source="source"
|
||
@sourceStarted="handleSourceStarted"
|
||
@sourceDeleted="handleSourceDeleted"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, computed } from 'vue'
|
||
import SourceCard from './Istochnik_one_kard.vue'
|
||
import { useDarkMode } from '@/composables/useDarkMode.js'
|
||
import { fetchCategories, fetchSources, addSource as apiAddSource } from '@/services/sourceService.js'
|
||
|
||
const { isDark } = useDarkMode()
|
||
|
||
const newSourceUrl = ref('')
|
||
const newSourceCategory = ref('')
|
||
const categories = ref([])
|
||
const searchQuery = ref('')
|
||
const sources = ref([])
|
||
const activeCategory = ref('all')
|
||
|
||
const filteredSources = computed(() => {
|
||
if (!searchQuery.value) return sources.value
|
||
const q = searchQuery.value.toLowerCase()
|
||
return sources.value.filter(
|
||
(source) =>
|
||
source.url.toLowerCase().includes(q) ||
|
||
source.promt.toLowerCase().includes(q),
|
||
)
|
||
})
|
||
|
||
async function loadCategories() {
|
||
try {
|
||
categories.value = await fetchCategories()
|
||
} catch (err) {
|
||
console.error('Ошибка загрузки категорий:', err)
|
||
}
|
||
}
|
||
|
||
async function loadSources(category = 'all') {
|
||
try {
|
||
sources.value = await fetchSources(category)
|
||
} catch (err) {
|
||
console.error('Ошибка загрузки источников:', err)
|
||
}
|
||
}
|
||
|
||
async function addSource() {
|
||
if (!newSourceUrl.value.trim() || !newSourceCategory.value) {
|
||
alert('Заполните все поля')
|
||
return
|
||
}
|
||
|
||
try {
|
||
await apiAddSource(newSourceUrl.value, newSourceCategory.value)
|
||
newSourceUrl.value = ''
|
||
newSourceCategory.value = ''
|
||
await loadSources(activeCategory.value)
|
||
} catch (err) {
|
||
console.error('Ошибка добавления источника:', err)
|
||
alert('Ошибка при добавлении источника')
|
||
}
|
||
}
|
||
|
||
function onFilterChange() {
|
||
loadSources(activeCategory.value)
|
||
}
|
||
|
||
function handleSourceStarted(url) {
|
||
console.log('Парсинг запущен для:', url)
|
||
}
|
||
|
||
function handleSourceDeleted(deletedUrl) {
|
||
sources.value = sources.value.filter((source) => source.url !== deletedUrl)
|
||
}
|
||
|
||
onMounted(() => {
|
||
loadCategories()
|
||
loadSources('all')
|
||
})
|
||
</script>
|