mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-11-03 06:04:20 +08:00
refactor: refactor search logic & remove rxjs
This commit is contained in:
@@ -26,7 +26,6 @@
|
||||
"axios": "^0.27.2",
|
||||
"naive-ui": "^2.39.0",
|
||||
"pinia": "^2.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"vue": "^3.5.8",
|
||||
"vue-i18n": "^9.14.0",
|
||||
"vue-inline-svg": "^3.1.4",
|
||||
|
||||
3
webui/pnpm-lock.yaml
generated
3
webui/pnpm-lock.yaml
generated
@@ -26,9 +26,6 @@ importers:
|
||||
pinia:
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2(typescript@4.9.5)(vue@3.5.8(typescript@4.9.5))
|
||||
rxjs:
|
||||
specifier: ^7.8.1
|
||||
version: 7.8.1
|
||||
vue:
|
||||
specifier: ^3.5.8
|
||||
version: 3.5.8(typescript@4.9.5)
|
||||
|
||||
@@ -1,57 +1,60 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import type { Ref } from 'vue';
|
||||
import type { BangumiRule } from '#/bangumi';
|
||||
|
||||
import type { BangumiAPI, BangumiRule } from '#/bangumi';
|
||||
type EventSourceStatus = 'OPEN' | 'CONNECTING' | 'CLOSED';
|
||||
|
||||
export const apiSearch = {
|
||||
/**
|
||||
* 番剧搜索接口是 Server Send 流式数据,每条是一个 Bangumi JSON 字符串,
|
||||
* 使用接口方式是监听连接消息后,转为 Observable 配合外层调用时 switchMap 订阅使用
|
||||
*/
|
||||
get(keyword: string, site = 'mikan'): Observable<BangumiRule> {
|
||||
const bangumiInfo$ = new Observable<BangumiRule>((observer) => {
|
||||
const eventSource = new EventSource(
|
||||
`api/v1/search/bangumi?site=${site}&keywords=${encodeURIComponent(
|
||||
keyword
|
||||
)}`,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
get() {
|
||||
const eventSource = ref(null) as Ref<EventSource | null>;
|
||||
const status = ref<EventSourceStatus>('CLOSED');
|
||||
const data = ref<BangumiRule[]>([]);
|
||||
|
||||
eventSource.onmessage = (ev) => {
|
||||
try {
|
||||
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
|
||||
);
|
||||
}
|
||||
const keyword = ref('');
|
||||
const provider = ref('');
|
||||
|
||||
const close = () => {
|
||||
if (eventSource.value) {
|
||||
eventSource.value.close();
|
||||
eventSource.value = null;
|
||||
status.value = 'CLOSED';
|
||||
}
|
||||
};
|
||||
|
||||
const _init = () => {
|
||||
status.value = 'CONNECTING';
|
||||
|
||||
const url = `api/v1/search/bangumi?site=${
|
||||
provider.value
|
||||
}&keywords=${encodeURIComponent(keyword.value)}`;
|
||||
|
||||
const es = new EventSource(url, { withCredentials: true });
|
||||
eventSource.value = es;
|
||||
es.onopen = () => {
|
||||
status.value = 'OPEN';
|
||||
};
|
||||
|
||||
eventSource.onerror = (ev) => {
|
||||
console.error(
|
||||
'[/search/bangumi] Server Error |',
|
||||
{ keyword },
|
||||
'error:',
|
||||
ev
|
||||
);
|
||||
// 目前后端搜索完成关闭连接时会触发 error 事件,前端手动调用 close 不再自动重连
|
||||
eventSource.close();
|
||||
es.onmessage = (e) => {
|
||||
const newData = JSON.parse(e.data) as BangumiRule;
|
||||
data.value = [...data.value, newData];
|
||||
};
|
||||
|
||||
return () => {
|
||||
eventSource.close();
|
||||
es.onerror = (err) => {
|
||||
console.error('EventSource error:', err);
|
||||
close();
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return bangumiInfo$;
|
||||
const open = () => {
|
||||
data.value = [];
|
||||
_init();
|
||||
};
|
||||
|
||||
return {
|
||||
keyword,
|
||||
provider,
|
||||
status,
|
||||
data,
|
||||
open,
|
||||
close,
|
||||
};
|
||||
},
|
||||
|
||||
async getProvider() {
|
||||
|
||||
@@ -13,10 +13,10 @@ defineEmits<{
|
||||
(e: 'add-bangumi', bangumiRule: BangumiRule): void;
|
||||
}>();
|
||||
|
||||
const { providers, provider, loading, inputValue, bangumiList } = storeToRefs(
|
||||
const { providers, provider, loading, keyword, searchData } = storeToRefs(
|
||||
useSearchStore()
|
||||
);
|
||||
const { getProviders, onSearch, clearSearch } = useSearchStore();
|
||||
const { getProviders, clearSearch, openSearch } = useSearchStore();
|
||||
|
||||
onMounted(() => {
|
||||
getProviders();
|
||||
@@ -54,20 +54,20 @@ onMounted(() => {
|
||||
z-5
|
||||
>
|
||||
<ab-search
|
||||
v-model:inputValue="inputValue"
|
||||
v-model:input-value="keyword"
|
||||
v-model:provider="provider"
|
||||
:providers="providers"
|
||||
:loading="loading"
|
||||
@search="onSearch"
|
||||
@search="openSearch"
|
||||
/>
|
||||
|
||||
<div class="search-list" space-y-10 overflow-auto>
|
||||
<transition-group name="fade-list">
|
||||
<template v-for="bangumi in bangumiList" :key="bangumi.order">
|
||||
<template v-for="i in searchData" :key="i.rss_link">
|
||||
<ab-bangumi-card
|
||||
:bangumi="bangumi.value"
|
||||
:bangumi="i"
|
||||
type="search"
|
||||
@click="() => $emit('add-bangumi', bangumi.value)"
|
||||
@click="() => $emit('add-bangumi', i)"
|
||||
/>
|
||||
</template>
|
||||
</transition-group>
|
||||
|
||||
@@ -1,74 +1,38 @@
|
||||
import { ref } from 'vue';
|
||||
import { EMPTY, Subject, debounceTime, switchMap, tap } from 'rxjs';
|
||||
import type { BangumiRule, SearchResult } from '#/bangumi';
|
||||
|
||||
export const useSearchStore = defineStore('search', () => {
|
||||
const bangumiList = ref<SearchResult[]>([]);
|
||||
const inputValue = ref<string>('');
|
||||
|
||||
const providers = ref<string[]>(['mikan', 'dmhy', 'nyaa']);
|
||||
const provider = ref<string>(providers.value[0]);
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const {
|
||||
keyword,
|
||||
provider,
|
||||
open: openSearch,
|
||||
close: closeSearch,
|
||||
data: searchData,
|
||||
status,
|
||||
} = apiSearch.get();
|
||||
|
||||
const input$ = new Subject<string>();
|
||||
provider.value = providers.value[0];
|
||||
|
||||
watch(inputValue, (input) => {
|
||||
input$.next(input);
|
||||
loading.value = !!input;
|
||||
});
|
||||
const loading = computed(() => status.value !== 'CLOSED');
|
||||
|
||||
function getProviders() {
|
||||
apiSearch.getProvider().then((res) => {
|
||||
providers.value = res;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* - 输入中 debounce 600ms 后触发搜索
|
||||
* - 按回车或点击搜索 icon 按钮后触发搜索
|
||||
* - 切换 provider 源站时触发搜索
|
||||
*/
|
||||
|
||||
const bangumiInfo$ = input$
|
||||
.pipe(
|
||||
debounceTime(600),
|
||||
// switchMap 把输入 keyword 查询为 bangumiInfo$ 流,多次输入自动取消并停止前一次查询
|
||||
switchMap((input: string) => {
|
||||
// 有输入更新后清理之前的搜索结果
|
||||
bangumiList.value = [];
|
||||
return input ? apiSearch.get(input, provider.value) : EMPTY;
|
||||
}),
|
||||
tap((bangumi: BangumiRule) => {
|
||||
const result: SearchResult = {
|
||||
order: bangumiList.value.length + 1,
|
||||
value: bangumi,
|
||||
};
|
||||
bangumiList.value.push(result);
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
function onSearch() {
|
||||
input$.next(inputValue.value);
|
||||
async function getProviders() {
|
||||
providers.value = await apiSearch.getProvider();
|
||||
provider.value = providers.value[0];
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
inputValue.value = '';
|
||||
bangumiList.value = [];
|
||||
keyword.value = '';
|
||||
}
|
||||
|
||||
return {
|
||||
input$,
|
||||
bangumiInfo$,
|
||||
inputValue,
|
||||
keyword,
|
||||
loading,
|
||||
provider,
|
||||
providers,
|
||||
bangumiList,
|
||||
searchData,
|
||||
|
||||
onSearch,
|
||||
clearSearch,
|
||||
getProviders,
|
||||
openSearch,
|
||||
closeSearch,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user