- 组件改造:EmptyState、ExportPanel、SearchConditionItem、SearchHistoryPanel、 SearchResultCard、SearchResultList、Select、SortSwitcher、TemplatePanel、 BottomNavigation、SearchConditionPanel - 替换硬编码中文为 $t() 调用和 computed 响应式翻译 - 新增翻译键:search(搜索历史/模板/条件)、export(导出选项)、 common.pleaseSelect 等 - 同步更新 zh-CN、en、zh-TW、ja、ko、es、fr、de、pt 共9种语言文件 - 重构 errorHandler:从 API 错误处理改为本地应用错误处理 - 移除 search.ts 中未使用的 SearchService 接口
242 lines
5.3 KiB
Vue
242 lines
5.3 KiB
Vue
<template>
|
|
<Card class="template-panel">
|
|
<view class="panel-header">
|
|
<Typography variant="h4" weight="semibold">{{ $t('search.searchTemplates') }}</Typography>
|
|
<Button size="small" type="text" @click="handleRefresh">
|
|
<Icon name="refresh" size="16rpx" color="rgba(113, 113, 122, 255)" />
|
|
</Button>
|
|
</view>
|
|
|
|
<view class="search-bar">
|
|
<view class="search-input">
|
|
<Icon name="search" size="16rpx" color="rgba(113, 113, 122, 255)" />
|
|
<input
|
|
v-model="searchKeyword"
|
|
:placeholder="$t('search.searchTemplates')"
|
|
class="input-field"
|
|
/>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="category-tabs">
|
|
<view
|
|
v-for="category in categories"
|
|
:key="category"
|
|
class="category-tab"
|
|
:class="{ 'category-tab--active': selectedCategory === category }"
|
|
@click="selectCategory(category)"
|
|
>
|
|
<Typography variant="body" class="category-label">{{ category }}</Typography>
|
|
</view>
|
|
</view>
|
|
|
|
<view v-if="filteredTemplates.length === 0" class="empty-templates">
|
|
<EmptyState
|
|
icon="empty"
|
|
:title="$t('search.noTemplatesTitle')"
|
|
:description="$t('search.noTemplatesDesc')"
|
|
size="small"
|
|
/>
|
|
</view>
|
|
|
|
<view v-else class="template-list">
|
|
<view
|
|
v-for="template in filteredTemplates"
|
|
:key="template.id"
|
|
class="template-item"
|
|
@click="handleSelectTemplate(template)"
|
|
>
|
|
<view class="template-content">
|
|
<Typography variant="body" weight="semibold" class="template-name">
|
|
{{ template.name }}
|
|
</Typography>
|
|
<Typography variant="caption" class="template-description">
|
|
{{ template.description }}
|
|
</Typography>
|
|
<Typography variant="caption" class="template-category">
|
|
{{ template.category }}
|
|
</Typography>
|
|
</view>
|
|
<Icon name="right" size="16rpx" color="rgba(113, 113, 122, 255)" />
|
|
</view>
|
|
</view>
|
|
</Card>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import type { SearchRequest, SearchTemplate } from '../../types/search'
|
|
import templateService from '../../services/templateService'
|
|
import Card from '../Card/Card.vue'
|
|
import Button from '../Button/Button.vue'
|
|
import Icon from '../Icon/Icon.vue'
|
|
import Typography from '../Typography/Typography.vue'
|
|
import EmptyState from '../EmptyState/index.vue'
|
|
|
|
const { t } = useI18n()
|
|
|
|
const emit = defineEmits<{
|
|
select: [condition: SearchRequest]
|
|
}>()
|
|
|
|
const searchKeyword = ref('')
|
|
const selectedCategory = ref('__all__')
|
|
const templates = ref<SearchTemplate[]>([])
|
|
|
|
const categories = computed(() => {
|
|
const allCategories = templateService.getCategories()
|
|
return [t('search.allCategories'), ...allCategories]
|
|
})
|
|
|
|
const filteredTemplates = computed(() => {
|
|
let result = templates.value
|
|
|
|
if (selectedCategory.value !== '__all__') {
|
|
result = templateService.getTemplatesByCategory(selectedCategory.value)
|
|
}
|
|
|
|
if (searchKeyword.value.trim()) {
|
|
result = templateService.searchTemplates(searchKeyword.value.trim())
|
|
}
|
|
|
|
return result
|
|
})
|
|
|
|
onMounted(() => {
|
|
loadTemplates()
|
|
})
|
|
|
|
function loadTemplates() {
|
|
templates.value = templateService.getSearchTemplates()
|
|
}
|
|
|
|
function handleRefresh() {
|
|
loadTemplates()
|
|
}
|
|
|
|
function selectCategory(category: string) {
|
|
selectedCategory.value = category
|
|
}
|
|
|
|
function handleSelectTemplate(template: SearchTemplate) {
|
|
emit('select', template.condition)
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.template-panel {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.panel-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.search-bar {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.search-input {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 12px 16px;
|
|
background: rgba(244, 244, 245, 255);
|
|
border-radius: var(--radius-md);
|
|
}
|
|
|
|
.input-field {
|
|
flex: 1;
|
|
border: none;
|
|
background: transparent;
|
|
font-size: 14px;
|
|
color: var(--color-text);
|
|
outline: none;
|
|
|
|
&::placeholder {
|
|
color: var(--color-text-secondary);
|
|
}
|
|
}
|
|
|
|
.category-tabs {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.category-tab {
|
|
padding: 6px 12px;
|
|
background: rgba(244, 244, 245, 255);
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
transition: all var(--transition-fast);
|
|
|
|
&:active {
|
|
background: rgba(230, 225, 220, 255);
|
|
}
|
|
}
|
|
|
|
.category-tab--active {
|
|
background: var(--color-primary);
|
|
}
|
|
|
|
.category-label {
|
|
color: var(--color-text);
|
|
font-size: 13px;
|
|
|
|
.category-tab--active & {
|
|
color: #FFFFFF;
|
|
}
|
|
}
|
|
|
|
.empty-templates {
|
|
padding: 24px 0;
|
|
}
|
|
|
|
.template-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.template-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 16px;
|
|
background: rgba(244, 244, 245, 255);
|
|
border-radius: var(--radius-md);
|
|
cursor: pointer;
|
|
transition: all var(--transition-fast);
|
|
|
|
&:active {
|
|
background: rgba(230, 225, 220, 255);
|
|
}
|
|
}
|
|
|
|
.template-content {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.template-name {
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.template-description {
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.template-category {
|
|
color: var(--color-primary);
|
|
font-weight: 500;
|
|
}
|
|
</style>
|