feat: 番剧搜索框接口使用和交互完成

- 三种搜索触发来源
  - 输入中 debounce 600ms 后触发搜索
  - 按回车或点击搜索 icon 按钮后触发搜索
  - 切换 provider 源站时触发搜索
- 渲染的番剧信息列表样式待调整
- 搜索框 placeholder / provider 展示样式/i18n 待调整
This commit is contained in:
zthxxx
2023-09-07 00:54:17 +08:00
parent 6681590b7b
commit 544dda81c4
4 changed files with 67 additions and 70 deletions

3
.vscode/launch.json vendored
View File

@@ -10,6 +10,9 @@
"request": "launch", "request": "launch",
"cwd": "${workspaceFolder}/backend/src", "cwd": "${workspaceFolder}/backend/src",
"program": "main.py", "program": "main.py",
"env": {
"HOST": "127.0.0.1",
},
"console": "integratedTerminal", "console": "integratedTerminal",
"justMyCode": true "justMyCode": true
} }

View File

@@ -100,7 +100,7 @@ if __name__ == "__main__":
if os.getenv("IPV6"): if os.getenv("IPV6"):
host = "::" host = "::"
else: else:
host = "0.0.0.0" host = os.getenv("HOST", "0.0.0.0")
uvicorn.run( uvicorn.run(
app, app,
host=host, host=host,

View File

@@ -2,35 +2,39 @@
import {Down, Search} from '@icon-park/vue-next'; import {Down, Search} from '@icon-park/vue-next';
import {ref} from 'vue'; import {ref} from 'vue';
const props = withDefaults(
defineProps<{
value?: string;
placeholder?: string;
}>(),
{
value: '',
placeholder: '',
}
);
const emit = defineEmits(['update:value', 'click-search']); const inputValue = ref<string>('');
const {site, providers, bangumiInfo$ } = storeToRefs(useSearchStore()); const selectingProvider = ref<boolean>(false);
const {getProviders, onInput} = useSearchStore();
const {input$, provider, providers, getProviders, bangumiList } = useSearchStore();
/**
* - 输入中 debounce 600ms 后触发搜索
* - 按回车或点击搜索 icon 按钮后触发搜索
* - 切换 provider 源站时触发搜索
*/
function onInput (e: Event) {
const value = (e.target as HTMLInputElement).value;
input$.next(value);
inputValue.value = value;
}
function onSearch () {
input$.next(inputValue.value);
}
function onSelect (site: string) {
provider.value = site;
selectingProvider.value = !selectingProvider.value
onSearch();
}
onMounted(() => { onMounted(() => {
getProviders(); getProviders();
}); });
const selectedProvider = computed(() => {
return site.value || '';
});
const onSelect = ref(false);
function onSearch() {
emit('click-search', props.value);
}
</script> </script>
<template> <template>
@@ -58,9 +62,9 @@ function onSearch() {
<input <input
type="text" type="text"
:value="value" placeholder="Input to search"
:placeholder="placeholder"
input-reset input-reset
:value="inputValue"
@keyup.enter="onSearch" @keyup.enter="onSearch"
@input="onInput" @input="onInput"
/> />
@@ -72,27 +76,23 @@ function onSearch() {
w-100px w-100px
is-btn is-btn
class="provider-select" class="provider-select"
@click="() => onSelect = !onSelect" @click="() => selectingProvider = !selectingProvider"
> >
<div text-h3 truncate> <div text-h3 truncate>
{{ selectedProvider }} {{ provider }}
</div> </div>
<div class="provider-select"> <div class="provider-select">
<Down/> <Down/>
</div> </div>
</div> </div>
</div> </div>
<div v-show="onSelect" abs top-84px left-540px w-100px rounded-12px shadow bg-white z-99 overflow-hidden> <div v-show="selectingProvider" abs top-84px left-540px w-100px rounded-12px shadow bg-white z-99 overflow-hidden>
<div <div
v-for="i in providers" v-for="site in providers"
:key="i" :key="site"
hover:bg-theme-row hover:bg-theme-row
is-btn is-btn
@click="() => { @click="() => onSelect(site)"
site = i;
onSelect = false;
}"
> >
<div <div
text-h3 text-h3
@@ -101,19 +101,20 @@ function onSearch() {
p-12px p-12px
truncate truncate
> >
{{ i }} {{ site }}
</div> </div>
</div> </div>
</div> </div>
<div <div
v-if="bangumiInfo$" abs top-84px left-200px z-98
abs top-84px left-200px z-98> >
<ab-bangumi-card <ab-bangumi-card
:key="bangumiInfo$.id" v-for="(item, index) in bangumiList"
:poster="bangumiInfo$.poster_link ?? ''" :key="index"
:name="bangumiInfo$.official_title" :poster="item.poster_link ?? ''"
:season="bangumiInfo$.season" :name="item.official_title"
:group="bangumiInfo$.group_name" :season="item.season"
:group="item.group_name"
/> />
</div> </div>
</template> </template>

View File

@@ -1,19 +1,21 @@
import {ref} from 'vue';
import { import {
Subject, Subject,
debounceTime, debounceTime,
filter,
switchMap, switchMap,
tap, tap,
} from "rxjs"; } from "rxjs";
import type {BangumiRule} from "#/bangumi"; import type {BangumiRule} from "#/bangumi";
export const useSearchStore = defineStore('search', () => { export function useSearchStore () {
const input$ = new Subject<string>(); const bangumiList = ref<BangumiRule[]>([]);
const onInput = (e: Event) => input$.next(e.target.value);
const providers = ref<string[]>(['mikan', 'dmhy', 'nyaa']);
const site = ref<string>('mikan');
const bangumiInfo$ = apiSearch.get('魔女之旅'); const providers = ref<string[]>(['mikan', 'dmhy', 'nyaa']);
const provider = ref<string>('mikan');
const input$ = new Subject<string>();
const {execute: getProviders, onResult: onGetProvidersResult} = useApi( const {execute: getProviders, onResult: onGetProvidersResult} = useApi(
apiSearch.getProvider apiSearch.getProvider
@@ -23,34 +25,25 @@ export const useSearchStore = defineStore('search', () => {
providers.value = res; providers.value = res;
}); });
input$.pipe( const bangumiInfo$ = input$.pipe(
debounceTime(1000), debounceTime(600),
tap((input: string) => { tap(() => {
console.log('input', input) bangumiList.value = [];
// clear Search Result List
}), }),
switchMap((input: string) => apiSearch.get(input, site.value)), filter(Boolean),
tap((bangumi: BangumiRule) => console.log(bangumi)), switchMap((input: string) => apiSearch.get(input, provider.value)),
tap((bangumi: BangumiRule) => { tap((bangumi: BangumiRule) => {
console.log('bangumi', bangumi) bangumiList.value.push(bangumi);
// set bangumi info to Search Result List
}), }),
).subscribe({ )
complete() { .subscribe()
// end of stream, stop loading animation
}
}, (err) => {
console.error(err);
});
return { return {
input$, input$,
onInput,
bangumiInfo$, bangumiInfo$,
site, provider,
getProviders, getProviders,
providers, providers,
bangumiList,
}; };
}); }