提交 87fd5b20 authored 作者: 龙菲's avatar 龙菲

feat: 增加pc端的全文搜索和跳转

上级 e979a9b4
...@@ -38,3 +38,16 @@ export function getDocumentBookmarks(id) { ...@@ -38,3 +38,16 @@ export function getDocumentBookmarks(id) {
method: 'get' method: 'get'
}); });
} }
/**
* 全文搜索
* @returns {Promise} 返回文档详情数据
*/
export function searchText(params) {
return request({
url: `/search/text`,
method: 'get',
params
});
}
...@@ -13,12 +13,12 @@ declare module 'vue' { ...@@ -13,12 +13,12 @@ declare module 'vue' {
BookReader: typeof import('./components/BookReader/index.vue')['default'] BookReader: typeof import('./components/BookReader/index.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElHeader: typeof import('element-plus/es')['ElHeader'] ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElText: typeof import('element-plus/es')['ElText'] ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
FileUpload: typeof import('./components/FileUpload.vue')['default'] FileUpload: typeof import('./components/FileUpload.vue')['default']
GuideMobile: typeof import('./components/GuideMobile/index.vue')['default'] GuideMobile: typeof import('./components/GuideMobile/index.vue')['default']
...@@ -28,10 +28,10 @@ declare module 'vue' { ...@@ -28,10 +28,10 @@ declare module 'vue' {
IconEcosystem: typeof import('./components/icons/IconEcosystem.vue')['default'] IconEcosystem: typeof import('./components/icons/IconEcosystem.vue')['default']
IconSupport: typeof import('./components/icons/IconSupport.vue')['default'] IconSupport: typeof import('./components/icons/IconSupport.vue')['default']
IconTooling: typeof import('./components/icons/IconTooling.vue')['default'] IconTooling: typeof import('./components/icons/IconTooling.vue')['default']
IndexFirst: typeof import('./components/PdfViewer/index-first.vue')['default']
PdfViewer: typeof import('./components/PdfViewer/index.vue')['default'] PdfViewer: typeof import('./components/PdfViewer/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
SearchBar: typeof import('./components/SearchBar/index.vue')['default']
SvgIcon: typeof import('./components/SvgIcon/index.vue')['default'] SvgIcon: typeof import('./components/SvgIcon/index.vue')['default']
Test: typeof import('./components/test/index.vue')['default'] Test: typeof import('./components/test/index.vue')['default']
VanCell: typeof import('vant/es')['Cell'] VanCell: typeof import('vant/es')['Cell']
......
<template>
<el-drawer
v-loading="loading"
v-model="showDrawer"
title="全文搜索"
direction="ttb"
:before-close="handleClose"
:size="searchResult.length > 0 ? '500px' : '200px'"
>
<div class="wrapper">
<el-input
v-model="keyword"
@keyup.enter="onSearch"
placeholder="请输入关键词进行全文搜索"
clearable
>
<template #append>
<el-button :icon="Search" @click="onSearch" />
</template>
</el-input>
<div class="list">
<div
class="list-item"
v-for="value in searchResult"
:key="value.book_id"
@click="handleLocatePage(value.page_num)"
>
<div v-html="value.text" class="line-clamp-3"></div>
<el-tag type="primary" class="pageNum"
>第{{ value.page_num }}页</el-tag
>
</div>
</div>
</div>
</el-drawer>
</template>
<script lang="ts" setup>
import { searchText } from "@/api";
import { Search } from "@element-plus/icons-vue";
/* props */
/* emit */
const $emit = defineEmits(["locatePage"]);
/* data */
const showDrawer = ref(false);
const keyword = ref("");
const loading = ref(false);
const searchResult = ref([]);
/* computed */
/* methods */
function show() {
showDrawer.value = true;
}
function handleClose() {
showDrawer.value = false;
}
async function onSearch() {
console.log(keyword.value);
try {
loading.value = true;
const params = { q: keyword.value };
const res = await searchText(params);
const { results } = res;
searchResult.value = results.map((item) => {
return {
...item,
url: import.meta.env.VITE_API_BASE_URL + "/static/" + item.url,
};
});
} catch (error) {
console.log(error);
}
}
function handleLocatePage(param) {
$emit("locatePage", param);
showDirectory.value = false;
}
defineExpose({
show,
});
</script>
<style lang="scss" scoped>
.list {
padding: 20px 0;
height: 300px;
overflow: auto;
.list-item {
padding: 20px;
font-size: 14px;
color: #666;
border-bottom: 1px solid #eee;
display: flex;
cursor: pointer;
&:hover {
background-color: #eee;
}
}
}
:deep(.el-drawer__body) {
height: 500px;
}
.wrapper {
padding: 0 50px;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
word-wrap: break-word;
word-break: break-all;
}
</style>
...@@ -4,7 +4,15 @@ ...@@ -4,7 +4,15 @@
<el-button type="primary" link class="back" @click="goBack" <el-button type="primary" link class="back" @click="goBack"
>返回</el-button >返回</el-button
> >
微方志 {{ title || "微方志" }}
<el-button
type="text"
class="search"
:icon="Search"
@click="handleOpenSearch"
>
全文搜索
</el-button>
</div> </div>
<BookReader <BookReader
:pages="pages" :pages="pages"
...@@ -12,12 +20,15 @@ ...@@ -12,12 +20,15 @@
:pdfUrl="pdfUrl" :pdfUrl="pdfUrl"
:bookmarks="bookmarks" :bookmarks="bookmarks"
:enableTextLayer="true" :enableTextLayer="true"
ref="bookReaderRef"
/> />
<SearchBar ref="searchBarRef" @locatePage="locatePage" />
</div> </div>
</template> </template>
<script setup> <script setup>
import { getDocumentDetail, getDocumentBookmarks } from "@/api"; import { getDocumentDetail, getDocumentBookmarks } from "@/api";
import BookReader from "@/components/BookReader/index.vue"; import BookReader from "@/components/BookReader/index.vue";
import { Search } from "@element-plus/icons-vue";
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
/* props */ /* props */
...@@ -27,6 +38,9 @@ const title = ref(""); ...@@ -27,6 +38,9 @@ const title = ref("");
const pages = ref([]); const pages = ref([]);
const bookmarks = ref([]); const bookmarks = ref([]);
const pdfUrl = ref(""); const pdfUrl = ref("");
const searchBarRef = ref(null);
const bookReaderRef = ref(null);
/* computed */ /* computed */
/* methods */ /* methods */
async function loadData(id) { async function loadData(id) {
...@@ -63,6 +77,16 @@ async function fetchBookmarks(id) { ...@@ -63,6 +77,16 @@ async function fetchBookmarks(id) {
} }
} }
function handleOpenSearch() {
searchBarRef.value.show();
}
function locatePage(pageNum) {
if (bookReaderRef.value) {
bookReaderRef.value.goToPage(pageNum);
}
}
onMounted(() => { onMounted(() => {
const { id } = route.params; const { id } = route.params;
title.value = route.query.title; title.value = route.query.title;
...@@ -85,12 +109,19 @@ onMounted(() => { ...@@ -85,12 +109,19 @@ onMounted(() => {
align-items: center; align-items: center;
position: relative; position: relative;
height: 46px; height: 46px;
padding: 0 20px; padding: 0 40px;
.back { .back {
position: absolute; position: absolute;
left: 20px; left: 20px;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
} }
.search {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
color: #fff;
}
} }
</style> </style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论