refactor: 项目结构重构 #48

Manually merged
remilia merged 10 commits from refactor/components into master 2025-10-29 17:56:08 +08:00
41 changed files with 1293 additions and 1126 deletions
Showing only changes of commit 84b99deef6 - Show all commits

View 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>

View 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>

View File

@ -1,80 +1,10 @@
<template>
<div class="search-page">
<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>
<search-header v-model="keyword" />
<div v-if="loading" class="search-state">
<el-skeleton :rows="4" animated />
</div>
<div v-else-if="hasResults" class="search-results">
<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>
<search-tabs v-else-if="hasResults" :search-items="searchItems" />
<div v-else class="search-state">
<el-empty
:description="
@ -88,8 +18,6 @@
</template>
<script setup lang="ts">
import { Search } from '@element-plus/icons-vue';
// i18n相关
const { t } = useI18n();
const { getDirectusLocale } = useLocalizations();
@ -97,8 +25,6 @@
// 路由相关
const route = useRoute();
const router = useRouter();
const localePath = useLocalePath();
// 搜索相关
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(() =>
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(
() => route.query.query,
async (newQuery) => {
@ -216,42 +108,6 @@
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 {
display: flex;
justify-content: center;