From a2e6660ec7dec5ef73f84fd81eeac4a36313fa5d Mon Sep 17 00:00:00 2001 From: Yakumo Hokori Date: Sun, 27 Apr 2025 03:02:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.production | 2 +- src/components/Home.vue | 30 +++++- src/components/Results.vue | 201 +++++++++++++++++++++++++++++++++---- 3 files changed, 209 insertions(+), 24 deletions(-) diff --git a/.env.production b/.env.production index aad0739..26c8667 100644 --- a/.env.production +++ b/.env.production @@ -1,2 +1,2 @@ # 生产环境变量 -VITE_API_BASE_URL=https://lux.llvho.com/ssapi \ No newline at end of file +VITE_API_BASE_URL=/api \ No newline at end of file diff --git a/src/components/Home.vue b/src/components/Home.vue index a365e51..0917791 100644 --- a/src/components/Home.vue +++ b/src/components/Home.vue @@ -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()) { diff --git a/src/components/Results.vue b/src/components/Results.vue index 4868d91..a7ecdae 100644 --- a/src/components/Results.vue +++ b/src/components/Results.vue @@ -9,22 +9,40 @@ /> - -

搜索: "{{ searchQuery }}

+ +

搜索: {{ searchQuery }}

+ +
+ + +
+
+ {{ item }} +
+
+
加载中...
@@ -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({