mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-04-24 10:31:09 +08:00
Merge pull request #448 from EstrellaXD/search-component
webui: 完成搜索组件的各类功能
This commit is contained in:
@@ -14,9 +14,13 @@ logger = logging.getLogger(__name__)
|
||||
class RSSAnalyser(TitleParser):
|
||||
def official_title_parser(self, bangumi: Bangumi, rss: RSSItem, torrent: Torrent):
|
||||
if rss.parser == "mikan":
|
||||
bangumi.poster_link, bangumi.official_title = self.mikan_parser(
|
||||
torrent.homepage
|
||||
)
|
||||
try:
|
||||
bangumi.poster_link, bangumi.official_title = self.mikan_parser(
|
||||
torrent.homepage
|
||||
)
|
||||
except AttributeError:
|
||||
logger.warning("[Parser] Mikan torrent has no homepage info.")
|
||||
pass
|
||||
elif rss.parser == "tmdb":
|
||||
tmdb_title, season, year, poster_link = self.tmdb_parser(
|
||||
bangumi.official_title, bangumi.season, settings.rss_parser.language
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
Observable,
|
||||
} from 'rxjs';
|
||||
|
||||
import type { BangumiRule } from '#/bangumi';
|
||||
import type { BangumiRule, BangumiAPI } from '#/bangumi';
|
||||
|
||||
export const apiSearch = {
|
||||
/**
|
||||
@@ -18,7 +18,12 @@ export const apiSearch = {
|
||||
|
||||
eventSource.onmessage = ev => {
|
||||
try {
|
||||
const data: BangumiRule = JSON.parse(ev.data);
|
||||
const apiData: BangumiAPI = JSON.parse(ev.data);
|
||||
const data: BangumiRule = {
|
||||
...apiData,
|
||||
filter: apiData.filter.split(','),
|
||||
rss_link: apiData.rss_link.split(','),
|
||||
}
|
||||
observer.next(data);
|
||||
} catch (error) {
|
||||
console.error('[/search/bangumi] Parse Error |', { keyword }, 'response:', ev.data)
|
||||
|
||||
@@ -13,8 +13,8 @@ const message = useMessage();
|
||||
const { getAll } = useBangumiStore();
|
||||
|
||||
const rss = ref<RSS>(rssTemplate);
|
||||
const searchRule = defineModel<BangumiRule>('searchRule', { default: null });
|
||||
const rule = ref<BangumiRule>(ruleTemplate);
|
||||
|
||||
const parserType = ['mikan', 'tmdb', 'parser'];
|
||||
|
||||
const window = reactive({
|
||||
@@ -33,6 +33,10 @@ watch(show, (val) => {
|
||||
setTimeout(() => {
|
||||
window.next = false;
|
||||
}, 300);
|
||||
} else if (val || searchRule.value) {
|
||||
window.next = true;
|
||||
window.rule = true;
|
||||
rule.value = searchRule.value;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -59,9 +63,6 @@ async function addRss() {
|
||||
window.loading = true;
|
||||
const data = await apiDownload.analysis(rss.value);
|
||||
window.loading = false;
|
||||
const response = await apiRSS.add(rss.value);
|
||||
message.success(response.msg_en);
|
||||
console.log('rss', response);
|
||||
rule.value = data;
|
||||
window.next = true;
|
||||
window.rule = true;
|
||||
|
||||
91
webui/src/components/ab-search-bar.vue
Normal file
91
webui/src/components/ab-search-bar.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<script lang="ts" setup>
|
||||
import {ref} from 'vue';
|
||||
import {vOnClickOutside} from "@vueuse/components";
|
||||
|
||||
defineEmits(['add-bangumi']);
|
||||
const showProvider = ref(false);
|
||||
const {
|
||||
providers,
|
||||
getProviders,
|
||||
provider,
|
||||
loading,
|
||||
onSearch,
|
||||
clearSearch,
|
||||
inputValue,
|
||||
bangumiList,
|
||||
} = useSearchStore();
|
||||
|
||||
onMounted(() => {
|
||||
getProviders();
|
||||
});
|
||||
|
||||
function onSelect(site: string) {
|
||||
provider.value = site;
|
||||
showProvider.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<ab-search
|
||||
v-model:inputValue="inputValue"
|
||||
:provider="provider"
|
||||
:loading="loading"
|
||||
@search="onSearch"
|
||||
@select="() => showProvider = !showProvider"
|
||||
/>
|
||||
<div
|
||||
v-show="showProvider"
|
||||
v-on-click-outside="() => showProvider = false"
|
||||
abs top-84px
|
||||
left-540px w-100px
|
||||
rounded-12px
|
||||
shadow
|
||||
bg-white
|
||||
z-99
|
||||
overflow-hidden
|
||||
>
|
||||
<div
|
||||
v-for="site in providers"
|
||||
:key="site"
|
||||
hover:bg-theme-row
|
||||
is-btn
|
||||
@click="() => onSelect(site)"
|
||||
>
|
||||
<div
|
||||
text-h3
|
||||
text-primary
|
||||
hover:text-white
|
||||
p-12px
|
||||
truncate
|
||||
>
|
||||
{{ site }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-on-click-outside="clearSearch"
|
||||
abs top-84px left-192px z-8
|
||||
>
|
||||
<TransitionGroup name="result" tag="ab-bangumi-card" space-y-12px>
|
||||
<!-- TODO: Transition Effect to fix. -->
|
||||
<ab-bangumi-card
|
||||
v-for="bangumi in bangumiList"
|
||||
:key="bangumi.id"
|
||||
:bangumi="bangumi"
|
||||
type="search"
|
||||
@click="() => $emit('add-bangumi', bangumi)"
|
||||
/>
|
||||
</TransitionGroup>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.result-enter-active {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,28 +1,25 @@
|
||||
<script lang="ts" setup>
|
||||
import {Down, Search} from '@icon-park/vue-next';
|
||||
import { vOnClickOutside } from '@vueuse/components'
|
||||
import { NSpin } from 'naive-ui';
|
||||
import { watch } from 'vue';
|
||||
|
||||
defineEmits(['click']);
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
provider: string;
|
||||
loading: boolean;
|
||||
}>(),
|
||||
{
|
||||
provider: '',
|
||||
loading: false,
|
||||
}
|
||||
);
|
||||
|
||||
const {
|
||||
onSelect,
|
||||
onSearch,
|
||||
inputValue,
|
||||
selectingProvider,
|
||||
provider,
|
||||
providers,
|
||||
getProviders,
|
||||
bangumiList
|
||||
} = useSearchStore();
|
||||
defineEmits(['select', 'search']);
|
||||
|
||||
function closeModal() {
|
||||
selectingProvider.value = false;
|
||||
bangumiList.value = [];
|
||||
}
|
||||
const inputValue = defineModel<string>('inputValue');
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getProviders();
|
||||
watch(inputValue, (val) => {
|
||||
console.log(val);
|
||||
});
|
||||
|
||||
</script>
|
||||
@@ -41,86 +38,47 @@ onMounted(() => {
|
||||
shadow-inner
|
||||
>
|
||||
<Search
|
||||
v-if="!loading"
|
||||
theme="outline"
|
||||
size="24"
|
||||
fill="#fff"
|
||||
is-btn
|
||||
btn-click
|
||||
@click="onSearch"
|
||||
@click="$emit('search')"
|
||||
/>
|
||||
<NSpin v-else :size="20"/>
|
||||
|
||||
<input
|
||||
v-model="inputValue"
|
||||
type="text"
|
||||
:placeholder="$t('topbar.search.placeholder')"
|
||||
input-reset
|
||||
@keyup.enter="onSearch"
|
||||
@keyup.enter="$emit('search')"
|
||||
/>
|
||||
<div
|
||||
h-full
|
||||
f-cer
|
||||
justify-between
|
||||
px-12px
|
||||
w-100px
|
||||
is-btn
|
||||
class="provider-select"
|
||||
@click="() => selectingProvider = !selectingProvider"
|
||||
>
|
||||
<div text-h3 truncate>
|
||||
{{ provider }}
|
||||
</div>
|
||||
<div class="provider-select">
|
||||
<Down/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-show="selectingProvider"
|
||||
v-on-click-outside="closeModal"
|
||||
abs top-84px
|
||||
left-540px w-100px
|
||||
rounded-12px shadow bg-white z-99 overflow-hidden>
|
||||
<div
|
||||
v-for="site in providers"
|
||||
:key="site"
|
||||
hover:bg-theme-row
|
||||
is-btn
|
||||
@click="() => onSelect(site)"
|
||||
>
|
||||
<div
|
||||
text-h3
|
||||
text-primary
|
||||
hover:text-white
|
||||
p-12px
|
||||
truncate
|
||||
h-full
|
||||
f-cer
|
||||
justify-between
|
||||
px-12px
|
||||
w-100px
|
||||
class="provider"
|
||||
is-btn
|
||||
@click="$emit('select')"
|
||||
>
|
||||
{{ site }}
|
||||
<div text-h3 truncate>
|
||||
{{ provider }}
|
||||
</div>
|
||||
<div class="provider">
|
||||
<Down/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
abs top-84px left-200px space-y-8px z-98
|
||||
>
|
||||
<ab-bangumi-card
|
||||
v-for="(item, index) in bangumiList"
|
||||
:key="index"
|
||||
v-on-click-outside="closeModal"
|
||||
:bangumi="item"
|
||||
type="search"
|
||||
transition-opacity
|
||||
@click="$emit('click', item)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.provider-select {
|
||||
.provider {
|
||||
background: #4E2A94;
|
||||
}
|
||||
|
||||
.list-enter-active, .list-leave-active {
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -7,12 +7,14 @@ import {
|
||||
Power,
|
||||
Refresh,
|
||||
} from '@icon-park/vue-next';
|
||||
import type {BangumiRule} from "#/bangumi";
|
||||
|
||||
const {t, changeLocale} = useMyI18n();
|
||||
const {running, onUpdate, offUpdate} = useAppInfo();
|
||||
|
||||
const show = ref(false);
|
||||
const showAccount = ref(false);
|
||||
const showAdd = ref(false);
|
||||
const searchRule = ref<BangumiRule>()
|
||||
|
||||
const {start, pause, shutdown, restart, resetRule} = useProgramStore();
|
||||
|
||||
@@ -52,13 +54,19 @@ const items = [
|
||||
label: () => t('topbar.profile.title'),
|
||||
icon: Me,
|
||||
handle: () => {
|
||||
show.value = true;
|
||||
showAccount.value = true;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const onSearchFocus = ref(false);
|
||||
|
||||
function addSearchResult(bangumi: BangumiRule) {
|
||||
showAdd.value = true;
|
||||
searchRule.value = bangumi;
|
||||
console.log('searchRule', searchRule.value);
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
onUpdate();
|
||||
});
|
||||
@@ -76,7 +84,7 @@ onUnmounted(() => {
|
||||
<img v-show="onSearchFocus === false" src="/images/AutoBangumi.svg" alt="AutoBangumi" h-24px rel top-2px/>
|
||||
</div>
|
||||
|
||||
<ab-search/>
|
||||
<ab-search-bar @add-bangumi="addSearchResult"/>
|
||||
</div>
|
||||
|
||||
<div ml-auto>
|
||||
@@ -88,8 +96,8 @@ onUnmounted(() => {
|
||||
></ab-status-bar>
|
||||
</div>
|
||||
|
||||
<ab-change-account v-model:show="show"></ab-change-account>
|
||||
<ab-change-account v-model:show="showAccount"></ab-change-account>
|
||||
|
||||
<ab-add-bangumi v-model:show="showAdd"></ab-add-bangumi>
|
||||
<ab-add-bangumi v-model:show="showAdd" v-model:searchRule="searchRule"></ab-add-bangumi>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -14,8 +14,11 @@ definePage({
|
||||
|
||||
<template>
|
||||
<div overflow-auto mt-12px flex-grow>
|
||||
<div flex="~ wrap" gap-y-12px gap-x-32px>
|
||||
<transition-group name="fade">
|
||||
<div>
|
||||
<TransitionGroup
|
||||
name="bangumi" tag="div"
|
||||
flex="~ wrap" gap-y-12px gap-x-32px>
|
||||
<!-- TODO: Transition Effect to fix. -->
|
||||
<ab-bangumi-card
|
||||
v-for="i in bangumi"
|
||||
:key="i.id"
|
||||
@@ -24,7 +27,7 @@ definePage({
|
||||
type="primary"
|
||||
@click="() => openEditPopup(i)"
|
||||
></ab-bangumi-card>
|
||||
</transition-group>
|
||||
</TransitionGroup>
|
||||
|
||||
<ab-edit-rule
|
||||
v-model:show="editRule.show"
|
||||
@@ -40,7 +43,7 @@ definePage({
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
.bangumi-enter-active, .bangumi-leave-active {
|
||||
transition: opacity .3s;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,8 +3,7 @@ import {
|
||||
EMPTY,
|
||||
Subject,
|
||||
debounceTime,
|
||||
switchMap,
|
||||
tap,
|
||||
switchMap, tap,
|
||||
} from "rxjs";
|
||||
import type {BangumiRule} from "#/bangumi";
|
||||
|
||||
@@ -12,15 +11,17 @@ import type {BangumiRule} from "#/bangumi";
|
||||
export function useSearchStore() {
|
||||
const bangumiList = ref<BangumiRule[]>([]);
|
||||
const inputValue = ref<string>('');
|
||||
const selectingProvider = ref<boolean>(false);
|
||||
|
||||
const providers = ref<string[]>(['mikan', 'dmhy', 'nyaa']);
|
||||
const provider = ref<string>('mikan');
|
||||
const provider = ref<string>(providers.value[0]);
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const input$ = new Subject<string>();
|
||||
|
||||
watch(inputValue, input => {
|
||||
input$.next(input);
|
||||
loading.value = !!input;
|
||||
})
|
||||
|
||||
const {execute: getProviders, onResult: onGetProvidersResult} = useApi(
|
||||
@@ -44,8 +45,8 @@ export function useSearchStore() {
|
||||
// 有输入更新后清理之前的搜索结果
|
||||
bangumiList.value = [];
|
||||
return input
|
||||
? apiSearch.get(input, provider.value)
|
||||
: EMPTY
|
||||
? apiSearch.get(input, provider.value)
|
||||
: EMPTY
|
||||
}),
|
||||
tap((bangumi: BangumiRule) => {
|
||||
bangumiList.value.push(bangumi);
|
||||
@@ -56,19 +57,18 @@ export function useSearchStore() {
|
||||
input$.next(inputValue.value);
|
||||
}
|
||||
|
||||
function onSelect(site: string) {
|
||||
provider.value = site;
|
||||
selectingProvider.value = !selectingProvider.value
|
||||
onSearch();
|
||||
function clearSearch() {
|
||||
inputValue.value = '';
|
||||
bangumiList.value = [];
|
||||
}
|
||||
|
||||
return {
|
||||
input$,
|
||||
bangumiInfo$,
|
||||
inputValue,
|
||||
selectingProvider,
|
||||
onSelect,
|
||||
onSearch,
|
||||
clearSearch,
|
||||
loading,
|
||||
provider,
|
||||
getProviders,
|
||||
providers,
|
||||
|
||||
1
webui/types/dts/components.d.ts
vendored
1
webui/types/dts/components.d.ts
vendored
@@ -24,6 +24,7 @@ declare module '@vue/runtime-core' {
|
||||
AbRssItem: typeof import('./../../src/components/ab-rss-item.vue')['default']
|
||||
AbRule: typeof import('./../../src/components/ab-rule.vue')['default']
|
||||
AbSearch: typeof import('./../../src/components/basic/ab-search.vue')['default']
|
||||
AbSearchBar: typeof import('./../../src/components/ab-search-bar.vue')['default']
|
||||
AbSelect: typeof import('./../../src/components/basic/ab-select.vue')['default']
|
||||
AbSetting: typeof import('./../../src/components/ab-setting.vue')['default']
|
||||
AbSidebar: typeof import('./../../src/components/layout/ab-sidebar.vue')['default']
|
||||
|
||||
Reference in New Issue
Block a user