refactor: 重构搜索页
All checks were successful
deploy to server / build-and-deploy (push) Successful in 2m56s
All checks were successful
deploy to server / build-and-deploy (push) Successful in 2m56s
- 提取页面部分为SearchHeader与SearchTabs组件
This commit is contained in:
78
app/components/pages/search/SearchHeader.vue
Normal file
78
app/components/pages/search/SearchHeader.vue
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search-header">
|
||||||
|
<h1 class="page-title">{{ $t('search.title') }}</h1>
|
||||||
|
<div class="search-bar">
|
||||||
|
<el-input
|
||||||
|
v-model="keyword"
|
||||||
|
class="search-input"
|
||||||
|
:placeholder="$t('search-placeholder')"
|
||||||
|
:prefix-icon="Search"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="navigateToQuery(keyword)"
|
||||||
|
@clear="handleClear"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
class="search-button"
|
||||||
|
@click="navigateToQuery(keyword)"
|
||||||
|
>
|
||||||
|
{{ $t('search.search-button') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Search } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
|
const keyword = defineModel<string>({ default: '' });
|
||||||
|
const localePath = useLocalePath();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const navigateToQuery = (value: string) => {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed) return;
|
||||||
|
navigateTo({
|
||||||
|
path: localePath('/search'),
|
||||||
|
query: { query: trimmed },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClear = () => {
|
||||||
|
keyword.value = '';
|
||||||
|
router.replace(localePath({ path: '/search' }));
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 2.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-button {
|
||||||
|
height: 50px;
|
||||||
|
width: 100px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
49
app/components/pages/search/SearchTabs.vue
Normal file
49
app/components/pages/search/SearchTabs.vue
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<el-tabs v-model="activeTab">
|
||||||
|
<el-tab-pane
|
||||||
|
v-for="tab in tabs"
|
||||||
|
:key="tab.name"
|
||||||
|
:label="`${tab.label}(${resultCount[tab.name] || 0})`"
|
||||||
|
:name="tab.name"
|
||||||
|
>
|
||||||
|
<SearchResults
|
||||||
|
v-model:current-page="currentPage"
|
||||||
|
:search-items="searchItems"
|
||||||
|
:category="tab.name === 'all' ? undefined : tab.name"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{
|
||||||
|
// resultCount: Record<string, number>;
|
||||||
|
searchItems: SearchItemView[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{ name: 'all', label: '全部' },
|
||||||
|
{ name: 'product', label: '产品' },
|
||||||
|
{ name: 'solution', label: '解决方案' },
|
||||||
|
{ name: 'question', label: '相关问题' },
|
||||||
|
{ name: 'document', label: '文档资料' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const resultCount = computed(() => {
|
||||||
|
const map: Record<string, number> = { all: props.searchItems.length };
|
||||||
|
for (const item of props.searchItems) {
|
||||||
|
map[item.type] = (map[item.type] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分类控制
|
||||||
|
const activeTab = ref('all');
|
||||||
|
|
||||||
|
// 分页控制
|
||||||
|
const currentPage = ref(1);
|
||||||
|
|
||||||
|
watch(activeTab, () => {
|
||||||
|
currentPage.value = 1; // 重置页码
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -1,80 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="search-page">
|
<div class="search-page">
|
||||||
<div class="search-header">
|
<search-header v-model="keyword" />
|
||||||
<h1 class="page-title">{{ $t('search.title') }}</h1>
|
|
||||||
<div class="search-bar">
|
|
||||||
<el-input
|
|
||||||
v-model="keyword"
|
|
||||||
class="search-input"
|
|
||||||
:placeholder="$t('search-placeholder')"
|
|
||||||
:prefix-icon="Search"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="navigateToQuery(keyword)"
|
|
||||||
@clear="handleClear"
|
|
||||||
/>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
class="search-button"
|
|
||||||
@click="navigateToQuery(keyword)"
|
|
||||||
>
|
|
||||||
{{ $t('search.search-button') }}
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="loading" class="search-state">
|
<div v-if="loading" class="search-state">
|
||||||
<el-skeleton :rows="4" animated />
|
<el-skeleton :rows="4" animated />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="hasResults" class="search-results">
|
<search-tabs v-else-if="hasResults" :search-items="searchItems" />
|
||||||
<el-tabs v-model="activeTab">
|
|
||||||
<el-tab-pane :label="`全部(${resultCount['all']})`" name="all">
|
|
||||||
<search-results
|
|
||||||
v-model:current-page="currentPage"
|
|
||||||
:search-items="searchItems"
|
|
||||||
/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane
|
|
||||||
:label="`产品(${resultCount['product'] || 0})`"
|
|
||||||
name="product"
|
|
||||||
>
|
|
||||||
<search-results
|
|
||||||
v-model:current-page="currentPage"
|
|
||||||
:search-items="searchItems"
|
|
||||||
category="product"
|
|
||||||
/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane
|
|
||||||
:label="`解决方案(${resultCount['solution'] || 0})`"
|
|
||||||
name="solution"
|
|
||||||
>
|
|
||||||
<search-results
|
|
||||||
v-model:current-page="currentPage"
|
|
||||||
:search-items="searchItems"
|
|
||||||
category="solution"
|
|
||||||
/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane
|
|
||||||
:label="`相关问题(${resultCount['question'] || 0})`"
|
|
||||||
name="question"
|
|
||||||
>
|
|
||||||
<search-results
|
|
||||||
v-model:current-page="currentPage"
|
|
||||||
:search-items="searchItems"
|
|
||||||
category="question"
|
|
||||||
/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane
|
|
||||||
:label="`文档资料(${resultCount['document'] || 0})`"
|
|
||||||
name="document"
|
|
||||||
>
|
|
||||||
<search-results
|
|
||||||
v-model:current-page="currentPage"
|
|
||||||
:search-items="searchItems"
|
|
||||||
category="document"
|
|
||||||
/>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
</div>
|
|
||||||
<div v-else class="search-state">
|
<div v-else class="search-state">
|
||||||
<el-empty
|
<el-empty
|
||||||
:description="
|
:description="
|
||||||
@ -88,8 +18,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Search } from '@element-plus/icons-vue';
|
|
||||||
|
|
||||||
// i18n相关
|
// i18n相关
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { getDirectusLocale } = useLocalizations();
|
const { getDirectusLocale } = useLocalizations();
|
||||||
@ -97,8 +25,6 @@
|
|||||||
|
|
||||||
// 路由相关
|
// 路由相关
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
|
||||||
const localePath = useLocalePath();
|
|
||||||
|
|
||||||
// 搜索相关
|
// 搜索相关
|
||||||
const { search } = useMeilisearch();
|
const { search } = useMeilisearch();
|
||||||
@ -144,44 +70,10 @@
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(searchItems.value);
|
|
||||||
|
|
||||||
// 分类控制
|
|
||||||
const activeTab = ref('all');
|
|
||||||
const resultCount = computed(() => {
|
|
||||||
const map: Record<string, number> = { all: searchItems.value.length };
|
|
||||||
for (const item of searchItems.value) {
|
|
||||||
map[item.type] = (map[item.type] ?? 0) + 1;
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 分页控制
|
|
||||||
const currentPage = ref(1);
|
|
||||||
|
|
||||||
const hasResults = computed(() =>
|
const hasResults = computed(() =>
|
||||||
filteredSections.value.some((section) => section.hits.length > 0)
|
filteredSections.value.some((section) => section.hits.length > 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
const navigateToQuery = (value: string) => {
|
|
||||||
const trimmed = value.trim();
|
|
||||||
if (!trimmed) return;
|
|
||||||
navigateTo({
|
|
||||||
path: localePath('/search'),
|
|
||||||
query: { query: trimmed },
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClear = () => {
|
|
||||||
keyword.value = '';
|
|
||||||
sections.value = [];
|
|
||||||
router.replace(localePath({ path: '/search' }));
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(activeTab, () => {
|
|
||||||
currentPage.value = 1; // 重置页码
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.query.query,
|
() => route.query.query,
|
||||||
async (newQuery) => {
|
async (newQuery) => {
|
||||||
@ -216,42 +108,6 @@
|
|||||||
min-height: 70vh;
|
min-height: 70vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
font-size: 2.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input {
|
|
||||||
flex: 1;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-button {
|
|
||||||
height: 50px;
|
|
||||||
width: 100px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-meta {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-state {
|
.search-state {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user