修改了搜索提示功能
This commit is contained in:
parent
9e91195bf0
commit
a2e6660ec7
@ -1,2 +1,2 @@
|
||||
# 生产环境变量
|
||||
VITE_API_BASE_URL=https://lux.llvho.com/ssapi
|
||||
VITE_API_BASE_URL=/api
|
@ -95,10 +95,34 @@ export default defineComponent({
|
||||
selectNext() {
|
||||
if (this.suggestions.length === 0) return;
|
||||
this.selectedIndex = Math.min(this.selectedIndex + 1, this.suggestions.length - 1);
|
||||
this.$nextTick(() => {
|
||||
this.scrollToSelected();
|
||||
});
|
||||
},
|
||||
selectPrev() {
|
||||
if (this.suggestions.length === 0) return;
|
||||
this.selectedIndex = Math.max(this.selectedIndex - 1, -1);
|
||||
this.$nextTick(() => {
|
||||
this.scrollToSelected();
|
||||
});
|
||||
},
|
||||
scrollToSelected() {
|
||||
if (this.selectedIndex < 0) return;
|
||||
const container = document.querySelector('.suggestions-container');
|
||||
const selectedItem = document.querySelector('.selected-item');
|
||||
if (!container || !selectedItem) return;
|
||||
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const selectedRect = selectedItem.getBoundingClientRect();
|
||||
|
||||
// 检查选中项是否在容器可视区域内
|
||||
if (selectedRect.bottom > containerRect.bottom) {
|
||||
// 如果选中项底部超出容器底部,向下滚动
|
||||
container.scrollTop += selectedRect.bottom - containerRect.bottom;
|
||||
} else if (selectedRect.top < containerRect.top) {
|
||||
// 如果选中项顶部超出容器顶部,向上滚动
|
||||
container.scrollTop -= containerRect.top - selectedRect.top;
|
||||
}
|
||||
},
|
||||
fetchSuggestions: _.debounce(async function() {
|
||||
// 双重检查确保空值时立即清除建议
|
||||
@ -110,7 +134,11 @@ export default defineComponent({
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const response = await fetch(`/api/completion?q=${encodeURIComponent(this.searchQuery)}`)
|
||||
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL
|
||||
const url = new URL(`${apiBaseUrl}/completion`, window.location.origin)
|
||||
url.searchParams.set('q', this.searchQuery)
|
||||
|
||||
const response = await fetch(url)
|
||||
const data = await response.json()
|
||||
// 确保当前searchQuery与请求时一致
|
||||
if (this.searchQuery.trim()) {
|
||||
|
@ -9,22 +9,40 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="search-box">
|
||||
<router-link to="/" class="home-logo">
|
||||
<div class="logo">
|
||||
<h1>Hokori Search</h1>
|
||||
</div>
|
||||
</router-link>
|
||||
<input
|
||||
type="text"
|
||||
v-model="newSearchQuery"
|
||||
placeholder="输入搜索关键词"
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
<el-button @click="handleSearch" type="warning">搜索</el-button>
|
||||
</div>
|
||||
|
||||
<h1 class="search-title">搜索: "{{ searchQuery }}</h1>
|
||||
|
||||
<h1 class="search-title">搜索: {{ searchQuery }}</h1>
|
||||
|
||||
<div class="search-box-wrapper">
|
||||
<div class="search-box">
|
||||
<router-link to="/" class="home-logo">
|
||||
<div class="logo">
|
||||
<h1>Hokori Search</h1>
|
||||
</div>
|
||||
</router-link>
|
||||
<input
|
||||
type="text"
|
||||
v-model="newSearchQuery"
|
||||
placeholder="输入搜索关键词"
|
||||
@keyup.enter="handleSearch"
|
||||
@keydown.down.prevent="selectNext"
|
||||
@keydown.up.prevent="selectPrev"
|
||||
/>
|
||||
<el-button @click="handleSearch" type="warning">搜索</el-button>
|
||||
</div>
|
||||
|
||||
<div v-if="suggestions.length > 0" class="suggestions-container">
|
||||
<div
|
||||
v-for="(item, index) in suggestions"
|
||||
:key="index"
|
||||
class="suggestion-item"
|
||||
:class="{'selected-item': index === selectedIndex}"
|
||||
@click="selectSuggestion(item)"
|
||||
>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-results">
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
@ -78,6 +96,7 @@
|
||||
import $ from 'jquery'
|
||||
import { defineComponent } from 'vue'
|
||||
import { useThemeStore } from '../stores/theme'
|
||||
import _ from 'lodash'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ResultsPage',
|
||||
@ -113,7 +132,10 @@ export default defineComponent({
|
||||
totalPages: 1,
|
||||
results: [],
|
||||
loading: false,
|
||||
error: null
|
||||
suggestionLoading: false,
|
||||
error: null,
|
||||
suggestions: [],
|
||||
selectedIndex: -1
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@ -137,6 +159,15 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
newSearchQuery: {
|
||||
handler() {
|
||||
this.selectedIndex = -1;
|
||||
this.fetchSuggestions();
|
||||
}
|
||||
},
|
||||
suggestions() {
|
||||
this.selectedIndex = -1;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -184,15 +215,82 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
if (!this.newSearchQuery.trim()) return
|
||||
const query = this.selectedIndex >= 0
|
||||
? this.suggestions[this.selectedIndex]
|
||||
: this.newSearchQuery.trim();
|
||||
|
||||
if (!query) return
|
||||
this.$router.push({
|
||||
path: '/search',
|
||||
query: {
|
||||
q: this.newSearchQuery,
|
||||
q: query,
|
||||
page: 1
|
||||
}
|
||||
})
|
||||
},
|
||||
selectSuggestion(item) {
|
||||
this.newSearchQuery = item;
|
||||
this.handleSearch();
|
||||
},
|
||||
selectNext() {
|
||||
if (this.suggestions.length === 0) return;
|
||||
this.selectedIndex = Math.min(this.selectedIndex + 1, this.suggestions.length - 1);
|
||||
this.$nextTick(() => {
|
||||
this.scrollToSelected();
|
||||
});
|
||||
},
|
||||
selectPrev() {
|
||||
if (this.suggestions.length === 0) return;
|
||||
this.selectedIndex = Math.max(this.selectedIndex - 1, -1);
|
||||
this.$nextTick(() => {
|
||||
this.scrollToSelected();
|
||||
});
|
||||
},
|
||||
scrollToSelected() {
|
||||
if (this.selectedIndex < 0) return;
|
||||
const container = document.querySelector('.suggestions-container');
|
||||
const selectedItem = document.querySelector('.selected-item');
|
||||
if (!container || !selectedItem) return;
|
||||
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const selectedRect = selectedItem.getBoundingClientRect();
|
||||
|
||||
// 检查选中项是否在容器可视区域内
|
||||
if (selectedRect.bottom > containerRect.bottom) {
|
||||
// 如果选中项底部超出容器底部,向下滚动
|
||||
container.scrollTop += selectedRect.bottom - containerRect.bottom;
|
||||
} else if (selectedRect.top < containerRect.top) {
|
||||
// 如果选中项顶部超出容器顶部,向上滚动
|
||||
container.scrollTop -= containerRect.top - selectedRect.top;
|
||||
}
|
||||
},
|
||||
fetchSuggestions: _.debounce(async function() {
|
||||
// 双重检查确保空值时立即清除建议
|
||||
const query = this.newSearchQuery.trim()
|
||||
if (!query) {
|
||||
this.suggestions = []
|
||||
return
|
||||
}
|
||||
|
||||
this.suggestionLoading = true
|
||||
try {
|
||||
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL
|
||||
const url = new URL(`${apiBaseUrl}/completion`, window.location.origin)
|
||||
url.searchParams.set('q', this.newSearchQuery)
|
||||
|
||||
const response = await fetch(url)
|
||||
const data = await response.json()
|
||||
// 确保当前searchQuery与请求时一致
|
||||
if (this.newSearchQuery.trim()) {
|
||||
this.suggestions = data.suggestions || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取搜索建议失败:', error)
|
||||
this.suggestions = []
|
||||
} finally {
|
||||
this.suggestionLoading = false
|
||||
}
|
||||
}, 300),
|
||||
goToPage(page) {
|
||||
if (page < 1 || page > this.totalPages) return
|
||||
this.$router.push({
|
||||
@ -211,10 +309,15 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-box {
|
||||
display: flex;
|
||||
.search-box-wrapper {
|
||||
position: relative;
|
||||
max-width: 600px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
@ -246,6 +349,7 @@ export default defineComponent({
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dark-mode .search-box input {
|
||||
@ -289,6 +393,14 @@ export default defineComponent({
|
||||
color: var(--uv-styles-color-text-default);
|
||||
}
|
||||
|
||||
.dark-mode .about a {
|
||||
color: #e6a23c
|
||||
}
|
||||
|
||||
.dark-mode .about a:visited {
|
||||
color: #f3f03f;
|
||||
}
|
||||
|
||||
.search-title {
|
||||
margin-top: 80px;
|
||||
text-align: center;
|
||||
@ -327,14 +439,59 @@ export default defineComponent({
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.dark-mode .result-item h3 {
|
||||
color: #80a0c2;
|
||||
.dark-mode .result-item h3 a {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.dark-mode .result-item h3 a:visited {
|
||||
color: #f3f03f;
|
||||
}
|
||||
|
||||
.result-item p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.suggestions-container {
|
||||
position: absolute;
|
||||
width: calc(100% - 220px); /* 调整宽度以匹配输入框 */
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
top: 100%; /* 位于搜索框正下方 */
|
||||
left: 200px; /* 与输入框左侧对齐 */
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
padding: 12px 20px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.suggestion-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.dark-mode .suggestions-container {
|
||||
background-color: #2d2d2d;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.dark-mode .suggestion-item:hover {
|
||||
background-color: #3d3d3d;
|
||||
}
|
||||
|
||||
.selected-item {
|
||||
background-color: #f0f0f0 !important;
|
||||
}
|
||||
|
||||
.dark-mode .selected-item {
|
||||
background-color: #4d4d4d !important;
|
||||
}
|
||||
|
||||
.loading, .error, .no-results {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
|
Loading…
x
Reference in New Issue
Block a user