|
@@ -0,0 +1,101 @@
|
|
|
+<template>
|
|
|
+ <popup v-bind="$attrs" title="全文搜索">
|
|
|
+ <search-input
|
|
|
+ v-model="detailStore.searchKey"
|
|
|
+ simple
|
|
|
+ @search="debounceSearch"
|
|
|
+ />
|
|
|
+
|
|
|
+ <DynamicScroller
|
|
|
+ v-if="list.length"
|
|
|
+ class="search-popup-inner"
|
|
|
+ :items="list"
|
|
|
+ key-field="cfi"
|
|
|
+ :min-item-size="100"
|
|
|
+ >
|
|
|
+ <template #default="{ item, index, active }">
|
|
|
+ <DynamicScrollerItem
|
|
|
+ :item="item"
|
|
|
+ :active="active"
|
|
|
+ :size-dependencies="[item.status, item.type]"
|
|
|
+ :data-index="index"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-html="item.excerpt"
|
|
|
+ class="search-popup-item"
|
|
|
+ @click="goToDetail(item.cfi)"
|
|
|
+ />
|
|
|
+ </DynamicScrollerItem>
|
|
|
+ </template>
|
|
|
+ </DynamicScroller>
|
|
|
+
|
|
|
+ <van-empty
|
|
|
+ v-if="!list.length && detailStore.searchKey && !loading"
|
|
|
+ description="搜索不到结果"
|
|
|
+ />
|
|
|
+ </popup>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, watch } from "vue";
|
|
|
+import { debounce } from "lodash";
|
|
|
+import { DynamicScroller, DynamicScrollerItem } from "vue-virtual-scroller";
|
|
|
+import SearchInput from "@/components/SearchInput.vue";
|
|
|
+import { useDetailStore, useEpubStore } from "@/stores";
|
|
|
+import Popup from "./Popup.vue";
|
|
|
+import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
|
|
|
+
|
|
|
+const detailStore = useDetailStore();
|
|
|
+const epubStore = useEpubStore();
|
|
|
+const list = ref([]);
|
|
|
+const loading = ref(false);
|
|
|
+
|
|
|
+const debounceSearch = debounce(async () => {
|
|
|
+ if (!detailStore.searchKey) {
|
|
|
+ list.value = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await epubStore.searchKeyword(detailStore.searchKey);
|
|
|
+ const reg = new RegExp(detailStore.searchKey, "gi");
|
|
|
+ list.value = res.map((item) => {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ excerpt: item.excerpt.replace(
|
|
|
+ reg,
|
|
|
+ `<span style="color:var(--van-primary-color);padding:4px">${detailStore.searchKey}</span>`
|
|
|
+ ),
|
|
|
+ };
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+}, 500);
|
|
|
+
|
|
|
+const goToDetail = (cfi) => {
|
|
|
+ epubStore.goToChapter(cfi);
|
|
|
+ detailStore.searchVisible = false;
|
|
|
+};
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => detailStore.searchKey,
|
|
|
+ () => {
|
|
|
+ loading.value = true;
|
|
|
+ debounceSearch();
|
|
|
+ }
|
|
|
+);
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.search-popup {
|
|
|
+ &-inner {
|
|
|
+ margin-top: 20px;
|
|
|
+ height: calc(100% - 96px - 20px);
|
|
|
+ }
|
|
|
+ &-item {
|
|
|
+ padding: 20px;
|
|
|
+ border-bottom: 1px solid var(--van-border-color);
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|